Merge remote-tracking branch 'origin/10.0-release' into chore/vite-dev-server

This commit is contained in:
Jessica Sachs
2022-03-08 15:48:56 -05:00
96 changed files with 2769 additions and 1018 deletions
@@ -0,0 +1,8 @@
exports['prepare-release-artifacts runs expected commands 1'] = `
$ node ./scripts/prepare-release-artifacts.js --dry-run --sha 57d0a85108fad6f77b39db88b8a7d8a3bfdb51a2 --version 1.2.3
🏗 Running \`move-binaries\`...
🏗 Dry run, not executing: node ./scripts/binary.js move-binaries --sha 57d0a85108fad6f77b39db88b8a7d8a3bfdb51a2 --version 1.2.3
🏗 Running \`create-stable-npm-package\`...
🏗 Dry run, not executing: ./scripts/create-stable-npm-package.sh https://cdn.cypress.io/beta/npm/1.2.3/linux-x64/develop-57d0a85108fad6f77b39db88b8a7d8a3bfdb51a2/cypress.tgz
`
Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

+2 -2
View File
@@ -1,4 +1,4 @@
{
"chrome:beta": "99.0.4844.45",
"chrome:stable": "98.0.4758.102"
"chrome:beta": "100.0.4896.20",
"chrome:stable": "99.0.4844.51"
}
+75 -59
View File
@@ -29,7 +29,7 @@ mainBuildFilters: &mainBuildFilters
only:
- develop
- 10.0-release
- merge-develop-2-28-22
- rebase/develop-10.0-release-2022-03-04
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -39,7 +39,8 @@ macWorkflowFilters: &mac-workflow-filters
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ '10.0-release', << pipeline.git.branch >> ]
- equal: [ merge-develop-2-28-22, << pipeline.git.branch >> ]
- equal: [ rebase/develop-10.0-release-2022-03-04, << pipeline.git.branch >> ]
- equal: [ fix-beta-build-caching, << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -49,7 +50,7 @@ windowsWorkflowFilters: &windows-workflow-filters
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ '10.0-release', << pipeline.git.branch >> ]
- equal: [ merge-develop-2-28-22, << pipeline.git.branch >> ]
- equal: [ fix-beta-build-caching, << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -82,6 +83,7 @@ executors:
macos:
# Executor should have Node >= required version
xcode: "13.0.0"
resource_class: macos.x86.medium.gen2
environment:
PLATFORM: mac
@@ -473,21 +475,14 @@ commands:
# https://www.gep13.co.uk/blog/chocolatey-error-hashes-do-not-match
[[ $PLATFORM == 'windows' && '<<parameters.browser>>' == 'chrome' ]] && choco install googlechrome --ignore-checksums || [[ $PLATFORM != 'windows' ]]
- run:
# the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars
# are a temporary hack to workaround percy issues with
# PR's not diffing the right base branch due to other problems
# upstream with finalizing builds. These will be removed
# once we implement a more permanent solution.
command: |
cmd=$([[ <<parameters.percy>> == 'true' ]] && echo 'yarn percy exec --parallel -- --') || true
DEBUG=<<parameters.debug>> \
CYPRESS_KONFIG_ENV=production \
CYPRESS_RECORD_KEY=$TEST_LAUNCHPAD_RECORD_KEY \
PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
PERCY_TARGET_BRANCH="10.0-release" \
PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \
$cmd yarn workspace @packages/<<parameters.package>> cypress:run:<<parameters.type>> --browser <<parameters.browser>> --record --parallel --group <<parameters.package>>-<<parameters.type>>
- store_test_results:
path: /tmp/cypress
@@ -881,6 +876,17 @@ commands:
path: /tmp/<<parameters.repo>>/cypress/videos
- store-npm-logs
wait-on-circle-jobs:
description: Polls certain Circle CI jobs until they finish
parameters:
job-names:
description: comma separated list of circle ci job names to wait for
type: string
steps:
- run:
name: "Waiting on Circle CI jobs: <<parameters.job-names>>"
command: node ./scripts/wait-on-circle-jobs.js --job-names="<<parameters.job-names>>"
build-binary:
steps:
- run:
@@ -1083,16 +1089,19 @@ jobs:
echo "This is an external PR, cannot access other services"
circleci-agent step halt
fi
- wait-on-circle-jobs:
job-names: >
cli-visual-tests,
reporter-integration-tests,
npm-design-system,
run-app-component-tests-chrome,
run-app-integration-tests-chrome,
run-frontend-shared-component-tests-chrome,
run-launchpad-component-tests-chrome,
run-launchpad-integration-tests-chrome
- run:
# the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars
# are a temporary hack to workaround percy issues with
# PR's not diffing the right base branch due to other problems
# upstream with finalizing builds. These will be removed
# once we implement a more permanent solution.
command: |
PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \
PERCY_TARGET_BRANCH="10.0-release" \
PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
yarn percy build:finalize
cli-visual-tests:
@@ -1113,17 +1122,10 @@ jobs:
path: cli/visual-snapshots
- run:
name: Upload CLI snapshots for diffing
# the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars
# are a temporary hack to workaround percy issues with
# PR's not diffing the right base branch due to other problems
# upstream with finalizing builds. These will be removed
# once we implement a more permanent solution.
command: |
PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
PERCY_TARGET_BRANCH="10.0-release" \
PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \
yarn percy snapshot ./cli/visual-snapshots
unit-tests:
@@ -1294,21 +1296,31 @@ jobs:
run-frontend-shared-component-tests-chrome:
<<: *defaults
parameters:
<<: *defaultsParameters
percy:
type: boolean
default: false
parallelism: 3
steps:
- run-new-ui-tests:
browser: chrome
percy: true
percy: << parameters.percy >>
package: frontend-shared
type: ct
run-launchpad-component-tests-chrome:
<<: *defaults
parameters:
<<: *defaultsParameters
percy:
type: boolean
default: false
parallelism: 7
steps:
- run-new-ui-tests:
browser: chrome
percy: true
percy: << parameters.percy >>
package: launchpad
type: ct
# debug: cypress:*,engine:socket
@@ -1320,22 +1332,30 @@ jobs:
resource_class:
type: string
default: medium
percy:
type: boolean
default: false
resource_class: << parameters.resource_class >>
parallelism: 3
steps:
- run-new-ui-tests:
browser: chrome
percy: true
percy: << parameters.percy >>
package: launchpad
type: e2e
run-app-component-tests-chrome:
<<: *defaults
parameters:
<<: *defaultsParameters
percy:
type: boolean
default: false
parallelism: 7
steps:
- run-new-ui-tests:
browser: chrome
percy: true
percy: << parameters.percy >>
package: app
type: ct
@@ -1346,12 +1366,15 @@ jobs:
resource_class:
type: string
default: medium
percy:
type: boolean
default: false
resource_class: << parameters.resource_class >>
parallelism: 8
steps:
- run-new-ui-tests:
browser: chrome
percy: true
percy: << parameters.percy >>
package: app
type: e2e
@@ -1394,19 +1417,12 @@ jobs:
command: yarn build-for-tests
working_directory: packages/reporter
- run:
# the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars
# are a temporary hack to workaround percy issues with
# PR's not diffing the right base branch due to other problems
# upstream with finalizing builds. These will be removed
# once we implement a more permanent solution.
command: |
CYPRESS_KONFIG_ENV=production \
CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \
PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
PERCY_TARGET_BRANCH="10.0-release" \
PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \
yarn percy exec --parallel -- -- \
yarn cypress:run --record --parallel --group reporter
working_directory: packages/reporter
@@ -1541,18 +1557,11 @@ jobs:
name: Run tests
# will use PERCY_TOKEN environment variable if available
#
# the PERCY_TARGET_BRANCH and PERCY_TARGET_COMMIT env vars
# are a temporary hack to workaround percy issues with
# PR's not diffing the right base branch due to other problems
# upstream with finalizing builds. These will be removed
# once we implement a more permanent solution.
command: |
CYPRESS_KONFIG_ENV=production \
PERCY_PARALLEL_NONCE=$PLATFORM-$CIRCLE_SHA1 \
PERCY_PARALLEL_NONCE=$CIRCLE_SHA1 \
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
PERCY_TARGET_BRANCH="10.0-release" \
PERCY_TARGET_COMMIT=$(git log -n 1 origin/10.0-release --pretty="%H") \
yarn percy exec --parallel -- -- \
yarn test --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json
working_directory: npm/design-system
@@ -1669,7 +1678,7 @@ jobs:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "merge-develop-2-28-22" && "$CIRCLE_BRANCH" != "10.0-release" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "rebase/develop-10.0-release-2022-03-04" && "$CIRCLE_BRANCH" != "10.0-release" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -2140,14 +2149,7 @@ linux-workflow: &linux-workflow
context: test-runner:poll-circle-workflow
required_env_var: PERCY_TOKEN # skips job if not defined (external PR)
requires:
- cli-visual-tests
- reporter-integration-tests
- npm-design-system
- run-app-component-tests-chrome
- run-app-integration-tests-chrome
- run-frontend-shared-component-tests-chrome
- run-launchpad-component-tests-chrome
- run-launchpad-integration-tests-chrome
- build
- lint-types:
requires:
- build
@@ -2206,22 +2208,27 @@ linux-workflow: &linux-workflow
- build
- run-frontend-shared-component-tests-chrome:
context: test-runner:launchpad-tests
percy: true
requires:
- build
- run-launchpad-integration-tests-chrome:
context: test-runner:launchpad-tests
percy: true
requires:
- build
- run-launchpad-component-tests-chrome:
context: test-runner:launchpad-tests
percy: true
requires:
- build
- run-app-integration-tests-chrome:
context: test-runner:launchpad-tests
percy: true
requires:
- build
- run-app-component-tests-chrome:
context: test-runner:launchpad-tests
percy: true
requires:
- build
- reporter-integration-tests:
@@ -2303,6 +2310,14 @@ linux-workflow: &linux-workflow
- ui-components-integration-tests
- unit-tests
- unit-tests-release
- cli-visual-tests
- reporter-integration-tests
- npm-design-system
- run-app-component-tests-chrome
- run-app-integration-tests-chrome
- run-frontend-shared-component-tests-chrome
- run-launchpad-component-tests-chrome
- run-launchpad-integration-tests-chrome
# various testing scenarios, like building full binary
# and testing it on a real project
@@ -2393,12 +2408,13 @@ mac-workflow: &mac-workflow
- node_modules_install:
name: darwin-node-modules-install
executor: mac
resource_class: macos.x86.medium.gen2
only-cache-for-root-user: true
- build:
name: darwin-build
executor: mac
resource_class: medium
resource_class: macos.x86.medium.gen2
requires:
- darwin-node-modules-install
@@ -2417,7 +2433,7 @@ mac-workflow: &mac-workflow
- test-runner:upload
- test-runner:commit-status-checks
executor: mac
resource_class: medium
resource_class: macos.x86.medium.gen2
requires:
- darwin-build
+1
View File
@@ -2,6 +2,7 @@ exports['package.json build outputs expected properties 1'] = {
"name": "test",
"engines": "test engines",
"version": "x.y.z",
"buildInfo": "replaced by normalizePackageJson",
"description": "Cypress.io end to end testing tool",
"homepage": "https://github.com/cypress-io/cypress",
"license": "MIT",
+1 -1
View File
@@ -43,4 +43,4 @@ https://download.cypress.io/desktop/0.20.2?platform=OS&arch=ARCH
exports['desktop url from template'] = `
https://download.cypress.io/desktop/0.20.2/darwin-x64/cypress.zip
`
`
+41 -3
View File
@@ -7,7 +7,7 @@ Application Data: /user/app/data/path
Browser Profiles: /user/app/data/path/to/browsers
Binary Caches: /user/path/to/binary/cache
Cypress Version: 0.0.0-development
Cypress Version: 0.0.0-development (stable)
System Platform: linux (Foo-OsVersion)
System Memory: 1.2 GB free 400 MB
@@ -29,7 +29,7 @@ Application Data: /user/app/data/path
Browser Profiles: /user/app/data/path/to/browsers
Binary Caches: /user/path/to/binary/cache
Cypress Version: 0.0.0-development
Cypress Version: 0.0.0-development (stable)
System Platform: linux (Foo-OsVersion)
System Memory: 1.2 GB free 400 MB
@@ -48,8 +48,46 @@ Application Data: /user/app/data/path
Browser Profiles: /user/app/data/path/to/browsers
Binary Caches: /user/path/to/binary/cache
Cypress Version: 0.0.0-development
Cypress Version: 0.0.0-development (stable)
System Platform: linux (Foo-OsVersion)
System Memory: 1.2 GB free 400 MB
`
exports['logs additional info about pre-releases'] = `
Proxy Settings: none detected
Environment Variables: none detected
Application Data: /user/app/data/path
Browser Profiles: /user/app/data/path/to/browsers
Binary Caches: /user/path/to/binary/cache
Cypress Version: 0.0.0-development (pre-release)
System Platform: linux (Foo-OsVersion)
System Memory: 1.2 GB free 400 MB
This is a pre-release build of Cypress.
Build info:
Commit SHA: abc123
Commit Branch: someBranchName
Commit Date: 2022-02-02Txx:xx:xx.000Z
`
exports['logs additional info about development'] = `
Proxy Settings: none detected
Environment Variables: none detected
Application Data: /user/app/data/path
Browser Profiles: /user/app/data/path/to/browsers
Binary Caches: /user/path/to/binary/cache
Cypress Version: 0.0.0-development (pre-release)
System Platform: linux (Foo-OsVersion)
System Memory: 1.2 GB free 400 MB
This is the development (un-built) Cypress CLI.
`
+36
View File
@@ -256,4 +256,40 @@ https://on.cypress.io/guides/getting-started/installing-cypress#system-requireme
Platform: win32-ia32
`
exports['/lib/tasks/install .start non-stable builds logs a warning about installing a pre-release 1'] = `
⚠ Warning: You are installing a pre-release build of Cypress.
Bugs may be present which do not exist in production builds.
This build was created from:
* Commit SHA: abc123
* Commit Branch: aBranchName
* Commit Timestamp: 1996-11-27Txx:xx:xx.000Z
Installing Cypress (version: https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-abc123/cypress.zip)
⠋ Downloaded Cypress
✔ Downloaded Cypress
✔ Downloaded Cypress
⠋ Unzipped Cypress
✔ Downloaded Cypress
✔ Unzipped Cypress
✔ Downloaded Cypress
✔ Unzipped Cypress
⠋ Finished Installation /cache/Cypress/1.2.3
✔ Downloaded Cypress
✔ Unzipped Cypress
✔ Finished Installation /cache/Cypress/1.2.3
✔ Downloaded Cypress
✔ Unzipped Cypress
✔ Finished Installation /cache/Cypress/1.2.3
You can now open Cypress by running: node_modules/.bin/cypress open
https://on.cypress.io/installing-cypress
`
+54 -43
View File
@@ -11,6 +11,7 @@ const _ = require('lodash')
const g = chalk.green
// color for paths
const p = chalk.cyan
const red = chalk.red
// urls
const link = chalk.blue.underline
@@ -43,56 +44,66 @@ const formatCypressVariables = () => {
return maskSensitiveVariables(vars)
}
methods.start = (options = {}) => {
methods.start = async (options = {}) => {
const args = ['--mode=info']
return spawn.start(args, {
await spawn.start(args, {
dev: options.dev,
})
.then(() => {
console.log()
const proxyVars = methods.findProxyEnvironmentVariables()
if (_.isEmpty(proxyVars)) {
console.log('Proxy Settings: none detected')
} else {
console.log('Proxy Settings:')
_.forEach(proxyVars, (value, key) => {
console.log('%s: %s', key, g(value))
})
console.log()
const proxyVars = methods.findProxyEnvironmentVariables()
console.log()
console.log('Learn More: %s', link('https://on.cypress.io/proxy-configuration'))
console.log()
}
})
.then(() => {
const cyVars = formatCypressVariables()
if (_.isEmpty(cyVars)) {
console.log('Environment Variables: none detected')
} else {
console.log('Environment Variables:')
_.forEach(cyVars, (value, key) => {
console.log('%s: %s', key, g(value))
})
}
})
.then(() => {
console.log()
console.log('Application Data:', p(util.getApplicationDataFolder()))
console.log('Browser Profiles:', p(util.getApplicationDataFolder('browsers')))
console.log('Binary Caches: %s', p(state.getCacheDir()))
})
.then(() => {
console.log()
return util.getOsVersionAsync().then((osVersion) => {
console.log('Cypress Version: %s', g(util.pkgVersion()))
console.log('System Platform: %s (%s)', g(os.platform()), g(osVersion))
console.log('System Memory: %s free %s', g(prettyBytes(os.totalmem())), g(prettyBytes(os.freemem())))
if (_.isEmpty(proxyVars)) {
console.log('Proxy Settings: none detected')
} else {
console.log('Proxy Settings:')
_.forEach(proxyVars, (value, key) => {
console.log('%s: %s', key, g(value))
})
})
console.log()
console.log('Learn More: %s', link('https://on.cypress.io/proxy-configuration'))
console.log()
}
const cyVars = formatCypressVariables()
if (_.isEmpty(cyVars)) {
console.log('Environment Variables: none detected')
} else {
console.log('Environment Variables:')
_.forEach(cyVars, (value, key) => {
console.log('%s: %s', key, g(value))
})
}
console.log()
console.log('Application Data:', p(util.getApplicationDataFolder()))
console.log('Browser Profiles:', p(util.getApplicationDataFolder('browsers')))
console.log('Binary Caches: %s', p(state.getCacheDir()))
console.log()
const osVersion = await util.getOsVersionAsync()
const buildInfo = util.pkgBuildInfo()
const isStable = buildInfo && buildInfo.stable
console.log('Cypress Version: %s', g(util.pkgVersion()), isStable ? g('(stable)') : red('(pre-release)'))
console.log('System Platform: %s (%s)', g(os.platform()), g(osVersion))
console.log('System Memory: %s free %s', g(prettyBytes(os.totalmem())), g(prettyBytes(os.freemem())))
if (!buildInfo) {
console.log()
console.log('This is the', red('development'), '(un-built) Cypress CLI.')
} else if (!isStable) {
console.log()
console.log('This is a', red('pre-release'), 'build of Cypress.')
console.log('Build info:')
console.log(' Commit SHA:', g(buildInfo.commitSha))
console.log(' Commit Branch:', g(buildInfo.commitBranch))
console.log(' Commit Date:', g(buildInfo.commitDate))
}
}
module.exports = methods
+8 -1
View File
@@ -41,8 +41,15 @@ const getVersions = () => {
return versions
})
.then((binaryVersions) => {
const buildInfo = util.pkgBuildInfo()
let packageVersion = util.pkgVersion()
if (!buildInfo) packageVersion += ' (development)'
else if (!buildInfo.stable) packageVersion += ' (pre-release)'
const versions = {
package: util.pkgVersion(),
package: packageVersion,
binary: binaryVersions.binary || 'not installed',
electronVersion: binaryVersions.electronVersion || 'not found',
electronNodeVersion: binaryVersions.electronNodeVersion || 'not found',
+138 -217
View File
@@ -1,7 +1,6 @@
const _ = require('lodash')
const arch = require('arch')
const os = require('os')
const url = require('url')
const path = require('path')
const chalk = require('chalk')
const debug = require('debug')('cypress:cli')
@@ -18,97 +17,10 @@ const logger = require('../logger')
const { throwFormErrorText, errors } = require('../errors')
const verbose = require('../VerboseRenderer')
const getNpmArgv = () => {
const json = process.env.npm_config_argv
const { buildInfo, version } = require('../../package.json')
if (!json) {
return
}
debug('found npm argv json %o', json)
try {
return JSON.parse(json).original || []
} catch (e) {
return []
}
}
// attempt to discover the version specifier used to install Cypress
// for example: "^5.0.0", "https://cdn.cypress.io/...", ...
const getVersionSpecifier = (startDir = path.resolve(__dirname, '../..')) => {
const argv = getNpmArgv()
if ((process.env.npm_package_resolved || '').endsWith('cypress.tgz')) {
return process.env.npm_package_resolved
}
if (argv) {
const tgz = _.find(argv, (t) => t.endsWith('cypress.tgz'))
if (tgz) {
return tgz
}
}
const getVersionSpecifierFromPkg = (dir) => {
debug('looking for versionSpecifier %o', { dir })
const tryParent = () => {
const parentPath = path.resolve(dir, '..')
if (parentPath === dir) {
debug('reached FS root with no versionSpecifier found')
return
}
return getVersionSpecifierFromPkg(parentPath)
}
return fs.readJSON(path.join(dir, 'package.json'))
.catch(() => ({}))
.then((pkg) => {
const specifier = _.chain(['dependencies', 'devDependencies', 'optionalDependencies'])
.map((prop) => _.get(pkg, `${prop}.cypress`))
.compact().first().value()
return specifier || tryParent()
})
}
// recurse through parent directories until package.json with `cypress` is found
return getVersionSpecifierFromPkg(startDir)
.then((versionSpecifier) => {
debug('finished looking for versionSpecifier', { versionSpecifier })
return versionSpecifier
})
}
const betaNpmUrlRe = /^\/beta\/npm\/(?<version>[0-9.]+)\/(?<platformSlug>.+?)\/(?<artifactSlug>.+?)\/cypress\.tgz$/
// convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL
const getBinaryUrlFromPrereleaseNpmUrl = (npmUrl) => {
let parsed
try {
parsed = url.parse(npmUrl)
} catch (e) {
return
}
const matches = betaNpmUrlRe.exec(parsed.pathname)
if (parsed.hostname !== 'cdn.cypress.io' || !matches) {
return
}
const { version, artifactSlug } = matches.groups
parsed.pathname = `/beta/binary/${version}/${os.platform()}-${arch()}/${artifactSlug}/cypress.zip`
return parsed.format()
function _getBinaryUrlFromBuildInfo ({ commitSha, commitBranch }) {
return `https://cdn.cypress.io/beta/binary/${version}/${os.platform()}-${arch()}/${commitBranch}-${commitSha}/cypress.zip`
}
const alreadyInstalledMsg = () => {
@@ -227,43 +139,71 @@ const validateOS = () => {
})
}
const start = (options = {}) => {
/**
* Returns the version to install - either a string like `1.2.3` to be fetched
* from the download server or a file path or HTTP URL.
*/
function getVersionOverride ({ envVarVersion, buildInfo }) {
// let this environment variable reset the binary version we need
if (envVarVersion) {
return envVarVersion
}
if (buildInfo && !buildInfo.stable) {
logger.log(
chalk.yellow(stripIndent`
${logSymbols.warning} Warning: You are installing a pre-release build of Cypress.
Bugs may be present which do not exist in production builds.
This build was created from:
* Commit SHA: ${buildInfo.commitSha}
* Commit Branch: ${buildInfo.commitBranch}
* Commit Timestamp: ${buildInfo.commitDate}
`),
)
logger.log()
return _getBinaryUrlFromBuildInfo(buildInfo)
}
}
function getEnvVarVersion () {
if (!util.getEnv('CYPRESS_INSTALL_BINARY')) return
// because passed file paths are often double quoted
// and might have extra whitespace around, be robust and trim the string
const trimAndRemoveDoubleQuotes = true
const envVarVersion = util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes)
debug('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion)
return envVarVersion
}
const start = async (options = {}) => {
debug('installing with options %j', options)
const envVarVersion = getEnvVarVersion()
if (envVarVersion === '0') {
debug('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install')
logger.log(
stripIndent`
${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`,
)
logger.log()
return
}
_.defaults(options, {
force: false,
buildInfo,
})
const pkgVersion = util.pkgVersion()
let needVersion = pkgVersion
let binaryUrlOverride
debug('version in package.json is', needVersion)
// let this environment variable reset the binary version we need
if (util.getEnv('CYPRESS_INSTALL_BINARY')) {
// because passed file paths are often double quoted
// and might have extra whitespace around, be robust and trim the string
const trimAndRemoveDoubleQuotes = true
const envVarVersion = util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes)
debug('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion)
if (envVarVersion === '0') {
debug('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install')
logger.log(
stripIndent`
${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`,
)
logger.log()
return Promise.resolve()
}
binaryUrlOverride = envVarVersion
}
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
const envCache = util.getEnv('CYPRESS_CACHE_FOLDER')
@@ -278,18 +218,21 @@ const start = (options = {}) => {
logger.log()
}
const installDir = state.getVersionDir(pkgVersion)
const pkgVersion = util.pkgVersion()
const versionOverride = getVersionOverride({ envVarVersion, buildInfo: options.buildInfo })
const versionToInstall = versionOverride || pkgVersion
debug('version in package.json is %s, version to install is %s', pkgVersion, versionToInstall)
const installDir = state.getVersionDir(pkgVersion, options.buildInfo)
const cacheDir = state.getCacheDir()
const binaryDir = state.getBinaryDir(pkgVersion)
return validateOS().then((isValid) => {
if (!isValid) {
return throwFormErrorText(errors.invalidOS)()
}
})
.then(() => {
return fs.ensureDirAsync(cacheDir)
})
if (!(await validateOS())) {
return throwFormErrorText(errors.invalidOS)()
}
await fs.ensureDirAsync(cacheDir)
.catch({ code: 'EACCES' }, (err) => {
return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent`
Failed to access ${chalk.cyan(cacheDir)}:
@@ -297,26 +240,11 @@ const start = (options = {}) => {
${err.message}
`)
})
.then(() => {
return Promise.all([
state.getBinaryPkgAsync(binaryDir).then(state.getBinaryPkgVersion),
getVersionSpecifier(),
])
})
.then(([binaryVersion, versionSpecifier]) => {
if (!binaryUrlOverride && versionSpecifier) {
const computedBinaryUrl = getBinaryUrlFromPrereleaseNpmUrl(versionSpecifier)
if (computedBinaryUrl) {
debug('computed binary url from version specifier %o', { computedBinaryUrl, needVersion })
binaryUrlOverride = computedBinaryUrl
}
}
needVersion = binaryUrlOverride || needVersion
debug('installed version is', binaryVersion, 'version needed is', needVersion)
const binaryPkg = await state.getBinaryPkgAsync(binaryDir)
const binaryVersion = await state.getBinaryPkgVersion(binaryPkg)
const shouldInstall = () => {
if (!binaryVersion) {
debug('no binary installed under cli version')
@@ -336,7 +264,7 @@ const start = (options = {}) => {
return true
}
if ((binaryVersion === needVersion) || !util.isSemver(needVersion)) {
if ((binaryVersion === versionToInstall) || !util.isSemver(versionToInstall)) {
// our version matches, tell the user this is a noop
alreadyInstalledMsg()
@@ -344,96 +272,89 @@ const start = (options = {}) => {
}
return true
})
.then((shouldInstall) => {
// noop if we've been told not to download
if (!shouldInstall) {
debug('Not downloading or installing binary')
}
return
}
// noop if we've been told not to download
if (!shouldInstall()) {
return debug('Not downloading or installing binary')
}
if (needVersion !== pkgVersion) {
logger.log(
chalk.yellow(stripIndent`
${logSymbols.warning} Warning: Forcing a binary version different than the default.
if (envVarVersion) {
logger.log(
chalk.yellow(stripIndent`
${logSymbols.warning} Warning: Forcing a binary version different than the default.
The CLI expected to install version: ${chalk.green(pkgVersion)}
The CLI expected to install version: ${chalk.green(pkgVersion)}
Instead we will install version: ${chalk.green(needVersion)}
Instead we will install version: ${chalk.green(versionToInstall)}
These versions may not work properly together.
`),
)
These versions may not work properly together.
`),
)
logger.log()
}
logger.log()
}
const getLocalFilePath = async () => {
// see if version supplied is a path to a binary
return fs.pathExistsAsync(needVersion)
.then((exists) => {
if (exists) {
return path.extname(needVersion) === '.zip' ? needVersion : false
}
if (await fs.pathExistsAsync(versionToInstall)) {
return path.extname(versionToInstall) === '.zip' ? versionToInstall : false
}
const possibleFile = util.formAbsolutePath(needVersion)
const possibleFile = util.formAbsolutePath(versionToInstall)
debug('checking local file', possibleFile, 'cwd', process.cwd())
debug('checking local file', possibleFile, 'cwd', process.cwd())
return fs.pathExistsAsync(possibleFile)
.then((exists) => {
// if this exists return the path to it
// else false
if (exists && path.extname(possibleFile) === '.zip') {
return possibleFile
}
// if this exists return the path to it
// else false
if ((await fs.pathExistsAsync(possibleFile)) && path.extname(possibleFile) === '.zip') {
return possibleFile
}
return false
})
})
.then((pathToLocalFile) => {
if (pathToLocalFile) {
const absolutePath = path.resolve(needVersion)
return false
}
debug('found local file at', absolutePath)
debug('skipping download')
const pathToLocalFile = await getLocalFilePath()
const rendererOptions = getRendererOptions()
if (pathToLocalFile) {
const absolutePath = path.resolve(versionToInstall)
return new Listr([unzipTask({
progress: {
throttle: 100,
onProgress: null,
},
zipFilePath: absolutePath,
installDir,
rendererOptions,
})], { rendererOptions }).run()
}
debug('found local file at', absolutePath)
debug('skipping download')
if (options.force) {
debug('Cypress already installed at', installDir)
debug('but the installation was forced')
}
const rendererOptions = getRendererOptions()
debug('preparing to download and unzip version ', needVersion, 'to path', installDir)
return new Listr([unzipTask({
progress: {
throttle: 100,
onProgress: null,
},
zipFilePath: absolutePath,
installDir,
rendererOptions,
})], { rendererOptions }).run()
}
const downloadDir = os.tmpdir()
if (options.force) {
debug('Cypress already installed at', installDir)
debug('but the installation was forced')
}
return downloadAndUnzip({ version: needVersion, installDir, downloadDir })
})
// delay 1 sec for UX, unless we are testing
.then(() => {
return Promise.delay(1000)
})
.then(displayCompletionMsg)
})
debug('preparing to download and unzip version ', versionToInstall, 'to path', installDir)
const downloadDir = os.tmpdir()
await downloadAndUnzip({ version: versionToInstall, installDir, downloadDir })
// delay 1 sec for UX, unless we are testing
await Promise.delay(1000)
displayCompletionMsg()
}
module.exports = {
start,
_getVersionSpecifier: getVersionSpecifier,
_getBinaryUrlFromPrereleaseNpmUrl: getBinaryUrlFromPrereleaseNpmUrl,
_getBinaryUrlFromBuildInfo,
}
const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }) => {
+5 -1
View File
@@ -50,7 +50,11 @@ const getBinaryDir = (version = util.pkgVersion()) => {
return path.join(getVersionDir(version), getPlatFormBinaryFolder())
}
const getVersionDir = (version = util.pkgVersion()) => {
const getVersionDir = (version = util.pkgVersion(), buildInfo = util.pkgBuildInfo()) => {
if (buildInfo && !buildInfo.stable) {
version = ['beta', version, buildInfo.commitBranch, buildInfo.commitSha].join('-')
}
return path.join(getCacheDir(), version)
}
+4
View File
@@ -365,6 +365,10 @@ const util = {
return process.cwd()
},
pkgBuildInfo () {
return pkg.buildInfo
},
pkgVersion () {
return pkg.version
},
+11
View File
@@ -1,5 +1,6 @@
const _ = require('lodash')
const path = require('path')
const shell = require('shelljs')
const fs = require('../lib/fs')
@@ -19,6 +20,10 @@ const {
const packageJsonSrc = path.join('package.json')
const packageJsonDest = path.join('build', 'package.json')
function getStdout (cmd) {
return shell.exec(cmd).trim()
}
function preparePackageForNpmRelease (json) {
// modify the existing package.json
// to prepare it for releasing to npm
@@ -29,6 +34,12 @@ function preparePackageForNpmRelease (json) {
_.extend(json, {
version,
buildInfo: {
commitBranch: process.env.CIRCLE_BRANCH || getStdout('git branch --show-current'),
commitSha: getStdout('git rev-parse HEAD'),
commitDate: new Date(getStdout('git show -s --format=%ci')).toISOString(),
stable: false,
},
description,
homepage,
license,
+12 -2
View File
@@ -10,7 +10,17 @@ const hasVersion = (json) => {
return la(is.semver(json.version), 'cannot find version', json)
}
const changeVersion = (o) => ({ ...o, version: 'x.y.z' })
const normalizePackageJson = (o) => {
expect(o.buildInfo).to.include({ stable: false })
expect(o.buildInfo.commitBranch).to.match(/.+/)
expect(o.buildInfo.commitSha).to.match(/[a-f0-9]+/)
return {
...o,
version: 'x.y.z',
buildInfo: 'replaced by normalizePackageJson',
}
}
describe('package.json build', () => {
beforeEach(function () {
@@ -32,7 +42,7 @@ describe('package.json build', () => {
it('outputs expected properties', () => {
return makeUserPackageFile()
.then(changeVersion)
.then(normalizePackageJson)
.then(snapshot)
})
})
+1
View File
@@ -28,6 +28,7 @@ describe('cli', () => {
os.platform.returns('darwin')
// sinon.stub(util, 'exit')
sinon.stub(util, 'logErrorExit1')
sinon.stub(util, 'pkgBuildInfo').returns({ stable: true })
this.exec = (args) => {
const cliArgs = `node test ${args}`.split(' ')
+21
View File
@@ -25,6 +25,10 @@ describe('exec info', function () {
.withArgs('browsers').returns('/user/app/data/path/to/browsers')
.withArgs().returns('/user/app/data/path')
sinon.stub(util, 'pkgBuildInfo').returns({
stable: true,
})
sinon.stub(state, 'getCacheDir').returns('/user/path/to/binary/cache')
})
@@ -68,4 +72,21 @@ describe('exec info', function () {
await startInfoAndSnapshot('cypress redacts sensitive vars')
})
it('logs additional info about pre-releases', async () => {
util.pkgBuildInfo.returns({
stable: false,
commitSha: 'abc123',
commitBranch: 'someBranchName',
commitDate: new Date('02-02-2022').toISOString(),
})
await startInfoAndSnapshot('logs additional info about pre-releases')
})
it('logs if unbuilt development', async () => {
util.pkgBuildInfo.returns(undefined)
await startInfoAndSnapshot('logs additional info about development')
})
})
+17
View File
@@ -18,6 +18,7 @@ describe('lib/exec/versions', function () {
})
sinon.stub(util, 'pkgVersion').returns('4.5.6')
sinon.stub(util, 'pkgBuildInfo').returns({ stable: true })
})
describe('.getVersions', function () {
@@ -50,6 +51,22 @@ describe('lib/exec/versions', function () {
})
})
it('appends pre-release if not stable', async function () {
util.pkgBuildInfo.returns({ stable: false })
const v = await versions.getVersions()
expect(v.package).to.eql('4.5.6 (pre-release)')
})
it('appends development if missing buildInfo', async function () {
util.pkgBuildInfo.returns(undefined)
const v = await versions.getVersions()
expect(v.package).to.eql('4.5.6 (development)')
})
it('reports default versions if not found', function () {
// imagine package.json only has version there
state.getBinaryPkgAsync.withArgs(binaryDir).resolves({
+35 -138
View File
@@ -1,11 +1,9 @@
require('../../spec_helper')
const _ = require('lodash')
const os = require('os')
const path = require('path')
const chalk = require('chalk')
const Promise = require('bluebird')
const mockfs = require('mock-fs')
const mockedEnv = require('mocked-env')
const snapshot = require('../../support/snapshot')
const stdout = require('../../support/stdout')
@@ -75,6 +73,32 @@ describe('/lib/tasks/install', function () {
})
})
describe('non-stable builds', () => {
function runInstall () {
return install.start({
buildInfo: {
stable: false,
commitSha: 'abc123',
commitBranch: 'aBranchName',
commitDate: new Date('11-27-1996').toISOString(),
},
})
}
it('install from a constructed CDN URL', async function () {
await runInstall()
expect(download.start).to.be.calledWithMatch({
version: 'https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-abc123/cypress.zip',
})
})
it('logs a warning about installing a pre-release', async function () {
await runInstall()
snapshot(normalize(this.stdout.toString()))
})
})
describe('override version', function () {
it('warns when specifying cypress version in env', function () {
const version = '0.12.1'
@@ -460,145 +484,18 @@ describe('/lib/tasks/install', function () {
})
})
context('._getBinaryUrlFromPrereleaseNpmUrl', function () {
beforeEach(() => {
context('._getBinaryUrlFromBuildInfo', function () {
const buildInfo = {
commitSha: 'abc123',
commitBranch: 'aBranchName',
}
it('generates the expected URL', () => {
os.platform.returns('linux')
sinon.stub(os, 'arch').returns('x64')
})
it('returns binary url for prerelease npm url', function () {
expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.tgz'))
.to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.zip')
expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz'))
.to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip')
expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz'))
.to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip')
})
it('returns nothing for an invalid url', function () {
expect(install._getBinaryUrlFromPrereleaseNpmUrl('1.2.3')).to.be.undefined
expect(install._getBinaryUrlFromPrereleaseNpmUrl(null)).to.be.undefined
})
})
context('._getVersionSpecifier', function () {
let restoreEnv
beforeEach(function () {
sinon.stub(fs, 'readJSON').rejects()
restoreEnv && restoreEnv()
})
it('resolves undefined if no versionSpecifier found', async function () {
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.be.undefined
})
it('resolves with cypress.tgz URL if specified in npm argv', async function () {
restoreEnv = mockedEnv({
npm_config_argv: JSON.stringify({
original: ['npm', 'i', 'https://foo.com/cypress.tgz'],
}),
})
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.eq('https://foo.com/cypress.tgz')
})
it('resolves with cypress.tgz URL if specified in npm env npm_package_resolved', async function () {
restoreEnv = mockedEnv({
npm_package_resolved: 'https://foo.com/cypress.tgz',
})
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.eq('https://foo.com/cypress.tgz')
})
it('resolves with versionSpecifier from parent pkg.json', async function () {
fs.readJSON.withArgs('/foo/bar/baz/package.json').resolves({
dependencies: {
'cypress': '1.2.3',
},
})
fs.readJSON.withArgs('/foo/bar/package.json').resolves({
dependencies: {
'cypress': 'wrong',
},
})
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.eq('1.2.3')
})
it('resolves with devDependencies too', async function () {
fs.readJSON.withArgs('/foo/bar/baz/package.json').resolves({
devDependencies: {
'cypress': '4.5.6',
},
})
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.eq('4.5.6')
})
it('resolves with optionalDependencies too', async function () {
fs.readJSON.withArgs('/foo/bar/baz/package.json').resolves({
optionalDependencies: {
'cypress': '6.7.8',
},
})
expect(await install._getVersionSpecifier('/foo/bar/baz')).to.eq('6.7.8')
})
context('with win32 path functions and paths', async function () {
const oldPath = _.clone(path)
beforeEach(() => {
_.assign(path, path.win32)
})
afterEach(() => {
_.assign(path, oldPath)
})
it('resolves undefined if no versionSpecifier found', async function () {
expect(await install._getVersionSpecifier('C:\\foo\\bar\\baz')).to.be.undefined
})
it('resolves with versionSpecifier from parent pkg.json', async function () {
fs.readJSON.withArgs('C:\\foo\\bar\\baz\\package.json').resolves({
dependencies: {
'cypress': '1.2.3',
},
})
fs.readJSON.withArgs('C:\\foo\\bar\\package.json').resolves({
dependencies: {
'cypress': 'wrong',
},
})
expect(await install._getVersionSpecifier('C:\\foo\\bar\\baz')).to.eq('1.2.3')
})
it('resolves with devDependencies too', async function () {
fs.readJSON.withArgs('C:\\foo\\bar\\baz\\package.json').resolves({
devDependencies: {
'cypress': '4.5.6',
},
})
expect(await install._getVersionSpecifier('C:\\foo\\bar\\baz')).to.eq('4.5.6')
})
it('resolves with optionalDependencies too', async function () {
fs.readJSON.withArgs('C:\\foo\\bar\\baz\\package.json').resolves({
optionalDependencies: {
'cypress': '6.7.8',
},
})
expect(await install._getVersionSpecifier('C:\\foo\\bar\\baz')).to.eq('6.7.8')
})
expect(install._getBinaryUrlFromBuildInfo(buildInfo))
.to.eq(`https://cdn.cypress.io/beta/binary/0.0.0-development/linux-x64/aBranchName-abc123/cypress.zip`)
})
})
})
+58 -73
View File
@@ -11,45 +11,30 @@ The `@cypress/`-namespaced NPM packages that live inside the [`/npm`](../npm) di
### Prerequisites
- Ensure you have the following permissions set up:
- An AWS account with permission to create AWS access keys for the Cypress CDN.
- An AWS account with permission to access and write to the AWS S3, i.e. the Cypress CDN.
- Permissions for your npm account to publish the `cypress` package.
- Permissions to update releases in ZenHub.
- Set up the following environment variables:
- Cypress AWS access key and secret in `aws_credentials_json`, which looks like this:
- For the `release-automations` steps, you will need setup the following envs:
- GitHub token - generated yourself in github.
- [ZenHub API token](https://app.zenhub.com/dashboard/tokens) to interact with Zenhub. Found in 1Password.
- The `cypress-bot` GitHub app credentials. Found in 1Password.
```text
aws_credentials_json='{"bucket":"cdn.cypress.io","folder":"desktop","key":"...","secret":"..."}'
GITHUB_TOKEN="..."
ZENHUB_API_TOKEN="..."
GITHUB_APP_CYPRESS_INSTALLATION_ID=
GITHUB_APP_ID=
GITHUB_PRIVATE_KEY=
```
- A [GitHub token](https://github.com/settings/tokens) and a [CircleCI token](https://circleci.com/account/api) in `ci_json`:
```text
ci_json='{"githubToken":"...","circleToken":"..."}'
```
- You'll also need to put the GitHub token under its own variable and get a [ZenHub API token](https://app.zenhub.com/dashboard/tokens) for the `release-automations` step.
```text
GITHUB_TOKEN="..."
ZENHUB_API_TOKEN="..."
```
- The `cypress-bot` GitHub app credentials are also needed. Ask another team member who has done a deploy for those.
```text
GITHUB_APP_CYPRESS_INSTALLATION_ID=
GITHUB_APP_ID=
GITHUB_PRIVATE_KEY=
```
- For purging the Cloudflare cache (part of the `move-binaries` step), you'll need `CF_ZONEID` and `CF_TOKEN` set. These can be found in 1Password. If you don't have access, ask a team member who has done a deploy.
- For purging the Cloudflare cache (part of the `move-binaries` step), you'll need `CF_ZONEID` and `CF_TOKEN` set. These can be found in 1Password.
```text
CF_ZONEID="..."
CF_TOKEN="..."
```
- If you don't have access to 1Password, ask a team member who has done a deploy.
- Tip: Use [as-a](https://github.com/bahmutov/as-a) to manage environment variables for different situations.
### Before Publishing a New Version
@@ -67,44 +52,59 @@ of Cypress. You can see the progress of the test projects by opening the status
![Screenshot of status checks](https://i.imgur.com/AsQwzgO.png)
Once the `develop` branch for all test projects are reliably passing with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.tgz`, publishing can proceed.
### Steps to Publish a New Version
In the following instructions, "X.Y.Z" is used to denote the [next version of Cypress being published](./next-version.md).
1. `develop` should contain all of the changes made in `master`. However, this occasionally may not be the case. Ensure that `master` does not have any additional commits that are not on `develop` and all auto-generated pull requests designed to merge master into develop have been successfully merged.
1. Confirm that every issue labeled [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) has a ZenHub release set. **Tip:** there is a command in [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to list and check such issues. Without a ZenHub release issues will not be included in the right changelog.
2. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
2. Create or review the release-specific documentation and changelog in [cypress-documentation](https://github.com/cypress-io/cypress-documentation). If there is not already a release-specific PR open, create one.
- Use [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to generate a starting point for the changelog, based off of ZenHub:
```shell
cd packages/issues-in-release
yarn do:changelog --release <release label>
```
- Ensure the changelog is up-to-date and has the correct date.
- Merge any release-specific documentation changes into the main release PR.
- You can view the doc's [branch deploy preview](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md#pull-requests) by clicking 'Details' on the PR's `netlify-cypress-docs/deploy-preview` GitHub status check.
3. Use the `move-binaries` script to move the binaries for `<commit sha>` from `beta` to the `desktop` folder for `<new target version>`. This also purges the cloudflare cache for this version.
3. `develop` should contain all of the changes made in `master`. However, this occasionally may not be the case.
- Ensure that `master` does not have any additional commits that are not on `develop`.
- Ensure all auto-generated pull requests designed to merge master into develop have been successfully merged.
4. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
5. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.tgz`, publishing can proceed.
6. Log into AWS SSO with `aws sso login --profile <name_of_profile>`. The release scripts assumes you are using the `production` profile. If you have setup your credentials under a different profile, be sure to set the `AWS_PROFILE` environment variable.
7. Use the `prepare-release-artifacts` script (Mac/Linux only) to prepare the latest commit to a stable release. When you run this script, the following happens:
* the binaries for `<commit sha>` are moved from `beta` to the `desktop` folder for `<new target version>` in S3
* the Cloudflare cache for this version is purged
* the pre-prod `cypress.tgz` NPM package is converted to a stable NPM package ready for release
```shell
yarn move-binaries --sha <commit sha> --version <new target version>
yarn prepare-release-artifacts --sha <commit sha> --version <new target version>
```
4. Publish the new npm package under the `dev` tag, using your personal npm account.
- To find the link to the package file `cypress.tgz`:
1. In GitHub, go to the latest commit (the one whose sha you used in the last step).
![commit-link](https://user-images.githubusercontent.com/1157043/80608728-33fe6100-8a05-11ea-8b53-375303757b67.png)
2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z/<platform>/<long sha>/cypress.tgz` link.
![commit-bot-comment](../assets/cypress-bot-pre-release-comment.png)
- Make sure the `linux-x64` binary and npm package are present at the commented locations. See [Before Publishing a New Version](#before-publishing-a-new-version).
- Publish the `linux-x64` distribution to the npm registry straight from the URL:
You can pass `--dry-run` to see the commands this would run under the hood.
```shell
npm publish https://cdn.cypress.io/beta/npm/X.Y.Z/<long sha>/cypress.tgz --tag dev
```
:bangbang: Important :bangbang: Be sure to release the `linux-x64` distribution.
8. Validate you are logged in to `npm` with `npm whoami`. Otherwise log in with `npm login`.
5. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output:
9. Publish the generated npm package under the `dev` tag, using your personal npm account.
```shell
npm publish /tmp/cypress-prod.tgz --tag dev
```
10. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output:
```shell
dist-tags:
dev: 3.4.0 latest: 3.3.2
```
6. Test `cypress@X.Y.Z` to make sure everything is working.
11. Test `cypress@X.Y.Z` to make sure everything is working.
- Install the new version: `npm install -g cypress@X.Y.Z`
- Run a quick, manual smoke test:
- `cypress open`
@@ -113,43 +113,28 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
- [cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app) uses yarn and represents a typical consumer implementation.
- Optionally, do more thorough tests, for example test the new version of Cypress against the Cypress dashboard repo.
7. Confirm that every issue labeled [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) has a ZenHub release set. **Tip:** there is a command in [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to list and check such issues. Without a ZenHub release issues will not be included in the right changelog.
8. Deploy the release-specific documentation and changelog in [cypress-documentation](https://github.com/cypress-io/cypress-documentation).
- If there is not already a release-specific PR open, create one. You can use [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to generate a starting point for the changelog, based off of ZenHub:
```shell
cd packages/issues-in-release
yarn do:changelog --release <release label>
```
- Ensure the changelog is up-to-date and has the correct date.
- Merge any release-specific documentation changes into the main release PR.
- You can view the doc's [branch deploy preview](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md#pull-requests) by clicking 'Details' on the PR's `netlify-cypress-docs/deploy-preview` GitHub status check.
- Merge this PR into `master` to deploy it to production.
9. Make the new npm version the "latest" version by updating the dist-tag `latest` to point to the new version:
12. Make the new npm version the "latest" version by updating the dist-tag `latest` to point to the new version:
```shell
npm dist-tag add cypress@X.Y.Z
```
10. Run `binary-release` to update the [download server's manifest](https://download.cypress.io/desktop.json). This will also ensure the binary for the version is downloadable for each system.
13. Run `binary-release` to update the [download server's manifest](https://download.cypress.io/desktop.json). This will also ensure the binary for the version is downloadable for each system.
```shell
yarn binary-release --version X.Y.Z
```
11. If needed, push out any updated changes to the links manifest to [`on.cypress.io`](https://github.com/cypress-io/cypress-services/tree/develop/packages/on).
14. If needed, push out any updated changes to the links manifest to [`on.cypress.io`](https://github.com/cypress-io/cypress-services/tree/develop/packages/on).
12. If needed, deploy the updated [`cypress-example-kitchensink`][cypress-example-kitchensink] to `example.cypress.io` by following [these instructions under "Deployment"](../packages/example/README.md).
15. If needed, deploy the updated [`cypress-example-kitchensink`][cypress-example-kitchensink] to `example.cypress.io` by following [these instructions under "Deployment"](../packages/example/README.md).
13. Update the releases in [ZenHub](https://app.zenhub.com/workspaces/test-runner-5c3ea3baeb1e75374f7b0708/reports/release):
16. Update the releases in [ZenHub](https://app.zenhub.com/workspaces/test-runner-5c3ea3baeb1e75374f7b0708/reports/release):
- Close the current release in ZenHub.
- Create a new patch release (and a new minor release, if this is a minor release) in ZenHub, and schedule them both to be completed 2 weeks from the current date.
- Move all issues that are still open from the current release to the appropriate future release.
14. Bump `version` in [`package.json`](package.json), commit it to `develop`, tag it with the version, and push the tag up:
17. Bump `version` in [`package.json`](package.json), commit it to `develop`, tag it with the version, and push the tag up:
```shell
git commit -am "release X.Y.Z [skip ci]"
@@ -159,7 +144,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
git push origin vX.Y.Z
```
15. Merge `develop` into `master` and push both branches up. Note: pushing to `master` will automatically publish any independent npm packages that have not yet been published.
18. Merge `develop` into `master` and push both branches up. Note: pushing to `master` will automatically publish any independent npm packages that have not yet been published.
```shell
git push origin develop
@@ -168,7 +153,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
git push origin master
```
16. Inside of [cypress-io/release-automations][release-automations]:
19. Inside of [cypress-io/release-automations][release-automations]:
- Publish GitHub release to [cypress-io/cypress/releases](https://github.com/cypress-io/cypress/releases) using package `set-releases`:
```shell
@@ -183,9 +168,9 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
- Confirm there are no issues with the label [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) left
17. Publish a new docker image in [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images) under `included` for the new cypress version. Note: we use the base image with the Node version matching the bundled Node version. Instructions for updating `cypress-docker-images` can be found [here](https://github.com/cypress-io/cypress-docker-images/blob/master/CONTRIBUTING.md#add-new-included-image).
20. Publish a new docker image in [`cypress-docker-images`](https://github.com/cypress-io/cypress-docker-images) under `included` for the new cypress version. Note: we use the base image with the Node version matching the bundled Node version. Instructions for updating `cypress-docker-images` can be found [here](https://github.com/cypress-io/cypress-docker-images/blob/master/CONTRIBUTING.md#add-new-included-image).
18. Update example projects to the new version. For most projects, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
21. Update example projects to the new version. For most projects, you can go to the Renovate dependency issue and check the box next to `Update dependency cypress to X.Y.Z`. It will automatically create a PR. Once it passes, you can merge it. Try updating at least the following projects:
- [cypress-example-todomvc](https://github.com/cypress-io/cypress-example-todomvc/issues/99)
- [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)
@@ -195,7 +180,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
- [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.
19. Check if any test or example repositories have a branch for testing the features or fixes from the newly published version `x.y.z`. The branch should also be named `x.y.z`. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z`, merge it into `master`.
22. Check if any test or example repositories have a branch for testing the features or fixes from the newly published version `x.y.z`. The branch should also be named `x.y.z`. Check all `cypress-test-*` and `cypress-example-*` repositories, and if there is a branch named `x.y.z`, merge it into `master`.
**Test Repos**
@@ -71,18 +71,6 @@ For developer-facing behavior - props received, events emitted, any other side e
### E2E Tests
Certain side effects, like GraphQL mutations, do not fire from component tests, but can be monitored from an E2E test with `cy.intercept`. And some entire packages, like `reporter` are independent apps that can be mounted in an E2E test and tested for interactions with other parts of the system.
## Percy Snapshots
Percy snapshot diffs are reviewed and approved by somebody doing a PR review.
### Limitations and Notes
Percy snapshots confirm that the appearance of a given state matches the last approved snapshot of that state. They don't in and of themselves contain any information about correctness.
If a test only contains a percy snapshot, prefer a general name for that test as opposed to a specific detail of the snapshot. For example prefer "has expected appearance" to "has a purple outline" since nothing in the test is actually asserting that.
NOTE: 👆 This section needs more thought/discussion. Maybe "has a purple outline" is a good thing to have in the test name because it might alert somebody reviewing the snapshot about a change they should look for, or the thing we expected the snapshot to confirm. Ideally here we would end up with instructions and examples of what Percy tests we expect with new features and any conventions we have.
## Testing Style Guide
### String constants
+5 -5
View File
@@ -19,11 +19,11 @@
"watch": "yarn build --watch"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"@fortawesome/fontawesome-svg-core": "^1.2.34",
"@fortawesome/free-brands-svg-icons": "^5.15.2",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14",
"@fortawesome/fontawesome-free": "^6.0.0",
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/free-brands-svg-icons": "^6.0.0",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"@iconify/icons-vscode-icons": "^1.1.4",
"@iconify/react": "2.0.0-rc.8",
"@iconify/types": "^1.0.6",
+1
View File
@@ -187,3 +187,4 @@ describe('#startDevServer', () => {
})
})
})
.timeout(5000)
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "cypress",
"version": "9.5.0",
"version": "9.5.1",
"description": "Cypress.io end to end testing tool",
"private": true,
"scripts": {
@@ -44,7 +44,7 @@
"jscodeshift": "jscodeshift -t ./node_modules/js-codemod/transforms/arrow-function-arguments.js",
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.json,.vue .",
"lint-changed": "lint-changed",
"move-binaries": "node ./scripts/binary.js move-binaries",
"prepare-release-artifacts": "node ./scripts/prepare-release-artifacts.js",
"npm-release": "node scripts/npm-release.js",
"prestart": "yarn ensure-deps",
"start": "cypress open --dev --global",
@@ -66,7 +66,7 @@
"test-unit": "lerna exec yarn test-unit --ignore \"'{@packages/{driver,root,static,web-config,net-stubbing,rewriter,ui-components},@cypress/{webpack-dev-server,eslint-plugin-dev}}'\"",
"pretest-watch": "yarn ensure-deps",
"test-watch": "lerna exec yarn test-watch --ignore \"'@packages/{driver,root,static,web-config}'\"",
"type-check": "node scripts/type_check",
"type-check": "yarn lerna exec yarn type-check --scope @tooling/system-tests && node scripts/type_check",
"verify:mocha:results": "node ./scripts/verify_mocha_results",
"watch": "yarn gulp dev:watch",
"prepare": "husky install"
@@ -75,6 +75,7 @@
"nvm": "0.0.4"
},
"devDependencies": {
"@aws-sdk/credential-providers": "3.53.0",
"@cypress/commit-message-install": "3.1.3",
"@cypress/env-or-json-file": "2.0.0",
"@cypress/github-commit-status-check": "1.5.0",
@@ -165,6 +166,7 @@
"getenv": "^1.0.0",
"gift": "0.10.2",
"glob": "7.1.6",
"got": "11.8.3",
"graphql": "^15.5.1",
"gulp": "4.0.2",
"gulp-awspublish": "4.0.0",
@@ -70,7 +70,6 @@ export interface WizardDataShape {
detectedLanguage: NexusGenEnums['CodeLanguageEnum'] | null
detectedBundler: Bundler | null
detectedFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
__fakeInstalledPackagesForTesting: string[] | null
}
export interface MigrationDataShape{
@@ -168,7 +167,6 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
chosenManualInstall: false,
detectedBundler: null,
detectedFramework: null,
__fakeInstalledPackagesForTesting: null,
detectedLanguage: null,
},
migration: {
@@ -66,10 +66,6 @@ export class WizardDataSource {
}
async installedPackages (): Promise<string[]> {
if (this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting) {
return this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting
}
const packagesInitial = await this.packagesToInstall() || []
if (!this.ctx.currentProject) {
@@ -115,7 +115,7 @@ export async function getSpecs (projectRoot: string, config: OldCypressConfig):
const globs = integrationFolder === false
? []
: integrationFolder === 'cypress/integration'
? ['**/*'].map((glob) => `${integrationFolder}/${glob}`)
? ['**/*.{js,ts,jsx,tsx,coffee}'].map((glob) => `${integrationFolder}/${glob}`)
: integrationTestFiles.map((glob) => `${integrationFolder}/${glob}`)
let specs = integrationFolder === false
@@ -211,7 +211,7 @@ function createE2ETemplate (pluginPath: string, createConfigOptions: CreateConfi
if (!createConfigOptions.hasPluginsFile) {
return dedent`
e2e: {
setupNodeEvents(on, config) {}
setupNodeEvents(on, config) {},${formatObjectForConfig(options)}
}
`
}
@@ -14,7 +14,7 @@ function getTestFilesGlobs (config: OldCypressConfig, type: 'component' | 'integ
return ([] as string[]).concat(glob)
}
return ['**/*']
return ['**/*.{js,ts,jsx,tsx,coffee}']
}
export function getIntegrationTestFilesGlobs (config: OldCypressConfig): string[] {
@@ -30,7 +30,7 @@ export function isDefaultTestFiles (config: OldCypressConfig, type: 'component'
? getComponentTestFilesGlobs(config)
: getIntegrationTestFilesGlobs(config)
return testFiles.length === 1 && testFiles[0] === '**/*'
return testFiles.length === 1 && testFiles[0] === '**/*.{js,ts,jsx,tsx,coffee}'
}
export function getPluginsFile (config: OldCypressConfig) {
@@ -1,3 +1,5 @@
const stripAnsi = require('strip-ansi')
const { assertLogLength } = require('../../support/utils')
const { Promise } = Cypress
@@ -130,6 +132,10 @@ describe('src/cy/commands/fixtures', () => {
expect(err.message).to.include('A fixture file could not be found')
expect(err.message).to.include('cypress/fixtures/err')
// ensure ansi color codes are not embedded in the error msg
// https://github.com/cypress-io/cypress/issues/20208
expect(err.message).to.eq(stripAnsi(err.message))
done()
})
File diff suppressed because it is too large Load Diff
@@ -243,7 +243,7 @@ describe('per-test config', () => {
})
})
describe('in mulitple nested suites', {
describe('in multiple nested suites', {
foo: false,
}, () => {
describe('config in suite', {
+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.6",
"url-parse": "1.5.8",
"vanilla-text-mask": "5.1.1",
"vite": "2.8.4",
"webpack": "^4.44.2",
+1 -1
View File
@@ -251,7 +251,7 @@ class $Cypress {
// and those leak out into the stdout formatting.
const errMsg = _.isString(errResult)
? errResult
: `Expected ${format(errResult.key)} to be ${errResult.type}.\n\nInstead the value was: ${stringify(errResult.value)}\``
: `Expected ${format(errResult.key)} to be ${errResult.type}.\n\nInstead the value was: ${stringify(errResult.value)}`
throw new this.state('specWindow').Error(errMsg)
})
@@ -44,11 +44,13 @@ export const e2eProjectDirs = [
'migration-e2e-component-default-everything',
'migration-e2e-component-default-test-files',
'migration-e2e-component-default-with-types',
'migration-e2e-component-with-json-files',
'migration-e2e-custom-integration',
'migration-e2e-custom-test-files',
'migration-e2e-defaults',
'migration-e2e-defaults-no-specs',
'migration-e2e-export-default',
'migration-e2e-false-plugins-support-file',
'migration-e2e-fully-custom',
'migration-e2e-no-plugins-support-file',
'migration-specs-already-migrated',
@@ -1,6 +1,6 @@
import _ from 'lodash'
import { pipe, map } from 'wonka'
import type { Client } from '@urql/core'
import type { Client, TypedDocumentNode } from '@urql/core'
import { createClient, dedupExchange, errorExchange } from '@urql/core'
import { executeExchange } from '@urql/exchange-execute'
import { makeCacheExchange } from '@packages/frontend-shared/src/graphql/urqlClient'
@@ -13,7 +13,15 @@ import { GQLStubRegistry } from './stubgql-Registry'
import { pathToArray } from 'graphql/jsutils/Path'
import dedent from 'dedent'
export function testUrqlClient (context: ClientTestContext, onResult?: (result: any, context: ClientTestContext) => any): Client {
export type MutationResolverCallback<T extends TypedDocumentNode> = (
defineResult: (input: ResultType<T>) => ResultType<T>,
variables: Exclude<T['__variablesType'], undefined>) => ResultType<T> | void
export type ResultType<T> = T extends TypedDocumentNode<infer U, any> ? U : never
export function testUrqlClient (context: ClientTestContext,
onResult?: (result: any, context: ClientTestContext) => any,
mutationResolvers?: Map<string, MutationResolverCallback<any>>): Client {
return createClient({
url: '/__cypress/graphql',
exchanges: [
@@ -38,6 +46,22 @@ export function testUrqlClient (context: ClientTestContext, onResult?: (result:
},
}).end()
if (mutationResolvers && result.operation.kind === 'mutation') {
const firstMutation = result.operation.query.definitions[0]
if (firstMutation.kind === 'OperationDefinition') {
const mutationName = firstMutation.name?.value
if (mutationName && mutationResolvers[mutationName]) {
const val = mutationResolvers[mutationName]((conf: any) => (conf), result.operation.variables)
if (val) {
result.data = val
}
}
}
}
if (onResult) {
if (result.data.testFragmentMember) {
const val = onResult(result.data.testFragmentMember, context)
@@ -9,6 +9,7 @@ import type { TypedDocumentNode } from '@urql/vue'
import type { FragmentDefinitionNode } from 'graphql'
import { print } from 'graphql'
import { testUrqlClient } from './clientTestUrqlClient'
import type { MutationResolverCallback as MutationResolver, ResultType } from './clientTestUrqlClient'
import type { Component } from 'vue'
import { computed, watch, defineComponent, h, toRaw } from 'vue'
import { each } from 'lodash'
@@ -73,7 +74,7 @@ export const registerMountFn = ({ plugins }: MountFnOptions = {}) => {
createI18n(),
{
install (app) {
app.use(urql, testUrqlClient(context, options.onResult))
app.use(urql, testUrqlClient(context, options.onResult, mutationResolvers))
},
},
],
@@ -134,15 +135,35 @@ export const registerMountFn = ({ plugins }: MountFnOptions = {}) => {
}), mountingOptions).then(() => context)
}
const mutationResolvers: Map<string, MutationResolver<any>> = new Map()
function stubMutationResolver<Result, Variables, T extends TypedDocumentNode<Result, Variables>> (
document: T,
resolver: MutationResolver<T>,
) {
const definition = document.definitions[0]
if (definition.kind === 'OperationDefinition' && definition.name) {
mutationResolvers[definition.name.value] = resolver
} else {
throw new Error('only use mutation documents in stubMutationResolver first argument')
}
}
Cypress.Commands.add('mountFragment', mountFragment)
Cypress.Commands.add('stubMutationResolver', stubMutationResolver)
Cypress.Commands.add('mountFragmentList', (source, options) => {
// @ts-expect-error - todo: tim fix
return mountFragment(source, options, true)
})
}
type ResultType<T> = T extends TypedDocumentNode<infer U, any> ? U : never
beforeEach(() => {
// clean all resolvers after each test
mutationResolvers.clear()
})
}
type MountFragmentConfig<T extends TypedDocumentNode> = {
variables?: T['__variablesType']
@@ -184,6 +205,16 @@ declare global {
fragment: T,
config: MountFragmentConfig<T>
): Cypress.Chainable<ClientTestContext>
/**
* mock a mutation resolver when needed to spy on it or modify the result
* @param document
* @param resolver
*/
stubMutationResolver<Result, Variables, T extends TypedDocumentNode<Result, Variables>>(
document: T,
resolver: MutationResolver<T>
): Cypress.Chainable<ClientTestContext>
/**
* Mount helper for a component with a GraphQL fragment, as a list
*/
@@ -3,6 +3,7 @@ import { h } from 'vue'
// Subject Under Test
import Select from './Select.vue'
import IconHeart from '~icons/mdi/heart'
const selectText = defaultMessages.components.select
@@ -168,19 +169,19 @@ describe('<Select />', () => {
it('renders all of the slots', () => {
const vSlots = {
'item-body': () => 'Item Body',
'item-prefix': () => <div data-testid="item-prefix"></div>,
'item-suffix': () => <div data-testid="item-suffix"></div>,
'item-prefix': () => <IconHeart data-testid="item-prefix"></IconHeart>,
'item-suffix': () => <IconHeart data-testid="item-suffix"></IconHeart>,
'selected': () => 'Selected',
'input-prefix': () => <div data-testid="input-prefix"></div>,
'input-suffix': () => <div data-testid="input-suffix"></div>,
'input-prefix': () => <IconHeart data-testid="input-prefix"></IconHeart>,
'input-suffix': () => <IconHeart data-testid="input-suffix"></IconHeart>,
}
mountSelect({ vSlots })
// The input and tis prefixes and suffixes should be visible
cy.findByText('Selected').should('be.visible')
.get(`[data-testid=input-prefix]`).should('exist')
.get(`[data-testid=input-suffix]`).should('exist')
.get(`[data-testid=input-prefix]`).should('be.visible')
.get(`[data-testid=input-suffix]`).should('be.visible')
// The caret icon shouldn't exist because we overwrote it
.get(caretIconSelector).should('not.exist')
@@ -190,8 +191,8 @@ describe('<Select />', () => {
// The options and their prefixes + suffixes should be visible
.get(optionsSelector).should('be.visible')
.get(`[data-testid=item-prefix]`).should('exist')
.get(`[data-testid=item-suffix]`).should('exist')
.get(`[data-testid=item-prefix]`).should('be.visible')
.get(`[data-testid=item-suffix]`).should('be.visible')
.percySnapshot()
// Choose an option
@@ -251,6 +251,57 @@ describe('Full migration flow for each project', { retries: { openMode: 2, runMo
})
})
it('completes journey for migration-e2e-component-with-json-files', () => {
startMigrationFor('migration-e2e-component-with-json-files')
// default testFiles - auto
cy.get(renameAutoStep).should('exist')
cy.get(renameManualStep).should('exist')
cy.get(renameSupportStep).should('exist')
cy.get(setupComponentStep).should('exist')
cy.get(configFileStep).should('exist')
// Migration workflow
// before auto migration
cy.contains('cypress/integration/foo.spec.ts')
cy.contains('cypress/integration/spec.ts')
cy.contains('cypress/component/button.spec.js')
// after auto migration
cy.contains('cypress/e2e/foo.cy.ts')
cy.contains('cypress/e2e/spec.cy.ts')
cy.contains('cypress/component/button.cy.js')
runAutoRename()
cy.wait(100)
cy.withCtx(async (ctx) => {
const specs = [
'cypress/e2e/foo.cy.ts',
'cypress/component/button.cy.js',
]
for (const spec of specs) {
const stats = await ctx.actions.file.checkIfFileExists(ctx.path.join(spec))
expect(stats).to.not.be.null
}
})
skipCTMigration()
renameSupport()
migrateAndVerifyConfig()
finishMigrationAndContinue()
cy.withCtx(async (ctx) => {
const integrationFolderStats = await ctx.actions.file.checkIfFileExists(ctx.path.join('cypress', 'integration'))
expect(integrationFolderStats).to.be.null
})
checkOutcome()
})
it('completes journey for migration-e2e-component-default-with-types', () => {
startMigrationFor('migration-e2e-component-default-with-types')
// default testFiles - auto
@@ -506,6 +557,40 @@ describe('Full migration flow for each project', { retries: { openMode: 2, runMo
checkOutcome()
})
it('completes journey for migration-e2e-false-plugins-support-file', () => {
startMigrationFor('migration-e2e-false-plugins-support-file')
// defaults, rename all the things
// can rename integration->e2e
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// no supportFile
cy.get(renameSupportStep).should('not.exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// default testFiles but custom integration - can rename automatically
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// supportFile is false - cannot migrate
cy.get(renameSupportStep).should('not.exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// Migration workflow
// before auto migration
cy.contains('cypress/integration/foo.spec.js')
// after auto migration
cy.contains('cypress/e2e/foo.cy.js')
runAutoRename()
migrateAndVerifyConfig()
checkOutcome()
})
// TODO: Do we need to consider this case?
it.skip('completes journey for migration-e2e-defaults-no-specs', () => {
startMigrationFor('migration-e2e-defaults-no-specs')
@@ -2,12 +2,10 @@ import { BUNDLERS, FRONTEND_FRAMEWORKS, AllPackagePackages } from '@packages/sca
import { CODE_LANGUAGES } from '@packages/types/src'
function fakeInstalledDeps () {
cy.withCtx(async (ctx) => {
cy.withCtx(async (ctx, o) => {
const deps = (await ctx.wizard.packagesToInstall() ?? []).map((x) => x.package)
ctx.update((coreData) => {
coreData.wizard.__fakeInstalledPackagesForTesting = deps
})
o.sinon.stub(ctx.wizard, 'installedPackages').resolves(deps)
})
}
@@ -615,10 +613,6 @@ describe('Launchpad: Setup Project', () => {
})
})
beforeEach(() => {
fakeInstalledDeps()
})
const hasStorybookPermutations = [false, true]
FRONTEND_FRAMEWORKS.forEach((framework) => {
@@ -19,12 +19,10 @@ function verifyConfigFile (configFile: `cypress.config.${'js' | 'ts'}`) {
}
function fakeInstalledDeps () {
cy.withCtx(async (ctx) => {
cy.withCtx(async (ctx, o) => {
const deps = (await ctx.wizard.packagesToInstall() ?? []).map((x) => x.package)
ctx.update((coreData) => {
coreData.wizard.__fakeInstalledPackagesForTesting = deps
})
o.sinon.stub(ctx.wizard, 'installedPackages').resolves(deps)
})
}
@@ -197,7 +197,7 @@ query MigrationWizardQuery {
}
`
const query = useQuery({ query: MigrationWizardQueryDocument })
const query = useQuery({ query: MigrationWizardQueryDocument, pause: true })
const migration = computed(() => query.data.value?.migration)
const steps = computed(() => migration.value?.filteredSteps || [])
@@ -262,6 +262,17 @@ const SetInjectionLevel: ResponseMiddleware = function () {
this.res.wantsInjection = getInjectionLevel()
}
if (this.res.wantsInjection) {
// Chrome plans to make document.domain immutable in Chrome 106, with the default value
// of the Origin-Agent-Cluster header becoming 'true'. We explicitly disable this header
// so that we can continue to support tests that visit multiple subdomains in a single spec.
// https://github.com/cypress-io/cypress/issues/20147
//
// We set the header here only for proxied requests that have scripts injected that set the domain.
// Other proxied requests are ignored.
this.res.setHeader('Origin-Agent-Cluster', '?0')
}
this.res.wantsSecurityRemoved = this.config.modifyObstructiveCode && isReqMatchOriginPolicy && (
(this.res.wantsInjection === 'full')
|| resContentTypeIsJavaScript(this.incomingRes)
@@ -127,4 +127,66 @@ describe('http/response-middleware', function () {
}
}
})
describe('SetInjectionLevel', function () {
const { SetInjectionLevel } = ResponseMiddleware
let ctx
beforeEach(function () {
ctx = {
req: {
proxiedUrl: 'http://proxy.com',
cookies: {
'__cypress.initial': true,
},
headers: {
accept: ['text/html', 'application/xhtml+xml'],
},
},
res: {
setHeader: sinon.stub(),
},
getRemoteState: () => {
return {
strategy: 'http',
props: {
domain: 'proxy',
port: '80',
tld: 'com',
},
}
},
getRenderedHTMLOrigins: () => {
return {}
},
}
})
it('does not set Origin-Agent-Cluster header to false when injection is not expected', function () {
ctx.incomingRes = {
headers: {
'content-type': 'foo/bar',
},
}
return testMiddleware([SetInjectionLevel], ctx)
.then(() => {
expect(ctx.res.setHeader).not.to.be.calledWith('Origin-Agent-Cluster', '?0')
})
})
it('sets Origin-Agent-Cluster header to false when injection is expected', function () {
ctx.incomingRes = {
headers: {
'content-type': 'text/html',
},
}
return testMiddleware([SetInjectionLevel], ctx)
.then(() => {
expect(ctx.res.setHeader).to.be.calledWith('Origin-Agent-Cluster', '?0')
})
})
})
})
+1 -1
View File
@@ -14,7 +14,7 @@
"@cypress/react-tooltip": "0.5.3",
"@fontsource/mulish": "4.3.0",
"@fontsource/open-sans": "4.3.0",
"@fortawesome/fontawesome-free": "5.11.2",
"@fortawesome/fontawesome-free": "6.0.0",
"@packages/driver": "0.0.0-development",
"@packages/resolve-dist": "0.0.0-development",
"@packages/socket": "0.0.0-development",
+1 -1
View File
@@ -21,7 +21,7 @@
"@cypress/design-system": "0.0.0-development",
"@cypress/react-tooltip": "0.5.3",
"@fortawesome/free-regular-svg-icons": "5.15.2",
"@fortawesome/react-fontawesome": "^0.1.14",
"@fortawesome/react-fontawesome": "^0.1.17",
"@packages/driver": "0.0.0-development",
"@types/http-proxy": "1.17.4",
"@types/node": "14.14.31",
+1 -1
View File
@@ -25,7 +25,7 @@
"@cypress/webpack-preprocessor": "0.0.0-development",
"@fontsource/mulish": "4.3.0",
"@fontsource/open-sans": "4.3.0",
"@fortawesome/fontawesome-free": "5.12.1",
"@fortawesome/fontawesome-free": "6.0.0",
"@packages/driver": "0.0.0-development",
"@packages/icons": "0.0.0-development",
"@packages/reporter": "0.0.0-development",
@@ -31,6 +31,12 @@ export const iframesController = {
extraOptions,
})
// Chrome plans to make document.domain immutable in Chrome 106, with the default value
// of the Origin-Agent-Cluster header becoming 'true'. We explicitly disable this header
// so that we can continue to support tests that visit multiple subdomains in a single spec.
// https://github.com/cypress-io/cypress/issues/20147
res.setHeader('Origin-Agent-Cluster', '?0')
files.handleIframe(req, res, config, getRemoteState, extraOptions)
},
@@ -33,6 +33,12 @@ export const serveRunner = (runnerPkg: RunnerPkg, config: Cfg, res: Response) =>
const runnerPath = process.env.CYPRESS_INTERNAL_RUNNER_PATH || getPathToIndex(runnerPkg)
// Chrome plans to make document.domain immutable in Chrome 106, with the default value
// of the Origin-Agent-Cluster header becoming 'true'. We explicitly disable this header
// so that we can continue to support tests that visit multiple subdomains in a single spec.
// https://github.com/cypress-io/cypress/issues/20147
res.setHeader('Origin-Agent-Cluster', '?0')
return res.render(runnerPath, {
base64Config,
projectName: config.projectName,
+10 -1
View File
@@ -4,6 +4,8 @@ const debug = require('debug')('cypress:server:fixture')
const coffee = require('coffeescript')
const Promise = require('bluebird')
const jsonlint = require('jsonlint')
const stripAnsi = require('strip-ansi')
const errors = require('./errors')
const { fs } = require('./util/fs')
const glob = require('./util/glob')
@@ -60,7 +62,14 @@ module.exports = {
if (matches.length === 0) {
const relativePath = path.relative('.', p)
errors.throwErr('FIXTURE_NOT_FOUND', relativePath, extensions)
// TODO: there's no reason this error should be in
// the @packages/error list, it should be written in
// the driver since this error can only occur within
// driver commands and not outside of the test runner
const err = errors.get('FIXTURE_NOT_FOUND', relativePath, extensions)
err.message = stripAnsi(err.message)
throw err
}
debug('fixture matches found, using the first', matches)
+2 -1
View File
@@ -53,6 +53,7 @@
"data-uri-to-buffer": "2.0.1",
"dayjs": "^1.9.3",
"debug": "^4.3.2",
"dependency-tree": "8.1.2",
"duplexify": "4.1.1",
"electron-context-menu": "3.1.1",
"errorhandler": "1.5.1",
@@ -119,7 +120,7 @@
"ts-node": "^10.2.1",
"tslib": "2.3.1",
"underscore.string": "3.3.5",
"url-parse": "1.5.6",
"url-parse": "1.5.8",
"uuid": "8.3.2",
"which": "2.0.2",
"widest-line": "3.1.0"
+32
View File
@@ -0,0 +1,32 @@
require('../spec_helper')
const { iframesController } = require('../../lib/controllers/iframes')
const files = require('../../lib/controllers/files')
describe('controllers/iframes', () => {
describe('e2e', () => {
it('sets Origin-Agent-Cluster response header to false', () => {
sinon.stub(files, 'handleIframe')
const mockReq = {}
const mockRes = {
setHeader: sinon.stub(),
}
const controllerOptions = {
getSpec: sinon.stub(),
getRemoteState: sinon.stub(),
config: {},
}
iframesController.e2e(controllerOptions, mockReq, mockRes)
expect(mockRes.setHeader).to.have.been.calledWith('Origin-Agent-Cluster', '?0')
expect(files.handleIframe).to.have.been.calledWith(
mockReq, mockRes, controllerOptions.config, controllerOptions.getRemoteState, sinon.match({
specFilter: undefined, specType: 'integration',
}),
)
})
})
})
+19
View File
@@ -0,0 +1,19 @@
require('../spec_helper')
const { serveRunner } = require('../../lib/controllers/runner')
describe('controllers/runner', () => {
describe('serveRunner', () => {
it('sets Origin-Agent-Cluster response header to false', () => {
const mockRes = {
setHeader: sinon.stub(),
render: sinon.stub(),
}
serveRunner('runner', {}, mockRes)
expect(mockRes.setHeader).to.have.been.calledWith('Origin-Agent-Cluster', '?0')
expect(mockRes.render).to.have.been.called
})
})
})
+1 -1
View File
@@ -21,7 +21,7 @@
"@babel/preset-react": "7.9.4",
"@cypress/react-tooltip": "^0.5.3",
"@cypress/webpack-preprocessor": "0.0.0-development",
"@fortawesome/fontawesome-free": "5.12.1",
"@fortawesome/fontawesome-free": "6.0.0",
"@reach/dialog": "0.10.5",
"@reach/visually-hidden": "0.10.4",
"babel-loader": "8.1.0",
+1
View File
@@ -114,6 +114,7 @@ export async function buildCypressApp (options: BuildCypressAppOpts) {
meta.distDir('**', 'esprima', 'test'),
meta.distDir('**', 'bmp-js', 'test'),
meta.distDir('**', 'exif-parser', 'test'),
meta.distDir('**', 'app-module-path', 'test'),
], { force: true })
console.log('Deleted excess directories')
+11 -4
View File
@@ -113,6 +113,8 @@ export const prompts = {
export const moveBinaries = async (args = []) => {
debug('moveBinaries with args %o', args)
const options = arg({
'--s3bucket': String,
'--s3folder': String,
'--commit': String,
'--version': String,
// optional, if passed, only the binary for that platform will be moved
@@ -136,8 +138,13 @@ export const moveBinaries = async (args = []) => {
version: options['--version'],
}
const aws = uploadUtils.getS3Credentials()
const s3 = s3helpers.makeS3(aws)
const credentials = await uploadUtils.getS3Credentials()
const aws = {
'bucket': options['--s3bucket'] || uploadUtils.S3Configuration.bucket,
'folder': options['--s3folder'] || uploadUtils.S3Configuration.releaseFolder,
}
const s3 = s3helpers.makeS3(credentials)
// found s3 paths with last build for same commit for all platforms
const lastBuilds: Desktop[] = []
@@ -164,12 +171,12 @@ export const moveBinaries = async (args = []) => {
platformArch,
})
console.log('finding binary for %s in %s', platformArch, uploadDir)
console.log('finding binary in %s for %s in %s', aws.bucket, platformArch, uploadDir)
const list: string[] = await s3helpers.listS3Objects(uploadDir, aws.bucket, s3)
if (debug.enabled) {
console.log('all found subfolders')
console.log('all found sub-folders')
console.log(list.join('\n'))
}
+11 -5
View File
@@ -13,12 +13,18 @@ export const hasOnlyStringValues = (o) => {
*/
export const s3helpers = {
makeS3 (aws) {
la(is.unemptyString(aws.key), 'missing aws key')
la(is.unemptyString(aws.secret), 'missing aws secret')
la(is.unemptyString(aws.accessKeyId), 'missing aws accessKeyId')
la(is.unemptyString(aws.secretAccessKey), 'missing aws secretAccessKey')
if (!process.env.CIRCLECI) {
// sso is not required for CirceCI
la(is.unemptyString(aws.sessionToken), 'missing aws sessionToken')
}
return new S3({
accessKeyId: aws.key,
secretAccessKey: aws.secret,
accessKeyId: aws.accessKeyId,
secretAccessKey: aws.secretAccessKey,
sessionToken: aws.sessionToken,
})
},
@@ -40,7 +46,7 @@ export const s3helpers = {
debug('s3 data for %s', zipFile)
debug(data)
resolve()
resolve(null)
})
})
},
+7 -16
View File
@@ -9,16 +9,7 @@ const upload = require('./upload')
const uploadUtils = require('./util/upload')
const { s3helpers } = require('./s3-api')
const uploadTypes = {
binary: {
uploadFolder: 'binary',
uploadFileName: 'cypress.zip',
},
'npm-package': {
uploadFolder: 'npm',
uploadFileName: 'cypress.tgz',
},
}
const uploadTypes = uploadUtils.S3Configuration.betaUploadTypes
const getCDN = function (uploadPath) {
return [uploadUtils.getUploadUrl(), uploadPath].join('/')
@@ -32,16 +23,16 @@ const getUploadDirForPlatform = function (options) {
// the artifact will be uploaded for every platform and uploaded into under a unique folder
// https://cdn.cypress.io/beta/(binary|npm)/<version>/<platform>/<some unique version info>/cypress.zip
// For binary:
// beta/binary/9.4.2/win32-x64/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.zip
// beta/binary/9.4.2/win32-x64/develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.zip
// For NPM package:
// beta/npm/9.4.2/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.tgz
// beta/npm/9.4.2/develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.tgz
const getUploadPath = function (options) {
const { hash, uploadFileName } = options
return [getUploadDirForPlatform(options), hash, uploadFileName].join('/')
}
const setChecksum = (filename, key) => {
const setChecksum = async (filename, key) => {
console.log('setting checksum for file %s', filename)
console.log('on s3 object %s', key)
@@ -56,7 +47,7 @@ const setChecksum = (filename, key) => {
console.log('SHA256 checksum %s', checksum)
console.log('size', size)
const aws = uploadUtils.getS3Credentials()
const aws = await uploadUtils.getS3Credentials()
const s3 = s3helpers.makeS3(aws)
// S3 object metadata can only have string values
const metadata = {
@@ -66,7 +57,7 @@ const setChecksum = (filename, key) => {
// by default s3.copyObject does not preserve ACL when copying
// thus we need to reset it for our public files
return s3helpers.setUserMetadata(aws.bucket, key, metadata,
return s3helpers.setUserMetadata(uploadUtils.S3Configuration.bucket, key, metadata,
'application/zip', 'public-read', s3)
}
@@ -128,7 +119,7 @@ const uploadArtifactToS3 = function (args = []) {
.then(uploadUtils.saveUrl(`${options.type}-url.json`))
.catch((e) => {
console.error('There was an issue uploading the artifact.')
console.error(e)
throw e
})
}
+46 -49
View File
@@ -15,17 +15,13 @@ fs = Promise.promisifyAll(fs)
// TODO: refactor this
// system expects desktop application to be inside a file
// with this name
const zipName = 'cypress.zip'
const zipName = uploadUtils.S3Configuration.binaryZipName
module.exports = {
zipName,
getPublisher () {
return uploadUtils.getPublisher(this.getAwsObj)
},
getAwsObj () {
return uploadUtils.getS3Credentials()
async getPublisher () {
return uploadUtils.getPublisher()
},
// returns desktop folder for a given folder without platform
@@ -43,7 +39,7 @@ module.exports = {
let { folder, version, platformArch, name } = options
if (!folder) {
folder = this.getAwsObj().folder
folder = uploadUtils.S3Configuration.releaseFolder
}
la(check.unemptyString(folder), 'missing folder', options)
@@ -104,34 +100,34 @@ module.exports = {
},
s3Manifest (version) {
const publisher = this.getPublisher()
return this.getPublisher()
.then((publisher) => {
const { releaseFolder } = uploadUtils.S3Configuration
const aws = this.getAwsObj()
const headers = {
'Cache-Control': 'no-cache',
}
let manifest = null
const headers = {}
return new Promise((resolve, reject) => {
return this.createRemoteManifest(releaseFolder, version)
.then((src) => {
manifest = src
headers['Cache-Control'] = 'no-cache'
return gulp.src(src)
.pipe(rename((p) => {
p.dirname = `${releaseFolder}/${p.dirname}`
let manifest = null
return new Promise((resolve, reject) => {
return this.createRemoteManifest(aws.folder, version)
.then((src) => {
manifest = src
return gulp.src(src)
.pipe(rename((p) => {
p.dirname = `${aws.folder}/${p.dirname}`
return p
})).pipe(gulpDebug())
.pipe(publisher.publish(headers))
.pipe(awspublish.reporter())
.on('error', reject)
.on('end', resolve)
return p
})).pipe(gulpDebug())
.pipe(publisher.publish(headers))
.pipe(awspublish.reporter())
.on('error', reject)
.on('end', resolve)
})
}).finally(() => {
return fs.removeAsync(manifest)
})
}).finally(() => {
return fs.removeAsync(manifest)
})
},
@@ -144,26 +140,27 @@ module.exports = {
la(check.extension(path.extname(uploadPath))(file),
'invalid file to upload extension', file)
return new Promise((resolve, reject) => {
const publisher = this.getPublisher()
return this.getPublisher()
.then((publisher) => {
const headers = {
'Cache-Control': 'no-cache',
}
const headers = {}
return new Promise((resolve, reject) => {
return gulp.src(file)
.pipe(rename((p) => {
// rename to standard filename for upload
p.basename = path.basename(uploadPath, path.extname(uploadPath))
p.dirname = path.dirname(uploadPath)
headers['Cache-Control'] = 'no-cache'
return gulp.src(file)
.pipe(rename((p) => {
// rename to standard filename for upload
p.basename = path.basename(uploadPath, path.extname(uploadPath))
p.dirname = path.dirname(uploadPath)
return p
}))
.pipe(gulpDebug())
.pipe(publisher.publish(headers))
.pipe(awspublish.reporter())
.on('error', reject)
.on('end', resolve)
return p
}))
.pipe(gulpDebug())
.pipe(publisher.publish(headers))
.pipe(awspublish.reporter())
.on('error', reject)
.on('end', resolve)
})
})
},
}
+33 -29
View File
@@ -1,5 +1,4 @@
const _ = require('lodash')
const path = require('path')
const awspublish = require('gulp-awspublish')
const human = require('human-interval')
const la = require('lazy-ass')
@@ -7,7 +6,8 @@ const check = require('check-more-types')
const fse = require('fs-extra')
const os = require('os')
const Promise = require('bluebird')
const { configFromEnvOrJsonFile, filenameToShellVariable } = require('@cypress/env-or-json-file')
const { fromSSO, fromEnv } = require('@aws-sdk/credential-providers')
const konfig = require('../get-config')()
const { purgeCloudflareCache } = require('./purge-cloudflare-cache')
@@ -25,47 +25,50 @@ const formHashFromEnvironment = function () {
} = process
if (env.CIRCLECI) {
return `circle-${env.CIRCLE_BRANCH}-${env.CIRCLE_SHA1}`
return `${env.CIRCLE_BRANCH}-${env.CIRCLE_SHA1}`
}
throw new Error('Do not know how to form unique build hash on this CI')
}
const getS3Credentials = function () {
const key = path.join('scripts', 'support', 'aws-credentials.json')
const config = configFromEnvOrJsonFile(key)
if (!config) {
console.error('⛔️ Cannot find AWS credentials')
console.error('Using @cypress/env-or-json-file module')
console.error('and filename', key)
console.error('which is environment variable', filenameToShellVariable(key))
console.error('available environment variable keys')
console.error(Object.keys(process.env))
throw new Error('AWS config not found')
}
la(check.unemptyString(config.bucket), 'missing AWS config bucket')
la(check.unemptyString(config.folder), 'missing AWS config folder')
la(check.unemptyString(config.key), 'missing AWS key')
la(check.unemptyString(config.secret), 'missing AWS secret key')
return config
const S3Configuration = {
bucket: 'cdn.cypress.io',
releaseFolder: 'desktop',
binaryZipName: 'cypress.zip',
betaUploadTypes: {
binary: {
uploadFolder: 'binary',
uploadFileName: 'cypress.zip',
},
'npm-package': {
uploadFolder: 'npm',
uploadFileName: 'cypress.tgz',
},
},
}
const getPublisher = function (getAwsObj = getS3Credentials) {
const aws = getAwsObj()
const getS3Credentials = async function () {
// sso is not required for CirceCI
if (process.env.CIRCLECI) {
return fromEnv()()
}
return fromSSO({ profile: process.env.AWS_PROFILE || 'production' })()
}
const getPublisher = async function () {
const aws = await getS3Credentials()
// console.log("aws.bucket", aws.bucket)
return awspublish.create({
httpOptions: {
timeout: human('10 minutes'),
},
params: {
Bucket: aws.bucket,
Bucket: S3Configuration.bucket,
},
accessKeyId: aws.key,
secretAccessKey: aws.secret,
accessKeyId: aws.accessKeyId,
secretAccessKey: aws.secretAccessKey,
sessionToken: aws.sessionToken,
})
}
@@ -156,6 +159,7 @@ const saveUrl = (filename) => {
}
module.exports = {
S3Configuration,
getS3Credentials,
getPublisher,
purgeDesktopAppFromCache,
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
set -e # exit on error
PLATFORM=$(node -p 'process.platform')
if [[ $PLATFORM != "linux" && $PLATFORM != "darwin" ]]; then
echo "Currently, create-stable-npm-package is only supported on Linux and MacOS."
echo "See https://github.com/cypress-io/cypress/pull/20296#discussion_r817115583"
exit 1
fi
if [[ ! $1 ]]; then
echo "publish-npm-package takes the .tgz URL as the first argument"
exit 1
fi
if [[ $1 != *"linux-x64"* ]]; then
echo "Only publish the 'linux-x64' .tgz. A non-linux-x64 .tgz was passed."
exit 1
fi
set -x # log commands
TGZ_URL=$1
PREPROD_TGZ_PATH=/tmp/cypress-preprod.tgz
UNPACKED_PATH=/tmp/unpacked-cypress
PROD_TGZ_PATH=/tmp/cypress-prod.tgz
echo "Downloading tgz from TGZ_URL=$TGZ_URL"
curl $TGZ_URL -o $PREPROD_TGZ_PATH
echo "Untarring PREPROD_TGZ_PATH=$PREPROD_TGZ_PATH"
rm -rf $UNPACKED_PATH || true
mkdir $UNPACKED_PATH
tar -xzvf $PREPROD_TGZ_PATH -C $UNPACKED_PATH
export PKG_JSON_PATH=$UNPACKED_PATH/package/package.json
echo "Patching stable: true to package.json"
node <<EOF
const fs = require('fs')
const pkg = require("$PKG_JSON_PATH")
pkg.buildInfo.stable = true
const json = JSON.stringify(pkg, null, 2)
fs.writeFileSync("$PKG_JSON_PATH", json)
EOF
echo "New package.json:"
cat $UNPACKED_PATH/package/package.json
echo "Tarring..."
cd $UNPACKED_PATH
tar -czvf $PROD_TGZ_PATH *
set +x
echo "Prod NPM package built at:"
echo " $PROD_TGZ_PATH"
+27
View File
@@ -0,0 +1,27 @@
const minimist = require('minimist')
const shelljs = require('shelljs')
const args = minimist(process.argv.slice(2))
if (!/^[a-z0-9]{40}$/.test(args.sha)) {
throw new Error('A valid (40 character) commit SHA must be passed in `--sha`.')
}
if (!/^\d+\.\d+\.\d+$/.test(args.version)) {
throw new Error('A valid semantic version (X.Y.Z) must be passed in `--version`.')
}
// eslint-disable-next-line no-console
const log = (...args) => console.log('🏗', ...args)
const exec = args['dry-run'] ?
(...args) => log('Dry run, not executing:', ...args)
: (...args) => shelljs.exec(...args)
log('Running `move-binaries`...')
exec(`node ./scripts/binary.js move-binaries --sha ${args.sha} --version ${args.version}`)
const prereleaseNpmUrl = `https://cdn.cypress.io/beta/npm/${args.version}/linux-x64/develop-${args.sha}/cypress.tgz`
log('Running `create-stable-npm-package`...')
exec(`./scripts/create-stable-npm-package.sh ${prereleaseNpmUrl}`)
@@ -0,0 +1,10 @@
const shelljs = require('shelljs')
const snapshot = require('snap-shot-it')
describe('prepare-release-artifacts', () => {
it('runs expected commands', () => {
const stdout = shelljs.exec('yarn prepare-release-artifacts --dry-run --sha 57d0a85108fad6f77b39db88b8a7d8a3bfdb51a2 --version 1.2.3')
snapshot(stdout)
})
})
+159
View File
@@ -0,0 +1,159 @@
/* eslint-disable no-console */
const _ = require('lodash')
const minimist = require('minimist')
const Promise = require('bluebird')
const retry = require('bluebird-retry')
const got = require('got')
// always print the debug logs
const debug = require('debug')('*')
const { seconds, minutes } = require('./utils')
// we expect CircleCI to set the current polling job name
const jobName = process.env.CIRCLE_JOB || 'wait-on-circle-jobs'
const workflowId = process.env.CIRCLE_WORKFLOW_ID
const branchesToAlwaysFinalize = ['develop', '10.0-release']
const requireAllJobsToPass = !branchesToAlwaysFinalize.includes(process.env.CIRCLE_BRANCH)
const getAuth = () => `${process.env.CIRCLE_TOKEN}:`
const verifyCI = () => {
if (!process.env.CIRCLE_TOKEN) {
console.error('Cannot find CIRCLE_TOKEN')
process.exit(1)
}
if (!process.env.CIRCLE_WORKFLOW_ID) {
console.error('Cannot find CIRCLE_WORKFLOW_ID')
process.exit(1)
}
}
/**
* Job status
* - blocked (has not run yet)
* - running (currently running)
* - failed | success
*/
const getJobStatus = async (workflowId) => {
const auth = getAuth()
// typo at https://circleci.com/docs/2.0/api-intro/
// to retrieve all jobs, the url is "/workflow/:id/job"
const url = `https://${auth}@circleci.com/api/v2/workflow/${workflowId}/job`
const response = await got(url).json()
// returns something like
// {
// next_page_token: null,
// items: [
// {
// dependencies: [],
// job_number: 400959,
// id: '7021bcc7-90c1-47d9-bf99-c0372a4f8f49',
// started_at: '2020-07-20T19:45:46Z',
// name: 'build',
// project_slug: 'gh/cypress-io/cypress',
// status: 'success',
// type: 'build',
// stopped_at: '2020-07-20T19:50:07Z'
// }
// ]
// }
return response
}
const waitForAllJobs = async (jobNames, workflowId) => {
let response
try {
response = await getJobStatus(workflowId)
} catch (e) {
console.error(e)
process.exit(1)
}
// if a job is pending, its status will be "blocked"
const blockedJobs = _.filter(response.items, { status: 'blocked' })
const failedJobs = _.filter(response.items, { status: 'failed' })
const runningJobs = _.filter(response.items, { status: 'running' })
const blockedJobNames = _.map(blockedJobs, 'name')
const runningJobNames = _.map(runningJobs, 'name')
const failedJobNames = _.map(failedJobs, 'name')
if (requireAllJobsToPass && _.intersection(jobNames, failedJobNames).length) {
console.error('At least one failing job has prevented percy-finalize from running', failedJobs)
process.exit(1)
}
debug('failed jobs %o', _.map(failedJobs, 'name'))
debug('blocked jobs %o', blockedJobNames)
debug('running jobs %o', runningJobNames)
if (!runningJobs.length || (runningJobs.length === 1 && runningJobs[0].name === jobName)) {
// there are no more jobs to run, or this is the last running job
console.log('all jobs are done, finishing this job')
return Promise.resolve()
}
const futureOrRunning = _.union(blockedJobs, runningJobNames)
const jobsToWaitFor = _.intersection(jobNames, futureOrRunning)
// logging something every time this runs will avoid CI timing out if there is no activity for 10 mins.
console.log(`waiting for jobs, jobs outstanding: ${response.items.length}`)
debug('jobs to wait for %o', jobsToWaitFor)
if (!jobsToWaitFor.length) {
console.log('No more jobs to wait for!')
return Promise.resolve()
}
return Promise.reject(new Error('Jobs have not finished'))
}
const main = () => {
verifyCI()
const args = minimist(process.argv.slice(2), { boolean: false })
const jobNames = _
.chain(args['job-names'])
.split(',')
.without('true')
.map(_.trim)
.compact()
.value()
if (!jobNames.length) {
console.error('Missing argument: --job-names')
console.error('You must pass a comma separated list of Circle CI job names to wait for.')
process.exit(1)
}
debug('received circle jobs: %o', jobNames)
// https://github.com/demmer/bluebird-retry
retry(waitForAllJobs.bind(null, jobNames, workflowId), {
timeout: minutes(30), // max time for this job
interval: seconds(30), // poll intervals
max_interval: seconds(30),
}).then(() => {
console.log('all done')
}, (err) => {
console.error(err)
process.exit(1)
})
}
// execute main function if called from command line
if (require.main === module) {
main()
}
@@ -182,13 +182,13 @@ exports['testConfigOverrides / fails when passing invalid config values - [chrom
1) inline test config override throws error:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`""\`\`
Instead the value was: \`""\`
[stack trace lines]
2) inline test config override throws error when executed within cy cmd:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"null"\`\`
Instead the value was: \`"null"\`
[stack trace lines]
3) context config overrides throws error
@@ -197,7 +197,7 @@ Instead the value was: \`"null"\`\`
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
Error
@@ -209,7 +209,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
Error
@@ -221,7 +221,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
Error
@@ -234,7 +234,7 @@ https://on.cypress.io/config
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"not_an_http_url"\`\`
Instead the value was: \`"not_an_http_url"\`
https://on.cypress.io/config
Error
@@ -246,7 +246,7 @@ https://on.cypress.io/config
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
@@ -260,7 +260,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
Error
@@ -350,14 +350,14 @@ exports['testConfigOverrides / fails when passing invalid config values with bef
inline test config override throws error:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`""\`\`
Instead the value was: \`""\`
[stack trace lines]
2) runs all tests
inline test config override throws error when executed within cy cmd:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"null"\`\`
Instead the value was: \`"null"\`
[stack trace lines]
3) runs all tests
@@ -367,7 +367,7 @@ Instead the value was: \`"null"\`\`
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
Error
@@ -380,7 +380,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
Error
@@ -393,7 +393,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
Error
@@ -407,7 +407,7 @@ https://on.cypress.io/config
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"not_an_http_url"\`\`
Instead the value was: \`"not_an_http_url"\`
https://on.cypress.io/config
Error
@@ -420,7 +420,7 @@ https://on.cypress.io/config
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
@@ -435,7 +435,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
Error
@@ -517,7 +517,7 @@ exports['testConfigOverrides / correctly fails when invalid config values for it
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
Error
@@ -601,13 +601,13 @@ exports['testConfigOverrides / fails when passing invalid config values - [firef
1) inline test config override throws error:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`""\`\`
Instead the value was: \`""\`
[stack trace lines]
2) inline test config override throws error when executed within cy cmd:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"null"\`\`
Instead the value was: \`"null"\`
[stack trace lines]
3) context config overrides throws error
@@ -616,7 +616,7 @@ Instead the value was: \`"null"\`\`
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
[stack trace lines]
@@ -627,7 +627,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
[stack trace lines]
@@ -638,7 +638,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
[stack trace lines]
@@ -650,7 +650,7 @@ https://on.cypress.io/config
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"not_an_http_url"\`\`
Instead the value was: \`"not_an_http_url"\`
https://on.cypress.io/config
[stack trace lines]
@@ -661,7 +661,7 @@ https://on.cypress.io/config
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
@@ -674,7 +674,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
[stack trace lines]
@@ -763,14 +763,14 @@ exports['testConfigOverrides / fails when passing invalid config values with bef
inline test config override throws error:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`""\`\`
Instead the value was: \`""\`
[stack trace lines]
2) runs all tests
inline test config override throws error when executed within cy cmd:
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"null"\`\`
Instead the value was: \`"null"\`
[stack trace lines]
3) runs all tests
@@ -780,7 +780,7 @@ Instead the value was: \`"null"\`\`
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
[stack trace lines]
@@ -792,7 +792,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
[stack trace lines]
@@ -804,7 +804,7 @@ https://on.cypress.io/config
Expected \`defaultCommandTimeout\` to be a number.
Instead the value was: \`"500"\`\`
Instead the value was: \`"500"\`
https://on.cypress.io/config
[stack trace lines]
@@ -817,7 +817,7 @@ https://on.cypress.io/config
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`).
Instead the value was: \`"not_an_http_url"\`\`
Instead the value was: \`"not_an_http_url"\`
https://on.cypress.io/config
[stack trace lines]
@@ -829,7 +829,7 @@ https://on.cypress.io/config
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
@@ -843,7 +843,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
[stack trace lines]
@@ -924,7 +924,7 @@ exports['testConfigOverrides / correctly fails when invalid config values for it
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls.
Instead the value was: \`"1"\`\`
Instead the value was: \`"1"\`
https://on.cypress.io/config
[stack trace lines]
+8
View File
@@ -0,0 +1,8 @@
import { expect as _expect } from 'chai'
import _sinon from 'sinon'
declare global {
// these are made global in `spec_helper`
const expect: typeof _expect
const sinon: typeof _sinon
}
+2 -2
View File
@@ -351,8 +351,8 @@ export function removeProject (name) {
// returns the path to project fixture
// in the cyTmpDir
export function project (...args) {
return this.projectPath.apply(this, args)
export function project (name) {
return projectPath(name)
}
export function projectPath (name) {
-2
View File
@@ -2,8 +2,6 @@ import systemTests from './system-tests'
import dayjs from 'dayjs'
import _ from 'lodash'
const expect = global.expect as unknown as Chai.ExpectStatic
const STATIC_DATE = '2018-02-01T20:14:19.323Z'
const expectDurationWithin = function (obj, duration, low, high, reset) {
+1 -1
View File
@@ -166,7 +166,7 @@ const getResponse = function (responseSchema) {
}
const sendResponse = function (req, res, responseBody) {
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
const _writeRaw = res._writeRaw
res._writeRaw = function () {
+9 -6
View File
@@ -1,9 +1,10 @@
const snapshot = require('snap-shot-it')
import { SpawnOptions } from 'child_process'
import type { SpawnOptions } from 'child_process'
import stream from 'stream'
import { expect } from './spec_helper'
import { dockerSpawner } from './docker'
import Express from 'express'
const isCi = require('is-ci')
@@ -15,7 +16,6 @@ const path = require('path')
const http = require('http')
const human = require('human-interval')
const morgan = require('morgan')
const express = require('express')
const Bluebird = require('bluebird')
const debug = require('debug')('cypress:system-tests')
const httpsProxy = require('@packages/https-proxy')
@@ -32,7 +32,8 @@ require(`@packages/server/lib/project-base`)
type CypressConfig = { [key: string]: any }
type BrowserName = 'electron' | 'firefox' | 'chrome'
export type BrowserName = 'electron' | 'firefox' | 'chrome'
| '!electron' | '!chrome' | '!firefox'
type ExecResult = {
code: number
@@ -47,7 +48,8 @@ export type ItOptions = ExecOptions & {
* If a function is supplied, it will be executed instead of running the `systemTests.exec` function immediately.
*/
onRun?: (
execFn: ExecFn
execFn: ExecFn,
browser: BrowserName
) => Promise<any> | any
/**
* Same as using `systemTests.it.only`.
@@ -503,7 +505,7 @@ const startServer = function (obj) {
ensurePort(port)
const app = express()
const app = Express()
const srv = https ? httpsProxy.httpsServer(app) : new http.Server(app)
@@ -516,7 +518,7 @@ const startServer = function (obj) {
}
if (obj.static) {
app.use(express.static(path.join(__dirname, '../projects/e2e'), {}))
app.use(Express.static(path.join(__dirname, '../projects/e2e'), {}) as Express.RequestHandler)
}
return new Bluebird((resolve) => {
@@ -705,6 +707,7 @@ const systemTests = {
args = _.compact(args)
// avoid snapshot cwd issue - see /patches/snap-shot* for more information
// @ts-ignore
global.CACHED_CWD_FOR_SNAP_SHOT_IT = path.join(__dirname, '..')
return snapshot.apply(null, args)
+4 -1
View File
@@ -5,6 +5,7 @@
"private": true,
"main": "index.js",
"scripts": {
"type-check": "tsc --project .",
"projects:yarn:install": "node ./scripts/projects-yarn-install.js",
"test": "node ./scripts/run.js --glob-in-dir='{test,test-binary}'",
"test:ci": "node ./scripts/run.js"
@@ -28,6 +29,8 @@
"@packages/socket": "0.0.0-development",
"@packages/ts": "0.0.0-development",
"@storybook/testing-vue3": "0.0.1",
"@types/chai": "4.2.15",
"@types/mocha": "9.1.0",
"babel-loader": "8.1.0",
"bluebird": "3.7.2",
"body-parser": "1.19.0",
@@ -45,7 +48,7 @@
"dayjs": "^1.9.3",
"debug": "^4.3.2",
"dockerode": "3.3.1",
"execa": "1.0.0",
"execa": "4",
"express": "4.17.1",
"express-session": "1.16.1",
"express-useragent": "1.0.15",
@@ -1,5 +1,5 @@
const _ = require('lodash')
const execa = require('execa')
const { execa } = require('execa')
const util = require('util')
const si = require('systeminformation')
@@ -26,7 +26,7 @@ The user will need to rename and/or move their only component spec, `button.cy.j
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the componen ttesting reconfiguration workflow.
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the component testing reconfiguration workflow.
| Before | After|
|---|---|
@@ -25,7 +25,7 @@ No manual migration, since the user has a non default componentFolder.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the componen ttesting reconfiguration workflow.
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the component testing reconfiguration workflow.
| Before | After|
|---|---|
@@ -0,0 +1,41 @@
## Migration E2E Component With JSON files
A project with E2E and CT, both using default folders. Everything is default. It
includes JSON files in the integration and component folders, migrating those,
without changing the file extension.
The following migration steps will be used during this migration:
- [x] automatic folder rename of cypress/integration to cypress/e2e
- [x] manual file rename
- [x] rename support
- [x] update config file
- [x] setup component testing
## Automatic Migration
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/foo.spec.ts` | `e2e/foo.cy.ts` |
| `integration/spec.ts` | `e2e/spec.cy.ts` |
| `component/button.spec.js` | `component/button.cy.js` |
## Manual Files
The user will need to rename and/or move their only component spec, `button.cy.js` to its new location.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the component testing reconfiguration workflow.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,5 @@
module.exports = (on, config) => {
return {
test: 'value',
}
}
@@ -0,0 +1,14 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
component: {
setupNodeEvents (on, config) {},
},
})
@@ -0,0 +1,31 @@
## Migration E2E No Plugins No Support
An e2e project with `plugins/index.js` and `support/index.js` set to false.
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
- [ ] rename support
- [x] update config file
- [ ] setup component testing
## Automatic Migration
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/foo.spec.js` | `e2e/basic.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
This step is not used. There is no `supportFile` to rename.
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,4 @@
{
"pluginsFile": false,
"supportFile": false
}
@@ -0,0 +1,8 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {},
supportFile: false,
},
})
+19 -8
View File
@@ -2,6 +2,18 @@ import systemTests from '../lib/system-tests'
const beforeBrowserLaunchProject = 'plugin-before-browser-launch-deprecation'
const includesString = (s: string) => {
return (stdout: string) => {
expect(stdout).to.include(s)
}
}
const excludesString = (s: string) => {
return (stdout: string) => {
expect(stdout).to.not.include(s)
}
}
describe('deprecated before:browser:launch args', () => {
systemTests.setup()
@@ -28,8 +40,7 @@ describe('deprecated before:browser:launch args', () => {
project: beforeBrowserLaunchProject,
spec: 'app.cy.js',
snapshot: true,
stdoutInclude: 'Deprecation Warning:',
psInclude: ['--foo', '--bar'],
onStdout: includesString('Deprecation Warning:'),
})
systemTests.it('using non-deprecated API - no warning', {
@@ -46,8 +57,7 @@ describe('deprecated before:browser:launch args', () => {
project: beforeBrowserLaunchProject,
spec: 'app.cy.js',
snapshot: true,
stdoutExclude: 'Deprecation Warning:',
psInclude: ['--foo', '--bar'],
onStdout: excludesString('Deprecation Warning:'),
})
systemTests.it('concat return returns once', {
@@ -71,10 +81,12 @@ describe('deprecated before:browser:launch args', () => {
return exec({ originalTitle: `deprecated before:browser:launch args / concat return returns once per test run - [firefox,chromium]` })
},
stdoutInclude: 'Deprecation Warning:',
onStdout: includesString('Deprecation Warning:'),
})
systemTests.it('no mutate return', {
// TODO: fix/remove this test, it should be warning but is not
// https://github.com/cypress-io/cypress/issues/20436
systemTests.it.skip('no mutate return', {
// TODO: implement webPreferences.additionalArgs here
// once we decide if/what we're going to make the implemenation
// SUGGESTION: add this to Cypress.browser.args which will capture
@@ -88,8 +100,7 @@ describe('deprecated before:browser:launch args', () => {
project: beforeBrowserLaunchProject,
spec: 'app.cy.js',
snapshot: true,
stdoutInclude: 'Deprecation Warning:',
psInclude: '--foo',
onStdout: includesString('Deprecation Warning:'),
})
// TODO: these errors could be greatly improved by the code frame
-1
View File
@@ -25,7 +25,6 @@ describe('e2e firefox', function () {
config: {
video: false,
},
exit: false,
onRun: (exec) => {
return exec()
.then(() => {
+4 -4
View File
@@ -1,4 +1,4 @@
import systemTests from '../lib/system-tests'
import systemTests, { BrowserName } from '../lib/system-tests'
describe('e2e headless', function () {
systemTests.setup()
@@ -25,7 +25,7 @@ describe('e2e headless', function () {
systemTests.it('pass for browsers that do not need xvfb', {
...baseSpec,
browser: ['chrome', 'chrome-beta', 'firefox'],
browser: ['chrome', 'firefox'],
expectedExitCode: 0,
onRun (exec) {
return exec().then(({ stderr }) => {
@@ -60,10 +60,10 @@ describe('e2e headless', function () {
// "can not record video in headed mode" error
// this trick allows us to have 1 snapshot for electron
// and 1 for every other browser
;[
;([
'electron',
'!electron',
].map((b) => {
] as BrowserName[]).map((b) => {
systemTests.it(`tests in headed mode pass in ${b}`, {
spec: 'headless.cy.js',
config: {
+1 -1
View File
@@ -2,7 +2,7 @@ import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import systemTests from '../lib/system-tests'
let counts = null
let counts: Record<string, number> | null = null
const urlencodedParser = bodyParser.urlencoded({ extended: false })
const jsonParser = bodyParser.json()
+3 -1
View File
@@ -90,7 +90,9 @@ describe('e2e screenshots', () => {
fs.statAsync(screenshot4).get('size'),
fs.statAsync(screenshot5).get('size'),
fs.statAsync(screenshot6).get('size'),
fs.statAsync(screenshot7).get('size'),
// Ignore comparing 6 and 7 since they can sometimes be the same since we take the screenshot as close to the failure as possible and
// the test run error may not have displayed yet. Leaving this commented in case we want to change this behavior in the future
// fs.statAsync(screenshot7).get('size'),
fs.statAsync(screenshot8).get('size'),
fs.statAsync(screenshot9).get('size'),
])
@@ -1,6 +1,6 @@
import fs from 'fs-extra'
import path from 'path'
import systemTests, { expect } from '../lib/system-tests'
import systemTests, { expect, BrowserName } from '../lib/system-tests'
import Fixtures from '../lib/fixtures'
const e2ePath = Fixtures.projectPath('e2e')
@@ -35,7 +35,7 @@ describe('testConfigOverrides', () => {
// window.Error throws differently for firefox. break into
// browser permutations for snapshot comparisons
const permutations = [
const permutations: BrowserName[][] = [
['chrome', 'electron'],
['firefox'],
]
+23
View File
@@ -0,0 +1,23 @@
{
"extends": "../packages/ts/tsconfig.json",
"include": [
"test",
"lib",
],
"files": [
"../packages/ts/index.d.ts",
"./globals.d.ts"
],
"compilerOptions": {
"types": [
"mocha",
"node",
"chai"
],
"noEmit": true,
"lib": ["esnext"],
"skipLibCheck": true,
"noImplicitReturns": false,
"strictNullChecks": false
}
}
+1037 -90
View File
File diff suppressed because it is too large Load Diff