diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 306512f9e8ec..21c560be3d99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for taking the time to contribute! :smile: **Once you learn how to use Cypress, you can contribute in many ways:** -- Join the [Cypress Gitter chat](https://on.cypress.io/chat) or [Discord](https://on.cypress.io/discord) and answer questions. Teaching others how to use Cypress is a great way to learn more about how it works. +- Join the [Cypress Discord](https://on.cypress.io/discord) and answer questions. Teaching others how to use Cypress is a great way to learn more about how it works. - Blog about Cypress. We display blogs featuring Cypress on our [Examples](https://on.cypress.io/examples) page. If you'd like your blog featured, [open a PR to add it to our docs](https://github.com/cypress-io/cypress-documentation/blob/develop/CONTRIBUTING.md#adding-examples). - Write some documentation or improve our existing docs. See our [guide to contributing to our docs](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md). - Give a talk about Cypress. [Contact us](mailto:support@cypress.io) ahead of time and we'll send you some swag. :shirt: @@ -14,7 +14,7 @@ Thanks for taking the time to contribute! :smile: - [Report bugs](https://github.com/cypress-io/cypress/issues/new) by opening an issue. - [Request features](https://github.com/cypress-io/cypress/issues/new) by opening an issue. - [Help triage existing issues](#triaging-issues). -- Write code to address an issue. We have some issues labeled as [`first-timers-only`](https://github.com/cypress-io/cypress/labels/first-timers-only) that are a good place to start. Please thoroughly read our [Writing Code guide](#writing-code). +- Write code to address an issue. We have some issues labeled as [`good first issue`](https://github.com/cypress-io/cypress/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) that are a good place to start. Please thoroughly read our [Writing Code guide](#writing-code). ## Table of Contents @@ -329,9 +329,7 @@ This will install all the dependencies for the repo and perform a preliminary bu yarn start ``` -If there are errors building the packages, prefix the commands with `DEBUG=cypress:*` to see more details. - -This outputs a lot of debugging lines. To focus on an individual module, run with `DEBUG=cypress:launcher` for instance. +If there are errors building the packages, prefix the commands with `DEBUG=cypress:*` to see more details. This outputs a lot of debugging lines. To focus on an individual module, run with `DEBUG=cypress:launcher:*` for instance. See ["Debug logs"](./guides/debug-logs.md) for more info. When running `yarn start` this routes through the CLI and eventually calls `yarn dev` with the proper arguments. This enables Cypress day-to-day development to match the logic of the built binary + CLI integration. @@ -411,30 +409,9 @@ Each package is responsible for building itself and testing itself and can do so | `test-integration` | Run all integration tests within the package; `exit 0` if N/A | | `test-watch` | Run all unit tests in the package in watch mode | -#### Debugging - -Some packages use [debug](https://github.com/visionmedia/debug#readme) to -log debug messages to the console. The naming scheme should be -`cypress:`; where package name is without the `@packages` scope. For example to see launcher messages during unit -tests start it using - -```bash -$ DEBUG=cypress:launcher yarn test --scope @packages/launcher -``` - -If you want to see log messages from all Cypress projects use wild card +#### Debug Logs -```bash -$ DEBUG=cypress:* -``` - -Or for an individual package: - -```bash -DEBUG=cypress:cli -DEBUG=cypress:server -DEBUG=cypress:launcher -``` +Many Cypress packages print out debugging information to console via the `debug` module. See ["Debug logs"](./guides/debug-logs.md) for more information. ### Coding Style diff --git a/circle.yml b/circle.yml index 669824c1d034..8d0971693164 100644 --- a/circle.yml +++ b/circle.yml @@ -28,7 +28,7 @@ mainBuildFilters: &mainBuildFilters branches: only: - develop - - 10.0-release + - fix-unit-tests-release # uncomment & add to the branch conditions below to disable the main linux # flow if we don't want to test it for a certain branch @@ -45,7 +45,7 @@ macWorkflowFilters: &mac-workflow-filters when: or: - equal: [ develop, << pipeline.git.branch >> ] - - equal: [ '10.0-release', << pipeline.git.branch >> ] + - equal: [ 'fix-unit-tests-release', << pipeline.git.branch >> ] - matches: pattern: "-release$" value: << pipeline.git.branch >> @@ -57,7 +57,7 @@ fullWindowsWorkflowFilters: &full-windows-workflow-filters branches: only: - develop - - '10.0-release' + - 'fix-unit-tests-release' - '*-release' - 'win*' @@ -108,7 +108,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "tbiethman/issue-21236" && "$CIRCLE_BRANCH" != "10.0-release" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "fix-unit-tests-release" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi @@ -494,7 +494,7 @@ commands: package: description: package to target type: enum - enum: ['frontend-shared', 'launchpad', 'app'] + enum: ['frontend-shared', 'launchpad', 'app', 'reporter'] browser: description: browser shortname to target type: string @@ -519,11 +519,21 @@ commands: cmd=$([[ <> == 'true' ]] && echo 'yarn percy exec --parallel -- --') || true DEBUG=<> \ CYPRESS_KONFIG_ENV=production \ - CYPRESS_RECORD_KEY=$TEST_LAUNCHPAD_RECORD_KEY \ + CYPRESS_RECORD_KEY=${TEST_LAUNCHPAD_RECORD_KEY:-$MAIN_RECORD_KEY} \ PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ $cmd yarn workspace @packages/<> cypress:run:<> --browser <> --record --parallel --group <>-<> + - run: + command: | + if [[ <> == 'app' && <> == 'true' && -d "packages/app/cypress/screenshots/runner/screenshot/screenshot.cy.tsx/percy" ]]; then + PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \ + PERCY_ENABLE=${PERCY_TOKEN:-0} \ + PERCY_PARALLEL_TOTAL=-1 \ + yarn percy upload packages/app/cypress/screenshots/runner/screenshot/screenshot.cy.tsx/percy + else + echo "skipping percy screenshots uploading" + fi - store_test_results: path: /tmp/cypress - store_artifacts: @@ -1132,6 +1142,7 @@ jobs: run-frontend-shared-component-tests-chrome, run-launchpad-component-tests-chrome, run-launchpad-integration-tests-chrome, + run-reporter-component-tests-chrome, run-webpack-dev-server-integration-tests, run-vite-dev-server-integration-tests - run: @@ -1209,6 +1220,11 @@ jobs: parallelism: 1 steps: - restore_cached_workspace + - run: + name: Update known_hosts with github.com keys + command: | + mkdir -p ~/.ssh + ssh-keyscan github.com >> ~/.ssh/known_hosts - run: yarn test-npm-package-release-script lint-types: @@ -1475,6 +1491,21 @@ jobs: browser: electron experimentalSessionAndOrigin: true + run-reporter-component-tests-chrome: + <<: *defaults + parameters: + <<: *defaultsParameters + percy: + type: boolean + default: false + parallelism: 7 + steps: + - run-new-ui-tests: + browser: chrome + percy: << parameters.percy >> + package: reporter + type: ct + reporter-integration-tests: <<: *defaults parallelism: 3 @@ -2324,6 +2355,11 @@ linux-workflow: &linux-workflow percy: true requires: - build + - run-reporter-component-tests-chrome: + context: [test-runner:cypress-record-key, test-runner:percy] + percy: true + requires: + - build - reporter-integration-tests: context: [test-runner:cypress-record-key, test-runner:percy] requires: @@ -2413,6 +2449,7 @@ linux-workflow: &linux-workflow - run-frontend-shared-component-tests-chrome - run-launchpad-component-tests-chrome - run-launchpad-integration-tests-chrome + - run-reporter-component-tests-chrome # various testing scenarios, like building full binary # and testing it on a real project diff --git a/graphql-codegen.yml b/graphql-codegen.yml index 5bc8361b3e97..111c3dc6c3c1 100644 --- a/graphql-codegen.yml +++ b/graphql-codegen.yml @@ -103,7 +103,7 @@ generates: <<: *vueOperations './packages/frontend-shared/src/generated/graphql.ts': - documents: './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}' + documents: './packages/frontend-shared/src/{gql-components,graphql}/**/*.{vue,ts,tsx,js,jsx}' <<: *vueOperations ### # All GraphQL documents imported into the .spec.tsx files for component testing. diff --git a/guides/README.md b/guides/README.md index 93e321f56c14..9264d51e4064 100644 --- a/guides/README.md +++ b/guides/README.md @@ -10,11 +10,15 @@ For general contributor information, check out [`CONTRIBUTING.md`](../CONTRIBUTI ## Table of Contents +* [App lifecycle](./app-lifecycle.md) * [Building release artifacts](./building-release-artifacts.md) * [Code signing](./code-signing.md) +* [Debug logs](./debug-logs.md) * [Determining the next version of Cypress to be released](./next-version.md) -* [Remaining Platform Agnostic](./remaining-platform-agnostic.md) +* [E2E Open Mode Testing](./e2e-open-testing.md) * [Error handling](./error-handling.md) * [Patching packages](./patch-package.md) * [Release process](./release-process.md) -* [Testing other projects](./testing-other-projects.md) \ No newline at end of file +* [Testing other projects](./testing-other-projects.md) +* [Testing strategy and style guide (draft)](./testing-strategy-and-styleguide.md) +* [Writing cross-platform JavaScript](./writing-cross-platform-javascript.md) diff --git a/guides/debug-logs.md b/guides/debug-logs.md new file mode 100644 index 000000000000..2bf654201b15 --- /dev/null +++ b/guides/debug-logs.md @@ -0,0 +1,42 @@ +# Debug Logs + +Many Cypress packages use the [`debug` module][debug] to log runtime info to the console. + +## Choosing a namespace + +The naming scheme for debug namespaces should generally follow this pattern: + +``` +cypress:{packageName}:{relative path to file from src root, using : to separate directories, minus index if applicable} + +# examples: +# packages/server/lib/util/file.js -> cypress:server:util:file +# packages/launcher/windows/index.ts -> cypress:launcher:windows +``` + +`cypress-verbose` can be used instead of `cypress` if the logs are overly verbose and would make the output of `DEBUG=cypress:*` unreadable. + +Exceptions to these rules: +* The `cli` uses `cypress:cli:*`. +* NPM packages should use `{moduleName}` as a prefix instead of `cypress`, like `cypress-webpack-preprocessor` for `npm/webpack-preprocessor`. +* In some places, like per-request in the `proxy` package, it's more useful to attach `debug` messages to something besides the module (like individual HTTP requests). In that case, it's okay to create namespaces as you see fit. But at least begin with `cypress:{packageName}` or `cypress-verbose:{packageName}` + +## Using debug logs + +Pass the `DEBUG` environment variable to select a set of logs to print to `stderr`. Example selectors: + +```shell +# frequently useful to get a sense of what is happening in the app at a high level +DEBUG=cypress:* +# print all info and verbose logs, but don't print verbose logs from `some-noisy-package` +DEBUG=cypress:*,cypress-verbose:*,-cypress-verbose:some-noisy-package:* +# print out verbose per-request data for proxied HTTP requests +DEBUG=cypress-verbose:proxy:http + +# in the browser, set `localStorage.DEBUG`: +localStorage.DEBUG = 'cypress:driver,cypress:driver:*' +``` + +For more info, see the [public documentation for printing debug logs](https://docs.cypress.io/guides/references/troubleshooting#Print-DEBUG-logs) and the [`debug` module docs][debug] + +[debug]: https://github.com/visionmedia/debug#readme diff --git a/npm/vite-dev-server/cypress/e2e/react.cy.ts b/npm/vite-dev-server/cypress/e2e/react.cy.ts index 1916389a70fb..0f0e0ea5c33f 100644 --- a/npm/vite-dev-server/cypress/e2e/react.cy.ts +++ b/npm/vite-dev-server/cypress/e2e/react.cy.ts @@ -1,6 +1,7 @@ /// /// import type { fixtureDirs } from '@tooling/system-tests' +import dedent from 'dedent' type ProjectDirs = typeof fixtureDirs @@ -24,17 +25,19 @@ for (const project of VITE_REACT) { it('should mount a passing test', () => { cy.visitApp() cy.contains('App.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) it('MissingReact: should fail, rerun, succeed', () => { - cy.once('uncaught:exception', () => { + cy.on('uncaught:exception', () => { // Ignore the uncaught exception in the AUT return false }) cy.visitApp() cy.contains('MissingReact.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.failed > .num').should('contain', 1) cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject(`src/MissingReact.jsx`, @@ -46,8 +49,14 @@ for (const project of VITE_REACT) { }) it('MissingReactInSpec: should fail, rerun, succeed', () => { + cy.on('uncaught:exception', () => { + // Ignore the uncaught exception in the AUT + return false + }) + cy.visitApp() cy.contains('MissingReactInSpec.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.failed > .num').should('contain', 1) cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject(`src/MissingReactInSpec.cy.jsx`, @@ -56,5 +65,55 @@ for (const project of VITE_REACT) { cy.get('.passed > .num').should('contain', 1) }) + + it('AppCompilationError: should fail with uncaught exception error', () => { + cy.on('uncaught:exception', () => { + // Ignore the uncaught exception in the AUT + return false + }) + + cy.visitApp() + cy.contains('AppCompilationError.cy.jsx').click() + cy.waitForSpecToFinish() + cy.get('.failed > .num').should('contain', 1) + cy.contains('An uncaught error was detected outside of a test') + cy.contains('The following error originated from your test code, not from Cypress.') + + // Correct the problem + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + `src/AppCompilationError.cy.jsx`, + await ctx.file.readFileInProject('src/App.cy.jsx'), + ) + }) + + cy.waitForSpecToFinish() + cy.get('.passed > .num').should('contain', 1) + + const appCompilationErrorSpec = dedent` + import React from 'react' + import { mount } from 'cypress/react' + import { App } from './App' + + it('renders hello world', () => { + mount() + cy.get('h1').contains('Hello World') + } + }) + ` + + // Cause the problem again + cy.withCtx(async (ctx, o) => { + await ctx.actions.file.writeFileInProject( + `src/AppCompilationError.cy.jsx`, + o.appCompilationErrorSpec, + ) + }, { appCompilationErrorSpec }) + + cy.waitForSpecToFinish() + cy.get('.failed > .num').should('contain', 1) + cy.contains('An uncaught error was detected outside of a test') + cy.contains('The following error originated from your test code, not from Cypress.') + }) }) } diff --git a/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts b/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts index 7f70de2ffe4d..12d59f6da467 100644 --- a/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts +++ b/npm/vite-dev-server/cypress/e2e/vite-dev-server.cy.ts @@ -9,6 +9,7 @@ describe('Config options', () => { cy.visitApp() cy.contains('App.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) diff --git a/npm/vite-dev-server/cypress/support/commands.ts b/npm/vite-dev-server/cypress/support/commands.ts index 95857aea4cdf..c4dc7e61db6d 100644 --- a/npm/vite-dev-server/cypress/support/commands.ts +++ b/npm/vite-dev-server/cypress/support/commands.ts @@ -1,37 +1,34 @@ /// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable -// drag(subject: string, options?: Partial): Chainable -// dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable -// } -// } -// } + +declare global { + namespace Cypress { + interface Chainable { + /** + * Adapter to wait for a spec to finish in a standard way. It + * + * 1. Waits for the stats to reset which signifies that the test page has loaded + * 2. Waits for 'Your tests are loading...' to not be present so that we know the tests themselves have loaded + * 3. Waits (with a timeout of 30s) for the Rerun all tests button to be present. This ensures all tests have completed + * + */ + waitForSpecToFinish() + } + } +} + +// Here we export the function with no intention to import it +// This only tells the typescript type checker that this definitely is a module +// This way, we are allowed to use the global namespace declaration +export const waitForSpecToFinish = () => { + // First ensure the test is loaded + cy.get('.passed > .num').should('contain', '--') + cy.get('.failed > .num').should('contain', '--') + + // Then ensure the tests are running + cy.contains('Your tests are loading...').should('not.exist') + + // Then ensure the tests have finished + cy.get('[aria-label="Rerun all tests"]', { timeout: 30000 }) +} + +Cypress.Commands.add('waitForSpecToFinish', waitForSpecToFinish) diff --git a/npm/vite-dev-server/cypress/support/e2e.ts b/npm/vite-dev-server/cypress/support/e2e.ts index 0abc77d09a4c..c279241b560e 100644 --- a/npm/vite-dev-server/cypress/support/e2e.ts +++ b/npm/vite-dev-server/cypress/support/e2e.ts @@ -15,6 +15,4 @@ // Import commands.js using ES2015 syntax: import '@packages/frontend-shared/cypress/e2e/support/e2eSupport' - -// Alternatively you can use CommonJS syntax: -// require('./commands') +import './commands' diff --git a/npm/vite-dev-server/package.json b/npm/vite-dev-server/package.json index 332b3230c83a..17b87e6af365 100644 --- a/npm/vite-dev-server/package.json +++ b/npm/vite-dev-server/package.json @@ -21,6 +21,7 @@ "pathe": "0.2.0" }, "devDependencies": { + "dedent": "^0.7.0", "vite": "2.9.0-beta.3", "vite-plugin-inspect": "0.4.3" }, diff --git a/npm/vite-dev-server/src/resolveConfig.ts b/npm/vite-dev-server/src/resolveConfig.ts index c1f7814194cc..61e67b8ba39b 100644 --- a/npm/vite-dev-server/src/resolveConfig.ts +++ b/npm/vite-dev-server/src/resolveConfig.ts @@ -48,6 +48,24 @@ export const createViteDevServerConfig = async (config: ViteDevServerConfig, vit base: `${cypressConfig.devServerPublicPathRoute}/`, configFile, optimizeDeps: { + esbuildOptions: { + incremental: true, + plugins: [ + { + name: 'cypress-esbuild-plugin', + setup (build) { + build.onEnd(function (result) { + // We don't want to completely fail the build here on errors so we treat the errors as warnings + // which will handle things more gracefully. Vite will 500 on files that have errors when they + // are requested later and Cypress will display an error message. + // See: https://github.com/cypress-io/cypress/pull/21599 + result.warnings = [...result.warnings, ...result.errors] + result.errors = [] + }) + }, + }, + ], + }, entries: [ ...specs.map((s) => relative(root, s.relative)), ...(cypressConfig.supportFile ? [resolve(root, cypressConfig.supportFile)] : []), diff --git a/npm/webpack-dev-server/__snapshots__/makeWebpackConfig.spec.ts.js b/npm/webpack-dev-server/__snapshots__/makeWebpackConfig.spec.ts.js index ad29c798514f..f2dc52ed214a 100644 --- a/npm/webpack-dev-server/__snapshots__/makeWebpackConfig.spec.ts.js +++ b/npm/webpack-dev-server/__snapshots__/makeWebpackConfig.spec.ts.js @@ -1,8 +1,36 @@ -exports['makeWebpackConfig ignores userland webpack `output.publicPath` 1'] = { +exports['makeWebpackConfig ignores userland webpack `output.publicPath` and `devServer.overlay` with webpack-dev-server v4 1'] = { "output": { "publicPath": "/test-public-path/", "filename": "[name].js" }, + "devServer": { + "magicHtml": true, + "client": { + "progress": false, + "overlay": false + } + }, + "mode": "development", + "optimization": { + "splitChunks": { + "chunks": "all" + } + }, + "plugins": [ + "HtmlWebpackPlugin", + "CypressCTWebpackPlugin" + ] +} + +exports['makeWebpackConfig ignores userland webpack `output.publicPath` and `devServer.overlay` with webpack-dev-server v3 1'] = { + "output": { + "publicPath": "/test-public-path/", + "filename": "[name].js" + }, + "devServer": { + "progress": true, + "overlay": false + }, "mode": "development", "optimization": { "splitChunks": { diff --git a/npm/webpack-dev-server/cypress/e2e/create-react-app.cy.ts b/npm/webpack-dev-server/cypress/e2e/create-react-app.cy.ts index d859951054c8..b20c2ec4cf04 100644 --- a/npm/webpack-dev-server/cypress/e2e/create-react-app.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/create-react-app.cy.ts @@ -22,6 +22,7 @@ for (const project of WEBPACK_REACT) { it('should mount a passing test', () => { cy.visitApp() cy.contains('App.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) @@ -29,6 +30,7 @@ for (const project of WEBPACK_REACT) { cy.visitApp() cy.contains('App.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) cy.withCtx(async (ctx) => { @@ -61,6 +63,7 @@ for (const project of WEBPACK_REACT) { }) cy.contains('New.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) }) diff --git a/npm/webpack-dev-server/cypress/e2e/next.cy.ts b/npm/webpack-dev-server/cypress/e2e/next.cy.ts index 0a7cf2693e8c..46f0712747d9 100644 --- a/npm/webpack-dev-server/cypress/e2e/next.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/next.cy.ts @@ -22,6 +22,7 @@ for (const project of WEBPACK_REACT) { it('should mount a passing test', () => { cy.visitApp() cy.contains('index.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) @@ -29,6 +30,7 @@ for (const project of WEBPACK_REACT) { cy.visitApp() cy.contains('index.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) cy.withCtx(async (ctx) => { @@ -68,6 +70,7 @@ for (const project of WEBPACK_REACT) { }) cy.contains('New.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) }) diff --git a/npm/webpack-dev-server/cypress/e2e/nuxt.cy.ts b/npm/webpack-dev-server/cypress/e2e/nuxt.cy.ts index a8356d87aadd..379e70bc2795 100644 --- a/npm/webpack-dev-server/cypress/e2e/nuxt.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/nuxt.cy.ts @@ -24,6 +24,7 @@ for (const project of PROJECTS) { it('should mount a passing test and live-reload', () => { cy.visitApp() cy.contains('Tutorial.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) cy.withCtx(async (ctx) => { @@ -63,6 +64,7 @@ for (const project of PROJECTS) { }) cy.contains('New.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) }) diff --git a/npm/webpack-dev-server/cypress/e2e/react.cy.ts b/npm/webpack-dev-server/cypress/e2e/react.cy.ts index eafb0046b5e5..4eca06947717 100644 --- a/npm/webpack-dev-server/cypress/e2e/react.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/react.cy.ts @@ -1,6 +1,7 @@ /// /// import type { fixtureDirs } from '@tooling/system-tests' +import dedent from 'dedent' type ProjectDirs = typeof fixtureDirs @@ -24,17 +25,19 @@ for (const project of WEBPACK_REACT) { it('should mount a passing test', () => { cy.visitApp() cy.contains('App.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) it('MissingReact: should fail, rerun, succeed', () => { - cy.once('uncaught:exception', () => { + cy.on('uncaught:exception', () => { // Ignore the uncaught exception in the AUT return false }) cy.visitApp() cy.contains('MissingReact.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.failed > .num').should('contain', 1) cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject(`src/MissingReact.jsx`, @@ -46,8 +49,14 @@ for (const project of WEBPACK_REACT) { }) it('MissingReactInSpec: should fail, rerun, succeed', () => { + cy.on('uncaught:exception', () => { + // Ignore the uncaught exception in the AUT + return false + }) + cy.visitApp() cy.contains('MissingReactInSpec.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.failed > .num').should('contain', 1) cy.withCtx(async (ctx) => { await ctx.actions.file.writeFileInProject(`src/MissingReactInSpec.cy.jsx`, @@ -57,6 +66,56 @@ for (const project of WEBPACK_REACT) { cy.get('.passed > .num').should('contain', 1) }) + it('AppCompilationError: should fail with uncaught exception error', () => { + cy.on('uncaught:exception', () => { + // Ignore the uncaught exception in the AUT + return false + }) + + cy.visitApp() + cy.contains('AppCompilationError.cy.jsx').click() + cy.waitForSpecToFinish() + cy.get('.failed > .num').should('contain', 1) + cy.contains('An uncaught error was detected outside of a test') + cy.contains('The following error originated from your test code, not from Cypress.') + + // Correct the problem + cy.withCtx(async (ctx) => { + await ctx.actions.file.writeFileInProject( + `src/AppCompilationError.cy.jsx`, + await ctx.file.readFileInProject('src/App.cy.jsx'), + ) + }) + + cy.waitForSpecToFinish() + cy.get('.passed > .num').should('contain', 1) + + const appCompilationErrorSpec = dedent` + import React from 'react' + import { mount } from 'cypress/react' + import { App } from './App' + + it('renders hello world', () => { + mount() + cy.get('h1').contains('Hello World') + } + }) + ` + + // Cause the problem again + cy.withCtx(async (ctx, o) => { + await ctx.actions.file.writeFileInProject( + `src/AppCompilationError.cy.jsx`, + o.appCompilationErrorSpec, + ) + }, { appCompilationErrorSpec }) + + cy.waitForSpecToFinish() + cy.get('.failed > .num').should('contain', 1) + cy.contains('An uncaught error was detected outside of a test') + cy.contains('The following error originated from your test code, not from Cypress.') + }) + // https://cypress-io.atlassian.net/browse/UNIFY-1697 it('filters missing spec files from loader during pre-compilation', () => { cy.visitApp() diff --git a/npm/webpack-dev-server/cypress/e2e/vue-cli.cy.ts b/npm/webpack-dev-server/cypress/e2e/vue-cli.cy.ts index cbe6ac788bd8..57ada2da8fd0 100644 --- a/npm/webpack-dev-server/cypress/e2e/vue-cli.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/vue-cli.cy.ts @@ -22,6 +22,7 @@ for (const project of PROJECTS) { it('should mount a passing test', () => { cy.visitApp() cy.contains('HelloWorld.cy.js').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) cy.get('.commands-container').within(() => { cy.contains('mount') diff --git a/npm/webpack-dev-server/cypress/e2e/webpack-dev-server.cy.ts b/npm/webpack-dev-server/cypress/e2e/webpack-dev-server.cy.ts index d888a08608fd..3906f21c761d 100644 --- a/npm/webpack-dev-server/cypress/e2e/webpack-dev-server.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/webpack-dev-server.cy.ts @@ -6,6 +6,7 @@ describe('Config options', () => { cy.visitApp() cy.contains('App.cy.jsx').click() + cy.waitForSpecToFinish() cy.get('.passed > .num').should('contain', 1) }) }) diff --git a/npm/webpack-dev-server/cypress/support/commands.ts b/npm/webpack-dev-server/cypress/support/commands.ts index 95857aea4cdf..c4dc7e61db6d 100644 --- a/npm/webpack-dev-server/cypress/support/commands.ts +++ b/npm/webpack-dev-server/cypress/support/commands.ts @@ -1,37 +1,34 @@ /// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable -// drag(subject: string, options?: Partial): Chainable -// dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable -// } -// } -// } + +declare global { + namespace Cypress { + interface Chainable { + /** + * Adapter to wait for a spec to finish in a standard way. It + * + * 1. Waits for the stats to reset which signifies that the test page has loaded + * 2. Waits for 'Your tests are loading...' to not be present so that we know the tests themselves have loaded + * 3. Waits (with a timeout of 30s) for the Rerun all tests button to be present. This ensures all tests have completed + * + */ + waitForSpecToFinish() + } + } +} + +// Here we export the function with no intention to import it +// This only tells the typescript type checker that this definitely is a module +// This way, we are allowed to use the global namespace declaration +export const waitForSpecToFinish = () => { + // First ensure the test is loaded + cy.get('.passed > .num').should('contain', '--') + cy.get('.failed > .num').should('contain', '--') + + // Then ensure the tests are running + cy.contains('Your tests are loading...').should('not.exist') + + // Then ensure the tests have finished + cy.get('[aria-label="Rerun all tests"]', { timeout: 30000 }) +} + +Cypress.Commands.add('waitForSpecToFinish', waitForSpecToFinish) diff --git a/npm/webpack-dev-server/cypress/support/e2e.ts b/npm/webpack-dev-server/cypress/support/e2e.ts index 0abc77d09a4c..c279241b560e 100644 --- a/npm/webpack-dev-server/cypress/support/e2e.ts +++ b/npm/webpack-dev-server/cypress/support/e2e.ts @@ -15,6 +15,4 @@ // Import commands.js using ES2015 syntax: import '@packages/frontend-shared/cypress/e2e/support/e2eSupport' - -// Alternatively you can use CommonJS syntax: -// require('./commands') +import './commands' diff --git a/npm/webpack-dev-server/package.json b/npm/webpack-dev-server/package.json index 682ec2039f3c..39ae86ed5e09 100644 --- a/npm/webpack-dev-server/package.json +++ b/npm/webpack-dev-server/package.json @@ -33,6 +33,7 @@ "@types/speed-measure-webpack-plugin": "^1.3.4", "@types/webpack-dev-server-3": "npm:@types/webpack-dev-server@^3", "chai": "^4.3.6", + "dedent": "^0.7.0", "mocha": "^9.2.2", "proxyquire": "2.1.3", "sinon": "^13.0.1", diff --git a/npm/webpack-dev-server/src/CypressCTWebpackPlugin.ts b/npm/webpack-dev-server/src/CypressCTWebpackPlugin.ts index 70bcf3fc78f9..f625d6d2c98a 100644 --- a/npm/webpack-dev-server/src/CypressCTWebpackPlugin.ts +++ b/npm/webpack-dev-server/src/CypressCTWebpackPlugin.ts @@ -45,7 +45,6 @@ export const normalizeError = (error: Error | string) => { export class CypressCTWebpackPlugin { private files: Cypress.Cypress['spec'][] = [] private supportFile: string | false - private errorEmitted = false private compilation: Webpack45Compilation | null = null private webpack: Function @@ -93,42 +92,6 @@ export class CypressCTWebpackPlugin { callback() } - /* - * After compiling, we check for errors and inform the server of them. - */ - private afterCompile = () => { - if (!this.compilation) { - return - } - - const stats = this.compilation.getStats() - - if (stats.hasErrors()) { - this.errorEmitted = true - - // webpack 4: string[] - // webpack 5: Error[] - const errors = stats.toJson().errors as Array | undefined - - if (!errors || !errors.length) { - return - } - - this.devServerEvents.emit('dev-server:compile:error', normalizeError(errors[0])) - } else if (this.errorEmitted) { - // compilation succeed but assets haven't emitted to the output yet - this.devServerEvents.emit('dev-server:compile:error', null) - } - } - - // After emitting assets, we tell the server compilation was successful - // so it can trigger a reload the AUT iframe. - private afterEmit = () => { - if (!this.compilation?.getStats().hasErrors()) { - this.devServerEvents.emit('dev-server:compile:success') - } - } - /* * `webpack --watch` watches the existing specs and their dependencies for changes, * but we also need to add additional dependencies to our dynamic "browser.js" (generated @@ -178,8 +141,9 @@ export class CypressCTWebpackPlugin { this.devServerEvents.on('dev-server:specs:changed', this.onSpecsChange) _compiler.hooks.beforeCompile.tapAsync('CypressCTPlugin', this.beforeCompile) - _compiler.hooks.afterCompile.tap('CypressCTPlugin', this.afterCompile) - _compiler.hooks.afterEmit.tap('CypressCTPlugin', this.afterEmit) _compiler.hooks.compilation.tap('CypressCTPlugin', (compilation) => this.addCompilationHooks(compilation as Webpack45Compilation)) + _compiler.hooks.done.tap('CypressCTPlugin', () => { + this.devServerEvents.emit('dev-server:compile:success') + }) } } diff --git a/npm/webpack-dev-server/src/devServer.ts b/npm/webpack-dev-server/src/devServer.ts index 81bec1ff35ba..6fd1e7ad8300 100644 --- a/npm/webpack-dev-server/src/devServer.ts +++ b/npm/webpack-dev-server/src/devServer.ts @@ -40,10 +40,6 @@ type DevServerCreateResult = { compiler: Compiler } -const normalizeError = (error: Error | string) => { - return typeof error === 'string' ? error : error.message -} - /** * import { devServer } from '@cypress/webpack-dev-server' * @@ -56,21 +52,6 @@ export function devServer (devServerConfig: WebpackDevServerConfig): Promise { const result = await devServer.create(devServerConfig) as DevServerCreateResult - // When compiling in run mode - // Stop the clock early, no need to run all the tests on a failed build - result.compiler.hooks.done.tap('cyCustomErrorBuild', function (stats) { - if (stats.hasErrors()) { - const errors = stats.compilation.errors - - devServerConfig.devServerEvents.emit('dev-server:compile:error', normalizeError(errors[0])) - if (devServerConfig.cypressConfig.isTextTerminal) { - process.exit(1) - } - } - - devServerConfig.devServerEvents.emit('dev-server:compile:done') - }) - if (result.version === 3) { const srv = result.server.listen(0, '127.0.0.1', () => { const port = (srv.address() as AddressInfo).port diff --git a/npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts b/npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts index bb42a4603609..16ff6a8d60b0 100644 --- a/npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts +++ b/npm/webpack-dev-server/src/makeDefaultWebpackConfig.ts @@ -24,7 +24,7 @@ export function makeDefaultWebpackConfig ( debug(`Using HtmlWebpackPlugin version ${version} from ${importPath}`) - return { + const finalConfig = { mode: 'development', optimization: { splitChunks: { @@ -41,5 +41,24 @@ export function makeDefaultWebpackConfig ( template: indexHtmlFile, }) as any, ], + } as any + + if (config.sourceWebpackModulesResult.webpackDevServer.majorVersion === 4) { + return { + ...finalConfig, + devServer: { + client: { + overlay: false, + }, + }, + } + } + + // @ts-ignore + return { + ...finalConfig, + devServer: { + overlay: false, + }, } } diff --git a/npm/webpack-dev-server/src/makeWebpackConfig.ts b/npm/webpack-dev-server/src/makeWebpackConfig.ts index bded20b7c923..6d7bf74640dd 100644 --- a/npm/webpack-dev-server/src/makeWebpackConfig.ts +++ b/npm/webpack-dev-server/src/makeWebpackConfig.ts @@ -16,14 +16,14 @@ const removeList = [ // https://github.com/cypress-io/cypress/issues/15865 'HtmlWebpackPlugin', - // This plugin is an opitimization for HtmlWebpackPlugin for use in - // production environments, not relevent for testing. + // This plugin is an optimization for HtmlWebpackPlugin for use in + // production environments, not relevant for testing. 'PreloadPlugin', - // Another optimization not relevent in a testing environment. + // Another optimization not relevant in a testing environment. 'HtmlPwaPlugin', - // We already reload when webpack recomplies (via listeners on + // We already reload when webpack recompiles (via listeners on // devServerEvents). Removing this plugin can prevent double-refreshes // in some setups. 'HotModuleReplacementPlugin', diff --git a/npm/webpack-dev-server/test/devServer-e2e.spec.ts b/npm/webpack-dev-server/test/devServer-e2e.spec.ts index a7f66f7267d0..0fa39d3a32dd 100644 --- a/npm/webpack-dev-server/test/devServer-e2e.spec.ts +++ b/npm/webpack-dev-server/test/devServer-e2e.spec.ts @@ -1,5 +1,4 @@ import path from 'path' -import sinon from 'sinon' import { expect } from 'chai' import { once, EventEmitter } from 'events' import http from 'http' @@ -179,74 +178,6 @@ describe('#devServer', () => { }) }) - it('emits dev-server:compile:error event on error compilation', async () => { - const devServerEvents = new EventEmitter() - const exitSpy = sinon.stub(process, 'exit') - - const badSpec = `${root}/test/fixtures/compilation-fails.spec.js` - const { close } = await devServer({ - webpackConfig, - cypressConfig, - specs: [ - { - name: badSpec, - relative: badSpec, - absolute: badSpec, - }, - ], - devServerEvents, - }) - - const [err] = await once(devServerEvents, 'dev-server:compile:error') - - expect(err).to.contain('Module parse failed: Unexpected token (1:5)') - expect(err).to.contain('You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders') - expect(err).to.contain('> this is an invalid spec file') - expect(exitSpy.calledOnce).to.be.true - - await new Promise((resolve, reject) => { - close((err) => { - if (err) { - return reject(err) - } - - resolve() - }) - }) - }) - - it('does not inject files into loader that do not exist at compile time', async () => { - const devServerEvents = new EventEmitter() - const { close } = await devServer({ - webpackConfig, - cypressConfig, - specs: [...createSpecs('foo.spec.js'), ...createSpecs('does_not_exist.spec.js')], - devServerEvents, - }) - - let compileErrorOccurred - - devServerEvents.on('dev-server:compile:error', () => { - compileErrorOccurred = true - }) - - await once(devServerEvents, 'dev-server:compile:done') - - // An error event should not have been emitted, as we should have - // filtered any missing specs out of the set provided to the loader. - expect(compileErrorOccurred).to.not.be.true - - await new Promise((resolve, reject) => { - close((err) => { - if (err) { - return reject(err) - } - - resolve() - }) - }) - }) - it('touches browser.js when a spec file is added and recompile', async function () { const devServerEvents = new EventEmitter() const { close } = await devServer({ diff --git a/npm/webpack-dev-server/test/makeWebpackConfig.spec.ts b/npm/webpack-dev-server/test/makeWebpackConfig.spec.ts index ad8fe5c43b8f..bb00e29ee0a0 100644 --- a/npm/webpack-dev-server/test/makeWebpackConfig.spec.ts +++ b/npm/webpack-dev-server/test/makeWebpackConfig.spec.ts @@ -4,9 +4,10 @@ import snapshot from 'snap-shot-it' import { WebpackDevServerConfig } from '../src/devServer' import { sourceDefaultWebpackDependencies } from '../src/helpers/sourceRelativeWebpackModules' import { makeWebpackConfig } from '../src/makeWebpackConfig' +import { createModuleMatrixResult } from './test-helpers/createModuleMatrixResult' describe('makeWebpackConfig', () => { - it('ignores userland webpack `output.publicPath`', async () => { + it('ignores userland webpack `output.publicPath` and `devServer.overlay` with webpack-dev-server v3', async () => { const devServerConfig: WebpackDevServerConfig = { specs: [], cypressConfig: { @@ -17,7 +18,11 @@ describe('makeWebpackConfig', () => { } as Cypress.PluginConfigOptions, webpackConfig: { output: { - publicPath: '/this-will-be-ignored', + publicPath: '/this-will-be-ignored', // This will be overridden by makeWebpackConfig.ts + }, + devServer: { + progress: true, + overlay: true, // This will be overridden by makeWebpackConfig.ts }, }, devServerEvents: new EventEmitter(), @@ -38,4 +43,47 @@ describe('makeWebpackConfig', () => { expect(actual.output.publicPath).to.eq('/test-public-path/') snapshot(actual) }) + + it('ignores userland webpack `output.publicPath` and `devServer.overlay` with webpack-dev-server v4', async () => { + const devServerConfig: WebpackDevServerConfig = { + specs: [], + cypressConfig: { + isTextTerminal: false, + projectRoot: '.', + supportFile: '/support.js', + devServerPublicPathRoute: '/test-public-path', // This will be overridden by makeWebpackConfig.ts + } as Cypress.PluginConfigOptions, + webpackConfig: { + output: { + publicPath: '/this-will-be-ignored', + }, + devServer: { + magicHtml: true, + client: { + progress: false, + overlay: true, // This will be overridden by makeWebpackConfig.ts + }, + }, + }, + devServerEvents: new EventEmitter(), + } + const actual = await makeWebpackConfig({ + devServerConfig, + sourceWebpackModulesResult: createModuleMatrixResult({ + webpack: 4, + webpackDevServer: 4, + }), + }) + + // plugins contain circular deps which cannot be serialized in a snapshot. + // instead just compare the name and order of the plugins. + actual.plugins = actual.plugins.map((p) => p.constructor.name) + + // these will include paths from the user's local file system, so we should not include them the snapshot + delete actual.output.path + delete actual.entry + + expect(actual.output.publicPath).to.eq('/test-public-path/') + snapshot(actual) + }) }) diff --git a/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts index f2d50e7a42c5..ed62c4f0246c 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress-component.cy.ts @@ -126,7 +126,7 @@ describe('Cypress In Cypress CT', { viewportWidth: 1500, defaultCommandTimeout: cy.get('[data-model-state="passed"]').should('contain', 'renders the test component') cy.withCtx((ctx, o) => { - o.sinon.stub(ctx.actions.app, 'setActiveBrowserById') + o.sinon.stub(ctx.actions.browser, 'setActiveBrowserById') o.sinon.stub(ctx.actions.project, 'launchProject').resolves() }) @@ -137,10 +137,10 @@ describe('Cypress In Cypress CT', { viewportWidth: 1500, defaultCommandTimeout: .click() cy.withCtx((ctx, o) => { - const browserId = (ctx.actions.app.setActiveBrowserById as SinonStub).args[0][0] + const browserId = (ctx.actions.browser.setActiveBrowserById as SinonStub).args[0][0] const genId = ctx.fromId(browserId, 'Browser') - expect(ctx.actions.app.setActiveBrowserById).to.have.been.calledWith(browserId) + expect(ctx.actions.browser.setActiveBrowserById).to.have.been.calledWith(browserId) expect(genId).to.eql('firefox-firefox-stable') expect(ctx.actions.project.launchProject).to.have.been.calledWith( ctx.coreData.currentTestingType, {}, o.sinon.match(new RegExp('cypress\-in\-cypress\/src\/TestComponent\.spec\.jsx$')), diff --git a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts index 0434f0bb798a..12f05fd603b2 100644 --- a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts @@ -50,7 +50,7 @@ describe('hooks', { cy.get('.hook-open-in-ide').should('have.length', 4) cy.withCtx((ctx, o) => { - ctx.actions.file.openFile = o.sinon.stub() + o.sinon.stub(ctx.actions.file, 'openFile') }) cy.contains('Open in IDE').invoke('show').click({ force: true }) diff --git a/packages/app/cypress/e2e/runner/runner.ui.cy.ts b/packages/app/cypress/e2e/runner/runner.ui.cy.ts index f888bb877bef..bc2d1a66e4d8 100644 --- a/packages/app/cypress/e2e/runner/runner.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/runner.ui.cy.ts @@ -168,7 +168,7 @@ describe('src/cypress/runner', () => { }) cy.withCtx((ctx, o) => { - ctx.actions.file.openFile = o.sinon.stub() + o.sinon.stub(ctx.actions.file, 'openFile') }) cy.contains('a', 'simple-cy-assert.runner') diff --git a/packages/app/cypress/e2e/runner/support/verify-failures.ts b/packages/app/cypress/e2e/runner/support/verify-failures.ts index de6de8cec34e..c0f3676058b3 100644 --- a/packages/app/cypress/e2e/runner/support/verify-failures.ts +++ b/packages/app/cypress/e2e/runner/support/verify-failures.ts @@ -6,7 +6,10 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' const verifyIdeOpen = ({ fileName, action, hasPreferredIde, ideLine, ideColumn }) => { if (hasPreferredIde) { cy.withCtx((ctx, o) => { - ctx.actions.file.openFile = o.sinon.stub() + // @ts-expect-error - check if we've stubbed it already, only need to stub it once + if (!ctx.actions.file.openFile.restore?.sinon) { + o.sinon.stub(ctx.actions.file, 'openFile') + } }) action() diff --git a/packages/app/cypress/e2e/sidebar_navigation.cy.ts b/packages/app/cypress/e2e/sidebar_navigation.cy.ts index c8e937c94081..d1218d2a5e0b 100644 --- a/packages/app/cypress/e2e/sidebar_navigation.cy.ts +++ b/packages/app/cypress/e2e/sidebar_navigation.cy.ts @@ -321,16 +321,18 @@ describe('Sidebar Navigation', () => { cy.withCtx((ctx, o) => { o.sinon.stub(ctx.actions.project, 'setAndLoadCurrentTestingType') o.sinon.stub(ctx.actions.project, 'reconfigureProject').resolves() + o.sinon.stub(ctx.actions.wizard, 'scaffoldTestingType').resolves() }) cy.get('[data-cy-testingtype="e2e"]').within(() => { cy.contains('Not Configured') }).click() - cy.withCtx((ctx) => { + cy.withRetryableCtx((ctx) => { expect(ctx.coreData.app.relaunchBrowser).eq(false) expect(ctx.actions.project.setAndLoadCurrentTestingType).to.have.been.calledWith('e2e') expect(ctx.actions.project.reconfigureProject).to.have.been.called + expect(ctx.actions.wizard.scaffoldTestingType).to.have.been.called }) }) }) diff --git a/packages/app/cypress/e2e/subscriptions/baseErrorChange-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/baseErrorChange-subscription.cy.ts index f7210ad63dcb..f94fd9d98436 100644 --- a/packages/app/cypress/e2e/subscriptions/baseErrorChange-subscription.cy.ts +++ b/packages/app/cypress/e2e/subscriptions/baseErrorChange-subscription.cy.ts @@ -1,4 +1,4 @@ -describe('baseErrorChange subscription', () => { +describe('errorWarningChange subscription', () => { beforeEach(() => { cy.scaffoldProject('cypress-in-cypress') cy.openProject('cypress-in-cypress') diff --git a/packages/app/cypress/e2e/top-nav.cy.ts b/packages/app/cypress/e2e/top-nav.cy.ts index b676381b1e38..890cc6924bb0 100644 --- a/packages/app/cypress/e2e/top-nav.cy.ts +++ b/packages/app/cypress/e2e/top-nav.cy.ts @@ -97,17 +97,17 @@ describe('App Top Nav Workflows', () => { cy.findByTestId('top-nav-active-browser').click() cy.withCtx((ctx, o) => { - o.sinon.stub(ctx.actions.app, 'setActiveBrowserById') + o.sinon.stub(ctx.actions.browser, 'setActiveBrowserById') o.sinon.stub(ctx.actions.project, 'launchProject').resolves() }) cy.findAllByTestId('top-nav-browser-list-item').eq(1).click() cy.withCtx((ctx, o) => { - const browserId = (ctx.actions.app.setActiveBrowserById as SinonStub).args[0][0] + const browserId = (ctx.actions.browser.setActiveBrowserById as SinonStub).args[0][0] const genId = ctx.fromId(browserId, 'Browser') - expect(ctx.actions.app.setActiveBrowserById).to.have.been.calledWith(browserId) + expect(ctx.actions.browser.setActiveBrowserById).to.have.been.calledWith(browserId) expect(genId).to.eql('edge-chromium-stable') expect(ctx.actions.project.launchProject).to.have.been.calledWith( ctx.coreData.currentTestingType, {}, undefined, diff --git a/packages/app/src/layouts/default.vue b/packages/app/src/layouts/default.vue index 680801cbc0c8..1357606ac0ee 100644 --- a/packages/app/src/layouts/default.vue +++ b/packages/app/src/layouts/default.vue @@ -23,8 +23,8 @@ >
@@ -53,9 +53,8 @@ diff --git a/packages/app/src/pages/Specs/Runner.vue b/packages/app/src/pages/Specs/Runner.vue index e8c6a4778f6d..e8d500e712eb 100644 --- a/packages/app/src/pages/Specs/Runner.vue +++ b/packages/app/src/pages/Specs/Runner.vue @@ -147,6 +147,24 @@ iframe.aut-iframe { background: white; } +.is-screenshotting #main-pane { + overflow: auto !important; +} + +.is-screenshotting.screenshot-scrolling #main-pane { + overflow: visible !important; +} + +#resizable-panels-root { + overflow-x: auto; + overflow-y: hidden; +} + +.is-screenshotting #resizable-panels-root { + overflow-x: visible; + overflow-y: visible; +} + iframe.spec-iframe { border: none; height: 0; diff --git a/packages/app/src/runner/ResizablePanels.vue b/packages/app/src/runner/ResizablePanels.vue index 2aeab33998be..a9f1d4ad14d7 100644 --- a/packages/app/src/runner/ResizablePanels.vue +++ b/packages/app/src/runner/ResizablePanels.vue @@ -1,9 +1,10 @@