Merge pull request #20316 from cypress-io/10.0-release-merge-0143e13

chore: merge develop into 10.0-release (0143e13)
This commit is contained in:
Tim Griesser
2022-02-23 10:18:30 -05:00
committed by GitHub
56 changed files with 1319 additions and 744 deletions
+1 -1
View File
@@ -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,
}
-15
View File
@@ -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).
+3 -53
View File
@@ -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'] = [
exports['should have just circle and linux projects'] = [
{
"repo": "cypress-io/cypress-test-tiny",
"repo": "cypress-io/cypress-test-module-api",
"provider": "circle",
"platform": "darwin"
},
{
"repo": "cypress-io/cypress-test-example-repos",
"provider": "circle",
"platform": "darwin"
"platform": "linux"
}
]
+1 -1
View File
@@ -1,4 +1,4 @@
{
"chrome:beta": "99.0.4844.27",
"chrome:stable": "98.0.4758.80"
"chrome:stable": "98.0.4758.102"
}
+87 -53
View File
@@ -11,7 +11,7 @@ defaults: &defaults
type: boolean
default: false
executor: <<parameters.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:
@@ -1204,6 +1237,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
parallelism: 8
@@ -1933,14 +1980,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.
@@ -2143,6 +2198,7 @@ linux-workflow: &linux-workflow
requires:
- build
- system-tests-node-modules-install:
context: test-runner:performance-tracking
requires:
- build
- system-tests-chrome:
@@ -2321,17 +2377,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:
@@ -2340,6 +2389,10 @@ linux-workflow: &linux-workflow
<<: *mainBuildFilters
requires:
- create-build-artifacts
- test-binary-against-recipes:
<<: *mainBuildFilters
requires:
- create-build-artifacts
- test-binary-against-kitchensink-firefox:
<<: *mainBuildFilters
requires:
@@ -2348,29 +2401,23 @@ linux-workflow: &linux-workflow
<<: *mainBuildFilters
requires:
- create-build-artifacts
- test-binary-against-api-testing-firefox:
<<: *mainBuildFilters
requires:
- create-build-artifacts
- test-binary-against-piechopper-firefox:
<<: *mainBuildFilters
requires:
- create-build-artifacts
- test-binary-against-cypress-realworld-app:
<<: *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:
@@ -2411,19 +2458,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
-9
View File
@@ -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:
+1 -1
View File
@@ -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",
+7
View File
@@ -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)
@@ -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
+7
View File
@@ -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)
+1 -1
View File
@@ -2,4 +2,4 @@ declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
}
@@ -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",
+1 -1
View File
@@ -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",
+3 -3
View File
@@ -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",
@@ -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
@@ -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
}
@@ -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')
})
@@ -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 <input#input> 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')
})
})
})
})
@@ -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 <input#input> 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,
}, () => {
@@ -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),
})
})
})
})
})
})
@@ -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
})
})
})
})
@@ -66,10 +66,10 @@
target.addEventListener("click", () => {
updateLog("click");
});
target.addEventListener("keyup", () => {
target.addEventListener("keyup", (event) => {
updateLog("keyup");
});
});
</script>
</body>
</html>
</html>
+1 -1
View File
@@ -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",
@@ -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)
}
@@ -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<Cypress.Loggable & Cypress.Timeoutable>
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()
},
})
}
@@ -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,
}
@@ -9,68 +9,6 @@ import { getAliasedRequests, isDynamicAliasingPossible } from '../../net-stubbin
export default (Commands, Cypress, cy, state) => {
Commands.addAll({
// TODO: any -> Partial<Cypress.Loggable & Cypress.Timeoutable>
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<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>
get (selector, options: any = {}) {
const userOptions = options
@@ -409,36 +347,6 @@ export default (Commands, Cypress, cy, state) => {
return resolveElements()
},
// TODO: any -> Partial<Cypress.Loggable & Cypress.Timeoutable>
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'] }, {
@@ -0,0 +1,35 @@
import _ from 'lodash'
export default (Commands, Cypress, cy, state) => {
Commands.addAll({
// TODO: any -> Partial<Cypress.Loggable & Cypress.Timeoutable>
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)
},
})
}
+7 -1
View File
@@ -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)
+1 -1
View File
@@ -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"
},
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+4
View File
@@ -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
@@ -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')
})
})
})
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
@@ -152,6 +152,8 @@ function run (ipc, configFile, projectRoot) {
))
}
})
ipc.send('ready')
}
module.exports = run
+2 -2
View File
@@ -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"
@@ -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,
})
})
-13
View File
@@ -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',
],
},
}
+6
View File
@@ -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)
+3 -3
View File
@@ -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)
})
})
})
+31 -1
View File
@@ -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.
+151
View File
@@ -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<void>((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<string, string>
}) {
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
}
+2 -1
View File
@@ -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 {
+113 -42
View File
@@ -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
+84 -63
View File
@@ -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<ExecResult>
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<SpawnerResult>
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'))
@@ -761,11 +793,10 @@ const systemTests = {
return spec
}
if (options.testingType === 'component') {
return path.join(projectPath, spec)
}
const specDir = options.specDir
|| (options.testingType === 'component' ? '' : 'cypress/e2e')
return path.join(projectPath, 'cypress', 'e2e', spec)
return path.join(projectPath, specDir, spec)
})
// normalize the path to the spec
@@ -778,10 +809,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 +909,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 +929,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,7 +938,8 @@ const systemTests = {
}
if (!options.skipScaffold) {
await Fixtures.scaffoldCommonNodeModules()
// symlinks won't work via docker
options.dockerImage || await Fixtures.scaffoldCommonNodeModules()
await Fixtures.scaffoldProject(options.project)
await Fixtures.scaffoldProjectNodeModules(options.project)
}
@@ -929,10 +952,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 +1031,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',
const cmd = options.command || (options.withBinary ? 'cypress' : 'node')
// 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 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()
+3 -2
View File
@@ -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",
+53
View File
@@ -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
@@ -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()
})
}
@@ -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,
)
})
@@ -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)
})
+81 -22
View File
@@ -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==