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
This commit is contained in:
Tyler Biethman
2022-05-19 21:17:33 -05:00
committed by GitHub
parent d335f5cd8a
commit 87eec2b460
5 changed files with 94 additions and 2 deletions

View File

@@ -56,5 +56,36 @@ for (const project of WEBPACK_REACT) {
cy.get('.passed > .num').should('contain', 1)
})
// https://cypress-io.atlassian.net/browse/UNIFY-1697
it('filters missing spec files from loader during pre-compilation', () => {
cy.visitApp()
// 1. assert spec executes successfully
cy.contains('App.cy.jsx').click()
cy.get('.passed > .num').should('contain', 1)
// 2. remove file from file system
cy.withCtx(async (ctx) => {
await ctx.actions.file.removeFileInProject(`src/App.cy.jsx`)
})
// 3. assert redirect back to #/specs with alert presented
cy.contains('[data-cy="alert"]', 'Spec not found')
// 4. recreate spec, with same name as removed spec
cy.findByTestId('new-spec-button').click()
cy.findByRole('dialog').within(() => {
cy.get('input').clear().type('src/App.cy.jsx')
cy.contains('button', 'Create Spec').click()
})
cy.findByRole('dialog').within(() => {
cy.contains('button', 'Okay, run the spec').click()
})
// 5. assert recreated spec executes successfully
cy.get('.passed > .num').should('contain', 1)
})
})
}

View File

@@ -18,6 +18,7 @@
"test-unit": "mocha -r ts-node/register/transpile-only --config ./test/.mocharc.js"
},
"dependencies": {
"fs-extra": "9.1.0",
"html-webpack-plugin-4": "npm:html-webpack-plugin@^4",
"html-webpack-plugin-5": "npm:html-webpack-plugin@^5",
"speed-measure-webpack-plugin": "1.4.2",

View File

@@ -2,7 +2,7 @@ import type { Compiler, Compilation } from 'webpack'
import type webpack from 'webpack'
import type { EventEmitter } from 'events'
import _ from 'lodash'
import fs, { PathLike } from 'fs'
import fs, { PathLike } from 'fs-extra'
import path from 'path'
type UtimesSync = (path: PathLike, atime: string | number | Date, mtime: string | number | Date) => void
@@ -68,6 +68,31 @@ export class CypressCTWebpackPlugin {
}
};
private beforeCompile = async (compilationParams: object, callback: Function) => {
if (!this.compilation) {
callback()
return
}
// Ensure we don't try to load files that have been removed from the file system
// but have not yet been detected by the onSpecsChange handler
const foundFiles = (await Promise.all(this.files.map(async (file) => {
try {
const exists = await fs.pathExists(file.absolute)
return exists ? file : null
} catch (e) {
return null
}
})))
this.files = foundFiles.filter((file) => file !== null) as Cypress.Spec[]
callback()
}
/*
* After compiling, we check for errors and inform the server of them.
*/
@@ -96,7 +121,7 @@ export class CypressCTWebpackPlugin {
}
}
// After emitting assets, we tell the server complitation was successful
// After emitting assets, we tell the server compilation was successful
// so it can trigger a reload the AUT iframe.
private afterEmit = () => {
if (!this.compilation?.getStats().hasErrors()) {
@@ -152,6 +177,7 @@ export class CypressCTWebpackPlugin {
const _compiler = compiler as Compiler
this.devServerEvents.on('dev-server:specs:changed', this.onSpecsChange)
_compiler.hooks.beforeCompile.tapAsync('CypressCTPlugin', this.beforeCompile)
_compiler.hooks.afterCompile.tap('CypressCTPlugin', this.afterCompile)
_compiler.hooks.afterEmit.tap('CypressCTPlugin', this.afterEmit)
_compiler.hooks.compilation.tap('CypressCTPlugin', (compilation) => this.addCompilationHooks(compilation as Webpack45Compilation))

View File

@@ -66,6 +66,8 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise<Cyp
process.exit(1)
}
}
devServerConfig.devServerEvents.emit('dev-server:compile:done')
})
if (result.version === 3) {

View File

@@ -215,6 +215,38 @@ describe('#devServer', () => {
})
})
it('does not inject files into loader that do not exist at compile time', async () => {
const devServerEvents = new EventEmitter()
const { close } = await devServer({
webpackConfig,
cypressConfig,
specs: [...createSpecs('foo.spec.js'), ...createSpecs('does_not_exist.spec.js')],
devServerEvents,
})
let compileErrorOccurred
devServerEvents.on('dev-server:compile:error', () => {
compileErrorOccurred = true
})
await once(devServerEvents, 'dev-server:compile:done')
// An error event should not have been emitted, as we should have
// filtered any missing specs out of the set provided to the loader.
expect(compileErrorOccurred).to.not.be.true
await new Promise<void>((resolve, reject) => {
close((err) => {
if (err) {
return reject(err)
}
resolve()
})
})
})
it('touches browser.js when a spec file is added and recompile', async function () {
const devServerEvents = new EventEmitter()
const { close } = await devServer({