mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-24 08:50:15 -06:00
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:
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user