diff --git a/.vscode/settings.json b/.vscode/settings.json index f2baf4638404..803572235759 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,5 +38,5 @@ // Volar is the main extension that powers Vue's language features. // These are commented out because they slow down node development // "volar.autoCompleteRefs": false, - // "volar.takeOverMode.enabled": true + "volar.takeOverMode.enabled": true, } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 020517760924..306512f9e8ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,6 @@ Thanks for taking the time to contribute! :smile: ## Table of Contents -- [CI Status](#ci-status) - [Code of Conduct](#code-of-conduct) - [Opening Issues](#opening-issues) - [Triaging Issues](#triaging-issues) @@ -42,20 +41,6 @@ Thanks for taking the time to contribute! :smile: - [Code Review of Dependency Updates](#Code-Review-of-Dependency-Updates) - [Deployment](#deployment) -## CI status - -| Build status | Description | -| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------- | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-test-node-versions.svg?style=svg&circle-token=6a7c4e7e7ab427e11bea6c2af3df29c4491d2376)](https://circleci.com/gh/cypress-io/cypress-test-node-versions) | [cypress-test-node-versions](https://github.com/cypress-io/cypress-test-node-versions) | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-test-ci-environments.svg?style=svg&circle-token=66a4d36c3966cbe476f13e7dfbe3af0693db3fb9)](https://circleci.com/gh/cypress-io/cypress-test-ci-environments) | [cypress-test-ci-environments](https://github.com/cypress-io/cypress-test-ci-environments) | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-test-module-api.svg?style=svg&circle-token=317f79ae796e0ffd6cc7dd90859c0f67e5a306e7)](https://circleci.com/gh/cypress-io/cypress-test-module-api) | [cypress-test-module-api](https://github.com/cypress-io/cypress-test-module-api) | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-test-nested-projects.svg?style=svg)](https://circleci.com/gh/cypress-io/cypress-test-nested-projects) | [cypress-test-nested-projects](https://github.com/cypress-io/cypress-test-nested-projects) | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-on.svg?style=svg&circle-token=51ba85f5720654ee58212f45f6b9afc56d55d52a)](https://circleci.com/gh/cypress-io/cypress-on) | [cypress-on](https://github.com/cypress-io/cypress-on) | -| [![CircleCI](https://circleci.com/gh/cypress-io/cypress-test-node-versions.svg?style=svg&circle-token=6a7c4e7e7ab427e11bea6c2af3df29c4491d2376)](https://circleci.com/gh/cypress-io/cypress-test-node-versions) | [cypress-test-example-repos](https://github.com/cypress-io/cypress-test-example-repos) | -| [![CircleCI](https://circleci.com/gh/cypress-io/docsearch-scraper.svg?style=svg&circle-token=8087137233788ec1eab4f79d4451392ca53183b2)](https://circleci.com/gh/cypress-io/docsearch-scraper) | [docsearch-scraper](https://github.com/cypress-io/docsearch-scraper) | -| [![Docker Build Status](https://img.shields.io/docker/build/cypress/base.svg)](https://hub.docker.com/r/cypress/base/) | [cypress-docker-images](https://github.com/cypress-io/cypress-docker-images) | -| [![Build status](https://ci.appveyor.com/api/projects/status/ln8tg3dv42nk916c?svg=true)](https://ci.appveyor.com/project/cypress-io/cypress) | Windows CI | - ## Code of Conduct All contributors are expecting to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). diff --git a/__snapshots__/bump-spec.js b/__snapshots__/bump-spec.js index f004f1bfddfc..73a99a47b3bd 100644 --- a/__snapshots__/bump-spec.js +++ b/__snapshots__/bump-spec.js @@ -1,65 +1,15 @@ exports['list of all projects'] = [ - { - "repo": "cypress-io/cypress-test-tiny", - "provider": "circle", - "platform": "win32" - }, - { - "repo": "cypress-io/cypress-test-example-repos", - "provider": "circle", - "platform": "win32" - }, - { - "repo": "cypress-io/cypress-test-tiny", - "provider": "circle", - "platform": "linux" - }, { "repo": "cypress-io/cypress-test-module-api", "provider": "circle", "platform": "linux" - }, - { - "repo": "cypress-io/cypress-test-node-versions", - "provider": "circle", - "platform": "linux" - }, - { - "repo": "cypress-io/cypress-test-nested-projects", - "provider": "circle", - "platform": "linux" - }, - { - "repo": "cypress-io/cypress-test-ci-environments", - "provider": "circle", - "platform": "linux" - }, - { - "repo": "cypress-io/cypress-test-example-repos", - "provider": "circle", - "platform": "linux" - }, - { - "repo": "cypress-io/cypress-test-tiny", - "provider": "circle", - "platform": "darwin" - }, - { - "repo": "cypress-io/cypress-test-example-repos", - "provider": "circle", - "platform": "darwin" } ] -exports['should have just circle and darwin projects'] = [ - { - "repo": "cypress-io/cypress-test-tiny", - "provider": "circle", - "platform": "darwin" - }, +exports['should have just circle and linux projects'] = [ { - "repo": "cypress-io/cypress-test-example-repos", + "repo": "cypress-io/cypress-test-module-api", "provider": "circle", - "platform": "darwin" + "platform": "linux" } ] diff --git a/browser-versions.json b/browser-versions.json index beec05263965..b2731c658fbd 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,4 +1,4 @@ { "chrome:beta": "99.0.4844.27", - "chrome:stable": "98.0.4758.80" + "chrome:stable": "98.0.4758.102" } diff --git a/circle.yml b/circle.yml index 5dbf4c7dc1a9..ed03ebd7f362 100644 --- a/circle.yml +++ b/circle.yml @@ -11,7 +11,7 @@ defaults: &defaults type: boolean default: false executor: <> - environment: + environment: &defaultsEnvironment ## set specific timezone TZ: "/usr/share/zoneinfo/America/New_York" @@ -170,9 +170,12 @@ commands: - run: name: Generate Circle Cache Key command: node scripts/circle-cache.js --action cacheKey > circle_cache_key + - run: + name: Generate platform key + command: echo $PLATFORM > platform_key - restore_cache: name: Restore cache state, to check for known modules cache existence - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-node-modules-cache-{{ checksum "circle_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }} - run: name: Move node_modules back from /tmp command: | @@ -193,11 +196,14 @@ commands: - run: name: Generate Circle Cache key for system tests command: ./system-tests/scripts/cache-key.sh > system_tests_cache_key + - run: + name: Generate platform key + command: echo $PLATFORM > platform_key - restore_cache: name: Restore system tests node_modules cache keys: - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache- + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache- update_cached_system_tests_deps: description: 'Update the cached node_modules for projects in "system-tests/projects/**"' @@ -205,36 +211,42 @@ commands: - run: name: Generate Circle Cache key for system tests command: ./system-tests/scripts/cache-key.sh > system_tests_cache_key + - run: + name: Generate platform key + command: echo $PLATFORM > platform_key - restore_cache: name: Restore cache state, to check for known modules cache existence keys: - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }} + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }} + - run: + name: Send root honeycomb event for this CI build + command: cd system-tests/scripts && node ./send-root-honecomb-event.js - run: name: Bail if specific cache exists command: | - if [[ -f "system_tests_node_modules_installed" ]]; then + if [[ -f "/tmp/system_tests_node_modules_installed" ]]; then echo "No updates to system tests node modules, exiting" circleci-agent step halt fi - restore_cache: name: Restore system tests node_modules cache keys: - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache- + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache- - run: name: Update system-tests node_modules cache command: yarn workspace @tooling/system-tests projects:yarn:install - save_cache: name: Save system tests node_modules cache - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }} paths: - ~/.cache/cy-system-tests-node-modules - - run: touch system_tests_node_modules_installed + - run: touch /tmp/system_tests_node_modules_installed - save_cache: name: Save system tests node_modules cache state key - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }} paths: - - system_tests_node_modules_installed + - /tmp/system_tests_node_modules_installed caching-dependency-installer: description: 'Installs & caches the dependencies based on yarn lock & package json dependencies' @@ -247,13 +259,16 @@ commands: - run: name: Generate Circle Cache Key command: node scripts/circle-cache.js --action cacheKey > circle_cache_key + - run: + name: Generate platform key + command: echo $PLATFORM > platform_key - restore_cache: name: Restore cache state, to check for known modules cache existence - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-node-modules-cache-state-{{ checksum "circle_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-state-{{ checksum "circle_cache_key" }} - run: name: Bail if cache exists command: | - if [[ -f "node_modules_installed" ]]; then + if [[ -f "/tmp/node_modules_installed" ]]; then echo "Node modules already cached for dependencies, exiting" circleci-agent step halt fi @@ -261,7 +276,7 @@ commands: - restore_cache: name: Restore weekly yarn cache keys: - - v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-deps-root-weekly-{{ checksum "cache_date" }} + - v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }} - run: name: Install Node Modules command: | @@ -274,7 +289,7 @@ commands: steps: - save_cache: name: Saving node modules for root, cli, and all globbed workspace packages - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-node-modules-cache-{{ checksum "circle_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }} paths: - node_modules - cli/node_modules @@ -285,18 +300,18 @@ commands: steps: - save_cache: name: Saving node modules for root, cli, and all globbed workspace packages - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-node-modules-cache-{{ checksum "circle_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }} paths: - /tmp/node_modules_cache - - run: touch node_modules_installed + - run: touch /tmp/node_modules_installed - save_cache: name: Saving node-modules cache state key - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-node-modules-cache-state-{{ checksum "circle_cache_key" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-state-{{ checksum "circle_cache_key" }} paths: - - node_modules_installed + - /tmp/node_modules_installed - save_cache: name: Save weekly yarn cache - key: v{{ .Environment.CACHE_VERSION }}-{{ arch }}-test2-deps-root-weekly-{{ checksum "cache_date" }} + key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }} paths: - ~/.yarn @@ -510,6 +525,24 @@ commands: path: /tmp/artifacts - store-npm-logs + run-binary-system-tests: + steps: + - restore_cached_workspace + - restore_cached_system_tests_deps + - run: + name: Run system tests + command: | + ALL_SPECS=`circleci tests glob "$HOME/cypress/system-tests/test-binary/*spec*"` + SPECS=`echo $ALL_SPECS | xargs -n 1 | circleci tests split --split-by=timings` + echo SPECS=$SPECS + yarn workspace @tooling/system-tests test:ci $SPECS + - verify-mocha-results + - store_test_results: + path: /tmp/cypress + - store_artifacts: + path: /tmp/artifacts + - store-npm-logs + store-npm-logs: description: Saves any NPM debug logs as artifacts in case there is a problem steps: @@ -1205,6 +1238,20 @@ jobs: - restore_cached_workspace - update_cached_system_tests_deps + binary-system-tests: + parallelism: 2 + working_directory: ~/cypress + environment: + <<: *defaultsEnvironment + PLATFORM: linux + machine: + # using `machine` gives us a Linux VM that can run Docker + image: ubuntu-2004:202111-02 + docker_layer_caching: true + resource_class: medium + steps: + - run-binary-system-tests + system-tests-chrome: <<: *defaults resource_class: medium @@ -1948,14 +1995,22 @@ jobs: repo: cypress-example-recipes command: npm run test:ci:firefox - "test-binary-against-recipes-chrome": + test-binary-against-recipes-chrome: <<: *defaults + parallelism: 3 steps: - test-binary-against-repo: repo: cypress-example-recipes - browser: chrome command: npm run test:ci:chrome + test-binary-against-recipes: + <<: *defaults + parallelism: 3 + steps: + - test-binary-against-repo: + repo: cypress-example-recipes + command: npm run test:ci + # This is a special job. It allows you to test the current # built test runner against a pull request in the repo # cypress-example-recipes. @@ -2012,33 +2067,6 @@ jobs: repo: cypress-example-todomvc browser: firefox - test-binary-against-conduit-chrome: - <<: *defaults - resource_class: medium - steps: - - test-binary-against-repo: - repo: cypress-example-conduit-app - browser: chrome - command: "npm run cypress:run" - wait-on: http://localhost:3000 - - test-binary-against-api-testing-firefox: - <<: *defaults - steps: - - test-binary-against-repo: - repo: cypress-example-api-testing - browser: firefox - command: "npm run cy:run" - - test-binary-against-piechopper-firefox: - <<: *defaults - resource_class: medium - steps: - - test-binary-against-repo: - repo: cypress-example-piechopper - browser: firefox - command: "npm run cypress:run" - test-binary-against-cypress-realworld-app: <<: *defaults resource_class: medium+ @@ -2165,6 +2193,7 @@ linux-workflow: &linux-workflow requires: - build - system-tests-node-modules-install: + context: test-runner:performance-tracking requires: - build - system-tests-chrome: @@ -2343,17 +2372,10 @@ linux-workflow: &linux-workflow <<: *mainBuildFilters requires: - create-build-artifacts - - test-binary-against-kitchensink-chrome: <<: *mainBuildFilters requires: - create-build-artifacts - # Re-enable when the cypress-example-conduit-app project is fixed. - # https://github.com/cypress-io/cypress-example-conduit-app/issues/346 - # - test-binary-against-conduit-chrome: - # <<: *mainBuildFilters - # requires: - # - create-build-artifacts - test-binary-against-recipes-firefox: <<: *mainBuildFilters requires: @@ -2362,19 +2384,15 @@ linux-workflow: &linux-workflow <<: *mainBuildFilters requires: - create-build-artifacts - - test-binary-against-kitchensink-firefox: - <<: *mainBuildFilters - requires: - - create-build-artifacts - - test-binary-against-todomvc-firefox: + - test-binary-against-recipes: <<: *mainBuildFilters requires: - create-build-artifacts - - test-binary-against-api-testing-firefox: + - test-binary-against-kitchensink-firefox: <<: *mainBuildFilters requires: - create-build-artifacts - - test-binary-against-piechopper-firefox: + - test-binary-against-todomvc-firefox: <<: *mainBuildFilters requires: - create-build-artifacts @@ -2382,17 +2400,19 @@ linux-workflow: &linux-workflow <<: *mainBuildFilters requires: - create-build-artifacts - - test-binary-as-specific-user: name: "test binary as a non-root user" executor: non-root-docker-user requires: - create-build-artifacts - - test-binary-as-specific-user: name: "test binary as a root user" requires: - create-build-artifacts + - binary-system-tests: + requires: + - create-build-artifacts + - system-tests-node-modules-install mac-workflow: &mac-workflow jobs: @@ -2433,19 +2453,6 @@ mac-workflow: &mac-workflow requires: - darwin-build - - test-binary-against-kitchensink: - name: darwin-test-binary-against-kitchensink - executor: mac - requires: - - darwin-create-build-artifacts - - - test-binary-against-staging: - context: test-runner:record-tests - name: darwin-test-binary-against-staging - executor: mac - requires: - - darwin-create-build-artifacts - - test-binary-and-npm-against-other-projects: context: test-runner:trigger-test-jobs name: darwin-test-binary-and-npm-against-other-projects diff --git a/guides/release-process.md b/guides/release-process.md index ce2c0e8e5392..44a8b0c1b678 100644 --- a/guides/release-process.md +++ b/guides/release-process.md @@ -197,10 +197,8 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy - [cypress-example-todomvc-redux](https://github.com/cypress-io/cypress-example-todomvc-redux/issues/1) - [cypress-example-realworld](https://github.com/cypress-io/cypress-example-realworld/issues/2) - [cypress-example-recipes](https://github.com/cypress-io/cypress-example-recipes/issues/225) - - [cypress-example-api-testing](https://github.com/cypress-io/cypress-example-api-testing/issues/15) - [angular-pizza-creator](https://github.com/cypress-io/angular-pizza-creator/issues/5) - [cypress-fiddle](https://github.com/cypress-io/cypress-fiddle/issues/5) - - [cypress-example-piechopper](https://github.com/cypress-io/cypress-example-piechopper/issues/75) - [cypress-documentation](https://github.com/cypress-io/cypress-documentation/issues/1313) - [cypress-example-docker-compose](https://github.com/cypress-io/cypress-example-docker-compose) - Doesn't have a Renovate issue, but will auto-create and auto-merge non-major Cypress updates as long as the tests pass. @@ -209,11 +207,6 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy **Test Repos** - [cypress-test-tiny](https://github.com/cypress-io/cypress-test-tiny) - - [cypress-test-nested-projects](https://github.com/cypress-io/cypress-test-nested-projects) - - [cypress-test-example-repos](https://github.com/cypress-io/cypress-test-example-repos) - - [cypress-test-node-versions](https://github.com/cypress-io/cypress-test-node-versions) - - [cypress-test-module-api](https://github.com/cypress-io/cypress-test-module-api) - - [cypress-test-ci-environments](https://github.com/cypress-io/cypress-test-ci-environments) **Example Repos** @@ -222,8 +215,6 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy - [cypress-example-realworld](https://github.com/cypress-io/cypress-example-realworld) - [cypress-example-recipes](https://github.com/cypress-io/cypress-example-recipes) - [cypress-example-docker-compose](https://github.com/cypress-io/cypress-example-docker-compose) - - [cypress-example-api-testing](https://github.com/cypress-io/cypress-example-api-testing) - - [cypress-example-piechopper](https://github.com/cypress-io/cypress-example-piechopper) - [cypress-documentation](https://github.com/cypress-io/cypress-documentation) Take a break, you deserve it! :sunglasses: diff --git a/npm/create-cypress-tests/package.json b/npm/create-cypress-tests/package.json index 1082bb51092a..f6dbf6fdb964 100644 --- a/npm/create-cypress-tests/package.json +++ b/npm/create-cypress-tests/package.json @@ -23,7 +23,7 @@ "commander": "6.1.0", "fast-glob": "3.2.7", "find-up": "5.0.0", - "fs-extra": "^9.0.1", + "fs-extra": "^9.1.0", "glob": "^7.1.6", "inquirer": "7.3.3", "ora": "^5.1.0", diff --git a/npm/react/CHANGELOG.md b/npm/react/CHANGELOG.md index c4328a97ab73..4ae096533d09 100644 --- a/npm/react/CHANGELOG.md +++ b/npm/react/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/react-v5.12.3](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.12.2...@cypress/react-v5.12.3) (2022-02-10) + + +### Bug Fixes + +* set correct default when using react-scripts plugin ([#20141](https://github.com/cypress-io/cypress/issues/20141)) ([9b967e0](https://github.com/cypress-io/cypress/commit/9b967e06f5df1e8ae2c5b13d5c7f7170b069f5bc)) + # [@cypress/react-v5.12.2](https://github.com/cypress-io/cypress/compare/@cypress/react-v5.12.1...@cypress/react-v5.12.2) (2022-02-08) diff --git a/npm/react/plugins/react-scripts/findReactScriptsWebpackConfig.js b/npm/react/plugins/react-scripts/findReactScriptsWebpackConfig.js index 48c8402f7051..98d9fc7fb058 100644 --- a/npm/react/plugins/react-scripts/findReactScriptsWebpackConfig.js +++ b/npm/react/plugins/react-scripts/findReactScriptsWebpackConfig.js @@ -7,9 +7,11 @@ const { getTranspileFolders } = require('../utils/get-transpile-folders') const { addFolderToBabelLoaderTranspileInPlace } = require('../utils/babel-helpers') const { reactScriptsFiveModifications, isReactScripts5 } = require('../../dist/react-scripts/reactScriptsFive') -module.exports = function findReactScriptsWebpackConfig (config, { - webpackConfigPath, -} = { webpackConfigPath: 'react-scripts/config/webpack.config' }) { +module.exports = function findReactScriptsWebpackConfig (config, devServerOptions) { + const webpackConfigPath = (devServerOptions && devServerOptions.webpackConfigPath) + ? devServerOptions.webpackConfigPath + : 'react-scripts/config/webpack.config' + // this is required because // 1) we use our own HMR and we don't need react-refresh transpiling overhead // 2) it doesn't work with process.env=test @see https://github.com/cypress-io/cypress-realworld-app/pull/832 diff --git a/npm/vue/CHANGELOG.md b/npm/vue/CHANGELOG.md index 7bf89fc516c0..36a6bc47d375 100644 --- a/npm/vue/CHANGELOG.md +++ b/npm/vue/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@cypress/vue-v3.1.1](https://github.com/cypress-io/cypress/compare/@cypress/vue-v3.1.0...@cypress/vue-v3.1.1) (2022-02-10) + + +### Bug Fixes + +* create a dummy commit to trigger release ([97e6c14](https://github.com/cypress-io/cypress/commit/97e6c14b91661658b856038da8a0f5fa4319b19b)) + # [@cypress/vue-v3.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue-v3.0.5...@cypress/vue-v3.1.0) (2021-12-16) diff --git a/npm/vue/src/shims-vue.d.ts b/npm/vue/src/shims-vue.d.ts index a1dbcdb9adfe..f43ed923b679 100644 --- a/npm/vue/src/shims-vue.d.ts +++ b/npm/vue/src/shims-vue.d.ts @@ -2,4 +2,4 @@ declare module '*.vue' { import { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component - } \ No newline at end of file +} \ No newline at end of file diff --git a/npm/webpack-batteries-included-preprocessor/package.json b/npm/webpack-batteries-included-preprocessor/package.json index 200afb6f7db5..1844b995deb1 100644 --- a/npm/webpack-batteries-included-preprocessor/package.json +++ b/npm/webpack-batteries-included-preprocessor/package.json @@ -37,7 +37,7 @@ "eslint-plugin-json-format": "^2.0.1", "eslint-plugin-mocha": "^8.1.0", "eslint-plugin-react": "^7.22.0", - "fs-extra": "^9.0.1", + "fs-extra": "^9.1.0", "graphql": "14.0.0", "mocha": "^8.1.1", "react": "^16.13.1", diff --git a/npm/webpack-preprocessor/package.json b/npm/webpack-preprocessor/package.json index 0e637d374299..5094cca30b70 100644 --- a/npm/webpack-preprocessor/package.json +++ b/npm/webpack-preprocessor/package.json @@ -46,7 +46,7 @@ "eslint-plugin-mocha": "8.1.0", "fast-glob": "3.1.1", "find-webpack": "1.5.0", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "mocha": "^7.1.0", "mockery": "2.1.0", "proxyquire": "2.1.3", diff --git a/package.json b/package.json index d4cea2eb2f9b..a1c3bb8215d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cypress", - "version": "9.4.1", + "version": "9.5.0", "description": "Cypress.io end to end testing tool", "private": true, "scripts": { @@ -106,7 +106,7 @@ "@types/enzyme-adapter-react-16": "1.0.5", "@types/execa": "0.9.0", "@types/fluent-ffmpeg": "^2.1.18", - "@types/fs-extra": "^8.0.1", + "@types/fs-extra": "^9.0.13", "@types/getenv": "^1.0.0", "@types/glob": "7.1.1", "@types/gulp": "^4.0.9", @@ -162,7 +162,7 @@ "execa-wrap": "1.4.0", "filesize": "4.1.2", "find-package-json": "1.2.0", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "getenv": "^1.0.0", "gift": "0.10.2", "glob": "7.1.6", diff --git a/packages/data-context/src/data/ProjectConfigIpc.ts b/packages/data-context/src/data/ProjectConfigIpc.ts index d24b06541b95..84a514701530 100644 --- a/packages/data-context/src/data/ProjectConfigIpc.ts +++ b/packages/data-context/src/data/ProjectConfigIpc.ts @@ -72,6 +72,7 @@ export class ProjectConfigIpc extends EventEmitter { * When the config is loaded, it comes back with either a "reply", or an "error" if there was a problem * sourcing the config (script error, etc.) */ + once(evt: 'ready', listener: () => void): this once(evt: 'loadConfig:reply', listener: (payload: SerializedLoadConfigReply) => void): this once(evt: 'loadConfig:error', listener: (err: SerializedError) => void): this diff --git a/packages/data-context/src/data/ProjectLifecycleManager.ts b/packages/data-context/src/data/ProjectLifecycleManager.ts index d853b418f7a1..1367a092aa3d 100644 --- a/packages/data-context/src/data/ProjectLifecycleManager.ts +++ b/packages/data-context/src/data/ProjectLifecycleManager.ts @@ -1035,7 +1035,9 @@ export class ProjectLifecycleManager { }) debug('trigger the load of the file') - ipc.send('loadConfig') + ipc.once('ready', () => { + ipc.send('loadConfig') + }) return ipc } diff --git a/packages/driver/cypress/e2e/commands/actions/type.cy.js b/packages/driver/cypress/e2e/commands/actions/type.cy.js index 76ac875ede23..60f8db0a75db 100644 --- a/packages/driver/cypress/e2e/commands/actions/type.cy.js +++ b/packages/driver/cypress/e2e/commands/actions/type.cy.js @@ -610,8 +610,7 @@ describe('src/cy/commands/actions/type - #type', () => { targets.forEach((targetId) => { it(`${targetId}`, () => { - cy.get(`#target-${targetId}`).focus() - cy.get(`#target-${targetId}`).type('{enter}') + cy.get(`#target-${targetId}`).focus().type('{enter}') cy.get('li').eq(0).should('have.text', 'keydown') cy.get('li').eq(1).should('have.text', 'keypress') @@ -629,8 +628,7 @@ describe('src/cy/commands/actions/type - #type', () => { targets.forEach((targetId) => { it(`${targetId}`, () => { - cy.get(`#target-${targetId}`).focus() - cy.get(`#target-${targetId}`).type('{enter}') + cy.get(`#target-${targetId}`).focus().type('{enter}') cy.get('li').eq(0).should('have.text', 'keydown') cy.get('li').eq(1).should('have.text', 'keypress') @@ -646,17 +644,30 @@ describe('src/cy/commands/actions/type - #type', () => { }) const targets = [ - 'button-tag', - 'input-button', - 'input-image', - 'input-reset', - 'input-submit', + '#target-button-tag', + '#target-input-button', + '#target-input-image', + '#target-input-reset', + '#target-input-submit', ] describe(`triggers with single space`, () => { - targets.forEach((targetId) => { - it(targetId, () => { - cy.get(`#target-${targetId}`).focus().type(' ') + targets.forEach((target) => { + it(target, () => { + const events = [] + + $(target).on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + + cy.get(target).focus().type(' ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + 'click', + ]) + }) cy.get('li').eq(0).should('have.text', 'keydown') cy.get('li').eq(1).should('have.text', 'keypress') @@ -666,10 +677,61 @@ describe('src/cy/commands/actions/type - #type', () => { }) }) + describe(`does not trigger if keyup prevented`, () => { + targets.forEach((target) => { + it(`${target} does not fire click event`, () => { + const events = [] + + $(target) + .on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + .on('keyup', (evt) => { + evt.preventDefault() + }) + + cy.get(target).focus().type(' ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + ]) + }) + + cy.get('li').should('have.length', 3) + cy.get('li').eq(0).should('have.text', 'keydown') + cy.get('li').eq(1).should('have.text', 'keypress') + cy.get('li').eq(2).should('have.text', 'keyup') + }) + }) + }) + describe('triggers after other characters', () => { - targets.forEach((targetId) => { - it(targetId, () => { - cy.get(`#target-${targetId}`).focus().type('asd ') + targets.forEach((target) => { + it(target, () => { + const events = [] + + $(target).on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + + cy.get(target).focus().type('asd ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'click', + ]) + }) cy.get('li').eq(12).should('have.text', 'click') }) diff --git a/packages/driver/cypress/e2e/commands/querying/focused.cy.js b/packages/driver/cypress/e2e/commands/querying/focused.cy.js new file mode 100644 index 000000000000..ff7873099e39 --- /dev/null +++ b/packages/driver/cypress/e2e/commands/querying/focused.cy.js @@ -0,0 +1,227 @@ +const { assertLogLength } = require('../../../support/utils') + +const { _ } = Cypress + +describe('src/cy/commands/querying', () => { + beforeEach(() => { + cy.visit('/fixtures/dom.html') + }) + + context('#focused', () => { + it('returns the activeElement', () => { + const $button = cy.$$('#button') + + $button.get(0).focus() + + expect(cy.state('document').activeElement).to.eq($button.get(0)) + + cy.focused().then(($focused) => { + expect($focused.get(0)).to.eq($button.get(0)) + }) + }) + + it('returns null if no activeElement', () => { + const $button = cy.$$('#button') + + $button.get(0).focus() + $button.get(0).blur() + + cy.focused().should('not.exist').then(($focused) => { + expect($focused).to.be.null + }) + }) + + describe('assertion verification', () => { + beforeEach(function () { + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'assert') { + this.lastLog = log + } + }) + + return null + }) + + it('eventually passes the assertion', () => { + cy.on('command:retry', _.after(2, () => { + cy.$$(':text:first').addClass('focused').focus() + })) + + cy.focused().should('have.class', 'focused').then(function () { + const { lastLog } = this + + expect(lastLog.get('name')).to.eq('assert') + expect(lastLog.get('state')).to.eq('passed') + + expect(lastLog.get('ended')).to.be.true + }) + }) + + // https://github.com/cypress-io/cypress/issues/409 + it('retries on an elements value', () => { + const $input = cy.$$('input:first') + + cy.on('command:retry', _.after(2, () => { + $input.val('1234') + + $input.get(0).focus() + })) + + cy.focused().should('have.value', '1234').then(function () { + const { lastLog } = this + + expect(lastLog.get('name')).to.eq('assert') + expect(lastLog.get('state')).to.eq('passed') + + expect(lastLog.get('ended')).to.be.true + }) + }) + }) + + describe('.log', () => { + beforeEach(function () { + cy.$$('input:first').get(0).focus() + + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'focused') { + this.lastLog = log + } + }) + + return null + }) + + it('is a parent command', () => { + cy.get('body').focused().then(function () { + const { lastLog } = this + + expect(lastLog.get('type')).to.eq('parent') + }) + }) + + it('ends immediately', () => { + cy.focused().then(function () { + const { lastLog } = this + + expect(lastLog.get('ended')).to.be.true + + expect(lastLog.get('state')).to.eq('passed') + }) + }) + + it('snapshots immediately', () => { + cy.focused().then(function () { + const { lastLog } = this + + expect(lastLog.get('snapshots').length).to.eq(1) + + expect(lastLog.get('snapshots')[0]).to.be.an('object') + }) + }) + + it('passes in $el', () => { + cy.get('input:first').focused().then(function ($input) { + const { lastLog } = this + + expect(lastLog.get('$el')).to.eq($input) + }) + }) + + it('#consoleProps', () => { + cy.get('input:first').focused().then(function ($input) { + expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ + Command: 'focused', + Yielded: $input.get(0), + Elements: 1, + }) + }) + }) + + it('#consoleProps with null element', () => { + const button = cy.$$('#button') + + button.get(0).focus() + button.get(0).blur() + + cy.focused().should('not.exist').then(function () { + expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ + Command: 'focused', + Yielded: '--nothing--', + Elements: 0, + }) + }) + }) + }) + + describe('errors', { + defaultCommandTimeout: 100, + }, () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + this.lastLog = log + + this.logs.push(log) + }) + + return null + }) + + it('fails waiting for the element to exist', (done) => { + const button = cy.$$('#button') + + button.get(0).focus() + button.get(0).blur() + + cy.on('fail', (err) => { + expect(err.message).to.include('Expected to find element: `focused`, but never found it.') + + done() + }) + + cy.focused() + }) + + it('fails waiting for the focused element not to exist', (done) => { + cy.$$('input:first').focus() + + cy.on('fail', (err) => { + expect(err.message).to.include('Expected not to exist in the DOM, but it was continuously found.') + + done() + }) + + cy.focused().should('not.exist') + }) + + it('eventually fails the assertion', function (done) { + cy.$$('input:first').focus() + + cy.on('fail', (err) => { + const { lastLog } = this + + expect(err.message).to.include(lastLog.get('error').message) + expect(err.message).not.to.include('undefined') + expect(lastLog.get('name')).to.eq('assert') + expect(lastLog.get('state')).to.eq('failed') + expect(lastLog.get('error')).to.be.an.instanceof(chai.AssertionError) + + done() + }) + + cy.focused().should('have.class', 'focused') + }) + + it('does not log an additional log on failure', function (done) { + cy.on('fail', () => { + assertLogLength(this.logs, 2) + + done() + }) + + cy.focused().should('have.class', 'focused') + }) + }) + }) +}) diff --git a/packages/driver/cypress/e2e/commands/querying/querying.cy.js b/packages/driver/cypress/e2e/commands/querying/querying.cy.js index 6882f42c8be4..151198bccd5e 100644 --- a/packages/driver/cypress/e2e/commands/querying/querying.cy.js +++ b/packages/driver/cypress/e2e/commands/querying/querying.cy.js @@ -7,331 +7,6 @@ describe('src/cy/commands/querying', () => { cy.visit('/fixtures/dom.html') }) - context('#focused', () => { - it('returns the activeElement', () => { - const $button = cy.$$('#button') - - $button.get(0).focus() - - expect(cy.state('document').activeElement).to.eq($button.get(0)) - - cy.focused().then(($focused) => { - expect($focused.get(0)).to.eq($button.get(0)) - }) - }) - - it('returns null if no activeElement', () => { - const $button = cy.$$('#button') - - $button.get(0).focus() - $button.get(0).blur() - - cy.focused().should('not.exist').then(($focused) => { - expect($focused).to.be.null - }) - }) - - describe('assertion verification', () => { - beforeEach(function () { - cy.on('log:added', (attrs, log) => { - if (log.get('name') === 'assert') { - this.lastLog = log - } - }) - - return null - }) - - it('eventually passes the assertion', () => { - cy.on('command:retry', _.after(2, () => { - cy.$$(':text:first').addClass('focused').focus() - })) - - cy.focused().should('have.class', 'focused').then(function () { - const { lastLog } = this - - expect(lastLog.get('name')).to.eq('assert') - expect(lastLog.get('state')).to.eq('passed') - - expect(lastLog.get('ended')).to.be.true - }) - }) - - // https://github.com/cypress-io/cypress/issues/409 - it('retries on an elements value', () => { - const $input = cy.$$('input:first') - - cy.on('command:retry', _.after(2, () => { - $input.val('1234') - - $input.get(0).focus() - })) - - cy.focused().should('have.value', '1234').then(function () { - const { lastLog } = this - - expect(lastLog.get('name')).to.eq('assert') - expect(lastLog.get('state')).to.eq('passed') - - expect(lastLog.get('ended')).to.be.true - }) - }) - }) - - describe('.log', () => { - beforeEach(function () { - cy.$$('input:first').get(0).focus() - - cy.on('log:added', (attrs, log) => { - if (log.get('name') === 'focused') { - this.lastLog = log - } - }) - - return null - }) - - it('is a parent command', () => { - cy.get('body').focused().then(function () { - const { lastLog } = this - - expect(lastLog.get('type')).to.eq('parent') - }) - }) - - it('ends immediately', () => { - cy.focused().then(function () { - const { lastLog } = this - - expect(lastLog.get('ended')).to.be.true - - expect(lastLog.get('state')).to.eq('passed') - }) - }) - - it('snapshots immediately', () => { - cy.focused().then(function () { - const { lastLog } = this - - expect(lastLog.get('snapshots').length).to.eq(1) - - expect(lastLog.get('snapshots')[0]).to.be.an('object') - }) - }) - - it('passes in $el', () => { - cy.get('input:first').focused().then(function ($input) { - const { lastLog } = this - - expect(lastLog.get('$el')).to.eq($input) - }) - }) - - it('#consoleProps', () => { - cy.get('input:first').focused().then(function ($input) { - expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ - Command: 'focused', - Yielded: $input.get(0), - Elements: 1, - }) - }) - }) - - it('#consoleProps with null element', () => { - const button = cy.$$('#button') - - button.get(0).focus() - button.get(0).blur() - - cy.focused().should('not.exist').then(function () { - expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ - Command: 'focused', - Yielded: '--nothing--', - Elements: 0, - }) - }) - }) - }) - - describe('errors', { - defaultCommandTimeout: 100, - }, () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - this.lastLog = log - - this.logs.push(log) - }) - - return null - }) - - it('fails waiting for the element to exist', (done) => { - const button = cy.$$('#button') - - button.get(0).focus() - button.get(0).blur() - - cy.on('fail', (err) => { - expect(err.message).to.include('Expected to find element: `focused`, but never found it.') - - done() - }) - - cy.focused() - }) - - it('fails waiting for the focused element not to exist', (done) => { - cy.$$('input:first').focus() - - cy.on('fail', (err) => { - expect(err.message).to.include('Expected not to exist in the DOM, but it was continuously found.') - - done() - }) - - cy.focused().should('not.exist') - }) - - it('eventually fails the assertion', function (done) { - cy.$$('input:first').focus() - - cy.on('fail', (err) => { - const { lastLog } = this - - expect(err.message).to.include(lastLog.get('error').message) - expect(err.message).not.to.include('undefined') - expect(lastLog.get('name')).to.eq('assert') - expect(lastLog.get('state')).to.eq('failed') - expect(lastLog.get('error')).to.be.an.instanceof(chai.AssertionError) - - done() - }) - - cy.focused().should('have.class', 'focused') - }) - - it('does not log an additional log on failure', function (done) { - cy.on('fail', () => { - assertLogLength(this.logs, 2) - - done() - }) - - cy.focused().should('have.class', 'focused') - }) - }) - }) - - context('#root', () => { - it('returns html', () => { - const html = cy.$$('html') - - cy.root().then(($html) => { - expect($html.get(0)).to.eq(html.get(0)) - }) - }) - - it('returns withinSubject if exists', () => { - const form = cy.$$('form') - - cy.get('form').within(() => { - cy - .get('input') - .root().then(($root) => { - expect($root.get(0)).to.eq(form.get(0)) - }) - }) - }) - - it('eventually resolves', () => { - _.delay(() => { - cy.$$('html').addClass('foo').addClass('bar') - } - , 100) - - cy.root().should('have.class', 'foo').and('have.class', 'bar') - }) - - describe('.log', () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - if (attrs.name === 'root') { - this.lastLog = log - - this.logs.push(log) - } - }) - - return null - }) - - it('can turn off logging', () => { - cy.root({ log: false }).then(function () { - expect(this.log).to.be.undefined - }) - }) - - it('logs immediately before resolving', (done) => { - cy.on('log:added', (attrs, log) => { - if (log.get('name') === 'root') { - expect(log.get('state')).to.eq('pending') - expect(log.get('message')).to.eq('') - - done() - } - }) - - cy.root() - }) - - it('snapshots after clicking', () => { - cy.root().then(function () { - const { lastLog } = this - - expect(lastLog.get('snapshots').length).to.eq(1) - - expect(lastLog.get('snapshots')[0]).to.be.an('object') - }) - }) - - it('sets $el to document', () => { - const html = cy.$$('html') - - cy.root().then(function () { - expect(this.lastLog.get('$el').get(0)).to.eq(html.get(0)) - }) - }) - - it('sets $el to withinSubject', () => { - const form = cy.$$('form') - - cy.get('form').within(() => { - cy - .get('input') - .root().then(function ($root) { - expect(this.lastLog.get('$el').get(0)).to.eq(form.get(0)) - }) - }) - }) - - it('consoleProps', () => { - cy.root().then(function ($root) { - const consoleProps = this.lastLog.invoke('consoleProps') - - expect(consoleProps).to.deep.eq({ - Command: 'root', - Yielded: $root.get(0), - }) - }) - }) - }) - }) - context('#get', { defaultCommandTimeout: 200, }, () => { diff --git a/packages/driver/cypress/e2e/commands/querying/root.cy.js b/packages/driver/cypress/e2e/commands/querying/root.cy.js new file mode 100644 index 000000000000..e01b4ea30f59 --- /dev/null +++ b/packages/driver/cypress/e2e/commands/querying/root.cy.js @@ -0,0 +1,114 @@ +const { _ } = Cypress + +describe('src/cy/commands/querying', () => { + beforeEach(() => { + cy.visit('/fixtures/dom.html') + }) + + context('#root', () => { + it('returns html', () => { + const html = cy.$$('html') + + cy.root().then(($html) => { + expect($html.get(0)).to.eq(html.get(0)) + }) + }) + + it('returns withinSubject if exists', () => { + const form = cy.$$('form') + + cy.get('form').within(() => { + cy + .get('input') + .root().then(($root) => { + expect($root.get(0)).to.eq(form.get(0)) + }) + }) + }) + + it('eventually resolves', () => { + _.delay(() => { + cy.$$('html').addClass('foo').addClass('bar') + } + , 100) + + cy.root().should('have.class', 'foo').and('have.class', 'bar') + }) + + describe('.log', () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + if (attrs.name === 'root') { + this.lastLog = log + + this.logs.push(log) + } + }) + + return null + }) + + it('can turn off logging', () => { + cy.root({ log: false }).then(function () { + expect(this.log).to.be.undefined + }) + }) + + it('logs immediately before resolving', (done) => { + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'root') { + expect(log.get('state')).to.eq('pending') + expect(log.get('message')).to.eq('') + + done() + } + }) + + cy.root() + }) + + it('snapshots after clicking', () => { + cy.root().then(function () { + const { lastLog } = this + + expect(lastLog.get('snapshots').length).to.eq(1) + + expect(lastLog.get('snapshots')[0]).to.be.an('object') + }) + }) + + it('sets $el to document', () => { + const html = cy.$$('html') + + cy.root().then(function () { + expect(this.lastLog.get('$el').get(0)).to.eq(html.get(0)) + }) + }) + + it('sets $el to withinSubject', () => { + const form = cy.$$('form') + + cy.get('form').within(() => { + cy + .get('input') + .root().then(function ($root) { + expect(this.lastLog.get('$el').get(0)).to.eq(form.get(0)) + }) + }) + }) + + it('consoleProps', () => { + cy.root().then(function ($root) { + const consoleProps = this.lastLog.invoke('consoleProps') + + expect(consoleProps).to.deep.eq({ + Command: 'root', + Yielded: $root.get(0), + }) + }) + }) + }) + }) +}) diff --git a/packages/driver/cypress/e2e/cypress/error_utils.cy.ts b/packages/driver/cypress/e2e/cypress/error_utils.cy.ts index 7a5078b5c50d..fcd5a0b81db5 100644 --- a/packages/driver/cypress/e2e/cypress/error_utils.cy.ts +++ b/packages/driver/cypress/e2e/cypress/error_utils.cy.ts @@ -622,4 +622,18 @@ describe('driver/src/cypress/error_utils', () => { expect(stack).not.to.include('removeMeAndAbove') }) }) + + context('.wrapErr', () => { + [ + { value: undefined, label: 'undefined' }, + { value: null, label: 'null' }, + { value: '', label: 'empty string' }, + { value: true, label: 'boolean' }, + { value: 1, label: 'number' }, + ].forEach((err) => { + it(`returns undefined if err is ${err.label}`, () => { + expect($errUtils.wrapErr(err.value)).to.be.undefined + }) + }) + }) }) diff --git a/packages/driver/cypress/fixtures/click-event-by-type.html b/packages/driver/cypress/fixtures/click-event-by-type.html index 5999d152f00a..69a53b00c86a 100644 --- a/packages/driver/cypress/fixtures/click-event-by-type.html +++ b/packages/driver/cypress/fixtures/click-event-by-type.html @@ -66,10 +66,10 @@ target.addEventListener("click", () => { updateLog("click"); }); - target.addEventListener("keyup", () => { + target.addEventListener("keyup", (event) => { updateLog("keyup"); }); }); - \ No newline at end of file + diff --git a/packages/driver/package.json b/packages/driver/package.json index ec3ca413ab74..7c7cfd83f065 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -81,7 +81,7 @@ "text-mask-addons": "3.8.0", "underscore.string": "3.3.5", "unfetch": "4.1.0", - "url-parse": "1.5.2", + "url-parse": "1.5.6", "vanilla-text-mask": "5.1.1", "vite": "2.5.0", "webpack": "^4.44.2", diff --git a/packages/driver/src/cy/commands/actions/type.ts b/packages/driver/src/cy/commands/actions/type.ts index 7d255a65d668..edeebb49186f 100644 --- a/packages/driver/src/cy/commands/actions/type.ts +++ b/packages/driver/src/cy/commands/actions/type.ts @@ -342,7 +342,9 @@ export default function (Commands, Cypress, cy, state, config) { // event.target is null when used with shadow DOM. (event.target && $elements.isButtonLike(event.target)) && // When a space key is pressed for input radio elements, the click event is only fired when it's not checked. - !(event.target.tagName === 'INPUT' && event.target.type === 'radio' && event.target.checked === true) + !(event.target.tagName === 'INPUT' && event.target.type === 'radio' && event.target.checked === true) && + // When event is prevented, the click event should not be emitted + !event.defaultPrevented ) { fireClickEvent(event.target) } diff --git a/packages/driver/src/cy/commands/querying/focused.ts b/packages/driver/src/cy/commands/querying/focused.ts new file mode 100644 index 000000000000..45f67366ae20 --- /dev/null +++ b/packages/driver/src/cy/commands/querying/focused.ts @@ -0,0 +1,70 @@ +import _ from 'lodash' +import Promise from 'bluebird' + +import $dom from '../../../dom' + +export default (Commands, Cypress, cy, state) => { + Commands.addAll({ + // TODO: any -> Partial + focused (options: any = {}) { + const userOptions = options + + options = _.defaults({}, userOptions, { + verify: true, + log: true, + }) + + if (options.log) { + options._log = Cypress.log({ timeout: options.timeout }) + } + + const log = ($el) => { + if (options.log === false) { + return + } + + options._log.set({ + $el, + consoleProps () { + const ret = $el ? $dom.getElements($el) : '--nothing--' + + return { + Yielded: ret, + Elements: $el != null ? $el.length : 0, + } + }, + }) + } + + const getFocused = () => { + const focused = cy.getFocused() + + log(focused) + + return focused + } + + const resolveFocused = () => { + return Promise + .try(getFocused) + .then(($el) => { + if (options.verify === false) { + return $el + } + + if (!$el) { + $el = $dom.wrap(null) + $el.selector = 'focused' + } + + // pass in a null jquery object for assertions + return cy.verifyUpcomingAssertions($el, options, { + onRetry: resolveFocused, + }) + }) + } + + return resolveFocused() + }, + }) +} diff --git a/packages/driver/src/cy/commands/querying/index.ts b/packages/driver/src/cy/commands/querying/index.ts index 2574bc7fb66d..9985cbae492b 100644 --- a/packages/driver/src/cy/commands/querying/index.ts +++ b/packages/driver/src/cy/commands/querying/index.ts @@ -1,7 +1,11 @@ +import * as Focused from './focused' import * as Querying from './querying' +import * as Root from './root' import * as Within from './within' export { + Focused, Querying, + Root, Within, } diff --git a/packages/driver/src/cy/commands/querying/querying.ts b/packages/driver/src/cy/commands/querying/querying.ts index e372a9605b72..aa80eeb3c268 100644 --- a/packages/driver/src/cy/commands/querying/querying.ts +++ b/packages/driver/src/cy/commands/querying/querying.ts @@ -9,68 +9,6 @@ import { getAliasedRequests, isDynamicAliasingPossible } from '../../net-stubbin export default (Commands, Cypress, cy, state) => { Commands.addAll({ - // TODO: any -> Partial - focused (options: any = {}) { - const userOptions = options - - options = _.defaults({}, userOptions, { - verify: true, - log: true, - }) - - if (options.log) { - options._log = Cypress.log({ timeout: options.timeout }) - } - - const log = ($el) => { - if (options.log === false) { - return - } - - options._log.set({ - $el, - consoleProps () { - const ret = $el ? $dom.getElements($el) : '--nothing--' - - return { - Yielded: ret, - Elements: $el != null ? $el.length : 0, - } - }, - }) - } - - const getFocused = () => { - const focused = cy.getFocused() - - log(focused) - - return focused - } - - const resolveFocused = () => { - return Promise - .try(getFocused) - .then(($el) => { - if (options.verify === false) { - return $el - } - - if (!$el) { - $el = $dom.wrap(null) - $el.selector = 'focused' - } - - // pass in a null jquery object for assertions - return cy.verifyUpcomingAssertions($el, options, { - onRetry: resolveFocused, - }) - }) - } - - return resolveFocused() - }, - // TODO: any -> Partial get (selector, options: any = {}) { const userOptions = options @@ -409,36 +347,6 @@ export default (Commands, Cypress, cy, state) => { return resolveElements() }, - - // TODO: any -> Partial - root (options: any = {}) { - const userOptions = options - - options = _.defaults({}, userOptions, { log: true }) - - if (options.log !== false) { - options._log = Cypress.log({ - message: '', - timeout: options.timeout, - }) - } - - const log = ($el) => { - if (options.log) { - options._log.set({ $el }) - } - - return $el - } - - const withinSubject = state('withinSubject') - - if (withinSubject) { - return log(withinSubject) - } - - return cy.now('get', 'html', { log: false }).then(log) - }, }) Commands.addAll({ prevSubject: ['optional', 'window', 'document', 'element'] }, { diff --git a/packages/driver/src/cy/commands/querying/root.ts b/packages/driver/src/cy/commands/querying/root.ts new file mode 100644 index 000000000000..a9e12fd16d66 --- /dev/null +++ b/packages/driver/src/cy/commands/querying/root.ts @@ -0,0 +1,35 @@ +import _ from 'lodash' + +export default (Commands, Cypress, cy, state) => { + Commands.addAll({ + // TODO: any -> Partial + root (options: any = {}) { + const userOptions = options + + options = _.defaults({}, userOptions, { log: true }) + + if (options.log !== false) { + options._log = Cypress.log({ + message: '', + timeout: options.timeout, + }) + } + + const log = ($el) => { + if (options.log) { + options._log.set({ $el }) + } + + return $el + } + + const withinSubject = state('withinSubject') + + if (withinSubject) { + return log(withinSubject) + } + + return cy.now('get', 'html', { log: false }).then(log) + }, + }) +} diff --git a/packages/driver/src/cypress/error_utils.ts b/packages/driver/src/cypress/error_utils.ts index 9c58d6950aa8..3d001726ecff 100644 --- a/packages/driver/src/cypress/error_utils.ts +++ b/packages/driver/src/cypress/error_utils.ts @@ -52,8 +52,14 @@ const prepareErrorForSerialization = (err) => { return err } +// some errors, probably from user callbacks, might be boolean, number or falsy values +// which means serializing will not provide any useful context +const isSerializableError = (err) => { + return !!err && (typeof err === 'object' || typeof err === 'string') +} + const wrapErr = (err) => { - if (!err) return + if (!isSerializableError(err)) return prepareErrorForSerialization(err) diff --git a/packages/electron/package.json b/packages/electron/package.json index e89e833e32e0..1b99966c2e00 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -18,7 +18,7 @@ "@cypress/icons": "0.7.0", "bluebird": "3.5.3", "debug": "^4.3.2", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "lodash": "^4.17.21", "minimist": "1.2.5" }, diff --git a/packages/extension/package.json b/packages/extension/package.json index 61001bd3302a..6e636aa74cab 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -26,7 +26,7 @@ "coffeescript": "1.12.7", "cross-env": "6.0.3", "eol": "0.9.1", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "gulp": "4.0.2", "gulp-clean": "0.4.0", "gulp-rename": "1.4.0", diff --git a/packages/https-proxy/package.json b/packages/https-proxy/package.json index c67d98aa1cb2..42a9e3c6269c 100644 --- a/packages/https-proxy/package.json +++ b/packages/https-proxy/package.json @@ -16,7 +16,7 @@ "dependencies": { "bluebird": "3.5.3", "debug": "^4.3.2", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "lodash": "^4.17.21", "node-forge": "1.0.0", "rimraf": "3.0.2", diff --git a/packages/launcher/package.json b/packages/launcher/package.json index 051ef296e966..6f7cf341ae27 100644 --- a/packages/launcher/package.json +++ b/packages/launcher/package.json @@ -15,7 +15,7 @@ "bluebird": "3.5.3", "debug": "^4.3.2", "execa": "4.0.0", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "lodash": "^4.17.21", "plist": "3.0.1", "semver": "7.3.5", diff --git a/packages/net-stubbing/lib/server/util.ts b/packages/net-stubbing/lib/server/util.ts index c33d4be1464f..68d31bc26f9d 100644 --- a/packages/net-stubbing/lib/server/util.ts +++ b/packages/net-stubbing/lib/server/util.ts @@ -251,6 +251,10 @@ export function getBodyEncoding (req: CyHttpMessages.IncomingRequest): BodyEncod if (contentType.includes('charset=utf-8') || contentType.includes('charset="utf-8"')) { return 'utf8' } + + if (contentType.includes('multipart/form-data')) { + return 'binary' + } } // with fallback to inspecting the buffer using diff --git a/packages/net-stubbing/test/unit/util-spec.ts b/packages/net-stubbing/test/unit/util-spec.ts index 3775f48ffa8d..f2e1db390975 100644 --- a/packages/net-stubbing/test/unit/util-spec.ts +++ b/packages/net-stubbing/test/unit/util-spec.ts @@ -69,5 +69,19 @@ describe('net-stubbing util', () => { expect(getBodyEncoding(req), 'image').to.equal('binary') }) + + it('returns binary for form-data bodies', () => { + const formDataRequest = { + body: Buffer.from('hello world'), + headers: { + 'content-type': 'multipart/form-data', + }, + method: 'POST', + url: 'somewhere', + httpVersion: '1.1', + } + + expect(getBodyEncoding(formDataRequest)).to.equal('binary') + }) }) }) diff --git a/packages/network/package.json b/packages/network/package.json index 90326c5f64f3..851e742ebb8e 100644 --- a/packages/network/package.json +++ b/packages/network/package.json @@ -17,7 +17,7 @@ "bluebird": "3.5.3", "concat-stream": "1.6.2", "debug": "^4.3.2", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "lodash": "^4.17.21", "node-forge": "1.0.0", "proxy-from-env": "1.0.0" diff --git a/packages/resolve-dist/package.json b/packages/resolve-dist/package.json index e4ddb37d33bf..f5bf02d1a861 100644 --- a/packages/resolve-dist/package.json +++ b/packages/resolve-dist/package.json @@ -13,7 +13,7 @@ "test-watch": "yarn test-unit --watch" }, "dependencies": { - "fs-extra": "8.1.0" + "fs-extra": "9.1.0" }, "devDependencies": { "@packages/ts": "0.0.0-development", diff --git a/packages/rewriter/package.json b/packages/rewriter/package.json index 5b061bbc4451..982c40a6cd85 100644 --- a/packages/rewriter/package.json +++ b/packages/rewriter/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@cypress/request-promise": "4.2.6", "@types/parse5-html-rewriting-stream": "5.1.1", - "fs-extra": "9.0.0", + "fs-extra": "9.1.0", "nock": "12.0.3", "rimraf": "3.0.2", "sinon": "9.0.2", diff --git a/packages/runner/package.json b/packages/runner/package.json index 6694ddbcec6e..89d37566b94a 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -17,7 +17,7 @@ "watch": "webpack --watch --progress" }, "dependencies": { - "fs-extra": "8.1.0" + "fs-extra": "9.1.0" }, "devDependencies": { "@cypress/design-system": "0.0.0-development", diff --git a/packages/server/lib/plugins/child/run_require_async_child.js b/packages/server/lib/plugins/child/run_require_async_child.js index 1d5b5ba84b27..29b87f6d0ea1 100644 --- a/packages/server/lib/plugins/child/run_require_async_child.js +++ b/packages/server/lib/plugins/child/run_require_async_child.js @@ -152,6 +152,8 @@ function run (ipc, configFile, projectRoot) { )) } }) + + ipc.send('ready') } module.exports = run diff --git a/packages/server/package.json b/packages/server/package.json index b183c5fc3407..404a6567aebc 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -63,7 +63,7 @@ "firefox-profile": "4.0.0", "fix-path": "3.0.0", "fluent-ffmpeg": "2.1.2", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "get-port": "5.1.1", "getenv": "1.0.0", "getos": "3.2.1", @@ -119,7 +119,7 @@ "ts-node": "^10.2.1", "tslib": "2.3.1", "underscore.string": "3.3.5", - "url-parse": "1.5.2", + "url-parse": "1.5.6", "uuid": "8.3.2", "which": "2.0.2", "widest-line": "3.1.0" diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js index af21f40d4e7f..7204a69ef307 100644 --- a/packages/server/test/unit/plugins/index_spec.js +++ b/packages/server/test/unit/plugins/index_spec.js @@ -147,15 +147,19 @@ describe.skip('lib/plugins/index', () => { }) }) - it('sends \'load\' event with config via ipc', () => { - ipc.on.withArgs('loaded').yields([]) + it('sends \'load\' event with config via ipc once it receives \'ready\'', () => { const config = { pluginsFile: 'cypress-plugin', testingType: 'e2e' } - return plugins.init(config, getOptions({ testingType: 'e2e' }), ctx).then(() => { - expect(ipc.send).to.be.calledWith('load', { - ...config, - ...configExtras, - }) + plugins.init(config, getOptions({ testingType: 'e2e' })) + + expect(ipc.send).to.not.be.called + + // simulate async ready event + ipc.on.withArgs('ready').firstCall.callback() + + expect(ipc.send).to.be.calledWith('load', { + ...config, + ...configExtras, }) }) diff --git a/scripts/binary/bump.js b/scripts/binary/bump.js index b7cc4dd8dd40..f368f9aadc2a 100644 --- a/scripts/binary/bump.js +++ b/scripts/binary/bump.js @@ -16,20 +16,7 @@ const _PROVIDERS = { circle: { main: 'cypress-io/cypress', linux: [ - 'cypress-io/cypress-test-tiny', 'cypress-io/cypress-test-module-api', - 'cypress-io/cypress-test-node-versions', - 'cypress-io/cypress-test-nested-projects', - 'cypress-io/cypress-test-ci-environments', - 'cypress-io/cypress-test-example-repos', - ], - darwin: [ - 'cypress-io/cypress-test-tiny', - 'cypress-io/cypress-test-example-repos', - ], - win32: [ - 'cypress-io/cypress-test-tiny', - 'cypress-io/cypress-test-example-repos', ], }, } diff --git a/scripts/get-next-version.js b/scripts/get-next-version.js index b389a28dc7f6..949e9ba7fc22 100644 --- a/scripts/get-next-version.js +++ b/scripts/get-next-version.js @@ -24,6 +24,12 @@ const getNextVersionForPath = async (path) => { return semver.inc(currentVersion, releaseType || 'patch') } +if (require.main !== module) { + module.exports.getNextVersionForPath = getNextVersionForPath + + return +} + Bluebird.mapSeries(paths, async (path) => { const pathNextVersion = await getNextVersionForPath(path) diff --git a/scripts/unit/binary/bump-spec.js b/scripts/unit/binary/bump-spec.js index 4ebc06e0bf89..e5c6acb6dbc7 100644 --- a/scripts/unit/binary/bump-spec.js +++ b/scripts/unit/binary/bump-spec.js @@ -29,7 +29,7 @@ describe('bump', () => { ) }) - it('returns a filter function for circle and darwin', () => { + it('returns a filter function for circle and linux', () => { const projects = bump.remapProjects(bump._PROVIDERS) la( @@ -38,11 +38,11 @@ describe('bump', () => { projects, ) - const filter = bump.getFilterByProvider('circle', 'darwin') + const filter = bump.getFilterByProvider('circle', 'linux') const filtered = projects.filter(filter) la(filtered.length, 'there should be at least a few projects', filtered) - snapshot('should have just circle and darwin projects', filtered) + snapshot('should have just circle and linux projects', filtered) }) }) }) diff --git a/system-tests/README.md b/system-tests/README.md index 2a3ad47b4b70..a3504765de02 100644 --- a/system-tests/README.md +++ b/system-tests/README.md @@ -49,7 +49,7 @@ describe('my new project', () => { systemTests.setup() systemTests.it('fails as expected', { - project: Fixtures.projectPath('my-new-project'), + project: 'my-new-project', snapshot: true, spec: '*', expectedExitCode: 2 @@ -61,6 +61,36 @@ From here, you could run this test with `yarn test my-new-project`. There are many more options available for `systemTests.it` and `systemTests.setup`. You can massage the stdout, do pre-run tasks, set up HTTP/S servers, and more. Explore the typedocs in [`./lib/system-tests`](./lib/system-tests) for more information. +These tests run in the `system-tests-*` CI jobs. + +### Developing Docker-based tests against built binary + +Specs in the [`./test`](./test) directory are run against an unbuilt Cypress App. They don't test `cypress` NPM package installation or other prod app behavior. This is done so that they can run as fast as possible in CI, without waiting for a full build of the Cypress App. + +Specs in [`./test-binary`](./test-binary) are run against the *built Cypress App*. They also run inside of their own Docker containers to give a blank slate environment for Cypress to run in. Before each test, the prod CLI is `npm install`ed along with the built Cypress `.zip`, and real `cypress run` commands are used to run the tests. There should be no functional difference between running a project in these tests and running real prod Cypress inside of Docker in CI. + +The purpose of these tests is to test things that we normally can't inside of regular `system-tests`, such as testing Cypress with different Node versions, with/without Xvfb, or inside of different operating system versions. + +An example of using `dockerImage` and `withBinary` to write a binary system test: + +```ts +// ./test-binary/node-versions.spec.ts +import systemTests from '../lib/system-tests' +import Fixtures from '../lib/fixtures' + +describe('node versions', () => { + systemTests.it('runs in node 12', { + dockerImage: 'cypress:node/12', + project: 'todos', + withBinary: true, + }) +}) +``` + +Running `yarn test node-versions` would spin up a local Docker container for `cypress:node/12`, install Cypress from `../cypress.zip` and `../cli/build`, and then call the regular `cypress run` command within the container. Other options for `systemTests.it` such as `onRun` and `expectedExitCode` still function normally. + +These tests run in the `binary-system-tests` CI job. + ### Updating Snaphots Prepend `SNAPSHOT_UPDATE=1` to any test command. See [`snap-shot-it` instructions](https://github.com/bahmutov/snap-shot-it#advanced-use) for more info. diff --git a/system-tests/lib/docker.ts b/system-tests/lib/docker.ts new file mode 100644 index 000000000000..9d82af21d81b --- /dev/null +++ b/system-tests/lib/docker.ts @@ -0,0 +1,151 @@ +import type { SpawnerResult, Spawner } from './system-tests' +import Docker from 'dockerode' +import stream from 'stream' +import EventEmitter from 'events' +import path from 'path' +import { promises as fs } from 'fs' +import execa from 'execa' +import Fixtures from './fixtures' +import { nock } from './spec_helper' + +let docker: Docker | null = null + +const getDocker = () => { + return docker || (docker = new Docker()) +} + +const log = (...args) => { + console.error('🐋', ...args) +} + +class DockerProcess extends EventEmitter implements SpawnerResult { + stdout = new stream.PassThrough() + stderr = new stream.PassThrough() + + constructor (private dockerImage: string) { + super() + } + + pull () { + return new Promise((resolve, reject) => { + log('Pulling image', this.dockerImage) + getDocker().pull(this.dockerImage, null, (err, stream) => { + if (err) return reject(err) + + const onFinished = (err) => { + log('Pull complete', { err }) + if (err) return reject(err) + + resolve() + } + + const onProgress = (event) => { + log('Pull progress', JSON.stringify(event)) + } + + docker.modem.followProgress(stream, onFinished, onProgress) + }, null) + }) + } + + run (opts: { + cmd: string + args: string[] + env: Record + }) { + const containerCreateEnv = [] + + for (const k in opts.env) { + // skip problematic env vars that we don't wanna preserve from `process.env` + if (['DISPLAY', 'USER', 'HOME', 'USERNAME', 'PATH'].includes(k)) continue + + containerCreateEnv.push([k, opts.env[k]].join('=')) + } + + log('Running image', this.dockerImage) + + const cmd = [opts.cmd, ...opts.args] + + log('Running cmd', cmd.join(' ')) + + getDocker().run( + this.dockerImage, + cmd, + [this.stdout, this.stderr], + // option docs: https://docs.docker.com/engine/api/v1.37/#operation/ContainerCreate + { + AutoRemove: true, + Entrypoint: 'bash', + Tty: false, // so we can use stdout and stderr + Env: containerCreateEnv, + Binds: [ + [path.join(__dirname, '..', '..'), '/cypress'], + // map tmpDir to the same absolute path on the container to make it easier to reason about paths in tests + [Fixtures.cyTmpDir, Fixtures.cyTmpDir], + ].map((a) => a.join(':')), + }, + // option docs: https://docs.docker.com/engine/api/v1.37/#operation/ContainerStart + {}, + (err, data) => { + if (err) { + log('Docker run errored:', { err, data }) + + return this.emit('error', err) + } + + log('Docker run exited:', { err, data }) + this.emit('exit', data.StatusCode) + }, + ) + } +} + +const checkBuiltBinary = async () => { + try { + await fs.stat(path.join(__dirname, '..', '..', 'cypress.zip')) + } catch (err) { + throw new Error('Expected built cypress.zip at project root. Run `yarn binary-build` and `yarn binary-zip`.') + } + + try { + await fs.stat(path.join(__dirname, '..', '..', 'cli/build/package.json')) + } catch (err) { + throw new Error('Expected built CLI in /cli/build. Run `yarn build` in `cli`.') + } +} + +export const dockerSpawner: Spawner = async (cmd, args, env, options) => { + await checkBuiltBinary() + + const projectPath = Fixtures.projectPath(options.project) + + log('Running chmod 0777 on', projectPath, 'to avoid Docker permissions issues.') + await execa('chmod', `-R 0777 ${projectPath}`.split(' ')) + + const proc = new DockerProcess(options.dockerImage) + + nock.enableNetConnect('localhost') + + await proc.pull() + + if (options.withBinary) { + args = [cmd, ...args] + cmd = `/cypress/system-tests/scripts/bootstrap-docker-container.sh` + } else { + throw new Error('Docker testing is only supported with built binaries (withBinary: true)') + } + + env = { + ...env, + TEST_PROJECT_DIR: projectPath, + REPO_DIR: '/cypress', + } + + proc.run({ + cmd, + args, + env, + }) + + return proc +} diff --git a/system-tests/lib/fixtures.ts b/system-tests/lib/fixtures.ts index cca6884924d6..27aefa8941bb 100644 --- a/system-tests/lib/fixtures.ts +++ b/system-tests/lib/fixtures.ts @@ -9,7 +9,8 @@ const root = _path.join(__dirname, '..') const serverRoot = _path.join(__dirname, '../../packages/server/') const projects = _path.join(root, 'projects') -const cyTmpDir = _path.join(tempDir, 'cy-projects') + +export const cyTmpDir = _path.join(tempDir, 'cy-projects') const safeRemove = (path) => { try { diff --git a/system-tests/lib/performance-reporter.js b/system-tests/lib/performance-reporter.js index 8200ae4effca..234b1a2858e8 100644 --- a/system-tests/lib/performance-reporter.js +++ b/system-tests/lib/performance-reporter.js @@ -1,12 +1,62 @@ const path = require('path') const chalk = require('chalk') const Libhoney = require('libhoney') +const { v4: uuidv4 } = require('uuid') -const pkg = require('@packages/root') const ciProvider = require('@packages/server/lib/util/ci_provider') const { commitInfo } = require('@cypress/commit-info') +const { getNextVersionForPath } = require('../../scripts/get-next-version') -class StatsdReporter { +const honey = new Libhoney({ + dataset: 'systemtest-performance', + writeKey: process.env.HONEYCOMB_API_KEY, +}) + +// This event is created here independently every time the reporter +// is imported (in each parallel instance of the system-tests +// in circleci) so that we can use it as the parent, +// but ../scripts/send-root-honeycomb-event.js +// is only invoked once at the start of the build, +// and is responsible for sending it to honeycomb. +const spanId = process.env.CIRCLE_WORKFLOW_ID || uuidv4() +const circleCiRootEvent = honey.newEvent() + +circleCiRootEvent.timestamp = Date.now() +circleCiRootEvent.add({ + buildUrl: process.env.CIRCLE_BUILD_URL, + platform: process.platform, + arch: process.arch, + name: 'ci_run', + + spanId, + traceId: spanId, +}) + +// Mocha events ('test', 'test end', etc) have no way to wait +// for async callbacks, so we can't guarantee we have this +// data ready by the time any of the reporter's events are emitted. + +// Therefore, we have each honeycomb event await this promise +// before sending itself. +let asyncInfo = Promise.all([getNextVersionForPath(path.resolve(__dirname, '../../packages')), commitInfo()]) +.then(([nextVersion, commitInformation]) => { + const ciInformation = ciProvider.commitParams() || {} + + return { + nextVersion, + branch: commitInformation.branch || ciInformation.branch, + commitSha: commitInformation.sha || ciInformation.sha, + } +}) + +function addAsyncInfoAndSend (honeycombEvent) { + return asyncInfo.then((info) => { + honeycombEvent.add(info) + honeycombEvent.send() + }) +} + +class HoneycombReporter { constructor (runner) { if (!process.env.HONEYCOMB_API_KEY) { return @@ -14,23 +64,46 @@ class StatsdReporter { console.log(chalk.green('Reporting to honeycomb')) - let branch - let commitSha + runner.on('suite', (suite) => { + if (!suite.title) { + return + } - this.honey = new Libhoney({ - dataset: 'systemtest-performance', - writeKey: process.env.HONEYCOMB_API_KEY, - }) + const parent = suite.parent && suite.parent.honeycombEvent ? suite.parent.honeycombEvent : circleCiRootEvent - commitInfo().then((commitInformation) => { - const ciInformation = ciProvider.commitParams() || {} + suite.honeycombEvent = honey.newEvent() + suite.honeycombEvent.timestamp = Date.now() + suite.honeycombEvent.add({ + ...parent.data, + suite: suite.title, + specFile: suite.file && path.basename(suite.file), + name: 'spec_execution', - branch = commitInformation.branch || ciInformation.branch - commitSha = commitInformation.sha || ciInformation.sha + spanId: uuidv4(), + parentId: parent.data.spanId, + }) }) runner.on('test', (test) => { - test.wallclockStart = Date.now() + const path = test.titlePath() + // This regex pulls apart a string like `failing1 [electron]` + // into `failing1` and `electron`, letting us use the same + // test name for all browsers, with the browser as a separate field. + // The browser capture group is optional because some tests aren't browser specific, + // in which case it will be undefined and not passed as a field to honeycomb. + const [, testTitle, browser] = path[path.length - 1].match(/(.+?)(?: \[([a-z]+)\])?$/) + + test.honeycombEvent = honey.newEvent() + test.honeycombEvent.timestamp = Date.now() + test.honeycombEvent.add({ + ...test.parent.honeycombEvent.data, + test: testTitle, + browser, + name: 'test_execution', + + spanId: uuidv4(), + parentId: test.parent.honeycombEvent.data.spanId, + }) }) runner.on('test end', (test) => { @@ -39,44 +112,42 @@ class StatsdReporter { return } - const title = test.titlePath().join(' / ') - // This regex pulls apart a string like `e2e async timeouts / failing1 [electron]` - // into `e2e async timeouts / failing1` and `electron`, letting us use the same - // test name for all browsers, with the browser as a separate field. - // The browser capture group is optional because some tests aren't browser specific, - // in which case it will be undefined and not passed as a field to honeycomb. - const [, testTitle, browser] = title.match(/(.+?)(?: \[([a-z]+)\])?$/) - - const honeycombEvent = this.honey.newEvent() - - honeycombEvent.timestamp = test.wallclockStart - honeycombEvent.add({ - test: testTitle, - specFile: path.basename(test.file), - browser, + test.honeycombEvent.add({ state: test.state, err: test.err && test.err.message, errStack: test.err && test.err.stack, - durationMs: Date.now() - test.wallclockStart, - mochaDurationMs: test.duration, - branch, - commitSha, - buildUrl: process.env.CIRCLE_BUILD_URL, - platform: process.platform, - arch: process.arch, - version: pkg.version, + durationMs: Date.now() - test.honeycombEvent.timestamp, }) - honeycombEvent.send() + addAsyncInfoAndSend(test.honeycombEvent) + }) + + runner.on('suite end', (suite) => { + if (!suite.honeycombEvent) { + return + } + + suite.honeycombEvent.add({ + durationMs: Date.now() - suite.honeycombEvent.timestamp, + }) + + addAsyncInfoAndSend(suite.honeycombEvent) }) } - // If there is no done callback, then mocha-multi-reporter will kill the process without waiting for our honeycomb post to complete. + // If there is no done method, then mocha-multi-reporter will kill the process + // without waiting for our honeycomb posts to complete. done (failures, callback) { - if (this.honey) { - this.honey.flush().then(callback) - } + // Await the asyncInfo promise one last time, to ensure all events have + // added the data and sent themselves before we flush honeycomb's queue and exit. + asyncInfo + .then(() => honey.flush()) + .then(callback) } } -module.exports = StatsdReporter +module.exports = HoneycombReporter + +HoneycombReporter.honey = honey +HoneycombReporter.circleCiRootEvent = circleCiRootEvent +HoneycombReporter.addAsyncInfoAndSend = addAsyncInfoAndSend diff --git a/system-tests/lib/system-tests.ts b/system-tests/lib/system-tests.ts index ac95e21620dd..276deb7ee47b 100644 --- a/system-tests/lib/system-tests.ts +++ b/system-tests/lib/system-tests.ts @@ -1,7 +1,9 @@ const snapshot = require('snap-shot-it') import { SpawnOptions } from 'child_process' +import stream from 'stream' import { expect } from './spec_helper' +import { dockerSpawner } from './docker' const isCi = require('is-ci') @@ -13,7 +15,6 @@ const path = require('path') const http = require('http') const human = require('human-interval') const morgan = require('morgan') -const stream = require('stream') const express = require('express') const Bluebird = require('bluebird') const debug = require('debug')('cypress:system-tests') @@ -21,7 +22,6 @@ const httpsProxy = require('@packages/https-proxy') const Fixtures = require('./fixtures') const { allowDestroy } = require(`@packages/server/lib/util/server_destroy`) -const cypress = require(`@packages/server/lib/cypress`) const screenshots = require(`@packages/server/lib/screenshots`) const videoCapture = require(`@packages/server/lib/video_capture`) const settings = require(`@packages/server/lib/util/settings`) @@ -42,7 +42,7 @@ type ExecResult = { type ExecFn = (options?: ExecOptions) => Promise -type ItOptions = ExecOptions & { +export type ItOptions = ExecOptions & { /** * If a function is supplied, it will be executed instead of running the `systemTests.exec` function immediately. */ @@ -60,6 +60,14 @@ type ItOptions = ExecOptions & { } type ExecOptions = { + /** + * If set, `docker exec` will be used to run this test. Requires Docker. + */ + dockerImage?: string + /* + * If set, test using the built Cypress CLI and binary. Expects a built CLI in `/cli/build` and packed binary in `/cypress.zip`. + */ + withBinary?: boolean /** * Deprecated. Use `--cypress-inspect-brk` from command line instead. * @deprecated @@ -86,6 +94,10 @@ type ExecOptions = { * The spec argument to pass to Cypress. */ spec?: string + /** + * If set, use a non-default spec dir. + */ + specDir?: string /** * The project fixture to scaffold and pass to Cypress. */ @@ -246,11 +258,31 @@ type SetupOptions = { settings?: CypressConfig } +export type Spawner = (cmd, args, env, options: ExecOptions) => SpawnerResult | Promise + +export type SpawnerResult = { + stdout: stream.Readable + stderr: stream.Readable + on(event: 'error', cb: (err: Error) => void): void + on(event: 'exit', cb: (exitCode: number) => void): void +} + +const cpSpawner: Spawner = (cmd, args, env, options) => { + if (options.withBinary) { + throw new Error('withBinary is not supported without the use of dockerImage') + } + + return cp.spawn(cmd, args, { + env, + ...options.spawnOpts, + }) +} + const serverPath = path.dirname(require.resolve('@packages/server')) cp = Bluebird.promisifyAll(cp) -const env = _.clone(process.env) +const processEnvCache = _.clone(process.env) Bluebird.config({ longStackTraces: true, @@ -702,7 +734,7 @@ const systemTests = { }) afterEach(async function () { - process.env = _.clone(env) + process.env = _.clone(processEnvCache) this.timeout(human('2 minutes')) @@ -778,10 +810,15 @@ const systemTests = { args (options: ExecOptions) { debug('converting options to args %o', { options }) - const args = [ + const projectPath = Fixtures.projectPath(options.project) + const args = options.withBinary ? [ + `run`, + `--project=${projectPath}`, + ] : [ + require.resolve('@packages/server'), // hides a user warning to go through NPM module `--cwd=${serverPath}`, - `--run-project=${Fixtures.projectPath(options.project)}`, + `--run-project=${projectPath}`, `--testingType=${options.testingType || 'e2e'}`, ] @@ -873,20 +910,6 @@ const systemTests = { return args }, - start (ctx, options: ExecOptions) { - options = this.options(ctx, options) - const args = this.args(options) - - return cypress.start(args) - .then(() => { - const { expectedExitCode } = options - - maybeVerifyExitCode(expectedExitCode, () => { - expect(process.exit).to.be.calledWith(expectedExitCode) - }) - }) - }, - /** * Executes a given project and optionally sanitizes and checks output. * @example @@ -907,7 +930,7 @@ const systemTests = { debug('systemTests.exec options %o', options) options = this.options(ctx, options) debug('processed options %o', options) - let args = this.args(options) + const args = options.args || this.args(options) const specifiedBrowser = process.env.BROWSER @@ -916,8 +939,9 @@ const systemTests = { } if (!options.skipScaffold) { - await Fixtures.scaffoldCommonNodeModules() - await Fixtures.scaffoldProject(options.project) + // symlinks won't work via docker + options.dockerImage || await Fixtures.scaffoldCommonNodeModules() + Fixtures.scaffoldProject(options.project) await Fixtures.scaffoldProjectNodeModules(options.project) } @@ -929,10 +953,6 @@ const systemTests = { await settings.writeForTesting(e2ePath, ctx.settings) } - const serverEntryFile = require.resolve('@packages/server') - - args = options.args || [serverEntryFile].concat(args) - let stdout = '' let stderr = '' @@ -1012,41 +1032,43 @@ const systemTests = { } debug('spawning Cypress %o', { args }) - const cmd = options.command || 'node' - const sp = cp.spawn(cmd, args, { - env: _.chain(process.env) - .omit('CYPRESS_DEBUG') - .extend({ - // FYI: color will be disabled - // because we are piping the child process - COLUMNS: 100, - LINES: 24, - }) - .defaults({ - // match CircleCI's filesystem limits, so screenshot names in snapshots match - CYPRESS_MAX_SAFE_FILENAME_BYTES: 242, - FAKE_CWD_PATH: '/XXX/XXX/XXX', - DEBUG_COLORS: '1', - // prevent any Compression progress - // messages from showing up - VIDEO_COMPRESSION_THROTTLE: 120000, - - // don't fail our own tests running from forked PR's - CYPRESS_INTERNAL_SYSTEM_TESTS: '1', - - // Emulate no typescript environment - CYPRESS_INTERNAL_NO_TYPESCRIPT: options.noTypeScript ? '1' : '0', - - // disable frame skipping to make quick Chromium tests have matching snapshots/working video - CYPRESS_EVERY_NTH_FRAME: 1, - - // force file watching for use with --no-exit - ...(options.noExit ? { CYPRESS_INTERNAL_FORCE_FILEWATCH: '1' } : {}), - }) - .extend(options.processEnv) - .value(), - ...options.spawnOpts, + + const cmd = options.command || (options.withBinary ? 'cypress' : 'node') + + const env = _.chain(process.env) + .omit('CYPRESS_DEBUG') + .extend({ + // FYI: color will be disabled + // because we are piping the child process + COLUMNS: 100, + LINES: 24, + }) + .defaults({ + // match CircleCI's filesystem limits, so screenshot names in snapshots match + CYPRESS_MAX_SAFE_FILENAME_BYTES: 242, + FAKE_CWD_PATH: '/XXX/XXX/XXX', + DEBUG_COLORS: '1', + // prevent any Compression progress + // messages from showing up + VIDEO_COMPRESSION_THROTTLE: 120000, + + // don't fail our own tests running from forked PR's + CYPRESS_INTERNAL_SYSTEM_TESTS: '1', + + // Emulate no typescript environment + CYPRESS_INTERNAL_NO_TYPESCRIPT: options.noTypeScript ? '1' : '0', + + // disable frame skipping to make quick Chromium tests have matching snapshots/working video + CYPRESS_EVERY_NTH_FRAME: 1, + + // force file watching for use with --no-exit + ...(options.noExit ? { CYPRESS_INTERNAL_FORCE_FILEWATCH: '1' } : {}), }) + .extend(options.processEnv) + .value() + + const spawnerFn: Spawner = options.dockerImage ? dockerSpawner : cpSpawner + const sp: SpawnerResult = await spawnerFn(cmd, args, env, options) const ColorOutput = function () { const colorOutput = new stream.Transform() diff --git a/system-tests/package.json b/system-tests/package.json index cb7d2f3ab8b2..33ec93b49c90 100644 --- a/system-tests/package.json +++ b/system-tests/package.json @@ -6,7 +6,7 @@ "main": "index.js", "scripts": { "projects:yarn:install": "node ./scripts/projects-yarn-install.js", - "test": "node ./scripts/run.js --glob-in-dir=test", + "test": "node ./scripts/run.js --glob-in-dir='{test,test-binary}'", "test:ci": "node ./scripts/run.js" }, "devDependencies": { @@ -44,12 +44,13 @@ "cors": "2.8.5", "dayjs": "^1.9.3", "debug": "^4.3.2", + "dockerode": "3.3.1", "execa": "1.0.0", "express": "4.17.1", "express-session": "1.16.1", "express-useragent": "1.0.15", "fluent-ffmpeg": "2.1.2", - "fs-extra": "8.1.0", + "fs-extra": "9.1.0", "glob": "7.2.0", "https-proxy-agent": "3.0.1", "human-interval": "1.0.0", diff --git a/system-tests/scripts/bootstrap-docker-container.sh b/system-tests/scripts/bootstrap-docker-container.sh new file mode 100755 index 000000000000..058f215c5489 --- /dev/null +++ b/system-tests/scripts/bootstrap-docker-container.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -e # exit on error + +echo "$0 running as $(whoami)" +echo "Node version: $(node -v)" + +if [ ! -d "$TEST_PROJECT_DIR" ]; then + echo "Missing TEST_PROJECT_DIR=$TEST_PROJECT_DIR. Check Docker Bind+Env config" + exit 1 +fi + +if [ ! -d "$REPO_DIR" ]; then + echo "Missing REPO_DIR=$REPO_DIR. Check Docker Bind+Env config" + exit 1 +fi + +ZIP_PATH=$REPO_DIR/cypress.zip +CLI_PATH=$REPO_DIR/cli/build + +if [ ! -f "$ZIP_PATH" ]; then + echo "Missing $ZIP_PATH. Check Docker Bind config" + exit 1 +fi + +if [ ! -d "$CLI_PATH" ]; then + echo "Missing $CLI_PATH. Check Docker Bind config" + exit 1 +fi + +set -x # log commands + +cd $TEST_PROJECT_DIR + +export CYPRESS_INSTALL_BINARY=$ZIP_PATH +export CYPRESS_CACHE_FOLDER=/tmp/CYPRESS_CACHE_FOLDER/ + +npm install --save-dev --unsafe-perm --allow-root $CLI_PATH + +PATH=$PATH:./node_modules/.bin + +cypress install + +# run command passed in argv and store exit code +set +e +$@ +EXIT_CODE=$? +set -e + +# delete tmp to avoid permissions issues on the host +cd - +rm -rf $TEST_PROJECT_DIR + +exit $EXIT_CODE \ No newline at end of file diff --git a/system-tests/scripts/send-root-honecomb-event.js b/system-tests/scripts/send-root-honecomb-event.js new file mode 100644 index 000000000000..e3dc7bcdc67f --- /dev/null +++ b/system-tests/scripts/send-root-honecomb-event.js @@ -0,0 +1,13 @@ +const { addAsyncInfoAndSend, circleCiRootEvent, honey } = require('../lib/performance-reporter') + +// This file is executed once during the circleci build, +// so that we can send the root event honeycomb event for this +// run of the system tests exactly once. +// All the system test build hosts reference this root event, +// joining them into a single trace. +if (require.main === module) { + addAsyncInfoAndSend(circleCiRootEvent).then(() => { + console.log(circleCiRootEvent.data) + honey.flush() + }) +} diff --git a/system-tests/test-binary/ci_environments_spec.ts b/system-tests/test-binary/ci_environments_spec.ts new file mode 100644 index 000000000000..39d15c1d3fbb --- /dev/null +++ b/system-tests/test-binary/ci_environments_spec.ts @@ -0,0 +1,41 @@ +import systemTests, { ItOptions } from '../lib/system-tests' + +function smokeTestDockerImage (title: string, dockerImage: string, expectedExitCode: number, onRun?: ItOptions['onRun']) { + systemTests.it(title, { + withBinary: true, + browser: 'electron', + dockerImage, + spec: 'test1.js', + specDir: 'tests', + project: 'todos', + expectedExitCode, + onRun, + }) +} + +describe('e2e binary CI environments', () => { + smokeTestDockerImage( + 'bare node image fails (lacks xvfb)', + 'node:12', 1, + async (exec) => { + const { stdout } = await exec() + + expect(stdout).to.include('Your system is missing the dependency: Xvfb') + }, + ) + + smokeTestDockerImage( + 'bare xvfb image fails', + 'cypressinternal/xvfb:12.13.0', 1, + ) + + smokeTestDockerImage( + 'ubuntu 16 passes', + 'cypress/base:ubuntu16-12.13.1', 0, + ) + + smokeTestDockerImage( + 'ubuntu 19 passes', + 'cypress/base:ubuntu19-node12.14.1', 0, + ) +}) diff --git a/system-tests/test-binary/node_versions_spec.ts b/system-tests/test-binary/node_versions_spec.ts new file mode 100644 index 000000000000..8d1483ab410c --- /dev/null +++ b/system-tests/test-binary/node_versions_spec.ts @@ -0,0 +1,21 @@ +import systemTests from '../lib/system-tests' + +function smokeTestDockerImage (dockerImage: string) { + systemTests.it(`can run in ${dockerImage}`, { + withBinary: true, + browser: 'electron', + dockerImage, + spec: 'test1.js', + specDir: 'tests', + project: 'todos', + }) +} + +describe('e2e binary node versions', () => { + [ + 'cypress/base:12', + 'cypress/base:14', + 'cypress/base:16.5.0', + 'cypress/base:17.3.0', + ].forEach(smokeTestDockerImage) +}) diff --git a/yarn.lock b/yarn.lock index 2db05db527ad..44ad3c6cba3e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9002,7 +9002,7 @@ dependencies: "@types/node" "*" -"@types/fs-extra@^9.0.11": +"@types/fs-extra@^9.0.11", "@types/fs-extra@^9.0.13": version "9.0.13" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== @@ -12169,10 +12169,10 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== +asn1@^0.2.4, asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== dependencies: safer-buffer "~2.1.0" @@ -13801,7 +13801,7 @@ batch@0.6.1: resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= -bcrypt-pbkdf@^1.0.0: +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= @@ -16824,6 +16824,13 @@ cp-file@^7.0.0: nested-error-stacks "^2.0.0" p-event "^4.1.0" +cpu-features@0.0.2: + version "0.0.2" + resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz#9f636156f1155fd04bdbaa028bb3c2fbef3cea7a" + integrity sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA== + dependencies: + nan "^2.14.1" + cpy@^8.1.1: version "8.1.2" resolved "https://registry.yarnpkg.com/cpy/-/cpy-8.1.2.tgz#e339ea54797ad23f8e3919a5cffd37bfc3f25935" @@ -18648,6 +18655,24 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" +docker-modem@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.3.tgz#ac4bb1f32f81ac2e7120c5e99a068fab2458a32f" + integrity sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^1.4.0" + +dockerode@3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.3.1.tgz#74f66e239e092e7910e2beae6322d35c44b08cdc" + integrity sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ== + dependencies: + docker-modem "^3.0.0" + tar-fs "~2.0.1" + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -21854,6 +21879,16 @@ fs-extra@9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@9.1.0, fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -21901,16 +21936,6 @@ fs-extra@^6.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -30150,10 +30175,10 @@ mz@^2.4.0, mz@^2.5.0, mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.10.0, nan@^2.12.1: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nan@^2.10.0, nan@^2.12.1, nan@^2.14.1, nan@^2.15.0: + version "2.15.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nanoid@3.1.20: version "3.1.20" @@ -38769,6 +38794,11 @@ spin.js@^4.1.1: resolved "https://registry.yarnpkg.com/spin.js/-/spin.js-4.1.1.tgz#567464a08620541e523da856cb5f67af2d0f48ad" integrity sha512-3cjbjZBw8TmZmvzcmlXqArUpefJ1vGgQZ+dh1CdyDyxZZNxNmw+2Dq5jyoP/OCqQP+z78rWgSJX9m3uMuGaxxw== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -38843,6 +38873,17 @@ ssestream@1.0.1: resolved "https://registry.yarnpkg.com/ssestream/-/ssestream-1.0.1.tgz#351551b12c00e91e7550f38d558323f3f47b54c2" integrity sha1-NRVRsSwA6R51UPONVYMj8/R7VMI= +ssh2@^1.4.0: + version "1.6.0" + resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.6.0.tgz#61aebc3a6910fe488f9c85cd8355bdf8d4724e05" + integrity sha512-lxc+uvXqOxyQ99N2M7k5o4pkYDO5GptOTYduWw7hIM41icxvoBcCNHcj+LTKrjkL0vFcAl+qfZekthoSFRJn2Q== + dependencies: + asn1 "^0.2.4" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "0.0.2" + nan "^2.15.0" + sshpk@^1.14.1, sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -39923,7 +39964,17 @@ tar-fs@^2.0.0, tar-fs@^2.1.1: pump "^3.0.0" tar-stream "^2.1.4" -tar-stream@^2.1.4: +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0, tar-stream@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -41752,7 +41803,15 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@1.5.2, url-parse@^1.4.3, url-parse@^1.4.7: +url-parse@1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.6.tgz#b2a41d5a233645f3c31204cc8be60e76a15230a2" + integrity sha512-xj3QdUJ1DttD1LeSfvJlU1eiF1RvBSBfUu8GplFGdUzSO28y5yUtEl7wb//PI4Af6qh0o/K8545vUmucRrfWsw== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +url-parse@^1.4.3, url-parse@^1.4.7: version "1.5.2" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.2.tgz#a4eff6fd5ff9fe6ab98ac1f79641819d13247cda" integrity sha512-6bTUPERy1muxxYClbzoRo5qtQuyoGEbzbQvi0SW4/8U8UyVkAQhWFBlnigqJkRm4su4x1zDQfNbEzWkt+vchcg==