diff --git a/.circleci/cache-version.txt b/.circleci/cache-version.txt index 125dd7e97c..c8b9721d30 100644 --- a/.circleci/cache-version.txt +++ b/.circleci/cache-version.txt @@ -1,3 +1,3 @@ # Bump this version to force CI to re-create the cache from scratch. -12-12-22 +04-19-22 diff --git a/.circleci/workflows.yml b/.circleci/workflows.yml index dd0f2f7a23..2a398cdee1 100644 --- a/.circleci/workflows.yml +++ b/.circleci/workflows.yml @@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters - /^release\/\d+\.\d+\.\d+$/ # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - 'update-v8-snapshot-cache-on-develop' - - 'emily/before-spec-promise' + - 'ryanm/feat/unify-cdp-approach-in-electron' # 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 @@ -41,7 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'emily/before-spec-promise', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -52,7 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'fix/preflight', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> @@ -71,12 +71,12 @@ windowsWorkflowFilters: &windows-workflow-filters or: - equal: [ develop, << pipeline.git.branch >> ] # use the following branch as well to ensure that v8 snapshot cache updates are fully tested - - equal: [ 'lmiller/fixing-vite-windows', << pipeline.git.branch >> ] - equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ] - - equal: [ 'fix/preflight', << pipeline.git.branch >> ] + - equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ] - matches: pattern: /^release\/\d+\.\d+\.\d+$/ value: << pipeline.git.branch >> + executors: # the Docker image with Cypress dependencies and Chrome browser cy-doc: @@ -139,7 +139,7 @@ commands: - run: name: Check current branch to persist artifacts command: | - if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "emily/before-spec-promise" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" ]]; then + if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "ryanm/feat/unify-cdp-approach-in-electron" ]]; then echo "Not uploading artifacts or posting install comment for this branch." circleci-agent step halt fi @@ -495,6 +495,7 @@ commands: if [[ -v MAIN_RECORD_KEY ]]; then # internal PR CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \ + CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \ yarn cypress:run --record --parallel --group 5x-driver-<> --browser <> else # external PR @@ -567,6 +568,7 @@ commands: PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_WORKSPACE_ID \ PERCY_ENABLE=${PERCY_TOKEN:-0} \ PERCY_PARALLEL_TOTAL=-1 \ + CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \ $cmd yarn workspace @packages/<> cypress:run:<> --browser <> --record --parallel --group <>-<> else # external PR @@ -606,8 +608,6 @@ commands: fi - store_test_results: path: /tmp/cypress - - store_artifacts: - path: ./packages/<>/cypress/videos - store-npm-logs run-system-tests: @@ -1307,7 +1307,7 @@ jobs: <<: *defaultsParameters steps: - restore_cached_workspace - - run: + - run: name: 'Determine if Release Workflow should be triggered' command: | if [[ "$CIRCLE_BRANCH" != "develop" ]]; then @@ -1455,7 +1455,7 @@ jobs: # run type checking for each individual package - run: yarn lerna run types - verify-mocha-results: - expectedResultCount: 18 + expectedResultCount: 19 - store_test_results: path: /tmp/cypress # CLI tests generate HTML files with sample CLI command output diff --git a/.github/workflows/stale_issues_and_pr_cleanup.yml b/.github/workflows/stale_issues_and_pr_cleanup.yml index 5cae94f8f7..20b4f31b04 100644 --- a/.github/workflows/stale_issues_and_pr_cleanup.yml +++ b/.github/workflows/stale_issues_and_pr_cleanup.yml @@ -5,7 +5,7 @@ on: debug-only: description: 'debug-only' required: false - default: true + default: false days-before-stale: description: 'days-before-stale' required: false @@ -17,11 +17,11 @@ on: exempt-issue-labels: description: 'exempt-issue-labels' required: false - default: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,Popular Issue' + default: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,prevent-stale,triaged' exempt-pr-labels: description: 'exempt-pr-labels' required: false - default: 'type: feature,type: enhancement,Popular Issue' + default: 'type: feature,type: enhancement,prevent-stale,triaged' schedule: - cron: '30 1 * * *' permissions: @@ -31,8 +31,8 @@ env: DEFAULT_DEBUG_ONLY: true DEFAULT_DAYS_BEFORE_STALE: 180 DEFAULT_DAYS_BEFORE_CLOSE: 14 - DEFAULT_EXEMPT_ISSUE_LABELS: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,Popular Issue' - DEFAULT_EXEMPT_PR_LABELS: 'type: feature,type: enhancement,Popular Issue' + DEFAULT_EXEMPT_ISSUE_LABELS: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,prevent-stale,triaged' + DEFAULT_EXEMPT_PR_LABELS: 'type: feature,type: enhancement,prevent-stale,triaged' jobs: stale: runs-on: ubuntu-latest @@ -50,5 +50,5 @@ jobs: exempt-issue-labels: ${{ github.event.inputs.exempt-issue-labels || env.DEFAULT_EXEMPT_ISSUE_LABELS }} exempt-pr-labels: ${{ github.event.inputs.exempt-pr-labels || env.DEFAULT_EXEMPT_PR_LABELS }} exempt-all-milestones: true - operations-per-run: 1000 #using during debug mode to capture all the tickets impacted + operations-per-run: 400 #keeping this a bit higher because it processes newest tickets to oldest debug-only: ${{ github.event.inputs.debug-only || env.DEFAULT_DEBUG_ONLY }} diff --git a/.github/workflows/triage_add_to_project.yml b/.github/workflows/triage_add_to_project.yml index 0b1b5f16f0..cceead8133 100644 --- a/.github/workflows/triage_add_to_project.yml +++ b/.github/workflows/triage_add_to_project.yml @@ -35,7 +35,7 @@ jobs: echo 'IS_COLLABORATOR='$(jq -r '.data.repository.collaborators.totalCount' collaborators.json) >> $GITHUB_ENV - uses: actions/add-to-project@v0.4.0 # only add issues/prs from outside contributors to the project - if: ${{ env.IS_COLLABORATOR == 0 }} + if: ${{ env.IS_COLLABORATOR == 0 || github.event.repository.name == 'cypress-support-internal' || github.event.pull_request.user.login == 'github-actions[bot]' || github.event.issue.user.login == 'github-actions[bot]' }} with: project-url: https://github.com/orgs/${{github.repository_owner}}/projects/${{env.PROJECT_NUMBER}} github-token: ${{ secrets.ADD_TO_TRIAGE_BOARD_TOKEN }} diff --git a/.github/workflows/update_v8_snapshot_cache.yml b/.github/workflows/update_v8_snapshot_cache.yml index 60586ac441..61f06d52a3 100644 --- a/.github/workflows/update_v8_snapshot_cache.yml +++ b/.github/workflows/update_v8_snapshot_cache.yml @@ -30,7 +30,7 @@ jobs: strategy: max-parallel: 1 matrix: - platform: [ubuntu-latest, windows-latest, macos-latest] + platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} env: CYPRESS_BOT_APP_ID: ${{ secrets.RAM_APP }} diff --git a/.vscode/cspell.json b/.vscode/cspell.json index fd5df5d95b..bc6c6cdb3f 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -59,8 +59,7 @@ "viewports", "vite", "vitejs", - "vueuse", - "Windi" + "vueuse" ], "ignoreWords": [], "import": [] diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 845b2fc556..c28ee62bcb 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -22,9 +22,9 @@ // Description: Adds syntax highlighting for all gql tags. "apollographql.vscode-apollo", - // Name: WindiCSS Intellisense - // Description: Automatically sorts your WindiCSS classes. - "voorjaar.windicss-intellisense", + // Name: TailwindCSS Intellisense + // Description: Automatically sorts your TailwindCSS classes. + "bradlc.vscode-tailwindcss", // Name: Volar // Description: Language server for Vue. Required for any syntax highlighting in Vue files. diff --git a/.vscode/settings.json b/.vscode/settings.json index 47436c0f11..4b221598aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,9 +23,6 @@ }, "typescript.tsdk": "node_modules/typescript/lib", - // A flag that controls whether or not Windi CSS classes will be sorted on save on save. - "windicss.sortOnSave": true, - // Support autocompletion and preview of strings. // Additionally, support extraction of hardcoded strings into key-values. "i18n-ally.localesPaths": "packages/frontend-shared/src/locales", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eee1fe15ab..fedcaa775f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ Thanks for taking the time to contribute! :smile: ## Code of Conduct -All contributors are expecting to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). +All contributors are expected to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). ## Opening Issues @@ -112,7 +112,7 @@ video | Problems with video recordings | [open](https://github.com/cypress-io/cy ## Writing Documentation - + Cypress documentation lives in a separate repository with its own dependencies and build tools. See [Documentation Contributing Guidelines](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md). @@ -155,7 +155,7 @@ Here is a list of the core packages in this repository with a short description, | [proxy](./packages/proxy) | `@packages/proxy` | Code for Cypress' network proxy layer. | | [reporter](./packages/reporter) | `@packages/reporter` | The reporter shows the running results of the tests (The Command Log UI). | | [resolve-dist](./packages/resolve-dist) | `@packages/resolve-dist` | Centralizes the resolution of paths to compiled/static assets from server-side code.. | - | [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy. + | [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy. | [root](./packages/root) | `@packages/root` | Dummy package pointing at the root of the repository. | | [runner](./packages/runner) | `@packages/runner` | (deprecated) The runner is the minimal "chrome" around the user's application under test. | | [scaffold-config](./packages/scaffold-config) | `@packages/scaffold-config` | The logic related to scaffolding new projects using launchpad. | @@ -318,13 +318,13 @@ Each package is responsible for building itself and testing itself and can do so When executing top or package level scripts, [Vite](https://vitejs.dev/) may be used to build/host parts of the application. This section is to serve as a general reference for these environment variables that may be leverage throughout the repository. ###### `CYPRESS_INTERNAL_VITE_DEV` Set to `1` if wanting to leverage [vite's](https://vitejs.dev/guide/#command-line-interface) `vite dev` over `vite build` to avoid a full [production build](https://vitejs.dev/guide/build.html). -###### `CYPRESS_INTERNAL_VITE_INSPECT` +###### `CYPRESS_INTERNAL_VITE_INSPECT` Used internally to leverage [vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect) to view intermediary vite plugin state. The `CYPRESS_INTERNAL_VITE_DEV` is required for this to be applied correctly. Set to `1` to enable. -###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING` +###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING` Leveraged only for internal cy-in-cy type tests to access the Cypress instance from the parent frame. Please see the [E2E Open Mode Testing](./guides/e2e-open-testing.md) Guide. Set to `true` when doing -###### `CYPRESS_INTERNAL_VITE_APP_PORT` +###### `CYPRESS_INTERNAL_VITE_APP_PORT` Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the app on the specified port. The default port is `3333`. -###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT` +###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT` Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the launchpad on the specified port. The default port is `3001`. #### Debug Logs @@ -443,7 +443,7 @@ We do not continuously deploy the Cypress binary, so `develop` contains all of t - After the PR is approved, the original contributor can merge the PR (if the original contributor has access). - When you merge a PR into `develop`, select [**Squash and merge**](https://docs.github.com/en/github/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits). This will squash all commits into a single commit. -*The only exceptions to squashing are:* +*The only exceptions to squashing are:* 1. When converting files to another language and there is a clear commit history needed to maintain from the file conversion. 2. When merging a `release/*` branch to `develop`. Individual PRs were already squashed when they were merged to the release branch, and we want that history intact on develop. diff --git a/browser-versions.json b/browser-versions.json index 6f10d1c49c..2da445bd2b 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,5 +1,5 @@ { - "chrome:beta": "112.0.5615.49", - "chrome:stable": "111.0.5563.146", + "chrome:beta": "113.0.5672.24", + "chrome:stable": "112.0.5615.49", "chrome:minimum": "64.0.3282.0" } diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 8a8e2707ff..699cc15674 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -7,22 +7,64 @@ _Released 03/1/2023 (PENDING)_ - The [`cy.readFile()`](/api/commands/readfile) command is now retry-able as a [query command](https://on.cypress.io/retry-ability). This should not affect any tests using it; the functionality is unchanged. However, it can no longer be overwritten using [`Cypress.Commands.overwrite()`](/api/cypress-api/custom-commands#Overwrite-Existing-Commands). Addressed in [#25595](https://github.com/cypress-io/cypress/pull/25595). -## 12.9.1 +## 12.11.1 -_Released 04/11/2023 (PENDING)_ +_Released 05/09/2023 (PENDING)_ + +**Bugfixes:** + +- Fixed an issue in Electron where devtools gets out of sync with the DOM occasionally. Addresses [#15932](https://github.com/cypress-io/cypress/issues/15932). +- Updated the Chromium renderer process crash message to be more terse. Addressed in [#26597](https://github.com/cypress-io/cypress/pull/26597). + +## 12.11.0 + +_Released 04/26/2023_ + +**Features:** + +- Adds Component Testing support for Angular 16. Addresses [#26044](https://github.com/cypress-io/cypress/issues/26044). +- The run navigation component on the [Debug page](https://on.cypress.io/debug-page) will now display a warning message if there are more relevant runs than can be displayed in the list. Addresses [#26288](https://github.com/cypress-io/cypress/issues/26288). + +**Bugfixes:** + +- Fixed an issue where setting `videoCompression` to `0` would cause the video output to be broken. `0` is now treated as false. Addresses [#5191](https://github.com/cypress-io/cypress/issues/5191) and [#24595](https://github.com/cypress-io/cypress/issues/24595). +- Fixed an issue on the [Debug page](https://on.cypress.io/debug-page) where the passing run status would appear even if the Cypress Cloud organization was over its monthly test result limit. Addresses [#26528](https://github.com/cypress-io/cypress/issues/26528). + +**Misc:** + +- Cleaned up our open telemetry dependencies, reducing the size of the open telemetry modules. Addressed in [#26522](https://github.com/cypress-io/cypress/pull/26522). + +**Dependency Updates:** + +- Upgraded [`vue`](https://www.npmjs.com/package/vue) from `3.2.31` to `3.2.47`. Addressed in [#26555](https://github.com/cypress-io/cypress/pull/26555). + +## 12.10.0 + +_Released 04/17/2023_ + +**Features:** + +- The Component Testing setup wizard will now show a warning message if an issue is encountered with an installed [third party framework definition](https://on.cypress.io/component-integrations). Addresses [#25838](https://github.com/cypress-io/cypress/issues/25838). **Bugfixes:** - Capture the [Azure](https://azure.microsoft.com/) CI provider's environment variable [`SYSTEM_PULLREQUEST_PULLREQUESTNUMBER`](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services) to display the linked PR number in the Cloud. Addressed in [#26215](https://github.com/cypress-io/cypress/pull/26215). - - Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777). + - Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777) and [#26388](https://github.com/cypress-io/cypress/issues/26388). + - Updated to use the `SEMAPHORE_GIT_WORKING_BRANCH` [Semphore](https://docs.semaphoreci.com) CI environment variable to correctly associate a Cloud run to the current branch. Previously this was incorrectly associating a run to the target branch. Fixes [#26309](https://github.com/cypress-io/cypress/issues/26309). + - Fix an edge case in Component Testing where a custom `baseUrl` in `tsconfig.json` for Next.js 13.2.0+ is not respected. This was partially fixed in [#26005](https://github.com/cypress-io/cypress/pull/26005), but an edge case was missed. Fixes [#25951](https://github.com/cypress-io/cypress/issues/25951). + - Correctly detect and resolve dependencies when configuring Component Testing in projects using Yarn's [Plug'n'Play feature](https://yarnpkg.com/features/pnp). Fixes [#25960](https://github.com/cypress-io/cypress/issues/25960). + - Fixed an issue where `click` events fired on `.type('{enter}')` did not propagate through shadow roots. Fixes [#26392](https://github.com/cypress-io/cypress/issues/26392). + +**Misc:** + +- Removed unintentional debug logs. Addressed in [#26411](https://github.com/cypress-io/cypress/pull/26411). +- Improved styling on the [Runs Page](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs). Addresses [#26180](https://github.com/cypress-io/cypress/issues/26180). **Dependency Updates:** +- Upgraded [`commander`](https://www.npmjs.com/package/commander) from `^5.1.0` to `^6.2.1`. Addressed in [#26226](https://github.com/cypress-io/cypress/pull/26226). - Upgraded [`minimist`](https://www.npmjs.com/package/minimist) from `1.2.6` to `1.2.8` to address this [CVE-2021-44906](https://github.com/advisories/GHSA-xvch-5gv4-984h) NVD security vulnerability. Addressed in [#26254](https://github.com/cypress-io/cypress/pull/26254). -**Misc:** -- Removed unintentional debug logs. Address in [#26411](https://github.com/cypress-io/cypress/pull/26411) - ## 12.9.0 _Released 03/28/2023_ diff --git a/cli/__snapshots__/cli_spec.js b/cli/__snapshots__/cli_spec.js index 562cbeb7fd..36d6ee4f98 100644 --- a/cli/__snapshots__/cli_spec.js +++ b/cli/__snapshots__/cli_spec.js @@ -218,7 +218,7 @@ exports['cli help command shows help 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -258,7 +258,7 @@ exports['cli help command shows help for -h 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -298,7 +298,7 @@ exports['cli help command shows help for --help 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -339,7 +339,7 @@ exports['cli unknown command shows usage and exits 1'] = ` Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. @@ -408,20 +408,6 @@ Electron version: not found Bundled Node version: not found ` -exports['cli --version no binary version 1'] = ` -Cypress package version: 1.2.3 -Cypress binary version: not installed -Electron version: not found -Bundled Node version: not found -` - -exports['cli -v no binary version 1'] = ` -Cypress package version: 1.2.3 -Cypress binary version: not installed -Electron version: not found -Bundled Node version: not found -` - exports['cli cypress run warns with space-separated --spec 1'] = ` โš  Warning: It looks like you're passing --spec a space-separated list of arguments: @@ -466,7 +452,7 @@ exports['cli CYPRESS_INTERNAL_ENV allows and warns when staging environment 1'] Commands: help Shows CLI help and exits - version prints Cypress version + version [options] prints Cypress version open [options] Opens Cypress in the interactive GUI. run [options] Runs Cypress tests from the CLI without the GUI open-ct [options] Opens Cypress component testing interactive mode. diff --git a/cli/lib/cli.js b/cli/lib/cli.js index 04599c93fc..248a7d72a1 100644 --- a/cli/lib/cli.js +++ b/cli/lib/cli.js @@ -154,26 +154,16 @@ const text = (description) => { function includesVersion (args) { return ( - _.includes(args, 'version') || _.includes(args, '--version') || _.includes(args, '-v') ) } -function showVersions (args) { +function showVersions (opts) { debug('printing Cypress version') - debug('additional arguments %o', args) + debug('additional arguments %o', opts) - const versionParser = commander.option( - '--component ', 'component to report version for', - ) - .allowUnknownOption(true) - const parsed = versionParser.parse(args) - const parsedOptions = { - component: parsed.component, - } - - debug('parsed version arguments %o', parsedOptions) + debug('parsed version arguments %o', opts) const reportAllVersions = (versions) => { logger.always('Cypress package version:', versions.package) @@ -215,8 +205,8 @@ function showVersions (args) { return require('./exec/versions') .getVersions() .then((versions = defaultVersions) => { - if (parsedOptions.component) { - reportComponentVersion(parsedOptions.component, versions) + if (opts?.component) { + reportComponentVersion(opts.component, versions) } else { reportAllVersions(versions) } @@ -456,13 +446,19 @@ module.exports = { program.help() }) - program + const handleVersion = (cmd) => { + return cmd + .option('--component ', 'component to report version for') + .action((opts, ...other) => { + showVersions(util.parseOpts(opts)) + }) + } + + handleVersion(program + .storeOptionsAsProperties() .option('-v, --version', text('version')) .command('version') - .description(text('version')) - .action(() => { - showVersions(args) - }) + .description(text('version'))) maybeAddInspectFlags(addCypressOpenCommand(program)) .action((opts) => { @@ -665,7 +661,7 @@ module.exports = { // and now does not understand top level options // .option('-v, --version').command('version') // so we have to manually catch '-v, --version' - return showVersions(args) + handleVersion(program) } debug('program parsing arguments') diff --git a/cli/package.json b/cli/package.json index f5dec38660..ec12f4cbd1 100644 --- a/cli/package.json +++ b/cli/package.json @@ -34,7 +34,7 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", @@ -89,7 +89,6 @@ "mock-fs": "5.1.1", "mocked-env": "1.3.2", "nock": "13.2.9", - "postinstall-postinstall": "2.1.0", "proxyquire": "2.1.3", "resolve-pkg": "2.0.0", "shelljs": "0.8.5", diff --git a/cli/test/lib/cli_spec.js b/cli/test/lib/cli_spec.js index bf6ebf97cb..e688125f26 100644 --- a/cli/test/lib/cli_spec.js +++ b/cli/test/lib/cli_spec.js @@ -23,11 +23,11 @@ describe('cli', () => { beforeEach(() => { logger.reset() - sinon.stub(process, 'exit') + sinon.stub(process, 'exit').returns(null) os.platform.returns('darwin') - // sinon.stub(util, 'exit') - sinon.stub(util, 'logErrorExit1') + sinon.stub(util, 'logErrorExit1').returns(null) + sinon.stub(util, 'pkgBuildInfo').returns({ stable: true }) this.exec = (args) => { const cliArgs = `node test ${args}`.split(' ') @@ -136,189 +136,169 @@ describe('cli', () => { }) }) - context('cypress version', () => { - let restoreEnv + ;['--version', '-v', 'version'].forEach((versionCommand) => { + context(`cypress ${versionCommand}`, () => { + let restoreEnv - afterEach(() => { - if (restoreEnv) { - restoreEnv() - restoreEnv = null - } - }) + afterEach(() => { + if (restoreEnv) { + restoreEnv() + restoreEnv = null + } + }) - const binaryDir = '/binary/dir' + const binaryDir = '/binary/dir' - beforeEach(() => { - sinon.stub(state, 'getBinaryDir').returns(binaryDir) - }) - - describe('individual package versions', () => { beforeEach(() => { + sinon.stub(state, 'getBinaryDir').returns(binaryDir) + }) + + describe('individual package versions', () => { + beforeEach(() => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon + .stub(state, 'getBinaryPkgAsync') + .withArgs(binaryDir) + .resolves({ + version: 'X.Y.Z', + electronVersion: '10.9.8', + electronNodeVersion: '7.7.7', + }) + }) + + it('reports just the package version', (done) => { + this.exec(`${versionCommand} --component package`) + process.exit.callsFake((exitCode) => { + expect(logger.print()).to.equal('1.2.3') + done() + }) + }) + + it('reports just the binary version', (done) => { + this.exec(`${versionCommand} --component binary`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('X.Y.Z') + done() + }) + }) + + it('reports just the electron version', (done) => { + this.exec(`${versionCommand} --component electron`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('10.9.8') + done() + }) + }) + + it('reports just the bundled Node version', (done) => { + this.exec(`${versionCommand} --component node`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('7.7.7') + done() + }) + }) + + it('handles not found bundled Node version', (done) => { + state.getBinaryPkgAsync + .withArgs(binaryDir) + .resolves({ + version: 'X.Y.Z', + electronVersion: '10.9.8', + }) + + this.exec(`${versionCommand} --component node`) + process.exit.callsFake(() => { + expect(logger.print()).to.equal('not found') + done() + }) + }) + }) + + it('reports package version', (done) => { sinon.stub(util, 'pkgVersion').returns('1.2.3') sinon .stub(state, 'getBinaryPkgAsync') .withArgs(binaryDir) .resolves({ version: 'X.Y.Z', - electronVersion: '10.9.8', - electronNodeVersion: '7.7.7', }) - }) - it('reports just the package version', (done) => { - this.exec('version --component package') + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('1.2.3') + snapshot('cli version and binary version 1', logger.print(), { allowSharedSnapshot: true }) done() }) }) - it('reports just the binary version', (done) => { - this.exec('version --component binary') + it('reports package and binary message', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('X.Y.Z') + snapshot('cli version and binary version 2', logger.print(), { allowSharedSnapshot: true }) done() }) }) - it('reports just the electron version', (done) => { - this.exec('version --component electron') - process.exit.callsFake(() => { - expect(logger.print()).to.equal('10.9.8') - done() - }) - }) - - it('reports just the bundled Node version', (done) => { - this.exec('version --component node') - process.exit.callsFake(() => { - expect(logger.print()).to.equal('7.7.7') - done() - }) - }) - - it('handles not found bundled Node version', (done) => { - state.getBinaryPkgAsync - .withArgs(binaryDir) - .resolves({ + it('reports electron and node message', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z', - electronVersion: '10.9.8', + electronVersion: '10.10.88', + electronNodeVersion: '11.10.3', }) - this.exec('version --component node') + this.exec(versionCommand) process.exit.callsFake(() => { - expect(logger.print()).to.equal('not found') + snapshot('cli version with electron and node 1', logger.print(), { allowSharedSnapshot: true }) done() }) }) - }) - it('reports package version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon - .stub(state, 'getBinaryPkgAsync') - .withArgs(binaryDir) - .resolves({ - version: 'X.Y.Z', + it('reports package and binary message with npm log silent', (done) => { + restoreEnv = mockedEnv({ + npm_config_loglevel: 'silent', + }) + + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + + this.exec(versionCommand) + process.exit.callsFake(() => { + // should not be empty! + snapshot('cli version and binary version with npm log silent', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version and binary version 1', logger.print()) - done() - }) - }) + it('reports package and binary message with npm log warn', (done) => { + restoreEnv = mockedEnv({ + npm_config_loglevel: 'warn', + }) - it('reports package and binary message', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves({ + version: 'X.Y.Z', + }) - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version and binary version 2', logger.print()) - done() - }) - }) - - it('reports electron and node message', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ - version: 'X.Y.Z', - electronVersion: '10.10.88', - electronNodeVersion: '11.10.3', - }) - - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version with electron and node 1', logger.print()) - done() - }) - }) - - it('reports package and binary message with npm log silent', (done) => { - restoreEnv = mockedEnv({ - npm_config_loglevel: 'silent', - }) - - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' }) - - this.exec('version') - process.exit.callsFake(() => { + this.exec(versionCommand) + process.exit.callsFake(() => { // should not be empty! - snapshot('cli version and binary version with npm log silent', logger.print()) - done() - }) - }) - - it('reports package and binary message with npm log warn', (done) => { - restoreEnv = mockedEnv({ - npm_config_loglevel: 'warn', + snapshot('cli version and binary version with npm log warn', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves({ - version: 'X.Y.Z', - }) + it('handles non-existent binary', (done) => { + sinon.stub(util, 'pkgVersion').returns('1.2.3') + sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - this.exec('version') - process.exit.callsFake(() => { - // should not be empty! - snapshot('cli version and binary version with npm log warn', logger.print()) - done() - }) - }) - - it('handles non-existent binary version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('version') - process.exit.callsFake(() => { - snapshot('cli version no binary version 1', logger.print()) - done() - }) - }) - - it('handles non-existent binary --version', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('--version') - process.exit.callsFake(() => { - snapshot('cli --version no binary version 1', logger.print()) - done() - }) - }) - - it('handles non-existent binary -v', (done) => { - sinon.stub(util, 'pkgVersion').returns('1.2.3') - sinon.stub(state, 'getBinaryPkgAsync').resolves(null) - - this.exec('-v') - process.exit.callsFake(() => { - snapshot('cli -v no binary version 1', logger.print()) - done() + this.exec(versionCommand) + process.exit.callsFake(() => { + snapshot('cli version no binary version 1', logger.print(), { allowSharedSnapshot: true }) + done() + }) }) }) }) diff --git a/npm/create-cypress-tests/CHANGELOG.md b/npm/create-cypress-tests/CHANGELOG.md index db00f727d0..3b2f80df83 100644 --- a/npm/create-cypress-tests/CHANGELOG.md +++ b/npm/create-cypress-tests/CHANGELOG.md @@ -1,3 +1,5 @@ +# [create-cypress-tests-v2.0.2](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.1...create-cypress-tests-v2.0.2) (2023-04-07) + # [create-cypress-tests-v2.0.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.0...create-cypress-tests-v2.0.1) (2023-01-03) diff --git a/npm/create-cypress-tests/package.json b/npm/create-cypress-tests/package.json index 969dff7c97..e52091e85c 100644 --- a/npm/create-cypress-tests/package.json +++ b/npm/create-cypress-tests/package.json @@ -20,7 +20,7 @@ "bluebird": "3.7.2", "chalk": "4.1.0", "cli-highlight": "2.1.10", - "commander": "6.1.0", + "commander": "6.2.1", "fast-glob": "3.2.7", "find-up": "5.0.0", "fs-extra": "^9.1.0", diff --git a/npm/grep/README.md b/npm/grep/README.md index 4675a83c88..b9bdd13ad0 100644 --- a/npm/grep/README.md +++ b/npm/grep/README.md @@ -19,8 +19,6 @@ All other tests will be marked pending, see why in the [Cypress test statuses](h If you have multiple spec files, all specs will be loaded, and every test will be filtered the same way, since the grep is run-time operation and cannot eliminate the spec files without loading them. If you want to run only specific tests, use the built-in [--spec](https://on.cypress.io/command-line#cypress-run-spec-lt-spec-gt) CLI argument. -Watch the video [intro to @cypress/grep plugin](https://www.youtube.com/watch?v=HS-Px-Sghd8) - Table of Contents @@ -602,4 +600,4 @@ Version >= 3 of @cypress/grep _only_ supports Cypress >= 10. License: MIT - do anything with the code, but don't blame me if it does not work. Support: if you find any problems with this module, email / tweet / -[open issue](https://github.com/cypress-io/cypress/issues) on Github. \ No newline at end of file +[open issue](https://github.com/cypress-io/cypress/issues) on Github. diff --git a/npm/react/package.json b/npm/react/package.json index 6dfb00125b..a9da164657 100644 --- a/npm/react/package.json +++ b/npm/react/package.json @@ -18,7 +18,7 @@ "devDependencies": { "@cypress/mount-utils": "0.0.0-development", "@types/semver": "7.3.9", - "@vitejs/plugin-react": "1.3.1", + "@vitejs/plugin-react": "4.0.0", "axios": "0.21.2", "cypress": "0.0.0-development", "prop-types": "15.7.2", @@ -28,8 +28,8 @@ "react-router-dom": "6.0.0-alpha.1", "semver": "^7.3.2", "typescript": "^4.7.4", - "vite": "4.1.4", - "vite-plugin-require-transform": "1.0.3" + "vite": "4.3.2", + "vite-plugin-require-transform": "1.0.12" }, "peerDependencies": { "@types/react": "^16.9.16 || ^17.0.0", diff --git a/npm/vite-dev-server/package.json b/npm/vite-dev-server/package.json index af0f037517..94ccb532fe 100644 --- a/npm/vite-dev-server/package.json +++ b/npm/vite-dev-server/package.json @@ -27,8 +27,8 @@ "mocha": "^9.2.2", "sinon": "^13.0.1", "ts-node": "^10.9.1", - "vite": "4.1.4", - "vite-plugin-inspect": "0.4.3" + "vite": "4.3.2", + "vite-plugin-inspect": "0.7.24" }, "files": [ "dist", diff --git a/npm/vite-dev-server/src/resolveConfig.ts b/npm/vite-dev-server/src/resolveConfig.ts index 57296669ea..51e86f655f 100644 --- a/npm/vite-dev-server/src/resolveConfig.ts +++ b/npm/vite-dev-server/src/resolveConfig.ts @@ -80,7 +80,11 @@ function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): Inline paths: [projectRoot], }))) - const viteConfig: InlineConfig = { + // Our Vite typings do not have the 'incremental' field since it was removed in 4.2, but users' version + // of Vite may be older and we want to use it if it's there + type Vite_4_1_Config = { optimizeDeps: { esbuildOptions: { incremental?: boolean } } } + + const viteConfig: InlineConfig & Vite_4_1_Config = { root: projectRoot, base: `${devServerPublicPathRoute}/`, optimizeDeps: { diff --git a/npm/vite-plugin-cypress-esm/.eslintignore b/npm/vite-plugin-cypress-esm/.eslintignore new file mode 100644 index 0000000000..79afe972da --- /dev/null +++ b/npm/vite-plugin-cypress-esm/.eslintignore @@ -0,0 +1,5 @@ +**/dist +**/*.d.ts +**/package-lock.json +**/tsconfig.json +**/cypress/fixtures \ No newline at end of file diff --git a/npm/vite-plugin-cypress-esm/.eslintrc b/npm/vite-plugin-cypress-esm/.eslintrc new file mode 100644 index 0000000000..7158598773 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/.eslintrc @@ -0,0 +1,32 @@ +{ + "plugins": [ + "cypress", + "@cypress/dev" + ], + "extends": [ + "plugin:@cypress/dev/general", + "plugin:@cypress/dev/tests", + "plugin:@cypress/dev/react" + ], + "parser": "vue-eslint-parser", + "parserOptions": { + "parser": "@typescript-eslint/parser" + }, + "env": { + "cypress/globals": true + }, + "rules": { + "no-console": "off", + "mocha/no-global-tests": "off", + "react/jsx-filename-extension": [ + "warn", + { + "extensions": [ + ".js", + ".jsx", + ".tsx" + ] + } + ] + } +} diff --git a/npm/vite-plugin-cypress-esm/.gitignore b/npm/vite-plugin-cypress-esm/.gitignore new file mode 100644 index 0000000000..4068db97ef --- /dev/null +++ b/npm/vite-plugin-cypress-esm/.gitignore @@ -0,0 +1 @@ +cypress/videos/* \ No newline at end of file diff --git a/npm/vite-plugin-cypress-esm/CHANGELOG.md b/npm/vite-plugin-cypress-esm/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/npm/vite-plugin-cypress-esm/README.md b/npm/vite-plugin-cypress-esm/README.md new file mode 100644 index 0000000000..89606956ff --- /dev/null +++ b/npm/vite-plugin-cypress-esm/README.md @@ -0,0 +1,123 @@ +# @cypress/vite-plugin-cypress-esm + +A Vite plugin that intercepts and rewrites ES module imports within [Cypress component tests](https://docs.cypress.io/guides/component-testing/overview). The [ESM specification](https://tc39.es/ecma262/#sec-modules) generates modules that are "sealed", requiring the runtime (the browser) to prevent any alteration to the module namespace. While this has security and performance benefits, it prevents use of mocking libraries which would need to replace namespace members. This plugin wraps modules in a special [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) implementation, allowing for instrumentation by libraries such as Sinon. + +> **Note:** This package is a pre-release alpha and is not yet stable. There are likely to be bugs and edge cases. Please report any bugs [here](https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm). [Learn more about Cypress release stages](https://docs.cypress.io/guides/references/release-stages#Alpha) and expectations around stability. + +## Debugging + +Run Cypress with `DEBUG=cypress:vite-plugin-cypress-esm`. You will get logs in the terminal, for the code transformation, and in the browser console, for intercepting and wrapping the modules in a Proxy. +## Compatibility + +| @cypress/vite-plugin-mock-esm | cypress | +| ------------------------ | ------- | +| >= v1 | >= v12 | + +## Usage + +This plugin rewrites the ES modules served by Vite to make them mutable and therefore compatible with methods like [`cy.spy()`](https://docs.cypress.io/api/commands/spy) and [`cy.stub()`](https://docs.cypress.io/api/commands/stub) that require modifying otherwise-sealed objects. Since this is a testing-specific plugin it is recommended to apply it your Vite config only when running your Cypress tests. One way to do so would be in `cypress.config`: + +```ts +import { defineConfig } from 'cypress' +import viteConfig from './vite.config' +import { mergeConfig } from 'vite' +import { CypressEsm } from '@cypress/vite-plugin-cypress-esm' + +export default defineConfig({ + component: { + devServer: { + bundler: 'vite', + framework: 'react', + viteConfig: () => { + return mergeConfig( + viteConfig, + { + plugins: [ + CypressEsm(), + ] + } + ), + } + }, + } +}) +``` + +### `ignoreList` + +Some modules may be incompatible with Proxy-based implementation. The eventual goal is to support wrapping all modules in a Proxy to better facilitate testing. For now, if you run into any issues with a particular module, you can add it to the `ignoreList` like so: + +```ts +CypressEsm({ + ignoreList: ['react-router', 'react-router-dom'] +}) +``` + +You can also use a glob, which uses [`picomatch`](https://github.com/micromatch/picomatch) internally: + +```ts +CypressEsm({ + ignoreList: ['*react*'] +}) +``` + +React is known to have some conflicts with the Proxy implementation that cause problems stubbing internal React functionality. Since it is unlikely you want to stub parts of React itself, it's a good idea to add it to the `ignoreList`. + +## Known Issues + +### Import Syntax + +All known [import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) is supported, however there may edge cases that have not been identified. + +### Regular Expression matching + +This module uses Regular Expression matching to transform the modules on the server to facilitate wrapping them in a `Proxy` on the client. In future updates, a more robust AST-based approach will be explored. A limitation of the current approach is that it does not recognize syntax from actual code vs content found within strings (for instance, an error string that contains example code syntax). This can result in inappropriately modified string constants. + +### Auto-hosting + +ESM imports are automatically hoisted to the top of a given module so they happen first before any code that references them. This plugin does not currently perform any hoisting, so imports are transformed to variable references in place. If you have code that attempts to reference an imported value prior to that import it will likely break. This is a known issue with HMR logic in Svelte projects, and will typically present as a "use before define" error. + +### Self-references and internal calls + +This plugin works by intercepting calls coming *in* to a module. This will not work for situations where a module attempts to make *internal* calls to a function within the same module or directly compare against a function within the same module. Eg: + +```js +// mod_1.js +export function foo () { + // ... +} + +export function bar (mod) { + return mod === foo +} + +// mod_2.js +import { foo, bar } from './mod_1.js' + +bar(foo) //=> false +``` + +In this example, `bar(foo)` is passing a reference to `mod_1.foo`, where `mod_1` is a module wrapped in a `Proxy`. In the original `mod_1.js`, the reference to `foo` is the original, unwrapped `foo`, so the comparison return `false`. This may cause issues in some libraries, such as React Router when lazy loading routes. You can add modules to `ignoreList` to work around this issue. + +### Sinon compatibility + +This plugin is designed to work with [Sinon](https://sinonjs.org/) since that is what Cypress uses internally for `cy.stub` and `cy.spy` - attempting to utilize other stubbing/mocking libraries or directly mutating modules is not a supported use case and will likely not work as expected. + +## Troubleshooting + +This is an **_Alpha_** release, meaning there a very likely bugs in the implementation and it is expected that you will encounter issues. We appreciate any bug reports once you have performed the troubleshooting process below. + +If you encounter issues: +1. Ensure you're using the very latest version of this Plugin and Cypress +2. Try temporarily removing this plugin from your test's Vite config - if the issue is still present then it is not related to this plugin. +3. Verify you have not encountered one of the [Known Issues](#known-issues) +3. If the issue disappeared then try narrowing down if it's related to a specific module/dependency by using the `ignoreList` config +4. If your problem isn't related to a specific dependency and can't be isolated please file a bug report [here](https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm). A reproduction case project is extremely helpful to track down specific issues, and capturing [Debug Logs](#debugging) from both your terminal *and* the browser devtools console is very helpful. + +## License + +[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/cypress-io/cypress/blob/develop/LICENSE) + +This project is licensed under the terms of the [MIT license](/LICENSE). + +## [Changelog](./CHANGELOG.md) diff --git a/npm/vite-plugin-cypress-esm/client/moduleCache.js b/npm/vite-plugin-cypress-esm/client/moduleCache.js new file mode 100644 index 0000000000..d7ec78f120 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/client/moduleCache.js @@ -0,0 +1,191 @@ +const __cypressModuleCache = new Map() + +const NO_REDEFINE_LIST = new Set(['prototype']) + +let debug = false + +function createProxyModule (module) { + // What we build our module proxy off of depends on whether the module has a default export + // We need to be able to support `import DefaultValue from 'module'` => `const DefaultValue = __cypressModule(module)` + const base = module.default || module + let target + + // Work around for the fact that a module with a default export needs to work the same way via object destructuring + // for this module remapping concept to work + // ``` + // import TheDefault from 'module' + // `TheDefault` could be an object or a function + // ``` + if (typeof base === 'function') { + target = function (...params) { + if (typeof target.default === 'function') { + return target.default.apply(this, params) + } + + if (typeof module === 'function') { + return module.apply(this, params) + } + } + } else { + target = {} + } + + const proxies = {} + + function redefinePropertyDescriptors (module, overrides) { + Object.entries(Object.getOwnPropertyDescriptors(module)).forEach(([key, descriptor]) => { + if (Array.isArray(module)) { + return + } + + if (NO_REDEFINE_LIST.has(key)) { + log(`โญ๏ธ Skipping ${key}`) + + return + } + + log(`๐Ÿงช Redefining ${key}`) + + Object.defineProperty(target, key, { + ...descriptor, + ...overrides, + }) + + if (typeof descriptor.value === 'function') { + // This is how you can see if something is a class + // Playground: https://regex101.com/r/OS2Iyg/1 + // Important! RegEx instances are stateful, do not extract to a constant + const isClass = /class.+?\{.+?\}/gms.test(descriptor.value.toString()) + + if (isClass) { + log(`๐Ÿ—๏ธ Handling ${key} as a constructor`) + + proxies[key] = function (...params) { + // Edge case - use `apply` with `new` to create a class instance + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct + return Reflect.construct(target[key], params) + } + } else { + log(`๐ŸŽ Handling ${key} with a standard wrapper function`) + + proxies[key] = function (...params) { + return target[key].apply(this, params) + } + } + + proxies[key].prototype = target[key].prototype + } + }) + } + + // Do not proxify arrays - you can't spy on an array, no need. + if (Array.isArray(module.default)) { + return module.default + } + + if (module.default && typeof module.default !== 'function') { + redefinePropertyDescriptors(module.default, { + writable: true, + enumerable: true, + }) + } + + redefinePropertyDescriptors(module, { + configurable: true, + writable: true, + }) + + const moduleProxy = new Proxy(target, { + get (_, prop, receiver) { + const value = target[prop] + + if (typeof value === 'function') { + // Check to see if this retrieval is coming from a sinon `spy` creation + // If so, we want to supply the 'true' function rather than our proxied version + // so the spy can call through to the real implementation + const stack = new Error().stack + + if (stack?.includes('Sandbox.spy')) { + log(`๐Ÿ•ต๏ธ Detected ${prop} is being defined as a Sinon spy`) + + return value + } + + // Otherwise, return our proxied function implementation + return proxies[prop] + } + + return target[prop] + }, + set (obj, prop, value) { + target[prop] = value + + if (typeof value === 'function' && !(prop in proxies)) { + proxies[prop] = function (...params) { + return target[prop].apply(this, params) + } + } + + return true + }, + defineProperty (_, key, descriptor) { + // Ignore `define` attempts to set a sinon proxy, but return true anyways + // Allowing define would blow away our function proxy + // Sinon circles back and attempts to set via `set` anyways so this isn't necessary + if (descriptor.value?.isSinonProxy) { + return true + } + + Object.defineProperty(target, key, { ...descriptor, writable: true, configurable: true }) + + return true + }, + deleteProperty (_, prop) { + // Don't allow deletion - Sinon tries to delete things as a cleanup activity which breaks our proxied functions + + return true + }, + }) + + return moduleProxy +} + +function log (msg) { + if (!debug) { + return + } + + console.log(`[cypress:vite-plugin-mock-esm]: ${msg}`) +} + +function cacheAndProxifyModule (id, module) { + if (__cypressModuleCache.has(module)) { + return __cypressModuleCache.get(module) + } + + log(`๐Ÿ”จ creating proxy module for ${id}`) + + const moduleProxy = createProxyModule(module) + + log(`โœ… created proxy module for ${id}`) + + __cypressModuleCache.set(module, moduleProxy) + + log(`๐Ÿ“ˆ Module cache now contains ${__cypressModuleCache.size} entries`) + + return moduleProxy +} + +window.__cypressDynamicModule = function (id, importPromise, _debug = false) { + debug = _debug + + return Promise.resolve(importPromise.then((module) => { + return cacheAndProxifyModule(id, module) + })) +} + +window.__cypressModule = function (id, module, _debug = false) { + debug = _debug + + return cacheAndProxifyModule(id, module) +} diff --git a/npm/vite-plugin-cypress-esm/cypress.config.ts b/npm/vite-plugin-cypress-esm/cypress.config.ts new file mode 100644 index 0000000000..6d9e5ac8e7 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'cypress' +import react from '@vitejs/plugin-react' +import { CypressEsm } from './src' + +export default defineConfig({ + projectId: 'ypt4pf', + component: { + supportFile: false, + specPattern: 'cypress/component/**/*.cy.ts*', + devServer: { + bundler: 'vite', + framework: 'react', + viteConfig: () => { + return { + plugins: [ + react({ + jsxRuntime: 'classic', + }), + CypressEsm({ + ignoreList: ['*Immutable*', '*MyAsync*'], + }), + ], + } + }, + }, + }, +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/assetTypes.cy.ts b/npm/vite-plugin-cypress-esm/cypress/component/assetTypes.cy.ts new file mode 100644 index 0000000000..c3768fb881 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/assetTypes.cy.ts @@ -0,0 +1,6 @@ +import './fixtures/style.css' +import { add } from './fixtures/add' + +it('does not transform non JS assets', () => { + expect(add(1, 2)).to.eq(3) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/dynamicImport.cy.ts b/npm/vite-plugin-cypress-esm/cypress/component/dynamicImport.cy.ts new file mode 100644 index 0000000000..74975ec6b6 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/dynamicImport.cy.ts @@ -0,0 +1,77 @@ +/// + +import _ from 'lodash' + +describe('dynamic imports', () => { + it('uses real implementation', () => { + expect(_.add(1, 2)).to.eq(3) + }) + + it('mocks', () => { + cy.stub(_, 'add').callsFake(function multiply (a: number, b: number) { + return a * b + }) + + // Plot twist - add actually does multiplication! + expect(_.add(1, 2)).to.eq(2) + }) + + it('uses real implementation again', () => { + expect(_.add(1, 2)).to.eq(3) + }) + + it('stubs lodash method from node_modules using dynamic import', () => { + let done = false + + async function run () { + const _ = await import('lodash') + + cy.stub(_, 'camelCase').callsFake((str: string) => str.toUpperCase()) + const result = _.camelCase('foo_bar') + + expect(result).to.eq('FOO_BAR') + + done = true + } + + cy.wrap(run()).then(() => { + expect(done).to.be.true + }) + }) + + it('stub local dynamic import', () => { + let called = false + + async function run () { + const mod = await import('./fixtures/add') + + cy.stub(mod, 'add') + mod.add(1, 2) + called = true + } + + cy.wrap(run()).then(() => { + expect(called).to.be.true + }) + }) + + it('stubs lodash method from node_modules using `then`', () => { + import('lodash').then((mod) => { + cy.stub(mod, 'camelCase').callsFake((str: string) => str.toUpperCase()) + + const result = mod.camelCase('foo_bar') + + expect(result).to.eq('FOO_BAR') + }) + }) + + it('destructures', () => { + async function run () { + const { add } = await import('lodash') + + expect(add(1, 2)).to.eq(3) + } + + cy.wrap(run()) + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/edgeCases.cy.tsx b/npm/vite-plugin-cypress-esm/cypress/component/edgeCases.cy.tsx new file mode 100644 index 0000000000..cc1898037d --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/edgeCases.cy.tsx @@ -0,0 +1,83 @@ +/// + +import React, { Suspense, lazy } from 'react' +import { mount } from 'cypress/react' +import * as Mod from './fixtures/class' +import * as Foo from './fixtures/Foo' +import numbers from './fixtures/defaultExportArray' +import { App } from './fixtures/reactQuery' +import { letters } from './fixtures/namedExportArray' +import { BrowserRouter, Routes, Route } from 'react-router-dom' +const About = lazy(() => import('./fixtures/exportDefaultConst')) + +describe('edge cases', () => { + describe('class with constructor', () => { + context('named export', () => { + it('does not throw `Class constructor Foo cannot be invoked without "new"` error', () => { + const foo = new Mod.Foo('lachlan') + + expect(foo.hollaAt).to.eq('lachlan') + }) + }) + + context('default export', () => { + it('does not throw `Class constructor Foo cannot be invoked without "new"` error', () => { + const baz = new Mod.default('lachlan') + + expect(baz.hollaAt).to.eq('BAZ!') + }) + }) + }) + + it('works with react class component', () => { + mount() + cy.contains('Hello world').should('exist') + }) + + it('works with react function component', () => { + mount() + cy.contains('Hello world').should('exist') + }) + + it('inheritance via classes', () => { + expect(new Foo.Dog('bark').greet()).to.eq('bark') + }) + + /** + * Verifies a popular and complex library works properly. + * This library has some weird things going on with prototypes + * under the hood. + */ + it('works with react-query', () => { + mount() + }) + + it('works with array as default export', () => { + expect(Array.isArray(numbers)).to.be.true + expect(numbers).to.deep.eq([1, 2, 3]) + }) + + it('works with array as named export', () => { + expect(Array.isArray(letters)).to.be.true + expect(letters).to.deep.eq(['a', 'b', 'c']) + }) + + // TODO: This errors when the lazy loaded router is a component + // This error has more details in the package README. + it.skip('works with react-router and lazy route', () => { + function App () { + return ( + + }> + + } /> + + + + ) + } + + mount() + cy.get('h1').contains('About') + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/Foo.tsx b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/Foo.tsx new file mode 100644 index 0000000000..f485f06380 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/Foo.tsx @@ -0,0 +1,35 @@ +import React from 'react' + +export const Foo: React.FC<{ msg: string }> = (props) => { + return ( +

{props.msg}

+ ) +} + +class Animal { + voice: string = 'Hello' + + greet () { + return this.voice + } +} + +export class Dog extends Animal { + constructor (voice: string) { + super() + this.voice = voice + } +} + +export class BarClassComponent extends React.Component<{ msg: string }> { + /* eslint-disable @typescript-eslint/no-useless-constructor */ + constructor (props) { + super(props) + } + + render () { + return ( +

{this.props.msg}

+ ) + } +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyAsyncMod.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyAsyncMod.ts new file mode 100644 index 0000000000..c73dbed72e --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyAsyncMod.ts @@ -0,0 +1,3 @@ +export default function foo () { + console.log('foo') +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyImmutableModule.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyImmutableModule.ts new file mode 100644 index 0000000000..ce60dbad5d --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/MyImmutableModule.ts @@ -0,0 +1,7 @@ +export function unstubbable () { + return 'Hello from immutable' +} + +export default function nospy () { + return 'You cannot spy on this' +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/add.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/add.ts new file mode 100644 index 0000000000..7e4a92c86e --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/add.ts @@ -0,0 +1,3 @@ +export function add (a: number, b: number) { + return a + b +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/class.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/class.ts new file mode 100644 index 0000000000..a7df9dd1a6 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/class.ts @@ -0,0 +1,21 @@ +export class Foo { + private name: string + + constructor (name: string) { + this.name = name + } + + public get hollaAt () { + return this.name + } +} + +export function Bar () {} + +class Baz extends Foo { + public override get hollaAt () { + return 'BAZ!' + } +} + +export default Baz diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/defaultExportArray.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/defaultExportArray.ts new file mode 100644 index 0000000000..747af03a1a --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/defaultExportArray.ts @@ -0,0 +1 @@ +export default [1, 2, 3] diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/exportDefaultConst.tsx b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/exportDefaultConst.tsx new file mode 100644 index 0000000000..d6d0be8892 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/exportDefaultConst.tsx @@ -0,0 +1,5 @@ +import React from 'react' + +const About = () =>

About

+ +export default About diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/kitchenSink.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/kitchenSink.ts new file mode 100644 index 0000000000..efbef2a224 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/kitchenSink.ts @@ -0,0 +1,11 @@ +export const export1 = 'export1' + +export const export2 = 'export2' + +// @ts-expect-error +window.sideEffect = 'Side Effect' + +export default { + export1, + export2, +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_1.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_1.ts new file mode 100644 index 0000000000..59f38163b5 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_1.ts @@ -0,0 +1,13 @@ +export const Mod = { + async fetcher () { + import('./mod_2').then((mod) => { + document.querySelector(`[data-cy-root]`)!.innerHTML = `

${mod.greeting}

` + }) + }, + + run () { + window.setTimeout(() => { + this.fetcher() + }, 2000) + }, +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_2.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_2.ts new file mode 100644 index 0000000000..ca7e5308bc --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/mod_2.ts @@ -0,0 +1,3 @@ +export const greeting = { + name: 'lachlan', +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/namedExportArray.ts b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/namedExportArray.ts new file mode 100644 index 0000000000..eaba64bca0 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/namedExportArray.ts @@ -0,0 +1 @@ +export const letters = ['a', 'b', 'c'] diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/reactQuery.tsx b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/reactQuery.tsx new file mode 100644 index 0000000000..4aed7166a2 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/reactQuery.tsx @@ -0,0 +1,48 @@ +import { + useMutation, + useQueryClient, + QueryClient, + QueryClientProvider, +} from 'react-query' +import React from 'react' + +// Create a client +const queryClient = new QueryClient() + +export function App () { + return ( + // Provide the client to your App + + + + ) +} + +function Todos () { + // Access the client + const queryClient = useQueryClient() + + async function postTodo (args: any) { } + // Mutations + const mutation = useMutation(postTodo, { + onSuccess: () => { + // Invalidate and refetch + queryClient.invalidateQueries('todos') + }, + }) + + return ( +
+ +
+ ) +} diff --git a/npm/vite-plugin-cypress-esm/cypress/component/fixtures/style.css b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/style.css new file mode 100644 index 0000000000..795629d782 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/fixtures/style.css @@ -0,0 +1,3 @@ +div { + background: red; +} \ No newline at end of file diff --git a/npm/vite-plugin-cypress-esm/cypress/component/ignoreList.cy.ts b/npm/vite-plugin-cypress-esm/cypress/component/ignoreList.cy.ts new file mode 100644 index 0000000000..128ae88da8 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/ignoreList.cy.ts @@ -0,0 +1,47 @@ +import * as Immutable from './fixtures/MyImmutableModule' + +describe('ignoreList', () => { + it('ignoreList module is not proxified and cannot be stubbed', () => { + let called = false + + try { + cy.stub(Immutable, 'unstubbable') + } catch (e) { + expect(e.message).to.eq('ES Modules cannot be stubbed') + called = true + } + expect(called).to.be.true + }) + + it('ignoreList module is not proxified and cannot be spied', () => { + let called = false + + try { + cy.stub(Immutable, 'default') + } catch (e) { + expect(e.message).to.eq('ES Modules cannot be stubbed') + called = true + } + expect(called).to.be.true + }) + + it('correctly ignores dynamic imports', () => { + let called = false + + async function run () { + const mod = await import('./fixtures/MyAsyncMod') + + try { + cy.stub(mod, 'default') + mod.default() + } catch (e) { + called = true + expect(e.message).to.eq('ES Modules cannot be stubbed') + } + } + + cy.wrap(run()).then(() => { + expect(called).to.be.true + }) + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/importSyntax.cy.ts b/npm/vite-plugin-cypress-esm/cypress/component/importSyntax.cy.ts new file mode 100644 index 0000000000..e4cce16495 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/importSyntax.cy.ts @@ -0,0 +1,99 @@ +/// + +/* eslint-disable @typescript-eslint/no-duplicate-imports */ +import defaultExport1 from './fixtures/kitchenSink' +import * as name1 from './fixtures/kitchenSink' +import { export1 } from './fixtures/kitchenSink' +import { export1 as alias1 } from './fixtures/kitchenSink' +import { default as alias } from './fixtures/kitchenSink' +import defaultExport2, { export2 } from './fixtures/kitchenSink' +import defaultExport3, * as name2 from './fixtures/kitchenSink' +import { export1 as e1, export2 as e2 } from './fixtures/kitchenSink' +import './fixtures/kitchenSink' + +// Examples for all syntax +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import + +describe('supports every combination of import syntax in a single file', () => { + it('Import defaultExport1 from "./kitchenSink"', () => { + expect(defaultExport1).to.deep.eq({ + export1: 'export1', + export2: 'export2', + default: { + export1: 'export1', + export2: 'export2', + }, + }) + }) + + it('Import * as name1 from "./kitchenSink"', () => { + expect(name1).to.deep.eq({ + export1: 'export1', + export2: 'export2', + default: { + export1: 'export1', + export2: 'export2', + }, + }) + }) + + it('Import { export1 } from "./kitchenSink"', () => { + expect(export1).to.deep.eq(export1) + }) + + it('Import { export1 as alias1 } from "./kitchenSink"', () => { + expect(alias1).to.deep.eq(export1) + }) + + it('Import { default as alias } from "./kitchenSink"', () => { + expect(alias).to.deep.eq({ + export1: 'export1', + export2: 'export2', + }) + }) + + it('Import defaultExport2, { export2, } from "./kitchenSink"', () => { + expect(defaultExport2).to.deep.eq({ + export1: 'export1', + export2: 'export2', + default: { + export1: 'export1', + export2: 'export2', + }, + }) + + expect(export2).to.eq(export2) + }) + + it('Import defaultExport3, * as name2 from "./kitchenSink"', () => { + expect(defaultExport3).to.deep.eq({ + export1: 'export1', + export2: 'export2', + default: { + export1: 'export1', + export2: 'export2', + }, + }) + }) + + it('Import X, * as Y from "module" syntax is not supported.', () => { + expect(name2).to.deep.eq({ + export1: 'export1', + export2: 'export2', + default: { + export1: 'export1', + export2: 'export2', + }, + }) + }) + + it('Import { export1 as e1, export2 as e2 } from "./kitchenSink"', () => { + expect(e1).to.deep.eq(export1) + expect(e2).to.deep.eq(export2) + }) + + it('Import "./kitchenSink"', () => { + // @ts-expect-error + expect(window.sideEffect).to.eq('Side Effect') + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/spy.cy.ts b/npm/vite-plugin-cypress-esm/cypress/component/spy.cy.ts new file mode 100644 index 0000000000..f216781940 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/spy.cy.ts @@ -0,0 +1,11 @@ +/// + +import * as M from './fixtures/add' + +describe('spying ES modules', () => { + it('spies', () => { + cy.spy(M, 'add').as('add') + expect(M.add(2, 5)).to.eq(7) + cy.get('@add').should('have.been.calledWith', 2, 5) + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/component/stub.cy.tsx b/npm/vite-plugin-cypress-esm/cypress/component/stub.cy.tsx new file mode 100644 index 0000000000..6ded0642e0 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/component/stub.cy.tsx @@ -0,0 +1,56 @@ +/// +import React from 'react' +import * as M from './fixtures/add' +import { mount } from 'cypress/react' +import * as Foo from './fixtures/Foo' +import _ from 'lodash' + +describe('stubbing ES modules', () => { + it('uses real implementation', () => { + expect(M.add(1, 2)).to.eq(3) + }) + + it('mocks', () => { + cy.stub(M, 'add').callsFake(function multiply (a: number, b: number) { + return a * b + }) + + // Plot twist - add actually does multiplication! + expect(M.add(1, 2)).to.eq(2) + }) + + it('uses real implementation again', () => { + expect(M.add(1, 2)).to.eq(3) + }) + + it('mocks with alias', () => { + const stub = cy.stub(M, 'add').as('add') + + expect(M.add(1, 2)).to.be.undefined + + cy.wrap(stub).should('have.been.called') + cy.get('@add').should('have.been.called') + }) + + it('works with react class component', () => { + mount() + cy.contains('Hello world').should('exist') + }) + + it('works with react function component', () => { + mount() + cy.contains('Hello world').should('exist') + }) + + it('stubs react component', () => { + cy.stub(Foo, 'Foo').callsFake(() =>

Stub Component

) + mount() + cy.contains('Stub Component').should('exist') + cy.contains('Hello world').should('not.exist') + }) + + it('stubs lodash method from node_modules using static import', () => { + cy.stub(_, 'camelCase').callsFake(() => 'STUB') + expect(_.camelCase('aaaa')).to.eq('STUB') + }) +}) diff --git a/npm/vite-plugin-cypress-esm/cypress/support/component-index.html b/npm/vite-plugin-cypress-esm/cypress/support/component-index.html new file mode 100644 index 0000000000..ac6e79fd83 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/cypress/support/component-index.html @@ -0,0 +1,12 @@ + + + + + + + Components App + + +
+ + \ No newline at end of file diff --git a/npm/vite-plugin-cypress-esm/index.d.ts b/npm/vite-plugin-cypress-esm/index.d.ts new file mode 100644 index 0000000000..5d960ddb8b --- /dev/null +++ b/npm/vite-plugin-cypress-esm/index.d.ts @@ -0,0 +1 @@ +export * from './dist' diff --git a/npm/vite-plugin-cypress-esm/index.js b/npm/vite-plugin-cypress-esm/index.js new file mode 100644 index 0000000000..2d2f3c0bcb --- /dev/null +++ b/npm/vite-plugin-cypress-esm/index.js @@ -0,0 +1 @@ +module.exports = require('./dist') diff --git a/npm/vite-plugin-cypress-esm/package.json b/npm/vite-plugin-cypress-esm/package.json new file mode 100644 index 0000000000..9f65feccd2 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/package.json @@ -0,0 +1,45 @@ +{ + "name": "@cypress/vite-plugin-cypress-esm", + "version": "0.0.0-development", + "description": "Make ESM Modules mutable in the browser with Cypress and Vite", + "main": "index.js", + "scripts": { + "build": "tsc || echo 'built, with type errors'", + "build-prod": "tsc || echo 'built, with type errors'", + "check-ts": "tsc --noEmit", + "cypress:open": "node ../../scripts/cypress open --project . --component", + "test": "node ../../scripts/cypress run --project . --browser chrome --component", + "watch": "tsc -w", + "lint": "eslint --ext .js,.ts,.json, ." + }, + "dependencies": { + "debug": "^4.3.4", + "picomatch": "2.3.0" + }, + "devDependencies": { + "@types/picomatch": "2.3.0", + "@vitejs/plugin-react": "1.3.1", + "react": "16.8.6", + "react-dom": "16.8.6", + "react-query": "3.39.3", + "react-router": "6.10.0", + "react-router-dom": "6.10.0", + "vite": "4.1.4" + }, + "files": [ + "dist", + "client", + "index.d.ts" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/cypress-io/cypress.git" + }, + "homepage": "https://github.com/cypress-io/cypress/tree/develop/npm/vite-plugin-cypress-esm#readme", + "bugs": "https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm", + "module": "dist/index.js", + "publishConfig": { + "access": "public" + } +} diff --git a/npm/vite-plugin-cypress-esm/src/index.ts b/npm/vite-plugin-cypress-esm/src/index.ts new file mode 100644 index 0000000000..6598894e7b --- /dev/null +++ b/npm/vite-plugin-cypress-esm/src/index.ts @@ -0,0 +1,184 @@ +import debugFn from 'debug' +import picomatch from 'picomatch' +import type { Plugin } from 'vite' +import fs from 'fs' +import path from 'path' +const debug = debugFn('cypress:vite-plugin-mock-esm') + +const MODULE_IMPORTER_IDENTIFIER = '__cypressModule' +const MODULE_DYNAMIC_IMPORTER_IDENTIFIER = '__cypressDynamicModule' + +const MODULE_CACHE_FILEPATH = path.resolve( + __dirname, + '../client/moduleCache.js', +) + +interface CypressEsmOptions { + ignoreList?: string[] +} + +export const CypressEsm = (options?: CypressEsmOptions): Plugin => { + const ignoreList = options?.ignoreList ?? [] + + const importOnIgnoreList = (importTarget: string) => { + for (const ignore of ignoreList) { + if (picomatch.matchBase(importTarget, ignore)) { + return true + } + } + } + /** + * Remap static import calls to use the Cypress module cache + * + * ``` + * import DefaultExport, { NamedExport, Other as Alias } from 'module' + * ``` + * + * becomes + * + * ``` + * import * as _cypress_module_1 from 'module'; + * const DefaultExport = __cypressModule(_cypress_module_1); + * const { NamedExport, Other: Alias } = __cypressModule(_cypress_module_1); + * ``` + */ + const mapImportsToCache = (moduleId: string, code: string) => { + debug(`Remapping imports for module ${moduleId}`) + let counter = 0 + + const moduleIdentifier = moduleId.replace(/[^a-zA-Z\d]/g, '_') + + const importRegex = /import (.+?) from (.*)/g + + return code.replace( + importRegex, + (match, importVars: string, importTarget: string) => { + debug(`Mapping import ${counter + 1} in module ${moduleId}`) + + let replacement = `import * as cypress_${moduleIdentifier}_${++counter} from ${importTarget}` + + if (importOnIgnoreList(importTarget)) { + return match + } + + if (!replacement.endsWith(';')) { + replacement += ';' + } + + // Split the import declaration into named vs non-named segments + // e.g.: import TheDefault, { Named }, * as Everything from 'module' + // ======> ['TheDefault', '{ Named }', 'Everything'] + importVars + .split(/(?:(?<=})\s*,\s*)|(?:\s*,\s*(?={))/g) + .forEach((importVar) => { + const declarations = [] + + // If we're handling a destructure assignment then there aren't any special cases, can map through + // as a single assignment operation + if (importVar.includes('{')) { + declarations.push(importVar) + } else { + // Outside of a destructure we need to split each comma block as a separate assignment... + declarations.push( + ...importVar + .split(',') + // ...because we need to account for potential `* as foo` syntax + .map((decl) => decl.replace(/\*\s+as\s+/g, '').trim()) + .filter((decl) => !!decl), + ) + } + + let destructuredImports = declarations + .map((decl) => { + // support `import { foo as bar } from 'module'` syntax, converting to `const { foo: bar } ...` + decl = decl.replace(/(? mod) + * + * Returns: + * const m = __cypressDynamicModule(import("./mod_1")) + * const m = await __cypressDynamicModule(import("lodash")) + * __cypressDynamicModule(import("./mod_2")).then(mod => mod) + */ + const mapDynamicImportsToCache = (id: string, code: string) => { + const RE = /(import\((.+?)\))/g + + return code.replace( + RE, + (match, importVars: string, importTarget: string) => { + if (importOnIgnoreList(importTarget)) { + return match + } + + return match.replace( + RE, + `${MODULE_DYNAMIC_IMPORTER_IDENTIFIER}('${id}', $1, ${debug.enabled})`, + ) + }, + ) + } + + return { + name: 'cypress:mocks', + enforce: 'post', + transform (code, id, options) { + // Process all files to remap imports + // TODO: Restrict to JS files only? Any potential for .mjs etc? + let transformedCode = mapImportsToCache(id, code) + + transformedCode = mapDynamicImportsToCache(id, transformedCode) + + return { + code: transformedCode, + } + }, + async transformIndexHtml (html) { + // Process index.html file to remap imports + let transformedHtml = mapImportsToCache('index', html) + + transformedHtml = mapDynamicImportsToCache('index', transformedHtml) + + return { + html: transformedHtml, + tags: [ + // Pull in Cypress module cache script + { + tag: 'script', + attrs: { + type: 'module', + }, + // eslint-disable-next-line no-restricted-syntax + children: fs.readFileSync(MODULE_CACHE_FILEPATH, 'utf-8'), + }, + ], + } + }, + } +} diff --git a/npm/vite-plugin-cypress-esm/tsconfig.json b/npm/vite-plugin-cypress-esm/tsconfig.json new file mode 100644 index 0000000000..6bab84c6d0 --- /dev/null +++ b/npm/vite-plugin-cypress-esm/tsconfig.json @@ -0,0 +1,53 @@ +{ + "compilerOptions": { + /* Basic Options */ + "resolveJsonModule": true, + "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "lib": [ + "es2015", + "dom" + ] /* Specify library files to be included in the compilation: */, + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "dist" /* Redirect output structure to the directory. */, + // "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + "types": ["cypress"] /* Type declaration files to be included in compilation. */, + "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + + /* Source Map Options */ + // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "esModuleInterop": true, + /** Allows us to strip internal types sourced from webpack */ + "stripInternal": true + }, + "include": ["src"], + "exclude": ["node_modules", "*.js"] +} diff --git a/npm/vue/package.json b/npm/vue/package.json index d8567f091a..9b76ab3dc4 100644 --- a/npm/vue/package.json +++ b/npm/vue/package.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@cypress/mount-utils": "0.0.0-development", - "@vitejs/plugin-vue": "2.3.1", + "@vitejs/plugin-vue": "4.2.0", "@vue/compiler-sfc": "3.2.31", "@vue/test-utils": "2.0.2", "axios": "0.21.2", @@ -27,7 +27,7 @@ "globby": "^11.0.1", "tailwindcss": "1.1.4", "typescript": "^4.7.4", - "vite": "4.1.4", + "vite": "4.3.2", "vue": "3.2.31", "vue-i18n": "9.0.0-rc.6", "vue-router": "^4.0.0", diff --git a/npm/webpack-dev-server/CHANGELOG.md b/npm/webpack-dev-server/CHANGELOG.md index b2b8530335..63a5dcd8bd 100644 --- a/npm/webpack-dev-server/CHANGELOG.md +++ b/npm/webpack-dev-server/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/webpack-dev-server-v3.4.1](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.4.0...@cypress/webpack-dev-server-v3.4.1) (2023-04-07) + + +### Bug Fixes + +* correctly pass resolvedBaseUrl for Next.js 13.2.0+ ([#26399](https://github.com/cypress-io/cypress/issues/26399)) ([e8390f4](https://github.com/cypress-io/cypress/commit/e8390f46cd852417f2c0b07a9c73eeaf7437e823)) + # [@cypress/webpack-dev-server-v3.4.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.3.1...@cypress/webpack-dev-server-v3.4.0) (2023-03-20) diff --git a/npm/webpack-dev-server/cypress/e2e/angular.cy.ts b/npm/webpack-dev-server/cypress/e2e/angular.cy.ts index a67c61df8b..c053bc14df 100644 --- a/npm/webpack-dev-server/cypress/e2e/angular.cy.ts +++ b/npm/webpack-dev-server/cypress/e2e/angular.cy.ts @@ -2,12 +2,12 @@ /// import type { ProjectFixtureDir } from '@tooling/system-tests/lib/fixtureDirs' -const WEBPACK_REACT: ProjectFixtureDir[] = ['angular-13', 'angular-14', 'angular-15'] +const WEBPACK_ANGULAR: ProjectFixtureDir[] = ['angular-13', 'angular-14', 'angular-15', 'angular-16'] // Add to this list to focus on a particular permutation const ONLY_PROJECTS: ProjectFixtureDir[] = [] -for (const project of WEBPACK_REACT) { +for (const project of WEBPACK_ANGULAR) { if (ONLY_PROJECTS.length && !ONLY_PROJECTS.includes(project)) { continue } diff --git a/npm/webpack-dev-server/src/helpers/angularHandler.ts b/npm/webpack-dev-server/src/helpers/angularHandler.ts index 6e67646afa..31e3e2a32c 100644 --- a/npm/webpack-dev-server/src/helpers/angularHandler.ts +++ b/npm/webpack-dev-server/src/helpers/angularHandler.ts @@ -260,28 +260,33 @@ async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevSer buildOptions, context, (wco: any) => { - const stylesConfig = getStylesConfig(wco) - - // We modify resolve-url-loader and set `root` to be `projectRoot` + `sourceRoot` to ensure - // imports in scss, sass, etc are correctly resolved. - // https://github.com/cypress-io/cypress/issues/24272 - stylesConfig.module.rules.forEach((rule: RuleSetRule) => { - rule.rules?.forEach((ruleSet) => { - if (!Array.isArray(ruleSet.use)) { - return - } - - ruleSet.use.map((loader) => { - if (typeof loader !== 'object' || typeof loader.options !== 'object' || !loader.loader?.includes('resolve-url-loader')) { + // Starting in Angular 16, the `getStylesConfig` function returns a `Promise`. + // We wrap it with `Promise.resolve` so we support older version of Angular + // returning a non-Promise result. + const stylesConfig = Promise.resolve(getStylesConfig(wco)).then((config) => { + // We modify resolve-url-loader and set `root` to be `projectRoot` + `sourceRoot` to ensure + // imports in scss, sass, etc are correctly resolved. + // https://github.com/cypress-io/cypress/issues/24272 + config.module.rules.forEach((rule: RuleSetRule) => { + rule.rules?.forEach((ruleSet) => { + if (!Array.isArray(ruleSet.use)) { return } - const root = projectConfig.buildOptions.workspaceRoot || path.join(devServerConfig.cypressConfig.projectRoot, projectConfig.sourceRoot) + ruleSet.use.map((loader) => { + if (typeof loader !== 'object' || typeof loader.options !== 'object' || !loader.loader?.includes('resolve-url-loader')) { + return + } - debug('Adding root %s to resolve-url-loader options', root) - loader.options.root = root + const root = projectConfig.buildOptions.workspaceRoot || path.join(devServerConfig.cypressConfig.projectRoot, projectConfig.sourceRoot) + + debug('Adding root %s to resolve-url-loader options', root) + loader.options.root = root + }) }) }) + + return config }) return [getCommonConfig(wco), stylesConfig] diff --git a/npm/webpack-dev-server/src/helpers/nextHandler.ts b/npm/webpack-dev-server/src/helpers/nextHandler.ts index b1e9a5b632..ebd747cbea 100644 --- a/npm/webpack-dev-server/src/helpers/nextHandler.ts +++ b/npm/webpack-dev-server/src/helpers/nextHandler.ts @@ -29,7 +29,12 @@ export async function nextHandler (devServerConfig: WebpackDevServerConfig): Pro */ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { const resolvePaths = { paths: [devServerConfig.cypressConfig.projectRoot] } - const packages = {} as { loadConfig: Function, getNextJsBaseWebpackConfig: Function, nextLoadJsConfig: Function } + const packages = {} as { + loadConfig: (phase: 'development', dir: string) => Promise + getNextJsBaseWebpackConfig: Function + nextLoadJsConfig: Function + getSupportedBrowsers: (dir: string, isDevelopment: boolean, nextJsConfig: any) => Promise + } try { const loadConfigPath = require.resolve('next/dist/server/config', resolvePaths) @@ -55,6 +60,15 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { throw new Error(`Failed to load "next/dist/build/load-jsconfig" with error: ${ e.message ?? e}`) } + // Does not exist prior to Next 13. + try { + const getUtilsPath = require.resolve('next/dist/build/utils', resolvePaths) + + packages.getSupportedBrowsers = require(getUtilsPath).getSupportedBrowsers ?? (() => Promise.resolve([])) + } catch (e: any) { + throw new Error(`Failed to load "next/dist/build/utils" with error: ${ e.message ?? e}`) + } + return packages } @@ -173,12 +187,13 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) { ] */ async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Promise { - const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig } = getNextJsPackages(devServerConfig) + const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig, getSupportedBrowsers } = getNextJsPackages(devServerConfig) const nextConfig = await loadConfig('development', devServerConfig.cypressConfig.projectRoot) const runWebpackSpan = getRunWebpackSpan(devServerConfig) const reactVersion = getReactVersion(devServerConfig.cypressConfig.projectRoot) const jsConfigResult = await nextLoadJsConfig?.(devServerConfig.cypressConfig.projectRoot, nextConfig) + const supportedBrowsers = await getSupportedBrowsers(devServerConfig.cypressConfig.projectRoot, true, nextConfig) const webpackConfig = await getNextJsBaseWebpackConfig( devServerConfig.cypressConfig.projectRoot, @@ -196,8 +211,12 @@ async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Prom compilerType: 'client', // Required for Next.js > 13 hasReactRoot: reactVersion === 18, - // Required for Next.js > 13.2.1 to respect TS/JS config + // Required for Next.js > 13.2.0 to respect TS/JS config jsConfig: jsConfigResult.jsConfig, + // Required for Next.js > 13.2.0 to respect tsconfig.compilerOptions.baseUrl + resolvedBaseUrl: jsConfigResult.resolvedBaseUrl, + // Added in Next.js 13, passed via `...info`: https://github.com/vercel/next.js/pull/45637/files + supportedBrowsers, }, ) diff --git a/npm/webpack-preprocessor/CHANGELOG.md b/npm/webpack-preprocessor/CHANGELOG.md index 02be04fbf2..33ce5cdc4a 100644 --- a/npm/webpack-preprocessor/CHANGELOG.md +++ b/npm/webpack-preprocessor/CHANGELOG.md @@ -1,3 +1,5 @@ +# [@cypress/webpack-preprocessor-v5.17.1](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.17.0...@cypress/webpack-preprocessor-v5.17.1) (2023-05-01) + # [@cypress/webpack-preprocessor-v5.17.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.16.3...@cypress/webpack-preprocessor-v5.17.0) (2023-02-15) diff --git a/npm/webpack-preprocessor/__snapshots__/compilation.spec.js b/npm/webpack-preprocessor/__snapshots__/compilation.spec.js index 1f07876295..162e379475 100644 --- a/npm/webpack-preprocessor/__snapshots__/compilation.spec.js +++ b/npm/webpack-preprocessor/__snapshots__/compilation.spec.js @@ -1,6 +1,6 @@ exports['webpack preprocessor - e2e correctly preprocesses the file 1'] = ` it("is a test",(function(){expect(1).to.equal(1),expect(2).to.equal(2),expect(Math.min.apply(Math,[3,4])).to.equal(3)})); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUcsTUFBUkQsS0FBWSxDQUFDLEVBQUcsS0FBS0YsR0FBR0MsTUFBTSxFQUN2QyIsInNvdXJjZXMiOlsid2VicGFjazovL0BjeXByZXNzL3dlYnBhY2stcHJlcHJvY2Vzc29yLy4vdGVzdC9fdGVzdC1vdXRwdXQvZXhhbXBsZV9zcGVjLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIml0KCdpcyBhIHRlc3QnLCAoKSA9PiB7XG4gIGNvbnN0IFthLCBiXSA9IFsxLCAyXVxuXG4gIGV4cGVjdChhKS50by5lcXVhbCgxKVxuICBleHBlY3QoYikudG8uZXF1YWwoMilcbiAgZXhwZWN0KE1hdGgubWluKC4uLlszLCA0XSkpLnRvLmVxdWFsKDMpXG59KVxuIl0sIm5hbWVzIjpbIml0IiwiZXhwZWN0IiwidG8iLCJlcXVhbCIsIk1hdGgiLCJtaW4iXSwic291cmNlUm9vdCI6IiJ9 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUdDLE1BQVJGLEtBQVksQ0FBQyxFQUFHLEtBQUtGLEdBQUdDLE1BQU0sRUFDdkMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9AY3lwcmVzcy93ZWJwYWNrLXByZXByb2Nlc3Nvci8uL3Rlc3QvX3Rlc3Qtb3V0cHV0L2V4YW1wbGVfc3BlYy5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJpdCgnaXMgYSB0ZXN0JywgKCkgPT4ge1xuICBjb25zdCBbYSwgYl0gPSBbMSwgMl1cblxuICBleHBlY3QoYSkudG8uZXF1YWwoMSlcbiAgZXhwZWN0KGIpLnRvLmVxdWFsKDIpXG4gIGV4cGVjdChNYXRoLm1pbiguLi5bMywgNF0pKS50by5lcXVhbCgzKVxufSlcbiJdLCJuYW1lcyI6WyJpdCIsImV4cGVjdCIsInRvIiwiZXF1YWwiLCJNYXRoIiwibWluIiwiYXBwbHkiXSwic291cmNlUm9vdCI6IiJ9 ` exports['webpack preprocessor - e2e has less verbose syntax error 1'] = ` diff --git a/npm/webpack-preprocessor/package.json b/npm/webpack-preprocessor/package.json index 28a585053b..c9a57905bb 100644 --- a/npm/webpack-preprocessor/package.json +++ b/npm/webpack-preprocessor/package.json @@ -37,7 +37,7 @@ "common-tags": "^1.8.2", "cypress": "0.0.0-development", "dependency-check": "2.9.1", - "deps-ok": "1.2.1", + "deps-ok": "1.4.1", "fast-glob": "3.1.1", "find-webpack": "1.5.0", "fs-extra": "^10.1.0", diff --git a/package.json b/package.json index 2f7f06caf0..431178769b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cypress", - "version": "12.9.0", + "version": "12.11.0", "description": "Cypress is a next generation front end testing tool built for the modern web", "private": true, "scripts": { @@ -49,7 +49,7 @@ "stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude cypress-tests.ts,*only.cy.js", "stop-only-all": "yarn stop-only --folder packages", "pretest": "yarn ensure-deps", - "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"", + "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require,telemetry}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"", "test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{driver,root,static,web-config}'\"", "pretest-e2e": "yarn ensure-deps", "test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{driver,root,static,web-config}'\"", @@ -103,7 +103,7 @@ "@types/glob": "7.1.1", "@types/gulp": "^4.0.9", "@types/lodash": "^4.14.168", - "@types/markdown-it": "0.0.9", + "@types/markdown-it": "12.2.3", "@types/mini-css-extract-plugin": "1.2.3", "@types/mocha": "8.0.3", "@types/node": "14.14.31", @@ -188,7 +188,6 @@ "patch-package": "6.4.7", "playwright-webkit": "1.24.2", "pluralize": "8.0.0", - "postinstall-postinstall": "2.0.0", "print-arch": "1.0.0", "proxyquire": "2.1.3", "rimraf": "3.0.2", @@ -276,4 +275,4 @@ "sharp": "0.29.3", "vue-template-compiler": "2.6.12" } -} \ No newline at end of file +} diff --git a/packages/app/.gitignore b/packages/app/.gitignore index 86e379ad67..2fc8f018b1 100644 --- a/packages/app/.gitignore +++ b/packages/app/.gitignore @@ -1,2 +1,4 @@ cypress/videos/* -cypress/screenshots/* \ No newline at end of file +cypress/screenshots/* + +components.d.ts \ No newline at end of file diff --git a/packages/app/cypress/component/support/index.ts b/packages/app/cypress/component/support/index.ts index d4e9e48b5c..89c94cc559 100644 --- a/packages/app/cypress/component/support/index.ts +++ b/packages/app/cypress/component/support/index.ts @@ -18,8 +18,6 @@ import { registerMountFn } from '@packages/frontend-shared/cypress/support/commo // Import commands.js using ES2015 syntax: -import 'virtual:windi.css' -import '../../../src/main.scss' import '@iconify/iconify' import { createRouter } from '../../../src/router/router' import { createPinia } from '../../../src/store' diff --git a/packages/app/cypress/e2e/debug.cy.ts b/packages/app/cypress/e2e/debug.cy.ts index 76fce07b3a..a6efd30ff4 100644 --- a/packages/app/cypress/e2e/debug.cy.ts +++ b/packages/app/cypress/e2e/debug.cy.ts @@ -64,7 +64,7 @@ describe('App - Debug Page', () => { cy.findByTestId('header-top').contains('update projectId') cy.findByTestId('debug-header-dashboard-link') .contains('View in Cypress Cloud') - .should('have.attr', 'href', 'https://cloud.cypress.io/projects/7p5uce/runs/2') + .should('have.attr', 'href', 'https://cloud.cypress.io/projects/7p5uce/runs/2?utm_medium=Debug+Tab&utm_campaign=View+in+Cypress+Cloud&utm_source=Binary%3A+App') cy.findByTestId('debug-runNumber-PASSED').contains('#2') cy.findByTestId('debug-commitsAhead').contains('You are 1 commit ahead') @@ -133,7 +133,7 @@ describe('App - Debug Page', () => { cy.findByTestId('header-top').contains('chore: testing cypress') cy.findByTestId('debug-header-dashboard-link') .contains('View in Cypress Cloud') - .should('have.attr', 'href', 'https://cloud.cypress.io/projects/vgqrwp/runs/136') + .should('have.attr', 'href', 'https://cloud.cypress.io/projects/vgqrwp/runs/136?utm_medium=Debug+Tab&utm_campaign=View+in+Cypress+Cloud&utm_source=Binary%3A+App') cy.findByLabelText('Relevant run had 1 test failure').should('be.visible').contains('1') diff --git a/packages/app/cypress/e2e/runs.cy.ts b/packages/app/cypress/e2e/runs.cy.ts index 0cdce03ba5..f9df449949 100644 --- a/packages/app/cypress/e2e/runs.cy.ts +++ b/packages/app/cypress/e2e/runs.cy.ts @@ -100,7 +100,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.contains('a', 'OVERLIMIT').click() cy.withCtx((ctx) => { - expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/4') + expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/4') }) }) }) @@ -323,7 +323,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { moveToRunsPage() cy.findByText(defaultMessages.runs.connect.buttonProject).click() cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click() - cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible') + cy.findByText(defaultMessages.runs.connectSuccessAlert.title, { timeout: 10000 }).should('be.visible') cy.withCtx(async (ctx) => { const config = await ctx.project.getConfig() @@ -660,22 +660,22 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.visitApp() moveToRunsPage() - cy.get('[href="http://dummy.cypress.io/runs/0"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().within(() => { cy.findByText('fix: make gql work CANCELLED') cy.get('[data-cy="run-card-icon-CANCELLED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/1"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/1"]').first().within(() => { cy.findByText('fix: make gql work ERRORED') cy.get('[data-cy="run-card-icon-ERRORED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/2"]').first().within(() => { + cy.get('[href^="http://dummy.cypress.io/runs/2"]').first().within(() => { cy.findByText('fix: make gql work FAILED') cy.get('[data-cy="run-card-icon-FAILED"]') }) - cy.get('[href="http://dummy.cypress.io/runs/0"]').first().as('firstRun') + cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun') cy.get('@firstRun').within(() => { cy.get('[data-cy="run-card-author"]').contains('John Appleseed') @@ -699,7 +699,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => { cy.get('[data-cy^="runCard-"]').first().click() cy.withCtx((ctx) => { - expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/0') + expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0') }) }) diff --git a/packages/app/cypress/e2e/top-nav.cy.ts b/packages/app/cypress/e2e/top-nav.cy.ts index c5f87b945c..9cf84d4eed 100644 --- a/packages/app/cypress/e2e/top-nav.cy.ts +++ b/packages/app/cypress/e2e/top-nav.cy.ts @@ -111,7 +111,7 @@ describe('App Top Nav Workflows', () => { 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, { shouldLaunchNewTab: false }, undefined, + ctx.coreData.currentTestingType, { shouldLaunchNewTab: false }, '', ) }) }) diff --git a/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json b/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json index 45a17b0587..576baa8a4a 100644 --- a/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json +++ b/packages/app/cypress/fixtures/debug-Failing/gql-Debug.json @@ -5,6 +5,7 @@ "cloudProject": { "__typename": "CloudProject", "id": "cloud-project-test-id", + "cloudProjectUrl": "https://cloud.cypress.io/projects/vgqrwp", "runByNumber": { "id": "Q2xvdWRSdW46TUdWZXhvQkRPNg==", "runNumber": 136, diff --git a/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json b/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json index ea4f75c101..a016bf8199 100644 --- a/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json +++ b/packages/app/cypress/fixtures/debug-Passing/gql-Debug.json @@ -5,6 +5,7 @@ "cloudProject": { "__typename": "CloudProject", "id": "Q2xvdWRQcm9qZWN0OjdwNXVjZQ==", + "cloudProjectUrl": "https://cloud.cypress.io/projects/7p5uce", "runByNumber": { "id": "Q2xvdWRSdW46bkdudmx5d3BHWg==", "runNumber": 2, diff --git a/packages/app/index.d.ts b/packages/app/index.d.ts index c37b64a87b..9f093be679 100644 --- a/packages/app/index.d.ts +++ b/packages/app/index.d.ts @@ -13,9 +13,9 @@ export {} * To work around this, we build the driver, eventManager * and some other dependencies using webpack, and consumed the dist'd * source code. - * + * * This is attached to `window` under the `UnifiedRunner` namespace. - * + * * For now, just declare the types that we need to give us type safety where possible. * Eventually, we should decouple the event manager and import it directly. */ @@ -37,7 +37,7 @@ declare global { * We get a reference to the copy of React (and React DOM) * that is used in the Reporter and Driver, which are bundled with * webpack. - * + * * Unfortunately, attempting to have React in a project * using Vue causes mad conflicts because React'S JSX type * is ambient, so we cannot actually type it. @@ -54,7 +54,7 @@ declare global { * Any React components or general code needed from * runner, reporter or driver are also bundled with * webpack and made available via the window.UnifedRunner namespace. - * + * * We cannot import the correct types, because this causes the linter and type * checker to run on runner and reporter, and it blows up. */ @@ -64,4 +64,4 @@ declare global { } } } -} \ No newline at end of file +} diff --git a/packages/app/package.json b/packages/app/package.json index 3cccd49a23..36cc45a7f4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -20,24 +20,25 @@ }, "dependencies": {}, "devDependencies": { - "@cypress-design/vue-icon": "0.18.1", - "@cypress-design/vue-statusicon": "0.2.4", + "@cypress-design/vue-icon": "0.20.0", + "@cypress-design/vue-statusicon": "0.3.0", "@graphql-typed-document-node/core": "^3.1.0", "@headlessui/vue": "1.4.0", "@iconify/iconify": "2.1.2", "@iconify/json": "1.1.368", "@iconify/vue": "3.0.0-beta.1", - "@intlify/vite-plugin-vue-i18n": "2.4.0", + "@intlify/unplugin-vue-i18n": "0.10.0", "@packages/frontend-shared": "0.0.0-development", + "@packages/telemetry": "0.0.0-development", "@percy/cypress": "^3.1.0", "@popperjs/core": "2.11.6", "@testing-library/cypress": "9.0.0", "@types/faker": "5.5.8", "@urql/core": "2.4.4", "@urql/vue": "0.6.2", - "@vitejs/plugin-legacy": "^2.1.0", - "@vitejs/plugin-vue": "2.2.4", - "@vitejs/plugin-vue-jsx": "1.3.8", + "@vitejs/plugin-legacy": "4.0.3", + "@vitejs/plugin-vue": "4.2.0", + "@vitejs/plugin-vue-jsx": "3.0.1", "@vueuse/core": "7.2.2", "ansi-to-html": "0.6.14", "bluebird": "3.5.3", @@ -63,13 +64,12 @@ "rollup-plugin-copy": "3.4.0", "rollup-plugin-polyfill-node": "^0.7.0", "unplugin-icons": "0.13.2", - "unplugin-vue-components": "^0.15.2", - "vite": "4.1.4", - "vite-plugin-components": "0.11.3", - "vite-plugin-pages": "0.18.1", - "vite-plugin-vue-layouts": "0.6.0", - "vite-svg-loader": "3.1.2", - "vue": "3.2.31", + "unplugin-vue-components": "^0.24.1", + "vite": "4.3.2", + "vite-plugin-pages": "0.29.0", + "vite-plugin-vue-layouts": "0.8.0", + "vite-svg-loader": "4.0.0", + "vue": "3.2.47", "vue-i18n": "9.2.0-beta.7", "vue-router": "4", "vue-tsc": "^0.3.0", diff --git a/packages/app/postcss.config.js b/packages/app/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/packages/app/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue index 9f55e93ff9..abd1350e00 100644 --- a/packages/app/src/App.vue +++ b/packages/app/src/App.vue @@ -19,17 +19,3 @@ import { isRunMode } from '@packages/frontend-shared/src/utils/isRunMode' import LoginConnectModals from '@cy/gql-components/LoginConnectModals.vue' import CloudViewerAndProject from '@packages/frontend-shared/src/gql-components/CloudViewerAndProject.vue' - - diff --git a/packages/app/src/components/FileMatch.vue b/packages/app/src/components/FileMatch.vue index f5675fd4f6..9e5c287739 100644 --- a/packages/app/src/components/FileMatch.vue +++ b/packages/app/src/components/FileMatch.vue @@ -28,23 +28,23 @@