Skip to content

Commit 76eb083

Browse files
authored
fix: ensure Cypress.stop fires all end events appropriately (#31830)
1 parent ec252f8 commit 76eb083

File tree

11 files changed

+638
-13
lines changed

11 files changed

+638
-13
lines changed

cli/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ _Released 6/17/2025 (PENDING)_
77

88
- Install Cypress `win32-x64` binary on Windows `win32-arm64` systems. Cypress runs in emulation. Addresses [#30252](https://github.com/cypress-io/cypress/issues/30252).
99

10+
**Bugfixes:**
11+
12+
- Fixed an issue when using `Cypress.stop()` where a run may be aborted prior to receiving the required runner events causing Test Replay to not be available. Addresses [#31781](https://github.com/cypress-io/cypress/issues/31781).
13+
1014
## 14.4.1
1115

1216
_Released 6/3/2025_

packages/driver/src/cypress/runner.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,24 @@ const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, get
556556

557557
testAfterRun(test, Cypress)
558558
await testAfterRunAsync(test, Cypress)
559+
560+
// if the user has stopped the run, we need to abort,
561+
// this needs to happen after the test:after:run events have fired
562+
// to ensure protocol can properly handle the abort
563+
if (_runner.stopped) {
564+
// abort the run
565+
_runner.abort()
566+
567+
// emit the final 'end' event
568+
// since our reporter depends on this event
569+
// and mocha may never fire this because our
570+
// runnable may never finish
571+
_runner.emit('end')
572+
573+
// remove all the listeners
574+
// so no more events fire
575+
_runner.removeAllListeners()
576+
}
559577
})]
560578

561579
return newArgs
@@ -1916,19 +1934,6 @@ export default {
19161934
}
19171935

19181936
_runner.stopped = true
1919-
1920-
// abort the run
1921-
_runner.abort()
1922-
1923-
// emit the final 'end' event
1924-
// since our reporter depends on this event
1925-
// and mocha may never fire this because our
1926-
// runnable may never finish
1927-
_runner.emit('end')
1928-
1929-
// remove all the listeners
1930-
// so no more events fire
1931-
_runner.removeAllListeners()
19321937
},
19331938

19341939
getDisplayPropsForLog: LogUtils.getDisplayProps,

system-tests/__snapshots__/cypress-stop.spec.ts.js

Lines changed: 271 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
e2e: {
3+
setupNodeEvents (on, config) {},
4+
},
5+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
describe('Cypress.stop() in after', () => {
2+
after(() => {
3+
console.log('after 1')
4+
})
5+
6+
after(() => {
7+
Cypress.stop()
8+
console.log('after 2')
9+
})
10+
11+
after(() => {
12+
console.log('after 3')
13+
})
14+
15+
it('should run this test', () => {
16+
cy.url().should('equal', 'about:blank')
17+
})
18+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
describe('Cypress.stop() in afterEach', () => {
2+
afterEach(() => {
3+
console.log('afterEach 1')
4+
})
5+
6+
afterEach(() => {
7+
Cypress.stop()
8+
console.log('afterEach 2')
9+
})
10+
11+
afterEach(() => {
12+
console.log('afterEach 3')
13+
})
14+
15+
it('should run this test', () => {
16+
cy.url().should('equal', 'about:blank')
17+
})
18+
19+
it('should not run this test', () => {
20+
throw new Error('This test should not run')
21+
})
22+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
describe('Cypress.stop() in before', () => {
2+
before(() => {
3+
console.log('before 1')
4+
})
5+
6+
before(() => {
7+
Cypress.stop()
8+
console.log('before 2')
9+
})
10+
11+
before(() => {
12+
console.log('before 3')
13+
})
14+
15+
it('should not run this test', () => {
16+
throw new Error('This test should not run')
17+
})
18+
19+
it('should also not run this test', () => {
20+
throw new Error('This test should not run')
21+
})
22+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
describe('Cypress.stop() in beforeEach', () => {
2+
beforeEach(() => {
3+
console.log('beforeEach 1')
4+
})
5+
6+
beforeEach(() => {
7+
Cypress.stop()
8+
console.log('beforeEach 2')
9+
})
10+
11+
beforeEach(() => {
12+
console.log('beforeEach 3')
13+
})
14+
15+
it('should not run this test', () => {
16+
throw new Error('This test should not run')
17+
})
18+
19+
it('should also not run this test', () => {
20+
throw new Error('This test should not run')
21+
})
22+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
describe('Cypress.stop() in test', () => {
2+
it('should run this test', () => {
3+
console.log('test 1')
4+
})
5+
6+
it('should stop during test execution', () => {
7+
return Cypress.stop()
8+
9+
// eslint-disable-next-line no-unreachable
10+
console.log('test 2')
11+
throw new Error('This code should not run')
12+
})
13+
14+
it('should not run this test', () => {
15+
console.log('test 3')
16+
throw new Error('This test should not run')
17+
})
18+
})
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
before(() => {
2+
console.log('global before')
3+
})
4+
5+
beforeEach(() => {
6+
console.log('global beforeEach')
7+
})
8+
9+
afterEach(() => {
10+
console.log('global afterEach')
11+
})
12+
13+
after(() => {
14+
console.log('global after')
15+
})
16+
17+
Cypress.on('test:before:run:async', async () => {
18+
await new Promise((resolve) => setTimeout(resolve, 1000))
19+
console.log('test:before:run:async')
20+
})
21+
22+
Cypress.on('test:before:run', () => {
23+
console.log('test:before:run')
24+
})
25+
26+
Cypress.on('test:before:after:run:async', async () => {
27+
await new Promise((resolve) => setTimeout(resolve, 1000))
28+
console.log('test:before:after:run:async')
29+
})
30+
31+
Cypress.on('test:after:run', () => {
32+
console.log('test:after:run')
33+
})
34+
35+
Cypress.on('test:after:run:async', async () => {
36+
await new Promise((resolve) => setTimeout(resolve, 1000))
37+
console.log('test:after:run:async')
38+
})

0 commit comments

Comments
 (0)