diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 83ac1ec094a..64c396650a3 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -20,6 +20,7 @@ _Released 10/7/2025 (PENDING)_ - Added a dropdown menu in the Command Log that includes actions like Open in IDE and Add New Test in Studio, along with test preferences such as Auto-Scroll. Addresses [#32556](https://github.com/cypress-io/cypress/issues/32556) and [#32558](https://github.com/cypress-io/cypress/issues/32558). Addressed in [#32611](https://github.com/cypress-io/cypress/pull/32611). - Updated the Studio test editing header to include a Back button. This change ensures the Specs button remains functional for expanding or collapsing the specs panel. Addresses [#32556](https://github.com/cypress-io/cypress/issues/32556) and [#32558](https://github.com/cypress-io/cypress/issues/32558). Addressed in [#32611](https://github.com/cypress-io/cypress/pull/32611). - Fixed the Studio panel resizing when dragging. Addressed in [#32584](https://github.com/cypress-io/cypress/pull/32584). +- The Next button now maintains consistent visibility during stepping sessions when using `cy.pause`, staying visible but disabled when no immediate next command is available, providing clear visual feedback to users about stepping state. Addresses [#32476](https://github.com/cypress-io/cypress/issues/32476). Addressed in [#32536](https://github.com/cypress-io/cypress/pull/32536). **Dependency Updates:** diff --git a/packages/reporter/cypress/e2e/header.cy.ts b/packages/reporter/cypress/e2e/header.cy.ts index fa1ecaa9d4b..fd4f05fde47 100755 --- a/packages/reporter/cypress/e2e/header.cy.ts +++ b/packages/reporter/cypress/e2e/header.cy.ts @@ -200,12 +200,79 @@ describe('header', () => { cy.get('.restart').should('not.exist') }) - it('does not display next button', () => { + it('does not display the next button', () => { cy.get('.next').should('not.exist') }) }) }) + describe('when running after resume', () => { + beforeEach(() => { + runner.emit('run:start') + runner.emit('paused') + cy.get('.play').click() + }) + + it('displays next button as disabled when running after resume', () => { + cy.get('.next').should('be.visible').and('be.disabled') + }) + + it('shows "Step (not available)" tooltip when running after resume', () => { + cy.get('.next').trigger('mouseover', { force: true }) + cy.get('.cy-tooltip').should('have.text', 'Step (not available)') + }) + + it('does not emit runner:next when disabled button is clicked', () => { + cy.spy(runner, 'emit') + cy.get('.next').click({ force: true }) + cy.wrap(runner.emit).should('not.be.calledWith', 'runner:next') + }) + + it('displays stop button when running after resume', () => { + cy.get('.stop').should('be.visible') + }) + }) + + describe('when paused without next command', () => { + beforeEach(() => { + runner.emit('paused') + }) + + it('displays play button', () => { + cy.get('.play').should('be.visible') + }) + + it('displays tooltip for play button', () => { + cy.get('.play').trigger('mouseover') + cy.get('.cy-tooltip').should('have.text', 'Resume C') + }) + + it('emits runner:resume when play button is clicked', () => { + cy.spy(runner, 'emit') + cy.get('.play').click() + cy.wrap(runner.emit).should('be.calledWith', 'runner:resume') + }) + + it('displays next button', () => { + cy.get('.next').should('be.visible').and('be.disabled') + }) + + it('shows "Step (not available)" tooltip when next button is disabled', () => { + cy.get('.next').trigger('mouseover', { force: true }) + cy.get('.cy-tooltip').should('have.text', 'Step (not available)') + }) + + it('does not emit runner:next when disabled next button is clicked', () => { + cy.spy(runner, 'emit') + cy.get('.next').click({ force: true }) + cy.wrap(runner.emit).should('not.be.calledWith', 'runner:next') + }) + + it('does not display stop button', () => { + cy.get('.stop').should('not.exist') + }) + }) + describe('when paused with next command', () => { beforeEach(() => { runner.emit('paused', 'find') diff --git a/packages/reporter/src/header/controls.tsx b/packages/reporter/src/header/controls.tsx index a71f4f3a14d..462eb6a0299 100755 --- a/packages/reporter/src/header/controls.tsx +++ b/packages/reporter/src/header/controls.tsx @@ -53,11 +53,26 @@ const Controls: React.FC = observer(({ events = defaultEvents, appState } )} - {!!appState.nextCommandName && ( - Next [N]:{appState.nextCommandName}

} className='cy-tooltip'> + {(appState.isPaused || (appState.isRunning && appState.hasBeenPaused)) && ( + Next [N]:{appState.nextCommandName}

:

Step (not available)

} + className='cy-tooltip' + >
-
diff --git a/packages/reporter/src/lib/app-state.ts b/packages/reporter/src/lib/app-state.ts index 95a02304bae..7dcf2631df8 100644 --- a/packages/reporter/src/lib/app-state.ts +++ b/packages/reporter/src/lib/app-state.ts @@ -8,6 +8,7 @@ interface DefaultAppState { pinnedSnapshotId: number | string | null studioActive: boolean studioSingleTestActive: boolean + hasBeenPaused: boolean } // these are used for the `reset` method @@ -19,6 +20,7 @@ const defaults: DefaultAppState = { pinnedSnapshotId: null, studioActive: false, studioSingleTestActive: false, + hasBeenPaused: false, } class AppState { @@ -32,6 +34,7 @@ class AppState { studioActive = defaults.studioActive studioSingleTestActive = defaults.studioSingleTestActive isStopped = false + hasBeenPaused = defaults.hasBeenPaused _resetAutoScrollingEnabledTo = true; [key: string]: any @@ -46,6 +49,7 @@ class AppState { pinnedSnapshotId: observable, studioActive: observable, studioSingleTestActive: observable, + hasBeenPaused: observable, }) } @@ -57,6 +61,7 @@ class AppState { pause (nextCommandName?: string) { this.isPaused = true this.nextCommandName = nextCommandName + this.hasBeenPaused = true } resume () { diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts index b0b60b64df8..c11c0acc7fb 100644 --- a/packages/reporter/src/lib/events.ts +++ b/packages/reporter/src/lib/events.ts @@ -79,6 +79,7 @@ const events: Events = { runner.on('run:start', action('run:start', () => { if (runnablesStore.hasTests) { appState.startRunning() + appState.hasBeenPaused = false } }))