mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-09 16:10:11 -06:00
chore: convert CLI tests to vitest (#32416)
* chore: rename snapshots and spec files to fit vitest convention (#32405) * chore: move compiled files to dist directory to make vitest convertion easier (#32406) * chore: convert utils to vitest (#32407) * chore: convert logger to vitest * chore: convert errors spec to vitest * chore: convert cypress spec to vitest * chore: convert `exec` directory to `vitest` (#32428) * chore: cut over exec directory to vitest * Update cli/test/lib/exec/run.spec.ts * Update cli/test/lib/exec/run.spec.ts * Update cli/test/lib/exec/run.spec.ts * chore: convert the CLI and build script specs over to vitest (#32438) * chore: convert tasks directory to vitest (#32434) change way verify module is exported due to issues interpreting module (thinks its an esm) rework scripts as we cannot run an empty mocha suite chore: fix snapshots and verify requires that are internal to the cypress project fix stubbing issues with fs-extra which is also used by request-progress under the hood fix issues where xvfb was stopping prematurely * chore: remove files no longer used now that mocha tests are converted to vitest (#32455) * build binaries * chore: fix CLI tests (#32484) * chore: remove CI branch
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
# Bump this version to force CI to re-create the cache from scratch.
|
||||
9-2-2025-cli2
|
||||
9-12-2025
|
||||
|
||||
@@ -38,7 +38,7 @@ mainBuildFilters: &mainBuildFilters
|
||||
- /^release\/\d+\.\d+\.\d+$/
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- 'update-v8-snapshot-cache-on-develop'
|
||||
- 'update-tsx'
|
||||
- 'feature/cli_to_vitest'
|
||||
|
||||
# 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
|
||||
@@ -49,7 +49,7 @@ macWorkflowFilters: &darwin-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'update-tsx', << pipeline.git.branch >> ]
|
||||
- equal: [ 'feature/cli_to_vitest', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -60,7 +60,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'chore/migrate-electron-lib-ts', << pipeline.git.branch >> ]
|
||||
- equal: [ 'feature/cli_to_vitest', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -83,7 +83,7 @@ windowsWorkflowFilters: &windows-workflow-filters
|
||||
- equal: [ develop, << pipeline.git.branch >> ]
|
||||
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
|
||||
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
|
||||
- equal: [ 'chore/migrate-electron-lib-ts', << pipeline.git.branch >> ]
|
||||
- equal: [ 'feature/cli_to_vitest', << pipeline.git.branch >> ]
|
||||
- matches:
|
||||
pattern: /^release\/\d+\.\d+\.\d+$/
|
||||
value: << pipeline.git.branch >>
|
||||
@@ -163,7 +163,7 @@ commands:
|
||||
name: Set environment variable to determine whether or not to persist artifacts
|
||||
command: |
|
||||
echo "Setting SHOULD_PERSIST_ARTIFACTS variable"
|
||||
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-tsx" ]]; then
|
||||
echo 'if ! [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "feature/cli_to_vitest" ]]; then
|
||||
export SHOULD_PERSIST_ARTIFACTS=true
|
||||
fi' >> "$BASH_ENV"
|
||||
# You must run `setup_should_persist_artifacts` command and be using bash before running this command
|
||||
@@ -1828,7 +1828,7 @@ jobs:
|
||||
source ./scripts/ensure-node.sh
|
||||
yarn lerna run types
|
||||
- sanitize-verify-and-store-mocha-results:
|
||||
expectedResultCount: 20
|
||||
expectedResultCount: 19
|
||||
|
||||
verify-release-readiness:
|
||||
<<: *defaults
|
||||
|
||||
@@ -13,4 +13,5 @@ package.json
|
||||
/react
|
||||
/vue
|
||||
/svelte
|
||||
/mount-utils
|
||||
/mount-utils
|
||||
tsconfig.esm.json
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
spec: 'test/**/*_spec.ts',
|
||||
timeout: 10000,
|
||||
reporter: 'spec',
|
||||
recursive: true
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
exports['package.json build outputs expected properties 1'] = {
|
||||
'name': 'test',
|
||||
'engines': 'test engines',
|
||||
'version': 'x.y.z',
|
||||
'buildInfo': 'replaced by normalizePackageJson',
|
||||
'description': 'Cypress is a next generation front end testing tool built for the modern web',
|
||||
'homepage': 'https://cypress.io',
|
||||
'license': 'MIT',
|
||||
'bugs': {
|
||||
'url': 'https://github.com/cypress-io/cypress/issues',
|
||||
},
|
||||
'repository': {
|
||||
'type': 'git',
|
||||
'url': 'https://github.com/cypress-io/cypress.git',
|
||||
},
|
||||
'keywords': [
|
||||
'automation',
|
||||
'browser',
|
||||
'cypress',
|
||||
'cypress.io',
|
||||
'e2e',
|
||||
'end-to-end',
|
||||
'integration',
|
||||
'component',
|
||||
'mocks',
|
||||
'runner',
|
||||
'spies',
|
||||
'stubs',
|
||||
'test',
|
||||
'testing',
|
||||
],
|
||||
'types': 'types',
|
||||
'scripts': {
|
||||
'postinstall': 'node index.js --exec install',
|
||||
'size': 't="$(npm pack .)"; wc -c "${t}"; tar tvf "${t}"; rm "${t}";',
|
||||
},
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
exports['cypress .run resolves with contents of tmp file 1'] = {
|
||||
'code': 0,
|
||||
'failingTests': [],
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
exports['download status errors 1'] = `
|
||||
Error: The Cypress App could not be downloaded.
|
||||
|
||||
Does your workplace require a proxy to be used to access the Internet? If so, you must configure the HTTP_PROXY environment variable before downloading Cypress. Read more: https://on.cypress.io/proxy-configuration
|
||||
|
||||
Otherwise, please check network connectivity and try again:
|
||||
|
||||
----------
|
||||
|
||||
URL: https://download.cypress.io/desktop?platform=OS&arch=x64
|
||||
404 - Not Found
|
||||
|
||||
----------
|
||||
|
||||
Platform: OS-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['latest desktop url 1'] = `
|
||||
https://download.cypress.io/desktop?platform=OS&arch=ARCH
|
||||
`
|
||||
|
||||
exports['specific version desktop url 1'] = `
|
||||
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/OS-ARCH/cypress.zip
|
||||
`
|
||||
|
||||
exports['desktop url from template with version'] = `
|
||||
https://mycompany/0.20.2/OS-ARCH/cypress.zip
|
||||
`
|
||||
|
||||
exports['desktop url from template with multiple replacements'] = `
|
||||
https://download.cypress.io/desktop/0.20.2/OS/ARCH/cypress-0.20.2-OS-ARCH.zip?referrer=https://download.cypress.io/desktop/0.20.2&version=0.20.2
|
||||
`
|
||||
|
||||
exports['desktop url from template with escaped dollar sign'] = `
|
||||
https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip
|
||||
`
|
||||
|
||||
exports['desktop url from template wrapped in quote'] = `
|
||||
https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip
|
||||
`
|
||||
|
||||
exports['desktop url from template with escaped dollar sign wrapped in quote'] = `
|
||||
https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip
|
||||
`
|
||||
|
||||
exports['base url from CYPRESS_DOWNLOAD_MIRROR 1'] = `
|
||||
https://cypress.example.com/desktop/0.20.2?platform=OS&arch=ARCH
|
||||
`
|
||||
|
||||
exports['base url from CYPRESS_DOWNLOAD_MIRROR with trailing slash 1'] = `
|
||||
https://cypress.example.com/desktop/0.20.2?platform=OS&arch=ARCH
|
||||
`
|
||||
|
||||
exports['base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory 1'] = `
|
||||
https://cypress.example.com/example/desktop/0.20.2?platform=OS&arch=ARCH
|
||||
`
|
||||
|
||||
exports['base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory and trailing slash 1'] = `
|
||||
https://cypress.example.com/example/desktop/0.20.2?platform=OS&arch=ARCH
|
||||
`
|
||||
@@ -1,102 +0,0 @@
|
||||
exports['errors individual has the following errors 1'] = [
|
||||
'CYPRESS_RUN_BINARY',
|
||||
'binaryNotExecutable',
|
||||
'childProcessKilled',
|
||||
'failedDownload',
|
||||
'failedUnzip',
|
||||
'failedUnzipWindowsMaxPathLength',
|
||||
'incompatibleHeadlessFlags',
|
||||
'incompatibleTestTypeFlags',
|
||||
'incompatibleTestingTypeAndFlag',
|
||||
'invalidCacheDirectory',
|
||||
'invalidConfigFile',
|
||||
'invalidCypressEnv',
|
||||
'invalidOS',
|
||||
'invalidRunProjectPath',
|
||||
'invalidSmokeTestDisplayError',
|
||||
'invalidTestingType',
|
||||
'missingApp',
|
||||
'missingDependency',
|
||||
'missingXvfb',
|
||||
'nonZeroExitCodeXvfb',
|
||||
'notInstalledCI',
|
||||
'smokeTestFailure',
|
||||
'unexpected',
|
||||
'unknownError',
|
||||
'versionMismatch',
|
||||
]
|
||||
|
||||
exports['child kill error object'] = {
|
||||
'description': 'The Test Runner unexpectedly exited via a [36mexit[39m event with signal [36mSIGKILL[39m',
|
||||
'solution': 'Please search Cypress documentation for possible solutions:\n\n [34mhttps://on.cypress.io[39m\n\nCheck if there is a GitHub issue describing this crash:\n\n [34mhttps://github.com/cypress-io/cypress/issues[39m\n\nConsider opening a new issue.',
|
||||
}
|
||||
|
||||
exports['Error message'] = `
|
||||
The Test Runner unexpectedly exited via a [36mexit[39m event with signal [36mSIGKILL[39m
|
||||
|
||||
Please search Cypress documentation for possible solutions:
|
||||
|
||||
[34mhttps://on.cypress.io[39m
|
||||
|
||||
Check if there is a GitHub issue describing this crash:
|
||||
|
||||
[34mhttps://github.com/cypress-io/cypress/issues[39m
|
||||
|
||||
Consider opening a new issue.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
`
|
||||
|
||||
exports['errors .errors.formErrorText returns fully formed text message 1'] = `
|
||||
Your system is missing the dependency: Xvfb
|
||||
|
||||
Install Xvfb and run Cypress again.
|
||||
|
||||
Read our documentation on dependencies for more information:
|
||||
|
||||
[34mhttps://on.cypress.io/required-dependencies[39m
|
||||
|
||||
If you are using Docker, we provide containers with all required dependencies installed.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
`
|
||||
|
||||
exports['errors .errors.formErrorText calls solution if a function 1'] = `
|
||||
description
|
||||
|
||||
a solution
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
`
|
||||
|
||||
exports['invalid display error'] = `
|
||||
Cypress verification failed.
|
||||
|
||||
Cypress failed to start after spawning a new Xvfb server.
|
||||
|
||||
The error logs we received were:
|
||||
|
||||
----------
|
||||
|
||||
current message
|
||||
|
||||
----------
|
||||
|
||||
This may be due to a missing library or dependency. [34mhttps://on.cypress.io/required-dependencies[39m
|
||||
|
||||
Please refer to the error above for more detail.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
`
|
||||
@@ -1,321 +0,0 @@
|
||||
exports['silent install 1'] = `
|
||||
[no output]
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['error when installing on unsupported os'] = `
|
||||
Error: The Cypress App could not be installed. Your machine does not meet the operating system requirements.
|
||||
|
||||
https://on.cypress.io/app/get-started/install-cypress#System-requirements
|
||||
|
||||
----------
|
||||
|
||||
Platform: win32-ia32
|
||||
|
||||
`
|
||||
|
||||
exports['skip installation 1'] = `
|
||||
Note: Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.
|
||||
|
||||
|
||||
`
|
||||
|
||||
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: 3b7f0b5c59def1e9b5f385bd585c9b2836706c29
|
||||
* 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-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['specify version in env vars 1'] = `
|
||||
⚠ Warning: Forcing a binary version different than the default.
|
||||
|
||||
The CLI expected to install version: 1.2.3
|
||||
|
||||
Instead we will install version: 0.12.1
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
Installing Cypress (version: 0.12.1)
|
||||
|
||||
|
||||
⠋ 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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['version already installed - cypress install 1'] = `
|
||||
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Skipping installation:
|
||||
|
||||
Pass the --force option if you'd like to reinstall anyway.
|
||||
|
||||
`
|
||||
|
||||
exports['version already installed - postInstall 1'] = `
|
||||
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['continues installing on failure 1'] = `
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
⠋ 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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['installs without existing installation 1'] = `
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
⠋ 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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['installed version does not match needed version 1'] = `
|
||||
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
⠋ 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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['forcing true always installs 1'] = `
|
||||
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
⠋ 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 one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['warning installing as global 1'] = `
|
||||
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
⠋ 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
|
||||
|
||||
⚠ Warning: It looks like you've installed Cypress globally.
|
||||
|
||||
The recommended way to install Cypress is as a devDependency per project.
|
||||
|
||||
You should probably run these commands:
|
||||
|
||||
- npm uninstall -g cypress
|
||||
- npm install --save-dev cypress
|
||||
|
||||
`
|
||||
|
||||
exports['installing in ci 1'] = `
|
||||
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
|
||||
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['invalid cache directory 1'] = `
|
||||
Error: Cypress cannot write to the cache directory due to file permissions
|
||||
|
||||
See discussion and possible solutions at
|
||||
https://github.com/cypress-io/cypress/issues/1281
|
||||
|
||||
----------
|
||||
|
||||
Failed to access /invalid/cache/dir:
|
||||
|
||||
EACCES: permission denied, mkdir '/invalid'
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
@@ -1,15 +0,0 @@
|
||||
exports['stripIndent removes indent from literal string 1'] = `
|
||||
first line
|
||||
second line
|
||||
third line
|
||||
last line
|
||||
`
|
||||
|
||||
exports['stripIndent can be used with nested message 1'] = `
|
||||
first line
|
||||
|
||||
foo
|
||||
bar
|
||||
|
||||
last line
|
||||
`
|
||||
@@ -1,27 +0,0 @@
|
||||
exports['exec run .processRunOptions passes --browser option 1'] = [
|
||||
'--run-project',
|
||||
null,
|
||||
'--browser',
|
||||
'test browser',
|
||||
]
|
||||
|
||||
exports['exec run .processRunOptions passes --record option 1'] = [
|
||||
'--run-project',
|
||||
null,
|
||||
'--record',
|
||||
'my record id',
|
||||
]
|
||||
|
||||
exports['exec run .processRunOptions does not remove --record option when using --browser 1'] = [
|
||||
'--run-project',
|
||||
null,
|
||||
'--browser',
|
||||
'test browser',
|
||||
'--record',
|
||||
'foo',
|
||||
]
|
||||
|
||||
exports['exec run .processRunOptions defaults to e2e testingType 1'] = [
|
||||
'--run-project',
|
||||
null,
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
exports['lib/exec/spawn .start forces colors and streams when supported 1'] = {
|
||||
'FORCE_COLOR': '1',
|
||||
'DEBUG_COLORS': '1',
|
||||
'MOCHA_COLORS': '1',
|
||||
'FORCE_STDIN_TTY': '1',
|
||||
'FORCE_STDOUT_TTY': '1',
|
||||
'FORCE_STDERR_TTY': '1',
|
||||
}
|
||||
|
||||
exports['lib/exec/spawn .start does not force colors and streams when not supported 1'] = {
|
||||
'FORCE_COLOR': '0',
|
||||
'DEBUG_COLORS': '0',
|
||||
'FORCE_STDIN_TTY': '0',
|
||||
'FORCE_STDOUT_TTY': '0',
|
||||
'FORCE_STDERR_TTY': '0',
|
||||
}
|
||||
|
||||
exports['lib/exec/spawn .start detects kill signal exits with error on SIGKILL 1'] = `
|
||||
The Test Runner unexpectedly exited via a [36mexit[39m event with signal [36mSIGKILL[39m
|
||||
|
||||
Please search Cypress documentation for possible solutions:
|
||||
|
||||
[34mhttps://on.cypress.io[39m
|
||||
|
||||
Check if there is a GitHub issue describing this crash:
|
||||
|
||||
[34mhttps://github.com/cypress-io/cypress/issues[39m
|
||||
|
||||
Consider opening a new issue.
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 0.0.0-development
|
||||
`
|
||||
@@ -1,27 +0,0 @@
|
||||
exports['others_unchanged 1'] = {
|
||||
'foo': 'bar',
|
||||
}
|
||||
|
||||
exports['env_as_string 1'] = {
|
||||
'env': 'foo=bar',
|
||||
}
|
||||
|
||||
exports['env_as_object 1'] = {
|
||||
'env': '{"foo":"bar","magicNumber":1234,"host":"kevin.dev.local"}',
|
||||
}
|
||||
|
||||
exports['config_as_object 1'] = {
|
||||
'config': '{"baseUrl":"http://localhost:2000","watchForFileChanges":false}',
|
||||
}
|
||||
|
||||
exports['reporter_options_as_object 1'] = {
|
||||
'reporterOptions': '{"mochaFile":"results/my-test-output.xml","toConsole":true}',
|
||||
}
|
||||
|
||||
exports['spec_as_array 1'] = {
|
||||
'spec': '["a","b","c"]',
|
||||
}
|
||||
|
||||
exports['spec_as_string 1'] = {
|
||||
'spec': 'x,y,z',
|
||||
}
|
||||
@@ -1,383 +0,0 @@
|
||||
exports['Cypress non-executable permissions 1'] = `
|
||||
Error: Cypress cannot run because this binary file does not have executable permissions here:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
Reasons this may happen:
|
||||
|
||||
- node was installed as 'root' or with 'sudo'
|
||||
- the cypress npm package as 'root' or with 'sudo'
|
||||
|
||||
Please check that you have the appropriate user permissions.
|
||||
|
||||
You can also try clearing the cache with 'cypress cache clear' and reinstalling.
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['current version has not been verified 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['darwin: error when invalid CYPRESS_RUN_BINARY 1'] = `
|
||||
Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['different version installed 1'] = `
|
||||
Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
It looks like this is your first time using Cypress: 7.8.9
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['error binary not found in ci 1'] = `
|
||||
Error: The cypress npm package is installed, but the Cypress binary is missing.
|
||||
|
||||
We expected the binary to be installed here: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
Reasons it may be missing:
|
||||
|
||||
- You're caching 'node_modules' but are not caching this path: /cache/Cypress
|
||||
- You ran 'npm install' at an earlier build step but did not persist: /cache/Cypress
|
||||
|
||||
Properly caching the binary will fix this error and avoid downloading and unzipping Cypress.
|
||||
|
||||
Alternatively, you can run 'cypress install' to download the binary again.
|
||||
|
||||
https://on.cypress.io/not-installed-ci-error
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['executable cannot be found 1'] = `
|
||||
Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['fails verifying Cypress 1'] = `
|
||||
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
an error about dependencies
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['fails with no stderr 1'] = `
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
Error: EPERM NOT PERMITTED
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['lib/tasks/verify logs error when child process hangs 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress verification timed out.
|
||||
|
||||
This command failed with the following output:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress --no-sandbox --smoke-test --ping=222
|
||||
|
||||
----------
|
||||
|
||||
some stderr
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['lib/tasks/verify logs error when child process returns incorrect stdout (stderr when exists) 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress verification failed.
|
||||
|
||||
This command failed with the following output:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress --no-sandbox --smoke-test --ping=222
|
||||
|
||||
----------
|
||||
|
||||
some stderr
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['lib/tasks/verify logs error when child process returns incorrect stdout (stdout when no stderr) 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress verification failed.
|
||||
|
||||
This command failed with the following output:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress --no-sandbox --smoke-test --ping=222
|
||||
|
||||
----------
|
||||
|
||||
some stdout
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['linux: error when invalid CYPRESS_RUN_BINARY 1'] = `
|
||||
Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Cypress
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: linux-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['no Cypress executable 1'] = `
|
||||
Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['no version of Cypress installed 1'] = `
|
||||
Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['no welcome message 1'] = `
|
||||
Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['silent verify 1'] = `
|
||||
[no output]
|
||||
`
|
||||
|
||||
exports['valid CYPRESS_RUN_BINARY 1'] = `
|
||||
Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/Contents/MacOS/Cypress
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['verbose stdout output 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['verification with executable 1'] = `
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['verifying in ci 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
|
||||
`
|
||||
|
||||
exports['warning installed version does not match verified version 1'] = `
|
||||
Found binary version bloop installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version bloop does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['win32: error when invalid CYPRESS_RUN_BINARY 1'] = `
|
||||
Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Cypress.exe
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: win32-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['xvfb fails 1'] = `
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Xvfb exited with a non zero exit code.
|
||||
|
||||
There was a problem spawning Xvfb.
|
||||
|
||||
This is likely a problem with your system, permissions, or installation of Xvfb.
|
||||
|
||||
----------
|
||||
|
||||
Error: test without xvfb
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['tried to verify twice, on the first try got the DISPLAY error'] = `
|
||||
Cypress verification failed.
|
||||
|
||||
Cypress failed to start after spawning a new Xvfb server.
|
||||
|
||||
The error logs we received were:
|
||||
|
||||
----------
|
||||
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
some other error
|
||||
again with
|
||||
some weird indent
|
||||
|
||||
----------
|
||||
|
||||
This may be due to a missing library or dependency. [34mhttps://on.cypress.io/required-dependencies[39m
|
||||
|
||||
Please refer to the error above for more detail.
|
||||
|
||||
----------
|
||||
|
||||
Platform: linux-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
`
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const CLI = require('../lib/cli').default
|
||||
const CLI = require('../dist/cli').default
|
||||
|
||||
CLI.init()
|
||||
|
||||
107
cli/lib/cli.ts
107
cli/lib/cli.ts
@@ -11,7 +11,7 @@ import cache from './tasks/cache'
|
||||
|
||||
import openModule from './exec/open'
|
||||
import runModule from './exec/run'
|
||||
import verifyModule from './tasks/verify'
|
||||
import { start } from './tasks/verify'
|
||||
import installModule from './tasks/install'
|
||||
import versionModule from './exec/versions'
|
||||
import infoModule from './exec/info'
|
||||
@@ -27,7 +27,7 @@ function unknownOption (this: any, flag: string, type: string = 'option'): void
|
||||
logger.error(` error: unknown ${type}:`, flag)
|
||||
logger.error()
|
||||
this.outputHelp()
|
||||
util.exit(1)
|
||||
process.exit(1)
|
||||
}
|
||||
commander.Command.prototype.unknownOption = unknownOption
|
||||
|
||||
@@ -168,7 +168,7 @@ function includesVersion (args: string[]): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
function showVersions (opts: any): any {
|
||||
async function showVersions (opts: any): Promise<any> {
|
||||
debug('printing Cypress version')
|
||||
debug('additional arguments %o', opts)
|
||||
|
||||
@@ -211,9 +211,9 @@ function showVersions (opts: any): any {
|
||||
electronNodeVersion: undefined,
|
||||
}
|
||||
|
||||
return versionModule
|
||||
.getVersions()
|
||||
.then((versions: any = defaultVersions) => {
|
||||
try {
|
||||
const versions = (await versionModule.getVersions()) || defaultVersions
|
||||
|
||||
if (opts?.component) {
|
||||
reportComponentVersion(opts.component, versions)
|
||||
} else {
|
||||
@@ -221,8 +221,9 @@ function showVersions (opts: any): any {
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(util.logErrorExit1)
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
}
|
||||
|
||||
const createProgram = (): any => {
|
||||
@@ -403,7 +404,7 @@ const cliModule = {
|
||||
/**
|
||||
* Parses the command line and kicks off Cypress process.
|
||||
*/
|
||||
init (args?: string[]): any {
|
||||
async init (args?: string[]): Promise<any> {
|
||||
if (!args) {
|
||||
args = process.argv
|
||||
}
|
||||
@@ -472,21 +473,28 @@ const cliModule = {
|
||||
.description(text('version')))
|
||||
|
||||
maybeAddInspectFlags(addCypressOpenCommand(program))
|
||||
.action((opts: any) => {
|
||||
.action(async (opts: any) => {
|
||||
debug('opening Cypress')
|
||||
|
||||
openModule.start(util.parseOpts(opts))
|
||||
.then(util.exit)
|
||||
.catch(util.logErrorExit1)
|
||||
try {
|
||||
const code = await openModule.start(util.parseOpts(opts))
|
||||
|
||||
process.exit(code)
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
})
|
||||
|
||||
maybeAddInspectFlags(addCypressRunCommand(program))
|
||||
.action((...fnArgs: any[]) => {
|
||||
.action(async (...fnArgs: any[]) => {
|
||||
debug('running Cypress with args %o', fnArgs)
|
||||
try {
|
||||
const code = await runModule.start(parseVariableOpts(fnArgs, args as string[]))
|
||||
|
||||
runModule.start(parseVariableOpts(fnArgs, args as string[]))
|
||||
.then(util.exit)
|
||||
.catch(util.logErrorExit1)
|
||||
process.exit(code)
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
@@ -496,10 +504,12 @@ const cliModule = {
|
||||
'Installs the Cypress executable matching this package\'s version',
|
||||
)
|
||||
.option('-f, --force', text('forceInstall'))
|
||||
.action((opts: any) => {
|
||||
installModule
|
||||
.start(util.parseOpts(opts))
|
||||
.catch(util.logErrorExit1)
|
||||
.action(async (opts: any) => {
|
||||
try {
|
||||
await installModule.start(util.parseOpts(opts))
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
@@ -509,14 +519,16 @@ const cliModule = {
|
||||
'Verifies that Cypress is installed correctly and executable',
|
||||
)
|
||||
.option('--dev', text('dev'), coerceFalse)
|
||||
.action((opts: any) => {
|
||||
.action(async (opts: any) => {
|
||||
const defaultOpts = { force: true, welcomeMessage: false }
|
||||
const parsedOpts = util.parseOpts(opts)
|
||||
const options = _.extend(parsedOpts, defaultOpts)
|
||||
|
||||
verifyModule
|
||||
.start(options)
|
||||
.catch(util.logErrorExit1)
|
||||
try {
|
||||
await start(options)
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
@@ -528,10 +540,10 @@ const cliModule = {
|
||||
.option('clear', text('cacheClear'))
|
||||
.option('prune', text('cachePrune'))
|
||||
.option('--size', text('cacheSize'))
|
||||
.action(function (this: any, opts: any, args: string[]) {
|
||||
.action(async function (this: any, opts: any, args: string[]) {
|
||||
if (!args || !args.length) {
|
||||
this.outputHelp()
|
||||
util.exit(1)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const [command] = args
|
||||
@@ -546,16 +558,18 @@ const cliModule = {
|
||||
size: opts.size,
|
||||
})
|
||||
|
||||
return cache.list(opts.size)
|
||||
.catch({ code: 'ENOENT' }, () => {
|
||||
logger.always('No cached binary versions were found.')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
debug('cache list command failed with "%s"', e.message)
|
||||
try {
|
||||
const result = await cache.list(opts.size)
|
||||
|
||||
return result
|
||||
} catch (e: any) {
|
||||
if (e.code === 'ENOENT') {
|
||||
logger.always('No cached binary versions were found.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
util.logErrorExit1(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cache[command]()
|
||||
@@ -566,11 +580,14 @@ const cliModule = {
|
||||
.usage('[command]')
|
||||
.description('Prints Cypress and system information')
|
||||
.option('--dev', text('dev'), coerceFalse)
|
||||
.action((opts: any) => {
|
||||
infoModule
|
||||
.start(opts)
|
||||
.then(util.exit)
|
||||
.catch(util.logErrorExit1)
|
||||
.action(async (opts: any) => {
|
||||
try {
|
||||
const code = await infoModule.start(opts)
|
||||
|
||||
process.exit(code)
|
||||
} catch (e: any) {
|
||||
util.logErrorExit1(e)
|
||||
}
|
||||
})
|
||||
|
||||
debug('cli starts with arguments %j', args)
|
||||
@@ -590,7 +607,7 @@ const cliModule = {
|
||||
logger.error('Unknown command', `"${firstCommand}"`)
|
||||
program.outputHelp()
|
||||
|
||||
return util.exit(1)
|
||||
return process.exit(1)
|
||||
}
|
||||
|
||||
if (includesVersion(args)) {
|
||||
@@ -608,11 +625,3 @@ const cliModule = {
|
||||
}
|
||||
|
||||
export default cliModule
|
||||
|
||||
// @ts-ignore
|
||||
if (!module.parent) {
|
||||
logger.error('This CLI module should be required from another Node module')
|
||||
logger.error('and not executed directly')
|
||||
|
||||
process.exit(-1)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
// https://github.com/cypress-io/cypress/issues/316
|
||||
|
||||
import Bluebird from 'bluebird'
|
||||
import tmpModule from 'tmp'
|
||||
import fs from './fs'
|
||||
import tmp from 'tmp'
|
||||
import fs from 'fs-extra'
|
||||
import openModule from './exec/open'
|
||||
import runModule from './exec/run'
|
||||
import util from './util'
|
||||
import cli from './cli'
|
||||
|
||||
const tmp = Bluebird.promisifyAll(tmpModule) as any
|
||||
|
||||
const cypressModuleApi = {
|
||||
/**
|
||||
* Opens Cypress GUI
|
||||
@@ -25,35 +21,30 @@ const cypressModuleApi = {
|
||||
* Runs Cypress tests in the current project
|
||||
* @see https://on.cypress.io/module-api#cypress-run
|
||||
*/
|
||||
run (options: any = {}): any {
|
||||
async run (options: any = {}): Promise<any> {
|
||||
if (!runModule.isValidProject(options.project)) {
|
||||
return Bluebird.reject(new Error(`Invalid project path parameter: ${options.project}`))
|
||||
throw new Error(`Invalid project path parameter: ${options.project}`)
|
||||
}
|
||||
|
||||
options = util.normalizeModuleOptions(options)
|
||||
|
||||
tmp.setGracefulCleanup()
|
||||
|
||||
return tmp.fileAsync()
|
||||
.then((outputPath: string) => {
|
||||
options.outputPath = outputPath
|
||||
const outputPath: string = tmp.fileSync().name
|
||||
|
||||
return runModule.start(options)
|
||||
.then((failedTests: any) => {
|
||||
return fs.readJsonAsync(outputPath, { throws: false })
|
||||
.then((output: any) => {
|
||||
if (!output) {
|
||||
return {
|
||||
status: 'failed',
|
||||
failures: failedTests,
|
||||
message: 'Could not find Cypress test run results',
|
||||
}
|
||||
}
|
||||
options.outputPath = outputPath
|
||||
|
||||
return output
|
||||
})
|
||||
})
|
||||
})
|
||||
const failedTests = await runModule.start(options)
|
||||
const output = await fs.readJson(outputPath, { throws: false })
|
||||
|
||||
if (!output) {
|
||||
return {
|
||||
status: 'failed',
|
||||
failures: failedTests,
|
||||
message: 'Could not find Cypress test run results',
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
},
|
||||
|
||||
cli: {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import chalk from 'chalk'
|
||||
import { stripIndent, stripIndents } from 'common-tags'
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import util from './util'
|
||||
import state from './tasks/state'
|
||||
|
||||
// TODO: this package needs to be replaced as we can't import it in vitest
|
||||
const is = require('check-more-types')
|
||||
|
||||
const docsUrl = 'https://on.cypress.io'
|
||||
const requiredDependenciesUrl = `${docsUrl}/required-dependencies`
|
||||
const runDocumentationUrl = `${docsUrl}/cypress-run`
|
||||
@@ -266,10 +268,10 @@ const CYPRESS_RUN_BINARY = {
|
||||
},
|
||||
}
|
||||
|
||||
function addPlatformInformation (info: any): any {
|
||||
return util.getPlatformInfo().then((platform: string) => {
|
||||
return { ...info, platform }
|
||||
})
|
||||
async function addPlatformInformation (info: any): Promise<any> {
|
||||
const platform = await util.getPlatformInfo()
|
||||
|
||||
return { ...info, platform }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,88 +286,88 @@ function addPlatformInformation (info: any): any {
|
||||
return getError(errorObject).then(reject)
|
||||
```
|
||||
*/
|
||||
export function getError (errorObject: any): Promise<Error> {
|
||||
return formErrorText(errorObject).then((errorMessage: string) => {
|
||||
const err: any = new Error(errorMessage)
|
||||
export async function getError (errorObject: any): Promise<Error> {
|
||||
const errorMessage = await formErrorText(errorObject)
|
||||
|
||||
err.known = true
|
||||
const err: any = new Error(errorMessage)
|
||||
|
||||
return err
|
||||
})
|
||||
err.known = true
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms nice error message with error and platform information,
|
||||
* and if possible a way to solve it. Resolves with a string.
|
||||
*/
|
||||
export function formErrorText (info: any, msg?: string, prevMessage?: string): any {
|
||||
return addPlatformInformation(info).then((obj: any) => {
|
||||
const formatted: string[] = []
|
||||
export async function formErrorText (info: any, msg?: string, prevMessage?: string): Promise<string> {
|
||||
const infoWithPlatform = await addPlatformInformation(info)
|
||||
|
||||
function add (msg: string): void {
|
||||
formatted.push(stripIndents(msg))
|
||||
}
|
||||
const formatted: string[] = []
|
||||
|
||||
la(
|
||||
is.unemptyString(obj.description),
|
||||
'expected error description to be text',
|
||||
obj.description,
|
||||
)
|
||||
function add (msg: string): void {
|
||||
formatted.push(stripIndents(msg))
|
||||
}
|
||||
|
||||
// assuming that if there the solution is a function it will handle
|
||||
// error message and (optional previous error message)
|
||||
if (is.fn(obj.solution)) {
|
||||
const text = obj.solution(msg, prevMessage)
|
||||
la(
|
||||
is.unemptyString(infoWithPlatform.description),
|
||||
'expected error description to be text',
|
||||
infoWithPlatform.description,
|
||||
)
|
||||
|
||||
la(is.unemptyString(text), 'expected solution to be text', text)
|
||||
// assuming that if there the solution is a function it will handle
|
||||
// error message and (optional previous error message)
|
||||
if (is.fn(infoWithPlatform.solution)) {
|
||||
const text = infoWithPlatform.solution(msg, prevMessage)
|
||||
|
||||
add(`
|
||||
${obj.description}
|
||||
la(is.unemptyString(text), 'expected solution to be text', text)
|
||||
|
||||
add(`
|
||||
${infoWithPlatform.description}
|
||||
|
||||
${text}
|
||||
|
||||
`)
|
||||
} else {
|
||||
la(
|
||||
is.unemptyString(obj.solution),
|
||||
'expected error solution to be text',
|
||||
obj.solution,
|
||||
)
|
||||
} else {
|
||||
la(
|
||||
is.unemptyString(infoWithPlatform.solution),
|
||||
'expected error solution to be text',
|
||||
infoWithPlatform.solution,
|
||||
)
|
||||
|
||||
add(`
|
||||
${obj.description}
|
||||
add(`
|
||||
${infoWithPlatform.description}
|
||||
|
||||
${obj.solution}
|
||||
${infoWithPlatform.solution}
|
||||
|
||||
`)
|
||||
|
||||
if (msg) {
|
||||
add(`
|
||||
if (msg) {
|
||||
add(`
|
||||
${hr}
|
||||
|
||||
${msg}
|
||||
|
||||
`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(`
|
||||
add(`
|
||||
${hr}
|
||||
|
||||
${obj.platform}
|
||||
${infoWithPlatform.platform}
|
||||
`)
|
||||
|
||||
if (obj.footer) {
|
||||
add(`
|
||||
if (infoWithPlatform.footer) {
|
||||
add(`
|
||||
|
||||
${hr}
|
||||
|
||||
${obj.footer}
|
||||
${infoWithPlatform.footer}
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
return formatted.join('\n\n')
|
||||
})
|
||||
return formatted.join('\n\n')
|
||||
}
|
||||
|
||||
export const raise = (info: any) => {
|
||||
@@ -382,8 +384,10 @@ export const raise = (info: any) => {
|
||||
}
|
||||
|
||||
export const throwFormErrorText = (info: any) => {
|
||||
return (msg?: string, prevMessage?: string) => {
|
||||
return formErrorText(info, msg, prevMessage).then(raise(info))
|
||||
return async (msg?: string, prevMessage?: string) => {
|
||||
const errorText = await formErrorText(info, msg, prevMessage)
|
||||
|
||||
raise(info)(errorText)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,12 +398,12 @@ export const throwFormErrorText = (info: any) => {
|
||||
* @example return exitWithError(errors.invalidCypressEnv)('foo')
|
||||
*/
|
||||
export const exitWithError = (info: any) => {
|
||||
return (msg?: string) => {
|
||||
return formErrorText(info, msg).then((text: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(text)
|
||||
process.exit(info.exitCode || 1)
|
||||
})
|
||||
return async (msg?: string) => {
|
||||
const text: string = await formErrorText(info, msg)
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(text)
|
||||
process.exit(info.exitCode || 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Debug from 'debug'
|
||||
import util from '../util'
|
||||
import spawn from './spawn'
|
||||
import verifyModule from '../tasks/verify'
|
||||
import { start as verifyStart } from '../tasks/verify'
|
||||
import { processTestingType, checkConfigFile } from './shared'
|
||||
import { exitWithError } from '../errors'
|
||||
|
||||
@@ -74,7 +74,7 @@ export const processOpenOptions = (options: any = {}): string[] => {
|
||||
return args
|
||||
}
|
||||
|
||||
export const start = (options: any = {}): any => {
|
||||
export const start = async (options: any = {}): Promise<any> => {
|
||||
function open (): any {
|
||||
try {
|
||||
const args = processOpenOptions(options)
|
||||
@@ -96,8 +96,9 @@ export const start = (options: any = {}): any => {
|
||||
return open()
|
||||
}
|
||||
|
||||
return verifyModule.start()
|
||||
.then(open)
|
||||
await verifyStart()
|
||||
|
||||
return open()
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -2,7 +2,7 @@ import _ from 'lodash'
|
||||
import Debug from 'debug'
|
||||
import util from '../util'
|
||||
import spawn from './spawn'
|
||||
import verifyModule from '../tasks/verify'
|
||||
import { start } from '../tasks/verify'
|
||||
import { exitWithError, errors } from '../errors'
|
||||
import { processTestingType, throwInvalidOptionError, checkConfigFile } from './shared'
|
||||
|
||||
@@ -164,7 +164,7 @@ const runModule = {
|
||||
processRunOptions,
|
||||
isValidProject,
|
||||
// resolves with the number of failed tests
|
||||
start (options: any = {}): any {
|
||||
async start (options: any = {}): Promise<any> {
|
||||
_.defaults(options, {
|
||||
key: null,
|
||||
spec: null,
|
||||
@@ -195,8 +195,9 @@ const runModule = {
|
||||
return run()
|
||||
}
|
||||
|
||||
return verifyModule.start()
|
||||
.then(run)
|
||||
await start()
|
||||
|
||||
return run()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import Debug from 'debug'
|
||||
import util from '../util'
|
||||
import state from '../tasks/state'
|
||||
import xvfb from './xvfb'
|
||||
import verifyModule from '../tasks/verify'
|
||||
import { needsSandbox } from '../tasks/verify'
|
||||
import { throwFormErrorText, getError, errors } from '../errors'
|
||||
import readline from 'readline'
|
||||
import { stdin, stdout, stderr } from 'process'
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
|
||||
@@ -50,7 +51,7 @@ function getStdio (needsXvfb: boolean): any {
|
||||
}
|
||||
|
||||
const spawnModule = {
|
||||
start (args: any, options: any = {}): any {
|
||||
async start (args: any, options: any = {}): Promise<any> {
|
||||
const needsXvfb = xvfb.isNeeded()
|
||||
let executable = state.getPathToExecutable(state.getBinaryDir())
|
||||
|
||||
@@ -99,7 +100,7 @@ const spawnModule = {
|
||||
debug('in dev mode the args became %o', args)
|
||||
}
|
||||
|
||||
if (!options.dev && verifyModule.needsSandbox()) {
|
||||
if (!options.dev && needsSandbox()) {
|
||||
electronArgs.push('--no-sandbox')
|
||||
}
|
||||
|
||||
@@ -149,13 +150,15 @@ const spawnModule = {
|
||||
const child = cp.spawn(executable, args, stdioOptions)
|
||||
|
||||
function resolveOn (event: any): any {
|
||||
return function (code: any, signal: any): any {
|
||||
return async function (code: any, signal: any): Promise<any> {
|
||||
debug('child event fired %o', { event, code, signal })
|
||||
|
||||
if (code === null) {
|
||||
const errorObject = errors.childProcessKilled(event, signal)
|
||||
|
||||
return getError(errorObject).then(reject)
|
||||
const err = await getError(errorObject)
|
||||
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
resolve(code)
|
||||
@@ -168,8 +171,8 @@ const spawnModule = {
|
||||
|
||||
if (isPlatform('win32')) {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
input: stdin,
|
||||
output: stdout,
|
||||
})
|
||||
|
||||
// on windows, SIGINT does not propagate to the child process when ctrl+c is pressed
|
||||
@@ -188,12 +191,12 @@ const spawnModule = {
|
||||
// child STDERR => process STDERR with additional filtering
|
||||
if (child.stdin) {
|
||||
debug('piping process STDIN into child STDIN')
|
||||
process.stdin.pipe(child.stdin)
|
||||
stdin.pipe(child.stdin)
|
||||
}
|
||||
|
||||
if (child.stdout) {
|
||||
debug('piping child STDOUT to process STDOUT')
|
||||
child.stdout.pipe(process.stdout)
|
||||
child.stdout.pipe(stdout)
|
||||
}
|
||||
|
||||
// if this is defined then we are manually piping for linux
|
||||
@@ -210,7 +213,7 @@ const spawnModule = {
|
||||
}
|
||||
|
||||
// else pass it along!
|
||||
process.stderr.write(data)
|
||||
stderr.write(data)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,7 +224,7 @@ const spawnModule = {
|
||||
// into the child process. unpiping does not seem
|
||||
// to have any effect. so we're just catching the
|
||||
// error here and not doing anything.
|
||||
process.stdin.on('error', (err: any) => {
|
||||
stdin.on('error', (err: any) => {
|
||||
if (['EPIPE', 'ENOTCONN'].includes(err.code)) {
|
||||
return
|
||||
}
|
||||
@@ -235,17 +238,22 @@ const spawnModule = {
|
||||
})
|
||||
}
|
||||
|
||||
const spawnInXvfb = (): any => {
|
||||
return xvfb
|
||||
.start()
|
||||
.then(userFriendlySpawn)
|
||||
.finally(xvfb.stop)
|
||||
const spawnInXvfb = async (): Promise<number> => {
|
||||
try {
|
||||
await xvfb.start()
|
||||
|
||||
const code = await userFriendlySpawn()
|
||||
|
||||
return code
|
||||
} finally {
|
||||
await xvfb.stop()
|
||||
}
|
||||
}
|
||||
|
||||
const userFriendlySpawn = (linuxWithDisplayEnv: any): any => {
|
||||
const userFriendlySpawn = async (linuxWithDisplayEnv?: any): Promise<any> => {
|
||||
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv))
|
||||
|
||||
let brokenGtkDisplay: boolean
|
||||
let brokenGtkDisplay: boolean = false
|
||||
|
||||
const overrides: any = {}
|
||||
|
||||
@@ -262,8 +270,9 @@ const spawnModule = {
|
||||
})
|
||||
}
|
||||
|
||||
return spawn(overrides)
|
||||
.then((code: any) => {
|
||||
try {
|
||||
const code: number = await spawn(overrides)
|
||||
|
||||
if (code !== 0 && brokenGtkDisplay) {
|
||||
util.logBrokenGtkDisplayWarning()
|
||||
|
||||
@@ -271,10 +280,17 @@ const spawnModule = {
|
||||
}
|
||||
|
||||
return code
|
||||
})
|
||||
// we can format and handle an error message from the code above
|
||||
// prevent wrapping error again by using "known: undefined" filter
|
||||
.catch({ known: undefined }, throwFormErrorText(errors.unexpected))
|
||||
} catch (error: any) {
|
||||
// we can format and handle an error message from the code above
|
||||
// prevent wrapping error again by using "known: undefined" filter
|
||||
if ((error as any).known === undefined) {
|
||||
const raiseErrorFn = throwFormErrorText(errors.unexpected)
|
||||
|
||||
await raiseErrorFn(error.message)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (needsXvfb) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Bluebird from 'bluebird'
|
||||
import Debug from 'debug'
|
||||
import path from 'path'
|
||||
import util from '../util'
|
||||
@@ -7,59 +6,62 @@ import { throwFormErrorText, errors } from '../errors'
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
|
||||
const getVersions = (): any => {
|
||||
return Bluebird.try(() => {
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
let envBinaryPath = path.resolve(util.getEnv('CYPRESS_RUN_BINARY') as string)
|
||||
const getBinaryDirectory = async (): Promise<string> => {
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
let envBinaryPath = path.resolve(util.getEnv('CYPRESS_RUN_BINARY') as string)
|
||||
|
||||
return state.parseRealPlatformBinaryFolderAsync(envBinaryPath)
|
||||
.then((envBinaryDir: any) => {
|
||||
if (!envBinaryDir) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))()
|
||||
}
|
||||
try {
|
||||
const envBinaryDir = await state.parseRealPlatformBinaryFolderAsync(envBinaryPath)
|
||||
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir)
|
||||
if (!envBinaryDir) {
|
||||
const raiseErrorFn = throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))
|
||||
|
||||
return envBinaryDir
|
||||
})
|
||||
.catch({ code: 'ENOENT' }, (err: any) => {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message)
|
||||
})
|
||||
await raiseErrorFn()
|
||||
}
|
||||
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir)
|
||||
|
||||
return envBinaryDir
|
||||
} catch (err: any) {
|
||||
const raiseErrorFn = throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))
|
||||
|
||||
await raiseErrorFn(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
return state.getBinaryDir()
|
||||
})
|
||||
.then(state.getBinaryPkgAsync)
|
||||
.then((pkg: any) => {
|
||||
const versions = {
|
||||
binary: state.getBinaryPkgVersion(pkg),
|
||||
electronVersion: state.getBinaryElectronVersion(pkg),
|
||||
electronNodeVersion: state.getBinaryElectronNodeVersion(pkg),
|
||||
}
|
||||
return state.getBinaryDir()
|
||||
}
|
||||
|
||||
debug('binary versions %o', versions)
|
||||
const getVersions = async (): Promise<any> => {
|
||||
const binDir = await getBinaryDirectory()
|
||||
|
||||
return versions
|
||||
})
|
||||
.then((binaryVersions: any) => {
|
||||
const buildInfo = util.pkgBuildInfo()
|
||||
const pkg = await state.getBinaryPkgAsync(binDir)
|
||||
|
||||
let packageVersion = util.pkgVersion()
|
||||
const versions = {
|
||||
binary: state.getBinaryPkgVersion(pkg),
|
||||
electronVersion: state.getBinaryElectronVersion(pkg),
|
||||
electronNodeVersion: state.getBinaryElectronNodeVersion(pkg),
|
||||
}
|
||||
|
||||
if (!buildInfo) packageVersion += ' (development)'
|
||||
else if (!buildInfo.stable) packageVersion += ' (pre-release)'
|
||||
debug('binary versions %o', versions)
|
||||
|
||||
const versions = {
|
||||
package: packageVersion,
|
||||
binary: binaryVersions.binary || 'not installed',
|
||||
electronVersion: binaryVersions.electronVersion || 'not found',
|
||||
electronNodeVersion: binaryVersions.electronNodeVersion || 'not found',
|
||||
}
|
||||
const buildInfo = util.pkgBuildInfo()
|
||||
|
||||
debug('combined versions %o', versions)
|
||||
let packageVersion = util.pkgVersion()
|
||||
|
||||
return versions
|
||||
})
|
||||
if (!buildInfo) packageVersion += ' (development)'
|
||||
else if (!buildInfo.stable) packageVersion += ' (pre-release)'
|
||||
|
||||
const versionsFinal = {
|
||||
package: packageVersion,
|
||||
binary: versions.binary || 'not installed',
|
||||
electronVersion: versions.electronVersion || 'not found',
|
||||
electronNodeVersion: versions.electronNodeVersion || 'not found',
|
||||
}
|
||||
|
||||
debug('combined versions %o', versions)
|
||||
|
||||
return versionsFinal
|
||||
}
|
||||
|
||||
const versionsModule = {
|
||||
|
||||
@@ -33,29 +33,40 @@ const xvfbModule = {
|
||||
|
||||
_xvfbOptions: xvfbOptions, // expose for testing
|
||||
|
||||
start (): any {
|
||||
async start (): Promise<any> {
|
||||
debug('Starting Xvfb')
|
||||
|
||||
return xvfb.startAsync()
|
||||
.return(null)
|
||||
.catch({ nonZeroExitCode: true }, throwFormErrorText(errors.nonZeroExitCodeXvfb))
|
||||
.catch((err: any) => {
|
||||
if (err.known) {
|
||||
throw err
|
||||
try {
|
||||
await xvfb.startAsync()
|
||||
|
||||
return null
|
||||
} catch (e: any) {
|
||||
if (e.nonZeroExitCode === true) {
|
||||
const raiseErrorFn = throwFormErrorText(errors.nonZeroExitCodeXvfb)
|
||||
|
||||
await raiseErrorFn(e)
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingXvfb)(err)
|
||||
})
|
||||
if (e.known) {
|
||||
throw e
|
||||
}
|
||||
|
||||
const raiseErrorFn = throwFormErrorText(errors.missingXvfb)
|
||||
|
||||
await raiseErrorFn(e)
|
||||
}
|
||||
},
|
||||
|
||||
stop (): any {
|
||||
async stop (): Promise<null> {
|
||||
debug('Stopping Xvfb')
|
||||
|
||||
return xvfb.stopAsync()
|
||||
.return(null)
|
||||
.catch(() => {
|
||||
// noop
|
||||
})
|
||||
try {
|
||||
await xvfb.stopAsync()
|
||||
|
||||
return null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
isNeeded (): boolean {
|
||||
@@ -95,15 +106,18 @@ const xvfbModule = {
|
||||
},
|
||||
|
||||
// async method, resolved with Boolean
|
||||
verify (): any {
|
||||
return xvfb.startAsync()
|
||||
.return(true)
|
||||
.catch((err: any) => {
|
||||
async verify (): Promise<boolean> {
|
||||
try {
|
||||
await xvfb.startAsync()
|
||||
|
||||
return true
|
||||
} catch (err: any) {
|
||||
debug('Could not verify xvfb: %s', err.message)
|
||||
|
||||
return false
|
||||
})
|
||||
.finally(xvfb.stopAsync)
|
||||
} finally {
|
||||
await xvfb.stopAsync()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import Bluebird from 'bluebird'
|
||||
import fsExtra from 'fs-extra'
|
||||
|
||||
export default Bluebird.promisifyAll(fsExtra) as any
|
||||
@@ -2,7 +2,7 @@ import module from 'module'
|
||||
|
||||
const require = module.createRequire(import.meta.url)
|
||||
|
||||
const cypress = require('./lib/cypress')
|
||||
const cypress = require('./cypress')
|
||||
|
||||
export default cypress
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import minimist from 'minimist'
|
||||
import debug from 'debug'
|
||||
import util from './lib/util'
|
||||
import CLI from './lib/cypress'
|
||||
import installModule from './lib/tasks/install'
|
||||
import verifyModule from './lib/tasks/verify'
|
||||
import util from './util'
|
||||
import CLI from './cypress'
|
||||
import installModule from './tasks/install'
|
||||
import { start as verifyStart } from './tasks/verify'
|
||||
|
||||
const debugCli = debug('cypress:cli')
|
||||
const args: any = minimist(process.argv.slice(2))
|
||||
@@ -23,7 +23,7 @@ async function handleExec (): Promise<void> {
|
||||
// for simple testing in the monorepo
|
||||
debugCli('verifying Cypress')
|
||||
|
||||
verifyModule.start({ force: true }) // always force verification
|
||||
verifyStart({ force: true }) // always force verification
|
||||
.catch(util.logErrorExit1)
|
||||
|
||||
break
|
||||
@@ -1,6 +1,6 @@
|
||||
import state from './state'
|
||||
import logger from '../logger'
|
||||
import fs from '../fs'
|
||||
import fs from 'fs-extra'
|
||||
import util from '../util'
|
||||
|
||||
import { join } from 'path'
|
||||
@@ -28,7 +28,7 @@ const logCachePath = (): undefined => {
|
||||
}
|
||||
|
||||
const clear = (): Promise<void> => {
|
||||
return fs.removeAsync(state.getCacheDir())
|
||||
return fs.remove(state.getCacheDir())
|
||||
}
|
||||
|
||||
const prune = async (): Promise<void> => {
|
||||
@@ -38,7 +38,7 @@ const prune = async (): Promise<void> => {
|
||||
let deletedBinary = false
|
||||
|
||||
try {
|
||||
const versions = await fs.readdirAsync(cacheDir)
|
||||
const versions = await fs.readdir(cacheDir)
|
||||
|
||||
for (const version of versions) {
|
||||
if (version !== checkedInBinaryVersion) {
|
||||
@@ -46,7 +46,7 @@ const prune = async (): Promise<void> => {
|
||||
|
||||
const versionDir = join(cacheDir, version)
|
||||
|
||||
await fs.removeAsync(versionDir)
|
||||
await fs.remove(versionDir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,88 +74,98 @@ const fileSizeInMB = (size: number): string => {
|
||||
* Collects all cached versions, finds when each was used
|
||||
* and prints a table with results to the terminal
|
||||
*/
|
||||
const list = (showSize: boolean = false): any => {
|
||||
return getCachedVersions(showSize)
|
||||
.then((binaries: any) => {
|
||||
const head = [colors.titles('version'), colors.titles('last used')]
|
||||
const list = async (showSize: boolean = false): Promise<void> => {
|
||||
const binaries = await getCachedVersions(showSize)
|
||||
|
||||
const head = [colors.titles('version'), colors.titles('last used')]
|
||||
|
||||
if (showSize) {
|
||||
head.push(colors.titles('size'))
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head,
|
||||
})
|
||||
|
||||
binaries.forEach((binary: { version: string, accessed?: string, size?: number }) => {
|
||||
const versionString = colors.values(binary.version)
|
||||
const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown'
|
||||
const row = [versionString, lastUsed]
|
||||
|
||||
if (showSize) {
|
||||
head.push(colors.titles('size'))
|
||||
const size = colors.size(fileSizeInMB(binary.size as number))
|
||||
|
||||
row.push(size)
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head,
|
||||
})
|
||||
|
||||
binaries.forEach((binary: any) => {
|
||||
const versionString = colors.values(binary.version)
|
||||
const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown'
|
||||
const row = [versionString, lastUsed]
|
||||
|
||||
if (showSize) {
|
||||
const size = colors.size(fileSizeInMB(binary.size))
|
||||
|
||||
row.push(size)
|
||||
}
|
||||
|
||||
return table.push(row)
|
||||
})
|
||||
|
||||
logger.always(table.toString())
|
||||
return table.push(row)
|
||||
})
|
||||
|
||||
logger.always(table.toString())
|
||||
}
|
||||
|
||||
const getCachedVersions = (showSize: boolean): Promise<any> => {
|
||||
const getCachedVersions = async (showSize: boolean): Promise<{
|
||||
version: string
|
||||
folderPath: string
|
||||
accessed?: string
|
||||
size?: number
|
||||
}[]> => {
|
||||
const cacheDir = state.getCacheDir()
|
||||
|
||||
return fs
|
||||
.readdirAsync(cacheDir)
|
||||
.filter(util.isSemver)
|
||||
.map((version: any) => {
|
||||
const versions = await fs.readdir(cacheDir)
|
||||
|
||||
const filteredVersions = versions.filter(util.isSemver).map((version: any) => {
|
||||
return {
|
||||
version,
|
||||
folderPath: join(cacheDir, version),
|
||||
}
|
||||
})
|
||||
.mapSeries((binary: any) => {
|
||||
// last access time on the folder is different from last access time
|
||||
// on the Cypress binary
|
||||
|
||||
const binaries: {
|
||||
version: string
|
||||
folderPath: string
|
||||
accessed?: string
|
||||
size?: number
|
||||
}[] = []
|
||||
|
||||
for (const binary of filteredVersions) {
|
||||
const binaryDir = state.getBinaryDir(binary.version)
|
||||
const executable = state.getPathToExecutable(binaryDir)
|
||||
|
||||
return fs.statAsync(executable).then((stat: any) => {
|
||||
try {
|
||||
const stat = await fs.stat(executable)
|
||||
|
||||
const lastAccessedTime = _.get(stat, 'atime')
|
||||
|
||||
if (!lastAccessedTime) {
|
||||
// the test runner has never been opened
|
||||
// or could be a test simulating missing timestamp
|
||||
return binary
|
||||
if (lastAccessedTime) {
|
||||
const accessed = dayjs(lastAccessedTime).fromNow()
|
||||
|
||||
// @ts-expect-error - accessed is not defined in the type
|
||||
binary.accessed = accessed
|
||||
}
|
||||
|
||||
const accessed = dayjs(lastAccessedTime).fromNow()
|
||||
|
||||
binary.accessed = accessed
|
||||
|
||||
return binary
|
||||
}, (e: any) => {
|
||||
// if no lastAccessedTime
|
||||
// the test runner has never been opened
|
||||
// or could be a test simulating missing timestamp
|
||||
} catch (e) {
|
||||
// could not find the binary or gets its stats
|
||||
return binary
|
||||
})
|
||||
})
|
||||
.mapSeries((binary: any) => {
|
||||
// no-op
|
||||
}
|
||||
if (showSize) {
|
||||
const binaryDir = state.getBinaryDir(binary.version)
|
||||
|
||||
return getFolderSize(binaryDir).then((size: number) => {
|
||||
return {
|
||||
...binary,
|
||||
size,
|
||||
}
|
||||
})
|
||||
}
|
||||
const size: number = await getFolderSize(binaryDir)
|
||||
|
||||
return binary
|
||||
})
|
||||
binaries.push({
|
||||
...binary,
|
||||
size,
|
||||
})
|
||||
} else {
|
||||
binaries.push(binary)
|
||||
}
|
||||
}
|
||||
|
||||
return binaries
|
||||
}
|
||||
|
||||
const cacheModule = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import os from 'os'
|
||||
import url from 'url'
|
||||
import path from 'path'
|
||||
@@ -10,9 +9,12 @@ import requestProgress from 'request-progress'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import { getProxyForUrl } from 'proxy-from-env'
|
||||
import { throwFormErrorText, errors } from '../errors'
|
||||
import fs from '../fs'
|
||||
import fs from 'fs-extra'
|
||||
import util from '../util'
|
||||
|
||||
// TODO: this package needs to be replaced as we can't import it in vitest
|
||||
const is = require('check-more-types')
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
|
||||
const defaultBaseUrl = 'https://download.cypress.io/'
|
||||
@@ -39,22 +41,24 @@ const getBaseUrl = (): string => {
|
||||
return defaultBaseUrl
|
||||
}
|
||||
|
||||
const getCA = (): any => {
|
||||
return new Bluebird((resolve: any) => {
|
||||
if (process.env.npm_config_cafile) {
|
||||
fs.readFile(process.env.npm_config_cafile, 'utf8')
|
||||
.then((cafileContent: string) => {
|
||||
resolve(cafileContent)
|
||||
})
|
||||
.catch(() => {
|
||||
resolve()
|
||||
})
|
||||
} else if (process.env.npm_config_ca) {
|
||||
resolve(process.env.npm_config_ca)
|
||||
} else {
|
||||
resolve()
|
||||
const getCA = async (): Promise<string | undefined> => {
|
||||
if (process.env.npm_config_cafile) {
|
||||
try {
|
||||
const caFileContent = await fs.readFile(process.env.npm_config_cafile, 'utf8')
|
||||
|
||||
return caFileContent
|
||||
} catch (error) {
|
||||
debug('error reading ca file', error)
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (process.env.npm_config_ca) {
|
||||
return process.env.npm_config_ca
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const prepend = (arch: string, urlPath: string, version: string): string => {
|
||||
@@ -79,16 +83,16 @@ const prepend = (arch: string, urlPath: string, version: string): string => {
|
||||
: `${endpoint}?platform=${platform}&arch=${arch}`
|
||||
}
|
||||
|
||||
const getUrl = (arch: string, version: string): string => {
|
||||
const getUrl = (arch: string, version?: string): string => {
|
||||
if (is.webUrl(version)) {
|
||||
debug('version is already an url', version)
|
||||
|
||||
return version
|
||||
return version as string
|
||||
}
|
||||
|
||||
const urlPath = version ? `desktop/${version}` : 'desktop'
|
||||
|
||||
return prepend(arch, urlPath, version)
|
||||
return prepend(arch, urlPath, version || '')
|
||||
}
|
||||
|
||||
const statusMessage = (err: any): string => {
|
||||
@@ -112,7 +116,7 @@ const prettyDownloadErr = (err: any, url: string): any => {
|
||||
* Checks checksum and file size for the given file. Allows both
|
||||
* values or just one of them to be checked.
|
||||
*/
|
||||
const verifyDownloadedFile = (filename: string, expectedSize?: number, expectedChecksum?: string): any => {
|
||||
const verifyDownloadedFile = async (filename: string, expectedSize?: number, expectedChecksum?: string): Promise<any> => {
|
||||
if (expectedSize && expectedChecksum) {
|
||||
debug('verifying checksum and file size')
|
||||
|
||||
@@ -147,24 +151,23 @@ const verifyDownloadedFile = (filename: string, expectedSize?: number, expectedC
|
||||
if (expectedChecksum) {
|
||||
debug('only checking expected file checksum %d', expectedChecksum)
|
||||
|
||||
return util.getFileChecksum(filename)
|
||||
.then((checksum: string) => {
|
||||
if (checksum === expectedChecksum) {
|
||||
debug('downloaded file has the expected checksum ✅')
|
||||
const checksum: string = await util.getFileChecksum(filename)
|
||||
|
||||
return
|
||||
}
|
||||
if (checksum === expectedChecksum) {
|
||||
debug('downloaded file has the expected checksum ✅')
|
||||
|
||||
debug('raising error: file checksum mismatch')
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
return
|
||||
}
|
||||
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
`
|
||||
debug('raising error: file checksum mismatch')
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
throw new Error(text)
|
||||
})
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
`
|
||||
|
||||
throw new Error(text)
|
||||
}
|
||||
|
||||
if (expectedSize) {
|
||||
@@ -172,29 +175,28 @@ const verifyDownloadedFile = (filename: string, expectedSize?: number, expectedC
|
||||
// which we can check against the file size
|
||||
debug('only checking expected file size %d', expectedSize)
|
||||
|
||||
return util.getFileSize(filename)
|
||||
.then((filesize: number) => {
|
||||
if (filesize === expectedSize) {
|
||||
debug('downloaded file has the expected size ✅')
|
||||
const filesize: number = await util.getFileSize(filename)
|
||||
|
||||
return
|
||||
}
|
||||
if (filesize === expectedSize) {
|
||||
debug('downloaded file has the expected size ✅')
|
||||
|
||||
debug('raising error: file size mismatch')
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
return
|
||||
}
|
||||
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`
|
||||
debug('raising error: file size mismatch')
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
throw new Error(text)
|
||||
})
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`
|
||||
|
||||
throw new Error(text)
|
||||
}
|
||||
|
||||
debug('downloaded file lacks checksum or size to verify')
|
||||
|
||||
return Bluebird.resolve()
|
||||
return
|
||||
}
|
||||
|
||||
// downloads from given url
|
||||
@@ -354,18 +356,16 @@ const start = async (opts: any): Promise<any> => {
|
||||
debug('source url %s', versionUrl)
|
||||
debug(`downloading cypress.zip to "${downloadDestination}"`)
|
||||
|
||||
// ensure download dir exists
|
||||
return fs.ensureDirAsync(path.dirname(downloadDestination))
|
||||
.then(() => {
|
||||
return getCA()
|
||||
})
|
||||
.then((ca: any) => {
|
||||
try {
|
||||
// ensure download dir exists
|
||||
await fs.ensureDir(path.dirname(downloadDestination))
|
||||
const ca: string | undefined = await getCA()
|
||||
|
||||
return downloadFromUrl({ url: versionUrl, downloadDestination, progress, ca, version,
|
||||
...(redirectTTL ? { redirectTTL } : {}) })
|
||||
})
|
||||
.catch((err: any) => {
|
||||
} catch (err: any) {
|
||||
return prettyDownloadErr(err, versionUrl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const downloadModule = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import fs from '../fs'
|
||||
import fs from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import Bluebird from 'bluebird'
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import Debug from 'debug'
|
||||
import { Listr } from 'listr2'
|
||||
import Bluebird from 'bluebird'
|
||||
import logSymbols from 'log-symbols'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import fs from '../fs'
|
||||
import timers from 'timers/promises'
|
||||
|
||||
import fs from 'fs-extra'
|
||||
import download from './download'
|
||||
import util from '../util'
|
||||
import state from './state'
|
||||
@@ -92,24 +93,22 @@ const downloadAndUnzip = ({ version, installDir, downloadDir }: any): any => {
|
||||
const tasks = new Listr([
|
||||
{
|
||||
options: { title: util.titleize('Downloading Cypress') },
|
||||
task: (ctx: any, task: any) => {
|
||||
task: async (ctx: any, task: any) => {
|
||||
// as our download progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Downloading Cypress')
|
||||
|
||||
return download.start({ version, downloadDestination, progress })
|
||||
.then((redirectVersion: any) => {
|
||||
if (redirectVersion) version = redirectVersion
|
||||
const redirectVersion = await download.start({ version, downloadDestination, progress })
|
||||
|
||||
debug(`finished downloading file: ${downloadDestination}`)
|
||||
})
|
||||
.then(() => {
|
||||
// save the download destination for unzipping
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Downloaded Cypress')),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
})
|
||||
if (redirectVersion) version = redirectVersion
|
||||
|
||||
debug(`finished downloading file: ${downloadDestination}`)
|
||||
|
||||
// save the download destination for unzipping
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Downloaded Cypress')),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
},
|
||||
},
|
||||
unzipTask({
|
||||
@@ -120,35 +119,34 @@ const downloadAndUnzip = ({ version, installDir, downloadDir }: any): any => {
|
||||
}),
|
||||
{
|
||||
options: { title: util.titleize('Finishing Installation') },
|
||||
task: (ctx: any, task: any) => {
|
||||
const cleanup = () => {
|
||||
task: async (ctx: any, task: any) => {
|
||||
const cleanup = async () => {
|
||||
debug('removing zip file %s', downloadDestination)
|
||||
|
||||
return fs.removeAsync(downloadDestination)
|
||||
await fs.remove(downloadDestination)
|
||||
}
|
||||
|
||||
return cleanup()
|
||||
.then(() => {
|
||||
debug('finished installation in', installDir)
|
||||
await cleanup()
|
||||
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
})
|
||||
debug('finished installation in', installDir)
|
||||
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
},
|
||||
},
|
||||
], { rendererOptions })
|
||||
|
||||
// start the tasks!
|
||||
return Bluebird.resolve(tasks.run())
|
||||
return tasks.run()
|
||||
}
|
||||
|
||||
const validateOS = (): any => {
|
||||
return util.getPlatformInfo().then((platformInfo: string) => {
|
||||
return platformInfo.match(/(win32-x64|win32-arm64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/)
|
||||
})
|
||||
const validateOS = async (): Promise<RegExpMatchArray | null> => {
|
||||
const platformInfo = await util.getPlatformInfo()
|
||||
|
||||
return platformInfo.match(/(win32-x64|win32-arm64|linux-x64|linux-arm64|darwin-x64|darwin-arm64)/)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,14 +243,19 @@ const start = async (options: any = {}): Promise<any> => {
|
||||
return throwFormErrorText(errors.invalidOS)()
|
||||
}
|
||||
|
||||
await fs.ensureDirAsync(cacheDir)
|
||||
.catch({ code: 'EACCES' }, (err: any) => {
|
||||
return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent`
|
||||
Failed to access ${chalk.cyan(cacheDir)}:
|
||||
try {
|
||||
await fs.ensureDir(cacheDir)
|
||||
} catch (err: any) {
|
||||
if (err.code === 'EACCES') {
|
||||
return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent`
|
||||
Failed to access ${chalk.cyan(cacheDir)}:
|
||||
|
||||
${err.message}
|
||||
`)
|
||||
})
|
||||
${err.message}
|
||||
`)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const binaryPkg = await state.getBinaryPkgAsync(binaryDir)
|
||||
const binaryVersion = await state.getBinaryPkgVersion(binaryPkg)
|
||||
@@ -310,7 +313,7 @@ const start = async (options: any = {}): Promise<any> => {
|
||||
|
||||
const getLocalFilePath = async (): Promise<string | false> => {
|
||||
// see if version supplied is a path to a binary
|
||||
if (await fs.pathExistsAsync(versionToInstall)) {
|
||||
if (await fs.pathExists(versionToInstall)) {
|
||||
return path.extname(versionToInstall) === '.zip' ? versionToInstall : false
|
||||
}
|
||||
|
||||
@@ -320,7 +323,7 @@ const start = async (options: any = {}): Promise<any> => {
|
||||
|
||||
// if this exists return the path to it
|
||||
// else false
|
||||
if ((await fs.pathExistsAsync(possibleFile)) && path.extname(possibleFile) === '.zip') {
|
||||
if ((await fs.pathExists(possibleFile)) && path.extname(possibleFile) === '.zip') {
|
||||
return possibleFile
|
||||
}
|
||||
|
||||
@@ -360,7 +363,7 @@ const start = async (options: any = {}): Promise<any> => {
|
||||
await downloadAndUnzip({ version: versionToInstall, installDir, downloadDir })
|
||||
|
||||
// delay 1 sec for UX, unless we are testing
|
||||
await Bluebird.delay(1000)
|
||||
await timers.setTimeout(1000)
|
||||
|
||||
displayCompletionMsg()
|
||||
}
|
||||
@@ -368,18 +371,16 @@ const start = async (options: any = {}): Promise<any> => {
|
||||
const unzipTask = ({ zipFilePath, installDir, progress, rendererOptions }: any): any => {
|
||||
return {
|
||||
options: { title: util.titleize('Unzipping Cypress') },
|
||||
task: (ctx: any, task: any) => {
|
||||
task: async (ctx: any, task: any) => {
|
||||
// as our unzip progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Unzipping Cypress')
|
||||
|
||||
return unzip.start({ zipFilePath, installDir, progress })
|
||||
.then(() => {
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Unzipped Cypress')),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
})
|
||||
await unzip.start({ zipFilePath, installDir, progress })
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(chalk.green('Unzipped Cypress')),
|
||||
rendererOptions.renderer,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import os from 'os'
|
||||
import path from 'path'
|
||||
import untildify from 'untildify'
|
||||
import Debug from 'debug'
|
||||
import fs from '../fs'
|
||||
import { cwd } from 'process'
|
||||
import fs from 'fs-extra'
|
||||
import util from '../util'
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
@@ -65,7 +66,7 @@ const getVersionDir = (version: string = util.pkgVersion(), buildInfo: any = uti
|
||||
*/
|
||||
const isInstallingFromPostinstallHook = (): boolean => {
|
||||
// individual folders
|
||||
const cwdFolders = process.cwd().split(path.sep)
|
||||
const cwdFolders = cwd().split(path.sep)
|
||||
const length = cwdFolders.length
|
||||
|
||||
return cwdFolders[length - 2] === 'node_modules' && cwdFolders[length - 1] === 'cypress'
|
||||
@@ -93,20 +94,19 @@ const getCacheDir = (): string => {
|
||||
return cache_directory
|
||||
}
|
||||
|
||||
const parseRealPlatformBinaryFolderAsync = (binaryPath: string): any => {
|
||||
return fs.realpathAsync(binaryPath)
|
||||
.then((realPath: any) => {
|
||||
debug('CYPRESS_RUN_BINARY has realpath:', realPath)
|
||||
if (!realPath.toString().endsWith(getPlatformExecutable())) {
|
||||
return false
|
||||
}
|
||||
const parseRealPlatformBinaryFolderAsync = async (binaryPath: string): Promise<any> => {
|
||||
const realPath = await fs.realpath(binaryPath)
|
||||
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.resolve(realPath, '..', '..', '..')
|
||||
}
|
||||
debug('CYPRESS_RUN_BINARY has realpath:', realPath)
|
||||
if (!realPath.toString().endsWith(getPlatformExecutable())) {
|
||||
return false
|
||||
}
|
||||
|
||||
return path.resolve(realPath, '..')
|
||||
})
|
||||
if (os.platform() === 'darwin') {
|
||||
return path.resolve(realPath, '..', '..', '..')
|
||||
}
|
||||
|
||||
return path.resolve(realPath, '..')
|
||||
}
|
||||
|
||||
const getDistDir = (): string => {
|
||||
@@ -122,25 +122,34 @@ const getBinaryStatePath = (binaryDir: string): string => {
|
||||
return path.join(binaryDir, '..', 'binary_state.json')
|
||||
}
|
||||
|
||||
const getBinaryStateContentsAsync = (binaryDir: string): any => {
|
||||
const getBinaryStateContentsAsync = async (binaryDir: string): Promise<any> => {
|
||||
const fullPath = getBinaryStatePath(binaryDir)
|
||||
|
||||
return fs.readJsonAsync(fullPath)
|
||||
.catch({ code: 'ENOENT' }, SyntaxError, () => {
|
||||
debug('could not read binary_state.json file at "%s"', fullPath)
|
||||
try {
|
||||
const contents = await fs.readJson(fullPath)
|
||||
|
||||
return {}
|
||||
})
|
||||
debug('binary_state.json contents:', contents)
|
||||
|
||||
return contents
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
|
||||
debug('could not read binary_state.json file at "%s"', fullPath)
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const getBinaryVerifiedAsync = (binaryDir: string): any => {
|
||||
return getBinaryStateContentsAsync(binaryDir)
|
||||
.tap(debug)
|
||||
.get('verified')
|
||||
const getBinaryVerifiedAsync = async (binaryDir: string): Promise<boolean> => {
|
||||
const contents = await getBinaryStateContentsAsync(binaryDir)
|
||||
|
||||
return contents.verified
|
||||
}
|
||||
|
||||
const clearBinaryStateAsync = (binaryDir: string): any => {
|
||||
return fs.removeAsync(getBinaryStatePath(binaryDir))
|
||||
const clearBinaryStateAsync = async (binaryDir: string): Promise<void> => {
|
||||
await fs.remove(getBinaryStatePath(binaryDir))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,15 +158,14 @@ const clearBinaryStateAsync = (binaryDir: string): any => {
|
||||
* @param {string} binaryDir Folder holding the binary
|
||||
* @returns {Promise<void>} returns a promise
|
||||
*/
|
||||
const writeBinaryVerifiedAsync = (verified: boolean, binaryDir: string): any => {
|
||||
return getBinaryStateContentsAsync(binaryDir)
|
||||
.then((contents: any) => {
|
||||
return fs.outputJsonAsync(
|
||||
getBinaryStatePath(binaryDir),
|
||||
_.extend(contents, { verified }),
|
||||
{ spaces: 2 },
|
||||
)
|
||||
})
|
||||
const writeBinaryVerifiedAsync = async (verified: boolean, binaryDir: string): Promise<void> => {
|
||||
const contents = await getBinaryStateContentsAsync(binaryDir)
|
||||
|
||||
await fs.outputJson(
|
||||
getBinaryStatePath(binaryDir),
|
||||
_.extend(contents, { verified }),
|
||||
{ spaces: 2 },
|
||||
)
|
||||
}
|
||||
|
||||
const getPathToExecutable = (binaryDir: string): string => {
|
||||
@@ -168,19 +176,18 @@ const getPathToExecutable = (binaryDir: string): string => {
|
||||
* Resolves with an object read from the binary app package.json file.
|
||||
* If the file does not exist resolves with null
|
||||
*/
|
||||
const getBinaryPkgAsync = (binaryDir: string): any => {
|
||||
const getBinaryPkgAsync = async (binaryDir: string): Promise<any> => {
|
||||
const pathToPackageJson = getBinaryPkgPath(binaryDir)
|
||||
|
||||
debug('Reading binary package.json from:', pathToPackageJson)
|
||||
|
||||
return fs.pathExistsAsync(pathToPackageJson)
|
||||
.then((exists: boolean) => {
|
||||
if (!exists) {
|
||||
return null
|
||||
}
|
||||
const exists: boolean = await fs.pathExists(pathToPackageJson)
|
||||
|
||||
return fs.readJsonAsync(pathToPackageJson)
|
||||
})
|
||||
if (!exists) {
|
||||
return null
|
||||
}
|
||||
|
||||
return fs.readJson(pathToPackageJson)
|
||||
}
|
||||
|
||||
const getBinaryPkgVersion = (o: any): any => _.get(o, 'version', null)
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import _ from 'lodash'
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import cp from 'child_process'
|
||||
import os from 'os'
|
||||
import yauzl from 'yauzl'
|
||||
import Debug from 'debug'
|
||||
import extract from 'extract-zip'
|
||||
import Bluebird from 'bluebird'
|
||||
import readline from 'readline'
|
||||
import fs from 'fs-extra'
|
||||
import { throwFormErrorText, errors } from '../errors'
|
||||
import fs from '../fs'
|
||||
import util from '../util'
|
||||
|
||||
// TODO: this package needs to be replaced as we can't import it in vitest
|
||||
const is = require('check-more-types')
|
||||
|
||||
const debug = Debug('cypress:cli:unzip')
|
||||
|
||||
const unzipTools = {
|
||||
@@ -19,7 +20,7 @@ const unzipTools = {
|
||||
}
|
||||
|
||||
// expose this function for simple testing
|
||||
const unzip = ({ zipFilePath, installDir, progress }: any): any => {
|
||||
const unzip = async ({ zipFilePath, installDir, progress }: any): Promise<void> => {
|
||||
debug('unzipping from %s', zipFilePath)
|
||||
debug('into', installDir)
|
||||
|
||||
@@ -30,170 +31,167 @@ const unzip = ({ zipFilePath, installDir, progress }: any): any => {
|
||||
const startTime = Date.now()
|
||||
let yauzlDoneTime = 0
|
||||
|
||||
return fs.ensureDirAsync(installDir)
|
||||
.then(() => {
|
||||
return new Bluebird((resolve: any, reject: any) => {
|
||||
return yauzl.open(zipFilePath, (err: any, zipFile: any) => {
|
||||
yauzlDoneTime = Date.now()
|
||||
await fs.ensureDir(installDir)
|
||||
|
||||
if (err) {
|
||||
debug('error using yauzl %s', err.message)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
return yauzl.open(zipFilePath, (err: any, zipFile: any) => {
|
||||
yauzlDoneTime = Date.now()
|
||||
|
||||
return reject(err)
|
||||
if (err) {
|
||||
debug('error using yauzl %s', err.message)
|
||||
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const total = zipFile.entryCount
|
||||
|
||||
debug('zipFile entries count', total)
|
||||
|
||||
const started = new Date()
|
||||
|
||||
let percent = 0
|
||||
let count = 0
|
||||
|
||||
const notify = (percent: number): void => {
|
||||
const elapsed = +new Date() - +started
|
||||
|
||||
const eta = util.calculateEta(percent, elapsed)
|
||||
|
||||
progress.onProgress(percent, util.secsRemaining(eta))
|
||||
}
|
||||
|
||||
const tick = (): any => {
|
||||
count += 1
|
||||
|
||||
percent = ((count / total) * 100)
|
||||
const displayPercent = percent.toFixed(0)
|
||||
|
||||
return notify(Number(displayPercent))
|
||||
}
|
||||
|
||||
const unzipWithNode = async (): Promise<any> => {
|
||||
debug('unzipping with node.js (slow)')
|
||||
|
||||
const opts = {
|
||||
dir: installDir,
|
||||
onEntry: tick,
|
||||
}
|
||||
|
||||
const total = zipFile.entryCount
|
||||
debug('calling Node extract tool %s %o', zipFilePath, opts)
|
||||
|
||||
debug('zipFile entries count', total)
|
||||
try {
|
||||
await unzipTools.extract(zipFilePath, opts)
|
||||
debug('node unzip finished')
|
||||
|
||||
const started = new Date()
|
||||
return resolve()
|
||||
} catch (err: any) {
|
||||
const error = err || new Error('Unknown error with Node extract tool')
|
||||
|
||||
let percent = 0
|
||||
let count = 0
|
||||
debug('error %s', error.message)
|
||||
|
||||
const notify = (percent: number): void => {
|
||||
const elapsed = +new Date() - +started
|
||||
|
||||
const eta = util.calculateEta(percent, elapsed)
|
||||
|
||||
progress.onProgress(percent, util.secsRemaining(eta))
|
||||
return reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
const tick = (): any => {
|
||||
count += 1
|
||||
const unzipFallback = _.once(unzipWithNode)
|
||||
|
||||
percent = ((count / total) * 100)
|
||||
const displayPercent = percent.toFixed(0)
|
||||
const unzipWithUnzipTool = (): any => {
|
||||
debug('unzipping via `unzip`')
|
||||
|
||||
return notify(Number(displayPercent))
|
||||
}
|
||||
const inflatingRe = /inflating:/
|
||||
|
||||
const unzipWithNode = (): any => {
|
||||
debug('unzipping with node.js (slow)')
|
||||
const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir])
|
||||
|
||||
const opts = {
|
||||
dir: installDir,
|
||||
onEntry: tick,
|
||||
}
|
||||
sp.on('error', (err: any) => {
|
||||
debug('unzip tool error: %s', err.message)
|
||||
unzipFallback()
|
||||
})
|
||||
|
||||
debug('calling Node extract tool %s %o', zipFilePath, opts)
|
||||
|
||||
return unzipTools.extract(zipFilePath, opts)
|
||||
.then(() => {
|
||||
debug('node unzip finished')
|
||||
sp.on('close', (code: number) => {
|
||||
debug('unzip tool close with code %d', code)
|
||||
if (code === 0) {
|
||||
percent = 100
|
||||
notify(percent)
|
||||
|
||||
return resolve()
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const error = err || new Error('Unknown error with Node extract tool')
|
||||
}
|
||||
|
||||
debug('error %s', error.message)
|
||||
debug('`unzip` failed %o', { code })
|
||||
|
||||
return reject(error)
|
||||
})
|
||||
}
|
||||
return unzipFallback()
|
||||
})
|
||||
|
||||
const unzipFallback = _.once(unzipWithNode)
|
||||
sp.stdout.on('data', (data: any) => {
|
||||
if (inflatingRe.test(data)) {
|
||||
return tick()
|
||||
}
|
||||
})
|
||||
|
||||
const unzipWithUnzipTool = (): any => {
|
||||
debug('unzipping via `unzip`')
|
||||
sp.stderr.on('data', (data: any) => {
|
||||
debug('`unzip` stderr %s', data)
|
||||
})
|
||||
}
|
||||
|
||||
const inflatingRe = /inflating:/
|
||||
// we attempt to first unzip with the native osx
|
||||
// ditto because its less likely to have problems
|
||||
// with corruption, symlinks, or icons causing failures
|
||||
// and can handle resource forks
|
||||
// http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
|
||||
const unzipWithOsx = (): any => {
|
||||
debug('unzipping via `ditto`')
|
||||
|
||||
const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir])
|
||||
const copyingFileRe = /^copying file/
|
||||
|
||||
sp.on('error', (err: any) => {
|
||||
debug('unzip tool error: %s', err.message)
|
||||
unzipFallback()
|
||||
})
|
||||
const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir])
|
||||
|
||||
sp.on('close', (code: number) => {
|
||||
debug('unzip tool close with code %d', code)
|
||||
if (code === 0) {
|
||||
percent = 100
|
||||
notify(percent)
|
||||
// f-it just unzip with node
|
||||
sp.on('error', (err: any) => {
|
||||
debug(err.message)
|
||||
unzipFallback()
|
||||
})
|
||||
|
||||
return resolve()
|
||||
}
|
||||
|
||||
debug('`unzip` failed %o', { code })
|
||||
|
||||
return unzipFallback()
|
||||
})
|
||||
|
||||
sp.stdout.on('data', (data: any) => {
|
||||
if (inflatingRe.test(data)) {
|
||||
return tick()
|
||||
}
|
||||
})
|
||||
|
||||
sp.stderr.on('data', (data: any) => {
|
||||
debug('`unzip` stderr %s', data)
|
||||
})
|
||||
}
|
||||
|
||||
// we attempt to first unzip with the native osx
|
||||
// ditto because its less likely to have problems
|
||||
// with corruption, symlinks, or icons causing failures
|
||||
// and can handle resource forks
|
||||
// http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
|
||||
const unzipWithOsx = (): any => {
|
||||
debug('unzipping via `ditto`')
|
||||
|
||||
const copyingFileRe = /^copying file/
|
||||
|
||||
const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir])
|
||||
|
||||
// f-it just unzip with node
|
||||
sp.on('error', (err: any) => {
|
||||
debug(err.message)
|
||||
unzipFallback()
|
||||
})
|
||||
|
||||
sp.on('close', (code: number) => {
|
||||
if (code === 0) {
|
||||
sp.on('close', (code: number) => {
|
||||
if (code === 0) {
|
||||
// make sure we get to 100% on the progress bar
|
||||
// because reading in lines is not really accurate
|
||||
percent = 100
|
||||
notify(percent)
|
||||
percent = 100
|
||||
notify(percent)
|
||||
|
||||
return resolve()
|
||||
}
|
||||
return resolve()
|
||||
}
|
||||
|
||||
debug('`ditto` failed %o', { code })
|
||||
debug('`ditto` failed %o', { code })
|
||||
|
||||
return unzipFallback()
|
||||
})
|
||||
return unzipFallback()
|
||||
})
|
||||
|
||||
return readline.createInterface({
|
||||
input: sp.stderr,
|
||||
})
|
||||
.on('line', (line: string) => {
|
||||
if (copyingFileRe.test(line)) {
|
||||
return tick()
|
||||
}
|
||||
})
|
||||
}
|
||||
return readline.createInterface({
|
||||
input: sp.stderr,
|
||||
})
|
||||
.on('line', (line: string) => {
|
||||
if (copyingFileRe.test(line)) {
|
||||
return tick()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return unzipWithOsx()
|
||||
case 'linux':
|
||||
return unzipWithUnzipTool()
|
||||
case 'win32':
|
||||
return unzipWithNode()
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
.tap(() => {
|
||||
debug('unzip completed %o', {
|
||||
yauzlMs: yauzlDoneTime - startTime,
|
||||
unzipMs: Date.now() - yauzlDoneTime,
|
||||
})
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return unzipWithOsx()
|
||||
case 'linux':
|
||||
return unzipWithUnzipTool()
|
||||
case 'win32':
|
||||
return unzipWithNode()
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
debug('unzip completed %o', {
|
||||
yauzlMs: yauzlDoneTime - startTime,
|
||||
unzipMs: Date.now() - yauzlDoneTime,
|
||||
})
|
||||
}
|
||||
|
||||
function isMaybeWindowsMaxPathLengthError (err: any): boolean {
|
||||
@@ -214,7 +212,7 @@ const start = async ({ zipFilePath, installDir, progress }: any): Promise<void>
|
||||
if (installDirExists) {
|
||||
debug('removing existing unzipped binary', installDir)
|
||||
|
||||
await fs.removeAsync(installDir)
|
||||
await fs.remove(installDir)
|
||||
}
|
||||
|
||||
await unzip({ zipFilePath, installDir, progress })
|
||||
|
||||
@@ -16,7 +16,7 @@ import state from './state'
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
|
||||
const VERIFY_TEST_RUNNER_TIMEOUT_MS = (() => {
|
||||
export const verifyTestRunnerTimeoutMs = () => {
|
||||
const verifyTimeout = +(util?.getEnv('CYPRESS_VERIFY_TIMEOUT') || 'NaN')
|
||||
|
||||
if (_.isNumber(verifyTimeout) && !_.isNaN(verifyTimeout)) {
|
||||
@@ -24,60 +24,38 @@ const VERIFY_TEST_RUNNER_TIMEOUT_MS = (() => {
|
||||
}
|
||||
|
||||
return 30000
|
||||
})()
|
||||
}
|
||||
|
||||
const checkExecutable = (binaryDir: string): any => {
|
||||
const checkExecutable = async (binaryDir: string): Promise<void> => {
|
||||
const executable = state.getPathToExecutable(binaryDir)
|
||||
|
||||
debug('checking if executable exists', executable)
|
||||
|
||||
return util.isExecutableAsync(executable)
|
||||
.then((isExecutable: boolean) => {
|
||||
try {
|
||||
const isExecutable = await util.isExecutableAsync(executable)
|
||||
|
||||
debug('Binary is executable? :', isExecutable)
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.binaryNotExecutable(executable))()
|
||||
}
|
||||
})
|
||||
.catch({ code: 'ENOENT' }, () => {
|
||||
if (util.isCi()) {
|
||||
return throwFormErrorText(errors.notInstalledCI(executable))()
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (util.isCi()) {
|
||||
return throwFormErrorText(errors.notInstalledCI(executable))()
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(stripIndent`
|
||||
Cypress executable not found at: ${chalk.cyan(executable)}
|
||||
`)
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(stripIndent`
|
||||
Cypress executable not found at: ${chalk.cyan(executable)}
|
||||
`)
|
||||
})
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const runSmokeTest = (binaryDir: string, options: any): any => {
|
||||
let executable = state.getPathToExecutable(binaryDir)
|
||||
|
||||
const onSmokeTestError = (smokeTestCommand: string, linuxWithDisplayEnv: boolean) => {
|
||||
return (err: any) => {
|
||||
debug('Smoke test failed:', err)
|
||||
|
||||
let errMessage = err.stderr || err.message
|
||||
|
||||
debug('error message:', errMessage)
|
||||
|
||||
if (err.timedOut) {
|
||||
debug('error timedOut is true')
|
||||
|
||||
return throwFormErrorText(
|
||||
errors.smokeTestFailure(smokeTestCommand, true),
|
||||
)(errMessage)
|
||||
}
|
||||
|
||||
if (linuxWithDisplayEnv && util.isBrokenGtkDisplay(errMessage)) {
|
||||
util.logBrokenGtkDisplayWarning()
|
||||
|
||||
return throwFormErrorText(errors.invalidSmokeTestDisplayError)(errMessage)
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingDependency)(errMessage)
|
||||
}
|
||||
}
|
||||
|
||||
const needsXvfb = xvfb.isNeeded()
|
||||
|
||||
debug('needs Xvfb?', needsXvfb)
|
||||
@@ -86,7 +64,7 @@ const runSmokeTest = (binaryDir: string, options: any): any => {
|
||||
* Spawn Cypress running smoke test to check if all operating system
|
||||
* dependencies are good.
|
||||
*/
|
||||
const spawn = (linuxWithDisplayEnv: boolean): any => {
|
||||
const spawn = async (linuxWithDisplayEnv: boolean): Promise<any> => {
|
||||
const random = _.random(0, 1000)
|
||||
const args = ['--smoke-test', `--ping=${random}`]
|
||||
|
||||
@@ -118,13 +96,13 @@ const runSmokeTest = (binaryDir: string, options: any): any => {
|
||||
timeout: options.smokeTestTimeout,
|
||||
})
|
||||
|
||||
return Bluebird.resolve(util.exec(
|
||||
executable,
|
||||
args,
|
||||
stdioOptions,
|
||||
))
|
||||
.catch(onSmokeTestError(smokeTestCommand, linuxWithDisplayEnv))
|
||||
.then((result: any) => {
|
||||
try {
|
||||
const result = await util.exec(
|
||||
executable,
|
||||
args,
|
||||
stdioOptions,
|
||||
)
|
||||
|
||||
// TODO: when execa > 1.1 is released
|
||||
// change this to `result.all` for both stderr and stdout
|
||||
// use lodash to be robust during tests against null result or missing stdout
|
||||
@@ -140,25 +118,51 @@ const runSmokeTest = (binaryDir: string, options: any): any => {
|
||||
|
||||
return throwFormErrorText(errors.smokeTestFailure(smokeTestCommand, false))(errorText)
|
||||
}
|
||||
} catch (err: any) {
|
||||
debug('Smoke test failed:', err)
|
||||
|
||||
let errMessage = err.stderr || err.message
|
||||
|
||||
debug('error message:', errMessage)
|
||||
|
||||
if (err.timedOut) {
|
||||
debug('error timedOut is true')
|
||||
|
||||
return throwFormErrorText(
|
||||
errors.smokeTestFailure(smokeTestCommand, true),
|
||||
)(errMessage)
|
||||
}
|
||||
|
||||
if (linuxWithDisplayEnv && util.isBrokenGtkDisplay(errMessage)) {
|
||||
util.logBrokenGtkDisplayWarning()
|
||||
|
||||
return throwFormErrorText(errors.invalidSmokeTestDisplayError)(errMessage)
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingDependency)(errMessage)
|
||||
}
|
||||
}
|
||||
|
||||
const spawnInXvfb = async (linuxWithDisplayEnv?: boolean): Promise<any> => {
|
||||
await xvfb.start()
|
||||
|
||||
return spawn(linuxWithDisplayEnv || false).finally(async () => {
|
||||
await xvfb.stop()
|
||||
})
|
||||
}
|
||||
|
||||
const spawnInXvfb = (linuxWithDisplayEnv?: boolean): any => {
|
||||
return xvfb
|
||||
.start()
|
||||
.then(() => {
|
||||
return spawn(linuxWithDisplayEnv || false)
|
||||
})
|
||||
.finally(xvfb.stop)
|
||||
}
|
||||
|
||||
const userFriendlySpawn = (linuxWithDisplayEnv: boolean): any => {
|
||||
const userFriendlySpawn = async (linuxWithDisplayEnv: boolean): Promise<void> => {
|
||||
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv))
|
||||
|
||||
return spawn(linuxWithDisplayEnv)
|
||||
.catch({ code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR' }, () => {
|
||||
return spawnInXvfb(linuxWithDisplayEnv)
|
||||
})
|
||||
try {
|
||||
await spawn(linuxWithDisplayEnv)
|
||||
} catch (err: any) {
|
||||
if (err.code === 'INVALID_SMOKE_TEST_DISPLAY_ERROR') {
|
||||
return spawnInXvfb(linuxWithDisplayEnv)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
if (needsXvfb) {
|
||||
@@ -173,7 +177,7 @@ const runSmokeTest = (binaryDir: string, options: any): any => {
|
||||
return userFriendlySpawn(linuxWithDisplayEnv)
|
||||
}
|
||||
|
||||
function testBinary (version: string, binaryDir: string, options: any): any {
|
||||
function testBinary (version: string, binaryDir: string, options: any): Promise<any> {
|
||||
debug('running binary verification check', version)
|
||||
|
||||
// if running from 'cypress verify', don't print this message
|
||||
@@ -200,31 +204,28 @@ function testBinary (version: string, binaryDir: string, options: any): any {
|
||||
const tasks = new Listr([
|
||||
{
|
||||
title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)),
|
||||
task: (ctx: any, task: any) => {
|
||||
task: async (ctx: any, task: any) => {
|
||||
debug('clearing out the verified version')
|
||||
|
||||
return state.clearBinaryStateAsync(binaryDir)
|
||||
.then(() => {
|
||||
return Bluebird.all([
|
||||
runSmokeTest(binaryDir, options),
|
||||
Bluebird.delay(1500), // good user experience
|
||||
])
|
||||
})
|
||||
.then(() => {
|
||||
debug('write verified: true')
|
||||
await state.clearBinaryStateAsync(binaryDir)
|
||||
|
||||
return state.writeBinaryVerifiedAsync(true, binaryDir)
|
||||
})
|
||||
.then(() => {
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(
|
||||
chalk.green('Verified Cypress!'),
|
||||
chalk.gray(binaryDir),
|
||||
),
|
||||
rendererOptions.renderer as string,
|
||||
)
|
||||
})
|
||||
await Promise.all([
|
||||
runSmokeTest(binaryDir, options),
|
||||
Bluebird.delay(1500), // good user experience
|
||||
])
|
||||
|
||||
debug('write verified: true')
|
||||
|
||||
await state.writeBinaryVerifiedAsync(true, binaryDir)
|
||||
|
||||
util.setTaskTitle(
|
||||
task,
|
||||
util.titleize(
|
||||
chalk.green('Verified Cypress!'),
|
||||
chalk.gray(binaryDir),
|
||||
),
|
||||
rendererOptions.renderer as string,
|
||||
)
|
||||
},
|
||||
},
|
||||
] as any, rendererOptions as any)
|
||||
@@ -232,32 +233,30 @@ function testBinary (version: string, binaryDir: string, options: any): any {
|
||||
return tasks.run()
|
||||
}
|
||||
|
||||
const maybeVerify = (installedVersion: string, binaryDir: string, options: any): any => {
|
||||
return state.getBinaryVerifiedAsync(binaryDir)
|
||||
.then((isVerified: boolean) => {
|
||||
debug('is Verified ?', isVerified)
|
||||
const maybeVerify = async (installedVersion: string, binaryDir: string, options: any): Promise<void> => {
|
||||
const isVerified = await state.getBinaryVerifiedAsync(binaryDir)
|
||||
|
||||
let shouldVerify = !isVerified
|
||||
debug('is Verified ?', isVerified)
|
||||
|
||||
// force verify if options.force
|
||||
if (options.force) {
|
||||
debug('force verify')
|
||||
shouldVerify = true
|
||||
let shouldVerify = !isVerified
|
||||
|
||||
// force verify if options.force
|
||||
if (options.force) {
|
||||
debug('force verify')
|
||||
shouldVerify = true
|
||||
}
|
||||
|
||||
if (shouldVerify) {
|
||||
await testBinary(installedVersion, binaryDir, options)
|
||||
|
||||
if (options.welcomeMessage) {
|
||||
logger.log()
|
||||
logger.log('Opening Cypress...')
|
||||
}
|
||||
|
||||
if (shouldVerify) {
|
||||
return testBinary(installedVersion, binaryDir, options)
|
||||
.then(() => {
|
||||
if (options.welcomeMessage) {
|
||||
logger.log()
|
||||
logger.log('Opening Cypress...')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const start = (options: any = {}): any => {
|
||||
export const start = async (options: any = {}): Promise<void> => {
|
||||
debug('verifying Cypress app')
|
||||
|
||||
const packageVersion = util.pkgVersion()
|
||||
@@ -267,21 +266,21 @@ const start = (options: any = {}): any => {
|
||||
dev: false,
|
||||
force: false,
|
||||
welcomeMessage: true,
|
||||
smokeTestTimeout: VERIFY_TEST_RUNNER_TIMEOUT_MS,
|
||||
smokeTestTimeout: verifyTestRunnerTimeoutMs(),
|
||||
skipVerify: util.getEnv('CYPRESS_SKIP_VERIFY') === 'true',
|
||||
})
|
||||
|
||||
if (options.skipVerify) {
|
||||
debug('skipping verification of the Cypress app')
|
||||
|
||||
return Bluebird.resolve()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (options.dev) {
|
||||
return runSmokeTest('', options)
|
||||
}
|
||||
|
||||
const parseBinaryEnvVar = (): any => {
|
||||
const parseBinaryEnvVar = async (): Promise<void> => {
|
||||
const envBinaryPath = util.getEnv('CYPRESS_RUN_BINARY')
|
||||
|
||||
debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath)
|
||||
@@ -295,19 +294,18 @@ const start = (options: any = {}): any => {
|
||||
|
||||
logger.log()
|
||||
|
||||
return util.isExecutableAsync(envBinaryPath as string)
|
||||
.then((isExecutable: boolean) => {
|
||||
try {
|
||||
const isExecutable = await util.isExecutableAsync(envBinaryPath as string)
|
||||
|
||||
debug('CYPRESS_RUN_BINARY is executable? :', isExecutable)
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath as string))(stripIndent`
|
||||
The supplied binary path is not executable
|
||||
`)
|
||||
The supplied binary path is not executable
|
||||
`)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return state.parseRealPlatformBinaryFolderAsync(envBinaryPath as string)
|
||||
})
|
||||
.then((envBinaryDir: string) => {
|
||||
|
||||
const envBinaryDir = await state.parseRealPlatformBinaryFolderAsync(envBinaryPath as string)
|
||||
|
||||
if (!envBinaryDir) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath as string))()
|
||||
}
|
||||
@@ -315,31 +313,26 @@ const start = (options: any = {}): any => {
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir)
|
||||
|
||||
binaryDir = envBinaryDir
|
||||
})
|
||||
.catch({ code: 'ENOENT' }, (err: any) => {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath as string))(err.message)
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath as string))(err.message)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return Bluebird.try(() => {
|
||||
try {
|
||||
debug('checking environment variables')
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
return parseBinaryEnvVar()
|
||||
await parseBinaryEnvVar()
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return checkExecutable(binaryDir)
|
||||
})
|
||||
.tap(() => {
|
||||
return debug('binaryDir is ', binaryDir)
|
||||
})
|
||||
.then(() => {
|
||||
return state.getBinaryPkgAsync(binaryDir)
|
||||
})
|
||||
.then((pkg: any) => {
|
||||
return state.getBinaryPkgVersion(pkg)
|
||||
})
|
||||
.then((binaryVersion: string) => {
|
||||
|
||||
await checkExecutable(binaryDir)
|
||||
debug('binaryDir is ', binaryDir)
|
||||
const pkg = await state.getBinaryPkgAsync(binaryDir)
|
||||
const binaryVersion = state.getBinaryPkgVersion(pkg)
|
||||
|
||||
if (!binaryVersion) {
|
||||
debug('no Cypress binary found for cli version ', packageVersion)
|
||||
|
||||
@@ -366,16 +359,14 @@ const start = (options: any = {}): any => {
|
||||
logger.log()
|
||||
}
|
||||
|
||||
return maybeVerify(binaryVersion, binaryDir, options)
|
||||
})
|
||||
|
||||
.catch((err: any) => {
|
||||
await maybeVerify(binaryVersion, binaryDir, options)
|
||||
} catch (err: any) {
|
||||
if (err.known) {
|
||||
throw err
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.unexpected)(err.stack)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const isLinuxLike = (): boolean => os.platform() !== 'win32'
|
||||
@@ -389,10 +380,4 @@ const isLinuxLike = (): boolean => os.platform() !== 'win32'
|
||||
* Seems there is a lot of discussion around this issue among Electron users
|
||||
* @see https://github.com/electron/electron/issues/17972
|
||||
*/
|
||||
const needsSandbox = (): boolean => isLinuxLike()
|
||||
|
||||
export default {
|
||||
start,
|
||||
needsSandbox,
|
||||
VERIFY_TEST_RUNNER_TIMEOUT_MS,
|
||||
}
|
||||
export const needsSandbox = (): boolean => isLinuxLike()
|
||||
|
||||
@@ -4,7 +4,6 @@ import os from 'os'
|
||||
import ospath from 'ospath'
|
||||
import hasha from 'hasha'
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import tty from 'tty'
|
||||
import path from 'path'
|
||||
import { isCI as isCi } from 'ci-info'
|
||||
@@ -15,18 +14,20 @@ import Bluebird from 'bluebird'
|
||||
import cachedir from 'cachedir'
|
||||
import logSymbols from 'log-symbols'
|
||||
import executable from 'executable'
|
||||
import { cwd } from 'process'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import supportsColor from 'supports-color'
|
||||
import isInstalledGlobally from 'is-installed-globally'
|
||||
import logger from './logger'
|
||||
import Debug from 'debug'
|
||||
import fs from './fs'
|
||||
import fs from 'fs-extra'
|
||||
import pkg from '../package.json'
|
||||
|
||||
// TODO: this package needs to be replaced as we can't import it in vitest
|
||||
const is = require('check-more-types')
|
||||
|
||||
const debug = Debug('cypress:cli')
|
||||
|
||||
// Import package.json dynamically to avoid TypeScript JSON import issues
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'))
|
||||
|
||||
const issuesUrl = 'https://github.com/cypress-io/cypress/issues'
|
||||
|
||||
/**
|
||||
@@ -38,10 +39,12 @@ const getFileChecksum = (filename: string): any => {
|
||||
return hasha.fromFile(filename, { algorithm: 'sha512' })
|
||||
}
|
||||
|
||||
const getFileSize = (filename: string): any => {
|
||||
const getFileSize = async (filename: string): Promise<any> => {
|
||||
la(is.unemptyString(filename), 'expected filename', filename)
|
||||
|
||||
return fs.statAsync(filename).get('size')
|
||||
const { size } = await fs.stat(filename)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
const isBrokenGtkDisplayRe = /Gtk: cannot open display/
|
||||
@@ -162,7 +165,6 @@ function printNodeOptions (log: any = debug): void {
|
||||
```
|
||||
*/
|
||||
const dequote = (str: string): string => {
|
||||
// @ts-expect-error method exists but is not typed
|
||||
la(is.string(str), 'expected a string to remove double quotes', str)
|
||||
if (str.length > 1 && str[0] === '"' && str[str.length - 1] === '"') {
|
||||
return str.substr(1, str.length - 2)
|
||||
@@ -242,6 +244,7 @@ const getApplicationDataFolder = (...paths: string[]): string => {
|
||||
// allow overriding the app_data folder
|
||||
let folder = env.CYPRESS_CONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development'
|
||||
|
||||
// @ts-expect-error value exists but is not typed
|
||||
const PRODUCT_NAME = pkg.productName || pkg.name
|
||||
const OS_DATA_PATH = ospath.data()
|
||||
|
||||
@@ -270,13 +273,13 @@ const util = {
|
||||
getEnvOverrides (options: any = {}): any {
|
||||
return _
|
||||
.chain({})
|
||||
.extend(util.getEnvColors())
|
||||
.extend(util.getForceTty())
|
||||
.extend(this.getEnvColors())
|
||||
.extend(this.getForceTty())
|
||||
.omitBy(_.isUndefined) // remove undefined values
|
||||
.mapValues((value: any) => { // stringify to 1 or 0
|
||||
return value ? '1' : '0'
|
||||
})
|
||||
.extend(util.getOriginalNodeOptions())
|
||||
.extend(this.getOriginalNodeOptions())
|
||||
.value()
|
||||
},
|
||||
|
||||
@@ -292,14 +295,14 @@ const util = {
|
||||
|
||||
getForceTty (): any {
|
||||
return {
|
||||
FORCE_STDIN_TTY: util.isTty(process.stdin.fd),
|
||||
FORCE_STDOUT_TTY: util.isTty(process.stdout.fd),
|
||||
FORCE_STDERR_TTY: util.isTty(process.stderr.fd),
|
||||
FORCE_STDIN_TTY: this.isTty(process.stdin.fd),
|
||||
FORCE_STDOUT_TTY: this.isTty(process.stdout.fd),
|
||||
FORCE_STDERR_TTY: this.isTty(process.stderr.fd),
|
||||
}
|
||||
},
|
||||
|
||||
getEnvColors (): any {
|
||||
const sc = util.supportsColor()
|
||||
const sc = this.supportsColor()
|
||||
|
||||
return {
|
||||
FORCE_COLOR: sc,
|
||||
@@ -330,10 +333,11 @@ const util = {
|
||||
},
|
||||
|
||||
cwd (): string {
|
||||
return process.cwd()
|
||||
return cwd()
|
||||
},
|
||||
|
||||
pkgBuildInfo (): any {
|
||||
// @ts-expect-error value exists but is not typed
|
||||
return pkg.buildInfo
|
||||
},
|
||||
|
||||
@@ -341,6 +345,7 @@ const util = {
|
||||
return pkg.version
|
||||
},
|
||||
|
||||
// TODO: remove this method
|
||||
exit (code: number): never {
|
||||
process.exit(code)
|
||||
},
|
||||
@@ -411,30 +416,29 @@ const util = {
|
||||
|
||||
isLinux,
|
||||
|
||||
getOsVersionAsync () {
|
||||
return Bluebird.try(() => {
|
||||
return si.osInfo()
|
||||
.then((osInfo) => {
|
||||
if (osInfo.distro && osInfo.release) {
|
||||
return `${osInfo.distro} - ${osInfo.release}`
|
||||
}
|
||||
async getOsVersionAsync (): Promise<any> {
|
||||
try {
|
||||
const osInfo = await si.osInfo()
|
||||
|
||||
return os.release()
|
||||
}).catch(() => {
|
||||
return os.release()
|
||||
})
|
||||
})
|
||||
if (osInfo.distro && osInfo.release) {
|
||||
return `${osInfo.distro} - ${osInfo.release}`
|
||||
}
|
||||
} catch (err) {
|
||||
return os.release()
|
||||
}
|
||||
|
||||
return os.release()
|
||||
},
|
||||
|
||||
async getPlatformInfo (): Promise<string> {
|
||||
const [version, osArch] = await Bluebird.all([
|
||||
util.getOsVersionAsync(),
|
||||
this.getOsVersionAsync(),
|
||||
this.getRealArch(),
|
||||
])
|
||||
|
||||
return stripIndent`
|
||||
Platform: ${os.platform()}-${osArch} (${version})
|
||||
Cypress Version: ${util.pkgVersion()}
|
||||
Cypress Version: ${this.pkgVersion()}
|
||||
`
|
||||
},
|
||||
|
||||
@@ -493,7 +497,7 @@ const util = {
|
||||
return filename
|
||||
}
|
||||
|
||||
return path.join(process.cwd(), '..', '..', filename)
|
||||
return path.join(cwd(), '..', '..', filename)
|
||||
},
|
||||
|
||||
getEnv (varName: string, trim?: boolean): string | undefined {
|
||||
@@ -556,7 +560,6 @@ const util = {
|
||||
isPossibleLinuxWithIncorrectDisplay,
|
||||
|
||||
getGitHubIssueUrl (number: number): string {
|
||||
// @ts-expect-error method exists but is not typed
|
||||
la(is.positive(number), 'github issue should be a positive number', number)
|
||||
la(_.isInteger(number), 'github issue should be an integer', number)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"name": "cypress",
|
||||
"version": "0.0.0-development",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build-cli": "tsc && tsx ./scripts/build.ts && tsx ./scripts/post-build.ts",
|
||||
"build-cli": "tsc && tsc -p tsconfig.esm.json && tsx ./scripts/build.ts && tsx ./scripts/post-build.ts",
|
||||
"clean": "tsx ./scripts/clean.ts",
|
||||
"dtslint": "dtslint types",
|
||||
"postinstall": "patch-package && tsx ./scripts/post-install.ts",
|
||||
@@ -12,18 +12,17 @@
|
||||
"prebuild": "yarn postinstall && tsx ./scripts/start-build.ts",
|
||||
"size": "t=\"cypress-v0.0.0.tgz\"; yarn pack --filename \"${t}\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
|
||||
"test": "yarn test-unit",
|
||||
"test-debug": "node --inspect-brk $(yarn bin mocha)",
|
||||
"test-debug": "npx vitest --inspect-brk --no-file-parallelism --test-timeout=0",
|
||||
"test-dependencies": "dependency-check . --missing --no-dev --verbose",
|
||||
"test-unit": "yarn unit",
|
||||
"test-watch": "yarn unit --watch",
|
||||
"types": "yarn dtslint",
|
||||
"unit": "cross-env BLUEBIRD_DEBUG=1 NODE_ENV=test mocha -r ts-node/register/transpile-only --reporter mocha-multi-reporters --reporter-options configFile=../mocha-reporter-config.json"
|
||||
"test-unit": "vitest run",
|
||||
"types": "yarn dtslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cypress/request": "^3.0.9",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
"@types/sinonjs__fake-timers": "8.1.1",
|
||||
"@types/sizzle": "^2.3.2",
|
||||
"@types/tmp": "^0.2.3",
|
||||
"arch": "^2.2.0",
|
||||
"blob-util": "^2.0.2",
|
||||
"bluebird": "^3.7.2",
|
||||
@@ -83,32 +82,22 @@
|
||||
"@types/mocha": "8.0.3",
|
||||
"@types/sinon": "9.0.9",
|
||||
"@types/sinon-chai": "3.2.12",
|
||||
"chai": "3.5.0",
|
||||
"chai-as-promised": "7.1.1",
|
||||
"chai-string": "1.5.0",
|
||||
"cross-env": "7.0.3",
|
||||
"dependency-check": "4.1.0",
|
||||
"dtslint": "4.2.1",
|
||||
"execa-wrap": "1.4.0",
|
||||
"mocha": "6.2.2",
|
||||
"mock-fs": "5.4.0",
|
||||
"mocked-env": "1.3.2",
|
||||
"nock": "13.2.9",
|
||||
"proxyquire": "2.1.3",
|
||||
"resolve-pkg": "2.0.0",
|
||||
"shelljs": "0.8.5",
|
||||
"sinon": "7.2.2",
|
||||
"snap-shot-it": "7.9.10",
|
||||
"spawn-mock": "1.0.0",
|
||||
"strip-ansi": "6.0.1",
|
||||
"tsx": "4.20.5",
|
||||
"typescript": "~5.9.2"
|
||||
"typescript": "~5.9.2",
|
||||
"vitest": "3.2.4"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"index.js",
|
||||
"index.mjs",
|
||||
"dist",
|
||||
"types/**/*.d.ts",
|
||||
"mount-utils",
|
||||
"vue",
|
||||
@@ -126,8 +115,8 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/index.d.ts",
|
||||
"import": "./index.mjs",
|
||||
"require": "./index.js"
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./types/net-stubbing": {
|
||||
"types": "./types/net-stubbing.d.ts"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash'
|
||||
import path from 'path'
|
||||
import shell from 'shelljs'
|
||||
import fs from '../lib/fs'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
// grab the current version and a few other properties
|
||||
// from the root package.json
|
||||
@@ -50,7 +50,7 @@ function preparePackageForNpmRelease (json: any, branchName?: string): any {
|
||||
keywords,
|
||||
types: 'types', // typescript types
|
||||
scripts: {
|
||||
postinstall: 'node index.js --exec install',
|
||||
postinstall: 'node dist/index.js --exec install',
|
||||
size: 't="$(npm pack .)"; wc -c "${t}"; tar tvf "${t}"; rm "${t}";',
|
||||
},
|
||||
})
|
||||
@@ -58,20 +58,20 @@ function preparePackageForNpmRelease (json: any, branchName?: string): any {
|
||||
return json
|
||||
}
|
||||
|
||||
function makeUserPackageFile (branchName?: string): Promise<any> {
|
||||
return fs.readJsonAsync(packageJsonSrc)
|
||||
.then((json: any) => preparePackageForNpmRelease(json, branchName))
|
||||
.then((json: any) => {
|
||||
return fs.outputJsonAsync(packageJsonDest, json, {
|
||||
spaces: 2,
|
||||
})
|
||||
.return(json) // returning package json object makes it easy to test
|
||||
})
|
||||
async function makeUserPackageFile (branchName?: string): Promise<any> {
|
||||
const json = await fs.readJson(packageJsonSrc)
|
||||
const jsonPrepared = preparePackageForNpmRelease(json, branchName)
|
||||
|
||||
await fs.outputJson(packageJsonDest, jsonPrepared, {
|
||||
spaces: 2,
|
||||
}) // returning package json object makes it easy to test
|
||||
|
||||
return jsonPrepared
|
||||
}
|
||||
|
||||
export default makeUserPackageFile
|
||||
|
||||
if (!module.parent) {
|
||||
if (require.main === module) {
|
||||
makeUserPackageFile(process.env.BRANCH)
|
||||
.catch((err: any) => {
|
||||
/* eslint-disable no-console */
|
||||
|
||||
@@ -23,16 +23,15 @@ includeTypes.forEach((folder: string) => {
|
||||
})
|
||||
|
||||
// build the project and copy the build files over to the build directory
|
||||
shell.exec('tsc')
|
||||
shell.exec('tsc -p tsconfig.json')
|
||||
shell.exec('tsc -p tsconfig.esm.json')
|
||||
|
||||
shell.cp('index.js', 'build/index.js')
|
||||
shell.cp('index.mjs', 'build/index.mjs')
|
||||
shell.mkdir('-p', 'build/dist')
|
||||
shell.cp('dist/*.js', 'build/dist/')
|
||||
shell.cp('dist/*.mjs', 'build/dist/')
|
||||
|
||||
shell.mkdir('-p', 'build/lib')
|
||||
shell.cp('lib/*.js', 'build/lib/')
|
||||
shell.mkdir('-p', 'build/dist/exec')
|
||||
shell.cp('dist/exec/*.js', 'build/dist/exec')
|
||||
|
||||
shell.mkdir('-p', 'build/lib/exec')
|
||||
shell.cp('lib/exec/*.js', 'build/lib/exec')
|
||||
|
||||
shell.mkdir('-p', 'build/lib/tasks')
|
||||
shell.cp('lib/tasks/*.js', 'build/lib/tasks')
|
||||
shell.mkdir('-p', 'build/dist/tasks')
|
||||
shell.cp('dist/tasks/*.js', 'build/dist/tasks')
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
body {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
padding: 0 1em;
|
||||
line-height: 1.4;
|
||||
color: #eee;
|
||||
background-color: #000;
|
||||
}
|
||||
pre {
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><pre>┌─────────┬──────────────┐
|
||||
│ <span style="color:#AAA">version<span style="color:#eee"> │ <span style="color:#AAA">last used<span style="color:#eee"> │
|
||||
├─────────┼──────────────┤
|
||||
│ <span style="color:#0A0">1.2.3<span style="color:#eee"> │ <span style="color:#0AA">3 months ago<span style="color:#eee"> │
|
||||
├─────────┼──────────────┤
|
||||
│ <span style="color:#0A0">2.3.4<span style="color:#eee"> │ <span style="color:#0AA">5 days ago<span style="color:#eee"> │
|
||||
└─────────┴──────────────┘</span></span></span></span></span></span></span></span></span></span></span></span>
|
||||
</pre></body></html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
body {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
padding: 0 1em;
|
||||
line-height: 1.4;
|
||||
color: #eee;
|
||||
background-color: #000;
|
||||
}
|
||||
pre {
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><pre>┌─────────┬──────────────┐
|
||||
│ <span style="color:#AAA">version<span style="color:#eee"> │ <span style="color:#AAA">last used<span style="color:#eee"> │
|
||||
├─────────┼──────────────┤
|
||||
│ <span style="color:#0A0">1.2.3<span style="color:#eee"> │ <span style="color:#0AA">3 months ago<span style="color:#eee"> │
|
||||
├─────────┼──────────────┤
|
||||
│ <span style="color:#0A0">2.3.4<span style="color:#eee"> │ unknown │
|
||||
└─────────┴──────────────┘</span></span></span></span></span></span></span></span></span></span>
|
||||
</pre></body></html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
body {
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
padding: 0 1em;
|
||||
line-height: 1.4;
|
||||
color: #eee;
|
||||
background-color: #000;
|
||||
}
|
||||
pre {
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body><pre>┌─────────┬──────────────┬───────┐
|
||||
│ <span style="color:#AAA">version<span style="color:#eee"> │ <span style="color:#AAA">last used<span style="color:#eee"> │ <span style="color:#AAA">size<span style="color:#eee"> │
|
||||
├─────────┼──────────────┼───────┤
|
||||
│ <span style="color:#0A0">1.2.3<span style="color:#eee"> │ <span style="color:#0AA">3 months ago<span style="color:#eee"> │ <span style="color:#555">0.2MB<span style="color:#eee"> │
|
||||
├─────────┼──────────────┼───────┤
|
||||
│ <span style="color:#0A0">2.3.4<span style="color:#eee"> │ unknown │ <span style="color:#555">0.2MB<span style="color:#eee"> │
|
||||
└─────────┴──────────────┴───────┘</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
|
||||
</pre></body></html>
|
||||
41
cli/test/lib/__snapshots__/build.spec.ts.snap
Normal file
41
cli/test/lib/__snapshots__/build.spec.ts.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`package.json build > outputs expected properties 1`] = `
|
||||
{
|
||||
"bugs": {
|
||||
"url": "https://github.com/cypress-io/cypress/issues",
|
||||
},
|
||||
"buildInfo": "replaced by normalizePackageJson",
|
||||
"description": "Cypress is a next generation front end testing tool built for the modern web",
|
||||
"engines": "test engines",
|
||||
"homepage": "https://cypress.io",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"browser",
|
||||
"cypress",
|
||||
"cypress.io",
|
||||
"e2e",
|
||||
"end-to-end",
|
||||
"integration",
|
||||
"component",
|
||||
"mocks",
|
||||
"runner",
|
||||
"spies",
|
||||
"stubs",
|
||||
"test",
|
||||
"testing",
|
||||
],
|
||||
"license": "MIT",
|
||||
"name": "test",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cypress-io/cypress.git",
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node dist/index.js --exec install",
|
||||
"size": "t="$(npm pack .)"; wc -c "\${t}"; tar tvf "\${t}"; rm "\${t}";",
|
||||
},
|
||||
"types": "types",
|
||||
"version": "x.y.z",
|
||||
}
|
||||
`;
|
||||
@@ -1,5 +1,360 @@
|
||||
exports['shows help for open --foo 1'] = `
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`cli > CYPRESS_INTERNAL_ENV > allows and warns when staging environment 1`] = `
|
||||
" code: 0
|
||||
stdout:
|
||||
-------
|
||||
⚠ Warning: It looks like you're passing CYPRESS_INTERNAL_ENV=staging
|
||||
|
||||
The environment variable "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally.
|
||||
|
||||
Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > CYPRESS_INTERNAL_ENV > catches environment "foo" 1`] = `
|
||||
" code: 11
|
||||
stderr:
|
||||
-------
|
||||
The environment variable with the reserved name "CYPRESS_INTERNAL_ENV" is set.
|
||||
|
||||
Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.
|
||||
|
||||
----------
|
||||
|
||||
CYPRESS_INTERNAL_ENV=foo
|
||||
|
||||
----------
|
||||
|
||||
Platform: xxx
|
||||
Cypress Version: 1.2.3
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > handles non-existent binary 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: not installed
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > reports electron and node message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: 10.10.88
|
||||
Bundled Node version: 11.10.3"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > reports package and binary message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > reports package and binary message with npm log silent 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > reports package and binary message with npm log warn 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress --version > individual package versions > reports package version 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > handles non-existent binary 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: not installed
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > reports electron and node message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: 10.10.88
|
||||
Bundled Node version: 11.10.3"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > reports package and binary message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > reports package and binary message with npm log silent 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > reports package and binary message with npm log warn 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress -v > individual package versions > reports package version 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress cache list > prints explanation when no cache 1`] = `"No cached binary versions were found."`;
|
||||
|
||||
exports[`cli > cypress run > warns with space-separated --spec 1`] = `
|
||||
"⚠ Warning: It looks like you're passing --spec a space-separated list of arguments:
|
||||
|
||||
"a b c d e f g"
|
||||
|
||||
This will work, but it's not recommended.
|
||||
|
||||
If you are trying to pass multiple arguments, separate them with commas instead:
|
||||
cypress run --spec arg1,arg2,arg3
|
||||
|
||||
The most common cause of this warning is using an unescaped glob pattern. If you are
|
||||
trying to pass a glob pattern, escape it using quotes:
|
||||
cypress run --spec "**/*.spec.js""
|
||||
`;
|
||||
|
||||
exports[`cli > cypress run > warns with space-separated --tag 1`] = `
|
||||
"⚠ Warning: It looks like you're passing --tag a space-separated list of arguments:
|
||||
|
||||
"a b c d e f g"
|
||||
|
||||
This will work, but it's not recommended.
|
||||
|
||||
If you are trying to pass multiple arguments, separate them with commas instead:
|
||||
cypress run --tag arg1,arg2,arg3"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > handles non-existent binary 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: not installed
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > reports electron and node message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: 10.10.88
|
||||
Bundled Node version: 11.10.3"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > reports package and binary message 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > reports package and binary message with npm log silent 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > reports package and binary message with npm log warn 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > cypress version > individual package versions > reports package version 1`] = `
|
||||
"Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found"
|
||||
`;
|
||||
|
||||
exports[`cli > help command > shows help 1`] = `
|
||||
"
|
||||
command: bin/cypress help
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > help command > shows help for --help 1`] = `
|
||||
"
|
||||
command: bin/cypress --help
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > help command > shows help for -h 1`] = `
|
||||
"
|
||||
command: bin/cypress -h
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > unknown command > shows usage and exits 1`] = `
|
||||
"
|
||||
command: bin/cypress foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Unknown command "foo"
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > unknown option > shows help 1`] = `
|
||||
"
|
||||
command: bin/cypress open --foo
|
||||
code: 1
|
||||
failed: true
|
||||
@@ -45,12 +400,112 @@ exports['shows help for open --foo 1'] = `
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > unknown option > shows help for cache command - no sub-command 1`] = `
|
||||
"
|
||||
command: bin/cypress cache
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
`
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports['shows help for run --foo 1'] = `
|
||||
exports[`cli > unknown option > shows help for cache command - unknown option --foo 1`] = `
|
||||
"
|
||||
command: bin/cypress cache --foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
error: unknown option: --foo
|
||||
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > unknown option > shows help for cache command - unknown sub-command foo 1`] = `
|
||||
"
|
||||
command: bin/cypress cache foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
error: unknown command: cache foo
|
||||
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`cli > unknown option > shows help for run command 1`] = `
|
||||
"
|
||||
command: bin/cypress run --foo
|
||||
code: 1
|
||||
failed: true
|
||||
@@ -98,377 +553,5 @@ exports['shows help for run --foo 1'] = `
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli unknown option shows help for cache command - unknown option --foo 1'] = `
|
||||
|
||||
command: bin/cypress cache --foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
error: unknown option: --foo
|
||||
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli unknown option shows help for cache command - unknown sub-command foo 1'] = `
|
||||
|
||||
command: bin/cypress cache foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
error: unknown command: cache foo
|
||||
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli unknown option shows help for cache command - no sub-command 1'] = `
|
||||
|
||||
command: bin/cypress cache
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress cache [command]
|
||||
|
||||
Manages the Cypress binary cache
|
||||
|
||||
Options:
|
||||
list list cached binary versions
|
||||
path print the path to the binary cache
|
||||
clear delete all cached binaries
|
||||
prune deletes all cached binaries except for the version currently in
|
||||
use
|
||||
--size Used with the list command to show the sizes of the cached
|
||||
folders
|
||||
-h, --help display help for command
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli help command shows help 1'] = `
|
||||
|
||||
command: bin/cypress help
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli help command shows help for -h 1'] = `
|
||||
|
||||
command: bin/cypress -h
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli help command shows help for --help 1'] = `
|
||||
|
||||
command: bin/cypress --help
|
||||
code: 0
|
||||
failed: false
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli unknown command shows usage and exits 1'] = `
|
||||
|
||||
command: bin/cypress foo
|
||||
code: 1
|
||||
failed: true
|
||||
killed: false
|
||||
signal: null
|
||||
timedOut: false
|
||||
|
||||
stdout:
|
||||
-------
|
||||
Unknown command "foo"
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli CYPRESS_INTERNAL_ENV allows and warns when staging environment 1'] = `
|
||||
code: 0
|
||||
stdout:
|
||||
-------
|
||||
⚠ Warning: It looks like you're passing CYPRESS_INTERNAL_ENV=staging
|
||||
|
||||
The environment variable "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally.
|
||||
|
||||
Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.
|
||||
|
||||
Usage: cypress <command> [options]
|
||||
|
||||
Options:
|
||||
-v, --version prints Cypress version
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
help Shows CLI help and exits
|
||||
version [options] prints Cypress version
|
||||
open [options] Opens Cypress in the interactive GUI.
|
||||
run [options] Runs Cypress tests from the CLI without the GUI
|
||||
install [options] Installs the Cypress executable matching this package's
|
||||
version
|
||||
verify [options] Verifies that Cypress is installed correctly and
|
||||
executable
|
||||
cache [options] Manages the Cypress binary cache
|
||||
info [options] Prints Cypress and system information
|
||||
-------
|
||||
stderr:
|
||||
-------
|
||||
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli CYPRESS_INTERNAL_ENV catches environment "foo" 1'] = `
|
||||
code: 11
|
||||
stderr:
|
||||
-------
|
||||
The environment variable with the reserved name "CYPRESS_INTERNAL_ENV" is set.
|
||||
|
||||
Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.
|
||||
|
||||
----------
|
||||
|
||||
CYPRESS_INTERNAL_ENV=foo
|
||||
|
||||
----------
|
||||
|
||||
Platform: xxx
|
||||
Cypress Version: 1.2.3
|
||||
-------
|
||||
|
||||
`
|
||||
|
||||
exports['cli version and binary version 1'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found
|
||||
`
|
||||
|
||||
exports['cli version and binary version 2'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found
|
||||
`
|
||||
|
||||
exports['cli version with electron and node 1'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: 10.10.88
|
||||
Bundled Node version: 11.10.3
|
||||
`
|
||||
|
||||
exports['cli version and binary version with npm log silent'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found
|
||||
`
|
||||
|
||||
exports['cli version and binary version with npm log warn'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: X.Y.Z
|
||||
Electron version: not found
|
||||
Bundled Node version: not found
|
||||
`
|
||||
|
||||
exports['cli version no binary version 1'] = `
|
||||
Cypress package version: 1.2.3
|
||||
Cypress binary version: not installed
|
||||
Electron version: not found
|
||||
Bundled Node version: not found
|
||||
`
|
||||
|
||||
exports['cli cypress run warns with space-separated --spec 1'] = `
|
||||
[33m⚠[39m Warning: It looks like you're passing --spec a space-separated list of arguments:
|
||||
|
||||
"a b c d e f g"
|
||||
|
||||
This will work, but it's not recommended.
|
||||
|
||||
If you are trying to pass multiple arguments, separate them with commas instead:
|
||||
cypress run --spec arg1,arg2,arg3
|
||||
|
||||
The most common cause of this warning is using an unescaped glob pattern. If you are
|
||||
trying to pass a glob pattern, escape it using quotes:
|
||||
cypress run --spec "**/*.spec.js"
|
||||
`
|
||||
|
||||
exports['cli cypress run warns with space-separated --tag 1'] = `
|
||||
[33m⚠[39m Warning: It looks like you're passing --tag a space-separated list of arguments:
|
||||
|
||||
"a b c d e f g"
|
||||
|
||||
This will work, but it's not recommended.
|
||||
|
||||
If you are trying to pass multiple arguments, separate them with commas instead:
|
||||
cypress run --tag arg1,arg2,arg3
|
||||
`
|
||||
|
||||
exports['prints explanation when no cache'] = `
|
||||
No cached binary versions were found.
|
||||
`
|
||||
"
|
||||
`;
|
||||
8
cli/test/lib/__snapshots__/cypress.spec.ts.snap
Normal file
8
cli/test/lib/__snapshots__/cypress.spec.ts.snap
Normal file
@@ -0,0 +1,8 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`cypress > .run > resolves with contents of tmp file 1`] = `
|
||||
{
|
||||
"code": 0,
|
||||
"failingTests": [],
|
||||
}
|
||||
`;
|
||||
116
cli/test/lib/__snapshots__/errors.spec.ts.snap
Normal file
116
cli/test/lib/__snapshots__/errors.spec.ts.snap
Normal file
@@ -0,0 +1,116 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`errors > .errors.formErrorText > calls solution if a function 1`] = `
|
||||
"description
|
||||
|
||||
a solution
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3"
|
||||
`;
|
||||
|
||||
exports[`errors > .errors.formErrorText > forms full text for invalid display error 1`] = `
|
||||
"Cypress verification failed.
|
||||
|
||||
Cypress failed to start after spawning a new Xvfb server.
|
||||
|
||||
The error logs we received were:
|
||||
|
||||
----------
|
||||
|
||||
current message
|
||||
|
||||
----------
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error above for more detail.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3"
|
||||
`;
|
||||
|
||||
exports[`errors > .errors.formErrorText > returns fully formed text message 1`] = `
|
||||
"Your system is missing the dependency: Xvfb
|
||||
|
||||
Install Xvfb and run Cypress again.
|
||||
|
||||
Read our documentation on dependencies for more information:
|
||||
|
||||
https://on.cypress.io/required-dependencies
|
||||
|
||||
If you are using Docker, we provide containers with all required dependencies installed.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3"
|
||||
`;
|
||||
|
||||
exports[`errors > getError > forms full message and creates Error object 1`] = `
|
||||
{
|
||||
"description": "The Test Runner unexpectedly exited via a exit event with signal SIGKILL",
|
||||
"solution": "Please search Cypress documentation for possible solutions:
|
||||
|
||||
https://on.cypress.io
|
||||
|
||||
Check if there is a GitHub issue describing this crash:
|
||||
|
||||
https://github.com/cypress-io/cypress/issues
|
||||
|
||||
Consider opening a new issue.",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`errors > getError > forms full message and creates Error object 2`] = `
|
||||
"The Test Runner unexpectedly exited via a exit event with signal SIGKILL
|
||||
|
||||
Please search Cypress documentation for possible solutions:
|
||||
|
||||
https://on.cypress.io
|
||||
|
||||
Check if there is a GitHub issue describing this crash:
|
||||
|
||||
https://github.com/cypress-io/cypress/issues
|
||||
|
||||
Consider opening a new issue.
|
||||
|
||||
----------
|
||||
|
||||
Platform: test platform-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3"
|
||||
`;
|
||||
|
||||
exports[`errors > individual > has the following errors 1`] = `
|
||||
[
|
||||
"CYPRESS_RUN_BINARY",
|
||||
"binaryNotExecutable",
|
||||
"childProcessKilled",
|
||||
"failedDownload",
|
||||
"failedUnzip",
|
||||
"failedUnzipWindowsMaxPathLength",
|
||||
"incompatibleHeadlessFlags",
|
||||
"incompatibleTestTypeFlags",
|
||||
"incompatibleTestingTypeAndFlag",
|
||||
"invalidCacheDirectory",
|
||||
"invalidConfigFile",
|
||||
"invalidCypressEnv",
|
||||
"invalidOS",
|
||||
"invalidRunProjectPath",
|
||||
"invalidSmokeTestDisplayError",
|
||||
"invalidTestingType",
|
||||
"missingApp",
|
||||
"missingDependency",
|
||||
"missingXvfb",
|
||||
"nonZeroExitCodeXvfb",
|
||||
"notInstalledCI",
|
||||
"smokeTestFailure",
|
||||
"unexpected",
|
||||
"unknownError",
|
||||
"versionMismatch",
|
||||
]
|
||||
`;
|
||||
17
cli/test/lib/__snapshots__/logger.spec.ts.snap
Normal file
17
cli/test/lib/__snapshots__/logger.spec.ts.snap
Normal file
@@ -0,0 +1,17 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`stripIndent > can be used with nested message 1`] = `
|
||||
"first line
|
||||
|
||||
foo
|
||||
bar
|
||||
|
||||
last line"
|
||||
`;
|
||||
|
||||
exports[`stripIndent > removes indent from literal string 1`] = `
|
||||
"first line
|
||||
second line
|
||||
third line
|
||||
last line"
|
||||
`;
|
||||
43
cli/test/lib/__snapshots__/util.spec.ts.snap
Normal file
43
cli/test/lib/__snapshots__/util.spec.ts.snap
Normal file
@@ -0,0 +1,43 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`util > .normalizeModuleOptions > converts config object 1`] = `
|
||||
{
|
||||
"config": "{"baseUrl":"http://localhost:2000","watchForFileChanges":false}",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > converts environment object 1`] = `
|
||||
{
|
||||
"env": "{"foo":"bar","magicNumber":1234,"host":"kevin.dev.local"}",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > converts reporterOptions object 1`] = `
|
||||
{
|
||||
"reporterOptions": "{"mochaFile":"results/my-test-output.xml","toConsole":true}",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > converts specs array 1`] = `
|
||||
{
|
||||
"spec": "["a","b","c"]",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > does not change other properties 1`] = `
|
||||
{
|
||||
"foo": "bar",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > does not convert spec when string 1`] = `
|
||||
{
|
||||
"spec": "x,y,z",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`util > .normalizeModuleOptions > passes string env unchanged 1`] = `
|
||||
{
|
||||
"env": "foo=bar",
|
||||
}
|
||||
`;
|
||||
56
cli/test/lib/build.spec.ts
Normal file
56
cli/test/lib/build.spec.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import fs from 'fs-extra'
|
||||
import semver from 'semver'
|
||||
import makeUserPackageFile from '../../scripts/build'
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
readJson: vi.fn(),
|
||||
outputJson: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('package.json build', () => {
|
||||
beforeEach(function (): void {
|
||||
// stub package.json in CLI
|
||||
// with a few test props
|
||||
// the rest should come from root package.json file
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.readJson.mockResolvedValue({
|
||||
name: 'test',
|
||||
engines: 'test engines',
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.outputJson.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('has a semver version', async () => {
|
||||
const result = await makeUserPackageFile()
|
||||
|
||||
expect(semver.valid(result.version)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('outputs expected properties', async () => {
|
||||
const result = await makeUserPackageFile()
|
||||
|
||||
expect(result.buildInfo).to.include({ stable: false })
|
||||
expect(result.buildInfo.commitBranch).to.match(/.+/)
|
||||
expect(result.buildInfo.commitSha).to.match(/[a-f0-9]+/)
|
||||
|
||||
const snapshot = {
|
||||
...result,
|
||||
version: 'x.y.z',
|
||||
buildInfo: 'replaced by normalizePackageJson',
|
||||
}
|
||||
|
||||
expect(snapshot).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
import '../spec_helper'
|
||||
import makeUserPackageFile from '../../scripts/build'
|
||||
import snapshot from '../support/snapshot'
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import fs from '../../lib/fs'
|
||||
|
||||
const hasVersion = (json: any): void => {
|
||||
la(is.semver(json.version), 'cannot find version', json)
|
||||
}
|
||||
|
||||
const normalizePackageJson = (o: any): any => {
|
||||
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 (): void {
|
||||
// stub package.json in CLI
|
||||
// with a few test props
|
||||
// the rest should come from root package.json file
|
||||
sinon.stub(fs, 'readJsonAsync').resolves({
|
||||
name: 'test',
|
||||
engines: 'test engines',
|
||||
})
|
||||
|
||||
sinon.stub(fs, 'outputJsonAsync').resolves()
|
||||
})
|
||||
|
||||
it('version', () => {
|
||||
return makeUserPackageFile()
|
||||
.then((result: any) => {
|
||||
hasVersion(result)
|
||||
|
||||
return result
|
||||
})
|
||||
})
|
||||
|
||||
it('outputs expected properties', () => {
|
||||
return makeUserPackageFile()
|
||||
.then(normalizePackageJson)
|
||||
.then(snapshot)
|
||||
})
|
||||
})
|
||||
830
cli/test/lib/cli.spec.ts
Normal file
830
cli/test/lib/cli.spec.ts
Normal file
@@ -0,0 +1,830 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import Debug from 'debug'
|
||||
import execa from 'execa-wrap'
|
||||
import cli from '../../lib/cli'
|
||||
import util from '../../lib/util'
|
||||
import logger from '../../lib/logger'
|
||||
import info from '../../lib/exec/info'
|
||||
import run from '../../lib/exec/run'
|
||||
import open from '../../lib/exec/open'
|
||||
import cache from '../../lib/tasks/cache'
|
||||
import state from '../../lib/tasks/state'
|
||||
import { start as verifyStart } from '../../lib/tasks/verify'
|
||||
import install from '../../lib/tasks/install'
|
||||
|
||||
const debug = Debug('test')
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
logErrorExit1: vi.fn(),
|
||||
pkgBuildInfo: vi.fn(),
|
||||
pkgVersion: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/exec/run', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/exec/open', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/exec/info', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/tasks/install', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/tasks/verify', () => {
|
||||
return {
|
||||
start: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/tasks/cache', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
list: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getBinaryDir: vi.fn(),
|
||||
getBinaryPkgAsync: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const flushPromises = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 250)
|
||||
})
|
||||
}
|
||||
|
||||
describe('cli', () => {
|
||||
const binaryDir = '/binary/dir'
|
||||
let exec: (args: string) => Promise<any>
|
||||
let processExitSpy: ReturnType<typeof vi.spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.clearAllMocks()
|
||||
|
||||
logger.reset()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
processExitSpy = vi.spyOn(process, 'exit').mockReturnValue(null)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.logErrorExit1.mockReturnValue(null)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue({ stable: true })
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getBinaryDir.mockReturnValue(binaryDir)
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.9.8',
|
||||
electronNodeVersion: '7.7.7',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
exec = (args: string): any => {
|
||||
const cliArgs = `node test ${args}`.split(' ')
|
||||
|
||||
debug('calling cli.init with: %o', cliArgs)
|
||||
|
||||
return cli.init(cliArgs)
|
||||
}
|
||||
})
|
||||
|
||||
describe('unknown option', () => {
|
||||
// note it shows help for that specific command
|
||||
it('shows help', async () => {
|
||||
const result = await execa('bin/cypress', ['open', '--foo'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for run command', async () => {
|
||||
const result = await execa('bin/cypress', ['run', '--foo'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for cache command - unknown option --foo', async () => {
|
||||
const result = await execa('bin/cypress', ['cache', '--foo'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for cache command - unknown sub-command foo', async () => {
|
||||
const result = await execa('bin/cypress', ['cache', 'foo'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for cache command - no sub-command', async () => {
|
||||
const result = await execa('bin/cypress', ['cache'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('help command', () => {
|
||||
it('shows help', async () => {
|
||||
const result = await execa('bin/cypress', ['help'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for -h', async () => {
|
||||
const result = await execa('bin/cypress', ['-h'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('shows help for --help', async () => {
|
||||
const result = await execa('bin/cypress', ['--help'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('unknown command', () => {
|
||||
it('shows usage and exits', async () => {
|
||||
const result = await execa('bin/cypress', ['foo'])
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('CYPRESS_INTERNAL_ENV', () => {
|
||||
/**
|
||||
* Replaces line "Platform: ..." with "Platform: xxx"
|
||||
* @param {string} s
|
||||
*/
|
||||
const replacePlatform = (s: string): string => {
|
||||
return s.replace(/Platform: .+/, 'Platform: xxx')
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces line "Cypress Version: ..." with "Cypress Version: 1.2.3"
|
||||
* @param {string} s
|
||||
*/
|
||||
const replaceCypressVersion = (s: string): string => {
|
||||
return s.replace(/Cypress Version: .+/, 'Cypress Version: 1.2.3')
|
||||
}
|
||||
|
||||
const sanitizePlatform = (text: any): any => {
|
||||
return text
|
||||
// @ts-expect-error
|
||||
.split(os.eol)
|
||||
.map(replacePlatform)
|
||||
.map(replaceCypressVersion)
|
||||
// @ts-expect-error
|
||||
.join(os.eol)
|
||||
}
|
||||
|
||||
it('allows and warns when staging environment', async () => {
|
||||
const options = {
|
||||
env: {
|
||||
CYPRESS_INTERNAL_ENV: 'staging',
|
||||
},
|
||||
filter: ['code', 'stderr', 'stdout'],
|
||||
}
|
||||
|
||||
const result = await execa('bin/cypress', ['help'], options)
|
||||
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('catches environment "foo"', async () => {
|
||||
const options = {
|
||||
env: {
|
||||
CYPRESS_INTERNAL_ENV: 'foo',
|
||||
},
|
||||
// we are only interested in the exit code
|
||||
filter: ['code', 'stderr'],
|
||||
}
|
||||
|
||||
const result = await execa('bin/cypress', ['help'], options)
|
||||
|
||||
expect(sanitizePlatform(result)).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
const versionCommands = ['--version', '-v', 'version']
|
||||
|
||||
versionCommands.forEach((versionCommand: string) => {
|
||||
describe(`cypress ${versionCommand}`, () => {
|
||||
describe('individual package versions', () => {
|
||||
it('reports just the package version', async () => {
|
||||
await exec(`${versionCommand} --component package`)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toEqual('1.2.3')
|
||||
})
|
||||
|
||||
it('reports just the binary version', async () => {
|
||||
await exec(`${versionCommand} --component binary`)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toEqual('X.Y.Z')
|
||||
})
|
||||
|
||||
it('reports just the electron version', async () => {
|
||||
await exec(`${versionCommand} --component electron`)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toEqual('10.9.8')
|
||||
})
|
||||
|
||||
it('reports just the bundled Node version', async () => {
|
||||
await exec(`${versionCommand} --component node`)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toEqual('7.7.7')
|
||||
})
|
||||
|
||||
it('handles not found bundled Node version', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.9.8',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(`${versionCommand} --component node`)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toEqual('not found')
|
||||
})
|
||||
|
||||
it('reports package version', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('reports package and binary message', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('reports electron and node message', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.10.88',
|
||||
electronNodeVersion: '11.10.3',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('reports package and binary message with npm log silent', async () => {
|
||||
vi.stubEnv('npm_config_loglevel', 'silent')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('reports package and binary message with npm log warn', async () => {
|
||||
vi.stubEnv('npm_config_loglevel', 'warn')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: 'X.Y.Z',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('handles non-existent binary', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return null
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
await exec(versionCommand)
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress run', () => {
|
||||
beforeEach(() => {
|
||||
//@ts-expect-error - mockResolvedValue
|
||||
run.start.mockResolvedValue(0)
|
||||
})
|
||||
|
||||
it('calls run.start with options + exits with code', async () => {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
run.start.mockResolvedValue(10)
|
||||
|
||||
await exec('run')
|
||||
await flushPromises()
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(10)
|
||||
})
|
||||
|
||||
it('run.start with options + catches errors', async () => {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
run.start.mockRejectedValue(err)
|
||||
|
||||
await exec('run')
|
||||
await flushPromises()
|
||||
|
||||
expect(util.logErrorExit1).toHaveBeenCalledWith(err)
|
||||
})
|
||||
|
||||
it('calls run with port', async () => {
|
||||
await exec('run --port 7878')
|
||||
expect(run.start).toBeCalledWith({ port: '7878' })
|
||||
})
|
||||
|
||||
it('calls run with port with -p arg', async () => {
|
||||
await exec('run -p 8989')
|
||||
expect(run.start).toBeCalledWith({ port: '8989' })
|
||||
})
|
||||
|
||||
it('calls run with env variables', async () => {
|
||||
await exec('run --env foo=bar,host=http://localhost:8888')
|
||||
expect(run.start).toBeCalledWith({
|
||||
env: 'foo=bar,host=http://localhost:8888',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with config', async () => {
|
||||
await exec('run --config watchForFileChanges=false,baseUrl=localhost')
|
||||
expect(run.start).toBeCalledWith({
|
||||
config: 'watchForFileChanges=false,baseUrl=localhost',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with key', async () => {
|
||||
await exec('run --key asdf')
|
||||
expect(run.start).toBeCalledWith({ key: 'asdf' })
|
||||
})
|
||||
|
||||
it('calls run with --record', async () => {
|
||||
await exec('run --record')
|
||||
expect(run.start).toBeCalledWith({ record: true })
|
||||
})
|
||||
|
||||
it('calls run with --record false', async () => {
|
||||
await exec('run --record false')
|
||||
expect(run.start).toBeCalledWith({ record: false })
|
||||
})
|
||||
|
||||
it('calls run with relative --project folder', async () => {
|
||||
await exec('run --project foo/bar')
|
||||
expect(run.start).toBeCalledWith({ project: 'foo/bar' })
|
||||
})
|
||||
|
||||
it('calls run with absolute --project folder', async () => {
|
||||
await exec('run --project /tmp/foo/bar')
|
||||
expect(run.start).toBeCalledWith({ project: '/tmp/foo/bar' })
|
||||
})
|
||||
|
||||
it('calls run with headed', async () => {
|
||||
await exec('run --headed')
|
||||
expect(run.start).toBeCalledWith({ headed: true })
|
||||
})
|
||||
|
||||
it('calls run with --no-exit', async () => {
|
||||
await exec('run --no-exit')
|
||||
expect(run.start).toBeCalledWith({ exit: false })
|
||||
})
|
||||
|
||||
it('calls run with --parallel', async () => {
|
||||
await exec('run --parallel')
|
||||
expect(run.start).toBeCalledWith({ parallel: true })
|
||||
})
|
||||
|
||||
it('calls run with --ci-build-id', async () => {
|
||||
await exec('run --ci-build-id 123')
|
||||
expect(run.start).toBeCalledWith({ ciBuildId: '123' })
|
||||
})
|
||||
|
||||
it('calls run with --group', async () => {
|
||||
await exec('run --group staging')
|
||||
expect(run.start).toBeCalledWith({ group: 'staging' })
|
||||
})
|
||||
|
||||
it('calls run with spec', async () => {
|
||||
await exec('run --spec cypress/integration/foo_spec.js')
|
||||
expect(run.start).toBeCalledWith({
|
||||
spec: 'cypress/integration/foo_spec.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with space-separated --spec', async () => {
|
||||
await exec('run --spec a b c d e f g')
|
||||
expect(run.start).toBeCalledWith({ spec: 'a,b,c,d,e,f,g' })
|
||||
|
||||
await exec('run --dev bang --spec foo bar baz -P ./')
|
||||
expect(run.start).toBeCalledWith(expect.objectContaining({ spec: 'foo,bar,baz' }))
|
||||
})
|
||||
|
||||
it('warns with space-separated --spec', async () => {
|
||||
await exec('run --spec a b c d e f g --dev')
|
||||
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('calls run with --tag', async () => {
|
||||
await exec('run --tag nightly')
|
||||
expect(run.start).toBeCalledWith({ tag: 'nightly' })
|
||||
})
|
||||
|
||||
it('calls run comma-separated --tag', async () => {
|
||||
await exec('run --tag nightly,staging')
|
||||
expect(run.start).toBeCalledWith({ tag: 'nightly,staging' })
|
||||
})
|
||||
|
||||
it('does not remove double quotes from --tag', async () => {
|
||||
// I think it is a good idea to lock down this behavior
|
||||
// to make sure we either preserve it or change it in the future
|
||||
await exec('run --tag "nightly"')
|
||||
expect(run.start).toBeCalledWith({ tag: '"nightly"' })
|
||||
})
|
||||
|
||||
it('calls run comma-separated --spec', async () => {
|
||||
await exec('run --spec main_spec.js,view_spec.js')
|
||||
expect(run.start).toBeCalledWith({ spec: 'main_spec.js,view_spec.js' })
|
||||
})
|
||||
|
||||
it('calls run with space-separated --tag', async () => {
|
||||
await exec('run --tag a b c d e f g')
|
||||
expect(run.start).toBeCalledWith({ tag: 'a,b,c,d,e,f,g' })
|
||||
|
||||
await exec('run --dev bang --tag foo bar baz -P ./')
|
||||
expect(run.start).toBeCalledWith(expect.objectContaining({ tag: 'foo,bar,baz' }))
|
||||
})
|
||||
|
||||
it('warns with space-separated --tag', async () => {
|
||||
await exec('run --tag a b c d e f g --dev')
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('calls run with space-separated --tag and --spec', async () => {
|
||||
await exec('run --tag a b c d e f g --spec h i j k l')
|
||||
expect(run.start).toBeCalledWith({ tag: 'a,b,c,d,e,f,g', spec: 'h,i,j,k,l' })
|
||||
|
||||
await exec('run --dev bang --tag foo bar baz -P ./ --spec fizz buzz --headed false')
|
||||
expect(run.start).toBeCalledWith(expect.objectContaining({ tag: 'foo,bar,baz', spec: 'fizz,buzz' }))
|
||||
})
|
||||
|
||||
it('removes stray double quotes from --ci-build-id and --group', async () => {
|
||||
await exec('run --ci-build-id "123" --group "staging"')
|
||||
expect(run.start).toBeCalledWith({ ciBuildId: '123', group: 'staging' })
|
||||
})
|
||||
|
||||
it('calls run with --auto-cancel-after-failures', async () => {
|
||||
await exec('run --auto-cancel-after-failures 4')
|
||||
expect(run.start).toBeCalledWith({ autoCancelAfterFailures: '4' })
|
||||
})
|
||||
|
||||
it('calls run with --auto-cancel-after-failures with false', async () => {
|
||||
await exec('run --auto-cancel-after-failures false')
|
||||
expect(run.start).toBeCalledWith({ autoCancelAfterFailures: 'false' })
|
||||
})
|
||||
|
||||
it('calls run with --runner-ui', async () => {
|
||||
await exec('run --runner-ui')
|
||||
expect(run.start).toBeCalledWith({ runnerUi: true })
|
||||
})
|
||||
|
||||
it('calls run with --no-runner-ui', async () => {
|
||||
await exec('run --no-runner-ui')
|
||||
expect(run.start).toBeCalledWith({ runnerUi: false })
|
||||
})
|
||||
|
||||
describe('component-testing', () => {
|
||||
it('passes to run.start the correct args for component-testing', async () => {
|
||||
await exec('run --component --dev')
|
||||
|
||||
expect(run.start).toHaveBeenNthCalledWith(1, {
|
||||
component: true,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress open', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
open.start.mockResolvedValue(0)
|
||||
})
|
||||
|
||||
it('calls open.start with relative --project folder', async () => {
|
||||
await exec('open --project foo/bar')
|
||||
expect(open.start).toBeCalledWith({ project: 'foo/bar' })
|
||||
})
|
||||
|
||||
it('calls open.start with absolute --project folder', async () => {
|
||||
await exec('open --project /tmp/foo/bar')
|
||||
expect(open.start).toBeCalledWith({ project: '/tmp/foo/bar' })
|
||||
})
|
||||
|
||||
it('calls open.start with options', async () => {
|
||||
await exec('open --port 7878')
|
||||
expect(open.start).toBeCalledWith({ port: '7878' })
|
||||
})
|
||||
|
||||
it('calls open.start with global', async () => {
|
||||
await exec('open --port 7878 --global')
|
||||
expect(open.start).toBeCalledWith({ port: '7878', global: true })
|
||||
})
|
||||
|
||||
it('calls open.start + catches errors', async () => {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
open.start.mockRejectedValue(err)
|
||||
|
||||
await exec('open --port 7878')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(util.logErrorExit1).toHaveBeenCalledWith(err)
|
||||
})
|
||||
|
||||
describe('component-testing', () => {
|
||||
it('passes to open.start the correct args for component-testing', async () => {
|
||||
await exec('open --component --dev')
|
||||
await flushPromises()
|
||||
|
||||
expect(open.start).toHaveBeenNthCalledWith(1, {
|
||||
component: true,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress install', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
install.start.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('calls install.start without forcing', async () => {
|
||||
await exec('install')
|
||||
expect(install.start).not.toBeCalledWith({ force: true })
|
||||
})
|
||||
|
||||
it('calls install.start with force: true when passed', async () => {
|
||||
await exec('install --force')
|
||||
expect(install.start).toBeCalledWith({ force: true })
|
||||
})
|
||||
|
||||
it('install calls install.start + catches errors', async () => {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
install.start.mockRejectedValue(err)
|
||||
|
||||
await exec('install')
|
||||
await flushPromises()
|
||||
|
||||
expect(util.logErrorExit1).toHaveBeenCalledWith(err)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress verify', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
verifyStart.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('verify calls verifyStart with force: true', async () => {
|
||||
await exec('verify')
|
||||
expect(verifyStart).toBeCalledWith({
|
||||
force: true,
|
||||
welcomeMessage: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('verify calls verifyStart + catches errors', async () => {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
verifyStart.mockRejectedValue(err)
|
||||
|
||||
await exec('verify')
|
||||
await flushPromises()
|
||||
|
||||
expect(util.logErrorExit1).toHaveBeenCalledWith(err)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress info', () => {
|
||||
beforeEach(() => {
|
||||
info.start.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('calls info start', async () => {
|
||||
await exec('info')
|
||||
expect(info.start).toBeCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('cypress cache list', () => {
|
||||
it('prints explanation when no cache', async () => {
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = 'ENOENT'
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
cache.list.mockRejectedValue(err)
|
||||
|
||||
await exec('cache list')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(logger.print()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('catches rejection and exits', async () => {
|
||||
const err = new Error('cache list failed badly')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
cache.list.mockRejectedValue(err)
|
||||
|
||||
await exec('cache list')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(util.logErrorExit1).toHaveBeenCalledWith(err)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,692 +0,0 @@
|
||||
import '../spec_helper'
|
||||
import os from 'os'
|
||||
import snapshot from '../support/snapshot'
|
||||
import Debug from 'debug'
|
||||
import execa from 'execa-wrap'
|
||||
import mockedEnv from 'mocked-env'
|
||||
import { expect } from 'chai'
|
||||
import mochaBanner from 'mocha-banner'
|
||||
|
||||
const debug = Debug('test')
|
||||
|
||||
// Import modules dynamically to handle template literal paths
|
||||
import cli from '../../lib/cli'
|
||||
import util from '../../lib/util'
|
||||
import logger from '../../lib/logger'
|
||||
import info from '../../lib/exec/info'
|
||||
import run from '../../lib/exec/run'
|
||||
import open from '../../lib/exec/open'
|
||||
import cache from '../../lib/tasks/cache'
|
||||
import state from '../../lib/tasks/state'
|
||||
import verify from '../../lib/tasks/verify'
|
||||
import install from '../../lib/tasks/install'
|
||||
import spawn from '../../lib/exec/spawn'
|
||||
|
||||
describe('cli', () => {
|
||||
mochaBanner.register()
|
||||
|
||||
beforeEach(function (): void {
|
||||
logger.reset()
|
||||
// @ts-expect-error
|
||||
sinon.stub(process, 'exit').returns(null)
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
// @ts-expect-error
|
||||
sinon.stub(util, 'logErrorExit1').returns(null)
|
||||
|
||||
sinon.stub(util, 'pkgBuildInfo').returns({ stable: true })
|
||||
|
||||
;(this as any).exec = (args: string): any => {
|
||||
const cliArgs = `node test ${args}`.split(' ')
|
||||
|
||||
debug('calling cli.init with: %o', cliArgs)
|
||||
|
||||
return cli.init(cliArgs)
|
||||
}
|
||||
})
|
||||
|
||||
context('unknown option', () => {
|
||||
// note it shows help for that specific command
|
||||
it('shows help', () => {
|
||||
return execa('bin/cypress', ['open', '--foo']).then((result: any) => {
|
||||
snapshot('shows help for open --foo 1', result)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows help for run command', () => {
|
||||
return execa('bin/cypress', ['run', '--foo']).then((result: any) => {
|
||||
snapshot('shows help for run --foo 1', result)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows help for cache command - unknown option --foo', () => {
|
||||
return execa('bin/cypress', ['cache', '--foo']).then(snapshot)
|
||||
})
|
||||
|
||||
it('shows help for cache command - unknown sub-command foo', () => {
|
||||
return execa('bin/cypress', ['cache', 'foo']).then(snapshot)
|
||||
})
|
||||
|
||||
it('shows help for cache command - no sub-command', () => {
|
||||
return execa('bin/cypress', ['cache']).then(snapshot)
|
||||
})
|
||||
})
|
||||
|
||||
context('help command', () => {
|
||||
it('shows help', () => {
|
||||
return execa('bin/cypress', ['help']).then(snapshot)
|
||||
})
|
||||
|
||||
it('shows help for -h', () => {
|
||||
return execa('bin/cypress', ['-h']).then(snapshot)
|
||||
})
|
||||
|
||||
it('shows help for --help', () => {
|
||||
return execa('bin/cypress', ['--help']).then(snapshot)
|
||||
})
|
||||
})
|
||||
|
||||
context('unknown command', () => {
|
||||
it('shows usage and exits', () => {
|
||||
return execa('bin/cypress', ['foo']).then(snapshot)
|
||||
})
|
||||
})
|
||||
|
||||
context('CYPRESS_INTERNAL_ENV', () => {
|
||||
/**
|
||||
* Replaces line "Platform: ..." with "Platform: xxx"
|
||||
* @param {string} s
|
||||
*/
|
||||
const replacePlatform = (s: string): string => {
|
||||
return s.replace(/Platform: .+/, 'Platform: xxx')
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces line "Cypress Version: ..." with "Cypress Version: 1.2.3"
|
||||
* @param {string} s
|
||||
*/
|
||||
const replaceCypressVersion = (s: string): string => {
|
||||
return s.replace(/Cypress Version: .+/, 'Cypress Version: 1.2.3')
|
||||
}
|
||||
|
||||
const sanitizePlatform = (text: any): any => {
|
||||
return text
|
||||
// @ts-expect-error
|
||||
.split(os.eol)
|
||||
.map(replacePlatform)
|
||||
.map(replaceCypressVersion)
|
||||
// @ts-expect-error
|
||||
.join(os.eol)
|
||||
}
|
||||
|
||||
it('allows and warns when staging environment', () => {
|
||||
const options = {
|
||||
env: {
|
||||
CYPRESS_INTERNAL_ENV: 'staging',
|
||||
},
|
||||
filter: ['code', 'stderr', 'stdout'],
|
||||
}
|
||||
|
||||
return execa('bin/cypress', ['help'], options).then(snapshot)
|
||||
})
|
||||
|
||||
it('catches environment "foo"', () => {
|
||||
const options = {
|
||||
env: {
|
||||
CYPRESS_INTERNAL_ENV: 'foo',
|
||||
},
|
||||
// we are only interested in the exit code
|
||||
filter: ['code', 'stderr'],
|
||||
}
|
||||
|
||||
return execa('bin/cypress', ['help'], options)
|
||||
.then(sanitizePlatform)
|
||||
.then(snapshot)
|
||||
})
|
||||
})
|
||||
|
||||
;['--version', '-v', 'version'].forEach((versionCommand: string) => {
|
||||
context(`cypress ${versionCommand}`, () => {
|
||||
let restoreEnv: any
|
||||
|
||||
afterEach(() => {
|
||||
if (restoreEnv) {
|
||||
restoreEnv()
|
||||
restoreEnv = null
|
||||
}
|
||||
})
|
||||
|
||||
const binaryDir = '/binary/dir'
|
||||
|
||||
beforeEach((): void => {
|
||||
sinon.stub(state, 'getBinaryDir').returns(binaryDir)
|
||||
})
|
||||
|
||||
describe('individual package versions', () => {
|
||||
beforeEach((): void => {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon
|
||||
.stub(state, 'getBinaryPkgAsync')
|
||||
.withArgs(binaryDir)
|
||||
.resolves({
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.9.8',
|
||||
electronNodeVersion: '7.7.7',
|
||||
})
|
||||
})
|
||||
|
||||
it('reports just the package version', function (done) {
|
||||
(this as any).exec(`${versionCommand} --component package`)
|
||||
|
||||
;(process.exit as any).callsFake((exitCode: any) => {
|
||||
expect(logger.print()).to.equal('1.2.3')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports just the binary version', function (done) {
|
||||
(this as any).exec(`${versionCommand} --component binary`)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
expect(logger.print()).to.equal('X.Y.Z')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports just the electron version', function (done) {
|
||||
(this as any).exec(`${versionCommand} --component electron`)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
expect(logger.print()).to.equal('10.9.8')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports just the bundled Node version', function (done) {
|
||||
(this as any).exec(`${versionCommand} --component node`)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
expect(logger.print()).to.equal('7.7.7')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles not found bundled Node version', function (done) {
|
||||
state.getBinaryPkgAsync
|
||||
.withArgs(binaryDir)
|
||||
.resolves({
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.9.8',
|
||||
})
|
||||
|
||||
;(this as any).exec(`${versionCommand} --component node`)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
expect(logger.print()).to.equal('not found')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('reports package version', function (done) {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon
|
||||
.stub(state, 'getBinaryPkgAsync')
|
||||
.withArgs(binaryDir)
|
||||
.resolves({
|
||||
version: 'X.Y.Z',
|
||||
})
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
snapshot('cli version and binary version 1', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports package and binary message', function (done) {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
snapshot('cli version and binary version 2', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports electron and node message', function (done) {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves({
|
||||
version: 'X.Y.Z',
|
||||
electronVersion: '10.10.88',
|
||||
electronNodeVersion: '11.10.3',
|
||||
})
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
snapshot('cli version with electron and node 1', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports package and binary message with npm log silent', function (done) {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'silent',
|
||||
})
|
||||
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
// should not be empty!
|
||||
snapshot('cli version and binary version with npm log silent', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('reports package and binary message with npm log warn', function (done) {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'warn',
|
||||
})
|
||||
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves({
|
||||
version: 'X.Y.Z',
|
||||
})
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
// should not be empty!
|
||||
snapshot('cli version and binary version with npm log warn', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles non-existent binary', function (done) {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves(null)
|
||||
|
||||
;(this as any).exec(versionCommand)
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
snapshot('cli version no binary version 1', logger.print(), { allowSharedSnapshot: true })
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress run', () => {
|
||||
beforeEach((): void => {
|
||||
sinon.stub(run, 'start').resolves(0)
|
||||
sinon.stub(util, 'exit').withArgs(0)
|
||||
})
|
||||
|
||||
it('calls run.start with options + exits with code', function (done) {
|
||||
// @ts-expect-error
|
||||
run.start.resolves(10)
|
||||
|
||||
;(this as any).exec('run')
|
||||
|
||||
// @ts-expect-error
|
||||
util.exit.callsFake((code: number) => {
|
||||
expect(code).to.eq(10)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('run.start with options + catches errors', function (done) {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error
|
||||
run.start.rejects(err)
|
||||
|
||||
;(this as any).exec('run')
|
||||
|
||||
// @ts-expect-error
|
||||
util.logErrorExit1.callsFake((e: Error) => {
|
||||
expect(e).to.eq(err)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with port', function (): void {
|
||||
(this as any).exec('run --port 7878')
|
||||
expect(run.start).to.be.calledWith({ port: '7878' })
|
||||
})
|
||||
|
||||
it('calls run with port with -p arg', function (): void {
|
||||
(this as any).exec('run -p 8989')
|
||||
expect(run.start).to.be.calledWith({ port: '8989' })
|
||||
})
|
||||
|
||||
it('calls run with env variables', function (): void {
|
||||
(this as any).exec('run --env foo=bar,host=http://localhost:8888')
|
||||
expect(run.start).to.be.calledWith({
|
||||
env: 'foo=bar,host=http://localhost:8888',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with config', function (): void {
|
||||
(this as any).exec('run --config watchForFileChanges=false,baseUrl=localhost')
|
||||
expect(run.start).to.be.calledWith({
|
||||
config: 'watchForFileChanges=false,baseUrl=localhost',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with key', function (): void {
|
||||
(this as any).exec('run --key asdf')
|
||||
expect(run.start).to.be.calledWith({ key: 'asdf' })
|
||||
})
|
||||
|
||||
it('calls run with --record', function (): void {
|
||||
(this as any).exec('run --record')
|
||||
expect(run.start).to.be.calledWith({ record: true })
|
||||
})
|
||||
|
||||
it('calls run with --record false', function (): void {
|
||||
(this as any).exec('run --record false')
|
||||
expect(run.start).to.be.calledWith({ record: false })
|
||||
})
|
||||
|
||||
it('calls run with relative --project folder', function (): void {
|
||||
(this as any).exec('run --project foo/bar')
|
||||
expect(run.start).to.be.calledWith({ project: 'foo/bar' })
|
||||
})
|
||||
|
||||
it('calls run with absolute --project folder', function (): void {
|
||||
(this as any).exec('run --project /tmp/foo/bar')
|
||||
expect(run.start).to.be.calledWith({ project: '/tmp/foo/bar' })
|
||||
})
|
||||
|
||||
it('calls run with headed', function (): void {
|
||||
(this as any).exec('run --headed')
|
||||
expect(run.start).to.be.calledWith({ headed: true })
|
||||
})
|
||||
|
||||
it('calls run with --no-exit', function (): void {
|
||||
(this as any).exec('run --no-exit')
|
||||
expect(run.start).to.be.calledWith({ exit: false })
|
||||
})
|
||||
|
||||
it('calls run with --parallel', function (): void {
|
||||
(this as any).exec('run --parallel')
|
||||
expect(run.start).to.be.calledWith({ parallel: true })
|
||||
})
|
||||
|
||||
it('calls run with --ci-build-id', function (): void {
|
||||
(this as any).exec('run --ci-build-id 123')
|
||||
expect(run.start).to.be.calledWith({ ciBuildId: '123' })
|
||||
})
|
||||
|
||||
it('calls run with --group', function (): void {
|
||||
(this as any).exec('run --group staging')
|
||||
expect(run.start).to.be.calledWith({ group: 'staging' })
|
||||
})
|
||||
|
||||
it('calls run with spec', function (): void {
|
||||
(this as any).exec('run --spec cypress/integration/foo_spec.js')
|
||||
expect(run.start).to.be.calledWith({
|
||||
spec: 'cypress/integration/foo_spec.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run with space-separated --spec', function (): void {
|
||||
(this as any).exec('run --spec a b c d e f g')
|
||||
expect(run.start).to.be.calledWith({ spec: 'a,b,c,d,e,f,g' })
|
||||
|
||||
;(this as any).exec('run --dev bang --spec foo bar baz -P ./')
|
||||
expect(run.start).to.be.calledWithMatch({ spec: 'foo,bar,baz' })
|
||||
})
|
||||
|
||||
it('warns with space-separated --spec', function (done) {
|
||||
sinon.spy(logger, 'warn')
|
||||
|
||||
;(this as any).exec('run --spec a b c d e f g --dev')
|
||||
snapshot(logger.warn.getCall(0).args[0])
|
||||
done()
|
||||
})
|
||||
|
||||
it('calls run with --tag', function (): void {
|
||||
(this as any).exec('run --tag nightly')
|
||||
expect(run.start).to.be.calledWith({ tag: 'nightly' })
|
||||
})
|
||||
|
||||
it('calls run comma-separated --tag', function (): void {
|
||||
(this as any).exec('run --tag nightly,staging')
|
||||
expect(run.start).to.be.calledWith({ tag: 'nightly,staging' })
|
||||
})
|
||||
|
||||
it('does not remove double quotes from --tag', function (): void {
|
||||
// I think it is a good idea to lock down this behavior
|
||||
// to make sure we either preserve it or change it in the future
|
||||
(this as any).exec('run --tag "nightly"')
|
||||
expect(run.start).to.be.calledWith({ tag: '"nightly"' })
|
||||
})
|
||||
|
||||
it('calls run comma-separated --spec', function (): void {
|
||||
(this as any).exec('run --spec main_spec.js,view_spec.js')
|
||||
expect(run.start).to.be.calledWith({ spec: 'main_spec.js,view_spec.js' })
|
||||
})
|
||||
|
||||
it('calls run with space-separated --tag', function (): void {
|
||||
(this as any).exec('run --tag a b c d e f g')
|
||||
expect(run.start).to.be.calledWith({ tag: 'a,b,c,d,e,f,g' })
|
||||
|
||||
;(this as any).exec('run --dev bang --tag foo bar baz -P ./')
|
||||
expect(run.start).to.be.calledWithMatch({ tag: 'foo,bar,baz' })
|
||||
})
|
||||
|
||||
it('warns with space-separated --tag', function (done) {
|
||||
sinon.spy(logger, 'warn')
|
||||
|
||||
;(this as any).exec('run --tag a b c d e f g --dev')
|
||||
snapshot(logger.warn.getCall(0).args[0])
|
||||
done()
|
||||
})
|
||||
|
||||
it('calls run with space-separated --tag and --spec', function (): void {
|
||||
(this as any).exec('run --tag a b c d e f g --spec h i j k l')
|
||||
expect(run.start).to.be.calledWith({ tag: 'a,b,c,d,e,f,g', spec: 'h,i,j,k,l' })
|
||||
|
||||
;(this as any).exec('run --dev bang --tag foo bar baz -P ./ --spec fizz buzz --headed false')
|
||||
expect(run.start).to.be.calledWithMatch({ tag: 'foo,bar,baz', spec: 'fizz,buzz' })
|
||||
})
|
||||
|
||||
it('removes stray double quotes from --ci-build-id and --group', function (): void {
|
||||
(this as any).exec('run --ci-build-id "123" --group "staging"')
|
||||
expect(run.start).to.be.calledWith({ ciBuildId: '123', group: 'staging' })
|
||||
})
|
||||
|
||||
it('calls run with --auto-cancel-after-failures', function (): void {
|
||||
(this as any).exec('run --auto-cancel-after-failures 4')
|
||||
expect(run.start).to.be.calledWith({ autoCancelAfterFailures: '4' })
|
||||
})
|
||||
|
||||
it('calls run with --auto-cancel-after-failures with false', function (): void {
|
||||
(this as any).exec('run --auto-cancel-after-failures false')
|
||||
expect(run.start).to.be.calledWith({ autoCancelAfterFailures: 'false' })
|
||||
})
|
||||
|
||||
it('calls run with --runner-ui', function (): void {
|
||||
(this as any).exec('run --runner-ui')
|
||||
expect(run.start).to.be.calledWith({ runnerUi: true })
|
||||
})
|
||||
|
||||
it('calls run with --no-runner-ui', function (): void {
|
||||
(this as any).exec('run --no-runner-ui')
|
||||
expect(run.start).to.be.calledWith({ runnerUi: false })
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress open', () => {
|
||||
beforeEach((): void => {
|
||||
sinon.stub(open, 'start').resolves(0)
|
||||
})
|
||||
|
||||
it('calls open.start with relative --project folder', function (): void {
|
||||
(this as any).exec('open --project foo/bar')
|
||||
expect(open.start).to.be.calledWith({ project: 'foo/bar' })
|
||||
})
|
||||
|
||||
it('calls open.start with absolute --project folder', function (): void {
|
||||
(this as any).exec('open --project /tmp/foo/bar')
|
||||
expect(open.start).to.be.calledWith({ project: '/tmp/foo/bar' })
|
||||
})
|
||||
|
||||
it('calls open.start with options', function (): void {
|
||||
// sinon.stub(open, 'start').resolves()
|
||||
(this as any).exec('open --port 7878')
|
||||
expect(open.start).to.be.calledWith({ port: '7878' })
|
||||
})
|
||||
|
||||
it('calls open.start with global', function (): void {
|
||||
// sinon.stub(open, 'start').resolves()
|
||||
(this as any).exec('open --port 7878 --global')
|
||||
expect(open.start).to.be.calledWith({ port: '7878', global: true })
|
||||
})
|
||||
|
||||
it('calls open.start + catches errors', function (done) {
|
||||
const err = new Error('foo')
|
||||
|
||||
// @ts-expect-error
|
||||
open.start.rejects(err)
|
||||
|
||||
;(this as any).exec('open --port 7878')
|
||||
|
||||
// @ts-expect-error
|
||||
util.logErrorExit1.callsFake((e: Error) => {
|
||||
expect(e).to.eq(err)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress install', () => {
|
||||
it('calls install.start without forcing', function (): void {
|
||||
sinon.stub(install, 'start').resolves()
|
||||
|
||||
;(this as any).exec('install')
|
||||
expect(install.start).not.to.be.calledWith({ force: true })
|
||||
})
|
||||
|
||||
it('calls install.start with force: true when passed', function (): void {
|
||||
sinon.stub(install, 'start').resolves()
|
||||
|
||||
;(this as any).exec('install --force')
|
||||
expect(install.start).to.be.calledWith({ force: true })
|
||||
})
|
||||
|
||||
it('install calls install.start + catches errors', function (done) {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(install, 'start').rejects(err)
|
||||
|
||||
;(this as any).exec('install')
|
||||
|
||||
// @ts-expect-error
|
||||
util.logErrorExit1.callsFake((e: Error) => {
|
||||
expect(e).to.eq(err)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress verify', () => {
|
||||
it('verify calls verify.start with force: true', function (): void {
|
||||
sinon.stub(verify, 'start').resolves()
|
||||
|
||||
;(this as any).exec('verify')
|
||||
expect(verify.start).to.be.calledWith({
|
||||
force: true,
|
||||
welcomeMessage: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('verify calls verify.start + catches errors', function (done) {
|
||||
const err = new Error('foo')
|
||||
|
||||
sinon.stub(verify, 'start').rejects(err)
|
||||
|
||||
;(this as any).exec('verify')
|
||||
|
||||
// @ts-expect-error
|
||||
util.logErrorExit1.callsFake((e: Error) => {
|
||||
expect(e).to.eq(err)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress info', () => {
|
||||
beforeEach((): void => {
|
||||
sinon.stub(info, 'start').resolves(0)
|
||||
sinon.stub(util, 'exit').withArgs(0)
|
||||
})
|
||||
|
||||
it('calls info start', function (): void {
|
||||
(this as any).exec('info')
|
||||
expect(info.start).to.have.been.calledWith()
|
||||
})
|
||||
})
|
||||
|
||||
context('cypress cache list', () => {
|
||||
it('prints explanation when no cache', function (done) {
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = 'ENOENT'
|
||||
|
||||
sinon.stub(cache, 'list').rejects(err)
|
||||
|
||||
;(this as any).exec('cache list')
|
||||
|
||||
;(process.exit as any).callsFake(() => {
|
||||
snapshot('prints explanation when no cache', logger.print())
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('catches rejection and exits', function (done) {
|
||||
const err = new Error('cache list failed badly')
|
||||
|
||||
sinon.stub(cache, 'list').rejects(err)
|
||||
|
||||
;(this as any).exec('cache list')
|
||||
|
||||
// @ts-expect-error
|
||||
util.logErrorExit1.callsFake((e: Error) => {
|
||||
expect(e).to.eq(err)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('component-testing', () => {
|
||||
beforeEach((): void => {
|
||||
sinon.stub(spawn, 'start').resolves()
|
||||
})
|
||||
|
||||
it('spawns server with correct args for component-testing', function (): void {
|
||||
(this as any).exec('open --component --dev')
|
||||
// @ts-expect-error
|
||||
expect(spawn.start.firstCall.args[0]).to.include('--testing-type')
|
||||
// @ts-expect-error
|
||||
expect(spawn.start.firstCall.args[0]).to.include('component')
|
||||
})
|
||||
|
||||
it('runs server with correct args for component-testing', function (): void {
|
||||
(this as any).exec('run --component --dev')
|
||||
// @ts-expect-error
|
||||
expect(spawn.start.firstCall.args[0]).to.include('--testing-type')
|
||||
// @ts-expect-error
|
||||
expect(spawn.start.firstCall.args[0]).to.include('component')
|
||||
})
|
||||
})
|
||||
})
|
||||
305
cli/test/lib/cypress.spec.ts
Normal file
305
cli/test/lib/cypress.spec.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import { vi, beforeEach, describe, it, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import tmp from 'tmp'
|
||||
import fs from 'fs-extra'
|
||||
import open from '../../lib/exec/open'
|
||||
import run from '../../lib/exec/run'
|
||||
import cypress from '../../lib/cypress'
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
readJson: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('tmp', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
fileSync: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/exec/open', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/exec/run', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('cypress', function () {
|
||||
beforeEach(function () {
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
describe('.open', function () {
|
||||
it('calls open#start, passing in options', async function () {
|
||||
await cypress.open({ foo: 'foo' })
|
||||
|
||||
expect(open.start).toHaveBeenCalledWith({ foo: 'foo' })
|
||||
})
|
||||
|
||||
it('normalizes config object', async () => {
|
||||
const config = {
|
||||
pageLoadTime: 10000,
|
||||
watchForFileChanges: false,
|
||||
}
|
||||
|
||||
await cypress.open({ config })
|
||||
|
||||
expect(open.start).toHaveBeenCalledWith({ config: JSON.stringify(config) })
|
||||
})
|
||||
})
|
||||
|
||||
describe('.run fails to write results file', function () {
|
||||
it('resolves with error object', async function () {
|
||||
const outputPath = path.join(os.tmpdir(), 'cypress/monorepo/cypress_spec/output.json')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
tmp.fileSync.mockReturnValue({
|
||||
name: outputPath,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
run.start.mockResolvedValue(2)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.readJson.mockImplementation(async (args) => {
|
||||
if (args === outputPath) {
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('readJson not expected to fall through to this point'))
|
||||
})
|
||||
|
||||
const results = await cypress.run()
|
||||
|
||||
expect(results).toEqual({
|
||||
status: 'failed',
|
||||
failures: 2,
|
||||
message: 'Could not find Cypress test run results',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.run', function () {
|
||||
let outputPath: string
|
||||
|
||||
beforeEach(async function () {
|
||||
outputPath = path.join(os.tmpdir(), 'cypress/monorepo/cypress_spec/output.json')
|
||||
|
||||
// @ts-expect-error
|
||||
tmp.fileSync.mockReturnValue({
|
||||
name: outputPath,
|
||||
})
|
||||
|
||||
// @ts-expect-error
|
||||
run.start.mockResolvedValue(undefined)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.readJson.mockImplementation(async (args) => {
|
||||
if (args === outputPath) {
|
||||
return Promise.resolve({
|
||||
code: 0,
|
||||
failingTests: [],
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('readJson not expected to fall through to this point'))
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run#start, passing in options', async () => {
|
||||
await cypress.run({ spec: 'foo', autoCancelAfterFailures: 4 })
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith({
|
||||
outputPath,
|
||||
spec: 'foo',
|
||||
autoCancelAfterFailures: 4,
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run#start, passing in autoCancelAfterFailures false', async () => {
|
||||
await cypress.run({ autoCancelAfterFailures: false })
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith({
|
||||
outputPath,
|
||||
autoCancelAfterFailures: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run#start, passing in autoCancelAfterFailures 0', async () => {
|
||||
await cypress.run({ autoCancelAfterFailures: 0 })
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith({
|
||||
outputPath,
|
||||
autoCancelAfterFailures: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes config object', async () => {
|
||||
const config = {
|
||||
pageLoadTime: 10000,
|
||||
watchForFileChanges: false,
|
||||
}
|
||||
|
||||
await cypress.run({ config })
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith({
|
||||
outputPath,
|
||||
config: JSON.stringify(config),
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes env option if passed an object', async () => {
|
||||
const env = { foo: 'bar', another: 'one' }
|
||||
|
||||
await cypress.run({ env })
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith({
|
||||
outputPath,
|
||||
env: JSON.stringify(env),
|
||||
})
|
||||
})
|
||||
|
||||
it('gets random tmp file and passes it to run#start', async () => {
|
||||
await cypress.run()
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
outputPath,
|
||||
}))
|
||||
})
|
||||
|
||||
it('resolves with contents of tmp file', async () => {
|
||||
const results = await cypress.run()
|
||||
|
||||
expect(results).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('rejects if project is an empty string', async () => {
|
||||
await expect(cypress.run({ project: '' })).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('rejects if project is true', async () => {
|
||||
await expect(cypress.run({ project: true })).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('rejects if project is false', async () => {
|
||||
await expect(cypress.run({ project: false })).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('passes quiet: true', async () => {
|
||||
await cypress.run({
|
||||
quiet: true,
|
||||
})
|
||||
|
||||
expect(run.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
quiet: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('cli', function () {
|
||||
describe('.parseRunArguments', function () {
|
||||
it('parses CLI cypress run arguments', async () => {
|
||||
const args = 'cypress run --browser chrome --spec my/test/spec.js'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).toEqual({
|
||||
browser: 'chrome',
|
||||
spec: 'my/test/spec.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses CLI cypress run shorthand arguments', async () => {
|
||||
const args = 'cypress run -b firefox -p 5005 --headed --quiet'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).toEqual({
|
||||
browser: 'firefox',
|
||||
port: 5005,
|
||||
headed: true,
|
||||
quiet: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('coerces --record and --dev', async () => {
|
||||
const args = 'cypress run --record false --dev true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).toEqual({
|
||||
record: false,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('coerces --config-file cypress.config.js to string', async () => {
|
||||
const args = 'cypress run --config-file cypress.config.js'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).toEqual({
|
||||
configFile: 'cypress.config.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses config', async () => {
|
||||
const args = 'cypress run --config baseUrl=localhost,video=true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
// we don't need to convert the config into an object
|
||||
// since the logic inside cypress.run handles that
|
||||
expect(options).toEqual({
|
||||
config: 'baseUrl=localhost,video=true',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses env', async () => {
|
||||
const args = 'cypress run --env MY_NUMBER=42,MY_FLAG=true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
// we don't need to convert the environment into an object
|
||||
// since the logic inside cypress.run handles that
|
||||
expect(options).toEqual({
|
||||
env: 'MY_NUMBER=42,MY_FLAG=true',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,254 +0,0 @@
|
||||
import '../spec_helper'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
import snapshot from '../support/snapshot'
|
||||
import BluebirdPromise from 'bluebird'
|
||||
import tmpModule from 'tmp'
|
||||
import mockfs from 'mock-fs'
|
||||
import fs from '../../lib/fs'
|
||||
import open from '../../lib/exec/open'
|
||||
import run from '../../lib/exec/run'
|
||||
import cypress from '../../lib/cypress'
|
||||
|
||||
const tmp = BluebirdPromise.promisifyAll(tmpModule)
|
||||
|
||||
describe('cypress', function () {
|
||||
beforeEach(function () {
|
||||
mockfs({})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockfs.restore()
|
||||
})
|
||||
|
||||
context('.open', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(open, 'start').resolves()
|
||||
})
|
||||
|
||||
const getStartArgs = () => {
|
||||
expect(open.start).to.be.called
|
||||
|
||||
return _.get(open.start, ['lastCall', 'args', 0])
|
||||
}
|
||||
|
||||
it('calls open#start, passing in options', function () {
|
||||
return cypress.open({ foo: 'foo' })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args.foo).to.equal('foo')
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes config object', () => {
|
||||
const config = {
|
||||
pageLoadTime: 10000,
|
||||
watchForFileChanges: false,
|
||||
}
|
||||
|
||||
return cypress.open({ config })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args).to.deep.eq({ config: JSON.stringify(config) })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.run fails to write results file', function () {
|
||||
it('resolves with error object', function () {
|
||||
const outputPath = path.join(os.tmpdir(), 'cypress/monorepo/cypress_spec/output.json')
|
||||
|
||||
// @ts-expect-error - type doesn't exist
|
||||
sinon.stub(tmp, 'fileAsync').resolves(outputPath)
|
||||
sinon.stub(run, 'start').resolves(2)
|
||||
sinon.stub(fs, 'readJsonAsync').withArgs(outputPath).resolves()
|
||||
|
||||
return cypress.run().then((result: any) => {
|
||||
expect(result).to.deep.equal({
|
||||
status: 'failed',
|
||||
failures: 2,
|
||||
message: 'Could not find Cypress test run results',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.run', function () {
|
||||
let outputPath: string
|
||||
|
||||
beforeEach(function () {
|
||||
outputPath = path.join(os.tmpdir(), 'cypress/monorepo/cypress_spec/output.json')
|
||||
// @ts-expect-error - type doesn't exist
|
||||
sinon.stub(tmp, 'fileAsync').resolves(outputPath)
|
||||
sinon.stub(run, 'start').resolves()
|
||||
|
||||
return fs.outputJsonAsync(outputPath, {
|
||||
code: 0,
|
||||
failingTests: [],
|
||||
})
|
||||
})
|
||||
|
||||
const normalizeCallArgs = (args: any) => {
|
||||
expect(args.outputPath).to.equal(outputPath)
|
||||
delete args.outputPath
|
||||
|
||||
return args
|
||||
}
|
||||
const getStartArgs = () => {
|
||||
expect(run.start).to.be.called
|
||||
|
||||
return normalizeCallArgs(_.get(run.start, ['lastCall', 'args', 0]))
|
||||
}
|
||||
|
||||
it('calls run#start, passing in options', () => {
|
||||
return cypress.run({ spec: 'foo', autoCancelAfterFailures: 4 })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args.spec).to.equal('foo')
|
||||
expect(args.autoCancelAfterFailures).to.equal(4)
|
||||
expect(args.runnerUi).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run#start, passing in autoCancelAfterFailures false', () => {
|
||||
return cypress.run({ autoCancelAfterFailures: false })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args.autoCancelAfterFailures).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls run#start, passing in autoCancelAfterFailures 0', () => {
|
||||
return cypress.run({ autoCancelAfterFailures: 0 })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args.autoCancelAfterFailures).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes config object', () => {
|
||||
const config = {
|
||||
pageLoadTime: 10000,
|
||||
watchForFileChanges: false,
|
||||
}
|
||||
|
||||
return cypress.run({ config })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args).to.deep.eq({ config: JSON.stringify(config) })
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes env option if passed an object', () => {
|
||||
const env = { foo: 'bar', another: 'one' }
|
||||
|
||||
return cypress.run({ env })
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args).to.deep.eq({ env: JSON.stringify(env) })
|
||||
})
|
||||
})
|
||||
|
||||
it('gets random tmp file and passes it to run#start', function () {
|
||||
return cypress.run().then(() => {
|
||||
expect((run.start as any).lastCall.args[0].outputPath).to.equal(outputPath)
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves with contents of tmp file', () => {
|
||||
return cypress.run().then(snapshot)
|
||||
})
|
||||
|
||||
it('rejects if project is an empty string', () => {
|
||||
return expect(cypress.run({ project: '' })).to.be.rejected
|
||||
})
|
||||
|
||||
it('rejects if project is true', () => {
|
||||
return expect(cypress.run({ project: true })).to.be.rejected
|
||||
})
|
||||
|
||||
it('rejects if project is false', () => {
|
||||
return expect(cypress.run({ project: false })).to.be.rejected
|
||||
})
|
||||
|
||||
it('passes quiet: true', () => {
|
||||
const opts = {
|
||||
quiet: true,
|
||||
}
|
||||
|
||||
return cypress.run(opts)
|
||||
.then(getStartArgs)
|
||||
.then((args: any) => {
|
||||
expect(args).to.deep.eq(opts)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('cli', function () {
|
||||
describe('.parseRunArguments', function () {
|
||||
it('parses CLI cypress run arguments', async () => {
|
||||
const args = 'cypress run --browser chrome --spec my/test/spec.js'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).to.deep.equal({
|
||||
browser: 'chrome',
|
||||
spec: 'my/test/spec.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses CLI cypress run shorthand arguments', async () => {
|
||||
const args = 'cypress run -b firefox -p 5005 --headed --quiet'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).to.deep.equal({
|
||||
browser: 'firefox',
|
||||
port: 5005,
|
||||
headed: true,
|
||||
quiet: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('coerces --record and --dev', async () => {
|
||||
const args = 'cypress run --record false --dev true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).to.deep.equal({
|
||||
record: false,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('coerces --config-file cypress.config.js to string', async () => {
|
||||
const args = 'cypress run --config-file cypress.config.js'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
expect(options).to.deep.equal({
|
||||
configFile: 'cypress.config.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses config', async () => {
|
||||
const args = 'cypress run --config baseUrl=localhost,video=true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
// we don't need to convert the config into an object
|
||||
// since the logic inside cypress.run handles that
|
||||
expect(options).to.deep.equal({
|
||||
config: 'baseUrl=localhost,video=true',
|
||||
})
|
||||
})
|
||||
|
||||
it('parses env', async () => {
|
||||
const args = 'cypress run --env MY_NUMBER=42,MY_FLAG=true'.split(' ')
|
||||
const options = await cypress.cli.parseRunArguments(args)
|
||||
|
||||
// we don't need to convert the environment into an object
|
||||
// since the logic inside cypress.run handles that
|
||||
expect(options).to.deep.equal({
|
||||
env: 'MY_NUMBER=42,MY_FLAG=true',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
135
cli/test/lib/errors.spec.ts
Normal file
135
cli/test/lib/errors.spec.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { vi, describe, beforeEach, it, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import si from 'systeminformation'
|
||||
import util from '../../lib/util'
|
||||
import { errors, getError, formErrorText } from '../../lib/errors'
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
release: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('test platform')
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.release.mockReturnValue('release')
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
// @ts-expect-error mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
})
|
||||
|
||||
describe('individual', () => {
|
||||
it('has the following errors', () => {
|
||||
return expect(Object.keys(errors).sort()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getError', () => {
|
||||
it('forms full message and creates Error object', async () => {
|
||||
const errObject = errors.childProcessKilled('exit', 'SIGKILL')
|
||||
|
||||
expect(errObject).toMatchSnapshot()
|
||||
|
||||
const e = await getError(errObject)
|
||||
|
||||
expect(e).to.be.an('Error')
|
||||
expect(e).to.have.property('known', true)
|
||||
expect(e.message).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.errors.formErrorText', function () {
|
||||
it('returns fully formed text message', async () => {
|
||||
const { missingXvfb } = errors
|
||||
|
||||
expect(missingXvfb).to.be.an('object')
|
||||
|
||||
const text: string = await formErrorText(missingXvfb)
|
||||
|
||||
expect(text).to.be.a('string')
|
||||
expect(text).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('calls solution if a function', async () => {
|
||||
const solution = vi.fn().mockReturnValue('a solution')
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution,
|
||||
}
|
||||
|
||||
const text: string = await formErrorText(error)
|
||||
|
||||
expect(text).toMatchSnapshot()
|
||||
expect(solution).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('passes message and previous message', async () => {
|
||||
const solution = vi.fn().mockReturnValue('a solution')
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution,
|
||||
}
|
||||
|
||||
await formErrorText(error, 'msg', 'prevMsg')
|
||||
|
||||
expect(solution).toHaveBeenCalledWith('msg', 'prevMsg')
|
||||
})
|
||||
|
||||
it('expects solution to be a string', async () => {
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution: 42,
|
||||
}
|
||||
|
||||
await expect(formErrorText(error)).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('forms full text for invalid display error', async () => {
|
||||
const text: string = await formErrorText(errors.invalidSmokeTestDisplayError, 'current message', 'prev message')
|
||||
|
||||
expect(text).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,90 +0,0 @@
|
||||
import '../spec_helper'
|
||||
import os from 'os'
|
||||
import snapshot from '../support/snapshot'
|
||||
import util from '../../lib/util'
|
||||
import { errors, getError, formErrorText } from '../../lib/errors'
|
||||
|
||||
describe('errors', function () {
|
||||
const { missingXvfb } = errors
|
||||
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
|
||||
;(os.platform as any).returns('test platform')
|
||||
})
|
||||
|
||||
describe('individual', () => {
|
||||
it('has the following errors', () => {
|
||||
return snapshot(Object.keys(errors).sort())
|
||||
})
|
||||
})
|
||||
|
||||
context('getError', () => {
|
||||
it('forms full message and creates Error object', () => {
|
||||
const errObject = errors.childProcessKilled('exit', 'SIGKILL')
|
||||
|
||||
snapshot('child kill error object', errObject)
|
||||
|
||||
return getError(errObject).then((e: any) => {
|
||||
expect(e).to.be.an('Error')
|
||||
expect(e).to.have.property('known', true)
|
||||
snapshot('Error message', e.message)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.errors.formErrorText', function () {
|
||||
it('returns fully formed text message', () => {
|
||||
expect(missingXvfb).to.be.an('object')
|
||||
|
||||
return formErrorText(missingXvfb)
|
||||
.then((text: string) => {
|
||||
expect(text).to.be.a('string')
|
||||
snapshot(text)
|
||||
})
|
||||
})
|
||||
|
||||
it('calls solution if a function', () => {
|
||||
const solution = sinon.stub().returns('a solution')
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution,
|
||||
}
|
||||
|
||||
return formErrorText(error)
|
||||
.then((text: string) => {
|
||||
snapshot(text)
|
||||
expect(solution).to.have.been.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('passes message and previous message', () => {
|
||||
const solution = sinon.stub().returns('a solution')
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution,
|
||||
}
|
||||
|
||||
return formErrorText(error, 'msg', 'prevMsg')
|
||||
.then(() => {
|
||||
expect(solution).to.have.been.calledWithExactly('msg', 'prevMsg')
|
||||
})
|
||||
})
|
||||
|
||||
it('expects solution to be a string', () => {
|
||||
const error = {
|
||||
description: 'description',
|
||||
solution: 42,
|
||||
}
|
||||
|
||||
return expect(formErrorText(error)).to.be.rejected
|
||||
})
|
||||
|
||||
it('forms full text for invalid display error', () => {
|
||||
return formErrorText(errors.invalidSmokeTestDisplayError, 'current message', 'prev message')
|
||||
.then((text: string) => {
|
||||
snapshot('invalid display error', text)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,45 @@
|
||||
exports['cypress info without browsers or vars'] = `
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`exec info > logs additional info about pre-releases > logs additional info about pre-releases 1`] = `
|
||||
"
|
||||
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-02T00:00:00.000Z
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`exec info > logs if unbuilt development > logs additional info about development 1`] = `
|
||||
"
|
||||
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.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`exec info > prints collected info without env vars > cypress info without browsers or vars 1`] = `
|
||||
"
|
||||
Proxy Settings: none detected
|
||||
Environment Variables: none detected
|
||||
|
||||
@@ -8,16 +48,17 @@ Browser Profiles: /user/app/data/path/to/browsers
|
||||
Binary Caches: /user/path/to/binary/cache
|
||||
|
||||
Cypress Version: 0.0.0-development (stable)
|
||||
System Platform: linux (Foo-OsVersion)
|
||||
System Platform: linux (Foo - OsVersion)
|
||||
System Memory: 1.2 GB free 400 MB
|
||||
"
|
||||
`;
|
||||
|
||||
`
|
||||
|
||||
exports['cypress info with proxy and vars'] = `
|
||||
|
||||
exports[`exec info > prints proxy and cypress env vars > cypress info with proxy and vars 1`] = `
|
||||
"
|
||||
Proxy Settings:
|
||||
PROXY_ENV_VAR1: some proxy variable
|
||||
PROXY_ENV_VAR2: another proxy variable
|
||||
HTTP_PROXY: some proxy variable
|
||||
HTTPS_PROXY: another proxy variable
|
||||
NO_PROXY: no proxy variable
|
||||
|
||||
Learn More: https://on.cypress.io/proxy-configuration
|
||||
|
||||
@@ -30,13 +71,13 @@ Browser Profiles: /user/app/data/path/to/browsers
|
||||
Binary Caches: /user/path/to/binary/cache
|
||||
|
||||
Cypress Version: 0.0.0-development (stable)
|
||||
System Platform: linux (Foo-OsVersion)
|
||||
System Platform: linux (Foo - OsVersion)
|
||||
System Memory: 1.2 GB free 400 MB
|
||||
"
|
||||
`;
|
||||
|
||||
`
|
||||
|
||||
exports['cypress redacts sensitive vars'] = `
|
||||
|
||||
exports[`exec info > redacts sensitive cypress variables > cypress redacts sensitive vars 1`] = `
|
||||
"
|
||||
Proxy Settings: none detected
|
||||
Environment Variables:
|
||||
CYPRESS_ENV_VAR1: my Cypress variable
|
||||
@@ -49,45 +90,7 @@ Browser Profiles: /user/app/data/path/to/browsers
|
||||
Binary Caches: /user/path/to/binary/cache
|
||||
|
||||
Cypress Version: 0.0.0-development (stable)
|
||||
System Platform: linux (Foo-OsVersion)
|
||||
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.
|
||||
|
||||
`
|
||||
"
|
||||
`;
|
||||
37
cli/test/lib/exec/__snapshots__/run.spec.ts.snap
Normal file
37
cli/test/lib/exec/__snapshots__/run.spec.ts.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`exec run > .processRunOptions > defaults to e2e testingType 1`] = `
|
||||
[
|
||||
"--run-project",
|
||||
undefined,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`exec run > .processRunOptions > does not remove --record option when using --browser 1`] = `
|
||||
[
|
||||
"--run-project",
|
||||
undefined,
|
||||
"--browser",
|
||||
"test browser",
|
||||
"--record",
|
||||
"foo",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`exec run > .processRunOptions > passes --browser option 1`] = `
|
||||
[
|
||||
"--run-project",
|
||||
undefined,
|
||||
"--browser",
|
||||
"test browser",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`exec run > .processRunOptions > passes --record option 1`] = `
|
||||
[
|
||||
"--run-project",
|
||||
undefined,
|
||||
"--record",
|
||||
"my record id",
|
||||
]
|
||||
`;
|
||||
41
cli/test/lib/exec/__snapshots__/spawn.spec.ts.snap
Normal file
41
cli/test/lib/exec/__snapshots__/spawn.spec.ts.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`lib/exec/spawn > .start > detects kill signal > exits with error on SIGKILL 1`] = `
|
||||
"The Test Runner unexpectedly exited via a exit event with signal SIGKILL
|
||||
|
||||
Please search Cypress documentation for possible solutions:
|
||||
|
||||
https://on.cypress.io
|
||||
|
||||
Check if there is a GitHub issue describing this crash:
|
||||
|
||||
https://github.com/cypress-io/cypress/issues
|
||||
|
||||
Consider opening a new issue.
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo - OsVersion)
|
||||
Cypress Version: 0.0.0-development"
|
||||
`;
|
||||
|
||||
exports[`lib/exec/spawn > .start > does not force colors and streams when not supported 1`] = `
|
||||
{
|
||||
"DEBUG_COLORS": "0",
|
||||
"FORCE_COLOR": "0",
|
||||
"FORCE_STDERR_TTY": "0",
|
||||
"FORCE_STDIN_TTY": "0",
|
||||
"FORCE_STDOUT_TTY": "0",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`lib/exec/spawn > .start > forces colors and streams when supported 1`] = `
|
||||
{
|
||||
"DEBUG_COLORS": "1",
|
||||
"FORCE_COLOR": "1",
|
||||
"FORCE_STDERR_TTY": "1",
|
||||
"FORCE_STDIN_TTY": "1",
|
||||
"FORCE_STDOUT_TTY": "1",
|
||||
"MOCHA_COLORS": "1",
|
||||
}
|
||||
`;
|
||||
203
cli/test/lib/exec/info.spec.ts
Normal file
203
cli/test/lib/exec/info.spec.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import { Console } from 'console'
|
||||
import si from 'systeminformation'
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import info from '../../../lib/exec/info'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
totalmem: vi.fn(),
|
||||
freemem: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/exec/spawn', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getApplicationDataFolder: vi.fn(),
|
||||
pkgBuildInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getCacheDir: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('exec info', () => {
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
|
||||
// Direct console to process.stdout/stderr
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(() => {
|
||||
originalConsole = globalThis.console
|
||||
// Redirect console output to a custom stream or mock
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetAllMocks()
|
||||
|
||||
vi.stubEnv('NO_PROXY', undefined)
|
||||
vi.stubEnv('CYPRESS_COMMERCIAL_RECOMMENDATIONS', undefined)
|
||||
// common stubs
|
||||
// @ts-expect-error - mockReturnValue
|
||||
spawn.start.mockResolvedValue(null)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.totalmem.mockReturnValue(1.2e+9)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.freemem.mockReturnValue(4e+8)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
util.getApplicationDataFolder.mockImplementation((args) => {
|
||||
if (args === 'browsers') {
|
||||
return '/user/app/data/path/to/browsers'
|
||||
}
|
||||
|
||||
return '/user/app/data/path'
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue({
|
||||
stable: true,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getCacheDir.mockReturnValue('/user/path/to/binary/cache')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
})
|
||||
|
||||
it('prints collected info without env vars', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await info.start()
|
||||
|
||||
expect(output()).toMatchSnapshot('cypress info without browsers or vars')
|
||||
|
||||
expect(spawn.start).toBeCalledWith(['--mode=info'], { dev: undefined })
|
||||
})
|
||||
|
||||
it('prints proxy and cypress env vars', async () => {
|
||||
vi.stubEnv('HTTP_PROXY', 'some proxy variable')
|
||||
vi.stubEnv('HTTPS_PROXY', 'another proxy variable')
|
||||
vi.stubEnv('NO_PROXY', 'no proxy variable')
|
||||
|
||||
vi.stubEnv('CYPRESS_ENV_VAR1', 'my Cypress variable')
|
||||
vi.stubEnv('CYPRESS_ENV_VAR2', 'my other Cypress variable')
|
||||
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await info.start()
|
||||
|
||||
expect(output()).toMatchSnapshot('cypress info with proxy and vars')
|
||||
})
|
||||
|
||||
it('redacts sensitive cypress variables', async () => {
|
||||
vi.stubEnv('CYPRESS_ENV_VAR1', 'my Cypress variable')
|
||||
vi.stubEnv('CYPRESS_ENV_VAR2', 'my other Cypress variable')
|
||||
vi.stubEnv('CYPRESS_PROJECT_ID', 'abc123') // not sensitive
|
||||
vi.stubEnv('CYPRESS_RECORD_KEY', 'really really secret stuff') // should not be printed
|
||||
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await info.start()
|
||||
|
||||
expect(output()).toMatchSnapshot('cypress redacts sensitive vars')
|
||||
})
|
||||
|
||||
it('logs additional info about pre-releases', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue({
|
||||
stable: false,
|
||||
commitSha: 'abc123',
|
||||
commitBranch: 'someBranchName',
|
||||
commitDate: new Date('2022-02-02').toISOString(),
|
||||
})
|
||||
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await info.start()
|
||||
|
||||
expect(output()).toMatchSnapshot('logs additional info about pre-releases')
|
||||
})
|
||||
|
||||
it('logs if unbuilt development', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue(undefined)
|
||||
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await info.start()
|
||||
|
||||
expect(output()).toMatchSnapshot('logs additional info about development')
|
||||
})
|
||||
})
|
||||
@@ -1,94 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import os from 'os'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import stdout from '../../support/stdout'
|
||||
import normalize from '../../support/normalize'
|
||||
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import info from '../../../lib/exec/info'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
|
||||
describe('exec info', function () {
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(process, 'exit')
|
||||
|
||||
// common stubs
|
||||
sinon.stub(spawn, 'start').resolves()
|
||||
|
||||
;(os.platform as any).returns('linux')
|
||||
sinon.stub(os, 'totalmem').returns(1.2e+9)
|
||||
sinon.stub(os, 'freemem').returns(4e+8)
|
||||
sinon.stub(info, 'findProxyEnvironmentVariables').returns({})
|
||||
sinon.stub(info, 'findCypressEnvironmentVariables').returns({})
|
||||
sinon.stub(util, 'getApplicationDataFolder')
|
||||
.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')
|
||||
})
|
||||
|
||||
const startInfoAndSnapshot = async (snapshotName: string): Promise<void> => {
|
||||
expect(snapshotName, 'missing snapshot name').to.be.a('string')
|
||||
|
||||
const output = stdout.capture()
|
||||
|
||||
await info.start()
|
||||
stdout.restore()
|
||||
|
||||
snapshot(snapshotName, normalize(output.toString()))
|
||||
}
|
||||
|
||||
it('prints collected info without env vars', async () => {
|
||||
await startInfoAndSnapshot('cypress info without browsers or vars')
|
||||
expect(spawn.start).to.be.calledWith(['--mode=info'], { dev: undefined })
|
||||
})
|
||||
|
||||
it('prints proxy and cypress env vars', async () => {
|
||||
info.findProxyEnvironmentVariables.returns({
|
||||
PROXY_ENV_VAR1: 'some proxy variable',
|
||||
PROXY_ENV_VAR2: 'another proxy variable',
|
||||
})
|
||||
|
||||
info.findCypressEnvironmentVariables.returns({
|
||||
CYPRESS_ENV_VAR1: 'my Cypress variable',
|
||||
CYPRESS_ENV_VAR2: 'my other Cypress variable',
|
||||
})
|
||||
|
||||
await startInfoAndSnapshot('cypress info with proxy and vars')
|
||||
})
|
||||
|
||||
it('redacts sensitive cypress variables', async () => {
|
||||
info.findCypressEnvironmentVariables.returns({
|
||||
CYPRESS_ENV_VAR1: 'my Cypress variable',
|
||||
CYPRESS_ENV_VAR2: 'my other Cypress variable',
|
||||
CYPRESS_PROJECT_ID: 'abc123', // not sensitive
|
||||
CYPRESS_RECORD_KEY: 'really really secret stuff', // should not be printed
|
||||
})
|
||||
|
||||
await startInfoAndSnapshot('cypress redacts sensitive vars')
|
||||
})
|
||||
|
||||
it('logs additional info about pre-releases', async () => {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.pkgBuildInfo.returns({
|
||||
stable: false,
|
||||
commitSha: 'abc123',
|
||||
commitBranch: 'someBranchName',
|
||||
commitDate: new Date('2022-02-02').toISOString(),
|
||||
})
|
||||
|
||||
await startInfoAndSnapshot('logs additional info about pre-releases')
|
||||
})
|
||||
|
||||
it('logs if unbuilt development', async () => {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.pkgBuildInfo.returns(undefined)
|
||||
|
||||
await startInfoAndSnapshot('logs additional info about development')
|
||||
})
|
||||
})
|
||||
158
cli/test/lib/exec/open.spec.ts
Normal file
158
cli/test/lib/exec/open.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import util from '../../../lib/util'
|
||||
import { start as verifyStart } from '../../../lib/tasks/verify'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import open from '../../../lib/exec/open'
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
isInstalledGlobally: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/exec/spawn', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/verify', () => {
|
||||
return {
|
||||
start: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('exec open', function () {
|
||||
describe('.start', function () {
|
||||
beforeEach(function (): void {
|
||||
vi.clearAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isInstalledGlobally.mockReturnValue(true)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
verifyStart.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
spawn.start.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('verifies download', async () => {
|
||||
await open.start()
|
||||
expect(verifyStart).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('calls spawn with correct options', async () => {
|
||||
await open.start({ dev: true })
|
||||
expect(spawn.start).toHaveBeenCalledWith([], {
|
||||
detached: false,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with port', async () => {
|
||||
await open.start({ port: '1234' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--port', '1234'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --env', async () => {
|
||||
await open.start({ env: 'host=http://localhost:1337,name=brian' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(
|
||||
['--env', 'host=http://localhost:1337,name=brian'],
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns with --config', async () => {
|
||||
await open.start({ config: 'watchForFileChanges=false,baseUrl=localhost' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(
|
||||
['--config', 'watchForFileChanges=false,baseUrl=localhost'],
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns with --config-file set', async () => {
|
||||
await open.start({ configFile: 'special-cypress.config.js' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(
|
||||
['--config-file', 'special-cypress.config.js'],
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns with cwd as --project if not installed globally', async () => {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.mockReturnValue(false)
|
||||
|
||||
await open.start()
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--project', process.cwd()], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns without --project if not installed globally and passing --global option', async () => {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.mockReturnValue(false)
|
||||
|
||||
await open.start({ global: true })
|
||||
expect(spawn.start).not.toHaveBeenCalledWith(
|
||||
['--project', process.cwd()],
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns with --project passed in as options even when not installed globally', async () => {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.mockReturnValue(false)
|
||||
|
||||
await open.start({ project: '/path/to/project' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(
|
||||
['--project', '/path/to/project'],
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns with --project if specified and installed globally', async () => {
|
||||
await open.start({ project: '/path/to/project' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(
|
||||
['--project', '/path/to/project'],
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
|
||||
it('spawns without --project if not specified and installed globally', async () => {
|
||||
await open.start()
|
||||
expect(spawn.start).toHaveBeenCalledWith([], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns without --testing-type when not specified', async () => {
|
||||
await open.start()
|
||||
expect(spawn.start).toHaveBeenCalledWith([], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --testing-type e2e', async () => {
|
||||
await open.start({ testingType: 'e2e' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--testing-type', 'e2e'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --testing-type component', async () => {
|
||||
await open.start({ testingType: 'component' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--testing-type', 'component'], expect.anything())
|
||||
})
|
||||
|
||||
it('throws if --testing-type is invalid', () => {
|
||||
expect(() => open.processOpenOptions({ testingType: 'randomTestingType' })).toThrow()
|
||||
})
|
||||
|
||||
it('throws if --config-file is false', () => {
|
||||
expect(() => open.processOpenOptions({ configFile: 'false' })).toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,145 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
|
||||
import util from '../../../lib/util'
|
||||
import verify from '../../../lib/tasks/verify'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import open from '../../../lib/exec/open'
|
||||
|
||||
describe('exec open', function () {
|
||||
context('.start', function () {
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(util, 'isInstalledGlobally').returns(true)
|
||||
sinon.stub(verify, 'start').resolves()
|
||||
sinon.stub(spawn, 'start').resolves()
|
||||
})
|
||||
|
||||
it('verifies download', function () {
|
||||
return open.start()
|
||||
.then(() => {
|
||||
expect(verify.start).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('calls spawn with correct options', function () {
|
||||
return open.start({ dev: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([], {
|
||||
detached: false,
|
||||
dev: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with port', function () {
|
||||
return open.start({ port: '1234' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--port', '1234'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --env', function () {
|
||||
return open.start({ env: 'host=http://localhost:1337,name=brian' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--env', 'host=http://localhost:1337,name=brian'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --config', function () {
|
||||
return open.start({ config: 'watchForFileChanges=false,baseUrl=localhost' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--config', 'watchForFileChanges=false,baseUrl=localhost'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --config-file set', function () {
|
||||
return open.start({ configFile: 'special-cypress.config.js' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--config-file', 'special-cypress.config.js'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with cwd as --project if not installed globally', function () {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.returns(false)
|
||||
|
||||
return open.start()
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--project', process.cwd()],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns without --project if not installed globally and passing --global option', function () {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.returns(false)
|
||||
|
||||
return open.start({ global: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).not.to.be.calledWith(
|
||||
['--project', process.cwd()],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --project passed in as options even when not installed globally', function () {
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
util.isInstalledGlobally.returns(false)
|
||||
|
||||
return open.start({ project: '/path/to/project' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--project', '/path/to/project'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --project if specified and installed globally', function () {
|
||||
return open.start({ project: '/path/to/project' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--project', '/path/to/project'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns without --project if not specified and installed globally', function () {
|
||||
return open.start()
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns without --testing-type when not specified', () => {
|
||||
return open.start().then(() => {
|
||||
expect(spawn.start).to.be.calledWith([])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --testing-type e2e', () => {
|
||||
return open.start({ testingType: 'e2e' }).then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--testing-type', 'e2e'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --testing-type component', () => {
|
||||
return open.start({ testingType: 'component' }).then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--testing-type', 'component'])
|
||||
})
|
||||
})
|
||||
|
||||
it('throws if --testing-type is invalid', () => {
|
||||
expect(() => open.processOpenOptions({ testingType: 'randomTestingType' })).to.throw()
|
||||
})
|
||||
|
||||
it('throws if --config-file is false', () => {
|
||||
expect(() => open.processOpenOptions({ configFile: 'false' })).to.throw()
|
||||
})
|
||||
})
|
||||
})
|
||||
243
cli/test/lib/exec/run.spec.ts
Normal file
243
cli/test/lib/exec/run.spec.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import util from '../../../lib/util'
|
||||
import run from '../../../lib/exec/run'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import { start as verifyStart } from '../../../lib/tasks/verify'
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
isInstalledGlobally: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/exec/spawn', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/verify', () => {
|
||||
return {
|
||||
start: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('exec run', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isInstalledGlobally.mockReturnValue(true)
|
||||
})
|
||||
|
||||
describe('.processRunOptions', () => {
|
||||
it('allows string --project option', () => {
|
||||
const args = run.processRunOptions({
|
||||
project: '/path/to/project',
|
||||
})
|
||||
|
||||
expect(args).toEqual(['--run-project', '/path/to/project'])
|
||||
})
|
||||
|
||||
it('throws an error for empty string --project', () => {
|
||||
expect(() => run.processRunOptions({ project: '' })).toThrow()
|
||||
})
|
||||
|
||||
it('throws an error for boolean --project', () => {
|
||||
expect(() => run.processRunOptions({ project: false })).toThrow()
|
||||
expect(() => run.processRunOptions({ project: true })).toThrow()
|
||||
})
|
||||
|
||||
it('throws an error for --project "false" or "true"', () => {
|
||||
expect(() => run.processRunOptions({ project: 'false' })).toThrow()
|
||||
expect(() => run.processRunOptions({ project: 'true' })).toThrow()
|
||||
})
|
||||
|
||||
it('passes --browser option', () => {
|
||||
const args = run.processRunOptions({
|
||||
browser: 'test browser',
|
||||
})
|
||||
|
||||
expect(args).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('passes --record option', () => {
|
||||
const args = run.processRunOptions({
|
||||
record: 'my record id',
|
||||
})
|
||||
|
||||
expect(args).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('does not allow setting paradoxical --headed and --headless flags', () => {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
expect(() => run.processRunOptions({ headed: true, headless: true })).toThrow()
|
||||
})
|
||||
|
||||
it('passes --headed according to --headless', () => {
|
||||
expect(run.processRunOptions({ headless: true })).toEqual([
|
||||
'--run-project', undefined, '--headed', 'false',
|
||||
])
|
||||
})
|
||||
|
||||
it('does not remove --record option when using --browser', () => {
|
||||
const args = run.processRunOptions({
|
||||
record: 'foo',
|
||||
browser: 'test browser',
|
||||
})
|
||||
|
||||
expect(args).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('defaults to e2e testingType', () => {
|
||||
const args = run.processRunOptions()
|
||||
|
||||
expect(args).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('passes e2e testingType', () => {
|
||||
expect(run.processRunOptions({ testingType: 'e2e' })).toEqual([
|
||||
'--run-project', undefined, '--testing-type', 'e2e',
|
||||
])
|
||||
})
|
||||
|
||||
it('passes component testingType', () => {
|
||||
expect(run.processRunOptions({ testingType: 'component' })).toEqual([
|
||||
'--run-project', undefined, '--testing-type', 'component',
|
||||
])
|
||||
})
|
||||
|
||||
it('throws if testingType is invalid', () => {
|
||||
expect(() => run.processRunOptions({ testingType: 'randomTestingType' })).toThrow()
|
||||
})
|
||||
|
||||
it('throws if both e2e and component are set', () => {
|
||||
expect(() => run.processRunOptions({ e2e: true, component: true })).toThrow()
|
||||
})
|
||||
|
||||
it('throws if both testingType and component are set', () => {
|
||||
expect(() => run.processRunOptions({ testingType: 'component', component: true })).toThrow()
|
||||
})
|
||||
|
||||
it('throws if --config-file is false', () => {
|
||||
expect(() => run.processRunOptions({ configFile: 'false' })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.start', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
spawn.start.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
verifyStart.mockResolvedValue(undefined)
|
||||
})
|
||||
|
||||
it('verifies cypress', async () => {
|
||||
await run.start()
|
||||
expect(verifyStart).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('spawns with --key and xvfb', async () => {
|
||||
await run.start({ port: '1234' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--port', '1234'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --env', async () => {
|
||||
await run.start({ env: 'host=http://localhost:1337,name=brian' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--env', 'host=http://localhost:1337,name=brian'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --config', async () => {
|
||||
await run.start({ config: 'watchForFileChanges=false,baseUrl=localhost' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--config', 'watchForFileChanges=false,baseUrl=localhost'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --config-file set', async () => {
|
||||
await run.start({ configFile: 'special-cypress.config.js' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--config-file', 'special-cypress.config.js'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --record false', async () => {
|
||||
await run.start({ record: false })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--record', false], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --headed true', async () => {
|
||||
await run.start({ headed: true })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--headed', true], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --no-exit', async () => {
|
||||
await run.start({ exit: false })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--no-exit'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --output-path', async () => {
|
||||
await run.start({ outputPath: '/path/to/output' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--output-path', '/path/to/output'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --testing-type e2e when given --e2e', async () => {
|
||||
await run.start({ e2e: true })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--testing-type', 'e2e'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --testing-type component when given --component', async () => {
|
||||
await run.start({ component: true })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--testing-type', 'component'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --tag value', async () => {
|
||||
await run.start({ tag: 'nightly' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--tag', 'nightly'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with several --tag words unchanged', async () => {
|
||||
await run.start({ tag: 'nightly, sanity' })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--tag', 'nightly, sanity'], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --auto-cancel-after-failures value', async () => {
|
||||
await run.start({ autoCancelAfterFailures: 4 })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--auto-cancel-after-failures', 4], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --auto-cancel-after-failures value false', async () => {
|
||||
await run.start({ autoCancelAfterFailures: false })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--auto-cancel-after-failures', false], expect.anything())
|
||||
})
|
||||
|
||||
it('spawns with --runner-ui', async () => {
|
||||
await run.start({ runnerUi: true })
|
||||
expect(spawn.start).toHaveBeenCalledWith(['--run-project', process.cwd(), '--runner-ui', true], expect.anything())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,246 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import os from 'os'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import util from '../../../lib/util'
|
||||
import run from '../../../lib/exec/run'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import verify from '../../../lib/tasks/verify'
|
||||
|
||||
describe('exec run', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(util, 'isInstalledGlobally').returns(true)
|
||||
sinon.stub(process, 'exit')
|
||||
})
|
||||
|
||||
context('.processRunOptions', function () {
|
||||
it('allows string --project option', () => {
|
||||
const args = run.processRunOptions({
|
||||
project: '/path/to/project',
|
||||
})
|
||||
|
||||
expect(args).to.deep.equal(['--run-project', '/path/to/project'])
|
||||
})
|
||||
|
||||
it('throws an error for empty string --project', () => {
|
||||
expect(() => run.processRunOptions({ project: '' })).to.throw()
|
||||
})
|
||||
|
||||
it('throws an error for boolean --project', () => {
|
||||
expect(() => run.processRunOptions({ project: false })).to.throw()
|
||||
expect(() => run.processRunOptions({ project: true })).to.throw()
|
||||
})
|
||||
|
||||
it('throws an error for --project "false" or "true"', () => {
|
||||
expect(() => run.processRunOptions({ project: 'false' })).to.throw()
|
||||
expect(() => run.processRunOptions({ project: 'true' })).to.throw()
|
||||
})
|
||||
|
||||
it('passes --browser option', () => {
|
||||
const args = run.processRunOptions({
|
||||
browser: 'test browser',
|
||||
})
|
||||
|
||||
snapshot(args)
|
||||
})
|
||||
|
||||
it('passes --record option', () => {
|
||||
const args = run.processRunOptions({
|
||||
record: 'my record id',
|
||||
})
|
||||
|
||||
snapshot(args)
|
||||
})
|
||||
|
||||
it('does not allow setting paradoxical --headed and --headless flags', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(process.exit as any).returns()
|
||||
|
||||
expect(() => run.processRunOptions({ headed: true, headless: true })).to.throw()
|
||||
})
|
||||
|
||||
it('passes --headed according to --headless', () => {
|
||||
expect(run.processRunOptions({ headless: true })).to.deep.eq([
|
||||
'--run-project', undefined, '--headed', 'false',
|
||||
])
|
||||
})
|
||||
|
||||
it('does not remove --record option when using --browser', () => {
|
||||
const args = run.processRunOptions({
|
||||
record: 'foo',
|
||||
browser: 'test browser',
|
||||
})
|
||||
|
||||
snapshot(args)
|
||||
})
|
||||
|
||||
it('defaults to e2e testingType', () => {
|
||||
const args = run.processRunOptions()
|
||||
|
||||
snapshot(args)
|
||||
})
|
||||
|
||||
it('passes e2e testingType', () => {
|
||||
expect(run.processRunOptions({ testingType: 'e2e' })).to.deep.eq([
|
||||
'--run-project', undefined, '--testing-type', 'e2e',
|
||||
])
|
||||
})
|
||||
|
||||
it('passes component testingType', () => {
|
||||
expect(run.processRunOptions({ testingType: 'component' })).to.deep.eq([
|
||||
'--run-project', undefined, '--testing-type', 'component',
|
||||
])
|
||||
})
|
||||
|
||||
it('throws if testingType is invalid', () => {
|
||||
expect(() => run.processRunOptions({ testingType: 'randomTestingType' })).to.throw()
|
||||
})
|
||||
|
||||
it('throws if both e2e and component are set', () => {
|
||||
expect(() => run.processRunOptions({ e2e: true, component: true })).to.throw()
|
||||
})
|
||||
|
||||
it('throws if both testingType and component are set', () => {
|
||||
expect(() => run.processRunOptions({ testingType: 'component', component: true })).to.throw()
|
||||
})
|
||||
|
||||
it('throws if --config-file is false', () => {
|
||||
expect(() => run.processRunOptions({ configFile: 'false' })).to.throw()
|
||||
})
|
||||
})
|
||||
|
||||
context('.start', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(spawn, 'start').resolves()
|
||||
sinon.stub(verify, 'start').resolves()
|
||||
})
|
||||
|
||||
it('verifies cypress', function () {
|
||||
return run.start()
|
||||
.then(() => {
|
||||
expect(verify.start).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --key and xvfb', function () {
|
||||
return run.start({ port: '1234' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--port', '1234'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --env', function () {
|
||||
return run.start({ env: 'host=http://localhost:1337,name=brian' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--env', 'host=http://localhost:1337,name=brian'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --config', function () {
|
||||
return run.start({ config: 'watchForFileChanges=false,baseUrl=localhost' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--config', 'watchForFileChanges=false,baseUrl=localhost'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --config-file set', function () {
|
||||
return run.start({ configFile: 'special-cypress.config.js' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(
|
||||
['--run-project', process.cwd(), '--config-file', 'special-cypress.config.js'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --record false', function () {
|
||||
return run.start({ record: false })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--record', false])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --headed true', function () {
|
||||
return run.start({ headed: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--headed', true,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --no-exit', function () {
|
||||
return run.start({ exit: false })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--no-exit',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --output-path', function () {
|
||||
return run.start({ outputPath: '/path/to/output' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--output-path', '/path/to/output'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --testing-type e2e when given --e2e', function () {
|
||||
return run.start({ e2e: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--testing-type', 'e2e'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --testing-type component when given --component', function () {
|
||||
return run.start({ component: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith(['--run-project', process.cwd(), '--testing-type', 'component'])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --tag value', function () {
|
||||
return run.start({ tag: 'nightly' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--tag', 'nightly',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with several --tag words unchanged', function () {
|
||||
return run.start({ tag: 'nightly, sanity' })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--tag', 'nightly, sanity',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --auto-cancel-after-failures value', function () {
|
||||
return run.start({ autoCancelAfterFailures: 4 })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--auto-cancel-after-failures', 4,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --auto-cancel-after-failures value false', function () {
|
||||
return run.start({ autoCancelAfterFailures: false })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--auto-cancel-after-failures', false,
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('spawns with --runner-ui', function () {
|
||||
return run.start({ runnerUi: true })
|
||||
.then(() => {
|
||||
expect(spawn.start).to.be.calledWith([
|
||||
'--run-project', process.cwd(), '--runner-ui', true,
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
796
cli/test/lib/exec/spawn.spec.ts
Normal file
796
cli/test/lib/exec/spawn.spec.ts
Normal file
@@ -0,0 +1,796 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import cp from 'child_process'
|
||||
import os from 'os'
|
||||
import tty from 'tty'
|
||||
import path from 'path'
|
||||
import treeKill from 'tree-kill'
|
||||
import si from 'systeminformation'
|
||||
import { EventEmitter as EE } from 'events'
|
||||
import readline from 'readline'
|
||||
import createDebug from 'debug'
|
||||
import { stdin, stdout, stderr } from 'process'
|
||||
|
||||
import state from '../../../lib/tasks/state'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import { needsSandbox } from '../../../lib/tasks/verify'
|
||||
import util from '../../../lib/util'
|
||||
|
||||
const flushPromises = () => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, 100)
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('readline', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
createInterface: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('process', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
stdin: {
|
||||
// @ts-expect-error
|
||||
...actual.stdin,
|
||||
pipe: vi.fn(),
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
},
|
||||
stdout: vi.fn(),
|
||||
stderr: {
|
||||
// @ts-expect-error
|
||||
...actual.stderr,
|
||||
write: vi.fn(),
|
||||
},
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
stdin: {
|
||||
// @ts-expect-error
|
||||
...actual.default.stdin,
|
||||
pipe: vi.fn(),
|
||||
on: vi.fn(),
|
||||
},
|
||||
stdout: vi.fn(),
|
||||
stderr: {
|
||||
// @ts-expect-error
|
||||
...actual.default.stderr,
|
||||
write: vi.fn(),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('child_process', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
spawn: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('tty', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
isatty: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('tree-kill', () => {
|
||||
return {
|
||||
default: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/exec/xvfb', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
isNeeded: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getBinaryDir: vi.fn(),
|
||||
getPathToExecutable: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/verify', async () => {
|
||||
return {
|
||||
needsSandbox: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
supportsColor: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const cwd = process.cwd()
|
||||
const execPath = process.execPath
|
||||
const nodeVersion = process.versions.node
|
||||
const defaultBinaryDir = '/default/binary/dir'
|
||||
|
||||
describe('lib/exec/spawn', function () {
|
||||
let spawnedProcess: any
|
||||
let mockReadlineEE: any
|
||||
|
||||
beforeEach(function () {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
vi.stubEnv('DISPLAY', undefined)
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
spawnedProcess = new EE()
|
||||
spawnedProcess.unref = vi.fn().mockReturnValue(undefined)
|
||||
spawnedProcess.stdin = {
|
||||
on: vi.fn().mockReturnValue(undefined),
|
||||
pipe: vi.fn().mockReturnValue(undefined),
|
||||
}
|
||||
|
||||
spawnedProcess.stdout = {
|
||||
on: vi.fn().mockReturnValue(undefined),
|
||||
pipe: vi.fn().mockReturnValue(undefined),
|
||||
}
|
||||
|
||||
spawnedProcess.stderr = {
|
||||
pipe: vi.fn().mockReturnValue(undefined),
|
||||
on: vi.fn().mockReturnValue(undefined),
|
||||
}
|
||||
|
||||
spawnedProcess.kill = vi.fn()
|
||||
|
||||
mockReadlineEE = new EE()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
readline.createInterface.mockReturnValue(mockReadlineEE)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
cp.spawn.mockReturnValue(spawnedProcess)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.start.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.stop.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getBinaryDir.mockReturnValue(defaultBinaryDir)
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getPathToExecutable.mockImplementation((args) => {
|
||||
if (args === '/default/binary/dir') {
|
||||
return '/path/to/cypress'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('.start', function () {
|
||||
// ️️⚠️ NOTE ⚠️
|
||||
// when asserting the calls made to spawn the child Cypress process
|
||||
// we have to be _very_ careful. Spawn uses process.env object, if an assertion
|
||||
// fails, it will print the entire process.env object to the logs, which
|
||||
// might contain sensitive environment variables. Think about what the
|
||||
// failed assertion might print to the public CI logs and limit
|
||||
// the environment variables when running tests on CI.
|
||||
|
||||
it('passes args + options to spawn', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
needsSandbox.mockReturnValue(false)
|
||||
|
||||
// start the process
|
||||
const startPromise = spawn.start('--foo', { foo: 'bar' })
|
||||
|
||||
// simulate the process closing successfully
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
// await the process to complete and return
|
||||
await startPromise
|
||||
|
||||
expect(cp.spawn).toHaveBeenCalledWith('/path/to/cypress', [
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], expect.objectContaining({
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
}))
|
||||
})
|
||||
|
||||
it('uses --no-sandbox when needed', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
needsSandbox.mockReturnValue(true)
|
||||
|
||||
const startPromise = spawn.start('--foo', { foo: 'bar' })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// skip the options argument: we do not need anything about it
|
||||
// and also less risk that a failed assertion would dump the
|
||||
// entire ENV object with possible sensitive variables
|
||||
// @ts-expect-error - vitest mock
|
||||
const args = cp.spawn.mock.calls[0].slice(0, 2)
|
||||
|
||||
// it is important for "--no-sandbox" to appear before "--" separator
|
||||
const expectedCliArgs = [
|
||||
'--no-sandbox',
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
]
|
||||
|
||||
expect(args).toEqual(['/path/to/cypress', expectedCliArgs])
|
||||
})
|
||||
|
||||
it('uses npm command when running in dev mode', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
needsSandbox.mockReturnValue(false)
|
||||
|
||||
const startPromise = spawn.start('--foo', { dev: true, foo: 'bar' })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
const p = path.resolve('..', 'scripts', 'start.js')
|
||||
|
||||
expect(cp.spawn).toHaveBeenCalledWith('node', [
|
||||
p,
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], expect.objectContaining({
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
}))
|
||||
})
|
||||
|
||||
it('does not pass --no-sandbox when running in dev mode', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
needsSandbox.mockReturnValue(true)
|
||||
|
||||
const startPromise = spawn.start('--foo', { dev: true, foo: 'bar' })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
const p = path.resolve('..', 'scripts', 'start.js')
|
||||
|
||||
expect(cp.spawn).toHaveBeenCalledWith('node', [
|
||||
p,
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], expect.objectContaining({
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
}))
|
||||
})
|
||||
|
||||
it('starts xvfb when needed', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(true)
|
||||
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
expect(xvfb.start).toBeCalled()
|
||||
})
|
||||
|
||||
describe('closes', function () {
|
||||
['close', 'exit'].forEach((event) => {
|
||||
it(`if '${event}' event fired`, async () => {
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
spawnedProcess.emit(event, 0)
|
||||
|
||||
const code = await startPromise
|
||||
|
||||
expect(code).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('if exit event fired and close event fired', async () => {
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
spawnedProcess.emit('exit', 0)
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
const code = await startPromise
|
||||
|
||||
expect(code).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('detects kill signal', async () => {
|
||||
it('exits with error on SIGKILL', async () => {
|
||||
try {
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
spawnedProcess.emit('exit', null, 'SIGKILL')
|
||||
|
||||
await startPromise
|
||||
|
||||
throw new Error('should have hit error handler but did not')
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch(/SIGKILL/)
|
||||
expect(e.message).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('does not start xvfb when its not needed', async () => {
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
expect(xvfb.start).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('stops xvfb when spawn closes', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(true)
|
||||
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
expect(xvfb.stop).toBeCalled()
|
||||
})
|
||||
|
||||
it('resolves with spawned close code in the message', async () => {
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
spawnedProcess.emit('close', 10)
|
||||
|
||||
const code = await startPromise
|
||||
|
||||
expect(code).to.equal(10)
|
||||
})
|
||||
|
||||
describe('Linux display', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('DISPLAY', 'test-display')
|
||||
})
|
||||
|
||||
it('retries with xvfb if fails with display exit code', async () => {
|
||||
// mock display missing
|
||||
spawnedProcess.stderr.on.mockImplementation((event, callback) => {
|
||||
if (event === 'data') {
|
||||
callback('[some noise here] Gtk: cannot open display: 987')
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
// mock display error due to missing display
|
||||
spawnedProcess.emit('close', 1)
|
||||
|
||||
// mock the process actually starting up after xfvb is started
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
const code = await startPromise
|
||||
|
||||
expect(xvfb.start).toHaveBeenCalledOnce()
|
||||
expect(xvfb.stop).toHaveBeenCalledOnce()
|
||||
expect(cp.spawn).toHaveBeenCalledTimes(2)
|
||||
// second code should be 0 after successfully running with Xvfb
|
||||
expect(code).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects with error from spawn', async () => {
|
||||
const msg = 'the error message'
|
||||
|
||||
const startPromise = spawn.start('--foo')
|
||||
|
||||
spawnedProcess.emit('error', new Error(msg))
|
||||
|
||||
try {
|
||||
await startPromise
|
||||
|
||||
throw new Error('should have hit error handler but did not')
|
||||
} catch (e) {
|
||||
debug('error message', e.message)
|
||||
expect(e.message).toMatch(msg)
|
||||
}
|
||||
})
|
||||
|
||||
it('unrefs if options.detached is true', async () => {
|
||||
const startPromise = spawn.start(null, { detached: true })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
expect(spawnedProcess.unref).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('does not unref by default', async () => {
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
expect(spawnedProcess.unref).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sets process.env to options.env', async () => {
|
||||
vi.stubEnv('FOO', 'bar')
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.env.FOO).toEqual('bar')
|
||||
})
|
||||
|
||||
it('forces colors and streams when supported', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.supportsColor.mockReturnValue(true)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
tty.isatty.mockReturnValue(true)
|
||||
|
||||
const startPromise = spawn.start([], { env: {} })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.env).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('sets windowsHide:false property in windows', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
const startPromise = spawn.start([], { env: {} })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.windowsHide).toEqual(false)
|
||||
})
|
||||
|
||||
it('propagates treeKill if SIGINT is detected in windows console', async () => {
|
||||
spawnedProcess.pid = 7
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
const startPromise = spawn.start([], { env: {} })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
mockReadlineEE.emit('SIGINT')
|
||||
// since the import of tree-kill is async inside spawn, we need to wait for it to be imported and called
|
||||
await flushPromises()
|
||||
|
||||
expect(treeKill).toHaveBeenCalledWith(7, 'SIGINT')
|
||||
})
|
||||
|
||||
it('does not set windowsHide property when in darwin', async () => {
|
||||
const startPromise = spawn.start([], { env: {} })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.windowsHide).toBeUndefined()
|
||||
})
|
||||
|
||||
it('does not force colors and streams when not supported', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.supportsColor.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
tty.isatty.mockReturnValue(false)
|
||||
|
||||
const startPromise = spawn.start([], { env: {} })
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.env).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('pipes when on win32', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.stdio).toEqual('pipe')
|
||||
|
||||
expect(stdin.pipe).toHaveBeenCalledOnce()
|
||||
expect(stdin.pipe).toHaveBeenCalledWith(spawnedProcess.stdin)
|
||||
})
|
||||
|
||||
it('inherits when on linux and xvfb isn\'t needed', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.stdio).toEqual('inherit')
|
||||
})
|
||||
|
||||
it('uses [inherit, inherit, pipe] when linux and xvfb is needed', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.stdio).toEqual(['inherit', 'inherit', 'pipe'])
|
||||
})
|
||||
|
||||
it('uses [inherit, inherit, pipe] on darwin', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
await flushPromises()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// @ts-expect-error - mock argument
|
||||
const thirdArg = cp.spawn.mock.calls[0][2]
|
||||
|
||||
expect(thirdArg.stdio).to.deep.eq([
|
||||
'inherit', 'inherit', 'pipe',
|
||||
])
|
||||
})
|
||||
|
||||
it('writes everything on win32', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
const buf1 = Buffer.from('asdf')
|
||||
|
||||
// mock display missing
|
||||
spawnedProcess.stderr.on.mockImplementation((event, callback) => {
|
||||
if (event === 'data') {
|
||||
callback(buf1)
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
const startPromise = spawn.start()
|
||||
|
||||
spawnedProcess.emit('close', 0)
|
||||
|
||||
await startPromise
|
||||
|
||||
// validates the child process stderr event handler was called
|
||||
expect(stderr.write).toHaveBeenCalledWith(buf1)
|
||||
expect(stdin.pipe).toHaveBeenCalledExactlyOnceWith(spawnedProcess.stdin)
|
||||
expect(spawnedProcess.stdout.pipe).toHaveBeenCalledExactlyOnceWith(stdout)
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/1841
|
||||
// https://github.com/cypress-io/cypress/issues/5241
|
||||
const errCodes = ['EPIPE', 'ENOTCONN']
|
||||
|
||||
errCodes.forEach((errCode) => {
|
||||
beforeEach(() => {
|
||||
// create an EventEmitter and bind it to process.stdin
|
||||
const stdinEE = new EE()
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
stdin.emit.mockImplementation((event, ...args) => {
|
||||
stdinEE.emit(event, ...args)
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
stdin.on.mockImplementation((event, callback) => {
|
||||
return stdinEE.on(event, callback)
|
||||
})
|
||||
})
|
||||
|
||||
it(`catches process.stdin errors and returns when code=${errCode}`, async () => {
|
||||
expect(() => {
|
||||
// kick off the mock process
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
spawn.start()
|
||||
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = errCode
|
||||
|
||||
return stdin.emit('error', err)
|
||||
}).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
it('throws process.stdin errors code!=EPIPE', function () {
|
||||
expect(() => {
|
||||
// kick off the mock process
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
spawn.start()
|
||||
|
||||
const err: any = new Error('wattttt')
|
||||
|
||||
err.code = 'FAILWHALE'
|
||||
|
||||
return stdin.emit('error', err)
|
||||
}).toThrow(/wattttt/)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,525 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import cp from 'child_process'
|
||||
import os from 'os'
|
||||
import tty from 'tty'
|
||||
import path from 'path'
|
||||
import { EventEmitter as EE } from 'events'
|
||||
import mockedEnv from 'mocked-env'
|
||||
import readline from 'readline'
|
||||
import createDebug from 'debug'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
import spawn from '../../../lib/exec/spawn'
|
||||
import verify from '../../../lib/tasks/verify'
|
||||
import util from '../../../lib/util'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const cwd = process.cwd()
|
||||
const execPath = process.execPath
|
||||
const nodeVersion = process.versions.node
|
||||
|
||||
const defaultBinaryDir = '/default/binary/dir'
|
||||
let mockReadlineEE: any
|
||||
|
||||
describe('lib/exec/spawn', function () {
|
||||
beforeEach(function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
sinon.stub(process, 'exit')
|
||||
|
||||
;(this as any).spawnedProcess = {
|
||||
on: sinon.stub().returns(undefined),
|
||||
unref: sinon.stub().returns(undefined),
|
||||
stdin: {
|
||||
on: sinon.stub().returns(undefined),
|
||||
pipe: sinon.stub().returns(undefined),
|
||||
},
|
||||
stdout: {
|
||||
on: sinon.stub().returns(undefined),
|
||||
pipe: sinon.stub().returns(undefined),
|
||||
},
|
||||
stderr: {
|
||||
pipe: sinon.stub().returns(undefined),
|
||||
on: sinon.stub().returns(undefined),
|
||||
},
|
||||
kill: sinon.stub(),
|
||||
// expected by sinon
|
||||
cancel: sinon.stub(),
|
||||
}
|
||||
|
||||
// process.stdin is both an event emitter and a readable stream
|
||||
;(this as any).processStdin = new EE()
|
||||
mockReadlineEE = new EE()
|
||||
|
||||
;(this as any).processStdin.pipe = sinon.stub().returns(undefined)
|
||||
sinon.stub(process, 'stdin').value((this as any).processStdin)
|
||||
sinon.stub(readline, 'createInterface').returns(mockReadlineEE)
|
||||
sinon.stub(cp, 'spawn').returns((this as any).spawnedProcess)
|
||||
sinon.stub(xvfb, 'start').resolves()
|
||||
sinon.stub(xvfb, 'stop').resolves()
|
||||
sinon.stub(xvfb, 'isNeeded').returns(false)
|
||||
sinon.stub(state, 'getBinaryDir').returns(defaultBinaryDir)
|
||||
sinon.stub(state, 'getPathToExecutable').withArgs(defaultBinaryDir).returns('/path/to/cypress')
|
||||
})
|
||||
|
||||
context('.start', function () {
|
||||
// ️️⚠️ NOTE ⚠️
|
||||
// when asserting the calls made to spawn the child Cypress process
|
||||
// we have to be _very_ careful. Spawn uses process.env object, if an assertion
|
||||
// fails, it will print the entire process.env object to the logs, which
|
||||
// might contain sensitive environment variables. Think about what the
|
||||
// failed assertion might print to the public CI logs and limit
|
||||
// the environment variables when running tests on CI.
|
||||
|
||||
it('passes args + options to spawn', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
sinon.stub(verify, 'needsSandbox').returns(false)
|
||||
|
||||
return spawn.start('--foo', { foo: 'bar' })
|
||||
.then(() => {
|
||||
expect(cp.spawn).to.be.calledWithMatch('/path/to/cypress', [
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], {
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('uses --no-sandbox when needed', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
sinon.stub(verify, 'needsSandbox').returns(true)
|
||||
|
||||
return spawn.start('--foo', { foo: 'bar' })
|
||||
.then(() => {
|
||||
// skip the options argument: we do not need anything about it
|
||||
// and also less risk that a failed assertion would dump the
|
||||
// entire ENV object with possible sensitive variables
|
||||
const args = (cp.spawn as any).firstCall.args.slice(0, 2)
|
||||
// it is important for "--no-sandbox" to appear before "--" separator
|
||||
const expectedCliArgs = [
|
||||
'--no-sandbox',
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
]
|
||||
|
||||
expect(args).to.deep.equal(['/path/to/cypress', expectedCliArgs])
|
||||
})
|
||||
})
|
||||
|
||||
it('uses npm command when running in dev mode', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
sinon.stub(verify, 'needsSandbox').returns(false)
|
||||
|
||||
const p = path.resolve('..', 'scripts', 'start.js')
|
||||
|
||||
return spawn.start('--foo', { dev: true, foo: 'bar' })
|
||||
.then(() => {
|
||||
expect(cp.spawn).to.be.calledWithMatch('node', [
|
||||
p,
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], {
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not pass --no-sandbox when running in dev mode', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
sinon.stub(verify, 'needsSandbox').returns(true)
|
||||
|
||||
const p = path.resolve('..', 'scripts', 'start.js')
|
||||
|
||||
return spawn.start('--foo', { dev: true, foo: 'bar' })
|
||||
.then(() => {
|
||||
expect(cp.spawn).to.be.calledWithMatch('node', [
|
||||
p,
|
||||
'--',
|
||||
'--foo',
|
||||
'--cwd',
|
||||
cwd,
|
||||
'--userNodePath',
|
||||
execPath,
|
||||
'--userNodeVersion',
|
||||
nodeVersion,
|
||||
], {
|
||||
detached: false,
|
||||
stdio: ['inherit', 'inherit', 'pipe'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('starts xvfb when needed', function () {
|
||||
(xvfb.isNeeded as any).returns(true)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then(() => {
|
||||
expect(xvfb.start).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
context('closes', function () {
|
||||
['close', 'exit'].forEach((event) => {
|
||||
it(`if '${event}' event fired`, function () {
|
||||
(this as any).spawnedProcess.on.withArgs(event).yieldsAsync(0)
|
||||
|
||||
return spawn.start('--foo')
|
||||
})
|
||||
})
|
||||
|
||||
it('if exit event fired and close event fired', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('exit').yieldsAsync(0)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
return spawn.start('--foo')
|
||||
})
|
||||
})
|
||||
|
||||
context('detects kill signal', function () {
|
||||
it('exits with error on SIGKILL', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('exit').yieldsAsync(null, 'SIGKILL')
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then(() => {
|
||||
throw new Error('should have hit error handler but did not')
|
||||
}, (e: any) => {
|
||||
debug('error message', e.message)
|
||||
snapshot(e.message)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not start xvfb when its not needed', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then(() => {
|
||||
expect(xvfb.start).not.to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('stops xvfb when spawn closes', function () {
|
||||
(xvfb.isNeeded as any).returns(true)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yields()
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then(() => {
|
||||
expect(xvfb.stop).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves with spawned close code in the message', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(10)
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then((code: any) => {
|
||||
expect(code).to.equal(10)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Linux display', () => {
|
||||
let restore: any
|
||||
|
||||
beforeEach(() => {
|
||||
restore = mockedEnv({
|
||||
DISPLAY: 'test-display',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
restore()
|
||||
})
|
||||
|
||||
it('retries with xvfb if fails with display exit code', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').onFirstCall().yieldsAsync(1)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').onSecondCall().yieldsAsync(0)
|
||||
|
||||
const buf1 = '[some noise here] Gtk: cannot open display: 987'
|
||||
|
||||
;(this as any).spawnedProcess.stderr.on
|
||||
.withArgs('data')
|
||||
.yields(buf1)
|
||||
|
||||
;(os.platform as any).returns('linux')
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then((code: any) => {
|
||||
expect(xvfb.start).to.have.been.calledOnce
|
||||
expect(xvfb.stop).to.have.been.calledOnce
|
||||
expect(cp.spawn).to.have.been.calledTwice
|
||||
// second code should be 0 after successfully running with Xvfb
|
||||
expect(code).to.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects with error from spawn', function () {
|
||||
const msg = 'the error message'
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('error').yieldsAsync(new Error(msg))
|
||||
|
||||
return spawn.start('--foo')
|
||||
.then(() => {
|
||||
throw new Error('should have hit error handler but did not')
|
||||
}, (e: any) => {
|
||||
debug('error message', e.message)
|
||||
expect(e.message).to.include(msg)
|
||||
})
|
||||
})
|
||||
|
||||
it('unrefs if options.detached is true', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
return spawn.start(null, { detached: true })
|
||||
.then(() => {
|
||||
expect((this as any).spawnedProcess.unref).to.be.calledOnce
|
||||
})
|
||||
})
|
||||
|
||||
it('does not unref by default', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((this as any).spawnedProcess.unref).not.to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('sets process.env to options.env', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
process.env.FOO = 'bar'
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].env.FOO).to.eq('bar')
|
||||
})
|
||||
})
|
||||
|
||||
it('forces colors and streams when supported', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
sinon.stub(util, 'supportsColor').returns(true)
|
||||
sinon.stub(tty, 'isatty').returns(true)
|
||||
|
||||
return spawn.start([], { env: {} })
|
||||
.then(() => {
|
||||
snapshot((cp.spawn as any).firstCall.args[2].env)
|
||||
})
|
||||
})
|
||||
|
||||
it('sets windowsHide:false property in windows', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
|
||||
return spawn.start([], { env: {} })
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].windowsHide).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
it('propagates treeKill if SIGINT is detected in windows console', async function () {
|
||||
(this as any).spawnedProcess.pid = 7
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
|
||||
const treeKillMock = sinon.stub().returns(0)
|
||||
|
||||
const proxyquire = await import('proxyquire')
|
||||
const spawn = proxyquire.default(`../../../lib/exec/spawn`, { 'tree-kill': treeKillMock }).default
|
||||
|
||||
await spawn.start([], { env: {} })
|
||||
|
||||
mockReadlineEE.emit('SIGINT')
|
||||
// since the import of tree-kill is async inside spawn, we need to wait for it to be imported and called
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(resolve)
|
||||
})
|
||||
|
||||
expect(treeKillMock).to.have.been.calledWith(7, 'SIGINT')
|
||||
})
|
||||
|
||||
it('does not set windowsHide property when in darwin', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
return spawn.start([], { env: {} })
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].windowsHide).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('does not force colors and streams when not supported', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
sinon.stub(util, 'supportsColor').returns(false)
|
||||
sinon.stub(tty, 'isatty').returns(false)
|
||||
|
||||
return spawn.start([], { env: {} })
|
||||
.then(() => {
|
||||
snapshot((cp.spawn as any).firstCall.args[2].env)
|
||||
})
|
||||
})
|
||||
|
||||
it('pipes when on win32', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
|
||||
;(xvfb.isNeeded as any).returns(false)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].stdio).to.deep.eq('pipe')
|
||||
// parent process STDIN was piped to child process STDIN
|
||||
expect((this as any).processStdin.pipe, 'process.stdin').to.have.been.calledOnce
|
||||
.and.to.have.been.calledWith((this as any).spawnedProcess.stdin)
|
||||
})
|
||||
})
|
||||
|
||||
it('inherits when on linux and xvfb isn\'t needed', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(os.platform as any).returns('linux')
|
||||
|
||||
;(xvfb.isNeeded as any).returns(false)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].stdio).to.deep.eq('inherit')
|
||||
})
|
||||
})
|
||||
|
||||
it('uses [inherit, inherit, pipe] when linux and xvfb is needed', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(xvfb.isNeeded as any).returns(true)
|
||||
|
||||
;(os.platform as any).returns('linux')
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].stdio).to.deep.eq([
|
||||
'inherit', 'inherit', 'pipe',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('uses [inherit, inherit, pipe] on darwin', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
;(xvfb.isNeeded as any).returns(false)
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
expect((cp.spawn as any).firstCall.args[2].stdio).to.deep.eq([
|
||||
'inherit', 'inherit', 'pipe',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('writes everything on win32', function () {
|
||||
const buf1 = Buffer.from('asdf')
|
||||
|
||||
;(this as any).spawnedProcess.stdin.pipe.withArgs(process.stdin)
|
||||
|
||||
;(this as any).spawnedProcess.stdout.pipe.withArgs(process.stdout)
|
||||
|
||||
;(this as any).spawnedProcess.stderr.on
|
||||
.withArgs('data')
|
||||
.yields(buf1)
|
||||
|
||||
;(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
sinon.stub(process.stderr, 'write').withArgs(buf1)
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/1841
|
||||
// https://github.com/cypress-io/cypress/issues/5241
|
||||
;['EPIPE', 'ENOTCONN'].forEach((errCode) => {
|
||||
it(`catches process.stdin errors and returns when code=${errCode}`, function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
let called = false
|
||||
|
||||
const fn = () => {
|
||||
called = true
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = errCode
|
||||
|
||||
return process.stdin.emit('error', err)
|
||||
}
|
||||
|
||||
expect(fn).not.to.throw()
|
||||
expect(called).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('throws process.stdin errors code!=EPIPE', function () {
|
||||
(this as any).spawnedProcess.on.withArgs('close').yieldsAsync(0)
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
return spawn.start()
|
||||
.then(() => {
|
||||
const fn = () => {
|
||||
const err: any = new Error('wattttt')
|
||||
|
||||
err.code = 'FAILWHALE'
|
||||
|
||||
return process.stdin.emit('error', err)
|
||||
}
|
||||
|
||||
expect(fn).to.throw(/wattttt/)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
139
cli/test/lib/exec/versions.spec.ts
Normal file
139
cli/test/lib/exec/versions.spec.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import versions from '../../../lib/exec/versions'
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgBuildInfo: vi.fn(),
|
||||
pkgVersion: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getBinaryDir: vi.fn(),
|
||||
getBinaryPkgAsync: vi.fn(),
|
||||
parseRealPlatformBinaryFolderAsync: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/exec/versions', function () {
|
||||
const binaryDir = '/cache/1.2.3/Cypress.app'
|
||||
|
||||
beforeEach(function (): void {
|
||||
vi.unstubAllEnvs()
|
||||
vi.clearAllMocks()
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getBinaryDir.mockReturnValue(binaryDir)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: '1.2.3',
|
||||
electronVersion: '10.1.2',
|
||||
electronNodeVersion: '12.16.3',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('4.5.6')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue({ stable: true })
|
||||
})
|
||||
|
||||
describe('.getVersions', () => {
|
||||
it('gets the correct binary and package version', async () => {
|
||||
const { package: pkg, binary } = await versions.getVersions()
|
||||
|
||||
expect(pkg, 'package version').toEqual('4.5.6')
|
||||
expect(binary, 'binary version').toEqual('1.2.3')
|
||||
})
|
||||
|
||||
it('gets the correct Electron and bundled Node version', async () => {
|
||||
const { electronVersion, electronNodeVersion } = await versions.getVersions()
|
||||
|
||||
expect(electronVersion, 'electron version').toEqual('10.1.2')
|
||||
expect(electronNodeVersion, 'node version').toEqual('12.16.3')
|
||||
})
|
||||
|
||||
it('gets correct binary version if CYPRESS_RUN_BINARY', async () => {
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.parseRealPlatformBinaryFolderAsync.mockResolvedValue('/my/cypress/path')
|
||||
vi.stubEnv('CYPRESS_RUN_BINARY', '/my/cypress/path')
|
||||
|
||||
// @ts-expect-error
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === '/my/cypress/path') {
|
||||
return {
|
||||
version: '7.8.9',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
const { package: pkg, binary } = await versions.getVersions()
|
||||
|
||||
expect(pkg).toEqual('4.5.6')
|
||||
expect(binary).toEqual('7.8.9')
|
||||
})
|
||||
|
||||
it('appends pre-release if not stable', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue({ stable: false })
|
||||
|
||||
const version = await versions.getVersions()
|
||||
|
||||
expect(version.package).to.eql('4.5.6 (pre-release)')
|
||||
})
|
||||
|
||||
it('appends development if missing buildInfo', async () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgBuildInfo.mockReturnValue(undefined)
|
||||
const version = await versions.getVersions()
|
||||
|
||||
expect(version.package).to.eql('4.5.6 (development)')
|
||||
})
|
||||
|
||||
it('reports default versions if not found', async () => {
|
||||
// imagine package.json only has version there
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getBinaryPkgAsync.mockImplementation((args: string) => {
|
||||
if (args === binaryDir) {
|
||||
return {
|
||||
version: '90.9.9',
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('not found')
|
||||
})
|
||||
|
||||
const version = await versions.getVersions()
|
||||
|
||||
expect(version).toEqual({
|
||||
'package': '4.5.6',
|
||||
'binary': '90.9.9',
|
||||
'electronVersion': 'not found',
|
||||
'electronNodeVersion': 'not found',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,89 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import '../../spec_helper'
|
||||
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import versions from '../../../lib/exec/versions'
|
||||
|
||||
describe('lib/exec/versions', function () {
|
||||
const binaryDir = '/cache/1.2.3/Cypress.app'
|
||||
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(state, 'getBinaryDir').returns(binaryDir)
|
||||
sinon.stub(state, 'getBinaryPkgAsync').withArgs(binaryDir).resolves({
|
||||
version: '1.2.3',
|
||||
electronVersion: '10.1.2',
|
||||
electronNodeVersion: '12.16.3',
|
||||
})
|
||||
|
||||
sinon.stub(util, 'pkgVersion').returns('4.5.6')
|
||||
sinon.stub(util, 'pkgBuildInfo').returns({ stable: true })
|
||||
})
|
||||
|
||||
describe('.getVersions', function () {
|
||||
it('gets the correct binary and package version', function () {
|
||||
return versions.getVersions().then(({ package: pkg, binary }: any) => {
|
||||
expect(pkg, 'package version').to.eql('4.5.6')
|
||||
expect(binary, 'binary version').to.eql('1.2.3')
|
||||
})
|
||||
})
|
||||
|
||||
it('gets the correct Electron and bundled Node version', function () {
|
||||
return versions.getVersions().then(({ electronVersion, electronNodeVersion }: any) => {
|
||||
expect(electronVersion, 'electron version').to.eql('10.1.2')
|
||||
expect(electronNodeVersion, 'node version').to.eql('12.16.3')
|
||||
})
|
||||
})
|
||||
|
||||
it('gets correct binary version if CYPRESS_RUN_BINARY', function () {
|
||||
sinon.stub(state, 'parseRealPlatformBinaryFolderAsync').resolves('/my/cypress/path')
|
||||
process.env.CYPRESS_RUN_BINARY = '/my/cypress/path'
|
||||
state.getBinaryPkgAsync
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
.withArgs('/my/cypress/path')
|
||||
.resolves({
|
||||
version: '7.8.9',
|
||||
})
|
||||
|
||||
return versions.getVersions().then(({ package: pkg, binary }: any) => {
|
||||
expect(pkg).to.eql('4.5.6')
|
||||
expect(binary).to.eql('7.8.9')
|
||||
})
|
||||
})
|
||||
|
||||
it('appends pre-release if not stable', async function () {
|
||||
// @ts-expect-error - is shorthand stub on a 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 () {
|
||||
// @ts-expect-error - is shorthand stub on a 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
|
||||
// @ts-expect-error - is shorthand stub on a function
|
||||
state.getBinaryPkgAsync.withArgs(binaryDir).resolves({
|
||||
version: '90.9.9',
|
||||
})
|
||||
|
||||
return versions.getVersions().then((versions: any) => {
|
||||
expect(versions).to.deep.equal({
|
||||
'package': '4.5.6',
|
||||
'binary': '90.9.9',
|
||||
'electronVersion': 'not found',
|
||||
'electronNodeVersion': 'not found',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
63
cli/test/lib/exec/xvfb-integration.spec.ts
Normal file
63
cli/test/lib/exec/xvfb-integration.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/exec/xvfb-integration', function () {
|
||||
beforeEach(function (): void {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
})
|
||||
|
||||
describe('debugXvfb integration', function () {
|
||||
const { Debug } = xvfb._debugXvfb
|
||||
const { namespaces } = Debug
|
||||
|
||||
beforeEach(() => {
|
||||
Debug.enable(namespaces)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Debug.enable(namespaces)
|
||||
})
|
||||
|
||||
it('outputs when enabled', function () {
|
||||
const processStderrWriteSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(undefined)
|
||||
|
||||
Debug.enable(xvfb._debugXvfb.namespace)
|
||||
|
||||
xvfb._xvfb._onStderrData('asdf')
|
||||
|
||||
expect(processStderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('cypress:xvfb'))
|
||||
expect(processStderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('asdf'))
|
||||
})
|
||||
|
||||
it('does not output when disabled', function () {
|
||||
const processStderrWriteSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(undefined)
|
||||
|
||||
Debug.disable()
|
||||
|
||||
xvfb._xvfb._onStderrData('asdf')
|
||||
|
||||
expect(processStderrWriteSpy).not.toHaveBeenCalledWith(expect.stringContaining('cypress:xvfb'))
|
||||
expect(processStderrWriteSpy).not.toHaveBeenCalledWith(expect.stringContaining('asdf'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('xvfbOptions', function () {
|
||||
it('sets explicit screen', () => {
|
||||
expect(xvfb._xvfbOptions).toHaveProperty('xvfb_args', expect.arrayContaining(['-screen']))
|
||||
})
|
||||
})
|
||||
})
|
||||
101
cli/test/lib/exec/xvfb.spec.ts
Normal file
101
cli/test/lib/exec/xvfb.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import _xvfb from '@cypress/xvfb'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock(import('@cypress/xvfb'), async () => {
|
||||
const XVFB_MOCK = vi.fn()
|
||||
|
||||
XVFB_MOCK.prototype.start = vi.fn()
|
||||
|
||||
return {
|
||||
default: XVFB_MOCK,
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/exec/xvfb', function () {
|
||||
beforeEach(function (): void {
|
||||
vi.clearAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
})
|
||||
|
||||
describe('#start', function () {
|
||||
it('passes', async () => {
|
||||
vi.spyOn(_xvfb.prototype, 'start').mockImplementation((cb) => {
|
||||
// mock a pass
|
||||
cb()
|
||||
})
|
||||
|
||||
await expect(xvfb.start()).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it('fails with error message', async () => {
|
||||
const message = 'nope'
|
||||
|
||||
vi.spyOn(_xvfb.prototype, 'start').mockImplementation((cb) => {
|
||||
// mock a failure
|
||||
cb(new Error(message))
|
||||
})
|
||||
|
||||
await expect(xvfb.start()).rejects.toThrow(message)
|
||||
})
|
||||
|
||||
it('fails when xvfb exited with non zero exit code', async () => {
|
||||
const e: any = new Error('something bad happened')
|
||||
|
||||
e.nonZeroExitCode = true
|
||||
|
||||
vi.spyOn(_xvfb.prototype, 'start').mockImplementation((cb) => {
|
||||
// mock a failure
|
||||
cb(e)
|
||||
})
|
||||
|
||||
await expect(xvfb.start()).rejects.toThrow(expect.objectContaining({
|
||||
message: expect.stringContaining('something bad happened'),
|
||||
known: true,
|
||||
}))
|
||||
|
||||
await expect(xvfb.start()).rejects.toThrow(expect.objectContaining({
|
||||
message: expect.stringContaining('Xvfb exited with a non zero exit code.'),
|
||||
known: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isNeeded', function () {
|
||||
it('does not need xvfb on osx', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
expect(xvfb.isNeeded()).toBe(false)
|
||||
})
|
||||
|
||||
it('does not need xvfb on linux when DISPLAY is set', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
vi.stubEnv('DISPLAY', ':99')
|
||||
|
||||
expect(xvfb.isNeeded()).toBe(false)
|
||||
})
|
||||
|
||||
it('does need xvfb on linux when no DISPLAY is set', function () {
|
||||
vi.stubEnv('DISPLAY', undefined)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
expect(xvfb.isNeeded()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,111 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import os from 'os'
|
||||
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
|
||||
describe('lib/exec/xvfb', function () {
|
||||
beforeEach(function (): void {
|
||||
(os.platform as any).returns('win32')
|
||||
})
|
||||
|
||||
context('debugXvfb', function () {
|
||||
const { Debug } = xvfb._debugXvfb
|
||||
const { namespaces } = Debug
|
||||
|
||||
beforeEach(() => {
|
||||
Debug.enable(namespaces)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Debug.enable(namespaces)
|
||||
})
|
||||
|
||||
it('outputs when enabled', function () {
|
||||
sinon.stub(process.stderr, 'write').returns(undefined)
|
||||
Debug.enable(xvfb._debugXvfb.namespace)
|
||||
|
||||
xvfb._xvfb._onStderrData('asdf')
|
||||
|
||||
expect(process.stderr.write).to.be.calledWithMatch('cypress:xvfb')
|
||||
expect(process.stderr.write).to.be.calledWithMatch('asdf')
|
||||
})
|
||||
|
||||
it('does not output when disabled', function () {
|
||||
sinon.stub(process.stderr, 'write')
|
||||
Debug.disable()
|
||||
|
||||
xvfb._xvfb._onStderrData('asdf')
|
||||
|
||||
expect(process.stderr.write).not.to.be.calledWithMatch('cypress:xvfb')
|
||||
expect(process.stderr.write).not.to.be.calledWithMatch('asdf')
|
||||
})
|
||||
})
|
||||
|
||||
context('xvfbOptions', function () {
|
||||
it('sets explicit screen', () => {
|
||||
expect(xvfb._xvfbOptions).to.have.property('xvfb_args').that.includes('-screen')
|
||||
})
|
||||
})
|
||||
|
||||
context('#start', function () {
|
||||
it('passes', function () {
|
||||
sinon.stub(xvfb._xvfb, 'startAsync').resolves()
|
||||
|
||||
return xvfb.start()
|
||||
})
|
||||
|
||||
it('fails with error message', function () {
|
||||
const message = 'nope'
|
||||
|
||||
sinon.stub(xvfb._xvfb, 'startAsync').rejects(new Error(message))
|
||||
|
||||
return xvfb.start()
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown an error')
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
expect(err.message).to.include(message)
|
||||
})
|
||||
})
|
||||
|
||||
it('fails when xvfb exited with non zero exit code', function () {
|
||||
const e: any = new Error('something bad happened')
|
||||
|
||||
e.nonZeroExitCode = true
|
||||
|
||||
sinon.stub(xvfb._xvfb, 'startAsync').rejects(e)
|
||||
|
||||
return xvfb.start()
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown an error')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
expect(err.known).to.be.true
|
||||
expect(err.message).to.include('something bad happened')
|
||||
expect(err.message).to.include('Xvfb exited with a non zero exit code.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('#isNeeded', function () {
|
||||
it('does not need xvfb on osx', function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
expect(xvfb.isNeeded()).to.be.false
|
||||
})
|
||||
|
||||
it('does not need xvfb on linux when DISPLAY is set', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
process.env.DISPLAY = ':99'
|
||||
|
||||
expect(xvfb.isNeeded()).to.be.false
|
||||
})
|
||||
|
||||
it('does need xvfb on linux when no DISPLAY is set', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
expect(xvfb.isNeeded()).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
import '../spec_helper'
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import la from 'lazy-ass'
|
||||
import { stripIndent, stripIndents } from 'common-tags'
|
||||
import snapshot from '../support/snapshot'
|
||||
|
||||
describe('stripIndent', () => {
|
||||
it('removes indent from literal string', () => {
|
||||
@@ -13,7 +12,7 @@ describe('stripIndent', () => {
|
||||
`
|
||||
|
||||
// should preserve the structure of the text
|
||||
snapshot(removed)
|
||||
expect(removed).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('can be called as a function', () => {
|
||||
@@ -42,6 +41,6 @@ describe('stripIndent', () => {
|
||||
// bar
|
||||
//
|
||||
// last line
|
||||
snapshot(str)
|
||||
expect(str).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -1,79 +1,89 @@
|
||||
exports['lib/tasks/cache .path lists path to cache 1'] = `
|
||||
/.cache/Cypress
|
||||
`
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports['lib/tasks/cache .clear deletes cache folder and everything inside it 1'] = `
|
||||
[no output]
|
||||
`
|
||||
exports[`lib/tasks/cache > .clear > deletes cache folder and everything inside it 1`] = `""`;
|
||||
|
||||
exports['lib/tasks/cache .prune deletes cache binaries for all version but the current one 1'] = `
|
||||
Deleted all binary caches except for the 1.2.3 binary cache.
|
||||
`
|
||||
|
||||
exports['lib/tasks/cache .prune doesn\'t delete any cache binaries 1'] = `
|
||||
No binary caches found to prune.
|
||||
`
|
||||
|
||||
exports['lib/tasks/cache .prune exits cleanly if cache dir DNE 1'] = `
|
||||
No Cypress cache was found at /.cache/Cypress. Nothing to prune.
|
||||
`
|
||||
|
||||
exports['lib/tasks/cache .list lists all versions of cached binary 1'] = `
|
||||
┌─────────┬───────────┐
|
||||
exports[`lib/tasks/cache > .list > lists all versions of cached binary 1`] = `
|
||||
"┌─────────┬───────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼───────────┤
|
||||
│ 1.2.3 │ unknown │
|
||||
├─────────┼───────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴───────────┘
|
||||
`
|
||||
"
|
||||
`;
|
||||
|
||||
exports['cache list with silent log level'] = `
|
||||
┌─────────┬───────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼───────────┤
|
||||
│ 1.2.3 │ unknown │
|
||||
├─────────┼───────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴───────────┘
|
||||
`
|
||||
|
||||
exports['cache list with warn log level'] = `
|
||||
┌─────────┬───────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼───────────┤
|
||||
│ 1.2.3 │ unknown │
|
||||
├─────────┼───────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴───────────┘
|
||||
`
|
||||
|
||||
exports['lib/tasks/cache .list lists all versions of cached binary with last access 1'] = `
|
||||
┌─────────┬──────────────┐
|
||||
exports[`lib/tasks/cache > .list > lists all versions of cached binary with last access > list-of-versions 1`] = `
|
||||
"┌─────────┬──────────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼──────────────┤
|
||||
│ 1.2.3 │ 3 months ago │
|
||||
├─────────┼──────────────┤
|
||||
│ 2.3.4 │ 5 days ago │
|
||||
└─────────┴──────────────┘
|
||||
`
|
||||
"
|
||||
`;
|
||||
|
||||
exports['lib/tasks/cache .list some versions have never been opened 1'] = `
|
||||
┌─────────┬──────────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼──────────────┤
|
||||
│ 1.2.3 │ 3 months ago │
|
||||
├─────────┼──────────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴──────────────┘
|
||||
`
|
||||
exports[`lib/tasks/cache > .list > lists all versions of cached binary with npm log level silent > cache list with silent log level 1`] = `
|
||||
"┌─────────┬───────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼───────────┤
|
||||
│ 1.2.3 │ unknown │
|
||||
├─────────┼───────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴───────────┘
|
||||
"
|
||||
`;
|
||||
|
||||
exports['lib/tasks/cache .list shows sizes 1'] = `
|
||||
┌─────────┬──────────────┬───────┐
|
||||
exports[`lib/tasks/cache > .list > lists all versions of cached binary with npm log level warn > cache list with warn log level 1`] = `
|
||||
"┌─────────┬───────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼───────────┤
|
||||
│ 1.2.3 │ unknown │
|
||||
├─────────┼───────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴───────────┘
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .list > shows sizes > show-size 1`] = `
|
||||
"┌─────────┬──────────────┬───────┐
|
||||
│ version │ last used │ size │
|
||||
├─────────┼──────────────┼───────┤
|
||||
│ 1.2.3 │ 3 months ago │ 0.2MB │
|
||||
├─────────┼──────────────┼───────┤
|
||||
│ 2.3.4 │ unknown │ 0.2MB │
|
||||
└─────────┴──────────────┴───────┘
|
||||
`
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .list > some versions have never been opened > second-binary-never-used 1`] = `
|
||||
"┌─────────┬──────────────┐
|
||||
│ version │ last used │
|
||||
├─────────┼──────────────┤
|
||||
│ 1.2.3 │ 3 months ago │
|
||||
├─────────┼──────────────┤
|
||||
│ 2.3.4 │ unknown │
|
||||
└─────────┴──────────────┘
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .path > lists path to cache 1`] = `
|
||||
"/.cache/Cypress
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .prune > deletes cache binaries for all version but the current one 1`] = `
|
||||
"Deleted all binary caches except for the 1.2.3 binary cache.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .prune > doesn't delete any cache binaries 1`] = `
|
||||
"No binary caches found to prune.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/cache > .prune > exits cleanly if cache dir DNE 1`] = `
|
||||
"No Cypress cache was found at /.cache/Cypress. Nothing to prune.
|
||||
"
|
||||
`;
|
||||
44
cli/test/lib/tasks/__snapshots__/download.spec.ts.snap
Normal file
44
cli/test/lib/tasks/__snapshots__/download.spec.ts.snap
Normal file
@@ -0,0 +1,44 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`lib/tasks/download > catches download status errors and exits > download status errors 1 1`] = `
|
||||
"Error: The Cypress App could not be downloaded.
|
||||
|
||||
Does your workplace require a proxy to be used to access the Internet? If so, you must configure the HTTP_PROXY environment variable before downloading Cypress. Read more: https://on.cypress.io/proxy-configuration
|
||||
|
||||
Otherwise, please check network connectivity and try again:
|
||||
|
||||
----------
|
||||
|
||||
URL: https://download.cypress.io/desktop?platform=OS&arch=x64
|
||||
404 - Not Found
|
||||
|
||||
----------
|
||||
|
||||
Platform: OS-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/download > download base url from CYPRESS_DOWNLOAD_MIRROR env var > env var > base url from CYPRESS_DOWNLOAD_MIRROR 1 1`] = `"https://cypress.example.com/desktop/0.20.2?platform=OS&arch=ARCH"`;
|
||||
|
||||
exports[`lib/tasks/download > download base url from CYPRESS_DOWNLOAD_MIRROR env var > env var with subdirectory > base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory 1 1`] = `"https://cypress.example.com/example/desktop/0.20.2?platform=OS&arch=ARCH"`;
|
||||
|
||||
exports[`lib/tasks/download > download base url from CYPRESS_DOWNLOAD_MIRROR env var > env var with subdirectory and trailing slash > base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory and trailing slash 1 1`] = `"https://cypress.example.com/example/desktop/0.20.2?platform=OS&arch=ARCH"`;
|
||||
|
||||
exports[`lib/tasks/download > download base url from CYPRESS_DOWNLOAD_MIRROR env var > env var with trailing slash > base url from CYPRESS_DOWNLOAD_MIRROR with trailing slash 1 1`] = `"https://cypress.example.com/desktop/0.20.2?platform=OS&arch=ARCH"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template > desktop url from template 1`] = `"https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template with escaped dollar sign > desktop url from template with escaped dollar sign 1`] = `"https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template with escaped dollar sign wrapped in quote > desktop url from template with escaped dollar sign wrapped in quote 1`] = `"https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template with multiple replacements > desktop url from template with multiple replacements 1`] = `"https://download.cypress.io/desktop/0.20.2/OS/ARCH/cypress-0.20.2-OS-ARCH.zip?referrer=https://download.cypress.io/desktop/0.20.2&version=0.20.2"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template with version > desktop url from template with version 1`] = `"https://mycompany/0.20.2/OS-ARCH/cypress.zip"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns custom url from template wrapped in quote > desktop url from template wrapped in quote 1`] = `"https://download.cypress.io/desktop/0.20.2/OS-ARCH/cypress.zip"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns latest desktop url > latest desktop url 1 1`] = `"https://download.cypress.io/desktop?platform=OS&arch=ARCH"`;
|
||||
|
||||
exports[`lib/tasks/download > download url > returns specific desktop version url > specific version desktop url 1 1`] = `"https://download.cypress.io/desktop/0.20.2?platform=OS&arch=ARCH"`;
|
||||
282
cli/test/lib/tasks/__snapshots__/install.spec.ts.snap
Normal file
282
cli/test/lib/tasks/__snapshots__/install.spec.ts.snap
Normal file
@@ -0,0 +1,282 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`/lib/tasks/install > .start > exits with error when installing on unsupported os > error when installing on unsupported os 1`] = `
|
||||
"Error: The Cypress App could not be installed. Your machine does not meet the operating system requirements.
|
||||
|
||||
https://on.cypress.io/app/get-started/install-cypress#System-requirements
|
||||
|
||||
----------
|
||||
|
||||
Platform: win32-ia32
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > is silent when log level is silent > silent install 1 1`] = `
|
||||
"[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > non-stable builds > logs a warning about installing a pre-release > pre-release warning 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: 3b7f0b5c59def1e9b5f385bd585c9b2836706c29
|
||||
* Commit Branch: aBranchName
|
||||
* Commit Timestamp: 1996-11-27T00:00:00.000Z
|
||||
|
||||
Installing Cypress (version: https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/cypress.zip)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > as a global install > logs global warning and download > warning installing as global 1 1`] = `
|
||||
"
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
⚠ Warning: It looks like you've installed Cypress globally.
|
||||
|
||||
The recommended way to install Cypress is as a devDependency per project.
|
||||
|
||||
You should probably run these commands:
|
||||
|
||||
- npm uninstall -g cypress
|
||||
- npm install --save-dev cypress
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > failed write access to cache directory > logs error on failure > invalid cache directory 1 1`] = `
|
||||
"Error: Cypress cannot write to the cache directory due to file permissions
|
||||
|
||||
See discussion and possible solutions at
|
||||
https://github.com/cypress-io/cypress/issues/1281
|
||||
|
||||
----------
|
||||
|
||||
Failed to access /invalid/cache/dir:
|
||||
|
||||
EACCES: permission denied, mkdir '/invalid'
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > warns when specifying cypress version in env > specify version in env vars 1 1`] = `
|
||||
"⚠ Warning: Forcing a binary version different than the default.
|
||||
|
||||
The CLI expected to install version: 1.2.3
|
||||
|
||||
Instead we will install version: 0.12.1
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
Installing Cypress (version: 0.12.1)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when getting installed version does not match needed version > logs message and starts download > installed version does not match needed version 1 1`] = `
|
||||
"
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when getting installed version fails > logs message and starts download > continues installing on failure 1 1`] = `
|
||||
"Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when running in CI > uses verbose renderer > installing in ci 1 1`] = `
|
||||
"
|
||||
Cypress x.x.x is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
[STARTED] Task without title.
|
||||
[SUCCESS] Task without title.
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when there is no install version > logs message and starts download > installs without existing installation 1 1`] = `
|
||||
"Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when version is already installed > logs 'skipping install' when explicit cypress install > version already installed - cypress install 1 1`] = `
|
||||
"
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Skipping installation:
|
||||
|
||||
Pass the --force option if you'd like to reinstall anyway.
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > when version is already installed > logs when already installed when run from postInstall > version already installed - postInstall 1 1`] = `
|
||||
"
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > override version > with force: true > logs message and starts download > forcing true always installs 1 1`] = `
|
||||
"
|
||||
Cypress 1.2.3 is installed in /cache/Cypress/1.2.3
|
||||
|
||||
Installing Cypress (version: 1.2.3)
|
||||
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Downloaded Cypress
|
||||
[SUCCESS] Downloaded Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Unzipped Cypress
|
||||
[SUCCESS] Unzipped Cypress
|
||||
[STARTED] Task without title.
|
||||
[TITLE] Finished Installation /cache/Cypress/1.2.3
|
||||
[SUCCESS] Finished Installation /cache/Cypress/1.2.3
|
||||
|
||||
You can now open Cypress by running one of the following, depending on your package manager:
|
||||
|
||||
- npx cypress open
|
||||
- yarn cypress open
|
||||
- pnpm cypress open
|
||||
|
||||
https://on.cypress.io/opening-the-app
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`/lib/tasks/install > .start > skips install > when environment variable is set > skip installation 1 1`] = `
|
||||
"Note: Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.
|
||||
|
||||
"
|
||||
`;
|
||||
@@ -1,23 +1,7 @@
|
||||
exports['lib/tasks/unzip throws when cannot unzip 1'] = `
|
||||
Error: The Cypress App could not be unzipped.
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
Search for an existing issue or open a GitHub issue at
|
||||
|
||||
https://github.com/cypress-io/cypress/issues
|
||||
|
||||
----------
|
||||
|
||||
Error: end of central directory record signature not found
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo-OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
|
||||
`
|
||||
|
||||
exports['lib/tasks/unzip throws max path length error when cannot unzip due to realpath ENOENT on windows 1'] = `
|
||||
Error: The Cypress App could not be unzipped.
|
||||
exports[`lib/tasks/unzip > throws max path length error when cannot unzip due to realpath ENOENT on windows 1`] = `
|
||||
"Error: The Cypress App could not be unzipped.
|
||||
|
||||
This is most likely because the maximum path length is being exceeded on your system.
|
||||
|
||||
@@ -29,7 +13,25 @@ Error: failed
|
||||
|
||||
----------
|
||||
|
||||
Platform: win32-x64 (Foo-OsVersion)
|
||||
Platform: win32-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
`
|
||||
exports[`lib/tasks/unzip > throws when cannot unzip 1`] = `
|
||||
"Error: The Cypress App could not be unzipped.
|
||||
|
||||
Search for an existing issue or open a GitHub issue at
|
||||
|
||||
https://github.com/cypress-io/cypress/issues
|
||||
|
||||
----------
|
||||
|
||||
Error: end of central directory record signature not found
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (Foo - OsVersion)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
381
cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap
Normal file
381
cli/test/lib/tasks/__snapshots__/verify.spec.ts.snap
Normal file
@@ -0,0 +1,381 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`lib/tasks/verify > logs error and exits when executable cannot be found 1`] = `
|
||||
"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > logs error and exits when no version of Cypress is installed 1`] = `
|
||||
"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > logs error when child process hangs 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress verification timed out.
|
||||
|
||||
This command failed with the following output:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress --no-sandbox --smoke-test --ping=222
|
||||
|
||||
----------
|
||||
|
||||
some stderr
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > logs error when child process returns incorrect stdout (stderr when exists) 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
some stderr
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > logs error when child process returns incorrect stdout (stdout when no stderr) 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > logs warning when installed version does not match verified version 1`] = `
|
||||
"Found binary version bloop installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version bloop does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > on linux > logs error and exits when starting xvfb fails > xvfb fails 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Xvfb exited with a non zero exit code.
|
||||
|
||||
There was a problem spawning Xvfb.
|
||||
|
||||
This is likely a problem with your system, permissions, or installation of Xvfb.
|
||||
|
||||
----------
|
||||
|
||||
Error: test without xvfb
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > fails on both retries with our Xvfb on Linux > tried to verify twice, on the first try got the DISPLAY error 1`] = `
|
||||
"Cypress verification failed.
|
||||
|
||||
Cypress failed to start after spawning a new Xvfb server.
|
||||
|
||||
The error logs we received were:
|
||||
|
||||
----------
|
||||
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
some other error
|
||||
again with
|
||||
some weird indent
|
||||
|
||||
----------
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error above for more detail.
|
||||
|
||||
----------
|
||||
|
||||
Platform: linux-x64 (undefined)
|
||||
Cypress Version: 1.2.3"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > is silent when logLevel is silent > silent verify 1`] = `""`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs an error if Cypress executable does not exist > no Cypress executable 1`] = `
|
||||
"Error: No version of Cypress is installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
Please reinstall Cypress by running: cypress install
|
||||
|
||||
----------
|
||||
|
||||
Cypress executable not found at: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs an error if Cypress executable does not have permissions > Cypress non-executable permission 1`] = `
|
||||
"Error: Cypress cannot run because this binary file does not have executable permissions here:
|
||||
|
||||
/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
Reasons this may happen:
|
||||
|
||||
- node was installed as 'root' or with 'sudo'
|
||||
- the cypress npm package as 'root' or with 'sudo'
|
||||
|
||||
Please check that you have the appropriate user permissions.
|
||||
|
||||
You can also try clearing the cache with 'cypress cache clear' and reinstalling.
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs and runs when current version has not been verified > current version has not been verified 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs and runs when installed version is different than package version > different version installed 1`] = `
|
||||
"Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
It looks like this is your first time using Cypress: 7.8.9
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > logs error when fails smoke test unexpectedly without stderr > fails with no stderr 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
Error: EPERM NOT PERMITTED
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test retries on bad display with our Xvfb > turns off Opening Cypress... > no welcome message 1`] = `
|
||||
"Found binary version 7.8.9 installed in: /cache/Cypress/1.2.3/Cypress.app
|
||||
|
||||
⚠ Warning: Binary version 7.8.9 does not match the expected package version 1.2.3
|
||||
|
||||
These versions may not work properly together.
|
||||
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > smoke test with DEBUG output > finds ping value in the verbose output > verbose stdout output 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on darwin > darwin: error when invalid CYPRESS_RUN_BINARY 1`] = `
|
||||
"Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Contents/MacOS/Cypress
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on linux > linux: error when invalid CYPRESS_RUN_BINARY 1`] = `
|
||||
"Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Cypress
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: linux-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can log error to user on win32 > win32: error when invalid CYPRESS_RUN_BINARY 1`] = `
|
||||
"Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
Error: Could not run binary set by environment variable: CYPRESS_RUN_BINARY=/custom/
|
||||
|
||||
Ensure the environment variable is a path to the Cypress binary, matching **/Cypress.exe
|
||||
|
||||
----------
|
||||
|
||||
ENOENT: no such file or directory, stat '/custom/'
|
||||
|
||||
----------
|
||||
|
||||
Platform: win32-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when env var CYPRESS_RUN_BINARY > can validate and use executable > valid CYPRESS_RUN_BINARY 1`] = `
|
||||
"Note: You have set the environment variable:
|
||||
|
||||
CYPRESS_RUN_BINARY=/custom/Contents/MacOS/Cypress
|
||||
|
||||
This overrides the default Cypress binary path used.
|
||||
|
||||
It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when running in CI > logs error when binary not found > error binary not found in ci 1`] = `
|
||||
"Error: The cypress npm package is installed, but the Cypress binary is missing.
|
||||
|
||||
We expected the binary to be installed here: /cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress
|
||||
|
||||
Reasons it may be missing:
|
||||
|
||||
- You're caching 'node_modules' but are not caching this path: /cache/Cypress
|
||||
- You ran 'npm install' at an earlier build step but did not persist: /cache/Cypress
|
||||
|
||||
Properly caching the binary will fix this error and avoid downloading and unzipping Cypress.
|
||||
|
||||
Alternatively, you can run 'cypress install' to download the binary again.
|
||||
|
||||
https://on.cypress.io/not-installed-ci-error
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > when running in CI > uses verbose renderer > verifying in ci 1`] = `
|
||||
"It looks like this is your first time using Cypress: 1.2.3
|
||||
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > with force: true > clears verified version from state if verification fails > fails verifying Cypress 1`] = `
|
||||
"
|
||||
Error: Cypress failed to start.
|
||||
|
||||
This may be due to a missing library or dependency. https://on.cypress.io/required-dependencies
|
||||
|
||||
Please refer to the error below for more details.
|
||||
|
||||
----------
|
||||
|
||||
an error about dependencies
|
||||
|
||||
----------
|
||||
|
||||
Platform: darwin-x64 (undefined)
|
||||
Cypress Version: 1.2.3
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`lib/tasks/verify > with force: true > shows full path to executable when verifying > verification with executable 1`] = `
|
||||
"
|
||||
|
||||
Opening Cypress...
|
||||
"
|
||||
`;
|
||||
288
cli/test/lib/tasks/cache.spec.ts
Normal file
288
cli/test/lib/tasks/cache.spec.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import mockfs from 'mock-fs'
|
||||
import dayjs from 'dayjs'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { Console } from 'console'
|
||||
|
||||
import state from '../../../lib/tasks/state'
|
||||
import util from '../../../lib/util'
|
||||
import cache from '../../../lib/tasks/cache'
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
stat: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getCacheDir: vi.fn(),
|
||||
getBinaryDir: vi.fn(),
|
||||
getPathToExecutable: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/tasks/cache', () => {
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
|
||||
// Direct console to process.stdout/stderr
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
originalConsole = globalThis.console
|
||||
// Redirect console output to a custom stream or mock
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
})
|
||||
|
||||
beforeEach(async function () {
|
||||
mockfs({
|
||||
'/.cache/Cypress': {
|
||||
'1.2.3': {
|
||||
'Cypress': {
|
||||
'file1': Buffer.from(new Array(32 * 1024).fill(1)),
|
||||
'dir': {
|
||||
'file2': Buffer.from(new Array(128 * 1042).fill(2)),
|
||||
},
|
||||
},
|
||||
},
|
||||
'2.3.4': {
|
||||
'Cypress.app': {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// @ts-expect-error mockReturnValue
|
||||
state.getCacheDir.mockReturnValue('/.cache/Cypress')
|
||||
// @ts-expect-error mockReturnValue
|
||||
state.getBinaryDir.mockReturnValue('/.cache/Cypress')
|
||||
// @ts-expect-error mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockfs.restore()
|
||||
})
|
||||
|
||||
describe('.path', () => {
|
||||
it('lists path to cache', function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
cache.path()
|
||||
const stdout = output()
|
||||
|
||||
expect(stdout).to.eql('/.cache/Cypress\n')
|
||||
expect(stdout).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('lists path to cache with silent npm loglevel', function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'silent')
|
||||
|
||||
cache.path()
|
||||
expect(output()).to.eql('/.cache/Cypress\n')
|
||||
})
|
||||
|
||||
it('lists path to cache with silent npm warn', function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'warn')
|
||||
|
||||
cache.path()
|
||||
expect(output()).to.eql('/.cache/Cypress\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.clear', () => {
|
||||
it('deletes cache folder and everything inside it', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await cache.clear()
|
||||
|
||||
const exists = await fs.pathExists('/.cache/Cypress')
|
||||
|
||||
expect(exists).toEqual(false)
|
||||
expect(output()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.prune', () => {
|
||||
it('deletes cache binaries for all version but the current one', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await cache.prune()
|
||||
|
||||
const checkedInBinaryVersion = util.pkgVersion()
|
||||
|
||||
const files = await fs.readdir('/.cache/Cypress')
|
||||
|
||||
expect(files.length).to.eq(1)
|
||||
|
||||
files.forEach((file: any) => {
|
||||
expect(file).to.eq(checkedInBinaryVersion)
|
||||
})
|
||||
|
||||
expect(output()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('doesn\'t delete any cache binaries', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
const dir = path.join(state.getCacheDir(), '2.3.4')
|
||||
|
||||
await fs.remove(dir)
|
||||
|
||||
await cache.prune()
|
||||
|
||||
const checkedInBinaryVersion = util.pkgVersion()
|
||||
|
||||
const files = await fs.readdir('/.cache/Cypress')
|
||||
|
||||
expect(files.length).to.eq(1)
|
||||
|
||||
files.forEach((file: any) => {
|
||||
expect(file).to.eq(checkedInBinaryVersion)
|
||||
})
|
||||
|
||||
expect(output()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('exits cleanly if cache dir DNE', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await fs.remove(state.getCacheDir())
|
||||
await cache.prune()
|
||||
|
||||
expect(output()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.list', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error mockReturnValue
|
||||
state.getPathToExecutable.mockReturnValue('/.cache/Cypress/1.2.3/app/cypress')
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await cache.list()
|
||||
|
||||
expect(output()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with npm log level silent', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'silent')
|
||||
|
||||
await cache.list()
|
||||
// log output snapshot should have a grid of versions
|
||||
expect(output()).toMatchSnapshot('cache list with silent log level')
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with npm log level warn', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'warn')
|
||||
|
||||
await cache.list()
|
||||
|
||||
// log output snapshot should have a grid of versions
|
||||
expect(output()).toMatchSnapshot('cache list with warn log level')
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with last access', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce({
|
||||
atime: dayjs().subtract(5, 'day').valueOf(),
|
||||
})
|
||||
|
||||
await cache.list()
|
||||
await expect(output()).toMatchSnapshot('list-of-versions')
|
||||
})
|
||||
|
||||
it('some versions have never been opened', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
// the second binary has never been accessed
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce()
|
||||
|
||||
await cache.list()
|
||||
await expect(output()).toMatchSnapshot('second-binary-never-used')
|
||||
})
|
||||
|
||||
it('shows sizes', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
// the second binary has never been accessed
|
||||
// @ts-expect-error mockResolvedValueOnce
|
||||
fs.stat.mockResolvedValueOnce()
|
||||
|
||||
await cache.list(true)
|
||||
await expect(output()).toMatchSnapshot('show-size')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,277 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import mockfs from 'mock-fs'
|
||||
import stdout from '../../support/stdout'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import dayjs from 'dayjs'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import path from 'path'
|
||||
import termToHtml from 'term-to-html'
|
||||
import mockedEnv from 'mocked-env'
|
||||
import fs from '../../../lib/fs'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import util from '../../../lib/util'
|
||||
import cache from '../../../lib/tasks/cache'
|
||||
|
||||
const outputHtmlFolder = path.join(__dirname, '..', '..', 'html')
|
||||
|
||||
describe('lib/tasks/cache', () => {
|
||||
let stdoutCapture: any
|
||||
|
||||
beforeEach(async function () {
|
||||
mockfs({
|
||||
'/.cache/Cypress': {
|
||||
'1.2.3': {
|
||||
'Cypress': {
|
||||
'file1': Buffer.from(new Array(32 * 1024).fill(1)),
|
||||
'dir': {
|
||||
'file2': Buffer.from(new Array(128 * 1042).fill(2)),
|
||||
},
|
||||
},
|
||||
},
|
||||
'2.3.4': {
|
||||
'Cypress.app': {},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
sinon.stub(state, 'getCacheDir').returns('/.cache/Cypress')
|
||||
sinon.stub(state, 'getBinaryDir').returns('/.cache/Cypress')
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
|
||||
stdoutCapture = stdout.capture()
|
||||
})
|
||||
|
||||
const getSnapshotText = () => {
|
||||
const output = stdoutCapture.toString().split('\n').slice(0, -1).join('\n')
|
||||
const stdoutAsString = output || '[no output]'
|
||||
|
||||
// first restore the STDOUT, then confirm the value
|
||||
// otherwise the error might not even appear or appear twice!
|
||||
stdout.restore()
|
||||
|
||||
return stdoutAsString
|
||||
}
|
||||
|
||||
const saveHtml = async (filename: string, html: string) => {
|
||||
await fs.ensureDirAsync(outputHtmlFolder)
|
||||
const htmlFilename = path.join(outputHtmlFolder, filename)
|
||||
|
||||
await fs.writeFileAsync(htmlFilename, html, 'utf8')
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
mockfs.restore()
|
||||
})
|
||||
|
||||
const defaultSnapshot = (snapshotName?: string) => {
|
||||
const stdoutAsString = getSnapshotText()
|
||||
|
||||
const withoutAnsi = stripAnsi(stdoutAsString)
|
||||
|
||||
if (snapshotName) {
|
||||
snapshot(snapshotName, withoutAnsi)
|
||||
} else {
|
||||
snapshot(withoutAnsi)
|
||||
}
|
||||
}
|
||||
|
||||
const snapshotWithHtml = async (htmlFilename: string) => {
|
||||
const stdoutAsString = getSnapshotText()
|
||||
|
||||
snapshot(stripAnsi(stdoutAsString))
|
||||
|
||||
// if the sanitized snapshot matches, let's save the ANSI colors converted into HTML
|
||||
const html = termToHtml.strings(stdoutAsString, termToHtml.themes.dark.name)
|
||||
|
||||
await saveHtml(htmlFilename, html)
|
||||
}
|
||||
|
||||
describe('.path', () => {
|
||||
let restoreEnv: any
|
||||
|
||||
afterEach(() => {
|
||||
if (restoreEnv) {
|
||||
restoreEnv()
|
||||
restoreEnv = null
|
||||
}
|
||||
})
|
||||
|
||||
it('lists path to cache', function () {
|
||||
cache.path()
|
||||
expect(stdoutCapture.toString()).to.eql('/.cache/Cypress\n')
|
||||
defaultSnapshot()
|
||||
})
|
||||
|
||||
it('lists path to cache with silent npm loglevel', function () {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'silent',
|
||||
})
|
||||
|
||||
cache.path()
|
||||
expect(stdoutCapture.toString()).to.eql('/.cache/Cypress\n')
|
||||
})
|
||||
|
||||
it('lists path to cache with silent npm warn', function () {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'warn',
|
||||
})
|
||||
|
||||
cache.path()
|
||||
expect(stdoutCapture.toString()).to.eql('/.cache/Cypress\n')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.clear', () => {
|
||||
it('deletes cache folder and everything inside it', function () {
|
||||
return cache.clear()
|
||||
.then(() => {
|
||||
return fs.pathExistsAsync('/.cache/Cypress')
|
||||
.then((exists: any) => {
|
||||
expect(exists).to.eql(false)
|
||||
defaultSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.prune', () => {
|
||||
it('deletes cache binaries for all version but the current one', async function () {
|
||||
await cache.prune()
|
||||
|
||||
const checkedInBinaryVersion = util.pkgVersion()
|
||||
|
||||
const files = await fs.readdir('/.cache/Cypress')
|
||||
|
||||
expect(files.length).to.eq(1)
|
||||
|
||||
files.forEach((file: any) => {
|
||||
expect(file).to.eq(checkedInBinaryVersion)
|
||||
})
|
||||
|
||||
defaultSnapshot()
|
||||
})
|
||||
|
||||
it('doesn\'t delete any cache binaries', async function () {
|
||||
const dir = path.join(state.getCacheDir(), '2.3.4')
|
||||
|
||||
await fs.removeAsync(dir)
|
||||
|
||||
await cache.prune()
|
||||
|
||||
const checkedInBinaryVersion = util.pkgVersion()
|
||||
|
||||
const files = await fs.readdirAsync('/.cache/Cypress')
|
||||
|
||||
expect(files.length).to.eq(1)
|
||||
|
||||
files.forEach((file: any) => {
|
||||
expect(file).to.eq(checkedInBinaryVersion)
|
||||
})
|
||||
|
||||
defaultSnapshot()
|
||||
})
|
||||
|
||||
it('exits cleanly if cache dir DNE', async function () {
|
||||
await fs.removeAsync(state.getCacheDir())
|
||||
await cache.prune()
|
||||
|
||||
defaultSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.list', () => {
|
||||
let restoreEnv: any
|
||||
|
||||
afterEach(() => {
|
||||
if (restoreEnv) {
|
||||
restoreEnv()
|
||||
restoreEnv = null
|
||||
}
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary', async function () {
|
||||
// unknown access times
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
await cache.list()
|
||||
|
||||
defaultSnapshot()
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with npm log level silent', async function () {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'silent',
|
||||
})
|
||||
|
||||
// unknown access times
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
await cache.list()
|
||||
// log output snapshot should have a grid of versions
|
||||
defaultSnapshot('cache list with silent log level')
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with npm log level warn', async function () {
|
||||
restoreEnv = mockedEnv({
|
||||
npm_config_loglevel: 'warn',
|
||||
})
|
||||
|
||||
// unknown access times
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
await cache.list()
|
||||
|
||||
// log output snapshot should have a grid of versions
|
||||
defaultSnapshot('cache list with warn log level')
|
||||
})
|
||||
|
||||
it('lists all versions of cached binary with last access', async function () {
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
const statAsync = sinon.stub(fs, 'statAsync')
|
||||
|
||||
statAsync.onFirstCall().resolves({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
statAsync.onSecondCall().resolves({
|
||||
atime: dayjs().subtract(5, 'day').valueOf(),
|
||||
})
|
||||
|
||||
await cache.list()
|
||||
await snapshotWithHtml('list-of-versions.html')
|
||||
})
|
||||
|
||||
it('some versions have never been opened', async function () {
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
const statAsync = sinon.stub(fs, 'statAsync')
|
||||
|
||||
statAsync.onFirstCall().resolves({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
// the second binary has never been accessed
|
||||
statAsync.onSecondCall().resolves()
|
||||
|
||||
await cache.list()
|
||||
await snapshotWithHtml('second-binary-never-used.html')
|
||||
})
|
||||
|
||||
it('shows sizes', async function () {
|
||||
sinon.stub(state, 'getPathToExecutable').returns('/.cache/Cypress/1.2.3/app/cypress')
|
||||
|
||||
const statAsync = sinon.stub(fs, 'statAsync')
|
||||
|
||||
statAsync.onFirstCall().resolves({
|
||||
atime: dayjs().subtract(3, 'month').valueOf(),
|
||||
})
|
||||
|
||||
// the second binary has never been accessed
|
||||
statAsync.onSecondCall().resolves()
|
||||
|
||||
await cache.list(true)
|
||||
await snapshotWithHtml('show-size.html')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
/**
|
||||
* as of Webpack 5, dependencies that are polyfilled through the Provide plugin must be defined inside the CLI
|
||||
* in order to guarantee there is a version of the dependency accessible by the cypress CLI, either in the cypress directory
|
||||
@@ -5,22 +7,25 @@
|
||||
*/
|
||||
describe('dependencies', () => {
|
||||
it('process dependency exists in package.json and is available', async () => {
|
||||
// @ts-expect-error resolveJsonModule is set to true in tsconfig.json
|
||||
const { dependencies } = (await import('../../../package.json')).default
|
||||
|
||||
expect(dependencies.process).to.be.ok
|
||||
expect(dependencies.process).toBeDefined()
|
||||
|
||||
const process = await import('process')
|
||||
|
||||
expect(typeof process).to.equal('object')
|
||||
expect(typeof process).toEqual('object')
|
||||
})
|
||||
|
||||
it('buffer dependency exists in package.json and is available', async () => {
|
||||
// @ts-expect-error resolveJsonModule is set to true in tsconfig.json
|
||||
|
||||
const { dependencies } = (await import('../../../package.json')).default
|
||||
|
||||
expect(dependencies.buffer).to.be.ok
|
||||
expect(dependencies.buffer).toBeDefined()
|
||||
|
||||
const buffer = await import('buffer')
|
||||
|
||||
expect(typeof buffer).to.equal('object')
|
||||
expect(typeof buffer).toEqual('object')
|
||||
})
|
||||
})
|
||||
805
cli/test/lib/tasks/download.spec.ts
Normal file
805
cli/test/lib/tasks/download.spec.ts
Normal file
@@ -0,0 +1,805 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import si from 'systeminformation'
|
||||
import path from 'path'
|
||||
import nock from 'nock'
|
||||
import hasha from 'hasha'
|
||||
import createDebug from 'debug'
|
||||
import execa from 'execa'
|
||||
import fs from 'fs-extra'
|
||||
import { Console } from 'console'
|
||||
|
||||
import normalize from '../../support/normalize'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import download from '../../../lib/tasks/download'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const downloadDestination = path.join(os.tmpdir(), 'Cypress', 'download', 'cypress.zip')
|
||||
const version = '1.2.3'
|
||||
const examplePath = 'test/fixture/example.zip'
|
||||
|
||||
vi.mock('execa')
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
ensureDir: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
cwd: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/tasks/download', function () {
|
||||
const rootFolder = '/home/user/git'
|
||||
let options: any
|
||||
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
|
||||
// Direct console to process.stdout/stderr
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
nock.cleanAll()
|
||||
// make sure to clear out the cached arch in the util singleton
|
||||
util._cachedArch = undefined
|
||||
|
||||
originalConsole = globalThis.console
|
||||
// Redirect console output to a custom stream or mock
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
|
||||
logger.reset()
|
||||
|
||||
options = {
|
||||
downloadDestination,
|
||||
version,
|
||||
}
|
||||
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('OS')
|
||||
// @ts-expect-error mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
// @ts-expect-error mockReturnValue
|
||||
util.cwd.mockReturnValue(rootFolder)
|
||||
|
||||
const actualFsExtra = await vi.importActual<typeof import('fs-extra')>('fs-extra')
|
||||
|
||||
// @ts-expect-error - mockImplementation to pass through to the actual implementation to prevent issues with request-progress
|
||||
fs.ensureDir.mockImplementation(actualFsExtra.ensureDir)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
})
|
||||
|
||||
describe('download url', () => {
|
||||
it('returns url', () => {
|
||||
const url = download.getUrl('ARCH')
|
||||
|
||||
expect(() => new URL(url)).not.toThrow()
|
||||
})
|
||||
|
||||
it('returns latest desktop url', () => {
|
||||
const url = download.getUrl('ARCH')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('latest desktop url 1')
|
||||
})
|
||||
|
||||
it('returns specific desktop version url', () => {
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('specific version desktop url 1')
|
||||
})
|
||||
|
||||
it('returns custom url from template', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', '${endpoint}/${platform}-${arch}/cypress.zip')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template')
|
||||
})
|
||||
|
||||
it('returns custom url from template with version', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', 'https://mycompany/${version}/${platform}-${arch}/cypress.zip')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template with version')
|
||||
})
|
||||
|
||||
it('returns custom url from template with multiple replacements', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', '${endpoint}/${platform}/${arch}/cypress-${version}-${platform}-${arch}.zip?referrer=${endpoint}&version=${version}')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template with multiple replacements')
|
||||
})
|
||||
|
||||
it('returns custom url from template with escaped dollar sign', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', '\\${endpoint}/\\${platform}-\\${arch}/cypress.zip')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template with escaped dollar sign')
|
||||
})
|
||||
|
||||
it('returns custom url from template wrapped in quote', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', '"${endpoint}/${platform}-${arch}/cypress.zip"')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template wrapped in quote')
|
||||
})
|
||||
|
||||
it('returns custom url from template with escaped dollar sign wrapped in quote', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE', '"\\${endpoint}/\\${platform}-\\${arch}/cypress.zip"')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('desktop url from template with escaped dollar sign wrapped in quote')
|
||||
})
|
||||
|
||||
it('returns input if it is already an https link', () => {
|
||||
const url = 'https://somewhere.com'
|
||||
const result = download.getUrl('ARCH', url)
|
||||
|
||||
expect(result).toEqual(url)
|
||||
})
|
||||
|
||||
it('returns input if it is already an http link', () => {
|
||||
const url = 'http://local.com'
|
||||
const result = download.getUrl('ARCH', url)
|
||||
|
||||
expect(result).toEqual(url)
|
||||
})
|
||||
})
|
||||
|
||||
describe('download base url from CYPRESS_DOWNLOAD_MIRROR env var', () => {
|
||||
it('env var', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_MIRROR', 'https://cypress.example.com')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('base url from CYPRESS_DOWNLOAD_MIRROR 1')
|
||||
})
|
||||
|
||||
it('env var with trailing slash', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_MIRROR', 'https://cypress.example.com/')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('base url from CYPRESS_DOWNLOAD_MIRROR with trailing slash 1')
|
||||
})
|
||||
|
||||
it('env var with subdirectory', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_MIRROR', 'https://cypress.example.com/example')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory 1')
|
||||
})
|
||||
|
||||
it('env var with subdirectory and trailing slash', () => {
|
||||
vi.stubEnv('CYPRESS_DOWNLOAD_MIRROR', 'https://cypress.example.com/example/')
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
expect(normalize(url)).toMatchSnapshot('base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory and trailing slash 1')
|
||||
})
|
||||
})
|
||||
|
||||
it('saves example.zip to options.downloadDestination', async function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
const onProgress = vi.fn().mockReturnValue(undefined)
|
||||
|
||||
const responseVersion = await download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})
|
||||
|
||||
expect(responseVersion).to.eq('0.11.1')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
})
|
||||
|
||||
describe('verify downloaded file', function () {
|
||||
let expectedChecksum: string
|
||||
let expectedFileSize: number
|
||||
let onProgress: vi.Mock
|
||||
|
||||
beforeEach(function () {
|
||||
expectedChecksum = hasha.fromFileSync(examplePath)
|
||||
|
||||
expectedFileSize = fs.statSync(examplePath).size
|
||||
|
||||
onProgress = vi.fn().mockReturnValue(undefined)
|
||||
debug('example file %s should have checksum %s and file size %d',
|
||||
examplePath, expectedChecksum, expectedFileSize)
|
||||
})
|
||||
|
||||
it('throws if file size is different from expected', async function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
// definitely incorrect file size
|
||||
'content-length': '10',
|
||||
})
|
||||
|
||||
await expect(download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('throws if file size is different from expected x-amz-meta-size', async function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
// definitely incorrect file size
|
||||
'x-amz-meta-size': '10',
|
||||
})
|
||||
|
||||
await expect(download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('throws if checksum is different from expected', async function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': 'incorrect-checksum',
|
||||
})
|
||||
|
||||
await expect(download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('throws if checksum and file size are different from expected', async function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': 'incorrect-checksum',
|
||||
'x-amz-meta-size': '10',
|
||||
})
|
||||
|
||||
await expect(download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('passes when checksum and file size match', async function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
debug('creating read stream for %s', examplePath)
|
||||
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': expectedChecksum,
|
||||
'x-amz-meta-size': String(expectedFileSize),
|
||||
})
|
||||
|
||||
debug('downloading %s to %s for test version %s',
|
||||
examplePath, options.downloadDestination, options.version)
|
||||
|
||||
await download.start({
|
||||
downloadDestination: options.downloadDestination,
|
||||
version: options.version,
|
||||
progress: { onProgress },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves with response x-version if present', async function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('0.11.1')
|
||||
})
|
||||
|
||||
it('handles quadruple redirect with response x-version to the latest if present', async function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someone.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somebody.zip',
|
||||
'x-version': '0.11.2',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/something.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.4',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somebody.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/something.zip',
|
||||
'x-version': '0.11.3',
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someone.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('0.11.4')
|
||||
})
|
||||
|
||||
it('errors on too many redirects', async function () {
|
||||
function stubRedirects () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someone.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someone.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somebody.zip',
|
||||
'x-version': '0.11.2',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somebody.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/something.zip',
|
||||
'x-version': '0.11.3',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/something.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewhat.zip',
|
||||
'x-version': '0.11.4',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewhat.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/sometime.zip',
|
||||
'x-version': '0.11.5',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/sometime.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewhen.zip',
|
||||
'x-version': '0.11.6',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewhen.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewise.zip',
|
||||
'x-version': '0.11.7',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewise.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someways.zip',
|
||||
'x-version': '0.11.8',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someways.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somerset.zip',
|
||||
'x-version': '0.11.9',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somerset.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somedeal.zip',
|
||||
'x-version': '0.11.10',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somedeal.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.11',
|
||||
})
|
||||
}
|
||||
|
||||
stubRedirects()
|
||||
|
||||
try {
|
||||
await download.start(options)
|
||||
throw new Error('should have caught')
|
||||
} catch (error) {
|
||||
expect(error).to.be.instanceof(Error)
|
||||
expect(error.message).to.contain('redirect loop')
|
||||
}
|
||||
|
||||
stubRedirects()
|
||||
|
||||
// Double check to make sure that raising redirectTTL changes result
|
||||
const responseVersion = await download.start({ ...options, redirectTTL: 12 })
|
||||
|
||||
expect(responseVersion).to.eq('0.11.11')
|
||||
})
|
||||
|
||||
it('can specify cypress version in arguments', async function () {
|
||||
options.version = '0.13.0'
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/0.13.0')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.13.0',
|
||||
})
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('0.13.0')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
})
|
||||
|
||||
describe('architecture detection', () => {
|
||||
describe('Apple Silicon/M1', () => {
|
||||
function nockDarwinArm64 () {
|
||||
return nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query({ arch: 'arm64', platform: 'darwin' })
|
||||
.reply(200, undefined, {
|
||||
'x-version': '1.2.3',
|
||||
})
|
||||
}
|
||||
|
||||
it('downloads darwin-arm64 on M1', async function () {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.arch.mockReturnValue('arm64')
|
||||
|
||||
nockDarwinArm64()
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
})
|
||||
|
||||
it('downloads darwin-arm64 on M1 translated by Rosetta', async function () {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
|
||||
nockDarwinArm64()
|
||||
|
||||
// @ts-expect-error mockImplementation
|
||||
execa.mockImplementation((command, args, options) => {
|
||||
if (command === 'sysctl' && args[0] === '-n' && args[1] === 'sysctl.proc_translated') {
|
||||
return Promise.resolve({
|
||||
// will force arm64 inside util.getRealArch()
|
||||
stdout: '1',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Linux arm64/aarch64', () => {
|
||||
function nockLinuxArm64 () {
|
||||
return nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query({ arch: 'arm64', platform: 'linux' })
|
||||
.reply(200, undefined, {
|
||||
'x-version': '1.2.3',
|
||||
})
|
||||
}
|
||||
|
||||
it('downloads linux-arm64 on arm64 processor', async function () {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.arch.mockReturnValue('arm64')
|
||||
|
||||
nockLinuxArm64()
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
})
|
||||
|
||||
it('downloads linux-arm64 on non-arm64 node running on arm machine', async function () {
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
// @ts-expect-error mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
|
||||
for (const arch of ['aarch64_be', 'aarch64', 'armv8b', 'armv8l']) {
|
||||
nockLinuxArm64()
|
||||
|
||||
// @ts-expect-error mockImplementation
|
||||
execa.mockImplementation((command, args, options) => {
|
||||
if (command === 'uname' && args[0] === '-m') {
|
||||
return Promise.resolve({
|
||||
// will force arm64 inside util.getRealArch()
|
||||
stdout: arch,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const responseVersion = await download.start(options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.stat(downloadDestination)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('catches download status errors and exits', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
const err: any = new Error()
|
||||
|
||||
err.statusCode = 404
|
||||
err.statusMessage = 'Not Found'
|
||||
|
||||
options.version = null
|
||||
|
||||
// not really the download error, but the easiest way to
|
||||
// test the error handling
|
||||
// @ts-expect-error mockRejectedValue
|
||||
fs.ensureDir.mockRejectedValue(err)
|
||||
|
||||
try {
|
||||
await download.start(options)
|
||||
throw new Error('should have caught')
|
||||
} catch (error) {
|
||||
expect(error.message).not.toEqual('should have caught')
|
||||
logger.error(error)
|
||||
|
||||
expect(output()).toMatchSnapshot('download status errors 1')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('with proxy env vars', () => {
|
||||
const testUriHttp = 'http://anything.com'
|
||||
const testUriHttps = 'https://anything.com'
|
||||
|
||||
beforeEach(function () {
|
||||
// prevent ambient environment masking of environment variables referenced in this test
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
// add a default no_proxy which does not match the testUri
|
||||
vi.stubEnv('NO_PROXY', 'localhost,.org')
|
||||
})
|
||||
|
||||
it('uses http_proxy on http request', () => {
|
||||
vi.stubEnv('http_proxy', 'http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).toEqual('http://foo')
|
||||
})
|
||||
|
||||
it('ignores http_proxy on https request', () => {
|
||||
vi.stubEnv('http_proxy', 'http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual(null)
|
||||
vi.stubEnv('https_proxy', 'https://bar')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('https://bar')
|
||||
})
|
||||
|
||||
it('falls back to npm_config_proxy', () => {
|
||||
vi.stubEnv('npm_config_proxy', 'http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('http://foo')
|
||||
vi.stubEnv('npm_config_https_proxy', 'https://bar')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('https://bar')
|
||||
vi.stubEnv('https_proxy', 'https://baz')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('https://baz')
|
||||
})
|
||||
|
||||
it('respects no_proxy on http and https requests', () => {
|
||||
vi.stubEnv('NO_PROXY', 'localhost,.com')
|
||||
|
||||
vi.stubEnv('http_proxy', 'http://foo')
|
||||
vi.stubEnv('https_proxy', 'https://bar')
|
||||
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).toBeNull()
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores no_proxy for npm proxy configs, prefers https over http', () => {
|
||||
vi.stubEnv('NO_PROXY', 'localhost,.com')
|
||||
|
||||
vi.stubEnv('npm_config_proxy', 'http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).toEqual('http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('http://foo')
|
||||
|
||||
vi.stubEnv('npm_config_https_proxy', 'https://bar')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).toEqual('https://bar')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).toEqual('https://bar')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with CA and CAFILE env vars', () => {
|
||||
it('returns undefined if not set', async () => {
|
||||
const ca = await download.getCA()
|
||||
|
||||
expect(ca).toBeUndefined()
|
||||
})
|
||||
|
||||
it('returns CA from npm_config_ca', async () => {
|
||||
vi.stubEnv('npm_config_ca', 'foo')
|
||||
|
||||
const ca = await download.getCA()
|
||||
|
||||
expect(ca).toEqual('foo')
|
||||
})
|
||||
|
||||
it('returns CA from npm_config_cafile', async () => {
|
||||
vi.stubEnv('npm_config_cafile', 'test/fixture/cafile.pem')
|
||||
|
||||
const ca = await download.getCA()
|
||||
|
||||
expect(ca).toEqual('bar\n')
|
||||
})
|
||||
|
||||
it('returns undefined if failed reading npm_config_cafile', async () => {
|
||||
vi.stubEnv('npm_config_cafile', 'test/fixture/not-exists.pem')
|
||||
|
||||
const ca = await download.getCA()
|
||||
|
||||
expect(ca).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -1,723 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import _ from 'lodash'
|
||||
import os from 'os'
|
||||
import cp from 'child_process'
|
||||
import la from 'lazy-ass'
|
||||
import is from 'check-more-types'
|
||||
import path from 'path'
|
||||
import nock from 'nock'
|
||||
import hasha from 'hasha'
|
||||
import createDebug from 'debug'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import stdout from '../../support/stdout'
|
||||
import normalize from '../../support/normalize'
|
||||
import mockSpawnModule from '../../support/spawn-mock'
|
||||
import fs from '../../../lib/fs'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import download from '../../../lib/tasks/download'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const downloadDestination = path.join(os.tmpdir(), 'Cypress', 'download', 'cypress.zip')
|
||||
const version = '1.2.3'
|
||||
const examplePath = 'test/fixture/example.zip'
|
||||
|
||||
describe('lib/tasks/download', function () {
|
||||
before(async function () {
|
||||
const mochaMain = await import('mocha-banner')
|
||||
|
||||
mochaMain.register()
|
||||
})
|
||||
|
||||
const rootFolder = '/home/user/git'
|
||||
|
||||
beforeEach(function () {
|
||||
logger.reset()
|
||||
|
||||
;(this as any).stdout = stdout.capture()
|
||||
|
||||
;(this as any).options = {
|
||||
downloadDestination,
|
||||
version,
|
||||
}
|
||||
|
||||
;(os.platform as any).returns('OS')
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
sinon.stub(util, 'cwd').returns(rootFolder)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
stdout.restore()
|
||||
})
|
||||
|
||||
context('download url', () => {
|
||||
it('returns url', () => {
|
||||
const url = download.getUrl('ARCH')
|
||||
|
||||
la((is as any).url(url), url)
|
||||
})
|
||||
|
||||
it('returns latest desktop url', () => {
|
||||
const url = download.getUrl('ARCH')
|
||||
|
||||
snapshot('latest desktop url 1', normalize(url))
|
||||
})
|
||||
|
||||
it('returns specific desktop version url', () => {
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('specific version desktop url 1', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '${endpoint}/${platform}-${arch}/cypress.zip'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template with version', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = 'https://mycompany/${version}/${platform}-${arch}/cypress.zip'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template with version', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template with multiple replacements', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '${endpoint}/${platform}/${arch}/cypress-${version}-${platform}-${arch}.zip?referrer=${endpoint}&version=${version}'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template with multiple replacements', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template with escaped dollar sign', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '\\${endpoint}/\\${platform}-\\${arch}/cypress.zip'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template with escaped dollar sign', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template wrapped in quote', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '"${endpoint}/${platform}-${arch}/cypress.zip"'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template wrapped in quote', normalize(url))
|
||||
})
|
||||
|
||||
it('returns custom url from template with escaped dollar sign wrapped in quote', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_PATH_TEMPLATE = '"\\${endpoint}/\\${platform}-\\${arch}/cypress.zip"'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('desktop url from template with escaped dollar sign wrapped in quote', normalize(url))
|
||||
})
|
||||
|
||||
it('returns input if it is already an https link', () => {
|
||||
const url = 'https://somewhere.com'
|
||||
const result = download.getUrl('ARCH', url)
|
||||
|
||||
expect(result).to.equal(url)
|
||||
})
|
||||
|
||||
it('returns input if it is already an http link', () => {
|
||||
const url = 'http://local.com'
|
||||
const result = download.getUrl('ARCH', url)
|
||||
|
||||
expect(result).to.equal(url)
|
||||
})
|
||||
})
|
||||
|
||||
context('download base url from CYPRESS_DOWNLOAD_MIRROR env var', () => {
|
||||
it('env var', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_MIRROR = 'https://cypress.example.com'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('base url from CYPRESS_DOWNLOAD_MIRROR 1', normalize(url))
|
||||
})
|
||||
|
||||
it('env var with trailing slash', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_MIRROR = 'https://cypress.example.com/'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('base url from CYPRESS_DOWNLOAD_MIRROR with trailing slash 1', normalize(url))
|
||||
})
|
||||
|
||||
it('env var with subdirectory', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_MIRROR = 'https://cypress.example.com/example'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory 1', normalize(url))
|
||||
})
|
||||
|
||||
it('env var with subdirectory and trailing slash', () => {
|
||||
process.env.CYPRESS_DOWNLOAD_MIRROR = 'https://cypress.example.com/example/'
|
||||
const url = download.getUrl('ARCH', '0.20.2')
|
||||
|
||||
snapshot('base url from CYPRESS_DOWNLOAD_MIRROR with subdirectory and trailing slash 1', normalize(url))
|
||||
})
|
||||
})
|
||||
|
||||
it('saves example.zip to options.downloadDestination', function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
const onProgress = sinon.stub().returns(undefined)
|
||||
|
||||
return download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress },
|
||||
})
|
||||
.then((responseVersion: any) => {
|
||||
expect(responseVersion).to.eq('0.11.1')
|
||||
|
||||
return fs.statAsync(downloadDestination)
|
||||
})
|
||||
})
|
||||
|
||||
context('verify downloaded file', function () {
|
||||
before(function () {
|
||||
(this as any).expectedChecksum = hasha.fromFileSync(examplePath)
|
||||
|
||||
;(this as any).expectedFileSize = fs.statSync(examplePath).size
|
||||
|
||||
;(this as any).onProgress = sinon.stub().returns(undefined)
|
||||
debug('example file %s should have checksum %s and file size %d',
|
||||
examplePath, (this as any).expectedChecksum, (this as any).expectedFileSize)
|
||||
})
|
||||
|
||||
it('throws if file size is different from expected', function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
// definitely incorrect file size
|
||||
'content-length': '10',
|
||||
})
|
||||
|
||||
return expect(download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress: (this as any).onProgress },
|
||||
})).to.be.rejected
|
||||
})
|
||||
|
||||
it('throws if file size is different from expected x-amz-meta-size', function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
// definitely incorrect file size
|
||||
'x-amz-meta-size': '10',
|
||||
})
|
||||
|
||||
return expect(download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress: (this as any).onProgress },
|
||||
})).to.be.rejected
|
||||
})
|
||||
|
||||
it('throws if checksum is different from expected', function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': 'incorrect-checksum',
|
||||
})
|
||||
|
||||
return expect(download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress: (this as any).onProgress },
|
||||
})).to.be.rejected
|
||||
})
|
||||
|
||||
it('throws if checksum and file size are different from expected', function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': 'incorrect-checksum',
|
||||
'x-amz-meta-size': '10',
|
||||
})
|
||||
|
||||
return expect(download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress: (this as any).onProgress },
|
||||
})).to.be.rejected
|
||||
})
|
||||
|
||||
it('passes when checksum and file size match', function () {
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(200, () => {
|
||||
debug('creating read stream for %s', examplePath)
|
||||
|
||||
return fs.createReadStream(examplePath)
|
||||
}, {
|
||||
'x-amz-meta-checksum': (this as any).expectedChecksum,
|
||||
'x-amz-meta-size': String((this as any).expectedFileSize),
|
||||
})
|
||||
|
||||
debug('downloading %s to %s for test version %s',
|
||||
examplePath, (this as any).options.downloadDestination, (this as any).options.version)
|
||||
|
||||
return download.start({
|
||||
downloadDestination: (this as any).options.downloadDestination,
|
||||
version: (this as any).options.version,
|
||||
progress: { onProgress: (this as any).onProgress },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves with response x-version if present', function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
return download.start((this as any).options).then((responseVersion: any) => {
|
||||
expect(responseVersion).to.eq('0.11.1')
|
||||
})
|
||||
})
|
||||
|
||||
it('handles quadruple redirect with response x-version to the latest if present', function () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someone.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somebody.zip',
|
||||
'x-version': '0.11.2',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/something.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.4',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somebody.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/something.zip',
|
||||
'x-version': '0.11.3',
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someone.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
return download.start((this as any).options).then((responseVersion: any) => {
|
||||
expect(responseVersion).to.eq('0.11.4')
|
||||
})
|
||||
})
|
||||
|
||||
it('errors on too many redirects', async function () {
|
||||
function stubRedirects () {
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someone.zip',
|
||||
'x-version': '0.11.1',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someone.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somebody.zip',
|
||||
'x-version': '0.11.2',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somebody.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/something.zip',
|
||||
'x-version': '0.11.3',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/something.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewhat.zip',
|
||||
'x-version': '0.11.4',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewhat.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/sometime.zip',
|
||||
'x-version': '0.11.5',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/sometime.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewhen.zip',
|
||||
'x-version': '0.11.6',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewhen.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somewise.zip',
|
||||
'x-version': '0.11.7',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somewise.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/someways.zip',
|
||||
'x-version': '0.11.8',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/someways.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somerset.zip',
|
||||
'x-version': '0.11.9',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somerset.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/somedeal.zip',
|
||||
'x-version': '0.11.10',
|
||||
})
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/somedeal.zip')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.11.11',
|
||||
})
|
||||
}
|
||||
|
||||
stubRedirects()
|
||||
|
||||
await download.start((this as any).options).catch((error: any) => {
|
||||
expect(error).to.be.instanceof(Error)
|
||||
expect(error.message).to.contain('redirect loop')
|
||||
})
|
||||
|
||||
stubRedirects()
|
||||
|
||||
// Double check to make sure that raising redirectTTL changes result
|
||||
await download.start({ ...(this as any).options, redirectTTL: 12 }).then((responseVersion: any) => {
|
||||
expect(responseVersion).to.eq('0.11.11')
|
||||
})
|
||||
})
|
||||
|
||||
it('can specify cypress version in arguments', function () {
|
||||
(this as any).options.version = '0.13.0'
|
||||
|
||||
nock('https://aws.amazon.com')
|
||||
.get('/some.zip')
|
||||
.reply(200, () => {
|
||||
return fs.createReadStream(examplePath)
|
||||
})
|
||||
|
||||
nock('https://download.cypress.io')
|
||||
.get('/desktop/0.13.0')
|
||||
.query(true)
|
||||
.reply(302, undefined, {
|
||||
Location: 'https://aws.amazon.com/some.zip',
|
||||
'x-version': '0.13.0',
|
||||
})
|
||||
|
||||
return download.start((this as any).options).then((responseVersion: any) => {
|
||||
expect(responseVersion).to.eq('0.13.0')
|
||||
|
||||
return fs.statAsync(downloadDestination)
|
||||
})
|
||||
})
|
||||
|
||||
context('architecture detection', () => {
|
||||
context('Apple Silicon/M1', () => {
|
||||
function nockDarwinArm64 () {
|
||||
return nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query({ arch: 'arm64', platform: 'darwin' })
|
||||
.reply(200, undefined, {
|
||||
'x-version': '1.2.3',
|
||||
})
|
||||
}
|
||||
|
||||
it('downloads darwin-arm64 on M1', async function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
;(os.arch as any).returns('arm64')
|
||||
nockDarwinArm64()
|
||||
|
||||
const responseVersion = await download.start((this as any).options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.statAsync(downloadDestination)
|
||||
})
|
||||
|
||||
it('downloads darwin-arm64 on M1 translated by Rosetta', async function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
;(os.arch as any).returns('x64')
|
||||
nockDarwinArm64()
|
||||
|
||||
sinon.stub(cp, 'spawn').withArgs('sysctl', ['-n', 'sysctl.proc_translated'])
|
||||
.callsFake(mockSpawnModule.mockSpawn(((cp: any) => {
|
||||
cp.stdout.write('1')
|
||||
cp.emit('exit', 0, null)
|
||||
cp.end()
|
||||
})))
|
||||
|
||||
const responseVersion = await download.start((this as any).options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.statAsync(downloadDestination)
|
||||
})
|
||||
})
|
||||
|
||||
context('Linux arm64/aarch64', () => {
|
||||
function nockLinuxArm64 () {
|
||||
return nock('https://download.cypress.io')
|
||||
.get('/desktop/1.2.3')
|
||||
.query({ arch: 'arm64', platform: 'linux' })
|
||||
.reply(200, undefined, {
|
||||
'x-version': '1.2.3',
|
||||
})
|
||||
}
|
||||
|
||||
it('downloads linux-arm64 on arm64 processor', async function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(os.arch as any).returns('arm64')
|
||||
nockLinuxArm64()
|
||||
|
||||
const responseVersion = await download.start((this as any).options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.statAsync(downloadDestination)
|
||||
})
|
||||
|
||||
it('downloads linux-arm64 on non-arm64 node running on arm machine', async function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(os.arch as any).returns('x64')
|
||||
sinon.stub(cp, 'spawn')
|
||||
|
||||
for (const arch of ['aarch64_be', 'aarch64', 'armv8b', 'armv8l']) {
|
||||
nockLinuxArm64()
|
||||
|
||||
;(cp.spawn as any).withArgs('uname', ['-m'])
|
||||
.callsFake(mockSpawnModule.mockSpawn(((cp: any) => {
|
||||
cp.stdout.write(arch)
|
||||
cp.emit('exit', 0, null)
|
||||
cp.end()
|
||||
})))
|
||||
|
||||
const responseVersion = await download.start((this as any).options)
|
||||
|
||||
expect(responseVersion).to.eq('1.2.3')
|
||||
|
||||
await fs.statAsync(downloadDestination)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('catches download status errors and exits', function () {
|
||||
const ctx = this
|
||||
|
||||
const err: any = new Error()
|
||||
|
||||
err.statusCode = 404
|
||||
err.statusMessage = 'Not Found'
|
||||
|
||||
;(this as any).options.version = null
|
||||
|
||||
// not really the download error, but the easiest way to
|
||||
// test the error handling
|
||||
sinon.stub(fs, 'ensureDirAsync').rejects(err)
|
||||
|
||||
return download
|
||||
.start((this as any).options)
|
||||
.then(() => {
|
||||
throw new Error('should have caught')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
|
||||
return snapshot('download status errors 1', normalize((ctx as any).stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
context('with proxy env vars', () => {
|
||||
const testUriHttp = 'http://anything.com'
|
||||
const testUriHttps = 'https://anything.com'
|
||||
|
||||
beforeEach(function () {
|
||||
(this as any).env = _.clone(process.env)
|
||||
// prevent ambient environment masking of environment variables referenced in this test
|
||||
|
||||
;([
|
||||
'NO_PROXY', 'http_proxy',
|
||||
'https_proxy', 'npm_config_ca', 'npm_config_cafile',
|
||||
'npm_config_https_proxy', 'npm_config_proxy',
|
||||
]).forEach((e) => {
|
||||
delete process.env[e.toLowerCase()]
|
||||
delete process.env[e.toUpperCase()]
|
||||
})
|
||||
|
||||
// add a default no_proxy which does not match the testUri
|
||||
process.env.NO_PROXY = 'localhost,.org'
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
process.env = (this as any).env
|
||||
})
|
||||
|
||||
it('uses http_proxy on http request', () => {
|
||||
process.env.http_proxy = 'http://foo'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('http://foo')
|
||||
})
|
||||
|
||||
it('ignores http_proxy on https request', () => {
|
||||
process.env.http_proxy = 'http://foo'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq(null)
|
||||
process.env.https_proxy = 'https://bar'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
|
||||
})
|
||||
|
||||
it('falls back to npm_config_proxy', () => {
|
||||
process.env.npm_config_proxy = 'http://foo'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('http://foo')
|
||||
process.env.npm_config_https_proxy = 'https://bar'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
|
||||
process.env.https_proxy = 'https://baz'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://baz')
|
||||
})
|
||||
|
||||
it('respects no_proxy on http and https requests', () => {
|
||||
process.env.NO_PROXY = 'localhost,.com'
|
||||
|
||||
process.env.http_proxy = 'http://foo'
|
||||
process.env.https_proxy = 'https://bar'
|
||||
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq(null)
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq(null)
|
||||
})
|
||||
|
||||
it('ignores no_proxy for npm proxy configs, prefers https over http', () => {
|
||||
process.env.NO_PROXY = 'localhost,.com'
|
||||
|
||||
process.env.npm_config_proxy = 'http://foo'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('http://foo')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('http://foo')
|
||||
|
||||
process.env.npm_config_https_proxy = 'https://bar'
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttp)).to.eq('https://bar')
|
||||
expect(download.getProxyForUrlWithNpmConfig(testUriHttps)).to.eq('https://bar')
|
||||
})
|
||||
})
|
||||
|
||||
context('with CA and CAFILE env vars', () => {
|
||||
beforeEach(function () {
|
||||
(this as any).env = _.clone(process.env)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
process.env = (this as any).env
|
||||
})
|
||||
|
||||
it('returns undefined if not set', () => {
|
||||
return download.getCA().then((ca: any) => {
|
||||
expect(ca).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('returns CA from npm_config_ca', () => {
|
||||
process.env.npm_config_ca = 'foo'
|
||||
|
||||
return download.getCA().then((ca: any) => {
|
||||
expect(ca).to.eqls('foo')
|
||||
})
|
||||
})
|
||||
|
||||
it('returns CA from npm_config_cafile', () => {
|
||||
process.env.npm_config_cafile = 'test/fixture/cafile.pem'
|
||||
|
||||
return download.getCA().then((ca: any) => {
|
||||
expect(ca).to.eqls('bar\n')
|
||||
})
|
||||
})
|
||||
|
||||
it('returns undefined if failed reading npm_config_cafile', () => {
|
||||
process.env.npm_config_cafile = 'test/fixture/not-exists.pem'
|
||||
|
||||
return download.getCA().then((ca: any) => {
|
||||
expect(ca).to.be.undefined
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
694
cli/test/lib/tasks/install.spec.ts
Normal file
694
cli/test/lib/tasks/install.spec.ts
Normal file
@@ -0,0 +1,694 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import timers from 'timers/promises'
|
||||
import normalize from '../../support/normalize'
|
||||
import fs from 'fs-extra'
|
||||
import si from 'systeminformation'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import download from '../../../lib/tasks/download'
|
||||
import unzip from '../../../lib/tasks/unzip'
|
||||
import install from '../../../lib/tasks/install'
|
||||
import state from '../../../lib/tasks/state'
|
||||
import { Console } from 'console'
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('timers/promises', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
setTimeout: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
remove: vi.fn(),
|
||||
ensureDir: vi.fn(),
|
||||
pathExists: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
isCi: vi.fn(),
|
||||
isPostInstall: vi.fn(),
|
||||
getPlatformInfo: vi.fn(),
|
||||
isInstalledGlobally: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/download', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/unzip', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/tasks/state', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getVersionDir: vi.fn(),
|
||||
getBinaryDir: vi.fn(),
|
||||
getBinaryPkgAsync: vi.fn(),
|
||||
getCacheDir: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const packageVersion = '1.2.3'
|
||||
const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`)
|
||||
const installDir = '/cache/Cypress/1.2.3'
|
||||
|
||||
/**
|
||||
* NOTE: icons from listr2 do not render if process.stdout.isTTY is false,
|
||||
* which does not exist when running in a worker thread, which is commonly the case in Vitest.
|
||||
*
|
||||
* This means that the test environment implicitly uses the VerboseRenderer as a fallback,
|
||||
* where as the CLI uses the DefaultRenderer.
|
||||
*
|
||||
* This is the main reason the snapshots look different in testing mode vs when running the commands directly
|
||||
* via the CLI. This also allows us for our snapshot tests to be deterministic because we aren't rerendering icon states.
|
||||
*
|
||||
* @see https://listr2.kilic.dev/renderer/renderer.html#frontmatter-title
|
||||
*/
|
||||
describe('/lib/tasks/install', function () {
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
|
||||
// Direct console to process.stdout/stderr
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
vi.stubEnv('npm_config_loglevel', 'notice')
|
||||
|
||||
// allow simpler log message comparison without
|
||||
// chalk's terminal control strings
|
||||
chalk.level = 0
|
||||
|
||||
originalConsole = globalThis.console
|
||||
// Redirect console output to a custom stream or mock
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
chalk.level = 3
|
||||
})
|
||||
|
||||
describe('.start', function () {
|
||||
beforeEach(async () => {
|
||||
logger.reset()
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isCi.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isPostInstall.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue(packageVersion)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
download.start.mockResolvedValue(packageVersion)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
unzip.start.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getVersionDir.mockReturnValue('/cache/Cypress/1.2.3')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getBinaryDir.mockReturnValue('/cache/Cypress/1.2.3/Cypress.app')
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.remove.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.ensureDir.mockResolvedValue(undefined)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
timers.setTimeout.mockResolvedValue(undefined)
|
||||
|
||||
const actualUtil = (await vi.importActual<typeof import('../../../lib/util')>('../../../lib/util')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
util.getPlatformInfo.mockImplementation(actualUtil.getPlatformInfo)
|
||||
})
|
||||
|
||||
describe('skips install', function () {
|
||||
it('when environment variable is set', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', '0')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).not.toHaveBeenCalled()
|
||||
|
||||
expect(output()).toMatchSnapshot('skip installation 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('non-stable builds', () => {
|
||||
const buildInfo = {
|
||||
stable: false,
|
||||
commitSha: '3b7f0b5c59def1e9b5f385bd585c9b2836706c29',
|
||||
commitBranch: 'aBranchName',
|
||||
commitDate: new Date('1996-11-27').toISOString(),
|
||||
}
|
||||
|
||||
it('install from a constructed CDN URL', async function () {
|
||||
await install.start({ buildInfo })
|
||||
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: 'https://cdn.cypress.io/beta/binary/0.0.0-development/darwin-x64/aBranchName-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/cypress.zip',
|
||||
}))
|
||||
})
|
||||
|
||||
it('logs a warning about installing a pre-release', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await install.start({ buildInfo })
|
||||
expect(output()).toMatchSnapshot('pre-release warning')
|
||||
})
|
||||
|
||||
it('installs to the expected pre-release cache dir', async function () {
|
||||
const actualState = (await vi.importActual<typeof import('../../../lib/tasks/state')>('../../../lib/tasks/state')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
state.getVersionDir.mockImplementation(actualState.getVersionDir)
|
||||
|
||||
await install.start({ buildInfo })
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir: expect.stringMatching(/\/Cypress\/beta\-1\.2\.3\-aBranchName\-3b7f0b5c$/),
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('override version', function () {
|
||||
it('warns when specifying cypress version in env', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
const version = '0.12.1'
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', version)
|
||||
|
||||
await install.start()
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
zipFilePath: downloadDestination,
|
||||
}))
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('specify version in env vars 1')
|
||||
})
|
||||
|
||||
it('trims environment variable before installing', async function () {
|
||||
// note how the version has extra spaces around it on purpose
|
||||
const filename = '/tmp/local/file.zip'
|
||||
const version = ` ${filename} `
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', version)
|
||||
|
||||
// internally, the variable should be trimmed and just filename checked
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === filename) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
zipFilePath: filename,
|
||||
installDir,
|
||||
}))
|
||||
})
|
||||
|
||||
it('removes double quotes around the environment variable before installing', async function () {
|
||||
// note how the version has extra spaces around it on purpose
|
||||
// and there are double quotes
|
||||
const filename = '/tmp/local/file.zip'
|
||||
const version = ` "${filename}" `
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', version)
|
||||
// internally, the variable should be trimmed, double quotes removed
|
||||
// and just filename checked against the file system
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === filename) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
zipFilePath: filename,
|
||||
installDir,
|
||||
}))
|
||||
})
|
||||
|
||||
it('can install local binary zip file without download from absolute path', async function () {
|
||||
const version = '/tmp/local/file.zip'
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', version)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === version) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
zipFilePath: version,
|
||||
installDir,
|
||||
}))
|
||||
})
|
||||
|
||||
it('can install local binary zip file from relative path', async function () {
|
||||
const version = './cypress-resources/file.zip'
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === version) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', version)
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).not.toHaveBeenCalled()
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
zipFilePath: path.resolve(version),
|
||||
installDir,
|
||||
}))
|
||||
})
|
||||
|
||||
describe('when version is already installed', function () {
|
||||
beforeEach(function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: packageVersion })
|
||||
})
|
||||
|
||||
it('doesn\'t attempt to download', async function () {
|
||||
await install.start()
|
||||
|
||||
expect(download.start).not.toHaveBeenCalled()
|
||||
expect(state.getBinaryPkgAsync).toHaveBeenCalledWith('/cache/Cypress/1.2.3/Cypress.app')
|
||||
})
|
||||
|
||||
it('logs \'skipping install\' when explicit cypress install', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('version already installed - cypress install 1')
|
||||
})
|
||||
|
||||
it('logs when already installed when run from postInstall', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isPostInstall.mockReturnValue(true)
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('version already installed - postInstall 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when getting installed version fails', function () {
|
||||
it('logs message and starts download', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue(null)
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: packageVersion,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir,
|
||||
}))
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('continues installing on failure 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is no install version', function () {
|
||||
it('logs message and starts download', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue(null)
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: packageVersion,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir,
|
||||
}))
|
||||
|
||||
// cleans up the zip file
|
||||
expect(fs.remove).toHaveBeenCalledWith(
|
||||
downloadDestination,
|
||||
)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('installs without existing installation 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when getting installed version does not match needed version', function () {
|
||||
it('logs message and starts download', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: 'x.x.x' })
|
||||
|
||||
await install.start()
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: packageVersion,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir,
|
||||
}))
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('installed version does not match needed version 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with force: true', function () {
|
||||
it('logs message and starts download', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: packageVersion })
|
||||
|
||||
await install.start({ force: true })
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: packageVersion,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir,
|
||||
}))
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('forcing true always installs 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('as a global install', function () {
|
||||
it('logs global warning and download', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isInstalledGlobally.mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: 'x.x.x' })
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
version: packageVersion,
|
||||
}))
|
||||
|
||||
expect(unzip.start).toHaveBeenCalledWith(expect.objectContaining({
|
||||
installDir,
|
||||
}))
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('warning installing as global 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when running in CI', function () {
|
||||
it('uses verbose renderer', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isCi.mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: 'x.x.x' })
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('installing in ci 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('failed write access to cache directory', function () {
|
||||
it('logs error on failure', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
state.getCacheDir.mockReturnValue('/invalid/cache/dir')
|
||||
|
||||
const err: any = new Error('EACCES: permission denied, mkdir \'/invalid\'')
|
||||
|
||||
err.code = 'EACCES'
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
fs.ensureDir.mockRejectedValue(err)
|
||||
|
||||
try {
|
||||
await install.start()
|
||||
throw new Error('should have caught error')
|
||||
} catch (err) {
|
||||
expect(err.message).not.toEqual('should have caught error')
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('invalid cache directory 1')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('CYPRESS_INSTALL_BINARY is URL or Zip', function () {
|
||||
it('uses cache when correct version installed given URL', async function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: '1.2.3' })
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', 'www.cypress.io/cannot-download/2.4.5')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cache when mismatch version given URL', async function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: '1.2.3' })
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('4.0.0')
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', 'www.cypress.io/cannot-download/2.4.5')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(download.start).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cache when correct version installed given Zip', async function () {
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === '/path/to/zip.zip') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: '1.2.3' })
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', '/path/to/zip.zip')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(unzip.start).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cache when mismatch version given Zip ', async function () {
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === '/path/to/zip.zip') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
state.getBinaryPkgAsync.mockResolvedValue({ version: '1.2.3' })
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('4.0.0')
|
||||
|
||||
vi.stubEnv('CYPRESS_INSTALL_BINARY', '/path/to/zip.zip')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(unzip.start).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('is silent when log level is silent', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'silent')
|
||||
|
||||
await install.start()
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('silent install 1')
|
||||
})
|
||||
|
||||
it('exits with error when installing on unsupported os', async function () {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
util.getPlatformInfo.mockResolvedValue('Platform: win32-ia32')
|
||||
|
||||
try {
|
||||
await install.start()
|
||||
throw new Error('should have caught error')
|
||||
} catch (err) {
|
||||
expect(err.message).not.toEqual('should have caught error')
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('error when installing on unsupported os')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('._getBinaryUrlFromBuildInfo', function () {
|
||||
const buildInfo = {
|
||||
commitSha: 'abc123',
|
||||
commitBranch: 'aBranchName',
|
||||
}
|
||||
|
||||
it('generates the expected URL', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
expect(install._getBinaryUrlFromBuildInfo('x64', buildInfo)).toEqual(`https://cdn.cypress.io/beta/binary/0.0.0-development/linux-x64/aBranchName-abc123/cypress.zip`)
|
||||
})
|
||||
|
||||
it('overrides win32-arm64 to win32-x64 for pre-release', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
expect(install._getBinaryUrlFromBuildInfo('arm64', buildInfo))
|
||||
.to.eq(`https://cdn.cypress.io/beta/binary/0.0.0-development/win32-x64/aBranchName-abc123/cypress.zip`)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,520 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import BluebirdPromise from 'bluebird'
|
||||
import mockfs from 'mock-fs'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import stdout from '../../support/stdout'
|
||||
import normalize from '../../support/normalize'
|
||||
import fs from '../../../lib/fs'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import download from '../../../lib/tasks/download'
|
||||
import unzip from '../../../lib/tasks/unzip'
|
||||
import install from '../../../lib/tasks/install'
|
||||
import state from '../../../lib/tasks/state'
|
||||
|
||||
const packageVersion = '1.2.3'
|
||||
const downloadDestination = path.join(os.tmpdir(), `cypress-${process.pid}.zip`)
|
||||
const installDir = '/cache/Cypress/1.2.3'
|
||||
|
||||
describe('/lib/tasks/install', function () {
|
||||
before(async function () {
|
||||
const mochaMain = await import('mocha-banner')
|
||||
|
||||
mochaMain.register()
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
(this as any).stdout = stdout.capture()
|
||||
|
||||
// allow simpler log message comparison without
|
||||
// chalk's terminal control strings
|
||||
chalk.level = 0
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
stdout.restore()
|
||||
|
||||
chalk.level = 3
|
||||
})
|
||||
|
||||
context('.start', function () {
|
||||
beforeEach(function () {
|
||||
logger.reset()
|
||||
|
||||
sinon.stub(util, 'isCi').returns(false)
|
||||
sinon.stub(util, 'isPostInstall').returns(false)
|
||||
sinon.stub(util, 'pkgVersion').returns(packageVersion)
|
||||
sinon.stub(download, 'start').resolves(packageVersion)
|
||||
sinon.stub(unzip, 'start').resolves()
|
||||
sinon.stub(BluebirdPromise, 'delay').resolves()
|
||||
sinon.stub(fs, 'removeAsync').resolves()
|
||||
sinon.stub(state, 'getVersionDir').returns('/cache/Cypress/1.2.3')
|
||||
sinon.stub(state, 'getBinaryDir').returns('/cache/Cypress/1.2.3/Cypress.app')
|
||||
sinon.stub(state, 'getBinaryPkgAsync').resolves()
|
||||
sinon.stub(fs, 'ensureDirAsync').resolves(undefined)
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
})
|
||||
|
||||
describe('skips install', function () {
|
||||
it('when environment variable is set', function () {
|
||||
process.env.CYPRESS_INSTALL_BINARY = '0'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).not.to.be.called
|
||||
|
||||
snapshot(
|
||||
'skip installation 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('non-stable builds', () => {
|
||||
const buildInfo = {
|
||||
stable: false,
|
||||
commitSha: '3b7f0b5c59def1e9b5f385bd585c9b2836706c29',
|
||||
commitBranch: 'aBranchName',
|
||||
commitDate: new Date('1996-11-27').toISOString(),
|
||||
}
|
||||
|
||||
function runInstall () {
|
||||
return install.start({ buildInfo })
|
||||
}
|
||||
|
||||
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-3b7f0b5c59def1e9b5f385bd585c9b2836706c29/cypress.zip',
|
||||
})
|
||||
})
|
||||
|
||||
it('logs a warning about installing a pre-release', async function () {
|
||||
await runInstall()
|
||||
snapshot(normalize((this as any).stdout.toString()))
|
||||
})
|
||||
|
||||
it('installs to the expected pre-release cache dir', async function () {
|
||||
(state.getVersionDir as any).restore()
|
||||
await runInstall()
|
||||
expect(unzip.start).to.be.calledWithMatch({ installDir: sinon.match(/\/Cypress\/beta\-1\.2\.3\-aBranchName\-3b7f0b5c$/) })
|
||||
})
|
||||
})
|
||||
|
||||
describe('override version', function () {
|
||||
it('warns when specifying cypress version in env', function () {
|
||||
const version = '0.12.1'
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = version
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
zipFilePath: downloadDestination,
|
||||
})
|
||||
|
||||
snapshot(
|
||||
'specify version in env vars 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('trims environment variable before installing', function () {
|
||||
// note how the version has extra spaces around it on purpose
|
||||
const filename = '/tmp/local/file.zip'
|
||||
const version = ` ${filename} `
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = version
|
||||
// internally, the variable should be trimmed and just filename checked
|
||||
sinon.stub(fs, 'pathExistsAsync').withArgs(filename).resolves(true)
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
zipFilePath: filename,
|
||||
installDir,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('removes double quotes around the environment variable before installing', function () {
|
||||
// note how the version has extra spaces around it on purpose
|
||||
// and there are double quotes
|
||||
const filename = '/tmp/local/file.zip'
|
||||
const version = ` "${filename}" `
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = version
|
||||
// internally, the variable should be trimmed, double quotes removed
|
||||
// and just filename checked against the file system
|
||||
sinon.stub(fs, 'pathExistsAsync').withArgs(filename).resolves(true)
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
zipFilePath: filename,
|
||||
installDir,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can install local binary zip file without download from absolute path', function () {
|
||||
const version = '/tmp/local/file.zip'
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = version
|
||||
sinon.stub(fs, 'pathExistsAsync').withArgs(version).resolves(true)
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
zipFilePath: version,
|
||||
installDir,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('can install local binary zip file from relative path', function () {
|
||||
const version = './cypress-resources/file.zip'
|
||||
|
||||
mockfs({
|
||||
[version]: 'asdf',
|
||||
})
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = version
|
||||
|
||||
const installDir = state.getVersionDir()
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).not.to.be.called
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
zipFilePath: path.resolve(version),
|
||||
installDir,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when version is already installed', function () {
|
||||
beforeEach(function () {
|
||||
(state.getBinaryPkgAsync as any).resolves({ version: packageVersion })
|
||||
})
|
||||
|
||||
it('doesn\'t attempt to download', function () {
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).not.to.be.called
|
||||
expect(state.getBinaryPkgAsync).to.be.calledWith('/cache/Cypress/1.2.3/Cypress.app')
|
||||
})
|
||||
})
|
||||
|
||||
it('logs \'skipping install\' when explicit cypress install', function () {
|
||||
return install.start()
|
||||
.then(() => {
|
||||
return snapshot(
|
||||
'version already installed - cypress install 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('logs when already installed when run from postInstall', function () {
|
||||
(util.isPostInstall as any).returns(true)
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
snapshot(
|
||||
'version already installed - postInstall 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when getting installed version fails', function () {
|
||||
beforeEach(function () {
|
||||
(state.getBinaryPkgAsync as any).resolves(null)
|
||||
|
||||
return install.start()
|
||||
})
|
||||
|
||||
it('logs message and starts download', function () {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version: packageVersion,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
installDir,
|
||||
})
|
||||
|
||||
snapshot(
|
||||
'continues installing on failure 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is no install version', function () {
|
||||
beforeEach(function () {
|
||||
(state.getBinaryPkgAsync as any).resolves(null)
|
||||
|
||||
return install.start()
|
||||
})
|
||||
|
||||
it('logs message and starts download', function () {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version: packageVersion,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
installDir,
|
||||
})
|
||||
|
||||
// cleans up the zip file
|
||||
expect(fs.removeAsync).to.be.calledWith(
|
||||
downloadDestination,
|
||||
)
|
||||
|
||||
snapshot(
|
||||
'installs without existing installation 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when getting installed version does not match needed version', function () {
|
||||
beforeEach(function () {
|
||||
(state.getBinaryPkgAsync as any).resolves({ version: 'x.x.x' })
|
||||
|
||||
return install.start()
|
||||
})
|
||||
|
||||
it('logs message and starts download', function () {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version: packageVersion,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
installDir,
|
||||
})
|
||||
|
||||
snapshot(
|
||||
'installed version does not match needed version 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with force: true', function () {
|
||||
beforeEach(function () {
|
||||
(state.getBinaryPkgAsync as any).resolves({ version: packageVersion })
|
||||
|
||||
return install.start({ force: true })
|
||||
})
|
||||
|
||||
it('logs message and starts download', function () {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version: packageVersion,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
installDir,
|
||||
})
|
||||
|
||||
snapshot(
|
||||
'forcing true always installs 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('as a global install', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(util, 'isInstalledGlobally').returns(true)
|
||||
|
||||
;(state.getBinaryPkgAsync as any).resolves({ version: 'x.x.x' })
|
||||
|
||||
return install.start()
|
||||
})
|
||||
|
||||
it('logs global warning and download', function () {
|
||||
expect(download.start).to.be.calledWithMatch({
|
||||
version: packageVersion,
|
||||
})
|
||||
|
||||
expect(unzip.start).to.be.calledWithMatch({
|
||||
installDir,
|
||||
})
|
||||
|
||||
snapshot(
|
||||
'warning installing as global 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when running in CI', function () {
|
||||
beforeEach(function () {
|
||||
(util.isCi as any).returns(true)
|
||||
|
||||
;(state.getBinaryPkgAsync as any).resolves({ version: 'x.x.x' })
|
||||
|
||||
return install.start()
|
||||
})
|
||||
|
||||
it('uses verbose renderer', function () {
|
||||
snapshot(
|
||||
'installing in ci 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('failed write access to cache directory', function () {
|
||||
it('logs error on failure', function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
sinon.stub(state, 'getCacheDir').returns('/invalid/cache/dir')
|
||||
|
||||
const err: any = new Error('EACCES: permission denied, mkdir \'/invalid\'')
|
||||
|
||||
err.code = 'EACCES'
|
||||
|
||||
;(fs.ensureDirAsync as any).rejects(err)
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
throw new Error('should have caught error')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
|
||||
snapshot(
|
||||
'invalid cache directory 1',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('CYPRESS_INSTALL_BINARY is URL or Zip', function () {
|
||||
it('uses cache when correct version installed given URL', function () {
|
||||
(state.getBinaryPkgAsync as any).resolves({ version: '1.2.3' })
|
||||
|
||||
;(util.pkgVersion as any).returns('1.2.3')
|
||||
process.env.CYPRESS_INSTALL_BINARY = 'www.cypress.io/cannot-download/2.4.5'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).to.not.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('uses cache when mismatch version given URL ', function () {
|
||||
(state.getBinaryPkgAsync as any).resolves({ version: '1.2.3' })
|
||||
|
||||
;(util.pkgVersion as any).returns('4.0.0')
|
||||
process.env.CYPRESS_INSTALL_BINARY = 'www.cypress.io/cannot-download/2.4.5'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(download.start).to.not.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('uses cache when correct version installed given Zip', function () {
|
||||
sinon.stub(fs, 'pathExistsAsync').withArgs('/path/to/zip.zip').resolves(true)
|
||||
|
||||
;(state.getBinaryPkgAsync as any).resolves({ version: '1.2.3' })
|
||||
|
||||
;(util.pkgVersion as any).returns('1.2.3')
|
||||
|
||||
process.env.CYPRESS_INSTALL_BINARY = '/path/to/zip.zip'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(unzip.start).to.not.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('uses cache when mismatch version given Zip ', function () {
|
||||
sinon.stub(fs, 'pathExistsAsync').withArgs('/path/to/zip.zip').resolves(true)
|
||||
|
||||
;(state.getBinaryPkgAsync as any).resolves({ version: '1.2.3' })
|
||||
|
||||
;(util.pkgVersion as any).returns('4.0.0')
|
||||
process.env.CYPRESS_INSTALL_BINARY = '/path/to/zip.zip'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
expect(unzip.start).to.not.be.called
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('is silent when log level is silent', function () {
|
||||
process.env.npm_config_loglevel = 'silent'
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
return snapshot(
|
||||
'silent install 1',
|
||||
normalize(`[no output]${(this as any).stdout.toString()}`),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('exits with error when installing on unsupported os', function () {
|
||||
sinon.stub(util, 'getPlatformInfo').resolves('Platform: win32-ia32')
|
||||
|
||||
return install.start()
|
||||
.then(() => {
|
||||
throw new Error('should have caught error')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
|
||||
snapshot(
|
||||
'error when installing on unsupported os',
|
||||
normalize((this as any).stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('._getBinaryUrlFromBuildInfo', function () {
|
||||
const buildInfo = {
|
||||
commitSha: 'abc123',
|
||||
commitBranch: 'aBranchName',
|
||||
}
|
||||
|
||||
it('generates the expected URL', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
expect(install._getBinaryUrlFromBuildInfo('x64', buildInfo))
|
||||
.to.eq(`https://cdn.cypress.io/beta/binary/0.0.0-development/linux-x64/aBranchName-abc123/cypress.zip`)
|
||||
})
|
||||
|
||||
it('overrides win32-arm64 to win32-x64 for pre-release', () => {
|
||||
(os.platform as any).returns('win32')
|
||||
|
||||
expect(install._getBinaryUrlFromBuildInfo('arm64', buildInfo))
|
||||
.to.eq(`https://cdn.cypress.io/beta/binary/0.0.0-development/win32-x64/aBranchName-abc123/cypress.zip`)
|
||||
})
|
||||
})
|
||||
})
|
||||
478
cli/test/lib/tasks/state.spec.ts
Normal file
478
cli/test/lib/tasks/state.spec.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import createDebug from 'debug'
|
||||
import fs from 'fs-extra'
|
||||
import { cwd } from 'process'
|
||||
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
|
||||
vi.mock('path', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
cwd: vi.fn(),
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
resolve: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('process', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
cwd: vi.fn(),
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
cwd: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('fs-extra', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pathExists: vi.fn(),
|
||||
readJson: vi.fn(),
|
||||
outputJson: vi.fn(),
|
||||
realpath: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
getCacheDir: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const cacheDir = path.join('.cache/Cypress')
|
||||
const versionDir = path.join(cacheDir, '1.2.3')
|
||||
const binaryDir = path.join(versionDir, 'Cypress.app')
|
||||
const binaryPkgPath = path.join(
|
||||
binaryDir,
|
||||
'Contents',
|
||||
'Resources',
|
||||
'app',
|
||||
'package.json',
|
||||
)
|
||||
|
||||
describe('lib/tasks/state', function () {
|
||||
beforeEach(async function () {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
logger.reset()
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.getCacheDir.mockReturnValue(cacheDir)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue('1.2.3')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
|
||||
const actualProcess = (await vi.importActual<typeof import('process')>('process')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cwd.mockImplementation(() => {
|
||||
return actualProcess.cwd()
|
||||
})
|
||||
|
||||
const actualPath = (await vi.importActual<typeof import('path')>('path')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
path.resolve.mockImplementation((...args) => {
|
||||
return actualPath.resolve.apply(actualPath, args)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getBinaryPkgVersion', function () {
|
||||
it('returns version if present', () => {
|
||||
expect(state.getBinaryPkgVersion({ version: '1.2.3' })).toEqual('1.2.3')
|
||||
})
|
||||
|
||||
it('returns null if passed null', () => {
|
||||
expect(state.getBinaryPkgVersion(null)).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getBinaryPkgAsync', function () {
|
||||
it('resolves with loaded file when the file exists', async function () {
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === binaryPkgPath) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.readJson.mockImplementation((args) => {
|
||||
if (args === binaryPkgPath) {
|
||||
return { version: '2.0.48' }
|
||||
}
|
||||
})
|
||||
|
||||
const result = await state.getBinaryPkgAsync(binaryDir)
|
||||
|
||||
expect(result).toEqual({ version: '2.0.48' })
|
||||
})
|
||||
|
||||
it('returns null if no version found', async function () {
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === binaryPkgPath) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const result = await state.getBinaryPkgAsync(binaryDir)
|
||||
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('returns correct version if passed binaryDir', async function () {
|
||||
const customBinaryDir = '/custom/binary/dir'
|
||||
const customBinaryPackageDir =
|
||||
'/custom/binary/dir/Contents/Resources/app/package.json'
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === customBinaryPackageDir) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.readJson.mockImplementation((args) => {
|
||||
if (args === customBinaryPackageDir) {
|
||||
return { version: '3.4.5' }
|
||||
}
|
||||
})
|
||||
|
||||
const result = await state.getBinaryPkgAsync(customBinaryDir)
|
||||
|
||||
expect(result).toEqual({ version: '3.4.5' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getPathToExecutable', function () {
|
||||
it('resolves path on macOS', function () {
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).toEqual(
|
||||
'.cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress',
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on linux', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).toEqual(
|
||||
'.cache/Cypress/1.2.3/Cypress/Cypress',
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on windows', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).toMatch(/\.exe$/)
|
||||
})
|
||||
|
||||
it('resolves from custom binaryDir', function () {
|
||||
expect(state.getPathToExecutable('home/downloads/cypress.app')).toEqual(
|
||||
'home/downloads/cypress.app/Contents/MacOS/Cypress',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getBinaryDir', function () {
|
||||
it('resolves path on macOS', function () {
|
||||
expect(state.getBinaryDir()).toEqual(
|
||||
path.join(versionDir, 'Cypress.app'),
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on linux', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
expect(state.getBinaryDir()).toEqual(path.join(versionDir, 'Cypress'))
|
||||
})
|
||||
|
||||
it('resolves path on windows', async function () {
|
||||
vi.doMock('path', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
default: actual.default.win32,
|
||||
}
|
||||
})
|
||||
|
||||
vi.resetModules()
|
||||
const stateWithWin32Path = (await import('../../../lib/tasks/state')).default
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
const pathToExec = stateWithWin32Path.getBinaryDir()
|
||||
|
||||
expect(pathToExec).to.be.equal(path.win32.join(versionDir, 'Cypress'))
|
||||
})
|
||||
|
||||
it('resolves path to binary/installation directory', function () {
|
||||
expect(state.getBinaryDir()).toEqual(binaryDir)
|
||||
})
|
||||
|
||||
it('resolves path to binary/installation from version', function () {
|
||||
expect(state.getBinaryDir('4.5.6')).toEqual(
|
||||
path.join(cacheDir, '4.5.6', 'Cypress.app'),
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects on anything else', function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('unknown')
|
||||
expect(() => {
|
||||
return state.getBinaryDir()
|
||||
}).toThrow('Platform: "unknown" is not supported.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getBinaryVerifiedAsync', function () {
|
||||
it('resolves true if verified', async function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.readJson.mockResolvedValue({ verified: true })
|
||||
|
||||
const isVerified = await state.getBinaryVerifiedAsync('/asdf')
|
||||
|
||||
expect(isVerified).toEqual(true)
|
||||
})
|
||||
|
||||
it('resolves undefined if not verified', async function () {
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = 'ENOENT'
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
fs.readJson.mockRejectedValue(err)
|
||||
|
||||
const isVerified = await state.getBinaryVerifiedAsync('/asdf')
|
||||
|
||||
expect(isVerified).toEqual(undefined)
|
||||
})
|
||||
|
||||
it('can accept custom binaryDir', async function () {
|
||||
// note how the binary state file is in the runner's parent folder
|
||||
const customBinaryDir = '/custom/binary/1.2.3/runner'
|
||||
const binaryStatePath = '/custom/binary/1.2.3/binary_state.json'
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.pathExists.mockImplementation((args) => {
|
||||
if (args === binaryStatePath) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.readJson.mockImplementation((args) => {
|
||||
if (args === binaryStatePath) {
|
||||
return { verified: true }
|
||||
}
|
||||
})
|
||||
|
||||
const isVerified = await state.getBinaryVerifiedAsync(customBinaryDir)
|
||||
|
||||
expect(isVerified).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.writeBinaryVerified', function () {
|
||||
const binaryStateFilename = path.join(versionDir, 'binary_state.json')
|
||||
|
||||
it('writes to binary state verified:true', async function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.outputJson.mockResolvedValue()
|
||||
|
||||
await state.writeBinaryVerifiedAsync(true, binaryDir)
|
||||
|
||||
expect(fs.outputJson).toHaveBeenCalledWith(binaryStateFilename, { verified: true }, { spaces: 2 })
|
||||
})
|
||||
|
||||
it('write to binary state verified:false', async function () {
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
fs.outputJson.mockResolvedValue()
|
||||
|
||||
await state.writeBinaryVerifiedAsync(false, binaryDir)
|
||||
|
||||
expect(fs.outputJson).toHaveBeenCalledWith(
|
||||
binaryStateFilename,
|
||||
{ verified: false },
|
||||
{ spaces: 2 },
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getCacheDir', function () {
|
||||
beforeEach(async function () {
|
||||
vi.unstubAllEnvs()
|
||||
})
|
||||
|
||||
it('uses cachedir()', function () {
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).toEqual(cacheDir)
|
||||
})
|
||||
|
||||
it('uses env variable CYPRESS_CACHE_FOLDER', function () {
|
||||
vi.stubEnv('CYPRESS_CACHE_FOLDER', '/path/to/dir')
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).toEqual('/path/to/dir')
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from relative path', () => {
|
||||
vi.stubEnv('CYPRESS_CACHE_FOLDER', './local-cache/folder')
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).toEqual(path.resolve('local-cache/folder'))
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from relative path during postinstall', async () => {
|
||||
vi.stubEnv('CYPRESS_CACHE_FOLDER', './local-cache/folder')
|
||||
// simulates current folder when running "npm postinstall" hook
|
||||
// @ts-expect-error - mockReturnValue
|
||||
cwd.mockReturnValue('/my/project/folder/node_modules/cypress')
|
||||
|
||||
// @ts-expect-error - default import
|
||||
const actualPath = (await vi.importActual<typeof import('path')>('path')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
path.resolve.mockImplementation((...args) => {
|
||||
return actualPath.resolve('/my/project/folder/node_modules/cypress', args[0])
|
||||
})
|
||||
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('returned cache dir %s', ret)
|
||||
expect(ret).toEqual(actualPath.resolve('/my/project/folder/local-cache/folder'))
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from absolute path during postinstall', () => {
|
||||
vi.stubEnv('CYPRESS_CACHE_FOLDER', '/cache/folder/Cypress')
|
||||
|
||||
// simulates current folder when running "npm postinstall" hook
|
||||
// @ts-expect-error - mockReturnValue
|
||||
cwd.mockReturnValue('/my/project/folder/node_modules/cypress')
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('returned cache dir %s', ret)
|
||||
expect(ret).to.eql(path.resolve('/cache/folder/Cypress'))
|
||||
})
|
||||
|
||||
it('resolves ~ with user home folder', () => {
|
||||
const homeDir = os.homedir()
|
||||
|
||||
vi.stubEnv('CYPRESS_CACHE_FOLDER', '~/.cache/Cypress')
|
||||
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('cache dir is "%s"', ret)
|
||||
expect(path.isAbsolute(ret), ret).toEqual(true)
|
||||
expect(ret, '~ has been resolved').not.toContain('~')
|
||||
expect(ret, 'replaced ~ with home directory').toEqual(`${homeDir}/.cache/Cypress`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.parseRealPlatformBinaryFolderAsync', function () {
|
||||
beforeEach(function () {
|
||||
// @ts-expect-error - mockImplementation
|
||||
fs.realpath.mockImplementation((path) => Promise.resolve(path))
|
||||
})
|
||||
|
||||
it('can parse on darwin', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync(
|
||||
'/Documents/Cypress.app/Contents/MacOS/Cypress',
|
||||
)
|
||||
|
||||
expect(path).toEqual('/Documents/Cypress.app')
|
||||
})
|
||||
|
||||
it('can parse on linux', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress')
|
||||
|
||||
expect(path).toEqual('/Documents/Cypress')
|
||||
})
|
||||
|
||||
it('can parse on darwin', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
|
||||
expect(path).toEqual('/Documents/Cypress')
|
||||
})
|
||||
|
||||
it('throws when invalid on darwin', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
|
||||
expect(path).toEqual(false)
|
||||
})
|
||||
|
||||
it('throws when invalid on linux', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
|
||||
expect(path).toEqual(false)
|
||||
})
|
||||
|
||||
it('throws when invalid on windows', async function () {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
const path = await state.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress')
|
||||
|
||||
expect(path).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,382 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import BluebirdPromise from 'bluebird'
|
||||
import mockfs from 'mock-fs'
|
||||
import { expect } from 'chai'
|
||||
import createDebug from 'debug'
|
||||
import fs from '../../../lib/fs'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import state from '../../../lib/tasks/state'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const cacheDir = path.join('.cache/Cypress')
|
||||
const versionDir = path.join(cacheDir, '1.2.3')
|
||||
const binaryDir = path.join(versionDir, 'Cypress.app')
|
||||
const binaryPkgPath = path.join(
|
||||
binaryDir,
|
||||
'Contents',
|
||||
'Resources',
|
||||
'app',
|
||||
'package.json',
|
||||
)
|
||||
|
||||
describe('lib/tasks/state', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(util, 'getCacheDir').returns(cacheDir)
|
||||
logger.reset()
|
||||
sinon.stub(process, 'exit')
|
||||
sinon.stub(util, 'pkgVersion').returns('1.2.3')
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
})
|
||||
|
||||
context('.getBinaryPkgVersion', function () {
|
||||
it('returns version if present', () => {
|
||||
expect(state.getBinaryPkgVersion({ version: '1.2.3' })).to.equal('1.2.3')
|
||||
})
|
||||
|
||||
it('returns null if passed null', () => {
|
||||
expect(state.getBinaryPkgVersion(null)).to.equal(null)
|
||||
})
|
||||
})
|
||||
|
||||
context('.getBinaryPkgAsync', function () {
|
||||
it('resolves with loaded file when the file exists', function () {
|
||||
sinon
|
||||
.stub(fs, 'pathExistsAsync')
|
||||
.withArgs(binaryPkgPath)
|
||||
.resolves(true)
|
||||
|
||||
sinon
|
||||
.stub(fs, 'readJsonAsync')
|
||||
.withArgs(binaryPkgPath)
|
||||
.resolves({ version: '2.0.48' })
|
||||
|
||||
return state.getBinaryPkgAsync(binaryDir).then((result: any) => {
|
||||
expect(result).to.deep.equal({ version: '2.0.48' })
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null if no version found', function () {
|
||||
sinon.stub(fs, 'pathExistsAsync').resolves(false)
|
||||
|
||||
return state
|
||||
.getBinaryPkgAsync(binaryDir)
|
||||
.then((result: any) => {
|
||||
return expect(result).to.equal(null)
|
||||
})
|
||||
})
|
||||
|
||||
it('returns correct version if passed binaryDir', function () {
|
||||
const customBinaryDir = '/custom/binary/dir'
|
||||
const customBinaryPackageDir =
|
||||
'/custom/binary/dir/Contents/Resources/app/package.json'
|
||||
|
||||
sinon
|
||||
.stub(fs, 'pathExistsAsync')
|
||||
.withArgs(customBinaryPackageDir)
|
||||
.resolves(true)
|
||||
|
||||
sinon
|
||||
.stub(fs, 'readJsonAsync')
|
||||
.withArgs(customBinaryPackageDir)
|
||||
.resolves({ version: '3.4.5' })
|
||||
|
||||
return state
|
||||
.getBinaryPkgAsync(customBinaryDir)
|
||||
.then((result: any) => {
|
||||
return expect(result).to.deep.equal({ version: '3.4.5' })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getPathToExecutable', function () {
|
||||
it('resolves path on macOS', function () {
|
||||
const macExecutable =
|
||||
'.cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress'
|
||||
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).to.equal(
|
||||
macExecutable,
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on linux', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
const linuxExecutable = '.cache/Cypress/1.2.3/Cypress/Cypress'
|
||||
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).to.equal(
|
||||
linuxExecutable,
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on windows', function () {
|
||||
(os.platform as any).returns('win32')
|
||||
expect(state.getPathToExecutable(state.getBinaryDir())).to.match(/\.exe$/)
|
||||
})
|
||||
|
||||
it('resolves from custom binaryDir', function () {
|
||||
const customBinaryDir = 'home/downloads/cypress.app'
|
||||
|
||||
expect(state.getPathToExecutable(customBinaryDir)).to.equal(
|
||||
'home/downloads/cypress.app/Contents/MacOS/Cypress',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
context('.getBinaryDir', function () {
|
||||
it('resolves path on macOS', function () {
|
||||
expect(state.getBinaryDir()).to.equal(
|
||||
path.join(versionDir, 'Cypress.app'),
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves path on linux', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
expect(state.getBinaryDir()).to.equal(path.join(versionDir, 'Cypress'))
|
||||
})
|
||||
|
||||
it('resolves path on windows', async function () {
|
||||
const proxyquire = await import('proxyquire')
|
||||
const stateWithWin32Path = proxyquire.default(`../../../lib/tasks/state`, { path: path.win32 }).default
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
const pathToExec = stateWithWin32Path.getBinaryDir()
|
||||
|
||||
expect(pathToExec).to.be.equal(path.win32.join(versionDir, 'Cypress'))
|
||||
})
|
||||
|
||||
it('resolves path to binary/installation directory', function () {
|
||||
expect(state.getBinaryDir()).to.equal(binaryDir)
|
||||
})
|
||||
|
||||
it('resolves path to binary/installation from version', function () {
|
||||
expect(state.getBinaryDir('4.5.6')).to.be.equal(
|
||||
path.join(cacheDir, '4.5.6', 'Cypress.app'),
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects on anything else', function () {
|
||||
(os.platform as any).returns('unknown')
|
||||
expect(() => {
|
||||
return state.getBinaryDir().to.throw('Platform: "unknown" is not supported.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getBinaryVerifiedAsync', function () {
|
||||
it('resolves true if verified', function () {
|
||||
sinon.stub(fs, 'readJsonAsync').resolves({ verified: true })
|
||||
|
||||
return state
|
||||
.getBinaryVerifiedAsync('/asdf')
|
||||
.then((isVerified: any) => {
|
||||
return expect(isVerified).to.be.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves undefined if not verified', function () {
|
||||
const err: any = new Error()
|
||||
|
||||
err.code = 'ENOENT'
|
||||
sinon.stub(fs, 'readJsonAsync').rejects(err)
|
||||
|
||||
return state
|
||||
.getBinaryVerifiedAsync('/asdf')
|
||||
.then((isVerified: any) => {
|
||||
return expect(isVerified).to.be.equal(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
it('can accept custom binaryDir', function () {
|
||||
// note how the binary state file is in the runner's parent folder
|
||||
const customBinaryDir = '/custom/binary/1.2.3/runner'
|
||||
const binaryStatePath = '/custom/binary/1.2.3/binary_state.json'
|
||||
|
||||
sinon
|
||||
.stub(fs, 'pathExistsAsync')
|
||||
.withArgs(binaryStatePath)
|
||||
.resolves(true)
|
||||
|
||||
sinon
|
||||
.stub(fs, 'readJsonAsync')
|
||||
.withArgs(binaryStatePath)
|
||||
.resolves({ verified: true })
|
||||
|
||||
return state
|
||||
.getBinaryVerifiedAsync(customBinaryDir)
|
||||
.then((isVerified: any) => {
|
||||
return expect(isVerified).to.be.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.writeBinaryVerified', function () {
|
||||
const binaryStateFilename = path.join(versionDir, 'binary_state.json')
|
||||
|
||||
beforeEach(() => {
|
||||
mockfs({})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockfs.restore()
|
||||
})
|
||||
|
||||
it('writes to binary state verified:true', function () {
|
||||
sinon.stub(fs, 'outputJsonAsync').resolves()
|
||||
|
||||
return state
|
||||
.writeBinaryVerifiedAsync(true, binaryDir)
|
||||
.then(
|
||||
() => {
|
||||
return expect(fs.outputJsonAsync).to.be.calledWith(
|
||||
binaryStateFilename,
|
||||
{ verified: true },
|
||||
)
|
||||
},
|
||||
{ spaces: 2 },
|
||||
)
|
||||
})
|
||||
|
||||
it('write to binary state verified:false', function () {
|
||||
sinon.stub(fs, 'outputJsonAsync').resolves()
|
||||
|
||||
return state
|
||||
.writeBinaryVerifiedAsync(false, binaryDir)
|
||||
.then(() => {
|
||||
return expect(fs.outputJsonAsync).to.be.calledWith(
|
||||
binaryStateFilename,
|
||||
{ verified: false },
|
||||
{ spaces: 2 },
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getCacheDir', function () {
|
||||
it('uses cachedir()', function () {
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).to.equal(cacheDir)
|
||||
})
|
||||
|
||||
it('uses env variable CYPRESS_CACHE_FOLDER', function () {
|
||||
process.env.CYPRESS_CACHE_FOLDER = '/path/to/dir'
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).to.equal('/path/to/dir')
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from relative path', () => {
|
||||
process.env.CYPRESS_CACHE_FOLDER = './local-cache/folder'
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
expect(ret).to.eql(path.resolve('local-cache/folder'))
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from relative path during postinstall', () => {
|
||||
process.env.CYPRESS_CACHE_FOLDER = './local-cache/folder'
|
||||
// simulates current folder when running "npm postinstall" hook
|
||||
sinon.stub(process, 'cwd').returns('/my/project/folder/node_modules/cypress')
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('returned cache dir %s', ret)
|
||||
expect(ret).to.eql(path.resolve('/my/project/folder/local-cache/folder'))
|
||||
})
|
||||
|
||||
it('CYPRESS_CACHE_FOLDER resolves from absolute path during postinstall', () => {
|
||||
process.env.CYPRESS_CACHE_FOLDER = '/cache/folder/Cypress'
|
||||
// simulates current folder when running "npm postinstall" hook
|
||||
sinon.stub(process, 'cwd').returns('/my/project/folder/node_modules/cypress')
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('returned cache dir %s', ret)
|
||||
expect(ret).to.eql(path.resolve('/cache/folder/Cypress'))
|
||||
})
|
||||
|
||||
it('resolves ~ with user home folder', () => {
|
||||
const homeDir = os.homedir()
|
||||
|
||||
process.env.CYPRESS_CACHE_FOLDER = '~/.cache/Cypress'
|
||||
|
||||
const ret = state.getCacheDir()
|
||||
|
||||
debug('cache dir is "%s"', ret)
|
||||
expect(path.isAbsolute(ret), ret).to.be.true
|
||||
expect(ret, '~ has been resolved').to.not.contain('~')
|
||||
expect(ret, 'replaced ~ with home directory').to.equal(`${homeDir}/.cache/Cypress`)
|
||||
})
|
||||
})
|
||||
|
||||
context('.parseRealPlatformBinaryFolderAsync', function () {
|
||||
beforeEach(function () {
|
||||
sinon.stub(fs, 'realpathAsync').callsFake((path: any) => {
|
||||
return BluebirdPromise.resolve(path)
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse on darwin', function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync(
|
||||
'/Documents/Cypress.app/Contents/MacOS/Cypress',
|
||||
)
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql('/Documents/Cypress.app')
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse on linux', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress')
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql('/Documents/Cypress')
|
||||
})
|
||||
})
|
||||
|
||||
it('can parse on darwin', function () {
|
||||
(os.platform as any).returns('win32')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql('/Documents/Cypress')
|
||||
})
|
||||
})
|
||||
|
||||
it('throws when invalid on darwin', function () {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws when invalid on linux', function () {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress.exe')
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws when invalid on windows', function () {
|
||||
(os.platform as any).returns('win32')
|
||||
|
||||
return state
|
||||
.parseRealPlatformBinaryFolderAsync('/Documents/Cypress/Cypress')
|
||||
.then((path: any) => {
|
||||
return expect(path).to.eql(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
393
cli/test/lib/tasks/unzip.spec.ts
Normal file
393
cli/test/lib/tasks/unzip.spec.ts
Normal file
@@ -0,0 +1,393 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import events from 'events'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import extract from 'extract-zip'
|
||||
import cp from 'child_process'
|
||||
import createDebug from 'debug'
|
||||
import readline from 'readline'
|
||||
import fs from 'fs-extra'
|
||||
import si from 'systeminformation'
|
||||
import { Console } from 'console'
|
||||
|
||||
import normalize from '../../support/normalize'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import unzip from '../../../lib/tasks/unzip'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const version = '1.2.3'
|
||||
const installDir = path.join(os.tmpdir(), 'Cypress', version)
|
||||
|
||||
vi.mock('extract-zip')
|
||||
vi.mock('child_process')
|
||||
vi.mock('readline')
|
||||
vi.mock('fs-extra')
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
pkgVersion: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/tasks/unzip', function () {
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(async function () {
|
||||
vi.resetAllMocks()
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue(version)
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
// @ts-expect-error - default import
|
||||
const actualExtract = (await vi.importActual<typeof import('extract-zip')>('extract-zip')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
extract.mockImplementation(actualExtract)
|
||||
|
||||
// @ts-expect-error - default import
|
||||
const actualChildProcess = (await vi.importActual<typeof import('child_process')>('child_process')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cp.spawn.mockImplementation(actualChildProcess.spawn)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
readline.createInterface.mockImplementation(() => {
|
||||
return {
|
||||
on: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
originalConsole = globalThis.console
|
||||
// Redirect console output to a custom stream or mock
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
})
|
||||
|
||||
it('throws when cannot unzip', async function () {
|
||||
const stdout = createStdoutCapture()
|
||||
|
||||
try {
|
||||
await unzip.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'bad_example.zip'),
|
||||
installDir,
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
return expect(normalize(stdout())).toMatchSnapshot()
|
||||
}
|
||||
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('throws max path length error when cannot unzip due to realpath ENOENT on windows', async function () {
|
||||
const stdout = createStdoutCapture()
|
||||
|
||||
const err: any = new Error('failed')
|
||||
|
||||
err.code = 'ENOENT'
|
||||
err.syscall = 'realpath'
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
fs.ensureDir.mockRejectedValue(err)
|
||||
|
||||
try {
|
||||
await unzip.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'bad_example.zip'),
|
||||
installDir,
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
return expect(normalize(stdout())).toMatchSnapshot()
|
||||
}
|
||||
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('can really unzip', async function () {
|
||||
const onProgress = vi.fn().mockReturnValue(undefined)
|
||||
|
||||
await unzip.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'example.zip'),
|
||||
installDir,
|
||||
progress: { onProgress },
|
||||
})
|
||||
|
||||
expect(onProgress).toHaveBeenCalled()
|
||||
|
||||
await fs.stat(installDir)
|
||||
})
|
||||
|
||||
describe('on linux', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
})
|
||||
|
||||
it('can try unzip first then fall back to node unzip', async function () {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
extract.mockImplementation((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').toEqual(zipFilePath)
|
||||
|
||||
return Promise.resolve(undefined)
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stdout = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stderr = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cp.spawn.mockImplementation((args: string) => {
|
||||
if (args === 'unzip') {
|
||||
return unzipChildProcess
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
await unzip.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn).toHaveBeenCalledExactlyOnceWith('unzip', ['-o', zipFilePath, '-d', installDir])
|
||||
expect(extract).toHaveBeenCalledExactlyOnceWith(zipFilePath, expect.objectContaining({
|
||||
dir: installDir,
|
||||
onEntry: expect.any(Function),
|
||||
}))
|
||||
})
|
||||
|
||||
it('can try unzip first then fall back to node unzip and fails with an empty error', async function () {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
extract.mockRejectedValue(undefined)
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stdout = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stderr = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cp.spawn.mockImplementation((args: string) => {
|
||||
if (args === 'unzip') {
|
||||
return unzipChildProcess
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
try {
|
||||
await unzip.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
expect(err.message).toMatch('Unknown error with Node extract tool')
|
||||
|
||||
return
|
||||
}
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('calls node unzip just once', async function () {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
extract.mockImplementation((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').toEqual(zipFilePath)
|
||||
|
||||
return Promise.resolve(undefined)
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stdout = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stderr = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cp.spawn.mockImplementation((args: string) => {
|
||||
if (args === 'unzip') {
|
||||
return unzipChildProcess
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip close')
|
||||
unzipChildProcess.emit('close', 1)
|
||||
}, 110)
|
||||
|
||||
await unzip
|
||||
.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn).toHaveBeenCalledExactlyOnceWith('unzip', ['-o', zipFilePath, '-d', installDir])
|
||||
expect(extract).toHaveBeenCalledExactlyOnceWith(zipFilePath, expect.objectContaining({
|
||||
dir: installDir,
|
||||
onEntry: expect.any(Function),
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('on Mac', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
})
|
||||
|
||||
it('calls node unzip just once', async function () {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
extract.mockImplementation((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').toEqual(zipFilePath)
|
||||
|
||||
return Promise.resolve(undefined)
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stdout = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mocking process
|
||||
unzipChildProcess.stderr = {
|
||||
on: vi.fn(),
|
||||
}
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
cp.spawn.mockImplementation((args: string) => {
|
||||
if (args === 'ditto') {
|
||||
return unzipChildProcess
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting ditto error')
|
||||
unzipChildProcess.emit('error', new Error('ditto fails badly'))
|
||||
}, 100)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting ditto close')
|
||||
unzipChildProcess.emit('close', 1)
|
||||
}, 110)
|
||||
|
||||
await unzip.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn).toHaveBeenCalledExactlyOnceWith('ditto', ['-xkV', zipFilePath, installDir])
|
||||
expect(extract).toHaveBeenCalledExactlyOnceWith(zipFilePath, expect.objectContaining({
|
||||
dir: installDir,
|
||||
onEntry: expect.any(Function),
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,283 +0,0 @@
|
||||
import '../../spec_helper'
|
||||
import events from 'events'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import cp from 'child_process'
|
||||
import createDebug from 'debug'
|
||||
import readline from 'readline'
|
||||
import stdout from '../../support/stdout'
|
||||
import normalize from '../../support/normalize'
|
||||
import fs from '../../../lib/fs'
|
||||
import logger from '../../../lib/logger'
|
||||
import util from '../../../lib/util'
|
||||
import unzip from '../../../lib/tasks/unzip'
|
||||
|
||||
const debug = createDebug('test')
|
||||
|
||||
const version = '1.2.3'
|
||||
const installDir = path.join(os.tmpdir(), 'Cypress', version)
|
||||
|
||||
describe('lib/tasks/unzip', function () {
|
||||
before(async function () {
|
||||
const mochaMain = await import('mocha-banner')
|
||||
|
||||
mochaMain.register()
|
||||
})
|
||||
|
||||
beforeEach(function () {
|
||||
(this as any).stdout = stdout.capture()
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
sinon.stub(util, 'pkgVersion').returns(version)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
stdout.restore()
|
||||
})
|
||||
|
||||
it('throws when cannot unzip', async function () {
|
||||
try {
|
||||
await unzip.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'bad_example.zip'),
|
||||
installDir,
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
return snapshot(normalize((this as any).stdout.toString()))
|
||||
}
|
||||
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('throws max path length error when cannot unzip due to realpath ENOENT on windows', async function () {
|
||||
const err: any = new Error('failed')
|
||||
|
||||
err.code = 'ENOENT'
|
||||
err.syscall = 'realpath'
|
||||
|
||||
;(os.platform as any).returns('win32')
|
||||
sinon.stub(fs, 'ensureDirAsync').rejects(err)
|
||||
|
||||
try {
|
||||
await unzip.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'bad_example.zip'),
|
||||
installDir,
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
return snapshot(normalize((this as any).stdout.toString()))
|
||||
}
|
||||
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('can really unzip', function () {
|
||||
const onProgress = sinon.stub().returns(undefined)
|
||||
|
||||
return unzip
|
||||
.start({
|
||||
zipFilePath: path.join('test', 'fixture', 'example.zip'),
|
||||
installDir,
|
||||
progress: { onProgress },
|
||||
})
|
||||
.then(() => {
|
||||
expect(onProgress).to.be.called
|
||||
|
||||
return fs.statAsync(installDir)
|
||||
})
|
||||
})
|
||||
|
||||
context('on linux', () => {
|
||||
beforeEach(() => {
|
||||
(os.platform as any).returns('linux')
|
||||
})
|
||||
|
||||
it('can try unzip first then fall back to node unzip', function (done) {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
sinon.stub(unzip.utils.unzipTools, 'extract').callsFake((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').to.equal(zipFilePath)
|
||||
|
||||
return new Promise((resolve, reject) => resolve(undefined))
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
;(unzipChildProcess as any).stdout = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
;(unzipChildProcess as any).stderr = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
sinon.stub(cp, 'spawn').withArgs('unzip').returns(unzipChildProcess as any)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
unzip
|
||||
.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
.then(() => {
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn, 'unzip spawn').to.have.been.calledWith('unzip')
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called').to.be.calledWith(zipFilePath)
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called once').to.be.calledOnce
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('can try unzip first then fall back to node unzip and fails with an empty error', async function () {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
sinon.stub(unzip.utils.unzipTools, 'extract').callsFake(() => {
|
||||
return new Promise((_, reject) => reject())
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
;(unzipChildProcess as any).stdout = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
;(unzipChildProcess as any).stderr = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
sinon.stub(cp, 'spawn').withArgs('unzip').returns(unzipChildProcess as any)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
try {
|
||||
await unzip
|
||||
.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
} catch (err: any) {
|
||||
logger.error(err)
|
||||
expect(err.message).to.include('Unknown error with Node extract tool')
|
||||
|
||||
return
|
||||
}
|
||||
throw new Error('should have failed')
|
||||
})
|
||||
|
||||
it('calls node unzip just once', function (done) {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
sinon.stub(unzip.utils.unzipTools, 'extract').callsFake((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').to.equal(zipFilePath)
|
||||
|
||||
return new Promise((resolve, reject) => resolve(undefined))
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
;(unzipChildProcess as any).stdout = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
;(unzipChildProcess as any).stderr = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
sinon.stub(cp, 'spawn').withArgs('unzip').returns(unzipChildProcess as any)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip error')
|
||||
unzipChildProcess.emit('error', new Error('unzip fails badly'))
|
||||
}, 100)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting unzip close')
|
||||
unzipChildProcess.emit('close', 1)
|
||||
}, 110)
|
||||
|
||||
unzip
|
||||
.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
.then(() => {
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn, 'unzip spawn').to.have.been.calledWith('unzip')
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called').to.be.calledWith(zipFilePath)
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called once').to.be.calledOnce
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('on Mac', () => {
|
||||
beforeEach(() => {
|
||||
(os.platform as any).returns('darwin')
|
||||
})
|
||||
|
||||
it('calls node unzip just once', function (done) {
|
||||
const zipFilePath = path.join('test', 'fixture', 'example.zip')
|
||||
|
||||
sinon.stub(unzip.utils.unzipTools, 'extract').callsFake((filePath: any, opts: any) => {
|
||||
debug('unzip extract called with %s', filePath)
|
||||
expect(filePath, 'zipfile is the same').to.equal(zipFilePath)
|
||||
|
||||
return new Promise((resolve) => resolve(undefined))
|
||||
})
|
||||
|
||||
const unzipChildProcess = new events.EventEmitter()
|
||||
|
||||
;(unzipChildProcess as any).stdout = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
;(unzipChildProcess as any).stderr = {
|
||||
on () {},
|
||||
}
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
sinon.stub(cp, 'spawn').withArgs('ditto').returns(unzipChildProcess as any)
|
||||
sinon.stub(readline, 'createInterface').returns({
|
||||
on: () => {},
|
||||
} as any)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting ditto error')
|
||||
unzipChildProcess.emit('error', new Error('ditto fails badly'))
|
||||
}, 100)
|
||||
|
||||
setTimeout(() => {
|
||||
debug('emitting ditto close')
|
||||
unzipChildProcess.emit('close', 1)
|
||||
}, 110)
|
||||
|
||||
unzip
|
||||
.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
})
|
||||
.then(() => {
|
||||
debug('checking if unzip was called')
|
||||
expect(cp.spawn, 'unzip spawn').to.have.been.calledWith('ditto')
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called').to.be.calledWith(zipFilePath)
|
||||
expect(unzip.utils.unzipTools.extract, 'extract called once').to.be.calledOnce
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
981
cli/test/lib/tasks/verify.spec.ts
Normal file
981
cli/test/lib/tasks/verify.spec.ts
Normal file
@@ -0,0 +1,981 @@
|
||||
import { vi, describe, it, beforeEach, afterEach, expect, MockInstance } from 'vitest'
|
||||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
import os from 'os'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import mockfs from 'mock-fs'
|
||||
import normalize from '../../support/normalize'
|
||||
import { geteuid } from 'process'
|
||||
import { Console } from 'console'
|
||||
import fs from 'fs-extra'
|
||||
import si from 'systeminformation'
|
||||
import _xvfb from '@cypress/xvfb'
|
||||
|
||||
import util from '../../../lib/util'
|
||||
import logger from '../../../lib/logger'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
import { verifyTestRunnerTimeoutMs, start, needsSandbox } from '../../../lib/tasks/verify'
|
||||
|
||||
const packageVersion = '1.2.3'
|
||||
const cacheDir = '/cache/Cypress'
|
||||
const executablePath = '/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress'
|
||||
const binaryStatePath = '/cache/Cypress/1.2.3/binary_state.json'
|
||||
const DEFAULT_VERIFY_TIMEOUT = 30000
|
||||
|
||||
vi.mock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@cypress/xvfb', async () => {
|
||||
const XVFB_MOCK = vi.fn()
|
||||
|
||||
XVFB_MOCK.prototype.start = vi.fn()
|
||||
|
||||
return {
|
||||
default: XVFB_MOCK,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('lodash', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
random: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('process', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
geteuid: vi.fn(),
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
geteuid: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
platform: vi.fn(),
|
||||
release: vi.fn(),
|
||||
arch: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/exec/xvfb', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
isNeeded: vi.fn(),
|
||||
startAsync: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../lib/util', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
default: {
|
||||
// @ts-expect-error
|
||||
...actual.default,
|
||||
getCacheDir: vi.fn(),
|
||||
isCi: vi.fn(),
|
||||
pkgVersion: vi.fn(),
|
||||
exec: vi.fn(),
|
||||
getOsVersionAsync: vi.fn(),
|
||||
isPossibleLinuxWithIncorrectDisplay: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('lib/tasks/verify', () => {
|
||||
const createStdoutCapture = () => {
|
||||
const logs: string[] = []
|
||||
// eslint-disable-next-line no-console
|
||||
const originalOut = process.stdout.write
|
||||
|
||||
vi.spyOn(process.stdout, 'write').mockImplementation((strOrBugger: string | Uint8Array<ArrayBufferLike>) => {
|
||||
logs.push(strOrBugger as string)
|
||||
|
||||
return originalOut(strOrBugger)
|
||||
})
|
||||
|
||||
return () => logs.join('')
|
||||
}
|
||||
let spawnedProcess: any
|
||||
// Direct console to process.stdout/stderr
|
||||
let originalConsole: Console
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'notice')
|
||||
|
||||
originalConsole = globalThis.console
|
||||
|
||||
globalThis.console = new Console(process.stdout, process.stderr)
|
||||
|
||||
spawnedProcess = {
|
||||
code: 0,
|
||||
stderr: vi.fn(),
|
||||
stdout: '222',
|
||||
}
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.release.mockReturnValue('0.0.0')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.arch.mockReturnValue('x64')
|
||||
// @ts-expect-error mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Foo',
|
||||
release: 'OsVersion',
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.getCacheDir.mockReturnValue(cacheDir)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isCi.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.pkgVersion.mockReturnValue(packageVersion)
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
xvfb.start.mockResolvedValue()
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
xvfb.stop.mockResolvedValue()
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(1000)
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
_.random.mockReturnValue(222)
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
util.exec.mockImplementation((...args: any) => {
|
||||
if (args[0] === executablePath && _.isEqual(args[1], ['--no-sandbox', '--smoke-test', '--ping=222'])) {
|
||||
return Promise.resolve(spawnedProcess)
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('should have caught error'))
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.console = originalConsole // Restore original console
|
||||
mockfs.restore()
|
||||
})
|
||||
|
||||
it('has verify task timeout', () => {
|
||||
expect(verifyTestRunnerTimeoutMs()).to.eql(DEFAULT_VERIFY_TIMEOUT)
|
||||
})
|
||||
|
||||
it('accepts custom verify task timeout', () => {
|
||||
vi.stubEnv('CYPRESS_VERIFY_TIMEOUT', '500000')
|
||||
expect(verifyTestRunnerTimeoutMs()).toEqual(500000)
|
||||
})
|
||||
|
||||
it('accepts custom verify task timeout from npm', async () => {
|
||||
vi.stubEnv('npm_config_CYPRESS_VERIFY_TIMEOUT', '600000')
|
||||
expect(verifyTestRunnerTimeoutMs()).toEqual(600000)
|
||||
})
|
||||
|
||||
it('falls back to default verify task timeout if custom value is invalid', async () => {
|
||||
vi.stubEnv('CYPRESS_VERIFY_TIMEOUT', 'foobar')
|
||||
expect(verifyTestRunnerTimeoutMs()).toEqual(DEFAULT_VERIFY_TIMEOUT)
|
||||
})
|
||||
|
||||
it('returns early when `CYPRESS_SKIP_VERIFY` is set to true', async () => {
|
||||
vi.stubEnv('CYPRESS_SKIP_VERIFY', 'true')
|
||||
|
||||
const result = await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(result).toEqual(undefined)
|
||||
})
|
||||
|
||||
it('logs error and exits when no version of Cypress is installed', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
throw new Error('should have caught error')
|
||||
} catch (err) {
|
||||
expect(err.message).not.toContain('should have caught error')
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
|
||||
it('adds --no-sandbox when user is root', async () => {
|
||||
// make it think the executable exists
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(0) // user is root
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(util.exec).toHaveBeenCalledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything())
|
||||
})
|
||||
|
||||
it('adds --no-sandbox when user is non-root', async () => {
|
||||
// make it think the executable exists
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(1000) // user is non-root
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(util.exec).toHaveBeenCalledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything())
|
||||
})
|
||||
|
||||
it('is noop when binary is already verified', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// make it think the executable exists and is verified
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(output()).toEqual('')
|
||||
|
||||
expect(util.exec).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('logs warning when installed version does not match verified version', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: 'bloop',
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('logs error and exits when executable cannot be found', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
throw new Error('should have caught error')
|
||||
} catch (err) {
|
||||
expect(err.message).not.toContain('should have caught error')
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
|
||||
it('logs error when child process hangs', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
util.exec.mockRejectedValue({
|
||||
stderr: 'some stderr',
|
||||
stdout: 'some stdout',
|
||||
timedOut: true,
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ smokeTestTimeout: 1, listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
|
||||
it('logs error when child process returns incorrect stdout (stderr when exists)', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
util.exec.mockRejectedValue({
|
||||
stderr: 'some stderr',
|
||||
stdout: 'some stdout',
|
||||
code: 0,
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ smokeTestTimeout: 1, listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
|
||||
it('logs error when child process returns incorrect stdout (stdout when no stderr)', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
util.exec.mockRejectedValue({
|
||||
stdout: 'some stdout',
|
||||
code: 0,
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ smokeTestTimeout: 1, listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
expect(normalize(output())).toMatchSnapshot()
|
||||
}
|
||||
})
|
||||
|
||||
describe('FORCE_COLOR', () => {
|
||||
beforeEach(() => {
|
||||
// vi.unstubAllEnvs()
|
||||
vi.stubEnv('FORCE_COLOR', 'true')
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/28982
|
||||
it('sets FORCE_COLOR to 0 when piping stdioOptions to to the smoke test to avoid ANSI in binary smoke test', async () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
util.exec.mockResolvedValue({
|
||||
stdout: '222',
|
||||
stderr: '',
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(util.exec).toHaveBeenCalledWith(
|
||||
executablePath,
|
||||
['--no-sandbox', '--smoke-test', '--ping=222'],
|
||||
expect.objectContaining({ env: expect.objectContaining({ FORCE_COLOR: '0' }) }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with force: true', () => {
|
||||
beforeEach(() => {
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('shows full path to executable when verifying', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await start({ force: true, listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('verification with executable')
|
||||
})
|
||||
|
||||
it('clears verified version from state if verification fails', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
util.exec.mockRejectedValue({
|
||||
code: 1,
|
||||
stderr: 'an error about dependencies',
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ force: true, listrRenderer: 'silent' })
|
||||
throw new Error('Should have thrown')
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
}
|
||||
|
||||
const exists = await fs.pathExists(binaryStatePath)
|
||||
|
||||
expect(exists).toEqual(false)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('fails verifying Cypress')
|
||||
})
|
||||
})
|
||||
|
||||
describe('smoke test with DEBUG output', () => {
|
||||
beforeEach(() => {
|
||||
const stdoutWithDebugOutput = stripIndent`
|
||||
some debug output
|
||||
date: more debug output
|
||||
222
|
||||
after that more text
|
||||
`
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
util.exec.mockImplementation((...args: any) => {
|
||||
if (args[0] === executablePath) {
|
||||
return Promise.resolve({
|
||||
stdout: stdoutWithDebugOutput,
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('should have caught error'))
|
||||
})
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('finds ping value in the verbose output', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('verbose stdout output')
|
||||
})
|
||||
})
|
||||
|
||||
describe('smoke test retries on bad display with our Xvfb', () => {
|
||||
let loggerWarnSpy: MockInstance<(...messages: any[]) => void>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('DISPLAY', 'test-display')
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// sinon.spy(logger, 'warn')
|
||||
loggerWarnSpy = vi.spyOn(logger, 'warn')
|
||||
})
|
||||
|
||||
it('successfully retries with our Xvfb on Linux', async () => {
|
||||
// initially we think the user has everything set
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isPossibleLinuxWithIncorrectDisplay.mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - mockImplementationOnce
|
||||
util.exec.mockImplementationOnce((...args: any) => {
|
||||
const firstSpawnError: any = new Error('')
|
||||
|
||||
// this message contains typical Gtk error shown if X11 is incorrect
|
||||
// like in the case of DISPLAY=987
|
||||
firstSpawnError.stderr = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
and maybe a few other lines here with weird indent
|
||||
`
|
||||
|
||||
firstSpawnError.stdout = ''
|
||||
|
||||
// the second time the binary returns expected ping
|
||||
// @ts-expect-error - mockImplementationOnce
|
||||
util.exec.mockImplementationOnce((...args: any) => {
|
||||
if (args[0] === executablePath) {
|
||||
return Promise.resolve({
|
||||
stdout: '222',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.reject(firstSpawnError)
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(util.exec).toHaveBeenCalledTimes(2)
|
||||
// user should have been warned
|
||||
expect(loggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining(
|
||||
'This is likely due to a misconfigured DISPLAY environment variable.',
|
||||
))
|
||||
})
|
||||
|
||||
it('fails on both retries with our Xvfb on Linux', async () => {
|
||||
// initially we think the user has everything set
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(false)
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isPossibleLinuxWithIncorrectDisplay.mockReturnValue(true)
|
||||
|
||||
// @ts-expect-error - mockImplementationOnce
|
||||
util.exec.mockImplementationOnce((...args: any) => {
|
||||
// @ts-expect-error - mockImplementationOnce
|
||||
os.platform.mockReturnValue('linux')
|
||||
expect(xvfb.start).not.toHaveBeenCalled()
|
||||
|
||||
const firstSpawnError: any = new Error('')
|
||||
|
||||
// this message contains typical Gtk error shown if X11 is incorrect
|
||||
// like in the case of DISPLAY=987
|
||||
firstSpawnError.stderr = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
and maybe a few other lines here with weird indent
|
||||
`
|
||||
|
||||
firstSpawnError.stdout = ''
|
||||
|
||||
// the second time it runs, it fails for some other reason
|
||||
const secondMessage = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
some other error
|
||||
again with
|
||||
some weird indent
|
||||
`
|
||||
|
||||
// @ts-expect-error - mockImplementationOnce
|
||||
util.exec.mockImplementationOnce((...args: any) => {
|
||||
if (args[0] === executablePath) {
|
||||
return Promise.reject(new Error(secondMessage))
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.reject(firstSpawnError)
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (e) {
|
||||
expect(util.exec).toHaveBeenCalledTimes(2)
|
||||
// second time around we should have called Xvfb
|
||||
expect(xvfb.start).toHaveBeenCalledOnce
|
||||
expect(xvfb.stop).toHaveBeenCalledOnce
|
||||
|
||||
// user should have been warned
|
||||
expect(loggerWarnSpy).toHaveBeenCalledWith(expect.stringContaining('DISPLAY was set to: "test-display"'))
|
||||
|
||||
expect(e.message).toMatchSnapshot('tried to verify twice, on the first try got the DISPLAY error')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have failed')
|
||||
})
|
||||
|
||||
it('logs an error if Cypress executable does not exist', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: false,
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('no Cypress executable')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
|
||||
it('logs an error if Cypress executable does not have permissions', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
mockfs.restore()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o666 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('Cypress non-executable permission')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
|
||||
it('logs and runs when current version has not been verified', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('current version has not been verified')
|
||||
})
|
||||
|
||||
it('logs and runs when installed version is different than package version', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: '7.8.9',
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('different version installed')
|
||||
})
|
||||
|
||||
it('is silent when logLevel is silent', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
vi.stubEnv('npm_config_loglevel', 'silent')
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('silent verify')
|
||||
})
|
||||
|
||||
it('turns off Opening Cypress...', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: '7.8.9',
|
||||
})
|
||||
|
||||
await start({ welcomeMessage: false })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('no welcome message')
|
||||
})
|
||||
|
||||
it('logs error when fails smoke test unexpectedly without stderr', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
util.exec.mockRejectedValue({
|
||||
stderr: '',
|
||||
stdout: '',
|
||||
message: 'Error: EPERM NOT PERMITTED',
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('fails with no stderr')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on linux', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
xvfb.isNeeded.mockReturnValue(true)
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('starts xvfb', async () => {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(xvfb.start).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('stops xvfb on spawned process close', async () => {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(xvfb.stop).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('logs error and exits when starting xvfb fails', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
const actualXvfb = (await vi.importActual<typeof import('../../../lib/exec/xvfb')>('../../../lib/exec/xvfb')).default
|
||||
|
||||
// @ts-expect-error - mockImplementation to test integration with xvfb module
|
||||
xvfb.start.mockImplementation(actualXvfb.start)
|
||||
|
||||
const err: any = new Error('test without xvfb')
|
||||
|
||||
err.nonZeroExitCode = true
|
||||
err.stack = 'xvfb? no dice'
|
||||
|
||||
// stub the xvfb module to test integration
|
||||
vi.spyOn(_xvfb.prototype, 'start').mockImplementation((cb) => {
|
||||
// mock a failure
|
||||
cb(err)
|
||||
})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
expect(xvfb.stop).toHaveBeenCalledOnce
|
||||
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('xvfb fails')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when running in CI', () => {
|
||||
beforeEach(() => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
util.isCi.mockReturnValue(true)
|
||||
})
|
||||
|
||||
it('uses verbose renderer', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('verifying in ci')
|
||||
})
|
||||
|
||||
it('logs error when binary not found', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
mockfs({})
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
expect(normalize(output())).toMatchSnapshot('error binary not found in ci')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when env var CYPRESS_RUN_BINARY', async () => {
|
||||
it('can validate and use executable', async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
const envBinaryPath = '/custom/Contents/MacOS/Cypress'
|
||||
const realEnvBinaryPath = `/real${envBinaryPath}`
|
||||
|
||||
vi.stubEnv('CYPRESS_RUN_BINARY', envBinaryPath)
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
customDir: '/real/custom',
|
||||
})
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
util.exec.mockImplementation((...args: any) => {
|
||||
if (args[0] === realEnvBinaryPath && _.isEqual(args[1], ['--no-sandbox', '--smoke-test', '--ping=222'])) {
|
||||
return Promise.resolve(spawnedProcess)
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('should have caught error'))
|
||||
})
|
||||
|
||||
await start({ listrRenderer: 'silent' })
|
||||
|
||||
expect(util.exec).toHaveBeenCalledWith(realEnvBinaryPath, ['--no-sandbox', '--smoke-test', '--ping=222'], expect.anything())
|
||||
expect(normalize(output())).toMatchSnapshot('valid CYPRESS_RUN_BINARY')
|
||||
})
|
||||
|
||||
for (const platform of ['darwin', 'linux', 'win32']) {
|
||||
it(`can log error to user on ${platform}`, async () => {
|
||||
const output = createStdoutCapture()
|
||||
|
||||
vi.stubEnv('CYPRESS_RUN_BINARY', '/custom/')
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue(platform)
|
||||
|
||||
try {
|
||||
await start({ listrRenderer: 'silent' })
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
expect(normalize(output())).toMatchSnapshot(`${platform}: error when invalid CYPRESS_RUN_BINARY`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// tests for when Electron needs "--no-sandbox" CLI flag
|
||||
describe('.needsSandbox', () => {
|
||||
it('needs --no-sandbox on Linux as a root', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(0)
|
||||
expect(needsSandbox()).toEqual(true)
|
||||
})
|
||||
|
||||
it('needs --no-sandbox on Linux as a non-root', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('linux')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(1000)
|
||||
|
||||
expect(needsSandbox()).toEqual(true)
|
||||
})
|
||||
|
||||
it('needs --no-sandbox on Mac as a non-root', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('darwin')
|
||||
// @ts-expect-error - mockReturnValue
|
||||
geteuid.mockReturnValue(1000)
|
||||
|
||||
expect(needsSandbox()).toEqual(true)
|
||||
})
|
||||
|
||||
it('does not need --no-sandbox on Windows', () => {
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.platform.mockReturnValue('win32')
|
||||
|
||||
expect(needsSandbox()).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TODO this needs documentation with examples badly.
|
||||
function createfs ({ alreadyVerified, executable, packageVersion, customDir }: any) {
|
||||
if (!customDir) {
|
||||
customDir = '/cache/Cypress/1.2.3/Cypress.app'
|
||||
}
|
||||
|
||||
// binary state is stored one folder higher than the runner itself
|
||||
// see https://github.com/cypress-io/cypress/issues/6089
|
||||
const binaryStateFolder = path.join(customDir, '..')
|
||||
|
||||
const binaryState = {
|
||||
verified: alreadyVerified,
|
||||
}
|
||||
const binaryStateText = JSON.stringify(binaryState)
|
||||
|
||||
let mockFiles: any = {
|
||||
[binaryStateFolder]: {
|
||||
'binary_state.json': binaryStateText,
|
||||
},
|
||||
[customDir]: {
|
||||
Contents: {
|
||||
MacOS: executable
|
||||
? {
|
||||
Cypress: executable,
|
||||
}
|
||||
: {},
|
||||
Resources: {
|
||||
app: {
|
||||
'package.json': `{"version": "${packageVersion}"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (customDir) {
|
||||
mockFiles['/custom/Contents/MacOS/Cypress'] = mockfs.symlink({
|
||||
path: '/real/custom/Contents/MacOS/Cypress',
|
||||
mode: 0o777,
|
||||
})
|
||||
}
|
||||
|
||||
return mockfs(mockFiles)
|
||||
}
|
||||
@@ -1,868 +0,0 @@
|
||||
/* eslint-disable no-restricted-properties */
|
||||
import '../../spec_helper'
|
||||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
import os from 'os'
|
||||
import cp from 'child_process'
|
||||
import BluebirdPromise from 'bluebird'
|
||||
import { stripIndent } from 'common-tags'
|
||||
import mockfs from 'mock-fs'
|
||||
import mockedEnv from 'mocked-env'
|
||||
import Stdout from '../../support/stdout'
|
||||
import normalize from '../../support/normalize'
|
||||
import snapshot from '../../support/snapshot'
|
||||
import mockSpawnModule from '../../support/spawn-mock'
|
||||
|
||||
import fs from '../../../lib/fs'
|
||||
import util from '../../../lib/util'
|
||||
import logger from '../../../lib/logger'
|
||||
import xvfb from '../../../lib/exec/xvfb'
|
||||
import verify from '../../../lib/tasks/verify'
|
||||
|
||||
const packageVersion = '1.2.3'
|
||||
const cacheDir = '/cache/Cypress'
|
||||
const executablePath = '/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress'
|
||||
const binaryStatePath = '/cache/Cypress/1.2.3/binary_state.json'
|
||||
const DEFAULT_VERIFY_TIMEOUT = 30000
|
||||
|
||||
let stdout: any
|
||||
let spawnedProcess: any
|
||||
|
||||
/* eslint-disable no-octal */
|
||||
|
||||
context('lib/tasks/verify', () => {
|
||||
before(async function () {
|
||||
const mochaMain = await import('mocha-banner')
|
||||
|
||||
mochaMain.register()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
stdout = Stdout.capture()
|
||||
spawnedProcess = {
|
||||
code: 0,
|
||||
stderr: sinon.stub(),
|
||||
stdout: '222',
|
||||
}
|
||||
|
||||
;(os.platform as any).returns('darwin')
|
||||
|
||||
;(os.release as any).returns('0.0.0')
|
||||
|
||||
sinon.stub(util, 'getCacheDir').returns(cacheDir)
|
||||
sinon.stub(util, 'isCi').returns(false)
|
||||
sinon.stub(util, 'pkgVersion').returns(packageVersion)
|
||||
sinon.stub(util, 'exec')
|
||||
|
||||
sinon.stub(xvfb, 'start').resolves()
|
||||
sinon.stub(xvfb, 'stop').resolves()
|
||||
sinon.stub(xvfb, 'isNeeded').returns(false)
|
||||
sinon.stub(BluebirdPromise.prototype, 'delay').resolves()
|
||||
sinon.stub(process, 'geteuid').returns(1000)
|
||||
|
||||
sinon.stub(_, 'random').returns(222)
|
||||
|
||||
util.exec
|
||||
// @ts-expect-error - is a sinon stub
|
||||
.withArgs(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'])
|
||||
.resolves(spawnedProcess)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Stdout.restore()
|
||||
})
|
||||
|
||||
it('has verify task timeout', () => {
|
||||
expect(verify.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.eql(DEFAULT_VERIFY_TIMEOUT)
|
||||
})
|
||||
|
||||
it('accepts custom verify task timeout', async () => {
|
||||
process.env.CYPRESS_VERIFY_TIMEOUT = '500000'
|
||||
const proxyquire = await import('proxyquire')
|
||||
const newVerifyInstance = proxyquire.default(`../../../lib/tasks/verify`, {}).default
|
||||
|
||||
expect(newVerifyInstance.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.eql(500000)
|
||||
})
|
||||
|
||||
it('accepts custom verify task timeout from npm', async () => {
|
||||
process.env.npm_config_CYPRESS_VERIFY_TIMEOUT = '500000'
|
||||
const proxyquire = await import('proxyquire')
|
||||
const newVerifyInstance = proxyquire.default(`../../../lib/tasks/verify`, {}).default
|
||||
|
||||
expect(newVerifyInstance.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.eql(500000)
|
||||
})
|
||||
|
||||
it('falls back to default verify task timeout if custom value is invalid', async () => {
|
||||
process.env.CYPRESS_VERIFY_TIMEOUT = 'foobar'
|
||||
|
||||
const proxyquire = await import('proxyquire')
|
||||
const newVerifyInstance = proxyquire.default(`../../../lib/tasks/verify`, {}).default
|
||||
|
||||
expect(newVerifyInstance.VERIFY_TEST_RUNNER_TIMEOUT_MS).to.eql(DEFAULT_VERIFY_TIMEOUT)
|
||||
})
|
||||
|
||||
it('returns early when `CYPRESS_SKIP_VERIFY` is set to true', async () => {
|
||||
process.env.CYPRESS_SKIP_VERIFY = 'true'
|
||||
|
||||
const proxyquire = await import('proxyquire')
|
||||
const newVerifyInstance = proxyquire.default(`../../../lib/tasks/verify`, {}).default
|
||||
|
||||
return newVerifyInstance.start({ listrRenderer: 'silent' }).then((result: any) => {
|
||||
expect(result).to.eq(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error and exits when no version of Cypress is installed', () => {
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('should have caught error')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
|
||||
snapshot(
|
||||
'no version of Cypress installed 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('adds --no-sandbox when user is root', () => {
|
||||
// make it think the executable exists
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
;(process.geteuid as any).returns(0) // user is root
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.resolves({
|
||||
stdout: '222',
|
||||
stderr: '',
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
expect(util.exec).to.be.calledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'])
|
||||
})
|
||||
})
|
||||
|
||||
it('adds --no-sandbox when user is non-root', () => {
|
||||
// make it think the executable exists
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
;(process.geteuid as any).returns(1000) // user is non-root
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.resolves({
|
||||
stdout: '222',
|
||||
stderr: '',
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
expect(util.exec).to.be.calledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'])
|
||||
})
|
||||
})
|
||||
|
||||
it('is noop when binary is already verified', () => {
|
||||
// make it think the executable exists and is verified
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
// nothing should have been logged to stdout
|
||||
// since no verification took place
|
||||
expect(stdout.toString()).to.be.empty
|
||||
|
||||
expect(util.exec).not.to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('logs warning when installed version does not match verified version', () => {
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: 'bloop',
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('should have caught error')
|
||||
})
|
||||
.catch(() => {
|
||||
return snapshot(
|
||||
'warning installed version does not match verified version 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error and exits when executable cannot be found', () => {
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('should have caught error')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
|
||||
snapshot('executable cannot be found 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error when child process hangs', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - invalid number of arguments for given type
|
||||
sinon.stub(cp, 'spawn').withArgs('/cache/Cypress/1.2.3/Cypress.app/Contents/MacOS/Cypress').callsFake(mockSpawnModule.mockSpawn((cp: any) => {
|
||||
cp.stderr.write('some stderr')
|
||||
cp.stdout.write('some stdout')
|
||||
}))
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
|
||||
return verify
|
||||
.start({ smokeTestTimeout: 1, listrRenderer: 'silent' })
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
})
|
||||
.then(() => {
|
||||
snapshot(normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error when child process returns incorrect stdout (stderr when exists)', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
sinon.stub(cp, 'spawn').callsFake(mockSpawnModule.mockSpawn((cp: any) => {
|
||||
cp.stderr.write('some stderr')
|
||||
cp.stdout.write('some stdout')
|
||||
cp.emit('exit', 0, null)
|
||||
cp.end()
|
||||
}))
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
})
|
||||
.then(() => {
|
||||
snapshot(normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error when child process returns incorrect stdout (stdout when no stderr)', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
sinon.stub(cp, 'spawn').callsFake(mockSpawnModule.mockSpawn((cp: any) => {
|
||||
cp.stdout.write('some stdout')
|
||||
cp.emit('exit', 0, null)
|
||||
cp.end()
|
||||
}))
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
})
|
||||
.then(() => {
|
||||
snapshot(normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
describe('FORCE_COLOR', () => {
|
||||
let previousForceColors: any
|
||||
|
||||
beforeEach(() => {
|
||||
previousForceColors = process.env.FORCE_COLOR
|
||||
|
||||
process.env.FORCE_COLOR = 'true' as any
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env.FORCE_COLOR = previousForceColors
|
||||
})
|
||||
|
||||
// @see https://github.com/cypress-io/cypress/issues/28982
|
||||
it('sets FORCE_COLOR to 0 when piping stdioOptions to to the smoke test to avoid ANSI in binary smoke test', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.resolves({
|
||||
stdout: '222',
|
||||
stderr: '',
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
expect(util.exec).to.be.calledWith(executablePath, ['--no-sandbox', '--smoke-test', '--ping=222'],
|
||||
sinon.match({
|
||||
env: {
|
||||
FORCE_COLOR: '0',
|
||||
},
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with force: true', () => {
|
||||
beforeEach(() => {
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('shows full path to executable when verifying', () => {
|
||||
return verify.start({ force: true, listrRenderer: 'silent' }).then(() => {
|
||||
snapshot('verification with executable 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('clears verified version from state if verification fails', () => {
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
sinon
|
||||
.stub(util, 'exec')
|
||||
.withArgs(executablePath)
|
||||
.rejects({
|
||||
code: 1,
|
||||
stderr: 'an error about dependencies',
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({ force: true, listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
})
|
||||
.then(() => {
|
||||
return fs.pathExistsAsync(binaryStatePath)
|
||||
})
|
||||
.then((exists: any) => {
|
||||
return expect(exists).to.eq(false)
|
||||
})
|
||||
.then(() => {
|
||||
return snapshot(
|
||||
'fails verifying Cypress 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('smoke test with DEBUG output', () => {
|
||||
beforeEach(() => {
|
||||
const stdoutWithDebugOutput = stripIndent`
|
||||
some debug output
|
||||
date: more debug output
|
||||
222
|
||||
after that more text
|
||||
`
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.withArgs(executablePath).resolves({
|
||||
stdout: stdoutWithDebugOutput,
|
||||
})
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('finds ping value in the verbose output', () => {
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
snapshot('verbose stdout output 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('smoke test retries on bad display with our Xvfb', () => {
|
||||
let restore: any
|
||||
|
||||
beforeEach(() => {
|
||||
restore = mockedEnv({
|
||||
DISPLAY: 'test-display',
|
||||
})
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
sinon.spy(logger, 'warn')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
restore()
|
||||
})
|
||||
|
||||
it('successfully retries with our Xvfb on Linux', () => {
|
||||
// initially we think the user has everything set
|
||||
// @ts-expect-error - is a sinon stub
|
||||
xvfb.isNeeded.returns(false)
|
||||
sinon.stub(util, 'isPossibleLinuxWithIncorrectDisplay').returns(true)
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
sinon.stub(util, 'exec').callsFake(() => {
|
||||
const firstSpawnError: any = new Error('')
|
||||
|
||||
// this message contains typical Gtk error shown if X11 is incorrect
|
||||
// like in the case of DISPLAY=987
|
||||
firstSpawnError.stderr = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
and maybe a few other lines here with weird indent
|
||||
`
|
||||
|
||||
firstSpawnError.stdout = ''
|
||||
|
||||
// the second time the binary returns expected ping
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.withArgs(executablePath).resolves({
|
||||
stdout: '222',
|
||||
})
|
||||
|
||||
return BluebirdPromise.reject(firstSpawnError)
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
expect(util.exec).to.have.been.calledTwice
|
||||
// user should have been warned
|
||||
expect(logger.warn).to.have.been.calledWithMatch(
|
||||
'This is likely due to a misconfigured DISPLAY environment variable.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('fails on both retries with our Xvfb on Linux', () => {
|
||||
// initially we think the user has everything set
|
||||
// @ts-expect-error - is a sinon stub
|
||||
xvfb.isNeeded.returns(false)
|
||||
|
||||
sinon.stub(util, 'isPossibleLinuxWithIncorrectDisplay').returns(true)
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
sinon.stub(util, 'exec').callsFake(() => {
|
||||
(os.platform as any).returns('linux')
|
||||
expect(xvfb.start).to.not.have.been.called
|
||||
|
||||
const firstSpawnError: any = new Error('')
|
||||
|
||||
// this message contains typical Gtk error shown if X11 is incorrect
|
||||
// like in the case of DISPLAY=987
|
||||
firstSpawnError.stderr = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
and maybe a few other lines here with weird indent
|
||||
`
|
||||
|
||||
firstSpawnError.stdout = ''
|
||||
|
||||
// the second time it runs, it fails for some other reason
|
||||
const secondMessage = stripIndent`
|
||||
[some noise here] Gtk: cannot open display: 987
|
||||
some other error
|
||||
again with
|
||||
some weird indent
|
||||
`
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.withArgs(executablePath).rejects(new Error(secondMessage))
|
||||
|
||||
return BluebirdPromise.reject(firstSpawnError)
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
throw new Error('Should have failed')
|
||||
})
|
||||
.catch((e: any) => {
|
||||
expect(util.exec).to.have.been.calledTwice
|
||||
// second time around we should have called Xvfb
|
||||
expect(xvfb.start).to.have.been.calledOnce
|
||||
expect(xvfb.stop).to.have.been.calledOnce
|
||||
|
||||
// user should have been warned
|
||||
expect(logger.warn).to.have.been.calledWithMatch('DISPLAY was set to: "test-display"')
|
||||
|
||||
snapshot('tried to verify twice, on the first try got the DISPLAY error', e.message)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('logs an error if Cypress executable does not exist', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: false,
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
stdout = Stdout.capture()
|
||||
logger.error(err)
|
||||
|
||||
return snapshot('no Cypress executable 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs an error if Cypress executable does not have permissions', () => {
|
||||
mockfs.restore()
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o666 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
stdout = Stdout.capture()
|
||||
logger.error(err)
|
||||
|
||||
return snapshot(
|
||||
'Cypress non-executable permissions 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('logs and runs when current version has not been verified', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
return snapshot(
|
||||
'current version has not been verified 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('logs and runs when installed version is different than package version', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: '7.8.9',
|
||||
})
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
return snapshot(
|
||||
'different version installed 1',
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('is silent when logLevel is silent', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
process.env.npm_config_loglevel = 'silent'
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
return snapshot(
|
||||
'silent verify 1',
|
||||
normalize(`[no output]${stdout.toString()}`),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('turns off Opening Cypress...', () => {
|
||||
createfs({
|
||||
alreadyVerified: true,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion: '7.8.9',
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({
|
||||
welcomeMessage: false,
|
||||
})
|
||||
.then(() => {
|
||||
return snapshot('no welcome message 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error when fails smoke test unexpectedly without stderr', () => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.exec.restore()
|
||||
sinon.stub(util, 'exec').rejects({
|
||||
stderr: '',
|
||||
stdout: '',
|
||||
message: 'Error: EPERM NOT PERMITTED',
|
||||
})
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
stdout = Stdout.capture()
|
||||
logger.error(err)
|
||||
|
||||
return snapshot('fails with no stderr 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
describe('on linux', () => {
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error - is a sinon stub
|
||||
xvfb.isNeeded.returns(true)
|
||||
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
})
|
||||
|
||||
it('starts xvfb', () => {
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
expect(xvfb.start).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('stops xvfb on spawned process close', () => {
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
expect(xvfb.stop).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error and exits when starting xvfb fails', () => {
|
||||
const err: any = new Error('test without xvfb')
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
xvfb.start.restore()
|
||||
|
||||
err.nonZeroExitCode = true
|
||||
err.stack = 'xvfb? no dice'
|
||||
sinon.stub(xvfb._xvfb, 'startAsync').rejects(err)
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
expect(xvfb.stop).to.be.calledOnce
|
||||
|
||||
logger.error(err)
|
||||
|
||||
snapshot('xvfb fails 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when running in CI', () => {
|
||||
beforeEach(() => {
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
})
|
||||
|
||||
// @ts-expect-error - is a sinon stub
|
||||
util.isCi.returns(true)
|
||||
})
|
||||
|
||||
it('uses verbose renderer', () => {
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
snapshot('verifying in ci 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
it('logs error when binary not found', () => {
|
||||
mockfs({})
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
snapshot('error binary not found in ci 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when env var CYPRESS_RUN_BINARY', () => {
|
||||
it('can validate and use executable', () => {
|
||||
const envBinaryPath = '/custom/Contents/MacOS/Cypress'
|
||||
const realEnvBinaryPath = `/real${envBinaryPath}`
|
||||
|
||||
process.env.CYPRESS_RUN_BINARY = envBinaryPath
|
||||
createfs({
|
||||
alreadyVerified: false,
|
||||
executable: mockfs.file({ mode: 0o777 }),
|
||||
packageVersion,
|
||||
customDir: '/real/custom',
|
||||
})
|
||||
|
||||
util.exec
|
||||
// @ts-expect-error - is a sinon stub
|
||||
.withArgs(realEnvBinaryPath, ['--no-sandbox', '--smoke-test', '--ping=222'])
|
||||
.resolves(spawnedProcess)
|
||||
|
||||
return verify.start({ listrRenderer: 'silent' }).then(() => {
|
||||
// @ts-expect-error - is a sinon stub
|
||||
expect(util.exec.firstCall.args[0]).to.equal(realEnvBinaryPath)
|
||||
snapshot('valid CYPRESS_RUN_BINARY 1', normalize(stdout.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
_.each(['darwin', 'linux', 'win32'], (platform) => {
|
||||
return it('can log error to user', () => {
|
||||
process.env.CYPRESS_RUN_BINARY = '/custom/'
|
||||
|
||||
;(os.platform as any).returns(platform)
|
||||
|
||||
return verify
|
||||
.start({ listrRenderer: 'silent' })
|
||||
.then(() => {
|
||||
throw new Error('Should have thrown')
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.error(err)
|
||||
snapshot(
|
||||
`${platform}: error when invalid CYPRESS_RUN_BINARY 1`,
|
||||
normalize(stdout.toString()),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// tests for when Electron needs "--no-sandbox" CLI flag
|
||||
context('.needsSandbox', () => {
|
||||
it('needs --no-sandbox on Linux as a root', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(process.geteuid as any).returns(0) // user is root
|
||||
expect(verify.needsSandbox()).to.be.true
|
||||
})
|
||||
|
||||
it('needs --no-sandbox on Linux as a non-root', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(process.geteuid as any).returns(1000) // user is non-root
|
||||
expect(verify.needsSandbox()).to.be.true
|
||||
})
|
||||
|
||||
it('needs --no-sandbox on Mac as a non-root', () => {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
;(process.geteuid as any).returns(1000) // user is non-root
|
||||
expect(verify.needsSandbox()).to.be.true
|
||||
})
|
||||
|
||||
it('does not need --no-sandbox on Windows', () => {
|
||||
(os.platform as any).returns('win32')
|
||||
expect(verify.needsSandbox()).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// TODO this needs documentation with examples badly.
|
||||
function createfs ({ alreadyVerified, executable, packageVersion, customDir }: any) {
|
||||
if (!customDir) {
|
||||
customDir = '/cache/Cypress/1.2.3/Cypress.app'
|
||||
}
|
||||
|
||||
// binary state is stored one folder higher than the runner itself
|
||||
// see https://github.com/cypress-io/cypress/issues/6089
|
||||
const binaryStateFolder = path.join(customDir, '..')
|
||||
|
||||
const binaryState = {
|
||||
verified: alreadyVerified,
|
||||
}
|
||||
const binaryStateText = JSON.stringify(binaryState)
|
||||
|
||||
let mockFiles: any = {
|
||||
[binaryStateFolder]: {
|
||||
'binary_state.json': binaryStateText,
|
||||
},
|
||||
[customDir]: {
|
||||
Contents: {
|
||||
MacOS: executable
|
||||
? {
|
||||
Cypress: executable,
|
||||
}
|
||||
: {},
|
||||
Resources: {
|
||||
app: {
|
||||
'package.json': `{"version": "${packageVersion}"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (customDir) {
|
||||
mockFiles['/custom/Contents/MacOS/Cypress'] = mockfs.symlink({
|
||||
path: '/real/custom/Contents/MacOS/Cypress',
|
||||
mode: 0o777,
|
||||
})
|
||||
}
|
||||
|
||||
return mockfs(mockFiles)
|
||||
}
|
||||
733
cli/test/lib/util.spec.ts
Normal file
733
cli/test/lib/util.spec.ts
Normal file
@@ -0,0 +1,733 @@
|
||||
import { vi, describe, it, beforeEach, expect } from 'vitest'
|
||||
import hasha from 'hasha'
|
||||
import la from 'lazy-ass'
|
||||
import util from '../../lib/util'
|
||||
import logger from '../../lib/logger'
|
||||
|
||||
describe('util', () => {
|
||||
beforeEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetModules()
|
||||
})
|
||||
|
||||
describe('.isBrokenGtkDisplay', () => {
|
||||
it('detects only GTK message', () => {
|
||||
const text = '[some noise here] Gtk: cannot open display: 99'
|
||||
|
||||
expect(util.isBrokenGtkDisplay(text)).toEqual(true)
|
||||
// and not for the other messages
|
||||
expect(util.isBrokenGtkDisplay('display was set incorrectly')).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getGitHubIssueUrl', () => {
|
||||
it('returns url for issue number', () => {
|
||||
const url = util.getGitHubIssueUrl(4034)
|
||||
|
||||
expect(url).toEqual('https://github.com/cypress-io/cypress/issues/4034')
|
||||
})
|
||||
|
||||
it('throws for anything but a positive integer', () => {
|
||||
// @ts-expect-error
|
||||
expect(() => util.getGitHubIssueUrl('4024')).toThrow()
|
||||
|
||||
expect(() => util.getGitHubIssueUrl(-5)).toThrow()
|
||||
|
||||
expect(() => util.getGitHubIssueUrl(5.19)).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.stdoutLineMatches', () => {
|
||||
it('is a function', () => {
|
||||
expect(util.stdoutLineMatches).toBeTypeOf('function')
|
||||
})
|
||||
|
||||
it('matches entire output', () => {
|
||||
const line = '444'
|
||||
|
||||
expect(util.stdoutLineMatches(line, line)).toEqual(true)
|
||||
})
|
||||
|
||||
it('matches a line in output', () => {
|
||||
const line = '444'
|
||||
const stdout = ['start', line, 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).toEqual(true)
|
||||
})
|
||||
|
||||
it('matches a trimmed line in output', () => {
|
||||
const line = '444'
|
||||
const stdout = ['start', ` ${line} `, 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).toEqual(true)
|
||||
})
|
||||
|
||||
it('does not find match', () => {
|
||||
const line = '445'
|
||||
const stdout = ['start', '444', 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.normalizeModuleOptions', () => {
|
||||
it('does not change other properties', () => {
|
||||
const options = {
|
||||
foo: 'bar',
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('passes string env unchanged', () => {
|
||||
const options = {
|
||||
env: 'foo=bar',
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('converts environment object', () => {
|
||||
const options = {
|
||||
env: {
|
||||
foo: 'bar',
|
||||
magicNumber: 1234,
|
||||
host: 'kevin.dev.local',
|
||||
},
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('converts config object', () => {
|
||||
const options = {
|
||||
config: {
|
||||
baseUrl: 'http://localhost:2000',
|
||||
watchForFileChanges: false,
|
||||
},
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('converts reporterOptions object', () => {
|
||||
const options = {
|
||||
reporterOptions: {
|
||||
mochaFile: 'results/my-test-output.xml',
|
||||
toConsole: true,
|
||||
},
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('converts specs array', () => {
|
||||
const options = {
|
||||
spec: [
|
||||
'a', 'b', 'c',
|
||||
],
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('does not convert spec when string', () => {
|
||||
const options = {
|
||||
spec: 'x,y,z',
|
||||
}
|
||||
|
||||
expect(util.normalizeModuleOptions(options)).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('.supportsColor', () => {
|
||||
beforeEach(() => {
|
||||
// make sure CI is undefined when running in CircleCI to get deterministic results
|
||||
vi.stubEnv('CI', undefined)
|
||||
})
|
||||
|
||||
it('is true on obj return for stdout and stderr', async () => {
|
||||
vi.doMock('supports-color', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const utils = (await import('../../lib/util')).default
|
||||
|
||||
expect(utils.supportsColor()).toEqual(true)
|
||||
})
|
||||
|
||||
it('is false on false return for stdout', async () => {
|
||||
vi.doMock('supports-color', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
stdout: false,
|
||||
stderr: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const utils = (await import('../../lib/util')).default
|
||||
|
||||
expect(utils.supportsColor()).toEqual(false)
|
||||
})
|
||||
|
||||
it('is false on false return for stderr', async () => {
|
||||
vi.doMock('supports-color', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
stdout: true,
|
||||
stderr: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
expect(util.supportsColor()).toEqual(false)
|
||||
})
|
||||
|
||||
it('is true when running in CI', async () => {
|
||||
vi.stubEnv('CI', '1')
|
||||
|
||||
vi.doMock('supports-color', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
stdout: false,
|
||||
stderr: false,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
expect(util.supportsColor()).toEqual(true)
|
||||
})
|
||||
|
||||
it('is false when NO_COLOR has been set', async () => {
|
||||
vi.stubEnv('CI', '1')
|
||||
vi.stubEnv('NO_COLOR', '1')
|
||||
|
||||
vi.doMock('supports-color', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
stdout: true,
|
||||
stderr: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
expect(util.supportsColor()).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getEnvOverrides', () => {
|
||||
it('returns object with colors + process overrides', async () => {
|
||||
// force supportColors to return true
|
||||
vi.stubEnv('CI', '1')
|
||||
|
||||
vi.doMock('tty', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
isatty: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const tty = (await import('tty')).default
|
||||
|
||||
// @ts-expect-error mockImplementation
|
||||
tty.isatty.mockReturnValue(true)
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
expect(util.getEnvOverrides()).toEqual({
|
||||
FORCE_STDIN_TTY: '1',
|
||||
FORCE_STDOUT_TTY: '1',
|
||||
FORCE_STDERR_TTY: '1',
|
||||
FORCE_COLOR: '1',
|
||||
DEBUG_COLORS: '1',
|
||||
MOCHA_COLORS: '1',
|
||||
})
|
||||
|
||||
// force supportColors to return false
|
||||
vi.stubEnv('CI', undefined)
|
||||
vi.stubEnv('NO_COLOR', '1')
|
||||
|
||||
// @ts-expect-error - mockImplementation
|
||||
tty.isatty.mockReturnValue(false)
|
||||
|
||||
expect(util.getEnvOverrides()).toEqual({
|
||||
FORCE_STDIN_TTY: '0',
|
||||
FORCE_STDOUT_TTY: '0',
|
||||
FORCE_STDERR_TTY: '0',
|
||||
FORCE_COLOR: '0',
|
||||
DEBUG_COLORS: '0',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getForceTty', () => {
|
||||
it('forces when each stream is a tty', async () => {
|
||||
vi.doMock('tty', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
isatty: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const tty = (await import('tty')).default
|
||||
|
||||
// @ts-expect-error mockImplementation
|
||||
tty.isatty.mockImplementation((args) => {
|
||||
if (args === 0 || args === 1 || args === 2) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
expect(util.getForceTty()).toEqual({
|
||||
FORCE_STDIN_TTY: true,
|
||||
FORCE_STDOUT_TTY: true,
|
||||
FORCE_STDERR_TTY: true,
|
||||
})
|
||||
|
||||
// @ts-expect-error mockImplementation
|
||||
tty.isatty.mockReturnValue(false)
|
||||
|
||||
expect(util.getForceTty()).toEqual({
|
||||
FORCE_STDIN_TTY: false,
|
||||
FORCE_STDOUT_TTY: false,
|
||||
FORCE_STDERR_TTY: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getOriginalNodeOptions', () => {
|
||||
it('copy NODE_OPTIONS to ORIGINAL_NODE_OPTIONS', () => {
|
||||
vi.stubEnv('NODE_OPTIONS', '--require foo.js')
|
||||
|
||||
// @ts-expect-error - bad type
|
||||
expect(util.getOriginalNodeOptions({})).toEqual({
|
||||
ORIGINAL_NODE_OPTIONS: '--require foo.js',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.exit', () => {
|
||||
it('calls process.exit', () => {
|
||||
// @ts-expect-error wrong signature for process.exit
|
||||
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined)
|
||||
|
||||
util.exit(2)
|
||||
util.exit(0)
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(2)
|
||||
expect(processExitSpy).toHaveBeenCalledWith(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.logErrorExit1', () => {
|
||||
it('calls logger.error and process.exit', () => {
|
||||
const err = new Error('foo')
|
||||
// @ts-expect-error wrong signature for process.exit
|
||||
const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined)
|
||||
const loggerErrorSpy = vi.spyOn(logger, 'error').mockImplementation(() => undefined)
|
||||
|
||||
util.logErrorExit1(err)
|
||||
|
||||
expect(processExitSpy).toHaveBeenCalledWith(1)
|
||||
expect(loggerErrorSpy).toHaveBeenCalledWith('foo')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isSemver', () => {
|
||||
it('is true with 3-digit version', () => {
|
||||
expect(util.isSemver('1.2.3')).toEqual(true)
|
||||
})
|
||||
|
||||
it('is true with 2-digit version', () => {
|
||||
expect(util.isSemver('1.2')).toEqual(true)
|
||||
})
|
||||
|
||||
it('is true with 1-digit version', () => {
|
||||
expect(util.isSemver('1')).toEqual(true)
|
||||
})
|
||||
|
||||
it('is false with URL', () => {
|
||||
expect(util.isSemver('www.cypress.io/download/1.2.3')).toEqual(false)
|
||||
})
|
||||
|
||||
it('is false with file path', () => {
|
||||
expect(util.isSemver('0/path/1.2.3/mypath/2.3')).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.calculateEta', () => {
|
||||
it('Remaining eta is same as elapsed when 50%', () => {
|
||||
expect(util.calculateEta(50, 1000)).toEqual(1000)
|
||||
})
|
||||
|
||||
it('Remaining eta is 0 when 100%', () => {
|
||||
expect(util.calculateEta(100, 500)).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.convertPercentToPercentage', () => {
|
||||
it('converts to 100 when 1', () => {
|
||||
expect(util.convertPercentToPercentage(1)).toEqual(100)
|
||||
})
|
||||
|
||||
it('strips out extra decimals', () => {
|
||||
expect(util.convertPercentToPercentage(0.37892)).toEqual(38)
|
||||
})
|
||||
|
||||
it('returns 0 if null num', () => {
|
||||
expect(util.convertPercentToPercentage(null)).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.printNodeOptions', () => {
|
||||
describe('NODE_OPTIONS is not set', () => {
|
||||
it('does nothing if debug is not enabled', () => {
|
||||
const log = vi.fn()
|
||||
|
||||
// @ts-expect-error wrong signature for mock
|
||||
log.enabled = false
|
||||
util.printNodeOptions(log)
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('prints message when debug is enabled', () => {
|
||||
const log = vi.fn()
|
||||
|
||||
// @ts-expect-error wrong signature for mock
|
||||
log.enabled = true
|
||||
util.printNodeOptions(log)
|
||||
expect(log).toHaveBeenCalledWith('NODE_OPTIONS is not set')
|
||||
})
|
||||
})
|
||||
|
||||
describe('NODE_OPTIONS is set', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('NODE_OPTIONS', 'foo')
|
||||
})
|
||||
|
||||
it('does nothing if debug is not enabled', () => {
|
||||
const log = vi.fn()
|
||||
|
||||
// @ts-expect-error wrong signature for mock
|
||||
log.enabled = false
|
||||
util.printNodeOptions(log)
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('prints value when debug is enabled', () => {
|
||||
const log = vi.fn()
|
||||
|
||||
// @ts-expect-error wrong signature for mock
|
||||
log.enabled = true
|
||||
util.printNodeOptions(log)
|
||||
expect(log).toHaveBeenCalledWith('NODE_OPTIONS=%s', 'foo')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getOsVersionAsync', () => {
|
||||
beforeEach(() => {
|
||||
vi.doMock('os', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
platform: vi.fn(),
|
||||
release: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.doMock('systeminformation', async (importActual) => {
|
||||
const actual = await importActual()
|
||||
|
||||
return {
|
||||
// @ts-expect-error
|
||||
...actual,
|
||||
default: {
|
||||
osInfo: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('calls os.release when systeminformation fails', async () => {
|
||||
const os = (await import('os')).default
|
||||
const si = (await import('systeminformation')).default
|
||||
|
||||
// @ts-expect-error - mockReturnValue
|
||||
os.release.mockReturnValue('some-release')
|
||||
// @ts-expect-error - mockRejectedValue
|
||||
si.osInfo.mockRejectedValue(new Error('systeminformation failed'))
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
const result = await util.getOsVersionAsync()
|
||||
|
||||
expect(result).toEqual('some-release')
|
||||
expect(os.release).toHaveBeenCalled()
|
||||
expect(si.osInfo).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses systeminformation when it succeeds', async () => {
|
||||
const os = (await import('os')).default
|
||||
const si = (await import('systeminformation')).default
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Ubuntu',
|
||||
release: '22.04',
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
const result = await util.getOsVersionAsync()
|
||||
|
||||
expect(result).toEqual('Ubuntu - 22.04')
|
||||
expect(si.osInfo).toHaveBeenCalled()
|
||||
expect(os.release).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('falls back to os.release when systeminformation returns incomplete data', async () => {
|
||||
const os = (await import('os')).default
|
||||
const si = (await import('systeminformation')).default
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
os.release.mockReturnValue('5.15.0')
|
||||
|
||||
// @ts-expect-error - mockResolvedValue
|
||||
si.osInfo.mockResolvedValue({
|
||||
distro: 'Ubuntu',
|
||||
// missing release property
|
||||
})
|
||||
|
||||
const util = (await import('../../lib/util')).default
|
||||
|
||||
const result = await util.getOsVersionAsync()
|
||||
|
||||
expect(result).toEqual('5.15.0')
|
||||
expect(si.osInfo).toHaveBeenCalled()
|
||||
expect(os.release).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('dequote', () => {
|
||||
it('removes double quotes', () => {
|
||||
expect(util.dequote('"foo"')).toEqual('foo')
|
||||
})
|
||||
|
||||
it('keeps single quotes', () => {
|
||||
expect(util.dequote('\'foo\'')).toEqual('\'foo\'')
|
||||
})
|
||||
|
||||
it('keeps unbalanced double quotes', () => {
|
||||
expect(util.dequote('"foo')).toEqual('"foo')
|
||||
})
|
||||
|
||||
it('keeps inner double quotes', () => {
|
||||
expect(util.dequote('a"b"c')).toEqual('a"b"c')
|
||||
})
|
||||
|
||||
it('passes empty strings', () => {
|
||||
expect(util.dequote('')).toEqual('')
|
||||
})
|
||||
|
||||
it('keeps single double quote character', () => {
|
||||
expect(util.dequote('"')).toEqual('"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getEnv', () => {
|
||||
it('reads from package.json config', () => {
|
||||
vi.stubEnv('npm_package_config_CYPRESS_FOO', 'bar')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('bar')
|
||||
})
|
||||
|
||||
it('reads from .npmrc config', () => {
|
||||
vi.stubEnv('npm_config_CYPRESS_FOO', 'bar')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('bar')
|
||||
})
|
||||
|
||||
it('reads from env var', () => {
|
||||
vi.stubEnv('CYPRESS_FOO', 'bar')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('bar')
|
||||
})
|
||||
|
||||
it('prefers env var over .npmrc config', () => {
|
||||
vi.stubEnv('CYPRESS_FOO', 'bar')
|
||||
vi.stubEnv('npm_config_CYPRESS_FOO', 'baz')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('bar')
|
||||
})
|
||||
|
||||
it('prefers env var over .npmrc config even if it\'s an empty string', () => {
|
||||
vi.stubEnv('CYPRESS_FOO', '')
|
||||
vi.stubEnv('npm_config_CYPRESS_FOO', 'baz')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('')
|
||||
})
|
||||
|
||||
it('prefers .npmrc config over package config', () => {
|
||||
vi.stubEnv('npm_package_config_CYPRESS_FOO', 'baz')
|
||||
vi.stubEnv('npm_config_CYPRESS_FOO', 'bloop')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('bloop')
|
||||
})
|
||||
|
||||
it('prefers .npmrc config over package config even if it\'s an empty string', () => {
|
||||
vi.stubEnv('npm_package_config_CYPRESS_FOO', 'baz')
|
||||
vi.stubEnv('npm_config_CYPRESS_FOO', '')
|
||||
expect(util.getEnv('CYPRESS_FOO')).toEqual('')
|
||||
})
|
||||
|
||||
it('npm config set should work', () => {
|
||||
vi.stubEnv('npm_config_cypress_foo_foo', 'bazz')
|
||||
expect(util.getEnv('CYPRESS_FOO_FOO')).toEqual('bazz')
|
||||
})
|
||||
|
||||
it('throws on non-string name', () => {
|
||||
expect(() => util.getEnv()).toThrow()
|
||||
|
||||
expect(() => util.getEnv(42)).toThrow()
|
||||
})
|
||||
|
||||
describe('with trim = true', () => {
|
||||
it('trims returned string', () => {
|
||||
vi.stubEnv('FOO', ' bar ')
|
||||
expect(util.getEnv('FOO', true)).toEqual('bar')
|
||||
})
|
||||
|
||||
it('removes quotes from the returned string', () => {
|
||||
vi.stubEnv('FOO', ' "bar" ')
|
||||
expect(util.getEnv('FOO', true)).toEqual('bar')
|
||||
})
|
||||
|
||||
it('removes only single level of double quotes', () => {
|
||||
vi.stubEnv('FOO', ' ""bar"" ')
|
||||
expect(util.getEnv('FOO', true)).toEqual('"bar"')
|
||||
})
|
||||
|
||||
it('keeps unbalanced double quote', () => {
|
||||
vi.stubEnv('FOO', ' "bar ')
|
||||
expect(util.getEnv('FOO', true)).toEqual('"bar')
|
||||
})
|
||||
|
||||
it('trims but does not remove single quotes', () => {
|
||||
vi.stubEnv('FOO', ' \'bar\' ')
|
||||
expect(util.getEnv('FOO', true)).toEqual('\'bar\'')
|
||||
})
|
||||
|
||||
it('keeps whitespace inside removed quotes', () => {
|
||||
vi.stubEnv('FOO', '"foo.txt "')
|
||||
expect(util.getEnv('FOO', true)).toEqual('foo.txt ')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getFileChecksum', () => {
|
||||
it('computes same hash as Hasha SHA512', async () => {
|
||||
const [checksum, expectedChecksum] = await Promise.all([
|
||||
util.getFileChecksum(__filename),
|
||||
hasha.fromFile(__filename, { algorithm: 'sha512' }),
|
||||
])
|
||||
|
||||
la(checksum === expectedChecksum, 'our computed checksum', checksum,
|
||||
'is different from expected', expectedChecksum)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseOpts', () => {
|
||||
it('passes normal options and strips unknown ones', () => {
|
||||
const result = util.parseOpts({
|
||||
unknownOptions: true,
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('removes leftover double quotes', () => {
|
||||
const result = util.parseOpts({
|
||||
group: '"my group name"',
|
||||
ciBuildId: '"my ci build id"',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves unbalanced double quotes', () => {
|
||||
const result = util.parseOpts({
|
||||
group: 'my group name"',
|
||||
ciBuildId: '"my ci build id',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
group: 'my group name"',
|
||||
ciBuildId: '"my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('works with unspecified options', () => {
|
||||
const result = util.parseOpts({
|
||||
// notice that "group" option is missing
|
||||
ciBuildId: '"my ci build id"',
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,619 +0,0 @@
|
||||
import '../spec_helper'
|
||||
import os from 'os'
|
||||
import tty from 'tty'
|
||||
import snapshot from '../support/snapshot'
|
||||
import mockedEnv from 'mocked-env'
|
||||
import supportsColor from 'supports-color'
|
||||
import hasha from 'hasha'
|
||||
import la from 'lazy-ass'
|
||||
import util from '../../lib/util'
|
||||
import logger from '../../lib/logger'
|
||||
|
||||
describe('util', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(process, 'exit')
|
||||
sinon.stub(logger, 'error')
|
||||
})
|
||||
|
||||
context('.isBrokenGtkDisplay', () => {
|
||||
it('detects only GTK message', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
const text = '[some noise here] Gtk: cannot open display: 99'
|
||||
|
||||
expect(util.isBrokenGtkDisplay(text)).to.be.true
|
||||
// and not for the other messages
|
||||
expect(util.isBrokenGtkDisplay('display was set incorrectly')).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
context('.getGitHubIssueUrl', () => {
|
||||
it('returns url for issue number', () => {
|
||||
const url = util.getGitHubIssueUrl(4034)
|
||||
|
||||
expect(url).to.equal('https://github.com/cypress-io/cypress/issues/4034')
|
||||
})
|
||||
|
||||
it('throws for anything but a positive integer', () => {
|
||||
expect(() => {
|
||||
return util.getGitHubIssueUrl('4034')
|
||||
}).to.throw
|
||||
|
||||
expect(() => {
|
||||
return util.getGitHubIssueUrl(-5)
|
||||
}).to.throw
|
||||
|
||||
expect(() => {
|
||||
return util.getGitHubIssueUrl(5.19)
|
||||
}).to.throw
|
||||
})
|
||||
})
|
||||
|
||||
context('.stdoutLineMatches', () => {
|
||||
it('is a function', () => {
|
||||
expect(util.stdoutLineMatches).to.be.a('function')
|
||||
})
|
||||
|
||||
it('matches entire output', () => {
|
||||
const line = '444'
|
||||
|
||||
expect(util.stdoutLineMatches(line, line)).to.be.true
|
||||
})
|
||||
|
||||
it('matches a line in output', () => {
|
||||
const line = '444'
|
||||
const stdout = ['start', line, 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).to.be.true
|
||||
})
|
||||
|
||||
it('matches a trimmed line in output', () => {
|
||||
const line = '444'
|
||||
const stdout = ['start', ` ${line} `, 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).to.be.true
|
||||
})
|
||||
|
||||
it('does not find match', () => {
|
||||
const line = '445'
|
||||
const stdout = ['start', '444', 'something else'].join('\n')
|
||||
|
||||
expect(util.stdoutLineMatches(line, stdout)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
context('.normalizeModuleOptions', () => {
|
||||
it('does not change other properties', () => {
|
||||
const options = {
|
||||
foo: 'bar',
|
||||
}
|
||||
|
||||
snapshot('others_unchanged 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('passes string env unchanged', () => {
|
||||
const options = {
|
||||
env: 'foo=bar',
|
||||
}
|
||||
|
||||
snapshot('env_as_string 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('converts environment object', () => {
|
||||
const options = {
|
||||
env: {
|
||||
foo: 'bar',
|
||||
magicNumber: 1234,
|
||||
host: 'kevin.dev.local',
|
||||
},
|
||||
}
|
||||
|
||||
snapshot('env_as_object 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('converts config object', () => {
|
||||
const options = {
|
||||
config: {
|
||||
baseUrl: 'http://localhost:2000',
|
||||
watchForFileChanges: false,
|
||||
},
|
||||
}
|
||||
|
||||
snapshot('config_as_object 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('converts reporterOptions object', () => {
|
||||
const options = {
|
||||
reporterOptions: {
|
||||
mochaFile: 'results/my-test-output.xml',
|
||||
toConsole: true,
|
||||
},
|
||||
}
|
||||
|
||||
snapshot('reporter_options_as_object 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('converts specs array', () => {
|
||||
const options = {
|
||||
spec: [
|
||||
'a', 'b', 'c',
|
||||
],
|
||||
}
|
||||
|
||||
snapshot('spec_as_array 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
|
||||
it('does not convert spec when string', () => {
|
||||
const options = {
|
||||
spec: 'x,y,z',
|
||||
}
|
||||
|
||||
snapshot('spec_as_string 1', util.normalizeModuleOptions(options))
|
||||
})
|
||||
})
|
||||
|
||||
context('.supportsColor', () => {
|
||||
it('is true on obj return for stdout and stderr', () => {
|
||||
sinon.stub(supportsColor, 'stdout').value({})
|
||||
sinon.stub(supportsColor, 'stderr').value({})
|
||||
|
||||
expect(util.supportsColor()).to.be.true
|
||||
})
|
||||
|
||||
it('is false on false return for stdout', () => {
|
||||
delete process.env.CI
|
||||
|
||||
sinon.stub(supportsColor, 'stdout').value(false)
|
||||
sinon.stub(supportsColor, 'stderr').value({})
|
||||
|
||||
expect(util.supportsColor()).to.be.false
|
||||
})
|
||||
|
||||
it('is false on false return for stderr', () => {
|
||||
delete process.env.CI
|
||||
|
||||
sinon.stub(supportsColor, 'stdout').value({})
|
||||
sinon.stub(supportsColor, 'stderr').value(false)
|
||||
|
||||
expect(util.supportsColor()).to.be.false
|
||||
})
|
||||
|
||||
it('is true when running in CI', () => {
|
||||
process.env.CI = '1'
|
||||
sinon.stub(supportsColor, 'stdout').value(false)
|
||||
|
||||
expect(util.supportsColor()).to.be.true
|
||||
})
|
||||
|
||||
it('is false when NO_COLOR has been set', () => {
|
||||
process.env.CI = '1'
|
||||
process.env.NO_COLOR = '1'
|
||||
sinon.stub(supportsColor, 'stdout').value({})
|
||||
sinon.stub(supportsColor, 'stderr').value({})
|
||||
|
||||
expect(util.supportsColor()).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
context('.getEnvOverrides', () => {
|
||||
it('returns object with colors + process overrides', () => {
|
||||
// shouldn't be stubbing 'what we own' but its easiest in this case
|
||||
sinon.stub(util, 'supportsColor').returns(true)
|
||||
sinon.stub(tty, 'isatty').returns(true)
|
||||
|
||||
expect(util.getEnvOverrides()).to.deep.eq({
|
||||
FORCE_STDIN_TTY: '1',
|
||||
FORCE_STDOUT_TTY: '1',
|
||||
FORCE_STDERR_TTY: '1',
|
||||
FORCE_COLOR: '1',
|
||||
DEBUG_COLORS: '1',
|
||||
MOCHA_COLORS: '1',
|
||||
})
|
||||
|
||||
;(util.supportsColor as any).returns(false)
|
||||
|
||||
;(tty.isatty as any).returns(false)
|
||||
|
||||
expect(util.getEnvOverrides()).to.deep.eq({
|
||||
FORCE_STDIN_TTY: '0',
|
||||
FORCE_STDOUT_TTY: '0',
|
||||
FORCE_STDERR_TTY: '0',
|
||||
FORCE_COLOR: '0',
|
||||
DEBUG_COLORS: '0',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getForceTty', () => {
|
||||
it('forces when each stream is a tty', () => {
|
||||
sinon.stub(tty, 'isatty')
|
||||
.withArgs(0).returns(true)
|
||||
.withArgs(1).returns(true)
|
||||
.withArgs(2).returns(true)
|
||||
|
||||
expect(util.getForceTty()).to.deep.eq({
|
||||
FORCE_STDIN_TTY: true,
|
||||
FORCE_STDOUT_TTY: true,
|
||||
FORCE_STDERR_TTY: true,
|
||||
})
|
||||
|
||||
;(tty.isatty as any)
|
||||
.withArgs(0).returns(false)
|
||||
.withArgs(1).returns(false)
|
||||
.withArgs(2).returns(false)
|
||||
|
||||
expect(util.getForceTty()).to.deep.eq({
|
||||
FORCE_STDIN_TTY: false,
|
||||
FORCE_STDOUT_TTY: false,
|
||||
FORCE_STDERR_TTY: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getOriginalNodeOptions', () => {
|
||||
let restoreEnv: any
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
afterEach(() => {
|
||||
if (restoreEnv) {
|
||||
restoreEnv()
|
||||
restoreEnv = null
|
||||
}
|
||||
})
|
||||
|
||||
it('copy NODE_OPTIONS to ORIGINAL_NODE_OPTIONS', () => {
|
||||
sandbox.stub(process.versions, 'node').value('v16.14.2')
|
||||
sandbox.stub(process.versions, 'openssl').value('1.0.0')
|
||||
|
||||
restoreEnv = mockedEnv({
|
||||
NODE_OPTIONS: '--require foo.js',
|
||||
})
|
||||
|
||||
expect(util.getOriginalNodeOptions({})).to.deep.eq({
|
||||
ORIGINAL_NODE_OPTIONS: '--require foo.js',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.exit', () => {
|
||||
it('calls process.exit', () => {
|
||||
(process.exit as any).withArgs(2).withArgs(0)
|
||||
util.exit(2)
|
||||
util.exit(0)
|
||||
})
|
||||
})
|
||||
|
||||
context('.logErrorExit1', () => {
|
||||
it('calls logger.error and process.exit', () => {
|
||||
const err = new Error('foo')
|
||||
|
||||
;(logger.error as any).withArgs('foo')
|
||||
|
||||
;(process.exit as any).withArgs(1)
|
||||
|
||||
util.logErrorExit1(err)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isSemver', () => {
|
||||
it('is true with 3-digit version', () => {
|
||||
expect(util.isSemver('1.2.3')).to.equal(true)
|
||||
})
|
||||
|
||||
it('is true with 2-digit version', () => {
|
||||
expect(util.isSemver('1.2')).to.equal(true)
|
||||
})
|
||||
|
||||
it('is true with 1-digit version', () => {
|
||||
expect(util.isSemver('1')).to.equal(true)
|
||||
})
|
||||
|
||||
it('is false with URL', () => {
|
||||
expect(util.isSemver('www.cypress.io/download/1.2.3')).to.equal(false)
|
||||
})
|
||||
|
||||
it('is false with file path', () => {
|
||||
expect(util.isSemver('0/path/1.2.3/mypath/2.3')).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.calculateEta', () => {
|
||||
it('Remaining eta is same as elapsed when 50%', () => {
|
||||
expect(util.calculateEta('50', 1000)).to.equal(1000)
|
||||
})
|
||||
|
||||
it('Remaining eta is 0 when 100%', () => {
|
||||
expect(util.calculateEta('100', 500)).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.convertPercentToPercentage', () => {
|
||||
it('converts to 100 when 1', () => {
|
||||
expect(util.convertPercentToPercentage(1)).to.equal(100)
|
||||
})
|
||||
|
||||
it('strips out extra decimals', () => {
|
||||
expect(util.convertPercentToPercentage(0.37892)).to.equal(38)
|
||||
})
|
||||
|
||||
it('returns 0 if null num', () => {
|
||||
expect(util.convertPercentToPercentage(null)).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
context('.printNodeOptions', () => {
|
||||
describe('NODE_OPTIONS is not set', () => {
|
||||
it('does nothing if debug is not enabled', () => {
|
||||
const log = sinon.spy()
|
||||
|
||||
;(log as any).enabled = false
|
||||
util.printNodeOptions(log)
|
||||
expect(log).not.have.been.called
|
||||
})
|
||||
|
||||
it('prints message when debug is enabled', () => {
|
||||
const log = sinon.spy()
|
||||
|
||||
;(log as any).enabled = true
|
||||
util.printNodeOptions(log)
|
||||
expect(log).to.be.calledWith('NODE_OPTIONS is not set')
|
||||
})
|
||||
})
|
||||
|
||||
describe('NODE_OPTIONS is set', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_OPTIONS = 'foo'
|
||||
})
|
||||
|
||||
it('does nothing if debug is not enabled', () => {
|
||||
const log = sinon.spy()
|
||||
|
||||
;(log as any).enabled = false
|
||||
util.printNodeOptions(log)
|
||||
expect(log).not.have.been.called
|
||||
})
|
||||
|
||||
it('prints value when debug is enabled', () => {
|
||||
const log = sinon.spy()
|
||||
|
||||
;(log as any).enabled = true
|
||||
util.printNodeOptions(log)
|
||||
expect(log).to.be.calledWith('NODE_OPTIONS=%s', 'foo')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getOsVersionAsync', () => {
|
||||
let util
|
||||
let systeminformation = {
|
||||
osInfo: sinon.stub(),
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
const proxyquire = await import('proxyquire')
|
||||
|
||||
util = proxyquire.default(`../../lib/util`, { systeminformation }).default
|
||||
})
|
||||
|
||||
it('calls os.release when systeminformation fails', () => {
|
||||
(os.platform as any).returns('darwin')
|
||||
|
||||
;(os.release as any).returns('some-release')
|
||||
systeminformation.osInfo.rejects(new Error('systeminformation failed'))
|
||||
|
||||
return util.getOsVersionAsync()
|
||||
.then(() => {
|
||||
expect(os.release).to.be.called
|
||||
expect(systeminformation.osInfo).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('uses systeminformation when it succeeds', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
systeminformation.osInfo.resolves({
|
||||
distro: 'Ubuntu',
|
||||
release: '22.04',
|
||||
})
|
||||
|
||||
return util.getOsVersionAsync()
|
||||
.then((result) => {
|
||||
expect(result).to.equal('Ubuntu - 22.04')
|
||||
expect(systeminformation.osInfo).to.be.called
|
||||
// os.release should not be called when systeminformation succeeds
|
||||
expect(os.release).to.not.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to os.release when systeminformation returns incomplete data', () => {
|
||||
(os.platform as any).returns('linux')
|
||||
|
||||
;(os.release as any).returns('5.15.0')
|
||||
|
||||
systeminformation.osInfo.resolves({
|
||||
distro: 'Ubuntu',
|
||||
// missing release property
|
||||
})
|
||||
|
||||
return util.getOsVersionAsync()
|
||||
.then(() => {
|
||||
expect(systeminformation.osInfo).to.be.called
|
||||
expect(os.release).to.be.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('dequote', () => {
|
||||
it('removes double quotes', () => {
|
||||
expect(util.dequote('"foo"')).to.equal('foo')
|
||||
})
|
||||
|
||||
it('keeps single quotes', () => {
|
||||
expect(util.dequote('\'foo\'')).to.equal('\'foo\'')
|
||||
})
|
||||
|
||||
it('keeps unbalanced double quotes', () => {
|
||||
expect(util.dequote('"foo')).to.equal('"foo')
|
||||
})
|
||||
|
||||
it('keeps inner double quotes', () => {
|
||||
expect(util.dequote('a"b"c')).to.equal('a"b"c')
|
||||
})
|
||||
|
||||
it('passes empty strings', () => {
|
||||
expect(util.dequote('')).to.equal('')
|
||||
})
|
||||
|
||||
it('keeps single double quote character', () => {
|
||||
expect(util.dequote('"')).to.equal('"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getEnv', () => {
|
||||
it('reads from package.json config', () => {
|
||||
process.env.npm_package_config_CYPRESS_FOO = 'bar'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('bar')
|
||||
})
|
||||
|
||||
it('reads from .npmrc config', () => {
|
||||
process.env.npm_config_CYPRESS_FOO = 'bar'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('bar')
|
||||
})
|
||||
|
||||
it('reads from env var', () => {
|
||||
process.env.CYPRESS_FOO = 'bar'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('bar')
|
||||
})
|
||||
|
||||
it('prefers env var over .npmrc config', () => {
|
||||
process.env.CYPRESS_FOO = 'bar'
|
||||
process.env.npm_config_CYPRESS_FOO = 'baz'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('bar')
|
||||
})
|
||||
|
||||
it('prefers env var over .npmrc config even if it\'s an empty string', () => {
|
||||
process.env.CYPRESS_FOO = ''
|
||||
process.env.npm_config_CYPRESS_FOO = 'baz'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('')
|
||||
})
|
||||
|
||||
it('prefers .npmrc config over package config', () => {
|
||||
process.env.npm_package_config_CYPRESS_FOO = 'baz'
|
||||
process.env.npm_config_CYPRESS_FOO = 'bloop'
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('bloop')
|
||||
})
|
||||
|
||||
it('prefers .npmrc config over package config even if it\'s an empty string', () => {
|
||||
process.env.npm_package_config_CYPRESS_FOO = 'baz'
|
||||
process.env.npm_config_CYPRESS_FOO = ''
|
||||
expect(util.getEnv('CYPRESS_FOO')).to.eql('')
|
||||
})
|
||||
|
||||
it('npm config set should work', () => {
|
||||
process.env.npm_config_cypress_foo_foo = 'bazz'
|
||||
expect(util.getEnv('CYPRESS_FOO_FOO')).to.eql('bazz')
|
||||
})
|
||||
|
||||
it('throws on non-string name', () => {
|
||||
expect(() => {
|
||||
util.getEnv()
|
||||
}).to.throw()
|
||||
|
||||
expect(() => {
|
||||
util.getEnv(42)
|
||||
}).to.throw()
|
||||
})
|
||||
|
||||
context('with trim = true', () => {
|
||||
it('trims returned string', () => {
|
||||
process.env.FOO = ' bar '
|
||||
expect(util.getEnv('FOO', true)).to.equal('bar')
|
||||
})
|
||||
|
||||
it('removes quotes from the returned string', () => {
|
||||
process.env.FOO = ' "bar" '
|
||||
expect(util.getEnv('FOO', true)).to.equal('bar')
|
||||
})
|
||||
|
||||
it('removes only single level of double quotes', () => {
|
||||
process.env.FOO = ' ""bar"" '
|
||||
expect(util.getEnv('FOO', true)).to.equal('"bar"')
|
||||
})
|
||||
|
||||
it('keeps unbalanced double quote', () => {
|
||||
process.env.FOO = ' "bar '
|
||||
expect(util.getEnv('FOO', true)).to.equal('"bar')
|
||||
})
|
||||
|
||||
it('trims but does not remove single quotes', () => {
|
||||
process.env.FOO = ' \'bar\' '
|
||||
expect(util.getEnv('FOO', true)).to.equal('\'bar\'')
|
||||
})
|
||||
|
||||
it('keeps whitespace inside removed quotes', () => {
|
||||
process.env.FOO = '"foo.txt "'
|
||||
expect(util.getEnv('FOO', true)).to.equal('foo.txt ')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.getFileChecksum', () => {
|
||||
it('computes same hash as Hasha SHA512', () => {
|
||||
return Promise.all([
|
||||
util.getFileChecksum(__filename),
|
||||
hasha.fromFile(__filename, { algorithm: 'sha512' }),
|
||||
]).then(([checksum, expectedChecksum]) => {
|
||||
la(checksum === expectedChecksum, 'our computed checksum', checksum,
|
||||
'is different from expected', expectedChecksum)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('parseOpts', () => {
|
||||
it('passes normal options and strips unknown ones', () => {
|
||||
const result = util.parseOpts({
|
||||
unknownOptions: true,
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('removes leftover double quotes', () => {
|
||||
const result = util.parseOpts({
|
||||
group: '"my group name"',
|
||||
ciBuildId: '"my ci build id"',
|
||||
})
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
group: 'my group name',
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves unbalanced double quotes', () => {
|
||||
const result = util.parseOpts({
|
||||
group: 'my group name"',
|
||||
ciBuildId: '"my ci build id',
|
||||
})
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
group: 'my group name"',
|
||||
ciBuildId: '"my ci build id',
|
||||
})
|
||||
})
|
||||
|
||||
it('works with unspecified options', () => {
|
||||
const result = util.parseOpts({
|
||||
// notice that "group" option is missing
|
||||
ciBuildId: '"my ci build id"',
|
||||
})
|
||||
|
||||
expect(result).to.deep.equal({
|
||||
ciBuildId: 'my ci build id',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,124 +0,0 @@
|
||||
import _ from 'lodash'
|
||||
import os from 'os'
|
||||
import sinon from 'sinon'
|
||||
import mockfs from 'mock-fs'
|
||||
import Bluebird from 'bluebird'
|
||||
import util from '../lib/util'
|
||||
import nock from 'nock'
|
||||
import { MockChildProcess } from 'spawn-mock'
|
||||
import chai from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import chaiString from 'chai-string'
|
||||
import sinonChai from '@cypress/sinon-chai'
|
||||
|
||||
const _kill = MockChildProcess.prototype.kill
|
||||
|
||||
const patchMockSpawn = (): void => {
|
||||
MockChildProcess.prototype.kill = function (...args: any[]): any {
|
||||
this.emit('exit')
|
||||
|
||||
return _kill.apply(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
patchMockSpawn()
|
||||
|
||||
// Set up global variables for test environment
|
||||
declare global {
|
||||
const sinon: typeof import('sinon')
|
||||
const expect: typeof import('chai').expect
|
||||
const lib: string
|
||||
}
|
||||
|
||||
(global as any).sinon = sinon
|
||||
|
||||
;(global as any).expect = chai.expect
|
||||
|
||||
chai
|
||||
.use(sinonChai)
|
||||
.use(chaiString)
|
||||
.use(chaiAsPromised)
|
||||
|
||||
sinon.usingPromise(Bluebird as any)
|
||||
|
||||
delete process.env.CYPRESS_RUN_BINARY
|
||||
delete process.env.CYPRESS_INSTALL_BINARY
|
||||
delete process.env.CYPRESS_CACHE_FOLDER
|
||||
delete process.env.CYPRESS_DOWNLOAD_MIRROR
|
||||
delete process.env.DISPLAY
|
||||
|
||||
// enable running specs with --silent w/out affecting logging in tests
|
||||
process.env.npm_config_loglevel = 'notice'
|
||||
|
||||
const env = _.clone(process.env)
|
||||
|
||||
function throwIfFnNotStubbed (stub: any, method: string): void {
|
||||
const sig = `.${method}(...)`
|
||||
|
||||
stub.callsFake(function (...args: any[]): void {
|
||||
const err = new Error(`${sig} was called without being stubbed.
|
||||
|
||||
${sig} was called with arguments:
|
||||
|
||||
${args.map(JSON.stringify).join(', ')}
|
||||
`)
|
||||
|
||||
err.stack = _
|
||||
.chain(err.stack)
|
||||
.split('\n')
|
||||
.reject((str: string) => {
|
||||
return _.includes(str, 'sinon')
|
||||
})
|
||||
.join('\n')
|
||||
.value()
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const $stub = sinon.stub
|
||||
|
||||
sinon.stub = function (obj?: any, method?: string): any {
|
||||
/* eslint-disable prefer-rest-params */
|
||||
const stub = $stub.apply(this, arguments as any)
|
||||
|
||||
let fns = [method]
|
||||
|
||||
if (arguments.length === 1) {
|
||||
fns = _.functions(obj)
|
||||
}
|
||||
|
||||
if (arguments.length === 0) {
|
||||
throwIfFnNotStubbed(stub, '[anonymous function]')
|
||||
|
||||
return stub
|
||||
}
|
||||
|
||||
fns.forEach((name: string) => {
|
||||
const fn = obj[name]
|
||||
|
||||
if (_.isFunction(fn)) {
|
||||
throwIfFnNotStubbed(fn, name)
|
||||
}
|
||||
})
|
||||
|
||||
return stub
|
||||
}
|
||||
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(os, 'platform')
|
||||
sinon.stub(os, 'arch')
|
||||
sinon.stub(os, 'release')
|
||||
sinon.stub(util, 'getOsVersionAsync').resolves('Foo-OsVersion')
|
||||
|
||||
;(os.arch as any).returns('x64')
|
||||
})
|
||||
|
||||
afterEach(function (): void {
|
||||
mockfs.restore()
|
||||
process.env = _.clone(env)
|
||||
sinon.restore()
|
||||
nock.cleanAll()
|
||||
|
||||
;(util as any)._cachedArch = undefined
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
import _snapshot from 'snap-shot-it'
|
||||
import mockfs from 'mock-fs'
|
||||
|
||||
// Type as any to avoid strict typing issues with rest parameters
|
||||
const snapshotAny: any = _snapshot
|
||||
|
||||
const snapshot = (...args: any[]): void => {
|
||||
mockfs.restore()
|
||||
snapshotAny(...args)
|
||||
}
|
||||
|
||||
export default snapshot
|
||||
@@ -1,15 +0,0 @@
|
||||
import spawnMock from 'spawn-mock'
|
||||
|
||||
// sinon is assumed to be available globally in test environment
|
||||
declare const sinon: any
|
||||
|
||||
export default {
|
||||
mockSpawn (cb: (cp: any) => any): any {
|
||||
return spawnMock.mockSpawn((cp: any) => {
|
||||
// execa expects .cancel to exist
|
||||
cp.cancel = sinon.stub()
|
||||
|
||||
return cb(cp)
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
const _write = process.stdout.write
|
||||
|
||||
const stdoutModule = {
|
||||
capture (): { data: string[], toString: () => string } {
|
||||
const logs: string[] = []
|
||||
|
||||
const write = process.stdout.write
|
||||
|
||||
process.stdout.write = function (str: any): boolean {
|
||||
logs.push(str)
|
||||
|
||||
/* eslint-disable prefer-rest-params */
|
||||
return write.apply(this, arguments as any)
|
||||
}
|
||||
|
||||
return {
|
||||
data: logs,
|
||||
|
||||
toString: (): string => {
|
||||
return logs.join('')
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
restore (): void {
|
||||
process.stdout.write = _write
|
||||
},
|
||||
}
|
||||
|
||||
export default stdoutModule
|
||||
18
cli/tsconfig.esm.json
Normal file
18
cli/tsconfig.esm.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./lib",
|
||||
"outDir": "./dist",
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": false,
|
||||
},
|
||||
"include": [
|
||||
"lib/**/*.mts",
|
||||
]
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./lib",
|
||||
"outDir": "./dist",
|
||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
"resolveJsonModule": true,
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
"noImplicitAny": false,
|
||||
"types": [
|
||||
"mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"lib/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"types",
|
||||
"scripts"
|
||||
"lib/**/*.ts",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const {
|
||||
getIndexJscHash,
|
||||
DUMMY_INDEX_JSC_HASH,
|
||||
} = require('./binary/binary-sources')
|
||||
const verify = require('../cli/lib/tasks/verify').default
|
||||
const { needsSandbox } = require('../cli/lib/tasks/verify')
|
||||
const execa = require('execa')
|
||||
const meta = require('./binary/meta')
|
||||
|
||||
@@ -30,7 +30,7 @@ const CY_ROOT_DIR = path.join(__dirname, '..')
|
||||
const createJscFromCypress = async () => {
|
||||
const args = []
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const { moveBinaries } = require('./move-binaries')
|
||||
const { exec } = require('child_process')
|
||||
const xvfb = require('../../cli/lib/exec/xvfb').default
|
||||
const smoke = require('./smoke')
|
||||
const verify = require('../../cli/lib/tasks/verify').default
|
||||
const { needsSandbox } = require('../../cli/lib/tasks/verify')
|
||||
const execa = require('execa')
|
||||
|
||||
const log = function (msg) {
|
||||
@@ -69,7 +69,7 @@ async function testExecutableVersion (buildAppExecutable, version) {
|
||||
|
||||
const args = ['--version']
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const execa = require('execa')
|
||||
const path = require('path')
|
||||
const Promise = require('bluebird')
|
||||
const os = require('os')
|
||||
const verify = require('../../cli/lib/tasks/verify').default
|
||||
const { needsSandbox } = require('../../cli/lib/tasks/verify')
|
||||
const Fixtures = require('@tooling/system-tests')
|
||||
const { scaffoldCommonNodeModules } = require('@tooling/system-tests/lib/dep-installer')
|
||||
|
||||
@@ -37,7 +37,7 @@ const runSmokeTest = function (buildAppExecutable, timeoutSeconds = 30) {
|
||||
|
||||
const args = []
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ const runProjectTest = function (buildAppExecutable, e2e) {
|
||||
`--spec=${e2e}/cypress/e2e/simple_passing.cy.js`,
|
||||
]
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ const runFailingProjectTest = function (buildAppExecutable, e2e) {
|
||||
`--spec=${e2e}/cypress/e2e/simple_failing.cy.js`,
|
||||
]
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ const runV8SnapshotProjectTest = function (buildAppExecutable, e2e) {
|
||||
`--spec=${e2e}/cypress/e2e/simple_v8_snapshot.cy.js`,
|
||||
]
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ const runErroringProjectTest = function (buildAppExecutable, e2e, testName, erro
|
||||
`--spec=${e2e}/cypress/e2e/simple_passing.cy.js`,
|
||||
]
|
||||
|
||||
if (verify.needsSandbox()) {
|
||||
if (needsSandbox()) {
|
||||
args.push('--no-sandbox')
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user