Skip to content

Commit 81b6a86

Browse files
fix: ensure that current retry is maintained properly when tests fail prior to top changing (#32888)
* fix: ensure that current retry is maintained properly when tests fail prior to top changing * add protocol work * Update packages/driver/src/cypress/runner.ts * Update packages/server/lib/socket-base.ts * Add changelog entry for version 15.6.1 Added changelog entry for version 15.6.1 with a bugfix. * Update packages/server/test/unit/socket_spec.js Co-authored-by: Bill Glesias <[email protected]> --------- Co-authored-by: Bill Glesias <[email protected]>
1 parent f4e9ead commit 81b6a86

File tree

13 files changed

+175
-11
lines changed

13 files changed

+175
-11
lines changed

cli/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
_Released 11/18/2025 (PENDING)_
55

6+
**Bugfixes:**
7+
8+
- Fixed an issue where top changes on test retries could cause attempt numbers to show up more than one time in the reporter and cause attempts to be lost in Test Replay. Addressed in [#32888](https://github.com/cypress-io/cypress/pull/32888).
9+
610
**Misc:**
711

812
- The keyboard shortcuts modal now displays the keyboard shortcut for saving Studio changes - `` + `s` for Mac or `Ctrl` + `s` for Windows/Linux. Addressed [#32862](https://github.com/cypress-io/cypress/issues/32862). Addressed in [#32864](https://github.com/cypress-io/cypress/pull/32864).

packages/app/src/runner/event-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ export class EventManager {
554554
// if we have a currentId it means
555555
// we need to tell the Cypress to skip
556556
// ahead to that test
557-
Cypress.runner.resumeAtTest(runState.currentId, runState.emissions)
557+
Cypress.runner.resumeAtTest(runState.currentId, runState.currentRetry, runState.emissions)
558558
}
559559

560560
return run()

packages/driver/src/cypress.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ class $Cypress {
861861
const tests = this.runner.getTestsState(testId)
862862
let runState: RunState = {
863863
currentId: testId,
864+
currentRetry: this.currentRetry,
864865
tests,
865866
startTime: this.runner.getStartTime(),
866867
emissions: this.runner.getEmissions(),

packages/driver/src/cypress/runner.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ export default {
19801980
return
19811981
},
19821982

1983-
resumeAtTest (id, emissions: Emissions = {
1983+
resumeAtTest (id: string, currentRetry: number, emissions: Emissions = {
19841984
started: {},
19851985
ended: {},
19861986
}) {
@@ -1993,6 +1993,9 @@ export default {
19931993
test._ALREADY_RAN = true
19941994
test.pending = true
19951995
} else {
1996+
// set the current retry to the retry that we are resuming at
1997+
test._currentRetry = currentRetry ?? 0
1998+
19961999
// bail so we can stop now
19972000
return
19982001
}

packages/server/lib/cloud/protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ export class ProtocolManager implements ProtocolManagerShape {
259259
this.invokeSync('pageLoading', { isEssential: false }, input)
260260
}
261261

262-
resetTest (testId: string): void {
263-
this.invokeSync('resetTest', { isEssential: false }, testId)
262+
resetTest (testId: string, currentRetry?: number): void {
263+
this.invokeSync('resetTest', { isEssential: false }, testId, currentRetry)
264264
}
265265

266266
responseEndedWithEmptyBody (options: ResponseEndedWithEmptyBodyOptions): void {

packages/server/lib/socket-base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,9 @@ export class SocketBase {
617617
// the test state on the protocol manager and prompt manager
618618
if (s.currentId) {
619619
const testId = s.currentId
620+
const currentRetry = s.currentRetry ?? undefined
620621

621-
this._protocolManager?.resetTest(testId)
622+
this._protocolManager?.resetTest(testId, currentRetry)
622623

623624
try {
624625
const cyPrompt = await getCtx().coreData.cyPromptLifecycleManager?.getCyPrompt()

packages/server/test/unit/cloud/protocol_spec.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,27 @@ describe('lib/cloud/protocol', () => {
310310
expect(protocol.pageLoading).to.be.calledWith(input)
311311
})
312312

313-
it('should be able to reset the test', () => {
314-
sinon.stub(protocol, 'resetTest')
313+
describe('.resetTest', () => {
314+
it('should be able to reset the test with no current retry', () => {
315+
sinon.stub(protocol, 'resetTest')
315316

316-
const testId = 'r3'
317+
const testId = 'r3'
317318

318-
protocolManager.resetTest(testId)
319+
protocolManager.resetTest(testId)
319320

320-
expect(protocol.resetTest).to.be.calledWith(testId)
321+
expect(protocol.resetTest).to.be.calledWith(testId)
322+
})
323+
324+
it('should be able to reset the test with a current retry', () => {
325+
sinon.stub(protocol, 'resetTest')
326+
327+
const testId = 'r3'
328+
const currentRetry = 1
329+
330+
protocolManager.resetTest(testId, currentRetry)
331+
332+
expect(protocol.resetTest).to.be.calledWith(testId, currentRetry)
333+
})
321334
})
322335

323336
describe('.reset', () => {

packages/server/test/unit/socket_spec.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,40 @@ describe('lib/socket', () => {
783783
})
784784
})
785785
})
786+
787+
describe('on(get:cached:test:state)', () => {
788+
it('returns cached test state', async function () {
789+
await new Promise((resolve) => {
790+
this.client.emit('backend:request', 'preserve:run:state', {
791+
currentId: 'test',
792+
currentRetry: 0,
793+
}, resolve)
794+
})
795+
796+
const mockProtocolManager = {
797+
resetTest: sinon.stub(),
798+
}
799+
800+
this.socket['_protocolManager'] = mockProtocolManager
801+
802+
await new Promise((resolve) => {
803+
this.client.emit('get:cached:test:state', (runState, testState) => {
804+
expect(runState).to.deep.eq({
805+
currentId: 'test',
806+
currentRetry: 0,
807+
})
808+
809+
expect(testState).to.deep.eq({
810+
activeSessions: {},
811+
})
812+
813+
expect(mockProtocolManager.resetTest).to.be.calledWith('test', 0)
814+
815+
resolve()
816+
})
817+
})
818+
})
819+
})
786820
})
787821

788822
context('unit', () => {

packages/types/src/driver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ReporterRunState } from './reporter'
33
interface MochaRunnerState {
44
startTime?: number
55
currentId?: string | null
6+
currentRetry?: number | null
67
emissions?: Emissions
78
tests?: Record<string, Cypress.ObjectLike>
89
passed?: number

packages/types/src/protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export interface AppCaptureProtocolCommon {
3030
afterTest(test: Record<string, any>): Promise<void>
3131
afterSpec (): Promise<{ durations: AfterSpecDurations } | undefined>
3232
pageLoading (input: any): void
33-
resetTest (testId: string): void
33+
resetTest (testId: string, currentRetry?: number): void
3434
responseEndedWithEmptyBody: (options: ResponseEndedWithEmptyBodyOptions) => void
3535
responseStreamReceived (options: ResponseStreamOptions): Readable | undefined
3636
responseStreamTimedOut (options: ResponseStreamTimedOutOptions): void

0 commit comments

Comments
 (0)