diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 7ce8879e60ee..bf826ea053b9 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,16 @@ +## 12.8.1 + +_Released 03/15/2023 (PENDING)_ + +**Bugfixes:** + +- Fixed a regression in Cypress [10](https://docs.cypress.io/guides/references/changelog#10-0-0) where the reporter auto-scroll configuration inside user preferences was unintentionally being toggled off. User's must now explicitly enable/disable auto-scroll under user preferences, which is enabled by default. Fixes [#24171](https://github.com/cypress-io/cypress/issues/24171) and [#26113](https://github.com/cypress-io/cypress/issues/26113). + +**Dependency Updates:** + +- Upgraded [`ejs`](https://www.npmjs.com/package/ejs) from `3.1.6` to `3.1.8` to address this [CVE-2022-29078](https://github.com/advisories/GHSA-phwq-j96m-2c2q) NVD security vulnerability. Addressed in [#25279](https://github.com/cypress-io/cypress/pull/25279). + ## 12.8.0 _Released 03/14/2023_ @@ -28,7 +40,6 @@ _Released 03/14/2023_ **Dependency Updates:** -- Upgraded [`ejs`](https://www.npmjs.com/package/ejs) from `3.1.6` to `3.1.8` to address this [CVE-2022-29078](https://github.com/advisories/GHSA-phwq-j96m-2c2q) NVD security vulnerability. Addressed in [#25279](https://github.com/cypress-io/cypress/pull/25279). - Upgraded [`mocha-junit-reporter`](https://www.npmjs.com/package/mocha-junit-reporter) from `2.1.0` to `2.2.0` to be able to use [new placeholders](https://github.com/michaelleeallen/mocha-junit-reporter/pull/163) such as `[suiteFilename]` or `[suiteName]` when defining the test report name. Addressed in [#25922](https://github.com/cypress-io/cypress/pull/25922). ## 12.7.0 diff --git a/packages/app/cypress/e2e/reporter_header.cy.ts b/packages/app/cypress/e2e/reporter_header.cy.ts index 3b6cb949486b..194196f1acf5 100644 --- a/packages/app/cypress/e2e/reporter_header.cy.ts +++ b/packages/app/cypress/e2e/reporter_header.cy.ts @@ -1,19 +1,19 @@ describe('Reporter Header', () => { - beforeEach(() => { - cy.scaffoldProject('cypress-in-cypress') - cy.openProject('cypress-in-cypress') - cy.startAppServer() - cy.visitApp() - cy.contains('dom-content.spec').click() - cy.waitForSpecToFinish() - }) - context('Specs Shortcut', () => { + beforeEach(() => { + cy.scaffoldProject('cypress-in-cypress') + cy.openProject('cypress-in-cypress') + cy.startAppServer() + cy.visitApp() + cy.contains('dom-content.spec').click() + cy.waitForSpecToFinish() + }) + it('selects the correct spec in the Specs List', () => { cy.get('body').type('f') cy.get('[data-selected-spec="true"]').should('contain', 'dom-content').should('have.length', '1') - cy.get('[data-selected-spec="false"]').should('have.length', '27') + cy.get('[data-selected-spec="false"]').should('have.length', '28') }) // TODO: Reenable as part of https://github.com/cypress-io/cypress/issues/23902 @@ -38,26 +38,62 @@ describe('Reporter Header', () => { }) context('Testing Preferences', () => { - it('clicking the down arrow will open a panel showing Testing Preferences', () => { - cy.get('.testing-preferences-toggle').trigger('mouseover') - cy.get('.cy-tooltip').should('have.text', 'Open Testing Preferences') - - cy.get('.testing-preferences').should('not.exist') - cy.get('.testing-preferences-toggle').should('not.have.class', 'open').click() - cy.get('.testing-preferences-toggle').should('have.class', 'open') - cy.get('.testing-preferences').should('be.visible') - cy.get('.testing-preferences-toggle').click() - cy.get('.testing-preferences-toggle').should('not.have.class', 'open') - cy.get('.testing-preferences').should('not.exist') + const switchSelector = '[data-cy=auto-scroll-switch]' + + context('preferences menu', () => { + beforeEach(() => { + cy.scaffoldProject('cypress-in-cypress') + cy.openProject('cypress-in-cypress') + cy.startAppServer() + cy.visitApp() + cy.contains('dom-content.spec').click() + cy.waitForSpecToFinish() + }) + + it('clicking the down arrow will open a panel showing Testing Preferences', () => { + cy.get('.testing-preferences-toggle').trigger('mouseover') + cy.get('.cy-tooltip').should('have.text', 'Open Testing Preferences') + + cy.get('.testing-preferences').should('not.exist') + cy.get('.testing-preferences-toggle').should('not.have.class', 'open').click() + cy.get('.testing-preferences-toggle').should('have.class', 'open') + cy.get('.testing-preferences').should('be.visible') + cy.get('.testing-preferences-toggle').click() + cy.get('.testing-preferences-toggle').should('not.have.class', 'open') + cy.get('.testing-preferences').should('not.exist') + }) + + it('will show a toggle beside the auto-scrolling option', () => { + cy.get('.testing-preferences-toggle').click() + cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true') + cy.get(switchSelector).click() + cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'false') + }) }) - it('will show a toggle beside the auto-scrolling option', () => { - const switchSelector = '[data-cy=auto-scroll-switch]' + it('does NOT toggle off the user preferences auto-scroll if auto-scroll is temporarily disabled', () => { + cy.scaffoldProject('cypress-in-cypress') + cy.openProject('cypress-in-cypress') + cy.startAppServer() + cy.visitApp() + cy.contains('dom-content-scrollable-commands.spec').click() + + // wait for the test to scroll all the way to the bottom + cy.get(':contains("checks for list items to exist - iteration #25") + :contains("passed")', { + timeout: 20000, + }).should('exist') + + // then, use the runnable container to fire the scroll events that previously would override and sync the preference config + cy.get('[data-cy="reporter-panel"] .reporter > header + .container').its('0').then(($runnableContainer) => { + // scroll the container to the top. + // fire multiple scroll events so our scroller component believes the scroll came from an actual user. + [...Array(10).keys()].forEach(() => { + $runnableContainer.dispatchEvent(new CustomEvent('scroll')) + }) + }) cy.get('.testing-preferences-toggle').click() cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'true') - cy.get(switchSelector).click() - cy.get(switchSelector).invoke('attr', 'aria-checked').should('eq', 'false') }) }) }) diff --git a/packages/app/cypress/e2e/specs_list_e2e.cy.ts b/packages/app/cypress/e2e/specs_list_e2e.cy.ts index 904e5939f281..b44e023ed40f 100644 --- a/packages/app/cypress/e2e/specs_list_e2e.cy.ts +++ b/packages/app/cypress/e2e/specs_list_e2e.cy.ts @@ -144,21 +144,21 @@ describe('App: Spec List (E2E)', () => { it('displays only matching spec', function () { cy.get('button') - .contains('23 matches') + .contains('24 matches') .should('not.contain.text', 'of') clearSearchAndType('content') cy.findAllByTestId('spec-item') - .should('have.length', 2) + .should('have.length', 3) .and('contain', 'dom-content.spec.js') - cy.get('button').contains('2 of 23 matches') + cy.get('button').contains('3 of 24 matches') cy.findByLabelText('Search specs').clear().type('asdf') cy.findAllByTestId('spec-item') .should('have.length', 0) - cy.get('button').contains('0 of 23 matches') + cy.get('button').contains('0 of 24 matches') }) it('only shows matching folders', () => { @@ -209,7 +209,7 @@ describe('App: Spec List (E2E)', () => { cy.findByLabelText('Search specs') .should('have.value', '') - cy.get('button').contains('23 matches') + cy.get('button').contains('24 matches') }) it('clears the filter if the user presses ESC key', function () { @@ -218,7 +218,7 @@ describe('App: Spec List (E2E)', () => { cy.get('@searchField').should('have.value', '') - cy.get('button').contains('23 matches') + cy.get('button').contains('24 matches') }) it('shows empty message if no results', function () { @@ -234,7 +234,7 @@ describe('App: Spec List (E2E)', () => { cy.findByText('Clear search').click() cy.focused().should('have.id', 'spec-filter') - cy.get('button').contains('23 matches') + cy.get('button').contains('24 matches') }) it('normalizes directory path separators for Windows', function () { diff --git a/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts index 2a0318bc6368..b0236c746d23 100644 --- a/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/specChange-subscription.cy.ts @@ -63,6 +63,7 @@ describe('specChange subscription', () => { getPathForPlatform('cypress/e2e/blank-contents.spec.js'), getPathForPlatform('cypress/e2e/dom-container.spec.js'), getPathForPlatform('cypress/e2e/dom-content.spec.js'), + getPathForPlatform('cypress/e2e/dom-content-scrollable-commands.spec.js'), getPathForPlatform('cypress/e2e/dom-list.spec.js'), getPathForPlatform('cypress/e2e/withFailure.spec.js'), getPathForPlatform('cypress/e2e/withWait.spec.js'), @@ -106,6 +107,7 @@ describe('specChange subscription', () => { getPathForPlatform('cypress/e2e/blank-contents.spec.js'), getPathForPlatform('cypress/e2e/dom-container.spec.js'), getPathForPlatform('cypress/e2e/dom-content.spec.js'), + getPathForPlatform('cypress/e2e/dom-content-scrollable-commands.spec.js'), getPathForPlatform('cypress/e2e/withFailure.spec.js'), getPathForPlatform('cypress/e2e/withWait.spec.js'), getPathForPlatform('cypress/e2e/123.spec.js'), @@ -171,9 +173,10 @@ e2e: { }) cy.get('[data-cy="spec-item-link"]', { timeout: 7500 }) - .should('have.length', 2) + .should('have.length', 3) .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') + .should('contain', 'dom-content-scrollable-commands.spec.js') }) }) @@ -185,7 +188,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 23) + .should('have.length', 24) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -196,7 +199,7 @@ e2e: { }, { path: getPathForPlatform('cypress/e2e/new-file.spec.js') }) cy.get('[data-cy="spec-file-item"]') - .should('have.length', 24) + .should('have.length', 25) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -211,7 +214,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 23) + .should('have.length', 24) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -222,7 +225,7 @@ e2e: { }, { path: getPathForPlatform('cypress/e2e/dom-list.spec.js') }) cy.get('[data-cy="spec-file-item"]') - .should('have.length', 22) + .should('have.length', 23) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -241,6 +244,7 @@ e2e: { getPathForPlatform('cypress/e2e/blank-contents.spec.js'), getPathForPlatform('cypress/e2e/dom-container.spec.js'), getPathForPlatform('cypress/e2e/dom-list.spec.js'), + getPathForPlatform('cypress/e2e/dom-content-scrollable-commands.spec.js'), getPathForPlatform('cypress/e2e/withFailure.spec.js'), getPathForPlatform('cypress/e2e/withWait.spec.js'), getPathForPlatform('cypress/e2e/123.spec.js'), @@ -282,7 +286,7 @@ e2e: { cy.get('body').type('f') cy.get('[data-cy="spec-file-item"]') - .should('have.length', 23) + .should('have.length', 24) .should('contain', 'blank-contents.spec.js') .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') @@ -310,9 +314,10 @@ e2e: { }) cy.get('[data-cy="spec-file-item"]', { timeout: 7500 }) - .should('have.length', 2) + .should('have.length', 3) .should('contain', 'dom-container.spec.js') .should('contain', 'dom-content.spec.js') + .should('contain', 'dom-content-scrollable-commands.spec.js') }) }) @@ -324,14 +329,14 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '23 matches') + .should('contain', '24 matches') cy.withCtx(async (ctx, o) => { await ctx.actions.file.writeFileInProject(o.path, '') }, { path: getPathForPlatform('cypress/e2e/new-file.spec.js') }) cy.get('[data-cy="file-match-indicator"]') - .should('contain', '24 matches') + .should('contain', '25 matches') }) it('responds to specChange event for a removed file', () => { @@ -341,14 +346,14 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '23 matches') + .should('contain', '24 matches') cy.withCtx(async (ctx, o) => { await ctx.actions.file.removeFileInProject(o.path) }, { path: getPathForPlatform('cypress/e2e/dom-list.spec.js') }) cy.get('[data-cy="file-match-indicator"]') - .should('contain', '22 matches') + .should('contain', '23 matches') }) it('handles removing the last file', () => { @@ -364,6 +369,7 @@ e2e: { getPathForPlatform('cypress/e2e/blank-contents.spec.js'), getPathForPlatform('cypress/e2e/dom-container.spec.js'), getPathForPlatform('cypress/e2e/dom-content.spec.js'), + getPathForPlatform('cypress/e2e/dom-content-scrollable-commands.spec.js'), getPathForPlatform('cypress/e2e/withFailure.spec.js'), getPathForPlatform('cypress/e2e/withWait.spec.js'), getPathForPlatform('cypress/e2e/123.spec.js'), @@ -404,7 +410,7 @@ e2e: { cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.spec.{js,ts}') cy.get('[data-cy="file-match-indicator"]') - .should('contain', '23 matches') + .should('contain', '24 matches') cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject('cypress.config.js', @@ -428,7 +434,7 @@ e2e: { }) cy.get('[data-cy="file-match-indicator"]', { timeout: 7500 }) - .should('contain', '2 matches') + .should('contain', '3 matches') }) }) }) diff --git a/packages/reporter/cypress/e2e/unit/events.cy.ts b/packages/reporter/cypress/e2e/unit/events.cy.ts index de05377037e6..8deddea044e4 100644 --- a/packages/reporter/cypress/e2e/unit/events.cy.ts +++ b/packages/reporter/cypress/e2e/unit/events.cy.ts @@ -350,7 +350,7 @@ describe('events', () => { }) it('emits save:state on save:state', () => { - appState.autoScrollingEnabled = false + appState.autoScrollingUserPref = false appState.isSpecsListOpen = true events.emit('save:state') expect(runner.emit).to.have.been.calledWith('save:state', { diff --git a/packages/reporter/src/lib/app-state.ts b/packages/reporter/src/lib/app-state.ts index 2ccd675a92e0..ee5c7d298119 100644 --- a/packages/reporter/src/lib/app-state.ts +++ b/packages/reporter/src/lib/app-state.ts @@ -22,6 +22,7 @@ const defaults: DefaultAppState = { } class AppState { + @observable autoScrollingUserPref = true @observable autoScrollingEnabled = true @observable isSpecsListOpen = false @observable isPaused = defaults.isPaused @@ -69,6 +70,14 @@ class AppState { this.setAutoScrolling(!this.autoScrollingEnabled) } + /** + * Toggles the auto-scrolling user preference to true|false. This method should only be called from the + * preferences menu itself. + */ + toggleAutoScrollingUserPref () { + this.setAutoScrollingUserPref(!this.autoScrollingUserPref) + } + toggleSpecList () { this.isSpecsListOpen = !this.isSpecsListOpen } @@ -88,6 +97,19 @@ class AppState { } } + /** + * Sets the auto scroll user preference to true|false. + * When this preference is set, it overrides any temporary auto scrolling behaviors that may be in effect. + * @param {boolean | null | undefined} isEnabled - whether or not auto scroll should be enabled or disabled. + * If not a boolean, this method is a no-op. + */ + setAutoScrollingUserPref (isEnabled?: boolean | null) { + if (isEnabled != null) { + this.autoScrollingUserPref = isEnabled + this.setAutoScrolling(isEnabled) + } + } + setStudioActive (studioActive: boolean) { this.studioActive = studioActive } diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts index c808e71100a4..f0cc2afddd9c 100644 --- a/packages/reporter/src/lib/events.ts +++ b/packages/reporter/src/lib/events.ts @@ -194,7 +194,8 @@ const events: Events = { localBus.on('save:state', () => { runner.emit('save:state', { - autoScrollingEnabled: appState.autoScrollingEnabled, + // the "autoScrollingEnabled" key in `savedState` stores to the preference value itself, it is not the same as the "autoScrollingEnabled" variable stored in application state, which can be temporarily deactivated + autoScrollingEnabled: appState.autoScrollingUserPref, isSpecsListOpen: appState.isSpecsListOpen, }) }) diff --git a/packages/reporter/src/lib/shortcuts.ts b/packages/reporter/src/lib/shortcuts.ts index 8ed488798646..e3b38b3f91f0 100644 --- a/packages/reporter/src/lib/shortcuts.ts +++ b/packages/reporter/src/lib/shortcuts.ts @@ -37,7 +37,7 @@ class Shortcuts { case 'n': events.emit('next') break case 'a': action('set:scrolling', () => { - appState.setAutoScrolling(!appState.autoScrollingEnabled) + appState.toggleAutoScrollingUserPref() events.emit('save:state') })() diff --git a/packages/reporter/src/main.tsx b/packages/reporter/src/main.tsx index 698be9e30a16..fbc2fed10517 100644 --- a/packages/reporter/src/main.tsx +++ b/packages/reporter/src/main.tsx @@ -114,7 +114,8 @@ class Reporter extends Component { } action('set:scrolling', () => { - appState.setAutoScrolling(autoScrollingEnabled) + // set the initial enablement of auto scroll configured inside the user preferences when the app is loaded + appState.setAutoScrollingUserPref(autoScrollingEnabled) })() action('set:specs:list', () => { diff --git a/packages/reporter/src/preferences/testing-preferences.tsx b/packages/reporter/src/preferences/testing-preferences.tsx index d54ad569f06f..90675a2c46b6 100644 --- a/packages/reporter/src/preferences/testing-preferences.tsx +++ b/packages/reporter/src/preferences/testing-preferences.tsx @@ -15,8 +15,8 @@ const TestingPreferences = observer(({ events = defaultEvents, appState, }: Props) => { - const toggleAutoScrolling = () => { - appState.toggleAutoScrolling() + const toggleAutoScrollingUserPref = () => { + appState.toggleAutoScrollingUserPref() events.emit('save:state') } @@ -31,8 +31,8 @@ const TestingPreferences = observer(({ Auto-scrolling
diff --git a/system-tests/projects/cypress-in-cypress/cypress/e2e/dom-content-scrollable-commands.spec.js b/system-tests/projects/cypress-in-cypress/cypress/e2e/dom-content-scrollable-commands.spec.js new file mode 100644 index 000000000000..6af909af6960 --- /dev/null +++ b/system-tests/projects/cypress-in-cypress/cypress/e2e/dom-content-scrollable-commands.spec.js @@ -0,0 +1,19 @@ +describe('Dom Content - Scrollable Assertions', () => { + beforeEach(() => { + cy.visit('cypress/e2e/dom-content.html') + }) + + // create enough commands in the command log to enable scrolling + ;[...Array(25).keys()].forEach((idx) => { + it(`checks for list items to exist - iteration #${idx + 1}`, () => { + cy.get('li').contains('Item 1').should('exist') + cy.get('li').contains('Item 2').should('exist') + cy.get('li').contains('Item 3').should('exist') + }) + }) + + // allow the cy-in-cy test to perform user interaction during this long test + it('waits for an arbitrary amount of time', () => { + cy.wait((50000)) + }) +})