Skip to content

Commit 87eec2b

Browse files
authored
fix: Ensuring spec file presence prior to webpack-dev-server compilation (#21550)
* fix: Ensuring spec file presence prior to webpack-dev-server compilation * Clean up test a bit * Async beforeCompile * Adding unit test for new behavior * Moving e2e test to react specs for better project coverage
1 parent d335f5c commit 87eec2b

File tree

5 files changed

+94
-2
lines changed

5 files changed

+94
-2
lines changed

npm/webpack-dev-server/cypress/e2e/react.cy.ts

+31
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,36 @@ for (const project of WEBPACK_REACT) {
5656

5757
cy.get('.passed > .num').should('contain', 1)
5858
})
59+
60+
// https://cypress-io.atlassian.net/browse/UNIFY-1697
61+
it('filters missing spec files from loader during pre-compilation', () => {
62+
cy.visitApp()
63+
64+
// 1. assert spec executes successfully
65+
cy.contains('App.cy.jsx').click()
66+
cy.get('.passed > .num').should('contain', 1)
67+
68+
// 2. remove file from file system
69+
cy.withCtx(async (ctx) => {
70+
await ctx.actions.file.removeFileInProject(`src/App.cy.jsx`)
71+
})
72+
73+
// 3. assert redirect back to #/specs with alert presented
74+
cy.contains('[data-cy="alert"]', 'Spec not found')
75+
76+
// 4. recreate spec, with same name as removed spec
77+
cy.findByTestId('new-spec-button').click()
78+
cy.findByRole('dialog').within(() => {
79+
cy.get('input').clear().type('src/App.cy.jsx')
80+
cy.contains('button', 'Create Spec').click()
81+
})
82+
83+
cy.findByRole('dialog').within(() => {
84+
cy.contains('button', 'Okay, run the spec').click()
85+
})
86+
87+
// 5. assert recreated spec executes successfully
88+
cy.get('.passed > .num').should('contain', 1)
89+
})
5990
})
6091
}

npm/webpack-dev-server/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"test-unit": "mocha -r ts-node/register/transpile-only --config ./test/.mocharc.js"
1919
},
2020
"dependencies": {
21+
"fs-extra": "9.1.0",
2122
"html-webpack-plugin-4": "npm:html-webpack-plugin@^4",
2223
"html-webpack-plugin-5": "npm:html-webpack-plugin@^5",
2324
"speed-measure-webpack-plugin": "1.4.2",

npm/webpack-dev-server/src/CypressCTWebpackPlugin.ts

+28-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Compiler, Compilation } from 'webpack'
22
import type webpack from 'webpack'
33
import type { EventEmitter } from 'events'
44
import _ from 'lodash'
5-
import fs, { PathLike } from 'fs'
5+
import fs, { PathLike } from 'fs-extra'
66
import path from 'path'
77

88
type UtimesSync = (path: PathLike, atime: string | number | Date, mtime: string | number | Date) => void
@@ -68,6 +68,31 @@ export class CypressCTWebpackPlugin {
6868
}
6969
};
7070

71+
private beforeCompile = async (compilationParams: object, callback: Function) => {
72+
if (!this.compilation) {
73+
callback()
74+
75+
return
76+
}
77+
78+
// Ensure we don't try to load files that have been removed from the file system
79+
// but have not yet been detected by the onSpecsChange handler
80+
81+
const foundFiles = (await Promise.all(this.files.map(async (file) => {
82+
try {
83+
const exists = await fs.pathExists(file.absolute)
84+
85+
return exists ? file : null
86+
} catch (e) {
87+
return null
88+
}
89+
})))
90+
91+
this.files = foundFiles.filter((file) => file !== null) as Cypress.Spec[]
92+
93+
callback()
94+
}
95+
7196
/*
7297
* After compiling, we check for errors and inform the server of them.
7398
*/
@@ -96,7 +121,7 @@ export class CypressCTWebpackPlugin {
96121
}
97122
}
98123

99-
// After emitting assets, we tell the server complitation was successful
124+
// After emitting assets, we tell the server compilation was successful
100125
// so it can trigger a reload the AUT iframe.
101126
private afterEmit = () => {
102127
if (!this.compilation?.getStats().hasErrors()) {
@@ -152,6 +177,7 @@ export class CypressCTWebpackPlugin {
152177
const _compiler = compiler as Compiler
153178

154179
this.devServerEvents.on('dev-server:specs:changed', this.onSpecsChange)
180+
_compiler.hooks.beforeCompile.tapAsync('CypressCTPlugin', this.beforeCompile)
155181
_compiler.hooks.afterCompile.tap('CypressCTPlugin', this.afterCompile)
156182
_compiler.hooks.afterEmit.tap('CypressCTPlugin', this.afterEmit)
157183
_compiler.hooks.compilation.tap('CypressCTPlugin', (compilation) => this.addCompilationHooks(compilation as Webpack45Compilation))

npm/webpack-dev-server/src/devServer.ts

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp
6666
process.exit(1)
6767
}
6868
}
69+
70+
devServerConfig.devServerEvents.emit('dev-server:compile:done')
6971
})
7072

7173
if (result.version === 3) {

npm/webpack-dev-server/test/devServer-e2e.spec.ts

+32
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,38 @@ describe('#devServer', () => {
215215
})
216216
})
217217

218+
it('does not inject files into loader that do not exist at compile time', async () => {
219+
const devServerEvents = new EventEmitter()
220+
const { close } = await devServer({
221+
webpackConfig,
222+
cypressConfig,
223+
specs: [...createSpecs('foo.spec.js'), ...createSpecs('does_not_exist.spec.js')],
224+
devServerEvents,
225+
})
226+
227+
let compileErrorOccurred
228+
229+
devServerEvents.on('dev-server:compile:error', () => {
230+
compileErrorOccurred = true
231+
})
232+
233+
await once(devServerEvents, 'dev-server:compile:done')
234+
235+
// An error event should not have been emitted, as we should have
236+
// filtered any missing specs out of the set provided to the loader.
237+
expect(compileErrorOccurred).to.not.be.true
238+
239+
await new Promise<void>((resolve, reject) => {
240+
close((err) => {
241+
if (err) {
242+
return reject(err)
243+
}
244+
245+
resolve()
246+
})
247+
})
248+
})
249+
218250
it('touches browser.js when a spec file is added and recompile', async function () {
219251
const devServerEvents = new EventEmitter()
220252
const { close } = await devServer({

0 commit comments

Comments
 (0)