Skip to content

Commit 53eef4f

Browse files
authored
fix: angular and nuxt ct tests now fail on uncaught exceptions (#24122)
1 parent d0f13e9 commit 53eef4f

File tree

4 files changed

+76
-14
lines changed

4 files changed

+76
-14
lines changed

npm/angular/src/mount.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ window.Mocha['__zone_patch__'] = false
88
import 'zone.js/testing'
99

1010
import { CommonModule } from '@angular/common'
11-
import { Component, EventEmitter, SimpleChange, SimpleChanges, Type } from '@angular/core'
11+
import { Component, ErrorHandler, EventEmitter, Injectable, SimpleChange, SimpleChanges, Type } from '@angular/core'
1212
import {
1313
ComponentFixture,
1414
getTestBed,
@@ -99,6 +99,13 @@ export type MountResponse<T> = {
9999
// so we'll patch here pending a fix in that library
100100
globalThis.it.skip = globalThis.xit
101101

102+
@Injectable()
103+
class CypressAngularErrorHandler implements ErrorHandler {
104+
handleError (error: Error): void {
105+
throw error
106+
}
107+
}
108+
102109
/**
103110
* Bootstraps the TestModuleMetaData passed to the TestBed
104111
*
@@ -120,6 +127,17 @@ function bootstrapModule<T> (
120127
testModuleMetaData.imports = []
121128
}
122129

130+
if (!testModuleMetaData.providers) {
131+
testModuleMetaData.providers = []
132+
}
133+
134+
// Replace default error handler since it will swallow uncaught exceptions.
135+
// We want these to be uncaught so Cypress catches it and fails the test
136+
testModuleMetaData.providers.push({
137+
provide: ErrorHandler,
138+
useClass: CypressAngularErrorHandler,
139+
})
140+
123141
// check if the component is a standalone component
124142
if ((component as any).ɵcmp.standalone) {
125143
testModuleMetaData.imports.push(component)

npm/vue2/src/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -273,11 +273,11 @@ declare global {
273273
* @see https://github.com/cypress-io/cypress/issues/7910
274274
*/
275275
function failTestOnVueError (err, vm, info) {
276-
console.error(`Vue error`)
277-
console.error(err)
278-
console.error('component:', vm)
279-
console.error('info:', info)
280-
window.top.onerror(err)
276+
// Vue 2 try catches the error-handler so push the error to be caught outside
277+
// of the handler.
278+
setTimeout(() => {
279+
throw err
280+
})
281281
}
282282

283283
function registerAutoDestroy ($destroy: () => void) {

packages/app/cypress/e2e/runner/ct-framework-errors.cy.ts

+51-7
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ function loadErrorSpec (options: Options): VerifyFunc {
4040
cy.get('.runnable-header', { log: false }).should('be.visible')
4141
// Extended timeout needed due to lengthy Angular bootstrap on Windows
4242
cy.contains('Your tests are loading...', { timeout: 60000, log: false }).should('not.exist')
43+
// Then ensure the tests have finished
44+
cy.get('[aria-label="Rerun all tests"]', { timeout: 30000 })
4345
cy.findByLabelText('Stats').within(() => {
44-
cy.get('.passed .num', { timeout: 30000 }).should('have.text', `${passCount}`)
45-
cy.get('.failed .num', { timeout: 30000 }).should('have.text', `${failCount}`)
46+
cy.get('.passed .num').should('have.text', `${passCount}`)
47+
cy.get('.failed .num').should('have.text', `${failCount}`)
4648
})
4749

4850
// Return scoped verify function with spec options baked in
@@ -285,19 +287,36 @@ describe('Nuxt', {
285287
projectName: 'nuxtjs-vue2-configured',
286288
configFile: 'cypress.config.js',
287289
filePath: 'components/Errors.cy.js',
288-
failCount: 3,
290+
failCount: 4,
289291
})
290292

291293
verify('error on mount', {
292294
fileName: 'Errors.vue',
293295
line: 19,
296+
uncaught: true,
297+
uncaughtMessage: 'mount error',
294298
message: [
295299
'mount error',
296300
],
297301
stackRegex: /Errors\.vue:19/,
298302
codeFrameText: 'Errors.vue',
299303
})
300304

305+
verify('sync error', {
306+
fileName: 'Errors.vue',
307+
line: 24,
308+
uncaught: true,
309+
uncaughtMessage: 'sync error',
310+
message: [
311+
'The following error originated from your application code',
312+
'sync error',
313+
],
314+
stackRegex: /Errors\.vue:24/,
315+
codeFrameText: 'Errors.vue',
316+
}).then(() => {
317+
verifyErrorOnlyCapturedOnce('Error: sync error')
318+
})
319+
301320
verify('async error', {
302321
fileName: 'Errors.vue',
303322
line: 28,
@@ -413,12 +432,37 @@ angularVersions.forEach((angularVersion) => {
413432
projectName: `angular-${angularVersion}`,
414433
configFile: 'cypress.config.ts',
415434
filePath: 'src/app/errors.cy.ts',
416-
failCount: 1,
435+
failCount: 3,
436+
passCount: 1,
437+
})
438+
439+
verify('sync error', {
440+
fileName: 'errors.ts',
441+
line: 14,
442+
column: 11,
443+
uncaught: true,
444+
uncaughtMessage: 'sync error',
445+
message: [
446+
'The following error originated from your application code',
447+
'sync error',
448+
],
449+
}).then(() => {
450+
verifyErrorOnlyCapturedOnce('Error: sync error')
417451
})
418452

419-
// Angular uses ZoneJS which encapsulates errors thrown by components
420-
// Thus, the mount, sync, and async error case errors will not propagate to Cypress
421-
// and thus won't fail the tests
453+
verify('async error', {
454+
fileName: 'errors.ts',
455+
line: 19,
456+
column: 13,
457+
uncaught: true,
458+
uncaughtMessage: 'async error',
459+
message: [
460+
'The following error originated from your application code',
461+
'async error',
462+
],
463+
}).then(() => {
464+
verifyErrorOnlyCapturedOnce('Error: async error')
465+
})
422466

423467
verify('command failure', {
424468
line: 21,

system-tests/project-fixtures/angular/src/app/components/errors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ export class ErrorsComponent {
1717
asyncError() {
1818
setTimeout(() => {
1919
throw new Error('async error')
20-
}, 50)
20+
})
2121
}
2222
}

0 commit comments

Comments
 (0)