Skip to content

fix: ensure Cypress.stop fires all end events appropriately #31830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ _Released 6/17/2025 (PENDING)_

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

**Bugfixes:**

- 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).

## 14.4.1

_Released 6/3/2025_
Expand Down
31 changes: 18 additions & 13 deletions packages/driver/src/cypress/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,24 @@ const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, get

testAfterRun(test, Cypress)
await testAfterRunAsync(test, Cypress)

// if the user has stopped the run, we need to abort,
// this needs to happen after the test:after:run events have fired
// to ensure protocol can properly handle the abort
if (_runner.stopped) {
// abort the run
_runner.abort()

// emit the final 'end' event
// since our reporter depends on this event
// and mocha may never fire this because our
// runnable may never finish
_runner.emit('end')

// remove all the listeners
// so no more events fire
_runner.removeAllListeners()
}
})]

return newArgs
Expand Down Expand Up @@ -1916,19 +1934,6 @@ export default {
}

_runner.stopped = true

// abort the run
_runner.abort()

// emit the final 'end' event
// since our reporter depends on this event
// and mocha may never fire this because our
// runnable may never finish
_runner.emit('end')

// remove all the listeners
// so no more events fire
_runner.removeAllListeners()
},

getDisplayPropsForLog: LogUtils.getDisplayProps,
Expand Down
271 changes: 271 additions & 0 deletions system-tests/__snapshots__/cypress-stop.spec.ts.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions system-tests/projects/cypress-stop/cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
e2e: {
setupNodeEvents (on, config) {},
},
}
18 changes: 18 additions & 0 deletions system-tests/projects/cypress-stop/cypress/e2e/after.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
describe('Cypress.stop() in after', () => {
after(() => {
console.log('after 1')
})

after(() => {
Cypress.stop()
console.log('after 2')
})

after(() => {
console.log('after 3')
})

it('should run this test', () => {
cy.url().should('equal', 'about:blank')
})
})
22 changes: 22 additions & 0 deletions system-tests/projects/cypress-stop/cypress/e2e/afterEach.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
describe('Cypress.stop() in afterEach', () => {
afterEach(() => {
console.log('afterEach 1')
})

afterEach(() => {
Cypress.stop()
console.log('afterEach 2')
})

afterEach(() => {
console.log('afterEach 3')
})

it('should run this test', () => {
cy.url().should('equal', 'about:blank')
})

it('should not run this test', () => {
throw new Error('This test should not run')
})
})
22 changes: 22 additions & 0 deletions system-tests/projects/cypress-stop/cypress/e2e/before.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
describe('Cypress.stop() in before', () => {
before(() => {
console.log('before 1')
})

before(() => {
Cypress.stop()
console.log('before 2')
})

before(() => {
console.log('before 3')
})

it('should not run this test', () => {
throw new Error('This test should not run')
})

it('should also not run this test', () => {
throw new Error('This test should not run')
})
})
22 changes: 22 additions & 0 deletions system-tests/projects/cypress-stop/cypress/e2e/beforeEach.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
describe('Cypress.stop() in beforeEach', () => {
beforeEach(() => {
console.log('beforeEach 1')
})

beforeEach(() => {
Cypress.stop()
console.log('beforeEach 2')
})

beforeEach(() => {
console.log('beforeEach 3')
})

it('should not run this test', () => {
throw new Error('This test should not run')
})

it('should also not run this test', () => {
throw new Error('This test should not run')
})
})
18 changes: 18 additions & 0 deletions system-tests/projects/cypress-stop/cypress/e2e/test.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
describe('Cypress.stop() in test', () => {
it('should run this test', () => {
console.log('test 1')
})

it('should stop during test execution', () => {
return Cypress.stop()

// eslint-disable-next-line no-unreachable
console.log('test 2')
throw new Error('This code should not run')
})

it('should not run this test', () => {
console.log('test 3')
throw new Error('This test should not run')
})
})
38 changes: 38 additions & 0 deletions system-tests/projects/cypress-stop/cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
before(() => {
console.log('global before')
})

beforeEach(() => {
console.log('global beforeEach')
})

afterEach(() => {
console.log('global afterEach')
})

after(() => {
console.log('global after')
})

Cypress.on('test:before:run:async', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log('test:before:run:async')
})

Cypress.on('test:before:run', () => {
console.log('test:before:run')
})

Cypress.on('test:before:after:run:async', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log('test:before:after:run:async')
})

Cypress.on('test:after:run', () => {
console.log('test:after:run')
})

Cypress.on('test:after:run:async', async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log('test:after:run:async')
})
Loading
Loading