Skip to content
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
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
69 changes: 68 additions & 1 deletion packages/reporter/cypress/e2e/header.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
23 changes: 19 additions & 4 deletions packages/reporter/src/header/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,26 @@ const Controls: React.FC<Props> = observer(({ events = defaultEvents, appState }
</div>
</Tooltip>
)}
{!!appState.nextCommandName && (
<Tooltip placement='bottom' title={<p>Next <span className='kbd'>[N]:</span>{appState.nextCommandName}</p>} className='cy-tooltip'>
{(appState.isPaused || (appState.isRunning && appState.hasBeenPaused)) && (
<Tooltip
placement='bottom'
title={appState.nextCommandName ? <p>Next <span className='kbd'>[N]:</span>{appState.nextCommandName}</p> : <p>Step (not available)</p>}
className='cy-tooltip'
>
<div>
<Button size='20' variant='outline-dark' aria-label={`Next '${appState.nextCommandName}'`} className='next' onClick={emit('next')}>
<IconActionNext size='16' strokeColor={iconStrokeColor} fillColor={iconFillColor} />
<Button
size='20'
variant='outline-dark'
aria-label={appState.nextCommandName ? `Next '${appState.nextCommandName}'` : 'Next (not available)'}
className='next disabled:hover:border-white/20 disabled:focus:border-white/20'
disabled={!appState.nextCommandName}
onClick={appState.nextCommandName ? emit('next') : () => { }}
>
<IconActionNext
size='16'
strokeColor={(appState.nextCommandName) ? iconStrokeColor : 'gray-700'}
fillColor={iconFillColor}
/>
</Button>
</div>
</Tooltip>
Expand Down
5 changes: 5 additions & 0 deletions packages/reporter/src/lib/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface DefaultAppState {
pinnedSnapshotId: number | string | null
studioActive: boolean
studioSingleTestActive: boolean
hasBeenPaused: boolean
}

// these are used for the `reset` method
Expand All @@ -19,6 +20,7 @@ const defaults: DefaultAppState = {
pinnedSnapshotId: null,
studioActive: false,
studioSingleTestActive: false,
hasBeenPaused: false,
}

class AppState {
Expand All @@ -32,6 +34,7 @@ class AppState {
studioActive = defaults.studioActive
studioSingleTestActive = defaults.studioSingleTestActive
isStopped = false
hasBeenPaused = defaults.hasBeenPaused
_resetAutoScrollingEnabledTo = true;
[key: string]: any

Expand All @@ -46,6 +49,7 @@ class AppState {
pinnedSnapshotId: observable,
studioActive: observable,
studioSingleTestActive: observable,
hasBeenPaused: observable,
})
}

Expand All @@ -57,6 +61,7 @@ class AppState {
pause (nextCommandName?: string) {
this.isPaused = true
this.nextCommandName = nextCommandName
this.hasBeenPaused = true
}

resume () {
Expand Down
1 change: 1 addition & 0 deletions packages/reporter/src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const events: Events = {
runner.on('run:start', action('run:start', () => {
if (runnablesStore.hasTests) {
appState.startRunning()
appState.hasBeenPaused = false
}
}))

Expand Down