From b615e8ac30b681b4dc8485afe9814ff453da1fbf Mon Sep 17 00:00:00 2001 From: Zachary Williams Date: Tue, 30 Aug 2022 15:43:01 -0500 Subject: [PATCH 01/49] fix: don't show deps warning for projects that don't require bundler (#23598) --- packages/data-context/src/data/ProjectConfigManager.ts | 6 ------ .../test/unit/sources/WizardDataSource.spec.ts | 8 ++++---- packages/launchpad/cypress/e2e/config-warning.cy.ts | 10 ++++++++++ packages/launchpad/cypress/e2e/project-setup.cy.ts | 2 +- packages/scaffold-config/src/frameworks.ts | 3 --- .../__snapshots__/component_testing_spec.ts.js | 7 ------- .../experimentalSingleTabRunMode/webpack.config.js | 4 +++- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/data-context/src/data/ProjectConfigManager.ts b/packages/data-context/src/data/ProjectConfigManager.ts index 258ff7209fe9..2717bedc4fba 100644 --- a/packages/data-context/src/data/ProjectConfigManager.ts +++ b/packages/data-context/src/data/ProjectConfigManager.ts @@ -191,12 +191,6 @@ export class ProjectConfigManager { return } - const result = await isDependencyInstalled(bundler, this.options.projectRoot) - - if (!result.satisfied) { - unsupportedDeps.set(result.dependency.type, result) - } - const isFrameworkSatisfied = async (bundler: typeof WIZARD_BUNDLERS[number], framework: typeof WIZARD_FRAMEWORKS[number]) => { for (const dep of await (framework.dependencies(bundler.type, this.options.projectRoot))) { const res = await isDependencyInstalled(dep.dependency, this.options.projectRoot) diff --git a/packages/data-context/test/unit/sources/WizardDataSource.spec.ts b/packages/data-context/test/unit/sources/WizardDataSource.spec.ts index 334fe28c00c9..6bb6367d8877 100644 --- a/packages/data-context/test/unit/sources/WizardDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/WizardDataSource.spec.ts @@ -27,7 +27,7 @@ describe('packagesToInstall', () => { const actual = await ctx.wizard.installDependenciesCommand() - expect(actual).to.eq(`npm install -D react-scripts webpack react-dom react`) + expect(actual).to.eq(`npm install -D react-scripts react-dom react`) }) it('vueclivue2-unconfigured', async () => { @@ -43,7 +43,7 @@ describe('packagesToInstall', () => { const actual = await ctx.wizard.installDependenciesCommand() - expect(actual).to.eq(`npm install -D @vue/cli-service webpack vue@2`) + expect(actual).to.eq(`npm install -D @vue/cli-service vue@2`) }) it('vueclivue3-unconfigured', async () => { @@ -59,7 +59,7 @@ describe('packagesToInstall', () => { const actual = await ctx.wizard.installDependenciesCommand() - expect(actual).to.eq(`npm install -D @vue/cli-service webpack vue`) + expect(actual).to.eq(`npm install -D @vue/cli-service vue`) }) it('vuecli5vue3-unconfigured', async () => { @@ -75,7 +75,7 @@ describe('packagesToInstall', () => { const actual = await ctx.wizard.installDependenciesCommand() - expect(actual).to.eq(`npm install -D @vue/cli-service webpack vue`) + expect(actual).to.eq(`npm install -D @vue/cli-service vue`) }) it('regular react project with vite', async () => { diff --git a/packages/launchpad/cypress/e2e/config-warning.cy.ts b/packages/launchpad/cypress/e2e/config-warning.cy.ts index 4e9c03348ef7..fc1d1e782638 100644 --- a/packages/launchpad/cypress/e2e/config-warning.cy.ts +++ b/packages/launchpad/cypress/e2e/config-warning.cy.ts @@ -187,4 +187,14 @@ describe('component testing dependency warnings', () => { cy.contains('Choose a Browser', { timeout: 12000 }) cy.get('[data-cy="warning-alert"]').should('not.exist') }) + + it('does not show warning for project that does not require bundler to be installed', () => { + cy.scaffoldProject('next-12') + cy.openProject('next-12') + cy.visitLaunchpad() + cy.get('[data-cy="warning-alert"]').should('not.exist') + cy.get('[data-cy-testingtype="component"]').click() + cy.contains('Choose a Browser', { timeout: 12000 }) + cy.get('[data-cy="warning-alert"]').should('not.exist') + }) }) diff --git a/packages/launchpad/cypress/e2e/project-setup.cy.ts b/packages/launchpad/cypress/e2e/project-setup.cy.ts index 597d6af1f3d2..b86ebc28267f 100644 --- a/packages/launchpad/cypress/e2e/project-setup.cy.ts +++ b/packages/launchpad/cypress/e2e/project-setup.cy.ts @@ -379,9 +379,9 @@ describe('Launchpad: Setup Project', () => { cy.findByRole('button', { name: 'Next Step' }).click() cy.findByRole('button', { name: 'Waiting for you to install the dependencies...' }) - cy.contains('li', 'webpack') cy.contains('li', 'react-scripts') cy.contains('li', 'react') + cy.contains('li', 'react-dom') cy.findByRole('button', { name: 'Skip' }).click() diff --git a/packages/scaffold-config/src/frameworks.ts b/packages/scaffold-config/src/frameworks.ts index f006d46ec69b..c99428f5fa92 100644 --- a/packages/scaffold-config/src/frameworks.ts +++ b/packages/scaffold-config/src/frameworks.ts @@ -86,7 +86,6 @@ export const WIZARD_FRAMEWORKS = [ dependencies: (bundler: WizardBundler['type'], projectPath: string): Promise => { return Promise.all([ isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_REACT_SCRIPTS, projectPath), - isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_WEBPACK, projectPath), isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_REACT_DOM, projectPath), isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_REACT, projectPath), ]) @@ -107,7 +106,6 @@ export const WIZARD_FRAMEWORKS = [ dependencies: (bundler: WizardBundler['type'], projectPath: string): Promise => { return Promise.all([ isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_VUE_CLI_SERVICE, projectPath), - isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_WEBPACK, projectPath), isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_VUE_2, projectPath), ]) }, @@ -127,7 +125,6 @@ export const WIZARD_FRAMEWORKS = [ dependencies: (bundler: WizardBundler['type'], projectPath: string): Promise => { return Promise.all([ isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_VUE_CLI_SERVICE, projectPath), - isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_WEBPACK, projectPath), isDependencyInstalled(dependencies.WIZARD_DEPENDENCY_VUE_3, projectPath), ]) }, diff --git a/system-tests/__snapshots__/component_testing_spec.ts.js b/system-tests/__snapshots__/component_testing_spec.ts.js index ad7616d0ddd6..dcab2102a32e 100644 --- a/system-tests/__snapshots__/component_testing_spec.ts.js +++ b/system-tests/__snapshots__/component_testing_spec.ts.js @@ -533,13 +533,6 @@ exports['React major versions with Vite executes all of the tests for React v18 ` exports['experimentalSingleTabRunMode / executes all specs in a single tab'] = ` -We detected that you have versions of dependencies that are not officially supported: - - - \`webpack\`. Expected >=4.0.0 || >=5.0.0 but dependency was not found. - -If you're experiencing problems, downgrade dependencies and restart Cypress. - - 30 modules ==================================================================================================== diff --git a/system-tests/projects/experimentalSingleTabRunMode/webpack.config.js b/system-tests/projects/experimentalSingleTabRunMode/webpack.config.js index 4ba52ba2c8df..74edfc0c20e2 100644 --- a/system-tests/projects/experimentalSingleTabRunMode/webpack.config.js +++ b/system-tests/projects/experimentalSingleTabRunMode/webpack.config.js @@ -1 +1,3 @@ -module.exports = {} +module.exports = { + stats: 'errors-warnings', +} From 72007755a53360ace8b362852312d1bc07f95baf Mon Sep 17 00:00:00 2001 From: cypresschris <96069059+cypresschris@users.noreply.github.com> Date: Wed, 31 Aug 2022 08:52:19 -0500 Subject: [PATCH 02/49] chore: SEC-228 | Adding security scans (#23376) * SEC-228 | Adding security scans * Update .github/workflows/static_analysis.yaml Co-authored-by: Emily Rohrbough * Update .github/workflows/sca_scan.yaml Co-authored-by: Emily Rohrbough * SEC-228 | Changes to display scan findings in PR * Update .github/workflows/sca_scan.yaml Co-authored-by: Bill Glesias * Update .github/workflows/sca_scan.yaml Co-authored-by: Bill Glesias * SEC-228 | Update to fix secret not passing to fork * SEC-228 | renamed files, updt checkout vers * SEC-228 | removed old versions * Update .github/workflows/snyk_static_analysis_scan.yaml Suggested change Co-authored-by: Bill Glesias * Update .github/workflows/snyk_sca_scan.yaml Suggested change Co-authored-by: Bill Glesias * Update .github/workflows/snyk_sca_scan.yaml Suggested change Co-authored-by: Bill Glesias * Update .github/workflows/snyk_static_analysis_scan.yaml Suggested change Co-authored-by: Bill Glesias Co-authored-by: Emily Rohrbough Co-authored-by: Bill Glesias Co-authored-by: Matt Schile --- .github/workflows/snyk_sca_scan.yaml | 33 +++++++++++++++++++ .../workflows/snyk_static_analysis_scan.yaml | 29 ++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/snyk_sca_scan.yaml create mode 100644 .github/workflows/snyk_static_analysis_scan.yaml diff --git a/.github/workflows/snyk_sca_scan.yaml b/.github/workflows/snyk_sca_scan.yaml new file mode 100644 index 000000000000..d9b21b0ab8e2 --- /dev/null +++ b/.github/workflows/snyk_sca_scan.yaml @@ -0,0 +1,33 @@ +name: Snyk Software Composition Analysis Scan +# This git workflow leverages Snyk actions to perform a Software Composition +# Analysis scan on our Opensource libraries upon Pull Requests to Master & +# Develop branches. We use this as a control to prevent vulnerable packages +# from being introduced into the codebase. +on: + pull_request_target: + types: + - opened + branches: + - master + - develop +jobs: + Snyk_SCA_Scan: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v2 + - name: Setting up Node + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Installing snyk-delta and dependencies + run: npm i -g snyk-delta + - uses: snyk/actions/setup@master + - name: Perform SCA Scan + continue-on-error: false + run: | + snyk test --yarn-workspaces --strict-out-of-sync=false --detection-depth=6 --exclude=docker,Dockerfile --severity-threshold=critical + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} diff --git a/.github/workflows/snyk_static_analysis_scan.yaml b/.github/workflows/snyk_static_analysis_scan.yaml new file mode 100644 index 000000000000..f34b3de41e1c --- /dev/null +++ b/.github/workflows/snyk_static_analysis_scan.yaml @@ -0,0 +1,29 @@ +name: Snyk Static Analysis Scan +# This git workflow leverages Snyk actions to perform a Static Application +# Testing scan (SAST) on our first-party code upon Pull Requests to Master & +# Develop branches. We use this as a control to prevent vulnerabilities +# from being introduced into the codebase. +on: + pull_request_target: + types: + - opened + branches: + - master + - develop +jobs: + Snyk_SAST_Scan : + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: snyk/actions/setup@master + - name: Perform Static Analysis Test + continue-on-error: true + run: | + snyk code test --yarn-workspaces --strict-out-of-sync=false --detection-depth=6 --exclude=docker,Dockerfile --severity-threshold=high + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + # The Following Requires Advanced Security License + # - name: Upload results to Github Code Scanning + # uses: github/codeql-action/upload-sarif@v1 + # with: + # sarif_file: snyk_sarif From 212a055bb7c1a084d934d2ee73ee0ebcbb150e53 Mon Sep 17 00:00:00 2001 From: Tyler Biethman Date: Wed, 31 Aug 2022 09:48:21 -0500 Subject: [PATCH 03/49] chore(webkit): working around WebKit bug by patching Error (#23588) * chore(webkit): re-enable more driver tests * Revert "chore(webkit): re-enable more driver tests" This reverts commit c0b14ace572eecb5eb273fb24629f88f3478f284. * chore(webkit): working around WebKit bug by patching Error * Better comment * Address PR feedback Co-authored-by: Emily Rohrbough --- .../driver/cypress/e2e/e2e/uncaught_errors.cy.js | 8 +++----- packages/driver/src/cypress/cy.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/driver/cypress/e2e/e2e/uncaught_errors.cy.js b/packages/driver/cypress/e2e/e2e/uncaught_errors.cy.js index 95bc5eb8fdd4..e588d0930597 100644 --- a/packages/driver/cypress/e2e/e2e/uncaught_errors.cy.js +++ b/packages/driver/cypress/e2e/e2e/uncaught_errors.cy.js @@ -97,8 +97,7 @@ describe('uncaught errors', () => { cy.get('.error-two').invoke('text').should('equal', 'async error') }) - // TODO(webkit): fix+unskip. the browser emits the correct event, but not at the time expected - it('unhandled rejection triggers uncaught:exception and has promise as third argument', { browser: '!webkit' }, (done) => { + it('unhandled rejection triggers uncaught:exception and has promise as third argument', (done) => { cy.once('uncaught:exception', (err, runnable, promise) => { expect(err.stack).to.include('promise rejection') expect(err.stack).to.include('one') @@ -117,7 +116,7 @@ describe('uncaught errors', () => { // if we mutate the error, the app's listeners for 'error' or // 'unhandledrejection' will have our wrapped error instead of the original - it('original error is not mutated for "error"', { browser: '!webkit' }, () => { + it('original error is not mutated for "error"', () => { cy.once('uncaught:exception', () => false) cy.visit('/fixtures/errors.html') @@ -126,8 +125,7 @@ describe('uncaught errors', () => { cy.get('.error-two').invoke('text').should('equal', 'sync error') }) - // TODO(webkit): fix+unskip. the browser emits the correct event, but not at the time expected - it('original error is not mutated for "unhandledrejection"', { browser: '!webkit' }, () => { + it('original error is not mutated for "unhandledrejection"', () => { cy.once('uncaught:exception', () => false) cy.visit('/fixtures/errors.html') diff --git a/packages/driver/src/cypress/cy.ts b/packages/driver/src/cypress/cy.ts index 002b7f5455c0..3140cce77b12 100644 --- a/packages/driver/src/cypress/cy.ts +++ b/packages/driver/src/cypress/cy.ts @@ -550,6 +550,22 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert setWindowDocumentProps(autWindow, this.state) + if (this.Cypress.isBrowser('webkit')) { + // WebKit's unhandledrejection event will sometimes not fire within the AUT + // due to a documented bug: https://bugs.webkit.org/show_bug.cgi?id=187822 + // To ensure that the event will always fire (and always report these + // unhandled rejections to the user), we patch the AUT's Error constructor + // to enqueue a no-op microtask when executed, which ensures that the unhandledrejection + // event handler will be executed if this Error is uncaught. + const originalError = autWindow.Error + + autWindow.Error = function __CyWebKitError (...args) { + autWindow.queueMicrotask(() => {}) + + return originalError.apply(this, args) + } + } + // we may need to update the url now this.urlNavigationEvent('load') From c8b1b303234f8835dadbfc8aeea840f67512ea54 Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Wed, 31 Aug 2022 13:40:11 -0500 Subject: [PATCH 04/49] fix: run "before:spec" plugin event when experimentalInteractiveRunEvents is enabled (#23634) Co-authored-by: Lachlan Miller Co-authored-by: Rachel --- .../app/cypress/e2e/runner/pluginEvents.cy.ts | 78 +++++++++++++++++++ packages/app/src/runner/event-manager.ts | 4 + packages/app/src/runner/index.ts | 40 ++++------ packages/server/lib/modes/run.ts | 2 - packages/server/lib/open_project.ts | 10 +-- packages/server/lib/socket-base.ts | 19 ++++- .../server/test/unit/open_project_spec.js | 27 ------- .../plugin_run_events_spec.ts.js | 9 ++- .../plugin-run-events/cypress.config.js | 25 ++++-- 9 files changed, 139 insertions(+), 75 deletions(-) create mode 100644 packages/app/cypress/e2e/runner/pluginEvents.cy.ts diff --git a/packages/app/cypress/e2e/runner/pluginEvents.cy.ts b/packages/app/cypress/e2e/runner/pluginEvents.cy.ts new file mode 100644 index 000000000000..e7ef1509f048 --- /dev/null +++ b/packages/app/cypress/e2e/runner/pluginEvents.cy.ts @@ -0,0 +1,78 @@ +const path = require('path') + +describe('plugin events', () => { + it('supports "before:run" event', () => { + let projectRoot: string + + cy.scaffoldProject('plugin-run-events') + .then((projectPath) => { + projectRoot = projectPath + + cy.openProject('plugin-run-events') + cy.startAppServer('e2e') + cy.visitApp() + + cy.get('[data-cy-row="run_events_spec_1.cy.js"]').eq(1).click() + cy.waitForSpecToFinish({ + passCount: 1, + }) + + cy.readFile(path.join(projectRoot, 'beforeRun.json')) + .then((details) => { + expect(details).to.have.property('config') + expect(details).to.have.property('cypressVersion') + expect(details).to.have.property('system') + }) + }) + }) + + it('supports "before:spec" event', () => { + let projectRoot: string + + cy.scaffoldProject('plugin-run-events') + .then((projectPath) => { + projectRoot = projectPath + + cy.openProject('plugin-run-events') + cy.startAppServer('e2e') + cy.visitApp() + + cy.get('[data-cy-row="run_events_spec_1.cy.js"]').eq(1).click() + cy.waitForSpecToFinish({ + passCount: 1, + }) + + cy.readFile(path.join(projectRoot, 'beforeSpec.json')) + .then((spec) => { + expect(spec).to.deep.contains({ + baseName: 'run_events_spec_1.cy.js', + fileExtension: '.js', + fileName: 'run_events_spec_1', + name: 'run_events_spec_1.cy.js', + relative: 'cypress/e2e/run_events_spec_1.cy.js', + specFileExtension: '.cy.js', + specType: 'integration', + }) + }) + + cy.get('body').type('f') + cy.get('div[title="run_events_spec_2.cy.js"]').click() + cy.waitForSpecToFinish({ + passCount: 1, + }) + + cy.readFile(path.join(projectRoot, 'beforeSpec.json')) + .then((spec) => { + expect(spec).to.deep.contains({ + baseName: 'run_events_spec_2.cy.js', + fileExtension: '.js', + fileName: 'run_events_spec_2', + name: 'run_events_spec_2.cy.js', + relative: 'cypress/e2e/run_events_spec_2.cy.js', + specFileExtension: '.cy.js', + specType: 'integration', + }) + }) + }) + }) +}) diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index 8a907ba023b3..7b27857f7b91 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -384,6 +384,10 @@ export class EventManager { this._addListeners() this.ws.emit('watch:test:file', config.spec) + + if (config.isTextTerminal || config.experimentalInteractiveRunEvents) { + this.ws.emit('plugins:before:spec', config.spec) + } } isBrowser (browserName) { diff --git a/packages/app/src/runner/index.ts b/packages/app/src/runner/index.ts index feccc70a4bf9..9f7bd1c1d6b7 100644 --- a/packages/app/src/runner/index.ts +++ b/packages/app/src/runner/index.ts @@ -225,18 +225,7 @@ export function addCrossOriginIframe (location) { * Cypress on it. * */ -function runSpecCT (spec: SpecFile) { - // TODO: UNIFY-1318 - figure out how to manage window.config. - const config = getRunnerConfigFromWindow() - - // this is how the Cypress driver knows which spec to run. - config.spec = setSpecForDriver(spec) - - // creates a new instance of the Cypress driver for this spec, - // initializes a bunch of listeners - // watches spec file for changes. - getEventManager().setup(config) - +function runSpecCT (config, spec: SpecFile) { const $runnerRoot = getRunnerElement() // clear AUT, if there is one. @@ -290,18 +279,7 @@ function setSpecForDriver (spec: SpecFile) { * a Spec IFrame to load the spec's source code, and * initialize Cypress on the AUT. */ -function runSpecE2E (spec: SpecFile) { - // TODO: UNIFY-1318 - manage config with GraphQL, don't put it on window. - const config = getRunnerConfigFromWindow() - - // this is how the Cypress driver knows which spec to run. - config.spec = setSpecForDriver(spec) - - // creates a new instance of the Cypress driver for this spec, - // initializes a bunch of listeners - // watches spec file for changes. - getEventManager().setup(config) - +function runSpecE2E (config, spec: SpecFile) { const $runnerRoot = getRunnerElement() // clear AUT, if there is one. @@ -427,12 +405,22 @@ async function executeSpec (spec: SpecFile, isRerun: boolean = false) { UnifiedReporterAPI.setupReporter() + // TODO: UNIFY-1318 - figure out how to manage window.config. + const config = getRunnerConfigFromWindow() + + // this is how the Cypress driver knows which spec to run. + config.spec = setSpecForDriver(spec) + + // creates a new instance of the Cypress driver for this spec, + // initializes a bunch of listeners watches spec file for changes. + getEventManager().setup(config) + if (window.__CYPRESS_TESTING_TYPE__ === 'e2e') { - return runSpecE2E(spec) + return runSpecE2E(config, spec) } if (window.__CYPRESS_TESTING_TYPE__ === 'component') { - return runSpecCT(spec) + return runSpecCT(config, spec) } throw Error('Unknown or undefined testingType on window.__CYPRESS_TESTING_TYPE__') diff --git a/packages/server/lib/modes/run.ts b/packages/server/lib/modes/run.ts index 0343e4427938..7ec8c54a7c26 100644 --- a/packages/server/lib/modes/run.ts +++ b/packages/server/lib/modes/run.ts @@ -876,8 +876,6 @@ async function runSpec (config, spec: SpecWithRelativeRoot, options: { project: const screenshots = [] - await runEvents.execute('before:spec', config, spec) - const videoRecordProps = await maybeStartVideoRecording({ spec, browser, diff --git a/packages/server/lib/open_project.ts b/packages/server/lib/open_project.ts index 632e0dc14c63..e249a094634b 100644 --- a/packages/server/lib/open_project.ts +++ b/packages/server/lib/open_project.ts @@ -158,13 +158,9 @@ export class OpenProject { spec.relative, ) - if (!cfg.isTextTerminal && cfg.experimentalInteractiveRunEvents) { - await runEvents.execute('before:spec', cfg, spec) - } else { - // clear cookies and all session data before each spec - cookieJar.removeAllCookies() - session.clearSessions() - } + // clear cookies and all session data before each spec + cookieJar.removeAllCookies() + session.clearSessions() // TODO: Stub this so we can detect it being called if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) { diff --git a/packages/server/lib/socket-base.ts b/packages/server/lib/socket-base.ts index 2b16a296f1e2..0bef74d54f9c 100644 --- a/packages/server/lib/socket-base.ts +++ b/packages/server/lib/socket-base.ts @@ -1,9 +1,13 @@ import Bluebird from 'bluebird' import Debug from 'debug' -import _ from 'lodash' import EventEmitter from 'events' +import _ from 'lodash' +import path from 'path' +import { getCtx } from '@packages/data-context' +import { handleGraphQLSocketRequest } from '@packages/graphql/src/makeGraphQLServer' import { onNetStubbingEvent } from '@packages/net-stubbing' import * as socketIo from '@packages/socket' + import firefoxUtil from './browsers/firefox-util' import * as errors from './errors' import exec from './exec' @@ -17,11 +21,10 @@ import open from './util/open' import type { DestroyableHttpServer } from './util/server_destroy' import * as session from './session' import { cookieJar } from './util/cookies' +import runEvents from './plugins/run_events' + // eslint-disable-next-line no-duplicate-imports import type { Socket } from '@packages/socket' -import path from 'path' -import { getCtx } from '@packages/data-context' -import { handleGraphQLSocketRequest } from '@packages/graphql/src/makeGraphQLServer' type StartListeningCallbacks = { onSocketConnection: (socket: any) => void @@ -82,12 +85,14 @@ export class SocketBase { private _isRunnerSocketConnected private _sendFocusBrowserMessage + protected supportsRunEvents: boolean protected experimentalSessionAndOrigin: boolean protected ended: boolean protected _io?: socketIo.SocketIOServer localBus: EventEmitter constructor (config: Record) { + this.supportsRunEvents = config.isTextTerminal || config.experimentalInteractiveRunEvents this.experimentalSessionAndOrigin = config.experimentalSessionAndOrigin this.ended = false this.localBus = new EventEmitter() @@ -554,6 +559,12 @@ export class SocketBase { openFile(fileDetails) }) + if (this.supportsRunEvents) { + socket.on('plugins:before:spec', async (spec) => { + await runEvents.execute('before:spec', {}, spec) + }) + } + reporterEvents.forEach((event) => { socket.on(event, (data) => { this.toRunner(event, data) diff --git a/packages/server/test/unit/open_project_spec.js b/packages/server/test/unit/open_project_spec.js index f209dccb9262..f8a327eaa16b 100644 --- a/packages/server/test/unit/open_project_spec.js +++ b/packages/server/test/unit/open_project_spec.js @@ -121,33 +121,6 @@ describe('lib/open_project', () => { sinon.stub(runEvents, 'execute').resolves() }) - it('executes before:spec if in interactive mode', function () { - this.config.experimentalInteractiveRunEvents = true - this.config.isTextTerminal = false - - return openProject.launch(this.browser, this.spec).then(() => { - expect(runEvents.execute).to.be.calledWith('before:spec', this.config, this.spec) - }) - }) - - it('does not execute before:spec if not in interactive mode', function () { - this.config.experimentalInteractiveRunEvents = true - this.config.isTextTerminal = true - - return openProject.launch(this.browser, this.spec).then(() => { - expect(runEvents.execute).not.to.be.calledWith('before:spec') - }) - }) - - it('does not execute before:spec if experimental flag is not enabled', function () { - this.config.experimentalInteractiveRunEvents = false - this.config.isTextTerminal = false - - return openProject.launch(this.browser, this.spec).then(() => { - expect(runEvents.execute).not.to.be.calledWith('before:spec') - }) - }) - it('executes after:spec on browser close if in interactive mode', function () { this.config.experimentalInteractiveRunEvents = true this.config.isTextTerminal = false diff --git a/system-tests/__snapshots__/plugin_run_events_spec.ts.js b/system-tests/__snapshots__/plugin_run_events_spec.ts.js index 6fc8553370d0..bd0e5734523c 100644 --- a/system-tests/__snapshots__/plugin_run_events_spec.ts.js +++ b/system-tests/__snapshots__/plugin_run_events_spec.ts.js @@ -5,10 +5,11 @@ exports['e2e plugin run events / sends events'] = ` (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 2 found (run_events_spec_1.cy.js, run_events_spec_2.cy.js) │ - │ Searched: cypress/e2e/* │ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 2 found (run_events_spec_1.cy.js, run_events_spec_2.cy.js) │ + │ Searched: cypress/e2e/* │ + │ Experiments: experimentalInteractiveRunEvents=true │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ before:run: cypress/e2e/run_events_spec_1.cy.js electron diff --git a/system-tests/projects/plugin-run-events/cypress.config.js b/system-tests/projects/plugin-run-events/cypress.config.js index 1efbc63b0f81..f66e513fe2b9 100644 --- a/system-tests/projects/plugin-run-events/cypress.config.js +++ b/system-tests/projects/plugin-run-events/cypress.config.js @@ -1,15 +1,24 @@ /* eslint-disable no-console */ const Promise = require('bluebird') +const fs = require('fs') +const path = require('path') module.exports = { - 'fixturesFolder': false, - 'e2e': { - 'supportFile': false, + fixturesFolder: false, + experimentalInteractiveRunEvents: true, + e2e: { + supportFile: false, setupNodeEvents (on, config) { on('before:run', (runDetails) => { const { specs, browser } = runDetails - console.log('before:run:', specs[0].relative, browser.name) + if (config.isTextTerminal) { + console.log('before:run:', specs[0].relative, browser.name) + } else { + const outputPath = path.join(process.cwd(), 'beforeRun.json') + + fs.writeFileSync(outputPath, JSON.stringify(runDetails)) + } return Promise.delay(10).then(() => { return console.log('before:run is awaited') @@ -27,7 +36,13 @@ module.exports = { }) on('before:spec', (spec) => { - console.log('before:spec:', spec.relative) + if (config.isTextTerminal) { + console.log('before:spec:', spec.relative) + } else { + const outputPath = path.join(process.cwd(), 'beforeSpec.json') + + fs.writeFileSync(outputPath, JSON.stringify(spec)) + } return Promise.delay(10).then(() => { return console.log('before:spec is awaited') From bcd7548d74fdb5dab4790f10f158bb4f3e9387bc Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 31 Aug 2022 14:55:28 -0400 Subject: [PATCH 05/49] refactor: move more of video capture into browser automations (#23587) --- packages/resolve-dist/lib/index.ts | 2 - .../server/lib/browsers/cdp_automation.ts | 14 ++- packages/server/lib/browsers/chrome.ts | 38 +++----- packages/server/lib/browsers/electron.ts | 96 +++++++++---------- packages/server/lib/browsers/firefox.ts | 10 +- packages/server/lib/browsers/utils.ts | 7 +- packages/server/lib/gui/windows.ts | 72 +++++++------- packages/server/lib/modes/interactive.ts | 29 +++--- packages/server/lib/modes/run.ts | 82 +++------------- packages/server/lib/open_project.ts | 2 +- packages/server/lib/saved_state.ts | 2 - packages/server/lib/video_capture.ts | 3 +- .../server/test/integration/cypress_spec.js | 6 +- .../server/test/unit/browsers/chrome_spec.js | 22 +++-- .../test/unit/browsers/electron_spec.js | 9 +- packages/server/test/unit/gui/windows_spec.ts | 11 +-- .../test/unit/modes/interactive_spec.js | 28 +++--- packages/types/src/server.ts | 10 +- 18 files changed, 184 insertions(+), 259 deletions(-) diff --git a/packages/resolve-dist/lib/index.ts b/packages/resolve-dist/lib/index.ts index 32840ce388cb..809e42773b1b 100644 --- a/packages/resolve-dist/lib/index.ts +++ b/packages/resolve-dist/lib/index.ts @@ -33,7 +33,5 @@ export const getPathToIndex = (pkg: RunnerPkg) => { } export const getPathToDesktopIndex = (graphqlPort: number) => { - // For now, if we see that there's a CYPRESS_INTERNAL_VITE_DEV - // we assume we're running Cypress targeting that (dev server) return `http://localhost:${graphqlPort}/__launchpad/index.html` } diff --git a/packages/server/lib/browsers/cdp_automation.ts b/packages/server/lib/browsers/cdp_automation.ts index df00b00d059d..4d47aa459d70 100644 --- a/packages/server/lib/browsers/cdp_automation.ts +++ b/packages/server/lib/browsers/cdp_automation.ts @@ -10,6 +10,7 @@ import { URL } from 'url' import type { Automation } from '../automation' import type { ResourceType, BrowserPreRequest, BrowserResponseReceived } from '@packages/proxy' +import type { WriteVideoFrame } from '@packages/types' export type CdpCommand = keyof ProtocolMapping.Commands @@ -168,9 +169,9 @@ export const normalizeResourceType = (resourceType: string | undefined): Resourc return ffToStandardResourceTypeMap[resourceType] || 'other' } -type SendDebuggerCommand = (message: CdpCommand, data?: any) => Promise +type SendDebuggerCommand = (message: T, data?: any) => Promise type SendCloseCommand = (shouldKeepTabOpen: boolean) => Promise | void -type OnFn = (eventName: CdpEvent, cb: Function) => void +type OnFn = (eventName: T, cb: (data: ProtocolMapping.Events[T][0]) => void) => void // the intersection of what's valid in CDP and what's valid in FFCDP // Firefox: https://searchfox.org/mozilla-central/rev/98a9257ca2847fad9a19631ac76199474516b31e/remote/cdp/domains/parent/Network.jsm#22 @@ -188,6 +189,15 @@ export class CdpAutomation { onFn('Network.responseReceived', this.onResponseReceived) } + async startVideoRecording (writeVideoFrame: WriteVideoFrame, screencastOpts?) { + this.onFn('Page.screencastFrame', async (e) => { + writeVideoFrame(Buffer.from(e.data, 'base64')) + await this.sendDebuggerCommandFn('Page.screencastFrameAck', { sessionId: e.sessionId }) + }) + + await this.sendDebuggerCommandFn('Page.startScreencast', screencastOpts) + } + static async create (sendDebuggerCommandFn: SendDebuggerCommand, onFn: OnFn, sendCloseCommandFn: SendCloseCommand, automation: Automation, experimentalSessionAndOrigin: boolean): Promise { const cdpAutomation = new CdpAutomation(sendDebuggerCommandFn, onFn, sendCloseCommandFn, automation, experimentalSessionAndOrigin) diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index c2500b1ee230..2a70a0b94105 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -20,7 +20,7 @@ import { BrowserCriClient } from './browser-cri-client' import type { LaunchedBrowser } from '@packages/launcher/lib/browsers' import type { CriClient } from './cri-client' import type { Automation } from '../automation' -import type { BrowserLaunchOpts, BrowserNewTabOpts } from '@packages/types' +import type { BrowserLaunchOpts, BrowserNewTabOpts, WriteVideoFrame } from '@packages/types' const debug = debugModule('cypress:server:browsers:chrome') @@ -249,22 +249,10 @@ const _disableRestorePagesPrompt = function (userDir) { .catch(() => { }) } -const _maybeRecordVideo = async function (client, options, browserMajorVersion) { - if (!options.onScreencastFrame) { - debug('options.onScreencastFrame is false') +async function _recordVideo (cdpAutomation: CdpAutomation, writeVideoFrame: WriteVideoFrame, browserMajorVersion: number) { + const opts = browserMajorVersion >= CHROME_VERSION_WITH_FPS_INCREASE ? screencastOpts() : screencastOpts(1) - return client - } - - debug('starting screencast') - client.on('Page.screencastFrame', (meta) => { - options.onScreencastFrame(meta) - client.send('Page.screencastFrameAck', { sessionId: meta.sessionId }) - }) - - await client.send('Page.startScreencast', browserMajorVersion >= CHROME_VERSION_WITH_FPS_INCREASE ? screencastOpts() : screencastOpts(1)) - - return client + await cdpAutomation.startVideoRecording(writeVideoFrame, opts) } // a utility function that navigates to the given URL @@ -434,7 +422,9 @@ const _handlePausedRequests = async (client) => { const _setAutomation = async (client: CriClient, automation: Automation, resetBrowserTargets: (shouldKeepTabOpen: boolean) => Promise, options: BrowserLaunchOpts) => { const cdpAutomation = await CdpAutomation.create(client.send, client.on, resetBrowserTargets, automation, !!options.experimentalSessionAndOrigin) - return automation.use(cdpAutomation) + automation.use(cdpAutomation) + + return cdpAutomation } export = { @@ -448,7 +438,7 @@ export = { _removeRootExtension, - _maybeRecordVideo, + _recordVideo, _navigateUsingCRI, @@ -468,7 +458,7 @@ export = { return browserCriClient }, - async _writeExtension (browser: Browser, options) { + async _writeExtension (browser: Browser, options: BrowserLaunchOpts) { if (browser.isHeadless) { debug('chrome is running headlessly, not installing extension') @@ -565,7 +555,7 @@ export = { await this.attachListeners(browser, options.url, pageCriClient, automation, options) }, - async connectToExisting (browser: Browser, options: BrowserLaunchOpts, automation) { + async connectToExisting (browser: Browser, options: BrowserLaunchOpts, automation: Automation) { const port = await protocol.getRemoteDebuggingPort() debug('connecting to existing chrome instance with url and debugging port', { url: options.url, port }) @@ -580,17 +570,17 @@ export = { await this._setAutomation(pageCriClient, automation, browserCriClient.resetBrowserTargets, options) }, - async attachListeners (browser: Browser, url: string, pageCriClient, automation: Automation, options: BrowserLaunchOpts & { onInitializeNewBrowserTab?: () => void }) { + async attachListeners (browser: Browser, url: string, pageCriClient: CriClient, automation: Automation, options: BrowserLaunchOpts | BrowserNewTabOpts) { if (!browserCriClient) throw new Error('Missing browserCriClient in attachListeners') - await this._setAutomation(pageCriClient, automation, browserCriClient.resetBrowserTargets, options) + const cdpAutomation = await this._setAutomation(pageCriClient, automation, browserCriClient.resetBrowserTargets, options) await pageCriClient.send('Page.enable') - await options.onInitializeNewBrowserTab?.() + await options['onInitializeNewBrowserTab']?.() await Promise.all([ - this._maybeRecordVideo(pageCriClient, options, browser.majorVersion), + options.writeVideoFrame && this._recordVideo(cdpAutomation, options.writeVideoFrame, browser.majorVersion), this._handleDownloads(pageCriClient, options.downloadsFolder, automation), ]) diff --git a/packages/server/lib/browsers/electron.ts b/packages/server/lib/browsers/electron.ts index 5e022761a508..7f3eba817379 100644 --- a/packages/server/lib/browsers/electron.ts +++ b/packages/server/lib/browsers/electron.ts @@ -8,9 +8,13 @@ import { CdpAutomation, screencastOpts, CdpCommand, CdpEvent } from './cdp_autom import * as savedState from '../saved_state' import utils from './utils' import * as errors from '../errors' -import type { BrowserInstance } from './types' +import type { Browser, BrowserInstance } from './types' import type { BrowserWindow, WebContents } from 'electron' import type { Automation } from '../automation' +import type { BrowserLaunchOpts, Preferences } from '@packages/types' + +// TODO: unmix these two types +type ElectronOpts = Windows.WindowOptions & BrowserLaunchOpts const debug = Debug('cypress:server:browsers:electron') const debugVerbose = Debug('cypress-verbose:server:browsers:electron') @@ -68,7 +72,7 @@ const _getAutomation = async function (win, options, parent) { // after upgrading to Electron 8, CDP screenshots can hang if a screencast is not also running // workaround: start and stop screencasts between screenshots // @see https://github.com/cypress-io/cypress/pull/6555#issuecomment-596747134 - if (!options.onScreencastFrame) { + if (!options.writeVideoFrame) { await sendCommand('Page.startScreencast', screencastOpts()) const ret = await fn(message, data) @@ -105,37 +109,18 @@ function _installExtensions (win: BrowserWindow, extensionPaths: string[], optio })) } -const _maybeRecordVideo = async function (webContents, options) { - const { onScreencastFrame } = options - - debug('maybe recording video %o', { onScreencastFrame }) - - if (!onScreencastFrame) { - return - } - - webContents.debugger.on('message', (event, method, params) => { - if (method === 'Page.screencastFrame') { - onScreencastFrame(params) - webContents.debugger.sendCommand('Page.screencastFrameAck', { sessionId: params.sessionId }) - } - }) - - await webContents.debugger.sendCommand('Page.startScreencast', screencastOpts()) -} - export = { - _defaultOptions (projectRoot, state, options, automation) { + _defaultOptions (projectRoot: string | undefined, state: Preferences, options: BrowserLaunchOpts, automation: Automation): ElectronOpts { const _this = this - const defaults = { - x: state.browserX, - y: state.browserY, + const defaults: Windows.WindowOptions = { + x: state.browserX || undefined, + y: state.browserY || undefined, width: state.browserWidth || 1280, height: state.browserHeight || 720, - devTools: state.isBrowserDevToolsOpen, minWidth: 100, minHeight: 100, + devTools: state.isBrowserDevToolsOpen || undefined, contextMenu: true, partition: this._getPartition(options), trackState: { @@ -148,8 +133,21 @@ export = { webPreferences: { sandbox: true, }, + show: !options.browser.isHeadless, + // prevents a tiny 1px padding around the window + // causing screenshots/videos to be off by 1px + resizable: !options.browser.isHeadless, + onCrashed () { + const err = errors.get('RENDERER_CRASHED') + + errors.log(err) + + if (!options.onError) throw new Error('Missing onError in onCrashed') + + options.onError(err) + }, onFocus () { - if (options.show) { + if (!options.browser.isHeadless) { return menu.set({ withInternalDevTools: true }) } }, @@ -176,18 +174,12 @@ export = { }, } - if (options.browser.isHeadless) { - // prevents a tiny 1px padding around the window - // causing screenshots/videos to be off by 1px - options.resizable = false - } - return _.defaultsDeep({}, options, defaults) }, _getAutomation, - async _render (url: string, automation: Automation, preferences, options: { projectRoot: string, isTextTerminal: boolean }) { + async _render (url: string, automation: Automation, preferences, options: { projectRoot?: string, isTextTerminal: boolean }) { const win = Windows.create(options.projectRoot, preferences) if (preferences.browser.isHeadless) { @@ -212,21 +204,25 @@ export = { const [parentX, parentY] = parent.getPosition() - options = this._defaultOptions(projectRoot, state, options, automation) + const electronOptions = this._defaultOptions(projectRoot, state, options, automation) - _.extend(options, { + _.extend(electronOptions, { x: parentX + 100, y: parentY + 100, trackState: false, + // in run mode, force new windows to automatically open with show: false + // this prevents window.open inside of javascript client code to cause a new BrowserWindow instance to open + // https://github.com/cypress-io/cypress/issues/123 + show: !options.isTextTerminal, }) - const win = Windows.create(projectRoot, options) + const win = Windows.create(projectRoot, electronOptions) // needed by electron since we prevented default and are creating // our own BrowserWindow (https://electron.atom.io/docs/api/web-contents/#event-new-window) e.newGuest = win - return this._launch(win, url, automation, options) + return this._launch(win, url, automation, electronOptions) }, async _launch (win: BrowserWindow, url: string, automation: Automation, options) { @@ -278,7 +274,7 @@ export = { automation.use(cdpAutomation) await Promise.all([ - _maybeRecordVideo(win.webContents, options), + options.writeVideoFrame && cdpAutomation.startVideoRecording(options.writeVideoFrame), this._handleDownloads(win, options.downloadsFolder, automation), ]) @@ -459,30 +455,26 @@ export = { throw new Error('Attempting to connect to existing browser for Cypress in Cypress which is not yet implemented for electron') }, - async open (browser, url, options, automation) { - const { projectRoot, isTextTerminal } = options - + async open (browser: Browser, url: string, options: BrowserLaunchOpts, automation: Automation) { debug('open %o', { browser, url }) - const State = await savedState.create(projectRoot, isTextTerminal) + const State = await savedState.create(options.projectRoot, options.isTextTerminal) const state = await State.get() debug('received saved state %o', state) // get our electron default options - // TODO: this is bad, don't mutate the options object - options = this._defaultOptions(projectRoot, state, options, automation) - - // get the GUI window defaults now - options = Windows.defaults(options) + const electronOptions: ElectronOpts = Windows.defaults( + this._defaultOptions(options.projectRoot, state, options, automation), + ) - debug('browser window options %o', _.omitBy(options, _.isFunction)) + debug('browser window options %o', _.omitBy(electronOptions, _.isFunction)) const defaultLaunchOptions = utils.getDefaultLaunchOptions({ - preferences: options, + preferences: electronOptions, }) - const launchOptions = await utils.executeBeforeBrowserLaunch(browser, defaultLaunchOptions, options) + const launchOptions = await utils.executeBeforeBrowserLaunch(browser, defaultLaunchOptions, electronOptions) const { preferences } = launchOptions @@ -493,7 +485,7 @@ export = { isTextTerminal: options.isTextTerminal, }) - await _installExtensions(win, launchOptions.extensions, options) + await _installExtensions(win, launchOptions.extensions, electronOptions) // cause the webview to receive focus so that // native browser focus + blur events fire correctly diff --git a/packages/server/lib/browsers/firefox.ts b/packages/server/lib/browsers/firefox.ts index cb454643c837..4ebcd132f36e 100644 --- a/packages/server/lib/browsers/firefox.ts +++ b/packages/server/lib/browsers/firefox.ts @@ -1,5 +1,4 @@ import _ from 'lodash' -import Bluebird from 'bluebird' import fs from 'fs-extra' import Debug from 'debug' import getPort from 'get-port' @@ -21,6 +20,7 @@ import type { BrowserCriClient } from './browser-cri-client' import type { Automation } from '../automation' import { getCtx } from '@packages/data-context' import { getError } from '@packages/errors' +import type { BrowserLaunchOpts, BrowserNewTabOpts } from '@packages/types' const debug = Debug('cypress:server:browsers:firefox') @@ -371,7 +371,7 @@ export function _createDetachedInstance (browserInstance: BrowserInstance, brows return detachedInstance } -export async function connectToNewSpec (browser: Browser, options: any = {}, automation: Automation) { +export async function connectToNewSpec (browser: Browser, options: BrowserNewTabOpts, automation: Automation) { await firefoxUtil.connectToNewSpec(options, automation, browserCriClient) } @@ -379,7 +379,7 @@ export function connectToExisting () { getCtx().onWarning(getError('UNEXPECTED_INTERNAL_ERROR', new Error('Attempting to connect to existing browser for Cypress in Cypress which is not yet implemented for firefox'))) } -export async function open (browser: Browser, url, options: any = {}, automation): Promise { +export async function open (browser: Browser, url: string, options: BrowserLaunchOpts, automation: Automation): Promise { // see revision comment here https://wiki.mozilla.org/index.php?title=WebDriver/RemoteProtocol&oldid=1234946 const hasCdp = browser.majorVersion >= 86 const defaultLaunchOptions = utils.getDefaultLaunchOptions({ @@ -441,7 +441,7 @@ export async function open (browser: Browser, url, options: any = {}, automation const [ foxdriverPort, marionettePort, - ] = await Bluebird.all([getPort(), getPort()]) + ] = await Promise.all([getPort(), getPort()]) defaultLaunchOptions.preferences['devtools.debugger.remote-port'] = foxdriverPort defaultLaunchOptions.preferences['marionette.port'] = marionettePort @@ -452,7 +452,7 @@ export async function open (browser: Browser, url, options: any = {}, automation cacheDir, extensionDest, launchOptions, - ] = await Bluebird.all([ + ] = await Promise.all([ utils.ensureCleanCache(browser, options.isTextTerminal), utils.writeExtension(browser, options.isTextTerminal, options.proxyUrl, options.socketIoRoute), utils.executeBeforeBrowserLaunch(browser, defaultLaunchOptions, options), diff --git a/packages/server/lib/browsers/utils.ts b/packages/server/lib/browsers/utils.ts index 7c9062534d83..6fea932747ed 100644 --- a/packages/server/lib/browsers/utils.ts +++ b/packages/server/lib/browsers/utils.ts @@ -1,7 +1,7 @@ /* eslint-disable no-redeclare */ import Bluebird from 'bluebird' import _ from 'lodash' -import type { FoundBrowser } from '@packages/types' +import type { BrowserLaunchOpts, FoundBrowser } from '@packages/types' import * as errors from '../errors' import * as plugins from '../plugins' import { getError } from '@packages/errors' @@ -132,13 +132,14 @@ async function executeBeforeBrowserLaunch (browser, launchOptions: typeof defaul return launchOptions } -function extendLaunchOptionsFromPlugins (launchOptions, pluginConfigResult, options) { +function extendLaunchOptionsFromPlugins (launchOptions, pluginConfigResult, options: BrowserLaunchOpts) { // if we returned an array from the plugin // then we know the user is using the deprecated // interface and we need to warn them // TODO: remove this logic in >= v5.0.0 if (pluginConfigResult[0]) { - options.onWarning(getError( + // eslint-disable-next-line no-console + (options.onWarning || console.warn)(getError( 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', )) diff --git a/packages/server/lib/gui/windows.ts b/packages/server/lib/gui/windows.ts index 5e3a11a928a8..66695fb52568 100644 --- a/packages/server/lib/gui/windows.ts +++ b/packages/server/lib/gui/windows.ts @@ -3,34 +3,36 @@ import Bluebird from 'bluebird' import { BrowserWindow } from 'electron' import Debug from 'debug' import * as savedState from '../saved_state' -import { getPathToDesktopIndex } from '@packages/resolve-dist' const debug = Debug('cypress:server:windows') export type WindowOptions = Electron.BrowserWindowConstructorOptions & { type?: 'INDEX' - url?: string devTools?: boolean graphqlPort?: number + contextMenu?: boolean + partition?: string + /** + * Synchronizes properties of browserwindow with local state + */ + trackState?: TrackStateMap + onFocus?: () => void + onNewWindow?: (e, url, frameName, disposition, options) => Promise + onCrashed?: () => void } +export type WindowOpenOptions = WindowOptions & { url: string } + +type TrackStateMap = Record<'width' | 'height' | 'x' | 'y' | 'devTools', string> + let windows = {} let recentlyCreatedWindow = false -const getUrl = function (type, port: number) { - switch (type) { - case 'INDEX': - return getPathToDesktopIndex(port) - - default: - throw new Error(`No acceptable window type found for: '${type}'`) - } -} -const getByType = (type) => { +const getByType = (type: string) => { return windows[type] } -const setWindowProxy = function (win) { +const setWindowProxy = function (win: BrowserWindow) { if (!process.env.HTTP_PROXY) { return } @@ -41,7 +43,7 @@ const setWindowProxy = function (win) { }) } -export function installExtension (win: BrowserWindow, path) { +export function installExtension (win: BrowserWindow, path: string) { return win.webContents.session.loadExtension(path) .then((data) => { debug('electron extension installed %o', { data, path }) @@ -70,7 +72,7 @@ export function reset () { windows = {} } -export function destroy (type) { +export function destroy (type: string) { let win if (type && (win = getByType(type))) { @@ -78,7 +80,7 @@ export function destroy (type) { } } -export function get (type) { +export function get (type: string) { return getByType(type) || (() => { throw new Error(`No window exists for: '${type}'`) })() @@ -143,7 +145,7 @@ export function defaults (options = {}) { }) } -export function create (projectRoot, _options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow) { +export function create (projectRoot, _options: WindowOptions, newBrowserWindow = _newBrowserWindow) { const options = defaults(_options) if (options.show === false) { @@ -213,15 +215,15 @@ export function create (projectRoot, _options: WindowOptions = {}, newBrowserWin } // open launchpad BrowserWindow -export function open (projectRoot, launchpadPort: number, options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow): Bluebird { +export async function open (projectRoot: string, options: WindowOpenOptions, newBrowserWindow = _newBrowserWindow): Promise { // if we already have a window open based // on that type then just show + focus it! - let win = getByType(options.type) + const knownWin = options.type && getByType(options.type) - if (win) { - win.show() + if (knownWin) { + knownWin.show() - return Bluebird.resolve(win) + return Bluebird.resolve(knownWin) } recentlyCreatedWindow = true @@ -235,11 +237,7 @@ export function open (projectRoot, launchpadPort: number, options: WindowOptions }, }) - if (!options.url) { - options.url = getUrl(options.type, launchpadPort) - } - - win = create(projectRoot, options, newBrowserWindow) + const win = create(projectRoot, options, newBrowserWindow) debug('creating electron window with options %o', options) @@ -251,21 +249,15 @@ export function open (projectRoot, launchpadPort: number, options: WindowOptions }) } - // enable our url to be a promise - // and wait for this to be resolved - return Bluebird.join( - options.url, - setWindowProxy(win), - ) - .spread((url) => { - // navigate the window here! - win.loadURL(url) - - recentlyCreatedWindow = false - }).thenReturn(win) + await setWindowProxy(win) + await win.loadURL(options.url) + + recentlyCreatedWindow = false + + return win } -export function trackState (projectRoot, isTextTerminal, win, keys) { +export function trackState (projectRoot, isTextTerminal, win, keys: TrackStateMap) { const isDestroyed = () => { return win.isDestroyed() } diff --git a/packages/server/lib/modes/interactive.ts b/packages/server/lib/modes/interactive.ts index 5796f2023904..72e0a8c2b902 100644 --- a/packages/server/lib/modes/interactive.ts +++ b/packages/server/lib/modes/interactive.ts @@ -11,9 +11,10 @@ import { globalPubSub, getCtx, clearCtx } from '@packages/data-context' // eslint-disable-next-line no-duplicate-imports import type { WebContents } from 'electron' -import type { LaunchArgs } from '@packages/types' +import type { LaunchArgs, Preferences } from '@packages/types' import debugLib from 'debug' +import { getPathToDesktopIndex } from '@packages/resolve-dist' const debug = debugLib('cypress:server:interactive') @@ -26,7 +27,7 @@ export = { return os.platform() === 'darwin' }, - getWindowArgs (state) { + getWindowArgs (url: string, state: Preferences) { // Electron Window's arguments // These options are passed to Electron's BrowserWindow const minWidth = Math.round(/* 13" MacBook Air */ 1792 / 3) // Thirds @@ -46,6 +47,7 @@ export = { } const common = { + url, // The backgroundColor should match the value we will show in the // launchpad frontend. @@ -129,16 +131,10 @@ export = { return args[os.platform()] }, - /** - * @param {import('@packages/types').LaunchArgs} options - * @returns - */ - ready (options: {projectRoot?: string} = {}, port: number) { + async ready (options: LaunchArgs, launchpadPort: number) { const { projectRoot } = options const ctx = getCtx() - // TODO: potentially just pass an event emitter - // instance here instead of callback functions menu.set({ withInternalDevTools: isDev(), onLogOutClicked () { @@ -149,15 +145,14 @@ export = { }, }) - return savedState.create(projectRoot, false).then((state) => state.get()) - .then((state) => { - return Windows.open(projectRoot, port, this.getWindowArgs(state)) - .then((win) => { - ctx?.actions.electron.setBrowserWindow(win) + const State = await savedState.create(projectRoot, false) + const state = await State.get() + const url = getPathToDesktopIndex(launchpadPort) + const win = await Windows.open(projectRoot, this.getWindowArgs(url, state)) - return win - }) - }) + ctx?.actions.electron.setBrowserWindow(win) + + return win }, async run (options: LaunchArgs, _loading: Promise) { diff --git a/packages/server/lib/modes/run.ts b/packages/server/lib/modes/run.ts index 7ec8c54a7c26..cb00eb5c3fc0 100644 --- a/packages/server/lib/modes/run.ts +++ b/packages/server/lib/modes/run.ts @@ -1,6 +1,5 @@ /* eslint-disable no-console, @cypress/dev/arrow-body-multiline-braces */ import _ from 'lodash' -import la from 'lazy-ass' import pkg from '@packages/root' import path from 'path' import chalk from 'chalk' @@ -22,7 +21,7 @@ import random from '../util/random' import system from '../util/system' import chromePolicyCheck from '../util/chrome_policy_check' import * as objUtils from '../util/obj_utils' -import type { SpecWithRelativeRoot, SpecFile, TestingType, OpenProjectLaunchOpts, FoundBrowser } from '@packages/types' +import type { SpecWithRelativeRoot, SpecFile, TestingType, OpenProjectLaunchOpts, FoundBrowser, WriteVideoFrame } from '@packages/types' import type { Cfg } from '../project-base' import type { Browser } from '../browsers/types' import * as printResults from '../util/print-run' @@ -41,7 +40,7 @@ let exitEarly = (err) => { earlyExitErr = err } let earlyExitErr: Error -let currentWriteVideoFrameCallback: videoCapture.WriteVideoFrame +let currentWriteVideoFrameCallback: WriteVideoFrame let currentSetScreenshotMetadata: SetScreenshotMetadata const debug = Debug('cypress:server:run') @@ -121,70 +120,6 @@ async function getProjectId (project, id) { } } -const getDefaultBrowserOptsByFamily = (browser, project, writeVideoFrame, onError) => { - la(browserUtils.isBrowserFamily(browser.family), 'invalid browser family in', browser) - - if (browser.name === 'electron') { - return getElectronProps(browser.isHeaded, writeVideoFrame, onError) - } - - if (browser.family === 'chromium') { - return getCdpVideoProp(writeVideoFrame) - } - - if (browser.family === 'firefox') { - return getFirefoxProps(project, writeVideoFrame) - } - - return {} -} - -const getFirefoxProps = (project, writeVideoFrame) => { - if (writeVideoFrame) { - project.on('capture:video:frames', writeVideoFrame) - - return { onScreencastFrame: true } - } - - return {} -} - -const getCdpVideoProp = (writeVideoFrame) => { - if (!writeVideoFrame) { - return {} - } - - return { - onScreencastFrame: (e) => { - // https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame - writeVideoFrame(Buffer.from(e.data, 'base64')) - }, - } -} - -const getElectronProps = (isHeaded, writeVideoFrame, onError) => { - return { - ...getCdpVideoProp(writeVideoFrame), - width: 1280, - height: 720, - show: isHeaded, - onCrashed () { - const err = errors.get('RENDERER_CRASHED') - - errors.log(err) - - onError(err) - }, - onNewWindow (e, url, frameName, disposition, options) { - // force new windows to automatically open with show: false - // this prevents window.open inside of javascript client code - // to cause a new BrowserWindow instance to open - // https://github.com/cypress-io/cypress/issues/123 - options.show = false - }, - } -} - const sumByProp = (runs, prop) => { return _.sumBy(runs, prop) || 0 } @@ -380,15 +315,20 @@ async function postProcessRecording (name, cname, videoCompression, shouldUpload return continueProcessing(onProgress) } -function launchBrowser (options: { browser: Browser, spec: SpecWithRelativeRoot, writeVideoFrame?: videoCapture.WriteVideoFrame, setScreenshotMetadata: SetScreenshotMetadata, project: Project, screenshots: ScreenshotMetadata[], projectRoot: string, shouldLaunchNewTab: boolean, onError: (err: Error) => void }) { - const { browser, spec, writeVideoFrame, setScreenshotMetadata, project, screenshots, projectRoot, shouldLaunchNewTab, onError } = options +function launchBrowser (options: { browser: Browser, spec: SpecWithRelativeRoot, writeVideoFrame?: WriteVideoFrame, setScreenshotMetadata: SetScreenshotMetadata, project: Project, screenshots: ScreenshotMetadata[], projectRoot: string, shouldLaunchNewTab: boolean, onError: (err: Error) => void }) { + const { browser, spec, setScreenshotMetadata, project, screenshots, projectRoot, shouldLaunchNewTab, onError } = options const warnings = {} + if (options.writeVideoFrame && browser.family === 'firefox') { + project.on('capture:video:frames', options.writeVideoFrame) + } + const browserOpts: OpenProjectLaunchOpts = { - ...getDefaultBrowserOptsByFamily(browser, project, writeVideoFrame, onError), projectRoot, shouldLaunchNewTab, + onError, + writeVideoFrame: options.writeVideoFrame, automationMiddleware: { onBeforeRequest (message, data) { if (message === 'take:screenshot') { @@ -491,7 +431,7 @@ function writeVideoFrameCallback (data: Buffer) { return currentWriteVideoFrameCallback(data) } -function waitForBrowserToConnect (options: { project: Project, socketId: string, onError: (err: Error) => void, writeVideoFrame?: videoCapture.WriteVideoFrame, spec: SpecWithRelativeRoot, isFirstSpec: boolean, testingType: string, experimentalSingleTabRunMode: boolean, browser: Browser, screenshots: ScreenshotMetadata[], projectRoot: string, shouldLaunchNewTab: boolean, webSecurity: boolean }) { +function waitForBrowserToConnect (options: { project: Project, socketId: string, onError: (err: Error) => void, writeVideoFrame?: WriteVideoFrame, spec: SpecWithRelativeRoot, isFirstSpec: boolean, testingType: string, experimentalSingleTabRunMode: boolean, browser: Browser, screenshots: ScreenshotMetadata[], projectRoot: string, shouldLaunchNewTab: boolean, webSecurity: boolean }) { if (globalThis.CY_TEST_MOCK?.waitForBrowserToConnect) return Promise.resolve() const { project, socketId, onError, writeVideoFrame, spec } = options diff --git a/packages/server/lib/open_project.ts b/packages/server/lib/open_project.ts index e249a094634b..c1f4175e425f 100644 --- a/packages/server/lib/open_project.ts +++ b/packages/server/lib/open_project.ts @@ -84,7 +84,7 @@ export class OpenProject { proxyServer: cfg.proxyServer, socketIoRoute: cfg.socketIoRoute, chromeWebSecurity: cfg.chromeWebSecurity, - isTextTerminal: cfg.isTextTerminal, + isTextTerminal: !!cfg.isTextTerminal, downloadsFolder: cfg.downloadsFolder, experimentalSessionAndOrigin: cfg.experimentalSessionAndOrigin, experimentalModifyObstructiveThirdPartyCode: cfg.experimentalModifyObstructiveThirdPartyCode, diff --git a/packages/server/lib/saved_state.ts b/packages/server/lib/saved_state.ts index 3ee675e2c46b..47fbaa7ff41e 100644 --- a/packages/server/lib/saved_state.ts +++ b/packages/server/lib/saved_state.ts @@ -13,8 +13,6 @@ const debug = Debug('cypress:server:saved_state') const stateFiles: Record = {} -// TODO: remove `showedOnBoardingModal` from this list - it is only included so that misleading `allowed` are not thrown -// now that it has been removed from use export const formStatePath = (projectRoot?: string) => { return Bluebird.try(() => { debug('making saved state from %s', cwd()) diff --git a/packages/server/lib/video_capture.ts b/packages/server/lib/video_capture.ts index 9d25331978d4..0bdad7cfbe0a 100644 --- a/packages/server/lib/video_capture.ts +++ b/packages/server/lib/video_capture.ts @@ -7,8 +7,7 @@ import Bluebird from 'bluebird' import { path as ffmpegPath } from '@ffmpeg-installer/ffmpeg' import BlackHoleStream from 'black-hole-stream' import { fs } from './util/fs' - -export type WriteVideoFrame = (data: Buffer) => void +import type { WriteVideoFrame } from '@packages/types' const debug = Debug('cypress:server:video') const debugVerbose = Debug('cypress-verbose:server:video') diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index caa4fa28d6dd..55b957073da4 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -510,7 +510,9 @@ describe('lib/cypress', () => { .then(() => { expect(browsers.open).to.be.calledWithMatch(ELECTRON_BROWSER, { proxyServer: 'http://localhost:8888', - show: true, + browser: { + isHeadless: false, + }, }) this.expectExitWith(0) @@ -1022,7 +1024,7 @@ describe('lib/cypress', () => { browser: 'electron', foo: 'bar', onNewWindow: sinon.match.func, - onScreencastFrame: sinon.match.func, + writeVideoFrame: sinon.match.func, }) this.expectExitWith(0) diff --git a/packages/server/test/unit/browsers/chrome_spec.js b/packages/server/test/unit/browsers/chrome_spec.js index 75352873edde..7f3faafb3259 100644 --- a/packages/server/test/unit/browsers/chrome_spec.js +++ b/packages/server/test/unit/browsers/chrome_spec.js @@ -325,14 +325,14 @@ describe('lib/browsers/chrome', () => { // https://github.com/cypress-io/cypress/issues/9265 it('respond ACK after receiving new screenshot frame', function () { - const frameMeta = { data: Buffer.from(''), sessionId: '1' } + const frameMeta = { data: Buffer.from('foo'), sessionId: '1' } const write = sinon.stub() - const options = { onScreencastFrame: write } + const options = { writeVideoFrame: write } return this.onCriEvent('Page.screencastFrame', frameMeta, options) .then(() => { expect(this.pageCriClient.send).to.have.been.calledWith('Page.startScreencast') - expect(write).to.have.been.calledWith(frameMeta) + expect(write).to.have.been.calledWithMatch((arg) => Buffer.isBuffer(arg) && arg.length > 0) expect(this.pageCriClient.send).to.have.been.calledWith('Page.screencastFrameAck', { sessionId: frameMeta.sessionId }) }) }) @@ -516,12 +516,18 @@ describe('lib/browsers/chrome', () => { } let onInitializeNewBrowserTabCalled = false - const options = { ...openOpts, url: 'https://www.google.com', downloadsFolder: '/tmp/folder', onInitializeNewBrowserTab: () => { - onInitializeNewBrowserTabCalled = true - } } + const options = { + ...openOpts, + url: 'https://www.google.com', + downloadsFolder: '/tmp/folder', + writeVideoFrame: () => {}, + onInitializeNewBrowserTab: () => { + onInitializeNewBrowserTabCalled = true + }, + } sinon.stub(chrome, '_getBrowserCriClient').returns(browserCriClient) - sinon.stub(chrome, '_maybeRecordVideo').withArgs(pageCriClient, options, 354).resolves() + sinon.stub(chrome, '_recordVideo').withArgs(sinon.match.object, options.writeVideoFrame, 354).resolves() sinon.stub(chrome, '_navigateUsingCRI').withArgs(pageCriClient, options.url, 354).resolves() sinon.stub(chrome, '_handleDownloads').withArgs(pageCriClient, options.downloadFolder, automation).resolves() @@ -529,7 +535,7 @@ describe('lib/browsers/chrome', () => { expect(automation.use).to.be.called expect(chrome._getBrowserCriClient).to.be.called - expect(chrome._maybeRecordVideo).to.be.called + expect(chrome._recordVideo).to.be.called expect(chrome._navigateUsingCRI).to.be.called expect(chrome._handleDownloads).to.be.called expect(onInitializeNewBrowserTabCalled).to.be.true diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index 96f152d61f62..194bd1fecd3e 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -690,15 +690,16 @@ describe('lib/browsers/electron', () => { }) it('.onFocus', function () { - let opts = electron._defaultOptions('/foo', this.state, { show: true, browser: {} }) + const headlessOpts = electron._defaultOptions('/foo', this.state, { browser: { isHeadless: false } }) - opts.onFocus() + headlessOpts.onFocus() expect(menu.set).to.be.calledWith({ withInternalDevTools: true }) menu.set.reset() - opts = electron._defaultOptions('/foo', this.state, { show: false, browser: {} }) - opts.onFocus() + const headedOpts = electron._defaultOptions('/foo', this.state, { browser: { isHeadless: true } }) + + headedOpts.onFocus() expect(menu.set).not.to.be.called }) diff --git a/packages/server/test/unit/gui/windows_spec.ts b/packages/server/test/unit/gui/windows_spec.ts index ad7dee54b92e..4f14683ce768 100644 --- a/packages/server/test/unit/gui/windows_spec.ts +++ b/packages/server/test/unit/gui/windows_spec.ts @@ -9,7 +9,6 @@ import { EventEmitter } from 'events' import { BrowserWindow } from 'electron' import * as Windows from '../../../lib/gui/windows' import * as savedState from '../../../lib/saved_state' -import { getPathToDesktopIndex } from '@packages/resolve-dist' const DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0 Chrome/59.0.3071.115 Electron/1.8.2 Safari/537.36' @@ -42,23 +41,21 @@ describe('lib/gui/windows', () => { context('.open', () => { it('sets default options', function () { - const options: Windows.WindowOptions = { + const options: Windows.WindowOpenOptions = { type: 'INDEX', + url: 'foo', } - return Windows.open('/path/to/project', 1234, options, () => this.win) + return Windows.open('/path/to/project', options, () => this.win) .then((win) => { expect(options).to.include({ height: 500, width: 600, type: 'INDEX', show: true, - url: getPathToDesktopIndex(1234), }) - expect(win.loadURL).to.be.calledWith(getPathToDesktopIndex( - 1234, - )) + expect(win.loadURL).to.be.calledWith('foo') }) }) }) diff --git a/packages/server/test/unit/modes/interactive_spec.js b/packages/server/test/unit/modes/interactive_spec.js index 826e5974cc8b..2c6967e71fba 100644 --- a/packages/server/test/unit/modes/interactive_spec.js +++ b/packages/server/test/unit/modes/interactive_spec.js @@ -27,13 +27,13 @@ describe('gui/interactive', () => { context('.getWindowArgs', () => { it('quits app when onClose is called', () => { electron.app.quit = sinon.stub() - interactiveMode.getWindowArgs({}).onClose() + interactiveMode.getWindowArgs('http://app', {}).onClose() expect(electron.app.quit).to.be.called }) it('tracks state properties', () => { - const { trackState } = interactiveMode.getWindowArgs({}) + const { trackState } = interactiveMode.getWindowArgs('http://app', {}) const args = _.pick(trackState, 'width', 'height', 'x', 'y', 'devTools') @@ -51,49 +51,49 @@ describe('gui/interactive', () => { // Use the saved value if it's valid describe('when no dimension', () => { it('renders with preferred width if no width saved', () => { - expect(interactiveMode.getWindowArgs({}).width).to.equal(1200) + expect(interactiveMode.getWindowArgs('http://app', {}).width).to.equal(1200) }) it('renders with preferred height if no height saved', () => { - expect(interactiveMode.getWindowArgs({}).height).to.equal(800) + expect(interactiveMode.getWindowArgs('http://app', {}).height).to.equal(800) }) }) describe('when saved dimension is too small', () => { it('uses the preferred width', () => { - expect(interactiveMode.getWindowArgs({ appWidth: 1 }).width).to.equal(1200) + expect(interactiveMode.getWindowArgs('http://app', { appWidth: 1 }).width).to.equal(1200) }) it('uses the preferred height', () => { - expect(interactiveMode.getWindowArgs({ appHeight: 1 }).height).to.equal(800) + expect(interactiveMode.getWindowArgs('http://app', { appHeight: 1 }).height).to.equal(800) }) }) describe('when saved dimension is within min/max dimension', () => { it('uses the saved width', () => { - expect(interactiveMode.getWindowArgs({ appWidth: 1500 }).width).to.equal(1500) + expect(interactiveMode.getWindowArgs('http://app', { appWidth: 1500 }).width).to.equal(1500) }) it('uses the saved height', () => { - expect(interactiveMode.getWindowArgs({ appHeight: 1500 }).height).to.equal(1500) + expect(interactiveMode.getWindowArgs('http://app', { appHeight: 1500 }).height).to.equal(1500) }) }) }) it('renders with saved x if it exists', () => { - expect(interactiveMode.getWindowArgs({ appX: 3 }).x).to.equal(3) + expect(interactiveMode.getWindowArgs('http://app', { appX: 3 }).x).to.equal(3) }) it('renders with no x if no x saved', () => { - expect(interactiveMode.getWindowArgs({}).x).to.be.undefined + expect(interactiveMode.getWindowArgs('http://app', {}).x).to.be.undefined }) it('renders with saved y if it exists', () => { - expect(interactiveMode.getWindowArgs({ appY: 4 }).y).to.equal(4) + expect(interactiveMode.getWindowArgs('http://app', { appY: 4 }).y).to.equal(4) }) it('renders with no y if no y saved', () => { - expect(interactiveMode.getWindowArgs({}).y).to.be.undefined + expect(interactiveMode.getWindowArgs('http://app', {}).y).to.be.undefined }) describe('on window focus', () => { @@ -105,7 +105,7 @@ describe('gui/interactive', () => { const env = process.env['CYPRESS_INTERNAL_ENV'] process.env['CYPRESS_INTERNAL_ENV'] = 'development' - interactiveMode.getWindowArgs({}).onFocus() + interactiveMode.getWindowArgs('http://app', {}).onFocus() expect(menu.set.lastCall.args[0].withInternalDevTools).to.be.true process.env['CYPRESS_INTERNAL_ENV'] = env }) @@ -114,7 +114,7 @@ describe('gui/interactive', () => { const env = process.env['CYPRESS_INTERNAL_ENV'] process.env['CYPRESS_INTERNAL_ENV'] = 'production' - interactiveMode.getWindowArgs({}).onFocus() + interactiveMode.getWindowArgs('http://app', {}).onFocus() expect(menu.set.lastCall.args[0].withInternalDevTools).to.be.false process.env['CYPRESS_INTERNAL_ENV'] = env }) diff --git a/packages/types/src/server.ts b/packages/types/src/server.ts index fae60c3fae53..ce1540bb09b7 100644 --- a/packages/types/src/server.ts +++ b/packages/types/src/server.ts @@ -2,23 +2,27 @@ import type { FoundBrowser } from './browser' import type { ReceivedCypressOptions } from './config' import type { PlatformName } from './platform' +export type WriteVideoFrame = (data: Buffer) => void + export type OpenProjectLaunchOpts = { projectRoot: string shouldLaunchNewTab: boolean automationMiddleware: AutomationMiddleware + writeVideoFrame?: WriteVideoFrame onWarning: (err: Error) => void + onError: (err: Error) => void } export type BrowserLaunchOpts = { browsers: FoundBrowser[] - browser: FoundBrowser + browser: FoundBrowser & { isHeadless: boolean } url: string | undefined proxyServer: string + isTextTerminal: boolean onBrowserClose?: (...args: unknown[]) => void onBrowserOpen?: (...args: unknown[]) => void - onError?: (err: Error) => void } & Partial // TODO: remove the `Partial` here by making it impossible for openProject.launch to be called w/o OpenProjectLaunchOpts -& Pick +& Pick export type BrowserNewTabOpts = { onInitializeNewBrowserTab: () => void } & BrowserLaunchOpts From a1849c2da067921a3659e2c9a77556bb4d57e02c Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Wed, 31 Aug 2022 17:27:34 -0500 Subject: [PATCH 06/49] chore: Update release process doc --- guides/release-process.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/release-process.md b/guides/release-process.md index ad06cbb37c7d..10f52f058f3a 100644 --- a/guides/release-process.md +++ b/guides/release-process.md @@ -77,7 +77,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy 3. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version. -4. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.tgz`, publishing can proceed. +4. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/develop-/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/npm/X.Y.Z/linux-x64/develop-/cypress.tgz`, publishing can proceed. 5. Install and test the pre-release version to make sure everything is working. - Get the pre-release version that matches your system from the latest develop commit. @@ -169,7 +169,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy git pull origin develop git log --pretty=oneline # copy sha of the previous commit - git tag -a vX.Y.Z + git tag -a vX.Y.Z -m vX.Y.Z git push origin vX.Y.Z ``` From 33f6f8c493dd0fbf5fecf03a3195eda1f489f4ff Mon Sep 17 00:00:00 2001 From: Adam Stone Date: Wed, 31 Aug 2022 22:05:55 -0400 Subject: [PATCH 07/49] chore: Default dev mode to production cloud rather than staging (#23554) * chore: Default to production cloud rather than staging * Revert changes to package cypress configs * Revert changes to frontend-shared config --- scripts/gulp/gulpConstants.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/gulp/gulpConstants.ts b/scripts/gulp/gulpConstants.ts index fd736353ba51..191267e41174 100644 --- a/scripts/gulp/gulpConstants.ts +++ b/scripts/gulp/gulpConstants.ts @@ -8,7 +8,7 @@ declare global { } } -export const DEFAULT_INTERNAL_CLOUD_ENV = process.env.CYPRESS_INTERNAL_ENV || 'staging' +export const DEFAULT_INTERNAL_CLOUD_ENV = process.env.CYPRESS_INTERNAL_ENV || 'production' export type MODES = 'dev' | 'devWatch' | 'test' @@ -22,12 +22,12 @@ export const ENV_VARS = { // Uses the "built" vite assets, not the served ones DEV_OPEN: { CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig - CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // staging for now, until we get an e2e workflow w/ cloud project + CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, }, // Used when we're running Cypress in true "development" mode DEV: { CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig - CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // staging for now, until we get an e2e workflow w/ cloud project + CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, }, } From b44cb588912a3ba749d9e1118cb54d3b0113e8a2 Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Thu, 1 Sep 2022 14:14:02 -0500 Subject: [PATCH 08/49] chore: fix paths for windows (#23649) Co-authored-by: Lachlan Miller --- circle.yml | 2 +- packages/app/cypress/e2e/runner/pluginEvents.cy.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/circle.yml b/circle.yml index ac2a45859dbf..292d39b43978 100644 --- a/circle.yml +++ b/circle.yml @@ -66,7 +66,7 @@ windowsWorkflowFilters: &windows-workflow-filters or: - equal: [ develop, << pipeline.git.branch >> ] - equal: [ linux-arm64, << pipeline.git.branch >> ] - - equal: [ 'marktnoonan/windows-path-fix', << pipeline.git.branch >> ] + - equal: [ 'fix-windows', << pipeline.git.branch >> ] - equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ] - matches: pattern: "-release$" diff --git a/packages/app/cypress/e2e/runner/pluginEvents.cy.ts b/packages/app/cypress/e2e/runner/pluginEvents.cy.ts index e7ef1509f048..223b3019a026 100644 --- a/packages/app/cypress/e2e/runner/pluginEvents.cy.ts +++ b/packages/app/cypress/e2e/runner/pluginEvents.cy.ts @@ -1,4 +1,4 @@ -const path = require('path') +import { getPathForPlatform } from '../../../src/paths' describe('plugin events', () => { it('supports "before:run" event', () => { @@ -17,7 +17,7 @@ describe('plugin events', () => { passCount: 1, }) - cy.readFile(path.join(projectRoot, 'beforeRun.json')) + cy.readFile(`${projectRoot}/beforeRun.json`) .then((details) => { expect(details).to.have.property('config') expect(details).to.have.property('cypressVersion') @@ -42,14 +42,14 @@ describe('plugin events', () => { passCount: 1, }) - cy.readFile(path.join(projectRoot, 'beforeSpec.json')) + cy.readFile(`${projectRoot}/beforeSpec.json`) .then((spec) => { expect(spec).to.deep.contains({ baseName: 'run_events_spec_1.cy.js', fileExtension: '.js', fileName: 'run_events_spec_1', name: 'run_events_spec_1.cy.js', - relative: 'cypress/e2e/run_events_spec_1.cy.js', + relative: getPathForPlatform('cypress/e2e/run_events_spec_1.cy.js'), specFileExtension: '.cy.js', specType: 'integration', }) @@ -61,14 +61,14 @@ describe('plugin events', () => { passCount: 1, }) - cy.readFile(path.join(projectRoot, 'beforeSpec.json')) + cy.readFile(`${projectRoot}/beforeSpec.json`) .then((spec) => { expect(spec).to.deep.contains({ baseName: 'run_events_spec_2.cy.js', fileExtension: '.js', fileName: 'run_events_spec_2', name: 'run_events_spec_2.cy.js', - relative: 'cypress/e2e/run_events_spec_2.cy.js', + relative: getPathForPlatform('cypress/e2e/run_events_spec_2.cy.js'), specFileExtension: '.cy.js', specType: 'integration', }) From a6067a718c304f3e4d81ada6a24297642a1cbe7a Mon Sep 17 00:00:00 2001 From: Matt Schile Date: Thu, 1 Sep 2022 13:29:08 -0600 Subject: [PATCH 09/49] chore: add app triage project github actions (#23613) --- ..._project.yml => triage_add_to_project.yml} | 4 +- .../triage_add_to_routed_project.yml | 39 ++++++ .../workflows/triage_closed_issue_comment.yml | 89 ++++++++++++++ .github/workflows/triage_issue_metrics.yml | 114 ++++++++++++++++++ 4 files changed, 244 insertions(+), 2 deletions(-) rename .github/workflows/{add_to_triage_project.yml => triage_add_to_project.yml} (85%) create mode 100644 .github/workflows/triage_add_to_routed_project.yml create mode 100644 .github/workflows/triage_closed_issue_comment.yml create mode 100644 .github/workflows/triage_issue_metrics.yml diff --git a/.github/workflows/add_to_triage_project.yml b/.github/workflows/triage_add_to_project.yml similarity index 85% rename from .github/workflows/add_to_triage_project.yml rename to .github/workflows/triage_add_to_project.yml index 235874028d3c..4dcf519a2a17 100644 --- a/.github/workflows/add_to_triage_project.yml +++ b/.github/workflows/triage_add_to_project.yml @@ -1,10 +1,10 @@ -name: Add issue/PR to project +name: 'Triage: add issue/PR to project' on: issues: types: - opened - pull_request: + pull_request_target: types: - opened diff --git a/.github/workflows/triage_add_to_routed_project.yml b/.github/workflows/triage_add_to_routed_project.yml new file mode 100644 index 000000000000..d2e27377bc1b --- /dev/null +++ b/.github/workflows/triage_add_to_routed_project.yml @@ -0,0 +1,39 @@ +name: 'Triage: route to team project board' +on: + issues: + types: + - labeled +jobs: + add-to-e2e: + if: github.event.label.name == 'routed-to-e2e' + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }} + ORGANIZATION: 'cypress-io' + PROJECT_NUMBER: 10 + run: | + gh api graphql -f query=' + query($org: String!, $number: Int!) { + organization(login: $org){ + projectV2(number: $number) { + id + } + } + }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json + + echo 'PROJECT_ID='$(jq -r '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV + - name: add issue to e2e project + env: + GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }} + ISSUE_ID: ${{ github.event.issue.node_id }} + run: | + gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) { + item { + id + } + } + }' -f project=$PROJECT_ID -f issue=$ISSUE_ID diff --git a/.github/workflows/triage_closed_issue_comment.yml b/.github/workflows/triage_closed_issue_comment.yml new file mode 100644 index 000000000000..2270ebcfb745 --- /dev/null +++ b/.github/workflows/triage_closed_issue_comment.yml @@ -0,0 +1,89 @@ +name: 'Triage: closed issue comment' +on: + issue_comment: + types: + - created +jobs: + add-to-e2e: + if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'closed' && github.event.sender.login != 'cypress-bot' }} + runs-on: ubuntu-latest + steps: + - name: Get project data + env: + GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }} + ORGANIZATION: 'cypress-io' + REPOSITORY: 'cypress' + PROJECT_NUMBER: 9 + ISSUE_NUMBER: ${{ github.event.issue.number }} + run: | + gh api graphql -f query=' + query($org: String!, $repo: String!, $project: Int!, $issue: Int!) { + organization(login: $org) { + repository(name: $repo) { + issue(number: $issue) { + projectItems(first: 10, includeArchived: false) { + nodes { + id + fieldValueByName(name: "Status") { + ... on ProjectV2ItemFieldSingleSelectValue { + name + field { + ... on ProjectV2SingleSelectField { + project { + ... on ProjectV2 { + id + number + } + } + } + } + } + } + } + } + } + } + projectV2(number: $project) { + field(name: "Status") { + ... on ProjectV2SingleSelectField { + id + options { + id + name + } + } + } + } + } + }' -f org=$ORGANIZATION -f repo=$REPOSITORY -F issue=$ISSUE_NUMBER -F project=$PROJECT_NUMBER > project_data.json + + echo 'PROJECT_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName.field.project | select(.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV + echo 'PROJECT_ITEM_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[] | select(.fieldValueByName.field.project.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV + echo 'STATUS_FIELD_ID='$(jq -r '.data.organization.projectV2.field | .id' project_data.json) >> $GITHUB_ENV + echo 'STATUS='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName | select(.field.project.number == ${{ env.PROJECT_NUMBER }}) | .name' project_data.json) >> $GITHUB_ENV + echo 'NEW_ISSUE_OPTION_ID='$(jq -r '.data.organization.projectV2.field.options[] | select(.name== "New Issue") | .id' project_data.json) >> $GITHUB_ENV + - name: Move issue to New Issue status + env: + GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }} + if: ${{ env.STATUS == 'Closed' }} + run: | + gh api graphql -f query=' + mutation ( + $project: ID! + $item: ID! + $status_field: ID! + $status_value: String! + ) { + updateProjectV2ItemFieldValue(input: { + projectId: $project + itemId: $item + fieldId: $status_field + value: { + singleSelectOptionId: $status_value + } + }) { + projectV2Item { + id + } + } + }' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=$NEW_ISSUE_OPTION_ID diff --git a/.github/workflows/triage_issue_metrics.yml b/.github/workflows/triage_issue_metrics.yml new file mode 100644 index 000000000000..55c8101d3c65 --- /dev/null +++ b/.github/workflows/triage_issue_metrics.yml @@ -0,0 +1,114 @@ +name: 'Triage: issue metrics' + +on: + workflow_dispatch: + inputs: + startDate: + description: 'Start date (YYYY-MM-DD)' + type: date + endDate: + description: 'End date (YYYY-MM-DD)' + type: date +jobs: + seven-day-close: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + env: + ORGANIZATION: 'cypress-io' + REPOSITORY: 'cypress' + PROJECT_NUMBER: 9 + with: + github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }} + script: | + const ROUTED_TO_LABELS = ['routed-to-e2e', 'routed-to-ct'] + const MS_PER_DAY = 1000 * 60 * 60 * 24 + const { REPOSITORY, ORGANIZATION, PROJECT_NUMBER } = process.env + + const issues = [] + + const determineDateRange = () => { + const inputStartDate = '${{ inputs.startDate }}' + const inputEndDate = '${{ inputs.endDate }}' + + if (inputStartDate && inputEndDate) { + return { startDate: inputStartDate, endDate: inputEndDate } + } + + if (inputStartDate || inputEndDate) { + core.setFailed('Both startDate and endDate are required if one is provided.') + } + + const startDate = new Date() + + startDate.setDate(startDate.getDate() - 6) + + return { startDate: startDate.toISOString().split('T')[0], endDate: (new Date()).toISOString().split('T')[0] } + } + + const dateRange = determineDateRange() + const query = `is:issue+repo:${ORGANIZATION}/${REPOSITORY}+project:${ORGANIZATION}/${PROJECT_NUMBER}+created:${dateRange.startDate}..${dateRange.endDate}` + + const findLabelDateTime = async (issueNumber) => { + const iterator = github.paginate.iterator(github.rest.issues.listEventsForTimeline, { + owner: ORGANIZATION, + repo: REPOSITORY, + issue_number: issueNumber, + }) + + for await (const { data: timelineData } of iterator) { + for (const timelineItem of timelineData) { + if (timelineItem.event === 'labeled' && ROUTED_TO_LABELS.includes(timelineItem.label.name)) { + return timelineItem.created_at + } + } + } + } + + const calculateElapsedDays = (createdAt, routedOrClosedAt) => { + return Math.round((new Date(routedOrClosedAt) - new Date(createdAt)) / MS_PER_DAY, 0) + } + + const iterator = github.paginate.iterator(github.rest.search.issuesAndPullRequests, { + q: query, + per_page: 100, + }) + + for await (const { data } of iterator) { + for (const issue of data) { + let routedOrClosedAt + + if (!issue.pull_request) { + const routedLabel = issue.labels.find((label) => ROUTED_TO_LABELS.includes(label.name)) + + if (routedLabel) { + routedOrClosedAt = await findLabelDateTime(issue.number) + } else if (issue.state === 'closed') { + routedOrClosedAt = issue.closed_at + } + + let elapsedDays + + if (routedOrClosedAt) { + elapsedDays = calculateElapsedDays(issue.created_at, routedOrClosedAt) + } + + issues.push({ + number: issue.number, + title: issue.title, + state: issue.state, + url: issue.html_url, + createdAt: issue.created_at, + routedOrClosedAt, + elapsedDays, + }) + } + } + } + + const issuesRoutedOrClosedIn7Days = issues.filter((issue) => issue.elapsedDays <= 7).length + const percentage = Number(issues.length > 0 ? issuesRoutedOrClosedIn7Days / issues.length : 0).toLocaleString(undefined, { style: 'percent', minimumFractionDigits: 2 }) + + console.log(`Triage Metrics (${dateRange.startDate} - ${dateRange.endDate})`) + console.log('Total issues:', issues.length) + console.log(`Issues routed/closed within 7 days: ${issuesRoutedOrClosedIn7Days} (${percentage})`) From 8e4ebef0e08856b04805d544f943eb0f1f0fcc5a Mon Sep 17 00:00:00 2001 From: Blue F Date: Thu, 1 Sep 2022 14:48:26 -0700 Subject: [PATCH 10/49] fix: Aliases properly retry commands from inside custom commands (#23663) --- packages/driver/cypress/e2e/commands/aliasing.cy.js | 9 +++++++++ packages/driver/src/cypress/cy.ts | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/driver/cypress/e2e/commands/aliasing.cy.js b/packages/driver/cypress/e2e/commands/aliasing.cy.js index 0e0a2544ff41..524c7c44259b 100644 --- a/packages/driver/cypress/e2e/commands/aliasing.cy.js +++ b/packages/driver/cypress/e2e/commands/aliasing.cy.js @@ -101,6 +101,15 @@ describe('src/cy/commands/aliasing', () => { expect(this.input).to.eq(obj) }) }) + + it('retries previous commands invoked inside custom commands', () => { + Cypress.Commands.add('get2', (selector) => cy.get(selector)) + + cy.get2('body').children('div').as('divs') + cy.visit('/fixtures/dom.html') + + cy.get('@divs') + }) }) context('#assign', () => { diff --git a/packages/driver/src/cypress/cy.ts b/packages/driver/src/cypress/cy.ts index 3140cce77b12..dcb5ec931ac9 100644 --- a/packages/driver/src/cypress/cy.ts +++ b/packages/driver/src/cypress/cy.ts @@ -1230,11 +1230,13 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert } // A workaround to ensure that when looking back, aliases don't extend beyond the current - // chainer. This whole area (`replayCommandsFrom` for aliases) will be replaced with subject chains as - // part of the detached DOM work. + // chainer and commands invoked inside of it. This whole area (`replayCommandsFrom` for aliases) + // will be replaced with subject chains as part of the detached DOM work. + const chainerId = command.get('chainerId') const prev = command.get('prev') + const prevChainer = prev.get('chainerId') - if (prev.get('chainerId') !== command.get('chainerId')) { + if (prevChainer !== chainerId && cy.state('subjectLinks')[prevChainer] !== chainerId) { return memo } From 8b8f20eec77d4c0a704aee7f7077dc92dbafb93f Mon Sep 17 00:00:00 2001 From: Jon Hieb Date: Fri, 2 Sep 2022 06:40:23 -0600 Subject: [PATCH 11/49] fix: cypress/react18 rerender (#23360) Co-authored-by: astone123 --- npm/react18/src/index.ts | 8 +- .../component_testing_spec.ts.js | 196 +++++++++++++--- .../vite_dev_server_fresh_spec.ts.js | 159 ++++++++++--- .../webpack_dev_server_fresh_spec.ts.js | 210 +++++++++++++++--- .../react/src/Rerendering.cy.jsx | 27 +++ .../project-fixtures/react/vite.config.js | 6 + system-tests/projects/react17/package.json | 2 +- system-tests/projects/react17/yarn.lock | 2 +- system-tests/test/component_testing_spec.ts | 4 +- .../test/vite_dev_server_fresh_spec.ts | 3 + 10 files changed, 523 insertions(+), 94 deletions(-) create mode 100644 system-tests/project-fixtures/react/src/Rerendering.cy.jsx diff --git a/npm/react18/src/index.ts b/npm/react18/src/index.ts index 67beea54010a..ba402fe21a4d 100644 --- a/npm/react18/src/index.ts +++ b/npm/react18/src/index.ts @@ -11,12 +11,14 @@ import type { UnmountArgs, } from '@cypress/react' -let root: any +let root: ReactDOM.Root | null const cleanup = () => { if (root) { root.unmount() + root = null + return true } @@ -27,7 +29,9 @@ export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerende const internalOptions: InternalMountOptions = { reactDom: ReactDOM, render: (reactComponent: ReturnType, el: HTMLElement) => { - root = ReactDOM.createRoot(el) + if (!root) { + root = ReactDOM.createRoot(el) + } return root.render(reactComponent) }, diff --git a/system-tests/__snapshots__/component_testing_spec.ts.js b/system-tests/__snapshots__/component_testing_spec.ts.js index dcab2102a32e..22768b102cb4 100644 --- a/system-tests/__snapshots__/component_testing_spec.ts.js +++ b/system-tests/__snapshots__/component_testing_spec.ts.js @@ -7,15 +7,16 @@ exports['React major versions with Webpack executes all of the tests for React v ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 3 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx) │ - │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx │ + │ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx) │ + │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │ + │ y.jsx │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 3) - 43 modules + Running: App.cy.jsx (1 of 4) + 44 modules ✓ renders hello world @@ -46,7 +47,7 @@ exports['React major versions with Webpack executes all of the tests for React v ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (2 of 3) + Running: Unmount.cy.jsx (2 of 4) Comp with componentWillUnmount @@ -83,7 +84,7 @@ exports['React major versions with Webpack executes all of the tests for React v ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: UsingLegacyMount.cy.jsx (3 of 3) + Running: UsingLegacyMount.cy.jsx (3 of 4) using legacy mount @@ -114,6 +115,39 @@ exports['React major versions with Webpack executes all of the tests for React v - Finished processing: /XXX/XXX/XXX/cypress/videos/UsingLegacyMount.cy.jsx.mp4 (X second) +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Rerendering.cy.jsx (4 of 4) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + ==================================================================================================== (Run Finished) @@ -126,8 +160,10 @@ exports['React major versions with Webpack executes all of the tests for React v │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 5 5 - - - + ✔ All specs passed! XX:XX 6 6 - - - ` @@ -141,15 +177,16 @@ exports['React major versions with Webpack executes all of the tests for React v ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 3 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx) │ - │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx │ + │ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx) │ + │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │ + │ y.jsx │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 3) - 40 modules + Running: App.cy.jsx (1 of 4) + 41 modules ✓ renders hello world @@ -180,7 +217,7 @@ exports['React major versions with Webpack executes all of the tests for React v ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (2 of 3) + Running: Unmount.cy.jsx (2 of 4) Comp with componentWillUnmount @@ -217,7 +254,7 @@ exports['React major versions with Webpack executes all of the tests for React v ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: UsingLegacyMount.cy.jsx (3 of 3) + Running: UsingLegacyMount.cy.jsx (3 of 4) using legacy mount @@ -248,6 +285,39 @@ exports['React major versions with Webpack executes all of the tests for React v - Finished processing: /XXX/XXX/XXX/cypress/videos/UsingLegacyMount.cy.jsx.mp4 (X second) +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Rerendering.cy.jsx (4 of 4) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + ==================================================================================================== (Run Finished) @@ -260,8 +330,10 @@ exports['React major versions with Webpack executes all of the tests for React v │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 5 5 - - - + ✔ All specs passed! XX:XX 6 6 - - - ` @@ -275,14 +347,15 @@ exports['React major versions with Vite executes all of the tests for React v17 ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 3 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx) │ - │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx │ + │ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx) │ + │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │ + │ y.jsx │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 3) + Running: App.cy.jsx (1 of 4) ✓ renders hello world @@ -313,7 +386,7 @@ exports['React major versions with Vite executes all of the tests for React v17 ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (2 of 3) + Running: Unmount.cy.jsx (2 of 4) Comp with componentWillUnmount @@ -350,7 +423,7 @@ exports['React major versions with Vite executes all of the tests for React v17 ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: UsingLegacyMount.cy.jsx (3 of 3) + Running: UsingLegacyMount.cy.jsx (3 of 4) using legacy mount @@ -381,6 +454,39 @@ exports['React major versions with Vite executes all of the tests for React v17 - Finished processing: /XXX/XXX/XXX/cypress/videos/UsingLegacyMount.cy.jsx.mp4 (X second) +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Rerendering.cy.jsx (4 of 4) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + ==================================================================================================== (Run Finished) @@ -393,8 +499,10 @@ exports['React major versions with Vite executes all of the tests for React v17 │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 5 5 - - - + ✔ All specs passed! XX:XX 6 6 - - - ` @@ -408,14 +516,15 @@ exports['React major versions with Vite executes all of the tests for React v18 ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 3 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx) │ - │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx │ + │ Specs: 4 found (App.cy.jsx, Unmount.cy.jsx, UsingLegacyMount.cy.jsx, Rerendering.cy.jsx) │ + │ Searched: src/App.cy.jsx, src/Unmount.cy.jsx, src/UsingLegacyMount.cy.jsx, src/Rerendering.c │ + │ y.jsx │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 3) + Running: App.cy.jsx (1 of 4) ✓ renders hello world @@ -446,7 +555,7 @@ exports['React major versions with Vite executes all of the tests for React v18 ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (2 of 3) + Running: Unmount.cy.jsx (2 of 4) Comp with componentWillUnmount @@ -483,7 +592,7 @@ exports['React major versions with Vite executes all of the tests for React v18 ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: UsingLegacyMount.cy.jsx (3 of 3) + Running: UsingLegacyMount.cy.jsx (3 of 4) using legacy mount @@ -514,6 +623,39 @@ exports['React major versions with Vite executes all of the tests for React v18 - Finished processing: /XXX/XXX/XXX/cypress/videos/UsingLegacyMount.cy.jsx.mp4 (X second) +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Rerendering.cy.jsx (4 of 4) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + ==================================================================================================== (Run Finished) @@ -526,8 +668,10 @@ exports['React major versions with Vite executes all of the tests for React v18 │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ UsingLegacyMount.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 5 5 - - - + ✔ All specs passed! XX:XX 6 6 - - - ` diff --git a/system-tests/__snapshots__/vite_dev_server_fresh_spec.ts.js b/system-tests/__snapshots__/vite_dev_server_fresh_spec.ts.js index e27139955651..b565e3887e63 100644 --- a/system-tests/__snapshots__/vite_dev_server_fresh_spec.ts.js +++ b/system-tests/__snapshots__/vite_dev_server_fresh_spec.ts.js @@ -7,15 +7,15 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite3.0.2- ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) + Running: App.cy.jsx (1 of 6) ✓ renders hello world @@ -46,7 +46,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite3.0.2- ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -57,7 +57,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite3.0.2- 1) An uncaught error was detected outside of a test: TypeError: The following error originated from your test code, not from Cypress. - > Failed to fetch dynamically imported module: http://localhost:5173/__cypress/src/src/AppCompilationError.cy.jsx + > Failed to fetch dynamically imported module: http://localhost:xxxx/__cypress/src/src/AppCompilationError.cy.jsx When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. @@ -98,7 +98,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -146,7 +146,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -190,7 +190,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -240,9 +273,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` @@ -256,15 +291,15 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.8.6- ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) + Running: App.cy.jsx (1 of 6) ✓ renders hello world @@ -295,7 +330,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.8.6- ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -306,7 +341,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.8.6- 1) An uncaught error was detected outside of a test: TypeError: The following error originated from your test code, not from Cypress. - > Failed to fetch dynamically imported module: http://localhost:3000/__cypress/src/src/AppCompilationError.cy.jsx + > Failed to fetch dynamically imported module: http://localhost:xxxx/__cypress/src/src/AppCompilationError.cy.jsx When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. @@ -347,7 +382,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -395,7 +430,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -439,7 +474,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -489,9 +557,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` @@ -505,15 +575,15 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.9.1- ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) + Running: App.cy.jsx (1 of 6) ✓ renders hello world @@ -544,7 +614,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.9.1- ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -555,7 +625,7 @@ exports['@cypress/vite-dev-server react executes all of the tests for vite2.9.1- 1) An uncaught error was detected outside of a test: TypeError: The following error originated from your test code, not from Cypress. - > Failed to fetch dynamically imported module: http://localhost:3000/__cypress/src/src/AppCompilationError.cy.jsx + > Failed to fetch dynamically imported module: http://localhost:xxxx/__cypress/src/src/AppCompilationError.cy.jsx When Cypress detects uncaught errors originating from your test code it will automatically fail the current test. @@ -596,7 +666,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -644,7 +714,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -688,7 +758,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -738,9 +841,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` diff --git a/system-tests/__snapshots__/webpack_dev_server_fresh_spec.ts.js b/system-tests/__snapshots__/webpack_dev_server_fresh_spec.ts.js index 2892a8cde6f7..c9e28c52cef2 100644 --- a/system-tests/__snapshots__/webpack_dev_server_fresh_spec.ts.js +++ b/system-tests/__snapshots__/webpack_dev_server_fresh_spec.ts.js @@ -10,15 +10,15 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) + Running: App.cy.jsx (1 of 6) ✓ renders hello world @@ -49,7 +49,7 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -109,7 +109,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -157,7 +157,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -201,7 +201,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -251,9 +284,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` @@ -267,16 +302,16 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) - 43 modules + Running: App.cy.jsx (1 of 6) + 44 modules ERROR in ./src/AppCompilationError.cy.jsx Module build failed (from [..]): @@ -318,7 +353,7 @@ SyntaxError: /foo/bar/.projects/webpack4_wds4-react/src/AppCompilationError.cy.j ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -378,7 +413,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -426,7 +461,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -470,7 +505,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -520,9 +588,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` @@ -539,15 +609,15 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) + Running: App.cy.jsx (1 of 6) ✓ renders hello world @@ -578,7 +648,7 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -638,7 +708,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -686,7 +756,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -730,7 +800,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -780,9 +883,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` @@ -796,17 +901,17 @@ exports['@cypress/webpack-dev-server react executes all of the tests for webpack ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 5 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ - │ InSpec.cy.jsx, Unmount.cy.jsx) │ + │ Specs: 6 found (App.cy.jsx, AppCompilationError.cy.jsx, MissingReact.cy.jsx, MissingReact │ + │ InSpec.cy.jsx, Rerendering.cy.jsx, Unmount.cy.jsx) │ │ Searched: **/*.cy.{js,jsx,ts,tsx} │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: App.cy.jsx (1 of 5) -12 assets -53 modules + Running: App.cy.jsx (1 of 6) +13 assets +54 modules ERROR in ./src/AppCompilationError.cy.jsx Module build failed (from [..]): @@ -850,7 +955,7 @@ webpack x.x.x compiled with x errors in xxx ms ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: AppCompilationError.cy.jsx (2 of 5) + Running: AppCompilationError.cy.jsx (2 of 6) 1) An uncaught error was detected outside of a test @@ -910,7 +1015,7 @@ We dynamically generated a new test to display this failure. ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReact.cy.jsx (3 of 5) + Running: MissingReact.cy.jsx (3 of 6) 1) is missing React @@ -958,7 +1063,7 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: MissingReactInSpec.cy.jsx (4 of 5) + Running: MissingReactInSpec.cy.jsx (4 of 6) 1) is missing React in this file @@ -1002,7 +1107,40 @@ When Cypress detects uncaught errors originating from your test code it will aut ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: Unmount.cy.jsx (5 of 5) + Running: Rerendering.cy.jsx (5 of 6) + + + re-render + ✓ maintains component state across re-renders + + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: Rerendering.cy.jsx │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/Rerendering.cy.jsx.mp4 (X second) + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: Unmount.cy.jsx (6 of 6) Comp with componentWillUnmount @@ -1052,9 +1190,11 @@ When Cypress detects uncaught errors originating from your test code it will aut ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✖ MissingReactInSpec.cy.jsx XX:XX 1 - 1 - - │ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ ✔ Rerendering.cy.jsx XX:XX 1 1 - - - │ + ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │ ✔ Unmount.cy.jsx XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 3 of 5 failed (60%) XX:XX 7 4 3 - - + ✖ 3 of 6 failed (50%) XX:XX 8 5 3 - - ` diff --git a/system-tests/project-fixtures/react/src/Rerendering.cy.jsx b/system-tests/project-fixtures/react/src/Rerendering.cy.jsx new file mode 100644 index 000000000000..cbaaf04aba19 --- /dev/null +++ b/system-tests/project-fixtures/react/src/Rerendering.cy.jsx @@ -0,0 +1,27 @@ +import React from 'react'; + +function StatefulComponent ({ foo }) { + const [bar, setBar] = React.useState(0); + return ( + + ); +}; + +describe('re-render', () => { + it('maintains component state across re-renders', () => { + cy.mount().then(({ rerender }) => { + cy.get('button').should('have.text', 'baz 0'); + cy.get('button').click().should('have.text', 'baz 1'); + rerender() + + // The button should still show 1 after re-render + cy.get('button').should('have.text', 'baz 1'); + }); + }); +}); diff --git a/system-tests/project-fixtures/react/vite.config.js b/system-tests/project-fixtures/react/vite.config.js index e19ff0fc9c60..c02c21ec919f 100644 --- a/system-tests/project-fixtures/react/vite.config.js +++ b/system-tests/project-fixtures/react/vite.config.js @@ -1,5 +1,11 @@ const {defineConfig} = require('vite') module.exports = defineConfig({ + resolve: { + alias: { + 'react': require.resolve('react'), + 'react-dom': require.resolve('react-dom'), + }, + }, logLevel: 'silent' }) \ No newline at end of file diff --git a/system-tests/projects/react17/package.json b/system-tests/projects/react17/package.json index a56edf525c00..6cd85de4b2d9 100644 --- a/system-tests/projects/react17/package.json +++ b/system-tests/projects/react17/package.json @@ -1,7 +1,7 @@ { "dependencies": { "react": "17.0.2", - "react-dom": " 17.0.2" + "react-dom": "17.0.2" }, "devDependencies": { "@vitejs/plugin-react": "^1.3.2", diff --git a/system-tests/projects/react17/yarn.lock b/system-tests/projects/react17/yarn.lock index af880fecb9b3..2eac503e5d07 100644 --- a/system-tests/projects/react17/yarn.lock +++ b/system-tests/projects/react17/yarn.lock @@ -2345,7 +2345,7 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -"react-dom@ 17.0.2": +react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== diff --git a/system-tests/test/component_testing_spec.ts b/system-tests/test/component_testing_spec.ts index b2ee8e799c0b..dfc9d8adf841 100644 --- a/system-tests/test/component_testing_spec.ts +++ b/system-tests/test/component_testing_spec.ts @@ -79,7 +79,7 @@ describe(`React major versions with Vite`, function () { return systemTests.exec(this, { project: `react${majorVersion}`, configFile: 'cypress-vite.config.ts', - spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx', + spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx,src/Rerendering.cy.jsx', testingType: 'component', browser: 'chrome', snapshot: true, @@ -97,7 +97,7 @@ describe(`React major versions with Webpack`, function () { return systemTests.exec(this, { project: `react${majorVersion}`, configFile: 'cypress-webpack.config.ts', - spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx', + spec: 'src/App.cy.jsx,src/Unmount.cy.jsx,src/UsingLegacyMount.cy.jsx,src/Rerendering.cy.jsx', testingType: 'component', browser: 'chrome', snapshot: true, diff --git a/system-tests/test/vite_dev_server_fresh_spec.ts b/system-tests/test/vite_dev_server_fresh_spec.ts index 2da93c259626..d1592cb98aad 100644 --- a/system-tests/test/vite_dev_server_fresh_spec.ts +++ b/system-tests/test/vite_dev_server_fresh_spec.ts @@ -18,6 +18,9 @@ describe('@cypress/vite-dev-server', function () { browser: 'chrome', snapshot: true, expectedExitCode: 3, + onStdout: (stdout) => { + return stdout.replace(/http:\/\/localhost:\d+/g, 'http://localhost:xxxx') + }, }) }) } From 06beffdfe875fb129204c0631b0fc6974a9cf5d0 Mon Sep 17 00:00:00 2001 From: Matt Schile Date: Fri, 2 Sep 2022 08:37:00 -0600 Subject: [PATCH 12/49] chore: handle "Close with comment" (#23668) --- .github/workflows/triage_add_to_routed_project.yml | 2 +- .github/workflows/triage_closed_issue_comment.yml | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/triage_add_to_routed_project.yml b/.github/workflows/triage_add_to_routed_project.yml index d2e27377bc1b..6bb13d85fa3a 100644 --- a/.github/workflows/triage_add_to_routed_project.yml +++ b/.github/workflows/triage_add_to_routed_project.yml @@ -4,7 +4,7 @@ on: types: - labeled jobs: - add-to-e2e: + route-to-e2e: if: github.event.label.name == 'routed-to-e2e' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/triage_closed_issue_comment.yml b/.github/workflows/triage_closed_issue_comment.yml index 2270ebcfb745..1caf8abdbb3a 100644 --- a/.github/workflows/triage_closed_issue_comment.yml +++ b/.github/workflows/triage_closed_issue_comment.yml @@ -4,8 +4,12 @@ on: types: - created jobs: - add-to-e2e: - if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'closed' && github.event.sender.login != 'cypress-bot' }} + move-to-new-issue-status: + if: | + !github.event.issue.pull_request && + github.event.issue.state == 'closed' && + github.event.comment.created_at != github.event.issue.closed_at && + github.event.sender.login != 'cypress-bot' runs-on: ubuntu-latest steps: - name: Get project data @@ -65,7 +69,7 @@ jobs: - name: Move issue to New Issue status env: GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }} - if: ${{ env.STATUS == 'Closed' }} + if: env.STATUS == 'Closed' run: | gh api graphql -f query=' mutation ( From 2059ec562970fe24f5a1445e4e23cc94854953d4 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Sat, 3 Sep 2022 02:44:24 +1000 Subject: [PATCH 13/49] chore: fixing flaky tests (#23667) Co-authored-by: Emily Rohrbough Co-authored-by: Rachel --- circle.yml | 2 +- .../cypress/e2e/config-warning.cy.ts | 35 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/circle.yml b/circle.yml index 292d39b43978..5e4249a49b4d 100644 --- a/circle.yml +++ b/circle.yml @@ -66,7 +66,7 @@ windowsWorkflowFilters: &windows-workflow-filters or: - equal: [ develop, << pipeline.git.branch >> ] - equal: [ linux-arm64, << pipeline.git.branch >> ] - - equal: [ 'fix-windows', << pipeline.git.branch >> ] + - equal: [ 'lmiller/fixing-flake-1', << pipeline.git.branch >> ] - equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ] - matches: pattern: "-release$" diff --git a/packages/launchpad/cypress/e2e/config-warning.cy.ts b/packages/launchpad/cypress/e2e/config-warning.cy.ts index fc1d1e782638..d381910cbb1c 100644 --- a/packages/launchpad/cypress/e2e/config-warning.cy.ts +++ b/packages/launchpad/cypress/e2e/config-warning.cy.ts @@ -70,11 +70,30 @@ describe('baseUrl', () => { }) describe('experimentalSingleTabRunMode', () => { - // TODO: fix flaky test https://github.com/cypress-io/cypress/issues/23158 - it.skip('is a valid config for component testing', () => { + it('is a valid config for component testing', () => { cy.scaffoldProject('experimentalSingleTabRunMode') cy.openProject('experimentalSingleTabRunMode') cy.visitLaunchpad() + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject('cypress.config.js', ` + const { defineConfig } = require('cypress') + + module.exports = defineConfig({ + component: { + experimentalSingleTabRunMode: true, + devServer () { + // This test doesn't need to actually run any component tests + // so we create a fake dev server to make it run faster and + // avoid flake on CI. + return { + port: 1234, + close: () => {}, + } + }, + }, + })`) + }) + cy.get('[data-cy-testingtype="component"]').click() cy.get('h1').contains('Initializing Config').should('not.exist') cy.get('h1').contains('Choose a Browser') @@ -91,7 +110,7 @@ describe('experimentalSingleTabRunMode', () => { }) describe('experimentalStudio', () => { - it('is not a valid config for component testing', () => { + it('is not a valid config for component testing', { defaultCommandTimeout: THIRTY_SECONDS }, () => { cy.scaffoldProject('experimentalSingleTabRunMode') cy.openProject('experimentalSingleTabRunMode') cy.visitLaunchpad() @@ -102,8 +121,14 @@ describe('experimentalStudio', () => { module.exports = defineConfig({ component: { experimentalStudio: true, - devServer: { - bundler: 'webpack', + devServer () { + // This test doesn't need to actually run any component tests + // so we create a fake dev server to make it run faster and + // avoid flake on CI. + return { + port: 1234, + close: () => {}, + } }, }, })`) From 4e0e1cb85b12e0568bc6f6c640430939841bd84f Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Fri, 2 Sep 2022 17:50:49 -0500 Subject: [PATCH 14/49] chore: remove skipped test - covered elsewhere (#23675) --- packages/app/cypress/e2e/runner/runner.ui.cy.ts | 11 ----------- .../e2e/runner/disabled-command-log.runner.cy.js | 5 ----- 2 files changed, 16 deletions(-) delete mode 100644 system-tests/projects/runner-e2e-specs/cypress/e2e/runner/disabled-command-log.runner.cy.js diff --git a/packages/app/cypress/e2e/runner/runner.ui.cy.ts b/packages/app/cypress/e2e/runner/runner.ui.cy.ts index bc2d1a66e4d8..be616e889512 100644 --- a/packages/app/cypress/e2e/runner/runner.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/runner.ui.cy.ts @@ -296,16 +296,5 @@ describe('src/cypress/runner', () => { cy.get('.runnable-err-message').should('not.contain', 'ran afterEach even though specs were stopped') cy.get('.runnable-err-message').should('contain', 'Cypress test was stopped while running this command.') }) - - // TODO: blocked by UNIFY-1077 - it.skip('supports disabling command log reporter with env var NO_COMMAND_LOG', () => { - loadSpec({ - filePath: 'runner/disabled-command-log.runner.cy.js', - passCount: 0, - failCount: 0, - }) - - cy.get('.reporter').should('not.exist') - }) }) }) diff --git a/system-tests/projects/runner-e2e-specs/cypress/e2e/runner/disabled-command-log.runner.cy.js b/system-tests/projects/runner-e2e-specs/cypress/e2e/runner/disabled-command-log.runner.cy.js deleted file mode 100644 index e588839cf601..000000000000 --- a/system-tests/projects/runner-e2e-specs/cypress/e2e/runner/disabled-command-log.runner.cy.js +++ /dev/null @@ -1,5 +0,0 @@ -it('disabled command log with NO_COMMAND_LOG', { - env: { NO_COMMAND_LOG: '1' }, -}, () => { - assert(true) -}) From 1f2b3eb189cd34d6f9462139b49c7a330cc0134f Mon Sep 17 00:00:00 2001 From: amehta265 <65267668+amehta265@users.noreply.github.com> Date: Mon, 5 Sep 2022 19:11:00 -0400 Subject: [PATCH 15/49] chore: valid config files should be collapsed during onboarding (#23659) * default config files collapsible closed except for warnings * updated component tests and color accessibility * addressing PR comments for standardizing tests with .should * updated tests and made them more robust --- .../src/components/ListRowHeader.vue | 2 +- .../launchpad/cypress/e2e/project-setup.cy.ts | 16 +++++++++++++ .../src/components/code/FileRow.cy.tsx | 24 +++++++++++++++++++ .../launchpad/src/components/code/FileRow.vue | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/frontend-shared/src/components/ListRowHeader.vue b/packages/frontend-shared/src/components/ListRowHeader.vue index 993ac555368c..01ea206b0021 100644 --- a/packages/frontend-shared/src/components/ListRowHeader.vue +++ b/packages/frontend-shared/src/components/ListRowHeader.vue @@ -17,7 +17,7 @@ > -

+

{{ description }} diff --git a/packages/launchpad/cypress/e2e/project-setup.cy.ts b/packages/launchpad/cypress/e2e/project-setup.cy.ts index b86ebc28267f..a2eb349ae681 100644 --- a/packages/launchpad/cypress/e2e/project-setup.cy.ts +++ b/packages/launchpad/cypress/e2e/project-setup.cy.ts @@ -86,6 +86,10 @@ describe('Launchpad: Setup Project', () => { cy.containsPath('cypress/fixtures/example.json') }) + cy.get('[data-cy=valid] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + verifyScaffoldedFiles('e2e') }) @@ -241,6 +245,10 @@ describe('Launchpad: Setup Project', () => { cy.containsPath('cypress/fixtures/example.json') }) + cy.get('[data-cy=valid] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + verifyScaffoldedFiles('e2e') cy.findByRole('button', { name: 'Continue' }) @@ -307,6 +315,10 @@ describe('Launchpad: Setup Project', () => { cy.containsPath('cypress/fixtures/example.json') }) + cy.get('[data-cy=valid] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + verifyScaffoldedFiles('e2e') cy.findByRole('button', { name: 'Continue' }) @@ -339,6 +351,10 @@ describe('Launchpad: Setup Project', () => { cy.containsPath('cypress/fixtures/example.json') }) + cy.get('[data-cy=valid] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + verifyScaffoldedFiles('e2e') cy.findByRole('button', { name: 'Continue' }) diff --git a/packages/launchpad/src/components/code/FileRow.cy.tsx b/packages/launchpad/src/components/code/FileRow.cy.tsx index ceca0de26de1..7713c6797f90 100644 --- a/packages/launchpad/src/components/code/FileRow.cy.tsx +++ b/packages/launchpad/src/components/code/FileRow.cy.tsx @@ -54,6 +54,13 @@ describe('FileRow', () => { description={description} fileExtension=".js" /> + )) @@ -63,6 +70,23 @@ describe('FileRow', () => { cy.contains('cypress/integration/command.js') cy.contains('cypress.config.js') cy.contains('cypress/integration/index.js') + cy.contains('cypress/integration/function.ts') + + cy.get('[data-cy=valid] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + + cy.get('[data-cy=changes] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'true') + }) + + cy.get('[data-cy=skipped] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) + + cy.get('[data-cy=error] [data-cy=collapsible-header]').each((element) => { + cy.wrap(element).should('have.attr', 'aria-expanded', 'false') + }) cy.percySnapshot() }) diff --git a/packages/launchpad/src/components/code/FileRow.vue b/packages/launchpad/src/components/code/FileRow.vue index 0ae3b7cc9684..1ca34e29143e 100644 --- a/packages/launchpad/src/components/code/FileRow.vue +++ b/packages/launchpad/src/components/code/FileRow.vue @@ -119,7 +119,7 @@ const statusInfo: ComputedRef = computed(() => { }, valid: { icon: AddedIcon, - initiallyOpen: true, + initiallyOpen: false, }, error: { icon: ErrorIcon, From a51e340577e53181ae851c1ba5b261cdef2e19e1 Mon Sep 17 00:00:00 2001 From: Mike Plummer Date: Tue, 6 Sep 2022 09:57:00 -0500 Subject: [PATCH 16/49] fix: Refetch latest cloud data when opening cloud connect modals (#23650) --- packages/app/cypress/e2e/runs.cy.ts | 41 +++++++++++-- .../createCloudOrgModal-subscription.cy.ts | 6 +- .../runs/modals/CloudConnectModals.spec.tsx | 60 +++++++++++++++++-- .../src/runs/modals/CloudConnectModals.vue | 27 +++++++-- .../src/runs/modals/CreateCloudOrgModal.vue | 2 +- .../runs/modals/SelectCloudProjectModal.vue | 48 +++++++++------ packages/graphql/schemas/schema.graphql | 4 +- .../schemaTypes/objectTypes/gql-Mutation.ts | 4 +- 8 files changed, 149 insertions(+), 43 deletions(-) diff --git a/packages/app/cypress/e2e/runs.cy.ts b/packages/app/cypress/e2e/runs.cy.ts index e5c9aac554b2..e250fb1b3a90 100644 --- a/packages/app/cypress/e2e/runs.cy.ts +++ b/packages/app/cypress/e2e/runs.cy.ts @@ -139,10 +139,8 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.startAppServer('component') cy.remoteGraphQLIntercept(async (obj) => { - if ((obj.operationName === 'CheckCloudOrganizations_cloudViewerChange_cloudViewer' || obj.operationName === 'Runs_cloudViewer' || obj.operationName === 'SpecsPageContainer_cloudViewer')) { - if (obj.result.data?.cloudViewer?.organizations?.nodes) { - obj.result.data.cloudViewer.organizations.nodes = [] - } + if (obj?.result?.data?.cloudViewer?.organizations?.nodes) { + obj.result.data.cloudViewer.organizations.nodes = [] } return obj.result @@ -156,10 +154,45 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.findByText(defaultMessages.runs.connect.buttonProject).click() cy.get('[aria-modal="true"]').should('exist') + // Clear existing remote GQL intercept to allow new queries to execute normally + cy.remoteGraphQLIntercept(async (obj) => { + return obj.result + }) + cy.contains('button', defaultMessages.runs.connect.modal.createOrg.refreshButton).click() cy.findByText(defaultMessages.runs.connect.modal.selectProject.manageOrgs) }) + + it('refetches cloudViewer data on open', () => { + cy.scaffoldProject('component-tests') + cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js']) + cy.startAppServer('component') + + cy.remoteGraphQLIntercept(async (obj, testState) => { + if (obj.operationName === 'CloudConnectModals_RefreshCloudViewer_refreshCloudViewer_cloudViewer') { + testState.refetchCalled = true + } + + if (obj.result.data?.cloudViewer?.organizations?.nodes) { + obj.result.data.cloudViewer.organizations.nodes = [] + } + + return obj.result + }) + + cy.loginUser() + cy.visitApp() + + moveToRunsPage() + + cy.findByText(defaultMessages.runs.connect.buttonProject).click() + cy.get('[aria-modal="true"]').should('exist') + + cy.withCtx((_, o) => { + expect(o.testState.refetchCalled).to.eql(true) + }) + }) }) context('Runs - Connect Project', () => { diff --git a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts index d667d7e15685..41f607621d12 100644 --- a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts @@ -18,10 +18,8 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { // Simulate no orgs cy.remoteGraphQLIntercept(async (obj) => { - if ((obj.operationName === 'CheckCloudOrganizations_cloudViewerChange_cloudViewer' || obj.operationName === 'Runs_cloudViewer' || obj.operationName === 'SpecsPageContainer_cloudViewer')) { - if (obj.result.data?.cloudViewer?.organizations?.nodes) { - obj.result.data.cloudViewer.organizations.nodes = [] - } + if (obj.result.data?.cloudViewer?.organizations?.nodes) { + obj.result.data.cloudViewer.organizations.nodes = [] } return obj.result diff --git a/packages/app/src/runs/modals/CloudConnectModals.spec.tsx b/packages/app/src/runs/modals/CloudConnectModals.spec.tsx index 000a8c77bb9c..7d5d4adf0488 100644 --- a/packages/app/src/runs/modals/CloudConnectModals.spec.tsx +++ b/packages/app/src/runs/modals/CloudConnectModals.spec.tsx @@ -4,9 +4,15 @@ import { CloudUserStubs, } from '@packages/graphql/test/stubCloudTypes' import { CloudConnectModalsFragmentDoc } from '../../generated/graphql-test' import CloudConnectModals from './CloudConnectModals.vue' +import cloneDeep from 'lodash/cloneDeep' + +type MountOptions = { + hasOrg: boolean + hasProjects: boolean +} describe('', () => { - function mountDialog (noOrg = false) { + function mountDialog ({ hasOrg, hasProjects }: MountOptions) { cy.mountFragment(CloudConnectModalsFragmentDoc, { onResult: (result) => { result.currentProject = { @@ -18,7 +24,16 @@ describe('', () => { result.cloudViewer = { ...CloudUserStubs.me, - organizations: noOrg ? null : CloudOrganizationConnectionStubs, + organizations: hasOrg ? cloneDeep(CloudOrganizationConnectionStubs) : null, + } + + if (!hasProjects) { + result.cloudViewer.organizations?.nodes.forEach((org) => { + org.projects = { + ...org.projects, + nodes: [], + } + }) } }, render (gql) { @@ -29,10 +44,43 @@ describe('', () => { }) } - it('shows the select org modal when orgs are added', () => { - mountDialog() - cy.contains(defaultMessages.runs.connect.modal.selectProject.connectProject).should('be.visible') + context('has no organization', () => { + beforeEach(() => { + mountDialog({ hasOrg: false, hasProjects: false }) + }) + + it('shows the create/select org modal when orgs are added', () => { + cy.contains(defaultMessages.runs.connect.modal.createOrg.button).should('be.visible') + + cy.percySnapshot() + }) + }) + + context('has organizations', () => { + context('with no projects', () => { + beforeEach(() => { + mountDialog({ hasOrg: true, hasProjects: false }) + }) + + it('shows the select project modal with create new project action', () => { + cy.contains(defaultMessages.runs.connect.modal.selectProject.createProject).should('be.visible') + + cy.contains('a', defaultMessages.links.needHelp).should('have.attr', 'href', 'https://on.cypress.io/adding-new-project') + + cy.percySnapshot() + }) + }) + + context('with projects', () => { + beforeEach(() => { + mountDialog({ hasOrg: true, hasProjects: true }) + }) - cy.contains('a', defaultMessages.links.needHelp).should('have.attr', 'href', 'https://on.cypress.io/adding-new-project') + it('shows the select project modal with list of projects', () => { + cy.contains(defaultMessages.runs.connect.modal.selectProject.connectProject).should('be.visible') + + cy.percySnapshot() + }) + }) }) }) diff --git a/packages/app/src/runs/modals/CloudConnectModals.vue b/packages/app/src/runs/modals/CloudConnectModals.vue index 3f4578b6f04f..4f426fcabe8c 100644 --- a/packages/app/src/runs/modals/CloudConnectModals.vue +++ b/packages/app/src/runs/modals/CloudConnectModals.vue @@ -23,13 +23,13 @@ diff --git a/packages/app/src/specs/banners/ConnectProjectBanner.cy.tsx b/packages/app/src/specs/banners/ConnectProjectBanner.cy.tsx index aea9e561b263..0fc078252aff 100644 --- a/packages/app/src/specs/banners/ConnectProjectBanner.cy.tsx +++ b/packages/app/src/specs/banners/ConnectProjectBanner.cy.tsx @@ -1,5 +1,6 @@ import { defaultMessages } from '@cy/i18n' import ConnectProjectBanner from './ConnectProjectBanner.vue' +import { TrackedBanner_RecordBannerSeenDocument } from '../../generated/graphql' describe('', () => { it('should render expected content', () => { @@ -11,4 +12,23 @@ describe('', () => { cy.percySnapshot() }) + + it('should record expected event on mount', () => { + const recordEvent = cy.stub().as('recordEvent') + + cy.stubMutationResolver(TrackedBanner_RecordBannerSeenDocument, (defineResult, event) => { + recordEvent(event) + + return defineResult({ recordEvent: true }) + }) + + cy.mount({ render: () => }) + + cy.get('@recordEvent').should('have.been.calledWith', { + campaign: 'Create project', + medium: 'Specs Create Project Banner', + messageId: Cypress.sinon.match.string, + cohort: null, + }) + }) }) diff --git a/packages/app/src/specs/banners/ConnectProjectBanner.vue b/packages/app/src/specs/banners/ConnectProjectBanner.vue index 994acbc6c729..0037f455c342 100644 --- a/packages/app/src/specs/banners/ConnectProjectBanner.vue +++ b/packages/app/src/specs/banners/ConnectProjectBanner.vue @@ -8,6 +8,12 @@ class="mb-16px" :icon="ConnectIcon" dismissible + :has-banner-been-shown="hasBannerBeenShown" + :event-data="{ + campaign: 'Create project', + medium: 'Specs Create Project Banner', + cohort: '' // TODO Connect cohort + }" @update:model-value="value => emit('update:modelValue', value)" >

@@ -51,8 +57,10 @@ query ConnectProjectBanner { withDefaults(defineProps<{ modelValue: boolean + hasBannerBeenShown: boolean }>(), { modelValue: false, + hasBannerBeenShown: true, }) const emit = defineEmits<{ diff --git a/packages/app/src/specs/banners/CreateOrganizationBanner.cy.tsx b/packages/app/src/specs/banners/CreateOrganizationBanner.cy.tsx index 7399398e23bb..b7297ee1fb8c 100644 --- a/packages/app/src/specs/banners/CreateOrganizationBanner.cy.tsx +++ b/packages/app/src/specs/banners/CreateOrganizationBanner.cy.tsx @@ -1,5 +1,6 @@ import { defaultMessages } from '@cy/i18n' import CreateOrganizationBanner from './CreateOrganizationBanner.vue' +import { TrackedBanner_RecordBannerSeenDocument } from '../../generated/graphql' describe('', () => { it('should render expected content', () => { @@ -23,4 +24,23 @@ describe('', () => { cy.percySnapshot() }) + + it('should record expected event on mount', () => { + const recordEvent = cy.stub().as('recordEvent') + + cy.stubMutationResolver(TrackedBanner_RecordBannerSeenDocument, (defineResult, event) => { + recordEvent(event) + + return defineResult({ recordEvent: true }) + }) + + cy.mount({ render: () => }) + + cy.get('@recordEvent').should('have.been.calledWith', { + campaign: 'Set up your organization', + medium: 'Specs Create Organization Banner', + messageId: Cypress.sinon.match.string, + cohort: null, + }) + }) }) diff --git a/packages/app/src/specs/banners/CreateOrganizationBanner.vue b/packages/app/src/specs/banners/CreateOrganizationBanner.vue index 9edbd18f4911..21ef93f3c2e2 100644 --- a/packages/app/src/specs/banners/CreateOrganizationBanner.vue +++ b/packages/app/src/specs/banners/CreateOrganizationBanner.vue @@ -8,6 +8,12 @@ class="mb-16px" :icon="OrganizationIcon" dismissible + :has-banner-been-shown="hasBannerBeenShown" + :event-data="{ + campaign: 'Set up your organization', + medium: 'Specs Create Organization Banner', + cohort: '' // TODO Connect cohort + }" @update:model-value="value => emit('update:modelValue', value)" >

@@ -48,8 +54,10 @@ query CreateOrganizationBanner { withDefaults(defineProps<{ modelValue: boolean + hasBannerBeenShown: boolean }>(), { modelValue: false, + hasBannerBeenShown: true, }) const emit = defineEmits<{ diff --git a/packages/app/src/specs/banners/LoginBanner.cy.tsx b/packages/app/src/specs/banners/LoginBanner.cy.tsx index be35a5586ea4..5336a9c5338a 100644 --- a/packages/app/src/specs/banners/LoginBanner.cy.tsx +++ b/packages/app/src/specs/banners/LoginBanner.cy.tsx @@ -1,5 +1,6 @@ import { defaultMessages } from '@cy/i18n' import LoginBanner from './LoginBanner.vue' +import { TrackedBanner_RecordBannerSeenDocument } from '../../generated/graphql' describe('', () => { it('should render expected content', () => { @@ -11,4 +12,23 @@ describe('', () => { cy.percySnapshot() }) + + it('should record expected event on mount', () => { + const recordEvent = cy.stub().as('recordEvent') + + cy.stubMutationResolver(TrackedBanner_RecordBannerSeenDocument, (defineResult, event) => { + recordEvent(event) + + return defineResult({ recordEvent: true }) + }) + + cy.mount({ render: () => }) + + cy.get('@recordEvent').should('have.been.calledWith', { + campaign: 'Log In', + medium: 'Specs Login Banner', + messageId: Cypress.sinon.match.string, + cohort: null, + }) + }) }) diff --git a/packages/app/src/specs/banners/LoginBanner.vue b/packages/app/src/specs/banners/LoginBanner.vue index d98a2279c474..b48a4e7663b1 100644 --- a/packages/app/src/specs/banners/LoginBanner.vue +++ b/packages/app/src/specs/banners/LoginBanner.vue @@ -8,6 +8,12 @@ class="mb-16px" :icon="ConnectIcon" dismissible + :has-banner-been-shown="hasBannerBeenShown" + :event-data="{ + campaign: 'Log In', + medium: 'Specs Login Banner', + cohort: '' // TODO Connect cohort + }" @update:model-value="value => emit('update:modelValue', value)" >

@@ -50,8 +56,10 @@ query LoginBanner { withDefaults(defineProps<{ modelValue: boolean + hasBannerBeenShown: boolean }>(), { modelValue: false, + hasBannerBeenShown: true, }) const emit = defineEmits<{ diff --git a/packages/app/src/specs/banners/RecordBanner.cy.tsx b/packages/app/src/specs/banners/RecordBanner.cy.tsx index ff7f8acf8d2b..7ceac5a438e8 100644 --- a/packages/app/src/specs/banners/RecordBanner.cy.tsx +++ b/packages/app/src/specs/banners/RecordBanner.cy.tsx @@ -1,5 +1,6 @@ import { defaultMessages } from '@cy/i18n' import RecordBanner from './RecordBanner.vue' +import { TrackedBanner_RecordBannerSeenDocument } from '../../generated/graphql' describe('', () => { it('should render expected content', () => { @@ -27,4 +28,23 @@ describe('', () => { cy.percySnapshot() }) + + it('should record expected event on mount', () => { + const recordEvent = cy.stub().as('recordEvent') + + cy.stubMutationResolver(TrackedBanner_RecordBannerSeenDocument, (defineResult, event) => { + recordEvent(event) + + return defineResult({ recordEvent: true }) + }) + + cy.mount({ render: () => }) + + cy.get('@recordEvent').should('have.been.calledWith', { + campaign: 'Record Runs', + medium: 'Specs Record Runs Banner', + messageId: Cypress.sinon.match.string, + cohort: null, + }) + }) }) diff --git a/packages/app/src/specs/banners/RecordBanner.vue b/packages/app/src/specs/banners/RecordBanner.vue index 3e364cc99b07..3e09530500f2 100644 --- a/packages/app/src/specs/banners/RecordBanner.vue +++ b/packages/app/src/specs/banners/RecordBanner.vue @@ -8,6 +8,12 @@ class="mb-16px" :icon="RecordIcon" dismissible + :has-banner-been-shown="hasBannerBeenShown" + :event-data="{ + campaign: 'Record Runs', + medium: 'Specs Record Runs Banner', + cohort: '' // TODO Connect cohort + }" @update:model-value="value => emit('update:modelValue', value)" >

@@ -54,8 +60,10 @@ query RecordBanner { withDefaults(defineProps<{ modelValue: boolean + hasBannerBeenShown: boolean }>(), { modelValue: false, + hasBannerBeenShown: true, }) const emit = defineEmits<{ diff --git a/packages/app/src/specs/banners/TrackedBanner.cy.tsx b/packages/app/src/specs/banners/TrackedBanner.cy.tsx index 8a1feb89c0d7..b2ed41d430d8 100644 --- a/packages/app/src/specs/banners/TrackedBanner.cy.tsx +++ b/packages/app/src/specs/banners/TrackedBanner.cy.tsx @@ -1,10 +1,10 @@ import TrackedBanner from './TrackedBanner.vue' import { ref } from 'vue' -import { TrackedBanner_SetProjectStateDocument } from '../../generated/graphql' +import { TrackedBanner_RecordBannerSeenDocument, TrackedBanner_SetProjectStateDocument } from '../../generated/graphql' describe('', () => { it('should pass through props and child content', () => { - cy.mount({ render: () => Test Content }) + cy.mount({ render: () => Test Content }) cy.findByText('Test Content').should('be.visible') cy.findByTestId('alert-suffix-icon').should('be.visible') @@ -25,7 +25,7 @@ describe('', () => { // Initially mount as visible // @ts-ignore - cy.mount({ render: () => }) + cy.mount({ render: () => }) cy.get('[data-cy="banner"]').as('banner') @@ -48,7 +48,7 @@ describe('', () => { // Initially mount as visible // @ts-ignore - cy.mount({ render: () => }) + cy.mount({ render: () => }) cy.get('[data-cy="banner"]').as('banner') @@ -61,4 +61,47 @@ describe('', () => { expect(recordStub).to.have.been.calledWith('{"banners":{"test-banner":{"dismissed":1234}}}') }) }) + + describe('event recording', () => { + beforeEach(() => { + const recordEventStub = cy.stub().as('recordEvent') + + cy.stubMutationResolver(TrackedBanner_RecordBannerSeenDocument, (defineResult, event) => { + recordEventStub(event) + + return defineResult({ recordEvent: true }) + }) + }) + + context('when banner not previously shown', () => { + beforeEach(() => { + cy.mount({ + render: () => , + }) + }) + + it('should record event', () => { + cy.get('@recordEvent').should('have.been.calledOnce') + cy.get('@recordEvent').should( + 'have.been.calledWith', + Cypress.sinon.match({ campaign: 'CAM', messageId: Cypress.sinon.match.string, medium: 'MED', cohort: 'COH' }), + ) + }) + + it('should debounce event recording', () => { + cy.wait(250) + cy.get('@recordEvent').should('have.been.calledOnce') + }) + }) + + context('when banner has been previously shown', () => { + beforeEach(() => { + cy.mount({ render: () => }) + }) + + it('should not record event', () => { + cy.get('@recordEvent').should('not.have.been.called') + }) + }) + }) }) diff --git a/packages/app/src/specs/banners/TrackedBanner.vue b/packages/app/src/specs/banners/TrackedBanner.vue index e9f8b925e32a..c4c71be73373 100644 --- a/packages/app/src/specs/banners/TrackedBanner.vue +++ b/packages/app/src/specs/banners/TrackedBanner.vue @@ -10,16 +10,25 @@ diff --git a/packages/data-context/src/DataActions.ts b/packages/data-context/src/DataActions.ts index 8a8276264bf0..24d02ba7abde 100644 --- a/packages/data-context/src/DataActions.ts +++ b/packages/data-context/src/DataActions.ts @@ -12,6 +12,7 @@ import { AuthActions, } from './actions' import { ErrorActions } from './actions/ErrorActions' +import { EventCollectorActions } from './actions/EventCollectorActions' import { VersionsActions } from './actions/VersionsActions' import { cached } from './util' @@ -77,4 +78,9 @@ export class DataActions { get versions () { return new VersionsActions(this.ctx) } + + @cached + get eventCollector () { + return new EventCollectorActions(this.ctx) + } } diff --git a/packages/data-context/src/actions/AuthActions.ts b/packages/data-context/src/actions/AuthActions.ts index 4fd173e7e23d..af1816e3a3cf 100644 --- a/packages/data-context/src/actions/AuthActions.ts +++ b/packages/data-context/src/actions/AuthActions.ts @@ -4,7 +4,7 @@ import type { AuthenticatedUserShape, AuthStateShape } from '../data' export interface AuthApiShape { getUser(): Promise> - logIn(onMessage: (message: AuthStateShape) => void, utmSource: string, utmMedium: string): Promise + logIn(onMessage: (message: AuthStateShape) => void, utmSource: string, utmMedium: string, utmContent: string | null): Promise logOut(): Promise resetAuthState(): void } @@ -48,7 +48,7 @@ export class AuthActions { } } - async login (utmSource: string, utmMedium: string) { + async login (utmSource: string, utmMedium: string, utmContent?: string | null) { const onMessage = (authState: AuthStateShape) => { this.ctx.update((coreData) => { coreData.authState = authState @@ -66,7 +66,7 @@ export class AuthActions { this.#cancelActiveLogin = () => resolve(null) // NOTE: auth.logIn should never reject, it uses `onMessage` to propagate state changes (including errors) to the frontend. - this.authApi.logIn(onMessage, utmSource, utmMedium).then(resolve, reject) + this.authApi.logIn(onMessage, utmSource, utmMedium, utmContent || null).then(resolve, reject) }) const isMainWindowFocused = this.ctx._apis.electronApi.isMainWindowFocused() diff --git a/packages/data-context/src/actions/EventCollectorActions.ts b/packages/data-context/src/actions/EventCollectorActions.ts new file mode 100644 index 000000000000..3778ed95c349 --- /dev/null +++ b/packages/data-context/src/actions/EventCollectorActions.ts @@ -0,0 +1,38 @@ +import type { DataContext } from '..' +import Debug from 'debug' + +const debug = Debug('cypress:data-context:sources:EventCollectorDataSource') + +interface CollectableEvent { + campaign: string + messageId: string + medium: string + cohort?: string +} + +const cloudEnv = (process.env.CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV || 'staging') as 'development' | 'staging' | 'production' + +export class EventCollectorActions { + constructor (private ctx: DataContext) { + debug('Using %s environment for Event Collection', cloudEnv) + } + + async recordEvent (event: CollectableEvent): Promise { + try { + const dashboardUrl = this.ctx.cloud.getDashboardUrl(cloudEnv) + + await this.ctx.util.fetch( + `${dashboardUrl}/anon-collect`, + { method: 'POST', body: JSON.stringify(event) }, + ) + + debug(`Recorded event: %o`, event) + + return true + } catch (err) { + debug(`Failed to record event %o due to error %o`, event, err) + + return false + } + } +} diff --git a/packages/data-context/src/actions/index.ts b/packages/data-context/src/actions/index.ts index 927c68695cea..aa499b57a7cf 100644 --- a/packages/data-context/src/actions/index.ts +++ b/packages/data-context/src/actions/index.ts @@ -8,6 +8,7 @@ export * from './DataEmitterActions' export * from './DevActions' export * from './ElectronActions' export * from './ErrorActions' +export * from './EventCollectorActions' export * from './FileActions' export * from './LocalSettingsActions' export * from './MigrationActions' diff --git a/packages/data-context/src/sources/CloudDataSource.ts b/packages/data-context/src/sources/CloudDataSource.ts index a63bd4681437..1fca98c176ee 100644 --- a/packages/data-context/src/sources/CloudDataSource.ts +++ b/packages/data-context/src/sources/CloudDataSource.ts @@ -99,7 +99,7 @@ export class CloudDataSource { reset () { return this.#cloudUrqlClient = createClient({ - url: `${REMOTE_SCHEMA_URLS[cloudEnv]}/test-runner-graphql`, + url: `${this.getDashboardUrl(cloudEnv)}/test-runner-graphql`, exchanges: [ dedupExchange, cacheExchange({ @@ -332,4 +332,8 @@ export class CloudDataSource { return JSON.parse(this.#lastCache ?? '') } + + getDashboardUrl (env: keyof typeof REMOTE_SCHEMA_URLS) { + return REMOTE_SCHEMA_URLS[env] + } } diff --git a/packages/data-context/test/unit/actions/EventCollectorActions.spec.ts b/packages/data-context/test/unit/actions/EventCollectorActions.spec.ts new file mode 100644 index 000000000000..ae1f3c5c4d2a --- /dev/null +++ b/packages/data-context/test/unit/actions/EventCollectorActions.spec.ts @@ -0,0 +1,55 @@ +import type { DataContext } from '../../../src' +import { EventCollectorActions } from '../../../src/actions/EventCollectorActions' +import { createTestDataContext } from '../helper' +import sinon, { SinonStub } from 'sinon' +import sinonChai from 'sinon-chai' +import chai, { expect } from 'chai' + +chai.use(sinonChai) + +describe('EventCollectorActions', () => { + let ctx: DataContext + let actions: EventCollectorActions + + beforeEach(() => { + sinon.restore() + + ctx = createTestDataContext('open') + + sinon.stub(ctx.util, 'fetch').resolves({} as any) + + actions = new EventCollectorActions(ctx) + }) + + context('.recordEvent', () => { + it('makes expected request', async () => { + await actions.recordEvent({ + campaign: 'abc', + medium: 'def', + messageId: 'ghi', + cohort: '123', + }) + + expect(ctx.util.fetch).to.have.been.calledOnceWith( + sinon.match(/anon-collect$/), // Verify URL ends with expected 'anon-collect' path + { method: 'POST', body: '{"campaign":"abc","medium":"def","messageId":"ghi","cohort":"123"}' }, + ) + }) + + it('resolve true if request succeeds', async () => { + (ctx.util.fetch as SinonStub).resolves({} as any) + + const result = await actions.recordEvent({ campaign: '', medium: '', messageId: '', cohort: '' }) + + expect(result).to.eql(true) + }) + + it('resolves false if request fails', async () => { + (ctx.util.fetch as SinonStub).rejects({} as any) + + const result = await actions.recordEvent({ campaign: '', medium: '', messageId: '', cohort: '' }) + + expect(result).to.eql(false) + }) + }) +}) diff --git a/packages/frontend-shared/src/gql-components/Auth.vue b/packages/frontend-shared/src/gql-components/Auth.vue index 52ad97665168..9fd6d536fb01 100644 --- a/packages/frontend-shared/src/gql-components/Auth.vue +++ b/packages/frontend-shared/src/gql-components/Auth.vue @@ -91,6 +91,7 @@ const props = defineProps<{ showRetry?: boolean showLogout?: boolean utmMedium: string + utmContent?: string showConnectButtonAfterLogin?: boolean }>() @@ -118,8 +119,8 @@ mutation Auth_Logout { ` gql` -mutation Auth_Login ($utmSource: String!, $utmMedium: String!) { - login (utmSource: $utmSource, utmMedium: $utmMedium) { +mutation Auth_Login ($utmSource: String!, $utmMedium: String!, $utmContent: String) { + login (utmSource: $utmSource, utmContent: $utmContent, utmMedium: $utmMedium) { ...Auth } } @@ -194,7 +195,7 @@ const handleLoginOrContinue = async () => { loginInitiated.value = true - login.executeMutation({ utmMedium: props.utmMedium, utmSource: getUtmSource() }) + login.executeMutation({ utmMedium: props.utmMedium, utmContent: props.utmContent || null, utmSource: getUtmSource() }) } const handleLogout = () => { @@ -204,7 +205,7 @@ const handleLogout = () => { const handleTryAgain = async () => { await reset.executeMutation({}) - login.executeMutation({ utmMedium: props.utmMedium, utmSource: getUtmSource() }) + login.executeMutation({ utmMedium: props.utmMedium, utmContent: props.utmContent || null, utmSource: getUtmSource() }) } const handleCancel = () => { diff --git a/packages/frontend-shared/src/gql-components/topnav/LoginModal.vue b/packages/frontend-shared/src/gql-components/topnav/LoginModal.vue index bd23fcace06c..1a9b876d55a0 100644 --- a/packages/frontend-shared/src/gql-components/topnav/LoginModal.vue +++ b/packages/frontend-shared/src/gql-components/topnav/LoginModal.vue @@ -80,6 +80,7 @@ :gql="props.gql" :show-retry="!!error" :utm-medium="props.utmMedium" + :utm-content="props.utmContent" :show-connect-button-after-login="props.showConnectButtonAfterLogin" @continue="continueAuth" @connect-project="handleConnectProject" @@ -135,6 +136,7 @@ const props = defineProps<{ modelValue: boolean gql: LoginModalFragment utmMedium: string + utmContent?: string showConnectButtonAfterLogin?: boolean }>() diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index b306ff8bc67a..1cff35e34834 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -1234,7 +1234,7 @@ type Mutation { ): [RemoteFetchable]! """Auth with Cypress Dashboard""" - login(utmMedium: String!, utmSource: String!): Query + login(utmContent: String, utmMedium: String!, utmSource: String!): Query """Log out of Cypress Dashboard""" logout: Query @@ -1289,6 +1289,11 @@ type Mutation { """show the launchpad windows""" reconfigureProject: Boolean! + """ + Dispatch an event to the dashboard to be recorded. Events are completely anonymous and are only used to identify aggregate usage patterns across all Cypress users. + """ + recordEvent(campaign: String!, cohort: String, medium: String!, messageId: String!): Boolean + """ Signal that we are explicitly refetching remote data and should not use the server cache """ diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts index 1a05a68d4a17..6f49ed55c4f9 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts @@ -264,10 +264,11 @@ export const mutation = mutationType({ description: 'Auth with Cypress Dashboard', args: { utmMedium: nonNull(stringArg()), + utmContent: stringArg(), utmSource: nonNull(stringArg()), }, resolve: async (_, args, ctx) => { - await ctx.actions.auth.login(args.utmSource, args.utmMedium) + await ctx.actions.auth.login(args.utmSource, args.utmMedium, args.utmContent) return {} }, @@ -683,6 +684,25 @@ export const mutation = mutationType({ }, }) + t.field('recordEvent', { + type: 'Boolean', + description: 'Dispatch an event to the dashboard to be recorded. Events are completely anonymous and are only used to identify aggregate usage patterns across all Cypress users.', + args: { + campaign: nonNull(stringArg({})), + messageId: nonNull(stringArg({})), + medium: nonNull(stringArg({})), + cohort: stringArg({}), + }, + resolve: (source, args, ctx) => { + return ctx.actions.eventCollector.recordEvent({ + campaign: args.campaign, + messageId: args.messageId, + medium: args.medium, + cohort: args.cohort || undefined, + }) + }, + }) + t.boolean('_clearCloudCache', { description: 'Internal use only, clears the cloud cache', resolve: (source, args, ctx) => { diff --git a/packages/server/lib/gui/auth.ts b/packages/server/lib/gui/auth.ts index bdd2b3d90bac..af3d02528700 100644 --- a/packages/server/lib/gui/auth.ts +++ b/packages/server/lib/gui/auth.ts @@ -24,7 +24,7 @@ const buildLoginRedirectUrl = (server) => { return `http://127.0.0.1:${port}/redirect-to-auth` } -const buildFullLoginUrl = (baseLoginUrl, server, utmSource, utmMedium) => { +const buildFullLoginUrl = (baseLoginUrl, server, utmSource, utmMedium, utmContent) => { const { port } = server.address() if (!authState) { @@ -48,6 +48,7 @@ const buildFullLoginUrl = (baseLoginUrl, server, utmSource, utmMedium) => { utm_source: utmSource, utm_medium: utmMedium, utm_campaign: 'Log In', + utm_content: utmContent, ...authUrl.query, } } @@ -65,7 +66,7 @@ const getOriginFromUrl = (originalUrl) => { /** * @returns the currently running auth server instance, launches one if there is not one */ -const launchServer = (baseLoginUrl, sendMessage, utmSource, utmMedium) => { +const launchServer = (baseLoginUrl, sendMessage, utmSource, utmMedium, utmContent) => { if (!server) { // launch an express server to listen for the auth callback from dashboard const origin = getOriginFromUrl(baseLoginUrl) @@ -76,7 +77,7 @@ const launchServer = (baseLoginUrl, sendMessage, utmSource, utmMedium) => { app.get('/redirect-to-auth', (req, res) => { authRedirectReached = true - buildFullLoginUrl(baseLoginUrl, server, utmSource, utmMedium) + buildFullLoginUrl(baseLoginUrl, server, utmSource, utmMedium, utmContent) .then((fullLoginUrl) => { debug('Received GET to /redirect-to-auth, redirecting: %o', { fullLoginUrl }) @@ -187,7 +188,7 @@ const _internal = { /** * @returns a promise that is resolved with a user when auth is complete or rejected when it fails */ -const start = (onMessage, utmSource, utmMedium) => { +const start = (onMessage, utmSource, utmMedium, utmContent) => { function sendMessage (name, message) { onMessage({ name, @@ -199,7 +200,7 @@ const start = (onMessage, utmSource, utmMedium) => { return user.getBaseLoginUrl() .then((baseLoginUrl) => { - return _internal.launchServer(baseLoginUrl, sendMessage, utmSource, utmMedium) + return _internal.launchServer(baseLoginUrl, sendMessage, utmSource, utmMedium, utmContent) }) .then(() => { return _internal.buildLoginRedirectUrl(server) diff --git a/packages/server/lib/makeDataContext.ts b/packages/server/lib/makeDataContext.ts index 093a4965b202..1d400d5d1c04 100644 --- a/packages/server/lib/makeDataContext.ts +++ b/packages/server/lib/makeDataContext.ts @@ -64,8 +64,8 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext { getUser () { return user.get() }, - logIn (onMessage, utmSource, utmMedium) { - return auth.start(onMessage, utmSource, utmMedium) + logIn (onMessage, utmSource, utmMedium, utmContent) { + return auth.start(onMessage, utmSource, utmMedium, utmContent) }, logOut () { return user.logOut() diff --git a/packages/server/test/unit/gui/auth_spec.js b/packages/server/test/unit/gui/auth_spec.js index fc1a00deee16..c5567a24dbc1 100644 --- a/packages/server/test/unit/gui/auth_spec.js +++ b/packages/server/test/unit/gui/auth_spec.js @@ -15,7 +15,7 @@ const RANDOM_STRING = 'a'.repeat(32) const PORT = 9001 const REDIRECT_URL = `http://127.0.0.1:${PORT}/redirect-to-auth` const FULL_LOGIN_URL = `https://foo.invalid/login.html?port=${PORT}&state=${RANDOM_STRING}&machineId=abc123&cypressVersion=${pkg.version}&platform=linux` -const FULL_LOGIN_URL_UTM = `https://foo.invalid/login.html?utm_source=UTM%20Source&utm_medium=UTM%20Medium&utm_campaign=Log%20In&port=${PORT}&state=${RANDOM_STRING}&machineId=abc123&cypressVersion=${pkg.version}&platform=linux` +const FULL_LOGIN_URL_UTM = `https://foo.invalid/login.html?utm_source=UTM%20Source&utm_medium=UTM%20Medium&utm_campaign=Log%20In&utm_content=UTM%20Content&port=${PORT}&state=${RANDOM_STRING}&machineId=abc123&cypressVersion=${pkg.version}&platform=linux` describe('lib/gui/auth', function () { beforeEach(() => { @@ -71,7 +71,7 @@ describe('lib/gui/auth', function () { }) it('uses utm code to form a trackable URL', function () { - return auth._internal.buildFullLoginUrl(BASE_URL, this.server, 'UTM Source', 'UTM Medium') + return auth._internal.buildFullLoginUrl(BASE_URL, this.server, 'UTM Source', 'UTM Medium', 'UTM Content') .then((url) => { expect(url).to.eq(FULL_LOGIN_URL_UTM) }) diff --git a/scripts/gulp/gulpConstants.ts b/scripts/gulp/gulpConstants.ts index 191267e41174..9ff448e62c08 100644 --- a/scripts/gulp/gulpConstants.ts +++ b/scripts/gulp/gulpConstants.ts @@ -10,6 +10,8 @@ declare global { export const DEFAULT_INTERNAL_CLOUD_ENV = process.env.CYPRESS_INTERNAL_ENV || 'production' +export const DEFAULT_INTERNAL_EVENT_COLLECTOR_ENV = process.env.CYPRESS_INTERNAL_ENV || 'staging' + export type MODES = 'dev' | 'devWatch' | 'test' export const ENV_VARS = { @@ -17,17 +19,20 @@ export const ENV_VARS = { PROD: { CYPRESS_INTERNAL_ENV: 'production', CYPRESS_INTERNAL_CLOUD_ENV: 'production', + CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV: 'production', }, // Uses the "built" vite assets, not the served ones DEV_OPEN: { CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, + CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV: DEFAULT_INTERNAL_EVENT_COLLECTOR_ENV, }, // Used when we're running Cypress in true "development" mode DEV: { CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, + CYPRESS_INTERNAL_EVENT_COLLECTOR_ENV: DEFAULT_INTERNAL_EVENT_COLLECTOR_ENV, }, } diff --git a/yarn.lock b/yarn.lock index 833fbc9757b1..809b6aa89da7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24381,7 +24381,7 @@ nanoid@3.3.1: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== -nanoid@^3.3.4: +nanoid@3.3.4, nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== From 0f00d4ca60fb74913467a19ae66a5ac4e52a5e96 Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Thu, 8 Sep 2022 17:19:13 -0500 Subject: [PATCH 27/49] perf: remove stale runnables obj (#23737) --- packages/driver/src/cypress/runner.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/driver/src/cypress/runner.ts b/packages/driver/src/cypress/runner.ts index 64e5063ef58b..77b643c23fb1 100644 --- a/packages/driver/src/cypress/runner.ts +++ b/packages/driver/src/cypress/runner.ts @@ -486,7 +486,7 @@ const hasOnly = (suite) => { ) } -const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onRunnable, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { +const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { let hasTests = false // only loop until we find the first test @@ -505,7 +505,7 @@ const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onRunnab // create optimized lookups for the tests without // traversing through it multiple times const tests: Record = {} - const normalizedSuite = normalize(suite, tests, initialTests, onRunnable, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedSuite = normalize(suite, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if (setTestsById) { // use callback here to hand back @@ -542,7 +542,7 @@ const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onRunnab return normalizedSuite } -const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { +const normalize = (runnable, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { const normalizeRunnable = (runnable) => { if (!runnable.id) { runnable.id = getRunnableId() @@ -553,10 +553,6 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getRun runnable.type = 'suite' } - if (onRunnable) { - onRunnable(runnable) - } - // if we have a runnable in the initial state // then merge in existing properties into the runnable const i = initialTests[runnable.id] @@ -651,7 +647,7 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getRun _.each({ tests: runnableTests, suites: runnableSuites }, (_runnables, type) => { if (runnable[type]) { return normalizedRunnable[type] = _.compact(_.map(_runnables, (childRunnable) => { - const normalizedChild = normalize(childRunnable, tests, initialTests, onRunnable, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedChild = normalize(childRunnable, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if (type === 'tests' && onlyIdMode()) { if (normalizedChild.id === getOnlyTestId()) { @@ -740,7 +736,7 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getRun suite.suites = [] normalizedSuite.suites = _.compact(_.map(suiteSuites, (childSuite) => { - const normalizedChildSuite = normalize(childSuite, tests, initialTests, onRunnable, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedChildSuite = normalize(childSuite, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if ((suite._onlySuites.indexOf(childSuite) !== -1) || filterOnly(normalizedChildSuite, childSuite)) { if (onlyIdMode()) { @@ -1134,8 +1130,6 @@ export default { let _testsById: Record = {} const _testsQueue: any[] = [] const _testsQueueById: Record = {} - // only used during normalization - const _runnables: any[] = [] const _logsById: Record = {} let _emissions: Emissions = { started: {}, @@ -1165,11 +1159,6 @@ export default { return _tests } - const onRunnable = (r) => { - // set default retries at onRunnable time instead of onRunnableRun - return _runnables.push(r) - } - const onLogsById = (l) => { if (_skipCollectingLogs) return @@ -1354,7 +1343,6 @@ export default { tests, setTestsById, setTests, - onRunnable, onLogsById, getRunnableId, getHookId, From e32e3638df619a4d71f293de5637713db73ddbcf Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Thu, 8 Sep 2022 21:19:04 -0500 Subject: [PATCH 28/49] fix: suite-level-only configuration does not throw when config is restored (#23646) --- circle.yml | 2 +- packages/driver/cypress/e2e/util/config.cy.js | 26 ++++++++ packages/driver/src/cy/testConfigOverrides.ts | 7 ++- packages/driver/src/util/config.ts | 18 +++--- .../testConfigOverrides_spec.ts.js | 63 +++++++++++++++++++ .../testConfigOverrides/valid-suite-only.js | 29 +++++++++ system-tests/test/testConfigOverrides_spec.ts | 10 +++ 7 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 system-tests/projects/e2e/cypress/e2e/testConfigOverrides/valid-suite-only.js diff --git a/circle.yml b/circle.yml index 54562ebfa4f5..b68dbfaeb4df 100644 --- a/circle.yml +++ b/circle.yml @@ -1337,7 +1337,7 @@ jobs: steps: - run: yarn test-scripts # make sure packages with TypeScript can be transpiled to JS - - run: yarn lerna run build-prod --stream + - run: yarn lerna run build-prod --stream --concurrency 4 # run unit tests from each individual package - run: yarn test # run type checking for each individual package diff --git a/packages/driver/cypress/e2e/util/config.cy.js b/packages/driver/cypress/e2e/util/config.cy.js index c74840a9dd5c..47d4af4e1e9d 100644 --- a/packages/driver/cypress/e2e/util/config.cy.js +++ b/packages/driver/cypress/e2e/util/config.cy.js @@ -24,6 +24,18 @@ describe('driver/src/cypress/validate_config', () => { expect(overrideLevel).to.be.undefined }) + it('returns override level of restoring', () => { + const state = $SetterGetter.create({ + duringUserTestExecution: false, + test: { + _testConfig: { applied: 'restoring' }, + }, + }) + const overrideLevel = getMochaOverrideLevel(state) + + expect(overrideLevel).to.eq('restoring') + }) + it('returns override level of suite', () => { const state = $SetterGetter.create({ duringUserTestExecution: false, @@ -170,6 +182,20 @@ describe('driver/src/cypress/validate_config', () => { }).not.to.throw() }) + it('skips checking override level when restoring global configuration before next test', () => { + const state = $SetterGetter.create({ + duringUserTestExecution: false, + test: { + _testConfig: { applied: 'restoring' }, + }, + specWindow: { Error }, + }) + + expect(() => { + validateConfig(state, { testIsolation: 'legacy' }) + }).not.to.throw() + }) + it('throws when invalid configuration value', () => { const state = $SetterGetter.create({ duringUserTestExecution: true, diff --git a/packages/driver/src/cy/testConfigOverrides.ts b/packages/driver/src/cy/testConfigOverrides.ts index 5a49371c18b3..cf7a096a850d 100644 --- a/packages/driver/src/cy/testConfigOverrides.ts +++ b/packages/driver/src/cy/testConfigOverrides.ts @@ -3,7 +3,7 @@ import $errUtils from '../cypress/error_utils' // See Test Config Overrides in ../../../../cli/types/cypress.d.ts -const mochaOverrideLevel = ['suite', 'test'] as const +const mochaOverrideLevel = ['restoring', 'suite', 'test'] as const export type MochaOverrideLevel = typeof mochaOverrideLevel[number] @@ -158,7 +158,10 @@ export class TestConfigOverride { private restoreTestConfigFn: Cypress.Nullable<() => void> = null restoreAndSetTestConfigOverrides (test, config, env) { - if (this.restoreTestConfigFn) this.restoreTestConfigFn() + if (this.restoreTestConfigFn) { + test._testConfig.applied = 'restoring' + this.restoreTestConfigFn() + } const resolvedTestConfig = test._testConfig || { unverifiedTestConfig: [], diff --git a/packages/driver/src/util/config.ts b/packages/driver/src/util/config.ts index 59bd3a78c334..2a34d4c11cdb 100644 --- a/packages/driver/src/util/config.ts +++ b/packages/driver/src/util/config.ts @@ -77,7 +77,7 @@ export const getMochaOverrideLevel = (state): MochaOverrideLevel | undefined => const test = state('test') if (!state('duringUserTestExecution') && test && !Object.keys(test?._fired || {}).length) { - return test._testConfig.applied // either suite or test + return test._testConfig.applied } return undefined @@ -87,15 +87,17 @@ export const getMochaOverrideLevel = (state): MochaOverrideLevel | undefined => // be override and that the provided override values are the correct type. // // The run-time override levels (listed in order applied): -// fileLoad - config override via Cypress.config() when either loading the supportFile or specFile in the +// fileLoad - config override via Cypress.config() when either loading the supportFile or specFile in the // browser (this is before mocha as process the spec -// suite - config override via describe('', {...}, () => {}) -// test - config override via it('', {...}, () => {}) -// event - config override via Cypress.config() in test:before:runner or test:before:runner:async event -// runtime - config override via Cypress.config() when the test callback is executed +// restoring - restore global (suite-level) configuration before applying test-specific overrides +// suite - config override via describe('', {...}, () => {}) +// test - config override via it('', {...}, () => {}) +// event - config override via Cypress.config() in test:before:runner or test:before:runner:async event +// runtime - config override via Cypress.config() when the test callback is executed export const validateConfig = (state: State, config: Record, skipConfigOverrideValidation: boolean = false) => { - if (!skipConfigOverrideValidation) { - const mochaOverrideLevel = getMochaOverrideLevel(state) + const mochaOverrideLevel = getMochaOverrideLevel(state) + + if (!skipConfigOverrideValidation && mochaOverrideLevel !== 'restoring') { const isSuiteOverride = mochaOverrideLevel === 'suite' validateOverridableAtRunTime(config, isSuiteOverride, (validationResult) => { diff --git a/system-tests/__snapshots__/testConfigOverrides_spec.ts.js b/system-tests/__snapshots__/testConfigOverrides_spec.ts.js index 9beb5f2a694a..7f79449fb858 100644 --- a/system-tests/__snapshots__/testConfigOverrides_spec.ts.js +++ b/system-tests/__snapshots__/testConfigOverrides_spec.ts.js @@ -1249,3 +1249,66 @@ https://on.cypress.io/config ` + +exports['testConfigOverrides / successfully runs valid suite-level-only overrides'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (valid-suite-only.js) │ + │ Searched: cypress/e2e/testConfigOverrides/valid-suite-only.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: valid-suite-only.js (1 of 1) + + + suite-level-only overrides run as expected + ✓ 1st test passes + ✓ 2nd test passes + ✓ 3rd test passes + + nested contexts + nested suite-level-only overrides run as expected + ✓ 1st test passes + ✓ 2nd test passes + ✓ 3rd test passes + + + 6 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 6 │ + │ Passing: 6 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: false │ + │ Duration: X seconds │ + │ Spec Ran: valid-suite-only.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ valid-suite-only.js XX:XX 6 6 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 6 6 - - - + + +` diff --git a/system-tests/projects/e2e/cypress/e2e/testConfigOverrides/valid-suite-only.js b/system-tests/projects/e2e/cypress/e2e/testConfigOverrides/valid-suite-only.js new file mode 100644 index 000000000000..48f3310abe60 --- /dev/null +++ b/system-tests/projects/e2e/cypress/e2e/testConfigOverrides/valid-suite-only.js @@ -0,0 +1,29 @@ +describe('suite-level-only overrides run as expected', { testIsolation: 'legacy' }, () => { + it('1st test passes', () => { + cy.visit('https://example.cypress.io') + }) + + it('2nd test passes', () => { + cy.url().should('eq', 'https://example.cypress.io/') + }) + + it('3rd test passes', () => { + cy.url().should('eq', 'https://example.cypress.io/') + }) +}) + +describe('nested contexts ', () => { + describe('nested suite-level-only overrides run as expected', { testIsolation: 'legacy' }, () => { + it('1st test passes', () => { + cy.visit('https://example.cypress.io') + }) + + it('2nd test passes', () => { + cy.url().should('eq', 'https://example.cypress.io/') + }) + + it('3rd test passes', () => { + cy.url().should('eq', 'https://example.cypress.io/') + }) + }) +}) diff --git a/system-tests/test/testConfigOverrides_spec.ts b/system-tests/test/testConfigOverrides_spec.ts index 30ee1fc8c6fa..c88c78a3f3ec 100644 --- a/system-tests/test/testConfigOverrides_spec.ts +++ b/system-tests/test/testConfigOverrides_spec.ts @@ -10,6 +10,16 @@ const outputPath = path.join(e2ePath, 'output.json') describe('testConfigOverrides', () => { systemTests.setup() + systemTests.it('successfully runs valid suite-level-only overrides', { + spec: 'testConfigOverrides/valid-suite-only.js', + snapshot: true, + expectedExitCode: 0, + browser: 'electron', + config: { + video: false, + }, + }) + systemTests.it('fails when passing invalid config value browser', { browser: '!webkit', // TODO(webkit): fix+unskip (failing due to broken stack trace) spec: 'testConfigOverrides/invalid-browser.js', From 23d52af3a252dea7450c19c0f80b322fb278050e Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Fri, 9 Sep 2022 23:14:41 +1000 Subject: [PATCH 29/49] chore: update eventemitter2 and vite deps (#23740) --- circle.yml | 2 +- cli/package.json | 2 +- npm/react/package.json | 2 +- npm/vite-dev-server/package.json | 2 +- npm/vue/package.json | 2 +- packages/app/package.json | 2 +- packages/driver/package.json | 4 +- packages/frontend-shared/package.json | 2 +- packages/launchpad/package.json | 2 +- patches/vite+3.0.3.dev.patch | 14 -- yarn.lock | 297 +++++++++++++------------- 11 files changed, 159 insertions(+), 172 deletions(-) delete mode 100644 patches/vite+3.0.3.dev.patch diff --git a/circle.yml b/circle.yml index b68dbfaeb4df..47dacb99542b 100644 --- a/circle.yml +++ b/circle.yml @@ -27,6 +27,7 @@ mainBuildFilters: &mainBuildFilters branches: only: - develop + - fix-ci-deps # usually we don't build Mac app - it takes a long time # but sometimes we want to really confirm we are doing the right thing @@ -45,7 +46,6 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters when: or: - equal: [ develop, << pipeline.git.branch >> ] - - equal: [ "lmiller/experimental-single-tab-component-testing", << pipeline.git.branch >> ] - equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ] - matches: pattern: "-release$" diff --git a/cli/package.json b/cli/package.json index b350b423acba..0cb51269c791 100644 --- a/cli/package.json +++ b/cli/package.json @@ -38,7 +38,7 @@ "dayjs": "^1.10.4", "debug": "^4.3.2", "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", + "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", diff --git a/npm/react/package.json b/npm/react/package.json index a8acf12c8781..32ef2e880cf3 100644 --- a/npm/react/package.json +++ b/npm/react/package.json @@ -27,7 +27,7 @@ "react-router-dom": "6.0.0-alpha.1", "semver": "^7.3.2", "typescript": "^4.7.4", - "vite": "3.0.3", + "vite": "3.1.0", "vite-plugin-require-transform": "1.0.3" }, "peerDependencies": { diff --git a/npm/vite-dev-server/package.json b/npm/vite-dev-server/package.json index 85b284f6ba68..51fbb3a17153 100644 --- a/npm/vite-dev-server/package.json +++ b/npm/vite-dev-server/package.json @@ -27,7 +27,7 @@ "mocha": "^9.2.2", "sinon": "^13.0.1", "ts-node": "^10.9.1", - "vite": "3.0.3", + "vite": "3.1.0", "vite-plugin-inspect": "0.4.3" }, "files": [ diff --git a/npm/vue/package.json b/npm/vue/package.json index 5bbb2d623b80..ade741d517bc 100644 --- a/npm/vue/package.json +++ b/npm/vue/package.json @@ -25,7 +25,7 @@ "globby": "^11.0.1", "tailwindcss": "1.1.4", "typescript": "^4.7.4", - "vite": "3.0.3", + "vite": "3.1.0", "vue": "3.2.31", "vue-i18n": "9.0.0-rc.6", "vue-router": "^4.0.0", diff --git a/packages/app/package.json b/packages/app/package.json index 54378618eed4..b7051e9e28f0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -61,7 +61,7 @@ "rollup-plugin-polyfill-node": "^0.7.0", "unplugin-icons": "0.13.2", "unplugin-vue-components": "^0.15.2", - "vite": "3.0.3", + "vite": "3.1.0", "vite-plugin-components": "0.11.3", "vite-plugin-pages": "0.18.1", "vite-plugin-vue-layouts": "0.6.0", diff --git a/packages/driver/package.json b/packages/driver/package.json index 8099cb4ebc65..196be0f7bd3f 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -52,7 +52,7 @@ "debug": "^4.3.2", "error-stack-parser": "2.0.6", "errorhandler": "1.5.1", - "eventemitter2": "6.4.2", + "eventemitter2": "6.4.7", "express": "4.17.1", "is-valid-domain": "0.0.20", "is-valid-hostname": "1.0.1", @@ -83,7 +83,7 @@ "unfetch": "4.1.0", "url-parse": "1.5.9", "vanilla-text-mask": "5.1.1", - "vite": "3.0.3", + "vite": "3.1.0", "webpack": "^4.44.2", "zone.js": "0.9.0" }, diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 9883366b2fb2..2d48b490dfaa 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -65,7 +65,7 @@ "shiki": "^0.9.12", "unplugin-icons": "0.13.2", "unplugin-vue-components": "^0.15.4", - "vite": "3.0.3", + "vite": "3.1.0", "vite-plugin-components": "0.11.3", "vite-svg-loader": "3.1.2", "vue": "3.2.31", diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index a41056c97635..893e96373700 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -60,7 +60,7 @@ "rimraf": "3.0.2", "rollup-plugin-polyfill-node": "^0.7.0", "type-fest": "^2.3.4", - "vite": "3.0.3", + "vite": "3.1.0", "vite-plugin-components": "0.11.3", "vite-plugin-optimize-persist": "0.0.5", "vite-plugin-package-config": "0.0.3", diff --git a/patches/vite+3.0.3.dev.patch b/patches/vite+3.0.3.dev.patch deleted file mode 100644 index 561773873c6d..000000000000 --- a/patches/vite+3.0.3.dev.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/node_modules/vite/dist/node/chunks/dep-c6273c7a.js b/node_modules/vite/dist/node/chunks/dep-c6273c7a.js -index 22e37a1..ee75032 100644 ---- a/node_modules/vite/dist/node/chunks/dep-c6273c7a.js -+++ b/node_modules/vite/dist/node/chunks/dep-c6273c7a.js -@@ -62830,7 +62830,8 @@ async function bundleConfigFile(fileName, isESM) { - if (path$n.relative(idPkgDir, fileName).startsWith('..')) { - return { - // normalize actual import after bundled as a single vite config -- path: idFsPath, -+ // TODO: remove this patch once Vite 3.0.5 is released. -+ path: pathToFileURL(idFsPath).href, - external: true - }; - } diff --git a/yarn.lock b/yarn.lock index 809b6aa89da7..8e87626d8672 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2580,6 +2580,11 @@ ts-node "^9" tslib "^2" +"@esbuild/linux-loong64@0.15.7": + version "0.15.7" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz#1ec4af4a16c554cbd402cc557ccdd874e3f7be53" + integrity sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw== + "@eslint/eslintrc@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" @@ -15222,131 +15227,132 @@ es6-weak-map@^2.0.1: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild-android-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.50.tgz#a46fc80fa2007690e647680d837483a750a3097f" - integrity sha512-H7iUEm7gUJHzidsBlFPGF6FTExazcgXL/46xxLo6i6bMtPim6ZmXyTccS8yOMpy6HAC6dPZ/JCQqrkkin69n6Q== - -esbuild-android-arm64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.50.tgz#bdda7851fa7f5f770d6ff0ad593a8945d3a0fcdd" - integrity sha512-NFaoqEwa+OYfoYVpQWDMdKII7wZZkAjtJFo1WdnBeCYlYikvUhTnf2aPwPu5qEAw/ie1NYK0yn3cafwP+kP+OQ== - -esbuild-darwin-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.50.tgz#f0535435f9760766f30db14a991ee5ca94c022a4" - integrity sha512-gDQsCvGnZiJv9cfdO48QqxkRV8oKAXgR2CGp7TdIpccwFdJMHf8hyIJhMW/05b/HJjET/26Us27Jx91BFfEVSA== - -esbuild-darwin-arm64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.50.tgz#76a41a40e8947a15ae62970e9ed2853883c4b16c" - integrity sha512-36nNs5OjKIb/Q50Sgp8+rYW/PqirRiFN0NFc9hEvgPzNJxeJedktXwzfJSln4EcRFRh5Vz4IlqFRScp+aiBBzA== - -esbuild-freebsd-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.50.tgz#2ed6633c17ed42c20a1bd68e82c4bbc75ea4fb57" - integrity sha512-/1pHHCUem8e/R86/uR+4v5diI2CtBdiWKiqGuPa9b/0x3Nwdh5AOH7lj+8823C6uX1e0ufwkSLkS+aFZiBCWxA== - -esbuild-freebsd-arm64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.50.tgz#cb115f4cdafe9cdbe58875ba482fccc54d32aa43" - integrity sha512-iKwUVMQztnPZe5pUYHdMkRc9aSpvoV1mkuHlCoPtxZA3V+Kg/ptpzkcSY+fKd0kuom+l6Rc93k0UPVkP7xoqrw== - -esbuild-linux-32@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.50.tgz#fe2b724994dcf1d4e48dc4832ff008ad7d00bcfd" - integrity sha512-sWUwvf3uz7dFOpLzYuih+WQ7dRycrBWHCdoXJ4I4XdMxEHCECd8b7a9N9u7FzT6XR2gHPk9EzvchQUtiEMRwqw== - -esbuild-linux-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.50.tgz#7851ab5151df9501a2187bd4909c594ad232b623" - integrity sha512-u0PQxPhaeI629t4Y3EEcQ0wmWG+tC/LpP2K7yDFvwuPq0jSQ8SIN+ARNYfRjGW15O2we3XJvklbGV0wRuUCPig== - -esbuild-linux-arm64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.50.tgz#76a76afef484a0512f1fbbcc762edd705dee8892" - integrity sha512-ZyfoNgsTftD7Rp5S7La5auomKdNeB3Ck+kSKXC4pp96VnHyYGjHHXWIlcbH8i+efRn9brszo1/Thl1qn8RqmhQ== - -esbuild-linux-arm@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.50.tgz#6d7a8c0712091b0c3a668dd5d8b5c924adbaeb12" - integrity sha512-VALZq13bhmFJYFE/mLEb+9A0w5vo8z+YDVOWeaf9vOTrSC31RohRIwtxXBnVJ7YKLYfEMzcgFYf+OFln3Y0cWg== - -esbuild-linux-mips64le@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.50.tgz#43426909c1884c5dc6b40765673a08a7ec1d2064" - integrity sha512-ygo31Vxn/WrmjKCHkBoutOlFG5yM9J2UhzHb0oWD9O61dGg+Hzjz9hjf5cmM7FBhAzdpOdEWHIrVOg2YAi6rTw== - -esbuild-linux-ppc64le@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.50.tgz#c754ea3da1dd180c6e9b6b508dc18ce983d92b11" - integrity sha512-xWCKU5UaiTUT6Wz/O7GKP9KWdfbsb7vhfgQzRfX4ahh5NZV4ozZ4+SdzYG8WxetsLy84UzLX3Pi++xpVn1OkFQ== - -esbuild-linux-riscv64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.50.tgz#f3b2dd3c4c2b91bf191d3b98a9819c8aa6f5ad7f" - integrity sha512-0+dsneSEihZTopoO9B6Z6K4j3uI7EdxBP7YSF5rTwUgCID+wHD3vM1gGT0m+pjCW+NOacU9kH/WE9N686FHAJg== - -esbuild-linux-s390x@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.50.tgz#3dfbc4578b2a81995caabb79df2b628ea86a5390" - integrity sha512-tVjqcu8o0P9H4StwbIhL1sQYm5mWATlodKB6dpEZFkcyTI8kfIGWiWcrGmkNGH2i1kBUOsdlBafPxR3nzp3TDA== - -esbuild-netbsd-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.50.tgz#17dbf51eaa48d983e794b588d195415410ef8c85" - integrity sha512-0R/glfqAQ2q6MHDf7YJw/TulibugjizBxyPvZIcorH0Mb7vSimdHy0XF5uCba5CKt+r4wjax1mvO9lZ4jiAhEg== - -esbuild-openbsd-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.50.tgz#cf6b1a50c8cf67b0725aaa4bce9773976168c50e" - integrity sha512-7PAtmrR5mDOFubXIkuxYQ4bdNS6XCK8AIIHUiZxq1kL8cFIH5731jPcXQ4JNy/wbj1C9sZ8rzD8BIM80Tqk29w== - -esbuild-sunos-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.50.tgz#f705ae0dd914c3b45dc43319c4f532216c3d841f" - integrity sha512-gBxNY/wyptvD7PkHIYcq7se6SQEXcSC8Y7mE0FJB+CGgssEWf6vBPfTTZ2b6BWKnmaP6P6qb7s/KRIV5T2PxsQ== - -esbuild-windows-32@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.50.tgz#6364905a99c1e6c1e2fe7bfccebd958131b1cd6c" - integrity sha512-MOOe6J9cqe/iW1qbIVYSAqzJFh0p2LBLhVUIWdMVnNUNjvg2/4QNX4oT4IzgDeldU+Bym9/Tn6+DxvUHJXL5Zw== - -esbuild-windows-64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.50.tgz#56603cb6367e30d14098deb77de6aa18d76dd89b" - integrity sha512-r/qE5Ex3w1jjGv/JlpPoWB365ldkppUlnizhMxJgojp907ZF1PgLTuW207kgzZcSCXyquL9qJkMsY+MRtaZ5yQ== - -esbuild-windows-arm64@0.14.50: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.50.tgz#e7ddde6a97194051a5a4ac05f4f5900e922a7ea5" - integrity sha512-EMS4lQnsIe12ZyAinOINx7eq2mjpDdhGZZWDwPZE/yUTN9cnc2Ze/xUTYIAyaJqrqQda3LnDpADKpvLvol6ENQ== - -esbuild@^0.14.47: - version "0.14.50" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.50.tgz#7a665392c8df94bf6e1ae1e999966a5ee62c6cbc" - integrity sha512-SbC3k35Ih2IC6trhbMYW7hYeGdjPKf9atTKwBUHqMCYFZZ9z8zhuvfnZihsnJypl74FjiAKjBRqFkBkAd0rS/w== +esbuild-android-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz#a521604d8c4c6befc7affedc897df8ccde189bea" + integrity sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w== + +esbuild-android-arm64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz#307b81f1088bf1e81dfe5f3d1d63a2d2a2e3e68e" + integrity sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ== + +esbuild-darwin-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz#270117b0c4ec6bcbc5cf3a297a7d11954f007e11" + integrity sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg== + +esbuild-darwin-arm64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz#97851eacd11dacb7719713602e3319e16202fc77" + integrity sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ== + +esbuild-freebsd-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz#1de15ffaf5ae916aa925800aa6d02579960dd8c4" + integrity sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ== + +esbuild-freebsd-arm64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz#0f160dbf5c9a31a1d8dd87acbbcb1a04b7031594" + integrity sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q== + +esbuild-linux-32@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz#422eb853370a5e40bdce8b39525380de11ccadec" + integrity sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg== + +esbuild-linux-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz#f89c468453bb3194b14f19dc32e0b99612e81d2b" + integrity sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ== + +esbuild-linux-arm64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz#68a79d6eb5e032efb9168a0f340ccfd33d6350a1" + integrity sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw== + +esbuild-linux-arm@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz#2b7c784d0b3339878013dfa82bf5eaf82c7ce7d3" + integrity sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ== + +esbuild-linux-mips64le@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz#bb8330a50b14aa84673816cb63cc6c8b9beb62cc" + integrity sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw== + +esbuild-linux-ppc64le@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz#52544e7fa992811eb996674090d0bc41f067a14b" + integrity sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw== + +esbuild-linux-riscv64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz#a43ae60697992b957e454cbb622f7ee5297e8159" + integrity sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g== + +esbuild-linux-s390x@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz#8c76a125dd10a84c166294d77416caaf5e1c7b64" + integrity sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ== + +esbuild-netbsd-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz#19b2e75449d7d9c32b5d8a222bac2f1e0c3b08fd" + integrity sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ== + +esbuild-openbsd-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz#1357b2bf72fd037d9150e751420a1fe4c8618ad7" + integrity sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ== + +esbuild-sunos-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz#87ab2c604592a9c3c763e72969da0d72bcde91d2" + integrity sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag== + +esbuild-windows-32@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz#c81e688c0457665a8d463a669e5bf60870323e99" + integrity sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA== + +esbuild-windows-64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz#2421d1ae34b0561a9d6767346b381961266c4eff" + integrity sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q== + +esbuild-windows-arm64@0.15.7: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz#7d5e9e060a7b454cb2f57f84a3f3c23c8f30b7d2" + integrity sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw== + +esbuild@^0.15.6: + version "0.15.7" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.7.tgz#8a1f1aff58671a3199dd24df95314122fc1ddee8" + integrity sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw== optionalDependencies: - esbuild-android-64 "0.14.50" - esbuild-android-arm64 "0.14.50" - esbuild-darwin-64 "0.14.50" - esbuild-darwin-arm64 "0.14.50" - esbuild-freebsd-64 "0.14.50" - esbuild-freebsd-arm64 "0.14.50" - esbuild-linux-32 "0.14.50" - esbuild-linux-64 "0.14.50" - esbuild-linux-arm "0.14.50" - esbuild-linux-arm64 "0.14.50" - esbuild-linux-mips64le "0.14.50" - esbuild-linux-ppc64le "0.14.50" - esbuild-linux-riscv64 "0.14.50" - esbuild-linux-s390x "0.14.50" - esbuild-netbsd-64 "0.14.50" - esbuild-openbsd-64 "0.14.50" - esbuild-sunos-64 "0.14.50" - esbuild-windows-32 "0.14.50" - esbuild-windows-64 "0.14.50" - esbuild-windows-arm64 "0.14.50" + "@esbuild/linux-loong64" "0.15.7" + esbuild-android-64 "0.15.7" + esbuild-android-arm64 "0.15.7" + esbuild-darwin-64 "0.15.7" + esbuild-darwin-arm64 "0.15.7" + esbuild-freebsd-64 "0.15.7" + esbuild-freebsd-arm64 "0.15.7" + esbuild-linux-32 "0.15.7" + esbuild-linux-64 "0.15.7" + esbuild-linux-arm "0.15.7" + esbuild-linux-arm64 "0.15.7" + esbuild-linux-mips64le "0.15.7" + esbuild-linux-ppc64le "0.15.7" + esbuild-linux-riscv64 "0.15.7" + esbuild-linux-s390x "0.15.7" + esbuild-netbsd-64 "0.15.7" + esbuild-openbsd-64 "0.15.7" + esbuild-sunos-64 "0.15.7" + esbuild-windows-32 "0.15.7" + esbuild-windows-64 "0.15.7" + esbuild-windows-arm64 "0.15.7" escalade@^3.1.1: version "3.1.1" @@ -15863,15 +15869,10 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter2@6.4.2: - version "6.4.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.2.tgz#f31f8b99d45245f0edbc5b00797830ff3b388970" - integrity sha512-r/Pwupa5RIzxIHbEKCkNXqpEQIIT4uQDxmP4G/Lug/NokVUWj0joz/WzWl3OxRpC5kDrH/WdiUJoR+IrwvXJEw== - -eventemitter2@^6.4.3: - version "6.4.4" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b" - integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw== +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== eventemitter3@^3.1.0: version "3.1.2" @@ -27358,10 +27359,10 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.11, postcss@^7.0.14, postcss@^7.0.1 picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.1.10, postcss@^8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== +postcss@^8.1.10, postcss@^8.4.16: + version "8.4.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== dependencies: nanoid "^3.3.4" picocolors "^1.0.0" @@ -29596,10 +29597,10 @@ rollup-plugin-typescript2@^0.29.0: resolve "1.17.0" tslib "2.0.1" -rollup@^2.38.5, rollup@^2.75.6: - version "2.77.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.0.tgz#749eaa5ac09b6baa52acc076bc46613eddfd53f4" - integrity sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g== +rollup@^2.38.5, rollup@~2.78.0: + version "2.78.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.1.tgz#52fe3934d9c83cb4f7c4cb5fb75d88591be8648f" + integrity sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg== optionalDependencies: fsevents "~2.3.2" @@ -33918,15 +33919,15 @@ vite-svg-loader@3.1.2: "@vue/compiler-sfc" "^3.2.20" svgo "^2.7.0" -vite@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.3.tgz#c7b2ed9505a36a04be1d5d23aea4ea6fc028043f" - integrity sha512-sDIpIcl3mv1NUaSzZwiXGEy1ZoWwwC2vkxUHY6yiDacR6zf//ZFuBJrozO62gedpE43pmxnLATNR5IYUdAEkMQ== +vite@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vite/-/vite-3.1.0.tgz#3138b279072941d57e76bcf7f66f272fc6a17fe2" + integrity sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g== dependencies: - esbuild "^0.14.47" - postcss "^8.4.14" + esbuild "^0.15.6" + postcss "^8.4.16" resolve "^1.22.1" - rollup "^2.75.6" + rollup "~2.78.0" optionalDependencies: fsevents "~2.3.2" From afb8e67a0b63b28161efb3ee4d5e736a7a4cb2ba Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Fri, 9 Sep 2022 13:21:43 -0400 Subject: [PATCH 30/49] fix: also set electron user agent through CDP. see #23597 (#23721) --- packages/server/lib/browsers/electron.ts | 5 + .../test/unit/browsers/electron_spec.js | 167 +++++++++++------- 2 files changed, 112 insertions(+), 60 deletions(-) diff --git a/packages/server/lib/browsers/electron.ts b/packages/server/lib/browsers/electron.ts index 421e58297d0b..639c608b0dbf 100644 --- a/packages/server/lib/browsers/electron.ts +++ b/packages/server/lib/browsers/electron.ts @@ -437,6 +437,11 @@ export = { // set both because why not webContents.userAgent = userAgent + // In addition to the session, also set the user-agent optimistically through CDP. @see https://github.com/cypress-io/cypress/issues/23597 + webContents.debugger.sendCommand('Network.setUserAgentOverride', { + userAgent, + }) + return webContents.session.setUserAgent(userAgent) }, diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index beab0a2cfe4f..c981ce4f43d7 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -49,6 +49,9 @@ describe('lib/browsers/electron', () => { webRequest: { onBeforeSendHeaders () {}, }, + setUserAgent: sinon.stub(), + getUserAgent: sinon.stub(), + clearCache: sinon.stub(), }, getOSProcessId: sinon.stub().returns(ELECTRON_PID), 'debugger': { @@ -473,104 +476,148 @@ describe('lib/browsers/electron', () => { }) }) }) + }) - describe('setUserAgent with experimentalModifyObstructiveThirdPartyCode', () => { - let userAgent + describe('setUserAgent with experimentalModifyObstructiveThirdPartyCode', () => { + let userAgent + let originalSendCommandSpy - beforeEach(function () { - userAgent = '' - electron._getUserAgent.callsFake(() => userAgent) - }) + beforeEach(function () { + userAgent = '' + this.win.webContents.session.getUserAgent.callsFake(() => userAgent) + // set a reference to the original sendCommand as it is decorated in electron.ts. This way, we can assert on the spy + originalSendCommandSpy = this.win.webContents.debugger.sendCommand + }) - describe('disabled', function () { - it('does not attempt to replace the user agent', function () { - this.options.experimentalModifyObstructiveThirdPartyCode = false + describe('disabled', function () { + it('does not attempt to replace the user agent', function () { + this.options.experimentalModifyObstructiveThirdPartyCode = false - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).not.to.be.called + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(this.win.webContents.session.setUserAgent).not.to.be.called + expect(originalSendCommandSpy).not.to.be.calledWith('Network.setUserAgentOverride', { + userAgent, }) }) }) + }) - describe('enabled and attempts to replace obstructive user agent string containing:', function () { - beforeEach(function () { - this.options.experimentalModifyObstructiveThirdPartyCode = true - }) + describe('enabled and attempts to replace obstructive user agent string containing:', function () { + beforeEach(function () { + this.options.experimentalModifyObstructiveThirdPartyCode = true + }) - it('does not attempt to replace the user agent if the user passes in an explicit user agent', function () { - userAgent = 'barbaz' - this.options.experimentalModifyObstructiveThirdPartyCode = false - this.options.userAgent = 'foobar' + it('does not attempt to replace the user agent if the user passes in an explicit user agent', function () { + userAgent = 'barbaz' + this.options.experimentalModifyObstructiveThirdPartyCode = false + this.options.userAgent = 'foobar' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.be.calledWith(this.win.webContents, 'foobar') - expect(electron._setUserAgent).not.to.be.calledWith(this.win.webContents, 'barbaz') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + expect(this.win.webContents.session.setUserAgent).to.be.calledWith('foobar') + expect(this.win.webContents.session.setUserAgent).not.to.be.calledWith('barbaz') + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: 'foobar', }) }) + }) - it('versioned cypress', function () { - userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/10.0.3 Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' + it('versioned cypress', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/10.0.3 Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('development cypress', function () { - userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0-development Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' + it('development cypress', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0-development Chrome/100.0.4896.75 Electron/18.0.4 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('older Windows user agent', function () { - userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36' + it('older Windows user agent', function () { + userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) electron/1.0.0 Chrome/53.0.2785.113 Electron/1.4.3 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('newer Windows user agent', function () { - userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Electron/10.4.7 Safari/537.36' + it('newer Windows user agent', function () { + userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Electron/10.4.7 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.4689 Chrome/85.0.4183.121 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('Linux user agent', function () { - userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Electron/9.0.5 Safari/E7FBAF' + it('Linux user agent', function () { + userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Electron/9.0.5 Safari/E7FBAF' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Safari/E7FBAF') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Typora/0.9.93 Chrome/83.0.4103.119 Safari/E7FBAF' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('older MacOS user agent', function () { - // this user agent containing Cypress was actually a common UA found on a website for Electron purposes... - userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/8.3.0 Chrome/91.0.4472.124 Electron/13.1.7 Safari/537.36' + it('older MacOS user agent', function () { + // this user agent containing Cypress was actually a common UA found on a website for Electron purposes... + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/8.3.0 Chrome/91.0.4472.124 Electron/13.1.7 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) + }) - it('newer MacOS user agent', function () { - userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' + it('newer MacOS user agent', function () { + userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' - return electron._launch(this.win, this.url, this.automation, this.options) - .then(() => { - expect(electron._setUserAgent).to.have.been.calledWith(this.win.webContents, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36') + return electron._launch(this.win, this.url, this.automation, this.options) + .then(() => { + const expectedUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36' + + expect(this.win.webContents.session.setUserAgent).to.have.been.calledWith(expectedUA) + expect(originalSendCommandSpy).to.be.calledWith('Network.setUserAgentOverride', { + userAgent: expectedUA, }) }) }) From 406eb069a6d98b66ed087c5456a70938153f265a Mon Sep 17 00:00:00 2001 From: Rachel Date: Fri, 9 Sep 2022 11:36:19 -0700 Subject: [PATCH 31/49] fix: issue-7715 (#23575) Co-authored-by: Chris Breiding --- .../cypress/e2e/runner/reporter.errors.cy.ts | 18 +- .../cypress/e2e/runner/reporter.hooks.cy.ts | 2 +- .../runner/retries.mochaEvents.snapshots.ts | 51 ----- .../runner/runner.mochaEvents.snapshots.ts | 20 -- .../e2e/runner/support/mochaEventsUtils.ts | 2 - .../e2e/runner/support/verify-failures.ts | 32 +-- .../cypress/e2e/cypress/error_utils.cy.ts | 24 +-- .../cypress/e2e/cypress/stack_utils.cy.js | 55 +++--- packages/driver/src/cypress/error_utils.ts | 5 +- packages/driver/src/cypress/stack_utils.ts | 12 +- packages/reporter/src/errors/err-model.ts | 3 - .../__snapshots__/retries.mochaEvents.cy.js | 51 ----- .../network_error_handling_spec.js | 2 - system-tests/__snapshots__/request_spec.ts.js | 1 - system-tests/__snapshots__/visit_spec.js | 186 ------------------ .../projects/e2e/cypress/support/util.js | 7 +- system-tests/test/cy_origin_error_spec.ts | 2 +- system-tests/test/stdout_spec.js | 6 + 18 files changed, 83 insertions(+), 396 deletions(-) diff --git a/packages/app/cypress/e2e/runner/reporter.errors.cy.ts b/packages/app/cypress/e2e/runner/reporter.errors.cy.ts index 0d5fd57d257f..becbf40bde67 100644 --- a/packages/app/cypress/e2e/runner/reporter.errors.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.errors.cy.ts @@ -34,27 +34,24 @@ describe('errors ui', { }) verify('with expect().', { + line: 3, column: 25, message: `expected 'actual' to equal 'expected'`, verifyOpenInIde: true, - ideLine: 3, - ideColumn: 25, }) verify('with assert()', { - column: '(5|12)', // (chrome|firefox) + line: 7, + column: [5, 12], // [chrome, firefox] message: `should be true`, verifyOpenInIde: true, - ideLine: 7, - ideColumn: 5, }) verify('with assert.()', { + line: 11, column: 12, message: `expected 'actual' to equal 'expected'`, verifyOpenInIde: true, - ideLine: 11, - ideColumn: 12, }) }) @@ -85,7 +82,8 @@ describe('errors ui', { verify('in file outside project', { message: 'An outside error', - regex: /\/throws\-error\.js:5:9/, + stackRegex: /\/throws\-error\.js:5:8/, + codeFrameRegex: /\/throws\-error\.js:5:9/, codeFrameText: `thrownewError('An outside error')`, }) }) @@ -100,7 +98,7 @@ describe('errors ui', { // https://github.com/cypress-io/cypress/issues/8288 // https://github.com/cypress-io/cypress/issues/8350 verify('test', { - column: '(7|18)', // (chrome|firefox) + column: [7, 18], // [chrome, firefox] codeFrameText: 'beforeEach(()=>', message: `Cypress detected you registered a(n) beforeEach hook while a test was running`, }) @@ -483,7 +481,7 @@ describe('errors ui', { }) verify('from chai expect', { - column: '(5|12)', // (chrome|firefox) + column: [5, 12], // [chrome, firefox] message: 'Invalid Chai property: nope', stack: ['proxyGetter', 'From Your Spec Code:'], }) diff --git a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts index 12f05fd603b2..8b15e7d3f49c 100644 --- a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts @@ -57,7 +57,7 @@ describe('hooks', { cy.withCtx((ctx, o) => { expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), o.ideLine, o.ideColumn) - }, { ideLine: 2, ideColumn: Cypress.browser.family === 'firefox' ? 6 : 3 }) + }, { ideLine: 2, ideColumn: Cypress.browser.family === 'firefox' ? 5 : 2 }) }) it('does not display commands from skipped tests', () => { diff --git a/packages/app/cypress/e2e/runner/retries.mochaEvents.snapshots.ts b/packages/app/cypress/e2e/runner/retries.mochaEvents.snapshots.ts index 9bfabdb791f2..60a7322e91cd 100644 --- a/packages/app/cypress/e2e/runner/retries.mochaEvents.snapshots.ts +++ b/packages/app/cypress/e2e/runner/retries.mochaEvents.snapshots.ts @@ -78,7 +78,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -104,7 +103,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -119,7 +117,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -448,7 +445,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -495,7 +491,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -545,7 +540,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1065,7 +1059,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1112,7 +1105,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -1162,7 +1154,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1757,7 +1748,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1810,7 +1800,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -1861,7 +1850,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -2586,7 +2574,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -2644,7 +2631,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -2660,7 +2646,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -2685,7 +2670,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -2876,7 +2860,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -3863,7 +3846,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -3902,7 +3884,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -3918,7 +3899,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -3979,7 +3959,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -4043,7 +4022,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -4067,7 +4045,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -4105,7 +4082,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -4121,7 +4097,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -4181,7 +4156,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -4244,7 +4218,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'body': '[body]', @@ -4740,7 +4713,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -4758,7 +4730,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -4823,7 +4794,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -4869,7 +4839,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -5244,7 +5213,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -5262,7 +5230,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -5278,7 +5245,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -5366,7 +5332,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -5878,7 +5843,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -5918,7 +5882,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -5969,7 +5932,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6068,7 +6030,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6107,7 +6068,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -6157,7 +6117,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6826,7 +6785,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6852,7 +6810,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -6867,7 +6824,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6916,7 +6872,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -6941,7 +6896,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -6955,7 +6909,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -7003,7 +6956,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -7027,7 +6979,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -7041,7 +6992,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -7073,7 +7023,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', diff --git a/packages/app/cypress/e2e/runner/runner.mochaEvents.snapshots.ts b/packages/app/cypress/e2e/runner/runner.mochaEvents.snapshots.ts index d3819b964d8c..36def78adbc2 100644 --- a/packages/app/cypress/e2e/runner/runner.mochaEvents.snapshots.ts +++ b/packages/app/cypress/e2e/runner/runner.mochaEvents.snapshots.ts @@ -80,7 +80,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -98,7 +97,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -128,7 +126,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -167,7 +164,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -314,7 +310,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -332,7 +327,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -348,7 +342,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -401,7 +394,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -548,7 +540,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -566,7 +557,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -582,7 +572,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -639,7 +628,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -905,7 +893,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -923,7 +910,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -939,7 +925,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -996,7 +981,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1235,7 +1219,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1288,7 +1271,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, ], @@ -1373,7 +1355,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', @@ -1449,7 +1430,6 @@ export const snapshots = { 'message': '[error message]', 'name': 'AssertionError', 'stack': 'match.string', - 'sourceMappedStack': 'match.string', 'parsedStack': 'match.array', }, 'state': 'failed', diff --git a/packages/app/cypress/e2e/runner/support/mochaEventsUtils.ts b/packages/app/cypress/e2e/runner/support/mochaEventsUtils.ts index d8cf18c49f15..a1fc586c302c 100644 --- a/packages/app/cypress/e2e/runner/support/mochaEventsUtils.ts +++ b/packages/app/cypress/e2e/runner/support/mochaEventsUtils.ts @@ -43,7 +43,6 @@ const eventCleanseMap = { stack: () => 'match.string', file: (arg: string) => arg ? 'relative/path/to/spec.js' : undefined, message: () => '[error message]', - sourceMappedStack: () => 'match.string', parsedStack: () => 'match.array', name: (n: string) => n === 'Error' ? 'AssertionError' : n, err: () => { @@ -51,7 +50,6 @@ const eventCleanseMap = { message: '[error message]', name: 'AssertionError', stack: 'match.string', - sourceMappedStack: 'match.string', parsedStack: 'match.array', } }, diff --git a/packages/app/cypress/e2e/runner/support/verify-failures.ts b/packages/app/cypress/e2e/runner/support/verify-failures.ts index c0f3676058b3..1b3e14d92c35 100644 --- a/packages/app/cypress/e2e/runner/support/verify-failures.ts +++ b/packages/app/cypress/e2e/runner/support/verify-failures.ts @@ -3,7 +3,7 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' // Assert that either the the dialog is presented or the mutation is emitted, depending on // whether the test has a preferred IDE defined. -const verifyIdeOpen = ({ fileName, action, hasPreferredIde, ideLine, ideColumn }) => { +const verifyIdeOpen = ({ fileName, action, hasPreferredIde, line, column }) => { if (hasPreferredIde) { cy.withCtx((ctx, o) => { // @ts-expect-error - check if we've stubbed it already, only need to stub it once @@ -15,8 +15,8 @@ const verifyIdeOpen = ({ fileName, action, hasPreferredIde, ideLine, ideColumn } action() cy.withCtx((ctx, o) => { - expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`${o.fileName}$`)), o.ideLine, o.ideColumn) - }, { fileName, ideLine, ideColumn }) + expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`${o.fileName}$`)), o.line, o.column) + }, { fileName, line, column }) } else { action() @@ -40,16 +40,22 @@ const verifyFailure = (options) => { fileName, uncaught = false, uncaughtMessage, - ideLine, - ideColumn, + line, + regex, } = options - let { regex, line, codeFrameText } = options + let { codeFrameText, stackRegex, codeFrameRegex } = options if (!codeFrameText) { codeFrameText = specTitle } - regex = regex || new RegExp(`${fileName}:${line || '\\d+'}:${column}`) + const codeFrameColumnArray = [].concat(column) + const codeFrameColumn = codeFrameColumnArray[0] + const stackColumnArray = codeFrameColumnArray.map((col) => col - 1) + const stackColumn = stackColumnArray[0] + + stackRegex = regex || stackRegex || new RegExp(`${fileName}:${line || '\\d+'}:(${stackColumnArray.join('|')})`) + codeFrameRegex = regex || codeFrameRegex || new RegExp(`${fileName}:${line || '\\d+'}:(${codeFrameColumnArray.join('|')})`) cy.contains('.runnable-title', specTitle).closest('.runnable').as('Root') @@ -89,7 +95,7 @@ const verifyFailure = (options) => { cy.log('stack trace matches the specified pattern') cy.get('.runnable-err-stack-trace') .invoke('text') - .should('match', regex) + .should('match', stackRegex) if (stack) { const stackLines = [].concat(stack) @@ -126,8 +132,8 @@ const verifyFailure = (options) => { cy.get('@Root').contains('.runnable-err-stack-trace .runnable-err-file-path a', fileName) .click('left') }, - ideLine, - ideColumn, + line, + column: stackColumn, }) } @@ -160,7 +166,7 @@ const verifyFailure = (options) => { cy .get('.test-err-code-frame .runnable-err-file-path') .invoke('text') - .should('match', regex) + .should('match', codeFrameRegex) cy.get('.test-err-code-frame pre span').should('include.text', codeFrameText) }) @@ -173,8 +179,8 @@ const verifyFailure = (options) => { cy.get('@Root').contains('.test-err-code-frame .runnable-err-file-path a', fileName) .click() }, - ideLine, - ideColumn, + line, + column: codeFrameColumn, }) } } diff --git a/packages/driver/cypress/e2e/cypress/error_utils.cy.ts b/packages/driver/cypress/e2e/cypress/error_utils.cy.ts index 6d8e8d0e75bb..f2cad64afd05 100644 --- a/packages/driver/cypress/e2e/cypress/error_utils.cy.ts +++ b/packages/driver/cypress/e2e/cypress/error_utils.cy.ts @@ -392,23 +392,16 @@ describe('driver/src/cypress/error_utils', () => { beforeEach(() => { $stackUtils.replacedStack = cy.stub().returns('replaced stack') - $stackUtils.stackWithUserInvocationStackSpliced = cy.stub().returns({ stack: 'spliced stack' }) $stackUtils.getSourceStack = cy.stub().returns(sourceStack) $stackUtils.getCodeFrame = cy.stub().returns(codeFrame) err = { stack: 'Error: original stack message\n at originalStack (foo.js:1:1)' } }) - it('replaces stack with user invocation stack', () => { + it('replaces stack with source map stack', () => { const result = $errUtils.enhanceStack({ err, userInvocationStack }) - expect(result.stack).to.equal('replaced stack') - }) - - it('attaches source mapped stack', () => { - const result = $errUtils.enhanceStack({ err, userInvocationStack }) - - expect(result.sourceMappedStack).to.equal(sourceStack.sourceMapped) + expect(result.stack).to.equal(sourceStack.sourceMapped) }) it('attaches parsed stack', () => { @@ -426,17 +419,16 @@ describe('driver/src/cypress/error_utils', () => { it('appends user invocation stack when it is a cypress error', () => { err.name = 'CypressError' - const result = $errUtils.enhanceStack({ err, userInvocationStack }) - - expect(result.stack).to.equal('spliced stack') + cy.spy($stackUtils, 'stackWithUserInvocationStackSpliced') + $errUtils.enhanceStack({ err, userInvocationStack }) + expect($stackUtils.stackWithUserInvocationStackSpliced).to.be.called }) it('appends user invocation stack when it is a chai validation error', () => { err.message = 'Invalid Chai property' - - const result = $errUtils.enhanceStack({ err, userInvocationStack }) - - expect(result.stack).to.equal('spliced stack') + cy.spy($stackUtils, 'stackWithUserInvocationStackSpliced') + $errUtils.enhanceStack({ err, userInvocationStack }) + expect($stackUtils.stackWithUserInvocationStackSpliced).to.be.called }) it('does not replaced or append stack when there is no invocation stack', () => { diff --git a/packages/driver/cypress/e2e/cypress/stack_utils.cy.js b/packages/driver/cypress/e2e/cypress/stack_utils.cy.js index e5416a5ca742..4b6e069c53bd 100644 --- a/packages/driver/cypress/e2e/cypress/stack_utils.cy.js +++ b/packages/driver/cypress/e2e/cypress/stack_utils.cy.js @@ -1,6 +1,5 @@ const $stackUtils = require('@packages/driver/src/cypress/stack_utils').default const $sourceMapUtils = require('@packages/driver/src/cypress/source_map_utils').default -const { stripIndent } = require('common-tags') describe('driver/src/cypress/stack_utils', () => { context('.replacedStack', () => { @@ -80,13 +79,13 @@ describe('driver/src/cypress/stack_utils', () => { expect(codeFrame).to.be.an('object') expect(codeFrame.frame).to.contain(` 1 | it('is a failing test', () => {`) expect(codeFrame.frame).to.contain(`> 2 | cy.get('.not-there'`) - expect(codeFrame.frame).to.contain(` | ^`) + expect(codeFrame.frame).to.contain(` | ^`) expect(codeFrame.frame).to.contain(` 3 | }`) expect(codeFrame.absoluteFile).to.equal('/dev/app/cypress/integration/features/source_map_spec.js') expect(codeFrame.relativeFile).to.equal('cypress/integration/features/source_map_spec.js') expect(codeFrame.language).to.equal('js') expect(codeFrame.line).to.equal(2) - expect(codeFrame.column).to.eq(5) + expect(codeFrame.column).to.eq(6) }) it('does not add code frame if stack does not yield one', () => { @@ -111,7 +110,7 @@ describe('driver/src/cypress/stack_utils', () => { .then((errorLocation) => { expect(errorLocation, 'does not have disk information').to.deep.equal({ absoluteFile: undefined, - column: 4, + column: 3, fileUrl: 'http://localhost:8888/js/utils.js', function: '', line: 9, @@ -150,8 +149,8 @@ describe('driver/src/cypress/stack_utils', () => { const sourceStack = $stackUtils.getSourceStack(generatedStack, projectRoot) expect(sourceStack.sourceMapped).to.equal(`Error: spec iframe stack - at foo.bar (some_other_file.ts:2:2) - at Context. (cypress/integration/features/source_map_spec.coffee:4:4)\ + at foo.bar (some_other_file.ts:2:1) + at Context. (cypress/integration/features/source_map_spec.coffee:4:3)\ `) expect(sourceStack.parsed).to.eql([ @@ -166,7 +165,7 @@ describe('driver/src/cypress/stack_utils', () => { relativeFile: 'some_other_file.ts', absoluteFile: '/dev/app/some_other_file.ts', line: 2, - column: 2, + column: 1, whitespace: ' ', }, { @@ -176,7 +175,7 @@ describe('driver/src/cypress/stack_utils', () => { relativeFile: 'cypress/integration/features/source_map_spec.coffee', absoluteFile: '/dev/app/cypress/integration/features/source_map_spec.coffee', line: 4, - column: 4, + column: 3, whitespace: ' ', }, ]) @@ -186,8 +185,8 @@ describe('driver/src/cypress/stack_utils', () => { const sourceStack = $stackUtils.getSourceStack(generatedStack, projectRoot) expect(sourceStack.sourceMapped).to.equal(`Error: spec iframe stack - at foo.bar (some_other_file.ts:2:2) - at Context. (cypress/integration/features/source_map_spec.coffee:4:4)\ + at foo.bar (some_other_file.ts:2:1) + at Context. (cypress/integration/features/source_map_spec.coffee:4:3)\ `) }) @@ -195,8 +194,8 @@ describe('driver/src/cypress/stack_utils', () => { generatedStack = generatedStack.split('\n').slice(1).join('\n') const sourceStack = $stackUtils.getSourceStack(generatedStack, projectRoot) - expect(sourceStack.sourceMapped).to.equal(` at foo.bar (some_other_file.ts:2:2) - at Context. (cypress/integration/features/source_map_spec.coffee:4:4)\ + expect(sourceStack.sourceMapped).to.equal(` at foo.bar (some_other_file.ts:2:1) + at Context. (cypress/integration/features/source_map_spec.coffee:4:3)\ `) }) @@ -209,8 +208,8 @@ more lines Error: spec iframe stack - at foo.bar (some_other_file.ts:2:2) - at Context. (cypress/integration/features/source_map_spec.coffee:4:4)\ + at foo.bar (some_other_file.ts:2:1) + at Context. (cypress/integration/features/source_map_spec.coffee:4:3)\ `) }) @@ -230,8 +229,8 @@ Error: spec iframe stack const sourceStack = $stackUtils.getSourceStack(generatedStack, projectRoot) expect(sourceStack.sourceMapped).to.equal(`Error: spec iframe stack - at foo.bar (cypress:///some_other_file.ts:2:2) - at Context. (webpack:///cypress/integration/features/source_map_spec.coffee:4:4)\ + at foo.bar (cypress:///some_other_file.ts:2:1) + at Context. (webpack:///cypress/integration/features/source_map_spec.coffee:4:3)\ `) expect(sourceStack.parsed).to.eql([ @@ -246,7 +245,7 @@ Error: spec iframe stack relativeFile: 'some_other_file.ts', absoluteFile: '/dev/app/some_other_file.ts', line: 2, - column: 2, + column: 1, whitespace: ' ', }, { @@ -256,7 +255,7 @@ Error: spec iframe stack relativeFile: 'cypress/integration/features/source_map_spec.coffee', absoluteFile: '/dev/app/cypress/integration/features/source_map_spec.coffee', line: 4, - column: 4, + column: 3, whitespace: ' ', }, ]) @@ -278,8 +277,8 @@ Error: spec iframe stack const sourceStack = $stackUtils.getSourceStack(generatedStack, projectRoot) expect(sourceStack.sourceMapped).to.equal(`Error: spec iframe stack - at foo.bar (cypress:////root/absolute/path/some_other_file.ts:2:2) - at Context. (webpack:////root/absolute/path/cypress/integration/features/source_map_spec.coffee:4:4)\ + at foo.bar (cypress:////root/absolute/path/some_other_file.ts:2:1) + at Context. (webpack:////root/absolute/path/cypress/integration/features/source_map_spec.coffee:4:3)\ `) expect(sourceStack.parsed).to.eql([ @@ -294,7 +293,7 @@ Error: spec iframe stack relativeFile: '/root/absolute/path/some_other_file.ts', absoluteFile: '/root/absolute/path/some_other_file.ts', line: 2, - column: 2, + column: 1, whitespace: ' ', }, { @@ -304,7 +303,7 @@ Error: spec iframe stack relativeFile: '/root/absolute/path/cypress/integration/features/source_map_spec.coffee', absoluteFile: '/root/absolute/path/cypress/integration/features/source_map_spec.coffee', line: 4, - column: 4, + column: 3, whitespace: ' ', }, ]) @@ -317,7 +316,7 @@ Error: spec iframe stack context('.getSourceDetailsForFirstLine', () => { it('parses good stack trace', () => { - const stack = stripIndent` + const stack = ` Error at Suite.eval (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:101:3) at Object../cypress/integration/spec.js (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:100:1) @@ -336,7 +335,7 @@ Error: spec iframe stack }) it('parses anonymous eval line', () => { - const stack = stripIndent` + const stack = ` SyntaxError: The following error originated from your application code, not from Cypress. > Identifier 'app' has already been declared @@ -381,13 +380,13 @@ Error: spec iframe stack expect(details, 'minimal details').to.deep.equal({ absoluteFile: undefined, - column: 2, + column: 1, fileUrl: undefined, function: '', line: 1, originalFile: undefined, relativeFile: undefined, - whitespace: ' ', + whitespace: ' ', }) }) @@ -400,7 +399,7 @@ Error: spec iframe stack }) // stack is fairly irrelevant in this test - testing transforming getSourcePosition response - const stack = stripIndent` + const stack = ` Error at Object../cypress/integration/spec%with space &^$ emoji👍_你好.js (http://localhost:50129/__cypress/tests?p=cypress/integration/spec%25with%20space%20%26^$%20emoji👍_你好.js:99:1) ` @@ -421,7 +420,7 @@ Error: spec iframe stack }) // stack is fairly irrelevant in this test - testing transforming getSourcePosition response - const stack = stripIndent` + const stack = ` Error at Object../cypress/integration/spec.js (http://localhost:50129/__cypress/tests?p=/root/path/cypress/integration/spec.js:99:1) ` diff --git a/packages/driver/src/cypress/error_utils.ts b/packages/driver/src/cypress/error_utils.ts index a4905c4eefa7..30590923817f 100644 --- a/packages/driver/src/cypress/error_utils.ts +++ b/packages/driver/src/cypress/error_utils.ts @@ -8,7 +8,7 @@ import $errorMessages from './error_messages' import $stackUtils, { StackAndCodeFrameIndex } from './stack_utils' import $utils from './utils' -const ERROR_PROPS = 'message type name stack sourceMappedStack parsedStack fileName lineNumber columnNumber host uncaught actual expected showDiff isPending docsUrl codeFrame'.split(' ') +const ERROR_PROPS = 'message type name stack parsedStack fileName lineNumber columnNumber host uncaught actual expected showDiff isPending docsUrl codeFrame'.split(' ') const ERR_PREPARED_FOR_SERIALIZATION = Symbol('ERR_PREPARED_FOR_SERIALIZATION') const crossOriginScriptRe = /^script error/i @@ -434,8 +434,7 @@ const enhanceStack = ({ err, userInvocationStack, projectRoot }: { const { stack, index } = preferredStackAndCodeFrameIndex(err, userInvocationStack) const { sourceMapped, parsed } = $stackUtils.getSourceStack(stack, projectRoot) - err.stack = stack - err.sourceMappedStack = sourceMapped + err.stack = sourceMapped err.parsedStack = parsed err.codeFrame = $stackUtils.getCodeFrame(err, index) diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index ac10249a644b..37c386a91b17 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -127,9 +127,12 @@ const getLanguageFromExtension = (filePath) => { return (path.extname(filePath) || '').toLowerCase().replace('.', '') || null } -const getCodeFrameFromSource = (sourceCode, { line, column, relativeFile, absoluteFile }) => { +const getCodeFrameFromSource = (sourceCode, { line, column: originalColumn, relativeFile, absoluteFile }) => { if (!sourceCode) return + // stack columns are 0-based but code frames and IDEs start columns at 1. + // add 1 so the code frame and "open in IDE" point to the right line + const column = originalColumn + 1 const frame = codeFrameColumns(sourceCode, { start: { line, column } }) if (!frame) return @@ -298,7 +301,7 @@ const getSourceDetailsForLine = (projectRoot, line): LineDetail => { // if it couldn't be parsed, it's a message line if (!generatedDetails) { return { - message: line, + message: line.replace(whitespace, ''), // strip leading whitespace whitespace, } } @@ -332,8 +335,7 @@ const getSourceDetailsForLine = (projectRoot, line): LineDetail => { relativeFile, absoluteFile, line: sourceDetails.line, - // adding 1 to column makes more sense for code frame and opening in editor - column: sourceDetails.column + 1, + column: sourceDetails.column, whitespace, } } @@ -355,7 +357,7 @@ const reconstructStack = (parsedStack) => { const { whitespace, originalFile, function: fn, line, column } = parsedLine return `${whitespace}at ${fn} (${originalFile || ''}:${line}:${column})` - }).join('\n') + }).join('\n').trimEnd() } const getSourceStack = (stack, projectRoot?) => { diff --git a/packages/reporter/src/errors/err-model.ts b/packages/reporter/src/errors/err-model.ts index 41023b2896b8..29aff35b2948 100644 --- a/packages/reporter/src/errors/err-model.ts +++ b/packages/reporter/src/errors/err-model.ts @@ -26,7 +26,6 @@ export interface ErrProps { name: string message: string stack: string - sourceMappedStack: string parsedStack: ParsedStackLine[] docsUrl: string | string[] templateType: string @@ -37,7 +36,6 @@ export default class Err { @observable name = '' @observable message = '' @observable stack = '' - @observable sourceMappedStack = '' @observable.ref parsedStack: ParsedStackLine[] | null = null @observable docsUrl = '' as string | string[] @observable templateType = '' @@ -63,7 +61,6 @@ export default class Err { if (props.message) this.message = props.message if (props.stack) this.stack = props.stack if (props.docsUrl) this.docsUrl = props.docsUrl - if (props.sourceMappedStack) this.sourceMappedStack = props.sourceMappedStack if (props.parsedStack) this.parsedStack = props.parsedStack if (props.templateType) this.templateType = props.templateType if (props.codeFrame) this.codeFrame = props.codeFrame diff --git a/packages/runner/__snapshots__/retries.mochaEvents.cy.js b/packages/runner/__snapshots__/retries.mochaEvents.cy.js index 084f24a503bc..c58148c462ab 100644 --- a/packages/runner/__snapshots__/retries.mochaEvents.cy.js +++ b/packages/runner/__snapshots__/retries.mochaEvents.cy.js @@ -89,7 +89,6 @@ exports['src/cypress/runner retries mochaEvents simple retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -115,7 +114,6 @@ exports['src/cypress/runner retries mochaEvents simple retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -134,7 +132,6 @@ exports['src/cypress/runner retries mochaEvents simple retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -491,7 +488,6 @@ exports['src/cypress/runner retries mochaEvents test retry with hooks #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -538,7 +534,6 @@ exports['src/cypress/runner retries mochaEvents test retry with hooks #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -592,7 +587,6 @@ exports['src/cypress/runner retries mochaEvents test retry with hooks #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -1140,7 +1134,6 @@ exports['src/cypress/runner retries mochaEvents test retry with [only] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -1187,7 +1180,6 @@ exports['src/cypress/runner retries mochaEvents test retry with [only] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -1241,7 +1233,6 @@ exports['src/cypress/runner retries mochaEvents test retry with [only] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -1864,7 +1855,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [beforeEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -1917,7 +1907,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [beforeEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -1972,7 +1961,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [beforeEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -2725,7 +2713,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -2783,7 +2770,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -2799,7 +2785,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -2828,7 +2813,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -3023,7 +3007,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -4074,7 +4057,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -4113,7 +4095,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -4129,7 +4110,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -4194,7 +4174,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -4262,7 +4241,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -4290,7 +4268,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -4328,7 +4305,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -4344,7 +4320,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -4408,7 +4383,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -4475,7 +4449,6 @@ exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "body": "[body]", @@ -5007,7 +4980,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [before] #1'] = "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -5025,7 +4997,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [before] #1'] = "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -5094,7 +5065,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [before] #1'] = "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -5144,7 +5114,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [before] #1'] = "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -5527,7 +5496,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [after] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -5545,7 +5513,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [after] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -5565,7 +5532,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [after] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -5657,7 +5623,6 @@ exports['src/cypress/runner retries mochaEvents cant retry from [after] #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -6201,7 +6166,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -6241,7 +6205,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -6296,7 +6259,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -6403,7 +6365,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -6442,7 +6403,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -6496,7 +6456,6 @@ exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7213,7 +7172,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7239,7 +7197,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -7258,7 +7215,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7315,7 +7271,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7340,7 +7295,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -7358,7 +7312,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7414,7 +7367,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7438,7 +7390,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" } ], @@ -7456,7 +7407,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", @@ -7492,7 +7442,6 @@ exports['src/cypress/runner retries mochaEvents cleanses errors before emitting "message": "[error message]", "name": "AssertionError", "stack": "match.string", - "sourceMappedStack": "match.string", "parsedStack": "match.array" }, "state": "failed", diff --git a/system-tests/__snapshots__/network_error_handling_spec.js b/system-tests/__snapshots__/network_error_handling_spec.js index 1db110efbef8..66b93227358e 100644 --- a/system-tests/__snapshots__/network_error_handling_spec.js +++ b/system-tests/__snapshots__/network_error_handling_spec.js @@ -325,7 +325,6 @@ Common situations why this would fail: From Node.js Internals: Error: socket hang up [stack trace lines] - 2) network error handling cy.request() retries @@ -361,7 +360,6 @@ https://on.cypress.io/request From Node.js Internals: RequestError: Error: socket hang up [stack trace lines] - diff --git a/system-tests/__snapshots__/request_spec.ts.js b/system-tests/__snapshots__/request_spec.ts.js index ab5bd423bad0..56b77276ec92 100644 --- a/system-tests/__snapshots__/request_spec.ts.js +++ b/system-tests/__snapshots__/request_spec.ts.js @@ -131,7 +131,6 @@ https://on.cypress.io/request From Node.js Internals: RequestError: Error: connect ECONNREFUSED 127.0.0.1:16795 [stack trace lines] - diff --git a/system-tests/__snapshots__/visit_spec.js b/system-tests/__snapshots__/visit_spec.js index 984e75b10014..ea91c03b736c 100644 --- a/system-tests/__snapshots__/visit_spec.js +++ b/system-tests/__snapshots__/visit_spec.js @@ -79,102 +79,6 @@ exports['e2e visit / low response timeout / passes'] = ` ✔ All specs passed! XX:XX 18 18 - - - -` - -exports['e2e visit / low response timeout / fails when network connection immediately fails'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (visit_http_network_error_failing.cy.js) │ - │ Searched: cypress/e2e/visit_http_network_error_failing.cy.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: visit_http_network_error_failing.cy.js (1 of 1) - - - when network connection cannot be established - 1) fails - - - 0 passing - 1 failing - - 1) when network connection cannot be established - fails: - CypressError: \`cy.visit()\` failed trying to load: - -http://localhost:16795/ - -We attempted to make an http request to this URL but the request failed without a response. - -We received this error at the network level: - - > Error: connect ECONNREFUSED 127.0.0.1:16795 - -Common situations why this would fail: - - you don't have internet access - - you forgot to run / boot your web server - - your web server isn't accessible - - you have weird network configuration settings on your computer - [stack trace lines] - - From Node.js Internals: - Error: connect ECONNREFUSED 127.0.0.1:16795 - [stack trace lines] - - - - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 1 │ - │ Passing: 0 │ - │ Failing: 1 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 1 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: visit_http_network_error_failing.cy.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Screenshots) - - - /XXX/XXX/XXX/cypress/screenshots/visit_http_network_error_failing.cy.js/when net (1280x720) - work connection cannot be established -- fails (failed).png - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/visit_http_network_error_failin (X second) - g.cy.js.mp4 - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ visit_http_network_error_failing.cy XX:XX 1 - 1 - - │ - │ .js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 1 of 1 failed (100%) XX:XX 1 - 1 - - - - ` exports['e2e visit / low response timeout / fails when server responds with 500'] = ` @@ -262,93 +166,6 @@ If you do not want status codes to cause failures pass the option: \`failOnStatu ✖ 1 of 1 failed (100%) XX:XX 1 - 1 - - -` - -exports['e2e visit / low response timeout / fails when file server responds with 404'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (visit_file_404_response_failing.cy.js) │ - │ Searched: cypress/e2e/visit_file_404_response_failing.cy.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: visit_file_404_response_failing.cy.js (1 of 1) - - - when file server response is 404 - 1) fails - - - 0 passing - 1 failing - - 1) when file server response is 404 - fails: - CypressError: \`cy.visit()\` failed trying to load: - -/static/does-not-exist.html - -We failed looking for this file at the path: - -/foo/bar/.projects/e2e/static/does-not-exist.html - -The internal Cypress web server responded with: - - > 404: Not Found - [stack trace lines] - - - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 1 │ - │ Passing: 0 │ - │ Failing: 1 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 1 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: visit_file_404_response_failing.cy.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Screenshots) - - - /XXX/XXX/XXX/cypress/screenshots/visit_file_404_response_failing.cy.js/when file (1280x720) - server response is 404 -- fails (failed).png - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/visit_file_404_response_failing (X second) - .cy.js.mp4 - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ visit_file_404_response_failing.cy. XX:XX 1 - 1 - - │ - │ js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 1 of 1 failed (100%) XX:XX 1 - 1 - - - - ` exports['e2e visit / low response timeout / fails when content type isnt html'] = ` @@ -590,7 +407,6 @@ Common situations why this would fail: From Node.js Internals: Error: ESOCKETTIMEDOUT [stack trace lines] - 2) response timeouts result in an error handles no response errors when not initially visiting: @@ -614,7 +430,6 @@ Common situations why this would fail: From Node.js Internals: Error: ESOCKETTIMEDOUT [stack trace lines] - 3) response timeouts result in an error fails after reducing the responseTimeout option: @@ -638,7 +453,6 @@ Common situations why this would fail: From Node.js Internals: Error: ESOCKETTIMEDOUT [stack trace lines] - diff --git a/system-tests/projects/e2e/cypress/support/util.js b/system-tests/projects/e2e/cypress/support/util.js index eb1e6a0d38cd..cbff55bc2b68 100644 --- a/system-tests/projects/e2e/cypress/support/util.js +++ b/system-tests/projects/e2e/cypress/support/util.js @@ -25,7 +25,8 @@ export const verify = (ctx, options) => { stack, } = options - const fileRegex = new RegExp(`${Cypress.spec.relative}:${line}:${column}`) + const codeFrameFileRegex = new RegExp(`${Cypress.spec.relative}:${line}:${column}`) + const stackFileRegex = new RegExp(`${Cypress.spec.relative}:${line}:${column - 1}`) it(`✓ VERIFY`, function () { cy.wrap(Cypress.$(window.top.document.body)) @@ -45,7 +46,7 @@ export const verify = (ctx, options) => { cy.get('.runnable-err-stack-trace') .invoke('text') - .should('match', fileRegex) + .should('match', stackFileRegex) _.each([].concat(stack), (stackLine) => { cy.get('.runnable-err-stack-trace') @@ -60,7 +61,7 @@ export const verify = (ctx, options) => { cy .get('.test-err-code-frame .runnable-err-file-path') .invoke('text') - .should('match', fileRegex) + .should('match', codeFrameFileRegex) // code frames will show `fail(this,()=>` as the 1st line cy.get('.test-err-code-frame pre span').should('include.text', 'fail(this,()=>') diff --git a/system-tests/test/cy_origin_error_spec.ts b/system-tests/test/cy_origin_error_spec.ts index a465a1be6ce9..1f980ed869ad 100644 --- a/system-tests/test/cy_origin_error_spec.ts +++ b/system-tests/test/cy_origin_error_spec.ts @@ -44,7 +44,7 @@ describe('e2e cy.origin errors', () => { expect(res.stdout).to.contain('Timed out retrying after 1000ms: Expected to find element: `#doesnotexist`, but never found it.') // check to make sure the snapshot contains the 'cy.origin' sourcemap - expect(res.stdout).to.contain('http://localhost:3500/__cypress/tests?p=cypress/e2e/cy_origin_error.cy.ts:102:12') + expect(res.stdout).to.contain('webpack:///./cypress/e2e/cy_origin_error.cy.ts:8:7') }, }) }) diff --git a/system-tests/test/stdout_spec.js b/system-tests/test/stdout_spec.js index d45474d43c8a..ce0ca859ac41 100644 --- a/system-tests/test/stdout_spec.js +++ b/system-tests/test/stdout_spec.js @@ -27,6 +27,12 @@ describe('e2e stdout', () => { snapshot: true, spec: 'stdout_failing.cy.js', expectedExitCode: 3, + onStdout: (stdout) => { + // assert stack trace line numbers/columns mirror source map + expect(stdout).to.include('Context.eval (webpack:///./cypress/e2e/stdout_failing.cy.js:7:12)') + expect(stdout).to.include('Context.eval (webpack:///./cypress/e2e/stdout_failing.cy.js:15:9)') + expect(stdout).to.include('Context.eval (webpack:///./cypress/e2e/stdout_failing.cy.js:27:9)') + }, }) }) From b28bbcf56f43e147097d58c10ad2270a15f26178 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Fri, 9 Sep 2022 16:38:02 -0400 Subject: [PATCH 32/49] feat: add MaybeSimulateSecHeaders code to prevent 403 issues with google (#23720) --- packages/proxy/lib/http/request-middleware.ts | 16 ++++ .../test/unit/http/request-middleware.spec.ts | 76 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index c5f40e1b4fc0..0ad90c3144ea 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -31,6 +31,21 @@ const ExtractIsAUTFrameHeader: RequestMiddleware = function () { this.next() } +const MaybeSimulateSecHeaders: RequestMiddleware = function () { + if (!this.config.experimentalModifyObstructiveThirdPartyCode) { + this.next() + + return + } + + // Do NOT disclose destination to an iframe and simulate if iframe was top + if (this.req.isAUTFrame && this.req.headers['sec-fetch-dest'] === 'iframe') { + this.req.headers['sec-fetch-dest'] = 'document' + } + + this.next() +} + const MaybeAttachCrossOriginCookies: RequestMiddleware = function () { const currentAUTUrl = this.getAUTUrl() @@ -233,6 +248,7 @@ const SendRequestOutgoing: RequestMiddleware = function () { export default { LogRequest, ExtractIsAUTFrameHeader, + MaybeSimulateSecHeaders, MaybeAttachCrossOriginCookies, MaybeEndRequestWithBufferedResponse, CorrelateBrowserPreRequest, diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index 10f45d05141d..bf14243b7542 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -12,6 +12,7 @@ describe('http/request-middleware', () => { expect(_.keys(RequestMiddleware)).to.have.ordered.members([ 'LogRequest', 'ExtractIsAUTFrameHeader', + 'MaybeSimulateSecHeaders', 'MaybeAttachCrossOriginCookies', 'MaybeEndRequestWithBufferedResponse', 'CorrelateBrowserPreRequest', @@ -59,6 +60,81 @@ describe('http/request-middleware', () => { }) }) + describe('MaybeSimulateSecHeaders', () => { + const { MaybeSimulateSecHeaders } = RequestMiddleware + + it('is a noop if experimental modify third party code is off', async () => { + const ctx = { + config: { + experimentalModifyObstructiveThirdPartyCode: false, + }, + req: { + headers: { + 'sec-fetch-dest': 'iframe', + }, + }, + } + + await testMiddleware([MaybeSimulateSecHeaders], ctx) + + expect(ctx.req.headers['sec-fetch-dest']).to.equal('iframe') + }) + + it('is a noop if the request is not the AUT Frame', async () => { + const ctx = { + config: { + experimentalModifyObstructiveThirdPartyCode: true, + }, + req: { + isAUTFrame: false, + headers: { + 'sec-fetch-dest': 'iframe', + }, + }, + } + + await testMiddleware([MaybeSimulateSecHeaders], ctx) + + expect(ctx.req.headers['sec-fetch-dest']).to.equal('iframe') + }) + + it('is a noop if the request is the AUT Frame, but the sec-fetch-dest isn\t an iframe', async () => { + const ctx = { + config: { + experimentalModifyObstructiveThirdPartyCode: true, + }, + req: { + isAUTFrame: true, + headers: { + 'sec-fetch-dest': 'video', + }, + }, + } + + await testMiddleware([MaybeSimulateSecHeaders], ctx) + + expect(ctx.req.headers['sec-fetch-dest']).to.equal('video') + }) + + it('rewrites the sec-fetch-dest header if the experimental modify third party code is enabled, the request came from the AUT frame, and is an iframe', async () => { + const ctx = { + config: { + experimentalModifyObstructiveThirdPartyCode: true, + }, + req: { + isAUTFrame: true, + headers: { + 'sec-fetch-dest': 'iframe', + }, + }, + } + + await testMiddleware([MaybeSimulateSecHeaders], ctx) + + expect(ctx.req.headers['sec-fetch-dest']).to.equal('document') + }) + }) + describe('MaybeAttachCrossOriginCookies', () => { const { MaybeAttachCrossOriginCookies } = RequestMiddleware From 499c0d1566903dfde73516c2823c6e09f49bc30d Mon Sep 17 00:00:00 2001 From: Emily Rohrbough Date: Fri, 9 Sep 2022 16:38:13 -0500 Subject: [PATCH 33/49] perf: pull log attributes from test instead of storing log attrs multiple times (#23738) --- packages/app/src/runner/event-manager.ts | 28 +++--- packages/driver/src/cypress/runner.ts | 85 ++++++++----------- packages/reporter/cypress/e2e/commands.cy.ts | 16 ++-- .../reporter/cypress/e2e/unit/events.cy.ts | 11 ++- packages/reporter/src/commands/command.tsx | 12 +-- .../src/instruments/instrument-model.ts | 2 + packages/reporter/src/lib/events.ts | 25 +++--- packages/reporter/src/sessions/sessions.tsx | 4 +- 8 files changed, 89 insertions(+), 94 deletions(-) diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index e835d3382602..b072b1991c66 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -181,27 +181,27 @@ export class EventManager { }) }) - const logCommand = (logId) => { - const consoleProps = Cypress.runner.getConsolePropsForLogById(logId) + const logCommand = (testId, logId) => { + const consoleProps = Cypress.runner.getConsolePropsForLog(testId, logId) logger.logFormatted(consoleProps) } - this.reporterBus.on('runner:console:error', ({ err, commandId }) => { + this.reporterBus.on('runner:console:error', ({ err, testId, logId }) => { if (!Cypress) return - if (commandId || err) logger.clearLog() + if (logId || err) logger.clearLog() - if (commandId) logCommand(commandId) + if (logId) logCommand(testId, logId) if (err) logger.logError(err.stack) }) - this.reporterBus.on('runner:console:log', (logId) => { + this.reporterBus.on('runner:console:log', (testId, logId) => { if (!Cypress) return logger.clearLog() - logCommand(logId) + logCommand(testId, logId) }) this.reporterBus.on('set:user:editor', (editor) => { @@ -210,24 +210,24 @@ export class EventManager { this.reporterBus.on('runner:restart', rerun) - const sendEventIfSnapshotProps = (logId, event) => { + const sendEventIfSnapshotProps = (testId, logId, event) => { if (!Cypress) return - const snapshotProps = Cypress.runner.getSnapshotPropsForLogById(logId) + const snapshotProps = Cypress.runner.getSnapshotPropsForLog(testId, logId) if (snapshotProps) { this.localBus.emit(event, snapshotProps) } } - this.reporterBus.on('runner:show:snapshot', (logId) => { - sendEventIfSnapshotProps(logId, 'show:snapshot') + this.reporterBus.on('runner:show:snapshot', (testId, logId) => { + sendEventIfSnapshotProps(testId, logId, 'show:snapshot') }) this.reporterBus.on('runner:hide:snapshot', this._hideSnapshot.bind(this)) - this.reporterBus.on('runner:pin:snapshot', (logId) => { - sendEventIfSnapshotProps(logId, 'pin:snapshot') + this.reporterBus.on('runner:pin:snapshot', (testId, logId) => { + sendEventIfSnapshotProps(testId, logId, 'pin:snapshot') }) this.reporterBus.on('runner:unpin:snapshot', this._unpinSnapshot.bind(this)) @@ -884,7 +884,7 @@ export class EventManager { this.localBus.emit('save:app:state', state) } - // usefulf for testing + // useful for testing _testingOnlySetCypress (cypress: any) { Cypress = cypress } diff --git a/packages/driver/src/cypress/runner.ts b/packages/driver/src/cypress/runner.ts index 77b643c23fb1..dd67ea0ed831 100644 --- a/packages/driver/src/cypress/runner.ts +++ b/packages/driver/src/cypress/runner.ts @@ -486,7 +486,7 @@ const hasOnly = (suite) => { ) } -const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { +const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { let hasTests = false // only loop until we find the first test @@ -505,7 +505,7 @@ const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onLogsBy // create optimized lookups for the tests without // traversing through it multiple times const tests: Record = {} - const normalizedSuite = normalize(suite, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedSuite = normalize(suite, tests, initialTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if (setTestsById) { // use callback here to hand back @@ -542,7 +542,7 @@ const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onLogsBy return normalizedSuite } -const normalize = (runnable, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { +const normalize = (runnable, tests, initialTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) => { const normalizeRunnable = (runnable) => { if (!runnable.id) { runnable.id = getRunnableId() @@ -564,22 +564,12 @@ const normalize = (runnable, tests, initialTests, onLogsById, getRunnableId, get if (i.prevAttempts) { prevAttempts = _.map(i.prevAttempts, (test) => { - if (test) { - _.each(RUNNABLE_LOGS, (type) => { - return _.each(test[type], onLogsById) - }) - } - // reduce this runnable down to its props // and collections return wrapAll(test) }) } - _.each(RUNNABLE_LOGS, (type) => { - return _.each(i[type], onLogsById) - }) - _.extend(runnable, i) } @@ -647,7 +637,7 @@ const normalize = (runnable, tests, initialTests, onLogsById, getRunnableId, get _.each({ tests: runnableTests, suites: runnableSuites }, (_runnables, type) => { if (runnable[type]) { return normalizedRunnable[type] = _.compact(_.map(_runnables, (childRunnable) => { - const normalizedChild = normalize(childRunnable, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedChild = normalize(childRunnable, tests, initialTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if (type === 'tests' && onlyIdMode()) { if (normalizedChild.id === getOnlyTestId()) { @@ -736,7 +726,7 @@ const normalize = (runnable, tests, initialTests, onLogsById, getRunnableId, get suite.suites = [] normalizedSuite.suites = _.compact(_.map(suiteSuites, (childSuite) => { - const normalizedChildSuite = normalize(childSuite, tests, initialTests, onLogsById, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) + const normalizedChildSuite = normalize(childSuite, tests, initialTests, getRunnableId, getHookId, getOnlyTestId, getOnlySuiteId, createEmptyOnlyTest) if ((suite._onlySuites.indexOf(childSuite) !== -1) || filterOnly(normalizedChildSuite, childSuite)) { if (onlyIdMode()) { @@ -1130,7 +1120,7 @@ export default { let _testsById: Record = {} const _testsQueue: any[] = [] const _testsQueueById: Record = {} - const _logsById: Record = {} + // only used during normalization let _emissions: Emissions = { started: {}, ended: {}, @@ -1159,12 +1149,6 @@ export default { return _tests } - const onLogsById = (l) => { - if (_skipCollectingLogs) return - - return _logsById[l.id] = l - } - const getTest = () => { return _test } @@ -1320,7 +1304,6 @@ export default { onSpecError, setOnlyTestId, setOnlySuiteId, - normalizeAll (tests, skipCollectingLogs) { _skipCollectingLogs = skipCollectingLogs // if we have an uncaught error then slice out @@ -1343,7 +1326,6 @@ export default { tests, setTestsById, setTests, - onLogsById, getRunnableId, getHookId, getOnlyTestId, @@ -1682,19 +1664,33 @@ export default { getDisplayPropsForLog: LogUtils.getDisplayProps, - getConsolePropsForLogById (logId) { - const attrs = _logsById[logId] + getConsolePropsForLog (testId, logId) { + if (_skipCollectingLogs) return + + const test = getTestById(testId) + + if (!test) return + + const logAttrs = _.find(test.commands || [], (log) => log.id === logId) - if (attrs) { - return LogUtils.getConsoleProps(attrs) + if (logAttrs) { + return LogUtils.getConsoleProps(logAttrs) } + + return }, - getSnapshotPropsForLogById (logId) { - const attrs = _logsById[logId] + getSnapshotPropsForLog (testId, logId) { + if (_skipCollectingLogs) return + + const test = getTestById(testId) - if (attrs) { - return LogUtils.getSnapshotProps(attrs) + if (!test) return + + const logAttrs = _.find(test.commands || [], (log) => log.id === logId) + + if (logAttrs) { + return LogUtils.getSnapshotProps(logAttrs) } return @@ -1762,14 +1758,20 @@ export default { return } - // if this test isnt in the current queue + // if this test isn't in the current queue // then go ahead and add it if (!_testsQueueById[test.id]) { _testsQueueById[test.id] = true _testsQueue.push(test) } - const existing = _logsById[attrs.id] + const { instrument } = attrs + + // pluralize the instrument as a property on the runnable + const name = `${instrument}s` + const logs = test[name] != null ? test[name] : (test[name] = []) + + const existing = _.find(logs, (log) => log.id === attrs.id) if (existing) { // because log:state:changed may @@ -1785,20 +1787,7 @@ export default { return _.extend(existing, attrs) } - _logsById[attrs.id] = attrs - - const { testId, instrument } = attrs - - test = getTestById(testId) - - if (test) { - // pluralize the instrument as a property on the runnable - const name = `${instrument}s` - const logs = test[name] != null ? test[name] : (test[name] = []) - - // else push it onto the logs - return logs.push(attrs) - } + return logs.push(attrs) }, } }, diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index 18923cfd4c4b..84bdbeb0a4bd 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -450,7 +450,7 @@ describe('commands', { viewportHeight: 1000 }, () => { .should('have.class', 'command-is-pinned') .find('.command-pin') - cy.wrap(runner.emit).should('be.calledWith', 'runner:pin:snapshot', 2) + cy.wrap(runner.emit).should('be.calledWith', 'runner:pin:snapshot', 'r3', 2) cy.percySnapshot() }) @@ -469,13 +469,13 @@ describe('commands', { viewportHeight: 1000 }, () => { it('prints to console', () => { cy.spy(runner, 'emit') cy.contains('#exists').click() - cy.wrap(runner.emit).should('be.calledWith', 'runner:console:log', 2) + cy.wrap(runner.emit).should('be.calledWith', 'runner:console:log', 'r3', 2) }) it('shows the snapshot', () => { cy.spy(runner, 'emit') cy.contains('#exists').click() - cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 2) + cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 'r3', 2) }) it('unpins after clicking again, does not re-print to the console', () => { @@ -842,7 +842,7 @@ describe('commands', { viewportHeight: 1000 }, () => { .should('have.class', 'command-is-pinned') .find('.command-pin') - cy.wrap(runner.emit).should('be.calledWith', 'runner:pin:snapshot', fakeIdForTest) + cy.wrap(runner.emit).should('be.calledWith', 'runner:pin:snapshot', 'r3', fakeIdForTest) cy.percySnapshot() }) @@ -862,13 +862,13 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.spy(runner, 'emit') cy.get('.command-name-within').click('top') - cy.wrap(runner.emit).should('be.calledWith', 'runner:console:log', fakeIdForTest) + cy.wrap(runner.emit).should('be.calledWith', 'runner:console:log', 'r3', fakeIdForTest) }) it('shows the snapshot', () => { cy.spy(runner, 'emit') cy.get('.command-name-within').click('top') - cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', fakeIdForTest) + cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 'r3', fakeIdForTest) }) it('unpins after clicking again, does not re-print to the console', () => { @@ -903,7 +903,7 @@ describe('commands', { viewportHeight: 1000 }, () => { it('shows snapshot after 50ms passes', () => { cy.wrap(runner.emit).should('not.be.calledWith', 'runner:show:snapshot') cy.tick(50) - cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 1) + cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 'r3', 1) cy.wrap(runner.emit).should('be.calledOnce') }) @@ -915,7 +915,7 @@ describe('commands', { viewportHeight: 1000 }, () => { it('hides the snapshot after 50ms pass without another mouse over', () => { cy.tick(50) - cy.wrap(runner.emit).should('be.calledWith', 'runner:hide:snapshot', 1) + cy.wrap(runner.emit).should('be.calledWith', 'runner:hide:snapshot', 'r3', 1) }) it('does not hide the snapshot if there is another mouseover before 50ms passes', () => { diff --git a/packages/reporter/cypress/e2e/unit/events.cy.ts b/packages/reporter/cypress/e2e/unit/events.cy.ts index 92080068af1f..9c666dfe34fb 100644 --- a/packages/reporter/cypress/e2e/unit/events.cy.ts +++ b/packages/reporter/cypress/e2e/unit/events.cy.ts @@ -313,20 +313,22 @@ describe('events', () => { events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { err: test.err, - commandId: undefined, + testId: undefined, + logId: undefined, }) }) it('emits runner:console:error with test id and command id on show:error when it is a command error and there is a matching command', () => { const test = { err: { isCommandErr: true }, commandMatchingErr: () => { - return { id: 'matching command id' } + return { id: 'matching command id', testId: 'test' } } } runnablesStore.testById.returns(test) events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { err: test.err, - commandId: 'matching command id', + testId: 'test', + logId: 'matching command id', }) }) @@ -339,7 +341,8 @@ describe('events', () => { events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { err: test.err, - commandId: undefined, + testId: undefined, + logId: undefined, }) }) diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index 29a95a38c66e..a38cca762653 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -427,16 +427,16 @@ class Command extends Component { @action _toggleColumnPin = () => { if (this.props.appState.isRunning) return - const { id } = this.props.model + const { testId, id } = this.props.model if (this._isPinned()) { this.props.appState.pinnedSnapshotId = null - this.props.events.emit('unpin:snapshot', id) + this.props.events.emit('unpin:snapshot', testId, id) this._snapshot(true) } else { this.props.appState.pinnedSnapshotId = id as number - this.props.events.emit('pin:snapshot', id) - this.props.events.emit('show:command', this.props.model.id) + this.props.events.emit('pin:snapshot', testId, id) + this.props.events.emit('show:command', testId, id) } } @@ -465,7 +465,7 @@ class Command extends Component { this._showTimeout = setTimeout(() => { runnablesStore.showingSnapshot = true - this.props.events.emit('show:snapshot', model.id) + this.props.events.emit('show:snapshot', model.testId, model.id) }, 50) } else { runnablesStore.attemptingShowSnapshot = false @@ -476,7 +476,7 @@ class Command extends Component { // we aren't trying to show a different snapshot if (runnablesStore.showingSnapshot && !runnablesStore.attemptingShowSnapshot) { runnablesStore.showingSnapshot = false - this.props.events.emit('hide:snapshot', model.id) + this.props.events.emit('hide:snapshot', model.testId, model.id) } }, 50) } diff --git a/packages/reporter/src/instruments/instrument-model.ts b/packages/reporter/src/instruments/instrument-model.ts index 4431dd0f6f2c..60fb91aa88c0 100644 --- a/packages/reporter/src/instruments/instrument-model.ts +++ b/packages/reporter/src/instruments/instrument-model.ts @@ -36,6 +36,7 @@ export default class Log { @observable type?: string @observable state: string @observable.ref referencesAlias?: Alias + testId: string constructor (props: InstrumentProps) { this.id = props.id @@ -47,6 +48,7 @@ export default class Log { this.type = props.type this.state = props.state this.referencesAlias = props.referencesAlias + this.testId = props.testId } update (props: InstrumentProps) { diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts index 0b819c765e77..24c63e5cc504 100644 --- a/packages/reporter/src/lib/events.ts +++ b/packages/reporter/src/lib/events.ts @@ -17,7 +17,7 @@ interface InitEvent { } export interface Runner { - emit: ((event: string, payload?: any) => void) + emit(event: string | symbol, ...args: any[]): boolean on: ((event: string, action: ((...args: any) => void)) => void) } @@ -159,8 +159,8 @@ const events: Events = { runner.emit('runner:restart') })) - localBus.on('show:command', (commandId) => { - runner.emit('runner:console:log', commandId) + localBus.on('show:command', (testId, logId) => { + runner.emit('runner:console:log', testId, logId) }) localBus.on('show:error', (test: TestModel) => { @@ -168,24 +168,25 @@ const events: Events = { runner.emit('runner:console:error', { err: test.err, - commandId: command?.id, + testId: command?.testId, + logId: command?.id, }) }) - localBus.on('show:snapshot', (commandId) => { - runner.emit('runner:show:snapshot', commandId) + localBus.on('show:snapshot', (testId, logId) => { + runner.emit('runner:show:snapshot', testId, logId) }) - localBus.on('hide:snapshot', (commandId) => { - runner.emit('runner:hide:snapshot', commandId) + localBus.on('hide:snapshot', (testId, logId) => { + runner.emit('runner:hide:snapshot', testId, logId) }) - localBus.on('pin:snapshot', (commandId) => { - runner.emit('runner:pin:snapshot', commandId) + localBus.on('pin:snapshot', (testId, logId) => { + runner.emit('runner:pin:snapshot', testId, logId) }) - localBus.on('unpin:snapshot', (commandId) => { - runner.emit('runner:unpin:snapshot', commandId) + localBus.on('unpin:snapshot', (testId, logId) => { + runner.emit('runner:unpin:snapshot', testId, logId) }) localBus.on('get:user:editor', (cb) => { diff --git a/packages/reporter/src/sessions/sessions.tsx b/packages/reporter/src/sessions/sessions.tsx index e305ae1e3dae..eec773aecca7 100644 --- a/packages/reporter/src/sessions/sessions.tsx +++ b/packages/reporter/src/sessions/sessions.tsx @@ -20,9 +20,9 @@ class Sessions extends React.Component { } printToConsole = (name) => { - const logId = this.props.model[name].id + const { id, testId } = this.props.model[name] - this.props.events.emit('show:command', logId) + this.props.events.emit('show:command', testId, id) } render () { From d8d0ae57873857fecc81b772088223218efe940d Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Mon, 12 Sep 2022 10:20:48 +1000 Subject: [PATCH 34/49] chore: skip flaky test for now (#23742) * chore: fixing flake * comment out flaky test * revert * add link to issue [skip ci] --- .../launchpad/cypress/e2e/config-warning.cy.ts | 15 +++++++++------ packages/launchpad/src/setup/LaunchpadHeader.vue | 5 ++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/launchpad/cypress/e2e/config-warning.cy.ts b/packages/launchpad/cypress/e2e/config-warning.cy.ts index d381910cbb1c..14010151fb68 100644 --- a/packages/launchpad/cypress/e2e/config-warning.cy.ts +++ b/packages/launchpad/cypress/e2e/config-warning.cy.ts @@ -73,7 +73,6 @@ describe('experimentalSingleTabRunMode', () => { it('is a valid config for component testing', () => { cy.scaffoldProject('experimentalSingleTabRunMode') cy.openProject('experimentalSingleTabRunMode') - cy.visitLaunchpad() cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject('cypress.config.js', ` const { defineConfig } = require('cypress') @@ -94,8 +93,10 @@ describe('experimentalSingleTabRunMode', () => { })`) }) + cy.visitLaunchpad() + cy.get('[data-cy-testingtype="component"]').click() - cy.get('h1').contains('Initializing Config').should('not.exist') + cy.findByTestId('launchpad-Choose a Browser') cy.get('h1').contains('Choose a Browser') }) @@ -110,10 +111,10 @@ describe('experimentalSingleTabRunMode', () => { }) describe('experimentalStudio', () => { - it('is not a valid config for component testing', { defaultCommandTimeout: THIRTY_SECONDS }, () => { + // TODO: fix this flaky test. https://github.com/cypress-io/cypress/issues/23743 + it.skip('is not a valid config for component testing', { defaultCommandTimeout: THIRTY_SECONDS }, () => { cy.scaffoldProject('experimentalSingleTabRunMode') cy.openProject('experimentalSingleTabRunMode') - cy.visitLaunchpad() cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject('cypress.config.js', ` const { defineConfig } = require('cypress') @@ -134,8 +135,10 @@ describe('experimentalStudio', () => { })`) }) + cy.visitLaunchpad() cy.get('[data-cy-testingtype="component"]').click() - cy.findByTestId('alert-body').contains('The experimentalStudio experiment is currently only supported for End to End Testing.') + cy.findByTestId('error-header') + cy.contains('The experimentalStudio experiment is currently only supported for End to End Testing.') }) it('is a valid config for e2e testing', { defaultCommandTimeout: THIRTY_SECONDS }, () => { @@ -156,7 +159,7 @@ describe('experimentalStudio', () => { cy.visitLaunchpad() cy.get('[data-cy-testingtype="e2e"]').click() - cy.get('h1').contains('Initializing Config').should('not.exist') + cy.findByTestId('launchpad-Choose a Browser') cy.get('h1').contains('Choose a Browser') }) }) diff --git a/packages/launchpad/src/setup/LaunchpadHeader.vue b/packages/launchpad/src/setup/LaunchpadHeader.vue index 46375cc64da1..6b1e5170fe46 100644 --- a/packages/launchpad/src/setup/LaunchpadHeader.vue +++ b/packages/launchpad/src/setup/LaunchpadHeader.vue @@ -1,5 +1,8 @@ diff --git a/packages/app/src/specs/RunStatusDots.vue b/packages/app/src/specs/RunStatusDots.vue index b9d6389419f8..b5b06eb499cc 100644 --- a/packages/app/src/specs/RunStatusDots.vue +++ b/packages/app/src/specs/RunStatusDots.vue @@ -43,6 +43,10 @@ class="ml-4px" /> + {{ props.specFileName }}{{ props.specFileExtension }} test results