feat: improve stability when recording (#25837)

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
This commit is contained in:
Brian Mann
2023-02-24 17:32:36 -05:00
committed by GitHub
parent 948bc02700
commit 2a17efac74
56 changed files with 1727 additions and 468 deletions

View File

@@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'lmiller/fixing-vite-windows'
- 'fix/preflight'
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -41,6 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/preflight', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -51,6 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/preflight', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -71,6 +73,7 @@ windowsWorkflowFilters: &windows-workflow-filters
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'lmiller/fixing-vite-windows', << pipeline.git.branch >> ]
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/preflight', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -136,7 +139,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "lmiller/fixing-vite-windows" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "fix/preflight" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -1485,6 +1488,30 @@ jobs:
path: /tmp/cypress
- store-npm-logs
server-unit-tests-cloud-environment:
<<: *defaults
parameters:
<<: *defaultsParameters
resource_class:
type: string
default: medium
resource_class: << parameters.resource_class >>
parallelism: 1
steps:
- restore_cached_workspace
# TODO: Remove this once we switch off self-hosted M1 runners
- when:
condition:
equal: [ *darwin-arm64-executor, << parameters.executor >> ]
steps:
- run: rm -f /tmp/cypress/junit/*
- run: yarn workspace @packages/server test-unit cloud/environment_spec.ts
- verify-mocha-results:
expectedResultCount: 1
- store_test_results:
path: /tmp/cypress
- store-npm-logs
server-integration-tests:
<<: *defaults
parallelism: 1
@@ -2577,6 +2604,7 @@ linux-x64-workflow: &linux-x64-workflow
context:
- test-runner:upload
- test-runner:commit-status-checks
- test-runner:build-binary
requires:
- build
# various testing scenarios, like building full binary
@@ -2677,6 +2705,7 @@ linux-arm64-workflow: &linux-arm64-workflow
context:
- test-runner:upload
- test-runner:commit-status-checks
- test-runner:build-binary
executor: linux-arm64
resource_class: arm.medium
requires:
@@ -2694,6 +2723,12 @@ linux-arm64-workflow: &linux-arm64-workflow
resource_class: arm.medium
requires:
- linux-arm64-build
- server-unit-tests-cloud-environment:
name: linux-arm64-server-unit-tests-cloud-environment
executor: linux-arm64
resource_class: arm.medium
requires:
- linux-arm64-build
darwin-x64-workflow: &darwin-x64-workflow
jobs:
@@ -2717,6 +2752,7 @@ darwin-x64-workflow: &darwin-x64-workflow
- test-runner:sign-mac-binary
- test-runner:upload
- test-runner:commit-status-checks
- test-runner:build-binary
executor: mac
resource_class: macos.x86.medium.gen2
requires:
@@ -2740,6 +2776,12 @@ darwin-x64-workflow: &darwin-x64-workflow
resource_class: macos.x86.medium.gen2
requires:
- darwin-x64-build
- server-unit-tests-cloud-environment:
name: darwin-x64-driver-server-unit-tests-cloud-environment
executor: mac
resource_class: macos.x86.medium.gen2
requires:
- darwin-x64-build
darwin-arm64-workflow: &darwin-arm64-workflow
jobs:
@@ -2762,6 +2804,7 @@ darwin-arm64-workflow: &darwin-arm64-workflow
- test-runner:sign-mac-binary
- test-runner:upload
- test-runner:commit-status-checks
- test-runner:build-binary
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
@@ -2779,6 +2822,12 @@ darwin-arm64-workflow: &darwin-arm64-workflow
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-build
- server-unit-tests-cloud-environment:
name: darwin-arm64-server-unit-tests-cloud-environment
executor: darwin-arm64
resource_class: cypress-io/latest_m1
requires:
- darwin-arm64-build
windows-workflow: &windows-workflow
jobs:
@@ -2819,6 +2868,13 @@ windows-workflow: &windows-workflow
requires:
- windows-build
- server-unit-tests-cloud-environment:
name: windows-server-unit-tests-cloud-environment
executor: windows
resource_class: windows.medium
requires:
- windows-build
- create-build-artifacts:
name: windows-create-build-artifacts
executor: windows
@@ -2827,6 +2883,7 @@ windows-workflow: &windows-workflow
- test-runner:sign-windows-binary
- test-runner:upload
- test-runner:commit-status-checks
- test-runner:build-binary
requires:
- windows-build
- test-binary-against-kitchensink-chrome:

View File

@@ -8,10 +8,12 @@ _Released 02/28/2023 (PENDING)_
- It is now possible to set `hostOnly` cookies with [`cy.setCookie()`](https://docs.cypress.io/api/commands/setcookie) for a given domain. Addresses [#16856](https://github.com/cypress-io/cypress/issues/16856) and [#17527](https://github.com/cypress-io/cypress/issues/17527).
- Added a Public API for third party component libraries to define a Framework Definition, embedding their library into the Cypress onboarding workflow. Learn more [here](https://docs.cypress.io/guides/component-testing/third-party-definitions). Implemented in [#25780](https://github.com/cypress-io/cypress/pull/25780) and closes [#25638](https://github.com/cypress-io/cypress/issues/25638).
- Added a Debug Page tutorial slideshow for projects that are not connected to Cypress Cloud. Addresses [#25768](https://github.com/cypress-io/cypress/issues/25768).
- Improved various error message around interactions with the Cypress cloud. Implemented in [#25837](https://github.com/cypress-io/cypress/pull/25837)
- Updated the "new" status badge for the Debug page navigation link to be less noticeable when the navigation is collapsed. Addresses [#25739](https://github.com/cypress-io/cypress/issues/25739).
**Bugfixes:**
- Fixed various bugs when recording to the cloud. Fixed in [#25837](https://github.com/cypress-io/cypress/pull/25837)
- Fixed an issue where cookies were being duplicated with the same hostname, but a prepended dot. Fixed an issue where cookies may not be expiring correctly. Fixes [#25174](https://github.com/cypress-io/cypress/issues/25174), [#25205](https://github.com/cypress-io/cypress/issues/25205) and [#25495](https://github.com/cypress-io/cypress/issues/25495).
- Fixed an issue where cookies weren't being synced when the application was stable. Fixed in [#25855](https://github.com/cypress-io/cypress/pull/25855). Fixes [#25835](https://github.com/cypress-io/cypress/issues/25835).
- Added missing TypeScript type definitions for the [`cy.reload()`](https://docs.cypress.io/api/commands/reload) command. Addressed in [#25779](https://github.com/cypress-io/cypress/pull/25779).

View File

@@ -68,7 +68,7 @@ CANNOT_TRASH_ASSETS: (arg1: string) => {
return errTemplate`\
Warning: We failed to trash the existing run results.
This error will not alter the exit code.
This error will not affect or change the exit code.
${details(arg1)}`
},

View File

@@ -36,7 +36,7 @@
</head>
<body><pre><span style="color:#e05561">Warning: We failed to remove old browser profiles from previous runs.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#c062de"><span style="color:#e6e6e6">
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">

View File

@@ -36,7 +36,7 @@
</head>
<body><pre><span style="color:#e05561">Warning: We failed to trash the existing run results.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#c062de"><span style="color:#e6e6e6">
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">

View File

@@ -34,11 +34,10 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">We will retry 1 more time in 5 seconds...<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The server&#39;s response was:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,11 +34,10 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">We will retry 3 more times in 5 seconds...<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The server&#39;s response was:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,11 +34,11 @@
</style>
</head>
<body><pre><span style="color:#e05561">Warning: We encountered an error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">Warning: We encountered an error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This run will not be recorded.<span style="color:#e6e6e6">
<span style="color:#e05561">This run will proceed, but will not be recorded.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,14 +34,12 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Because you passed the <span style="color:#de73ff">--parallel<span style="color:#e05561"> flag, this run cannot proceed because it requires a valid response from our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --group flag you passed was: <span style="color:#e5e510">foo<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The server&#39;s response was:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,12 +34,10 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --group flag you passed was: <span style="color:#e5e510">foo<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The server&#39;s response was:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -38,7 +38,7 @@
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">These results will not be recorded.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,7 +34,7 @@
</style>
</head>
<body><pre><span style="color:#e05561">Recording this run failed because the request was invalid.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">Recording this run failed. The request was invalid.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e5e510">request should follow postRunRequest@2.0.0 schema<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">

View File

@@ -42,7 +42,7 @@
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">We will list the correct projectId in the &#39;Settings&#39; tab.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Alternatively, you can create a new project using the Desktop Application.<span style="color:#e6e6e6">
<span style="color:#e05561">Alternatively, you can create a new project directly from within the Cypress app.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">https://on.cypress.io/dashboard<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561">https://on.cypress.io/cloud<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,14 +34,12 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected error talking to our servers.<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected error communicating with our servers.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">There is likely something wrong with the request.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --group flag you passed was: <span style="color:#e5e510">foo<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">The server&#39;s response was:<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#de73ff">StatusCodeError: 500 - &quot;Internal Server Error&quot;<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561">The --ciBuildId flag you passed was: <span style="color:#e5e510">invalid<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -43,7 +43,7 @@
<span style="color:#e05561"><span style="color:#4f5666"> - <span style="color:#e05561"><span style="color:#4ec4ff">vite.config.js<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#4f5666"> - <span style="color:#e05561"><span style="color:#4ec4ff">vite.config.ts<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Add your <span style="color:#e5e510">vite<span style="color:#e05561"> config at one of the above paths, or import your configuration file and provide it to <span style="color:#e6e6e6">
<span style="color:#e05561">Add your <span style="color:#e5e510">vite<span style="color:#e05561"> config at one of the above paths, or import your configuration file and provide it to<span style="color:#e6e6e6">
<span style="color:#e05561">the devServer config as a <span style="color:#e5e510">viteConfig<span style="color:#e05561"> option.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,7 +34,7 @@
</style>
</head>
<body><pre><span style="color:#e05561">We&#39;re ending the experimental phase of Cypress Studio in Cypress version 10.0.0. <span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We&#39;re ending the experimental phase of Cypress Studio in Cypress version 10.0.0.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">If you don&#39;t think you can live without Studio or you&#39;d like to learn about how to work around its removal, please join the discussion here: http://on.cypress.io/studio-removal<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">

View File

@@ -34,7 +34,7 @@
</style>
</head>
<body><pre><span style="color:#e05561">You are running Cypress version 10.0.0 in global mode, but you are attempting to migrate a project where Cypress version 9.6.0 is installed. <span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">You are running Cypress version 10.0.0 in global mode, but you are attempting to migrate a project where Cypress version 9.6.0 is installed.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Ensure the project you are migrating has Cypress version Cypress version 10.0.0 installed.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">

View File

@@ -36,5 +36,5 @@
</head>
<body><pre><span style="color:#e05561">You&#39;re not logged in.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Run <span style="color:#e5e510">cypress open<span style="color:#e05561"> to open the Desktop App and log in.<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span>
<span style="color:#e05561">Run <span style="color:#e5e510">cypress open<span style="color:#e05561"> to open Cypress and log in.<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -40,5 +40,5 @@
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">These results will not be recorded.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -34,10 +34,11 @@
</style>
</head>
<body><pre><span style="color:#e05561">We encountered an unexpected internal error. Please check GitHub or open a new issue <span style="color:#e6e6e6">
<span style="color:#e05561">if you don&#39;t see one already with the details below:<span style="color:#e6e6e6">
<body><pre><span style="color:#e05561">We encountered an unexpected internal error.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">Please check GitHub or open a new issue if you don&#39;t see one already with the details below:<span style="color:#e6e6e6">
<span style="color:#c062de"><span style="color:#e6e6e6">
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">
<span style="color:#c062de"> at UNEXPECTED_INTERNAL_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span>
<span style="color:#c062de"> at UNEXPECTED_INTERNAL_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
</pre></body></html>

View File

@@ -36,7 +36,7 @@
</head>
<body><pre><span style="color:#e05561">Warning: We failed processing this video.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#c062de"><span style="color:#e6e6e6">
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">

View File

@@ -36,7 +36,7 @@
</head>
<body><pre><span style="color:#e05561">Warning: We failed to record the video.<span style="color:#e6e6e6">
<span style="color:#e05561"><span style="color:#e6e6e6">
<span style="color:#e05561">This error will not alter the exit code.<span style="color:#e6e6e6">
<span style="color:#e05561">This error will not affect or change the exit code.<span style="color:#e6e6e6">
<span style="color:#c062de"><span style="color:#e6e6e6">
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">

View File

@@ -55,7 +55,7 @@ export const AllCypressErrors = {
return errTemplate`\
Warning: We failed to trash the existing run results.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.stackTrace(arg1)}`
},
@@ -63,7 +63,7 @@ export const AllCypressErrors = {
return errTemplate`\
Warning: We failed to remove old browser profiles from previous runs.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.stackTrace(arg1)}`
},
@@ -71,7 +71,7 @@ export const AllCypressErrors = {
return errTemplate`\
Warning: We failed to record the video.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.stackTrace(arg1)}`
},
@@ -79,7 +79,7 @@ export const AllCypressErrors = {
return errTemplate`\
Warning: We failed processing this video.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.stackTrace(arg1)}`
},
@@ -133,7 +133,7 @@ export const AllCypressErrors = {
return errTemplate`\
You're not logged in.
Run ${fmt.highlight(`cypress open`)} to open the Desktop App and log in.`
Run ${fmt.highlight(`cypress open`)} to open Cypress and log in.`
},
TESTS_DID_NOT_START_RETRYING: (arg1: string) => {
return errTemplate`Timed out waiting for the browser to connect. ${fmt.off(arg1)}`
@@ -144,52 +144,49 @@ export const AllCypressErrors = {
CLOUD_CANCEL_SKIPPED_SPEC: () => {
return errTemplate`${fmt.off(`\n `)}This spec and its tests were skipped because the run has been canceled.`
},
CLOUD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: Error}) => {
CLOUD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delayMs: number, response: Error}) => {
const time = pluralize('time', arg1.tries)
const delay = humanTime.long(arg1.delay, false)
const delay = humanTime.long(arg1.delayMs, false)
return errTemplate`\
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
We will retry ${fmt.off(arg1.tries)} more ${fmt.off(time)} in ${fmt.off(delay)}...
The server's response was:
${fmt.highlightSecondary(arg1.response)}`
`
/* Because of fmt.listFlags() and fmt.listItems() */
/* eslint-disable indent */
},
CLOUD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: Error}) => {
return errTemplate`\
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
Because you passed the ${fmt.flag(`--parallel`)} flag, this run cannot proceed because it requires a valid response from our servers.
${fmt.listFlags(arg1.flags, {
group: '--group',
ciBuildId: '--ciBuildId',
})}
The server's response was:
${fmt.highlightSecondary(arg1.response)}`
})}`
},
CLOUD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: Error}) => {
return errTemplate`\
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
${fmt.listFlags(arg1.flags, {
group: '--group',
ciBuildId: '--ciBuildId',
})}
The server's response was:
${fmt.highlightSecondary(arg1.response)}`
})}`
},
CLOUD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: Error}) => {
return errTemplate`\
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
${fmt.highlightSecondary(arg1.response)}
There is likely something wrong with the request.
@@ -198,11 +195,7 @@ export const AllCypressErrors = {
group: '--group',
parallel: '--parallel',
ciBuildId: '--ciBuildId',
})}
The server's response was:
${fmt.highlightSecondary(arg1.response)}`
})}`
},
CLOUD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props?: any, message: string}) => {
if (!Object.keys(arg1.props).length) {
@@ -361,15 +354,15 @@ export const AllCypressErrors = {
${fmt.highlightSecondary(`Auto Cancellation`)} is not included under your current billing plan.
To enable this service, please visit your billing and upgrade to another plan with Auto Cancellation.
${fmt.off(arg1.link)}`
},
CLOUD_AUTO_CANCEL_MISMATCH: (arg1: {runUrl: string}) => {
return errTemplate`\
You passed the ${fmt.flag(`--auto-cancel-after-failures`)} flag, but this run originally started with a different value for the ${fmt.flag(`--auto-cancel-after-failures`)} flag.
The existing run is: ${fmt.url(arg1.runUrl)}
${fmt.listFlags(arg1, {
tags: '--tag',
group: '--group',
@@ -379,7 +372,7 @@ export const AllCypressErrors = {
})}
The first setting of --auto-cancel-after-failures for any given run takes precedent.
https://on.cypress.io/auto-cancellation-mismatch`
},
DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => {
@@ -497,7 +490,7 @@ export const AllCypressErrors = {
},
CLOUD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: string[], object: object}) => {
return errTemplate`\
Recording this run failed because the request was invalid.
Recording this run failed. The request was invalid.
${fmt.highlight(arg1.message)}
@@ -517,7 +510,7 @@ export const AllCypressErrors = {
These results will not be recorded.
This error will not alter the exit code.`
This error will not affect or change the exit code.`
},
CLOUD_CANNOT_UPLOAD_RESULTS: (apiErr: Error) => {
return errTemplate`\
@@ -525,17 +518,17 @@ export const AllCypressErrors = {
These results will not be recorded.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.highlightSecondary(apiErr)}`
},
CLOUD_CANNOT_CREATE_RUN_OR_INSTANCE: (apiErr: Error) => {
return errTemplate`\
Warning: We encountered an error talking to our servers.
Warning: We encountered an error communicating with our servers.
This run will not be recorded.
This run will proceed, but will not be recorded.
This error will not alter the exit code.
This error will not affect or change the exit code.
${fmt.highlightSecondary(apiErr)}`
},
@@ -559,9 +552,9 @@ export const AllCypressErrors = {
We will list the correct projectId in the 'Settings' tab.
Alternatively, you can create a new project using the Desktop Application.
Alternatively, you can create a new project directly from within the Cypress app.
https://on.cypress.io/dashboard`
https://on.cypress.io/cloud`
},
// TODO: make this relative path, not absolute
NO_PROJECT_ID: (configFilePath: string) => {
@@ -879,7 +872,7 @@ export const AllCypressErrors = {
CONFIG_FILES_LANGUAGE_CONFLICT: (projectRoot: string, filesFound: string[]) => {
return errTemplate`
Could not load a Cypress configuration file because there are multiple matches.
We've found ${fmt.highlight(filesFound.length)} Cypress configuration files named
${fmt.highlight(filesFound.join(', '))} at the location below:
@@ -1141,7 +1134,7 @@ export const AllCypressErrors = {
The ${fmt.highlight(`experimentalSessionSupport`)} configuration option was removed in ${fmt.cypressVersion(`9.6.0`)}.
You can safely remove this option from your config.
https://on.cypress.io/session`
},
EXPERIMENTAL_SESSION_AND_ORIGIN_REMOVED: () => {
@@ -1149,7 +1142,7 @@ export const AllCypressErrors = {
The ${fmt.highlight(`experimentalSessionAndOrigin`)} configuration option was removed in ${fmt.cypressVersion(`12.0.0`)}.
You can safely remove this option from your config.
https://on.cypress.io/session
https://on.cypress.io/origin`
},
@@ -1173,10 +1166,10 @@ export const AllCypressErrors = {
},
EXPERIMENTAL_STUDIO_REMOVED: () => {
return errTemplate`\
We're ending the experimental phase of Cypress Studio in ${fmt.cypressVersion(`10.0.0`)}.
We're ending the experimental phase of Cypress Studio in ${fmt.cypressVersion(`10.0.0`)}.
If you don't think you can live without Studio or you'd like to learn about how to work around its removal, please join the discussion here: http://on.cypress.io/studio-removal
Your feedback will help us factor in product decisions that may see Studio return in a future release.
You can safely remove the ${fmt.highlight(`experimentalStudio`)} configuration option from your config.`
@@ -1400,11 +1393,11 @@ export const AllCypressErrors = {
return errTemplate`\
The ${fmt.highlight('pluginsFile')} configuration option you have supplied has been replaced with ${fmt.highlightSecondary('setupNodeEvents')}.
This new option is not a one-to-one correlation and it must be configured separately as a testing type property: ${fmt.highlightSecondary('e2e.setupNodeEvents')} and ${fmt.highlightSecondary('component.setupNodeEvents')}
${fmt.code(code)}
https://on.cypress.io/migration-guide`
},
@@ -1565,8 +1558,9 @@ export const AllCypressErrors = {
UNEXPECTED_INTERNAL_ERROR: (err: Error) => {
return errTemplate`
We encountered an unexpected internal error. Please check GitHub or open a new issue
if you don't see one already with the details below:
We encountered an unexpected internal error.
Please check GitHub or open a new issue if you don't see one already with the details below:
${fmt.stackTrace(err)}
`
@@ -1617,7 +1611,7 @@ export const AllCypressErrors = {
${fmt.code(code)}
https://on.cypress.io/migration-guide
${stackTrace}
`
},
@@ -1661,14 +1655,14 @@ export const AllCypressErrors = {
${fmt.code(code)}
https://on.cypress.io/migration-guide
${stackTrace}
`
},
MIGRATION_MISMATCHED_CYPRESS_VERSIONS: (version: string, currentVersion: string) => {
return errTemplate`
You are running ${fmt.cypressVersion(currentVersion)} in global mode, but you are attempting to migrate a project where ${fmt.cypressVersion(version)} is installed.
You are running ${fmt.cypressVersion(currentVersion)} in global mode, but you are attempting to migrate a project where ${fmt.cypressVersion(version)} is installed.
Ensure the project you are migrating has Cypress version ${fmt.cypressVersion(currentVersion)} installed.
@@ -1691,14 +1685,14 @@ export const AllCypressErrors = {
return errTemplate`\
You are using ${fmt.highlight(devServer)} for your dev server, but a configuration file was not found. We traversed upwards from:
${fmt.highlightSecondary(root)}
looking for a file named:
${fmt.listItems(searchedFor, { prefix: ' - ' })}
Add your ${fmt.highlight(devServer)} config at one of the above paths, or import your configuration file and provide it to
Add your ${fmt.highlight(devServer)} config at one of the above paths, or import your configuration file and provide it to
the devServer config as a ${fmt.highlight(devServerConfigFile)} option.
`
},

View File

@@ -375,12 +375,12 @@ describe('visual error templates', () => {
return {
default: [{
tries: 3,
delay: 5000,
delayMs: 5000,
response: makeApiErr(),
}],
lastTry: [{
tries: 1,
delay: 5000,
delayMs: 5000,
response: makeApiErr(),
}],
}

View File

@@ -13,18 +13,18 @@ const errors = require('../errors')
const { apiUrl, apiRoutes, makeRoutes } = require('./routes')
import Bluebird from 'bluebird'
import type { OptionsWithUrl } from 'request-promise'
import { getText } from '../util/status_code'
import * as enc from './encryption'
import getEnvInformationForProjectRoot from './environment'
import type { OptionsWithUrl } from 'request-promise'
const THIRTY_SECONDS = humanInterval('30 seconds')
const SIXTY_SECONDS = humanInterval('60 seconds')
const TWO_MINUTES = humanInterval('2 minutes')
const DELAYS: number[] = process.env.API_RETRY_INTERVALS ? process.env.API_RETRY_INTERVALS.split(',').map(_.toNumber) : [
THIRTY_SECONDS,
SIXTY_SECONDS,
TWO_MINUTES,
]
const DELAYS: number[] = process.env.API_RETRY_INTERVALS
? process.env.API_RETRY_INTERVALS.split(',').map(_.toNumber)
: [THIRTY_SECONDS, SIXTY_SECONDS, TWO_MINUTES]
const runnerCapabilities = {
'dynamicSpecsInSerialMode': true,
@@ -35,6 +35,7 @@ let responseCache = {}
class DecryptionError extends Error {
isDecryptionError = true
constructor (message: string) {
super(message)
this.name = 'DecryptionError'
@@ -51,7 +52,7 @@ const rp = request.defaults((params: CypressRequestOptions, callback) => {
let resp
if (params.cacheable && (resp = getCachedResponse(params))) {
debug('resolving with cached response for ', params.url)
debug('resolving with cached response for %o', { url: params.url })
return Bluebird.resolve(resp)
}
@@ -65,7 +66,7 @@ const rp = request.defaults((params: CypressRequestOptions, callback) => {
rejectUnauthorized: true,
})
const headers = params.headers != null ? params.headers : (params.headers = {})
const headers = params.headers ??= {}
_.defaults(headers, {
'x-os-name': os.platform(),
@@ -89,19 +90,35 @@ const rp = request.defaults((params: CypressRequestOptions, callback) => {
const { secretKey, jwe } = await enc.encryptRequest(params)
params.transform = async function (body, response) {
if (response.headers['x-cypress-encrypted'] || params.encrypt === 'always' && response.statusCode < 500) {
const { statusCode } = response
const options = this // request promise options
const throwStatusCodeErrWithResp = (message, responseBody) => {
throw new RequestErrors.StatusCodeError(response.statusCode, message, options, responseBody)
}
// response is valid and we are encrypting
if (response.headers['x-cypress-encrypted'] || params.encrypt === 'always') {
let decryptedBody
try {
decryptedBody = await enc.decryptResponse(body, secretKey)
} catch (e) {
// we failed decrypting the response...
// if status code is >=500 or 404 remove body
if (statusCode >= 500 || statusCode === 404) {
// remove server responses and replace with basic status code text
throwStatusCodeErrWithResp(getText(statusCode), body)
}
throw new DecryptionError(e.message)
}
// If we've hit an encrypted payload error case, we need to re-constitute the error
// as it would happen normally, with the body as an error property
if (response.statusCode > 400) {
throw new RequestErrors.StatusCodeError(response.statusCode, decryptedBody, {}, decryptedBody)
throwStatusCodeErrWithResp(decryptedBody, decryptedBody)
}
return decryptedBody
@@ -136,28 +153,29 @@ const getCachedResponse = (params) => {
}
const retryWithBackoff = (fn) => {
// for e2e testing purposes
let attempt
if (process.env.DISABLE_API_RETRIES) {
debug('api retries disabled')
return Bluebird.try(() => fn(0))
}
return (attempt = (retryIndex) => {
const attempt = (retryIndex) => {
return Bluebird
.try(() => fn(retryIndex))
.catch(RequestErrors.TransformError, (err) => {
// Unroll the error thrown from within the transform
throw err.cause
})
.catch(isRetriableError, (err) => {
if (retryIndex > DELAYS.length) {
if (retryIndex >= DELAYS.length) {
throw err
}
const delay = DELAYS[retryIndex]
const delayMs = DELAYS[retryIndex]
errors.warning(
'CLOUD_API_RESPONSE_FAILED_RETRYING', {
delay,
delayMs,
tries: DELAYS.length - retryIndex,
response: err,
},
@@ -166,17 +184,16 @@ const retryWithBackoff = (fn) => {
retryIndex++
return Bluebird
.delay(delay)
.delay(delayMs)
.then(() => {
debug(`retry #${retryIndex} after ${delay}ms`)
debug(`retry #${retryIndex} after ${delayMs}ms`)
return attempt(retryIndex)
})
})
.catch(RequestErrors.TransformError, (err) => {
throw err.cause
})
})(0)
}
return attempt(0)
}
const formatResponseBody = function (err) {
@@ -198,10 +215,9 @@ const tagError = function (err) {
}
// retry on timeouts, 5xx errors, or any error without a status code
// do not retry on decryption errors
// including decryption errors
const isRetriableError = (err) => {
// TransformError means something failed in decryption handling
if (err instanceof RequestErrors.TransformError) {
if (err instanceof DecryptionError) {
return false
}
@@ -211,6 +227,7 @@ const isRetriableError = (err) => {
}
export type CreateRunOptions = {
projectRoot: string
ci: string
ciBuildId: string
projectId: string
@@ -228,7 +245,6 @@ export type CreateRunOptions = {
let preflightResult = {
encrypt: true,
apiUrl,
}
let recordRoutes = apiRoutes
@@ -248,7 +264,6 @@ module.exports = {
recordRoutes = apiRoutes
preflightResult = {
encrypt: true,
apiUrl,
}
},
@@ -270,9 +285,10 @@ module.exports = {
},
createRun (options: CreateRunOptions) {
const preflightOptions = _.pick(options, ['projectId', 'ciBuildId', 'browser', 'testingType', 'parallel'])
const preflightOptions = _.pick(options, ['projectId', 'projectRoot', 'ciBuildId', 'browser', 'testingType', 'parallel', 'timeout'])
return this.preflight(preflightOptions).then((result) => {
return this.sendPreflight(preflightOptions)
.then((result) => {
const { warnings } = result
return retryWithBackoff((attemptIndex) => {
@@ -300,7 +316,7 @@ module.exports = {
url: recordRoutes.runs(),
json: true,
encrypt: preflightResult.encrypt,
timeout: options.timeout != null ? options.timeout : SIXTY_SECONDS,
timeout: options.timeout ?? SIXTY_SECONDS,
headers: {
'x-route-version': '4',
'x-cypress-request-attempt': attemptIndex,
@@ -334,7 +350,7 @@ module.exports = {
url: recordRoutes.instances(runId),
json: true,
encrypt: preflightResult.encrypt,
timeout: timeout != null ? timeout : SIXTY_SECONDS,
timeout: timeout ?? SIXTY_SECONDS,
headers: {
'x-route-version': '5',
'x-cypress-run-id': runId,
@@ -354,7 +370,7 @@ module.exports = {
url: recordRoutes.instanceTests(instanceId),
json: true,
encrypt: preflightResult.encrypt,
timeout: timeout || SIXTY_SECONDS,
timeout: timeout ?? SIXTY_SECONDS,
headers: {
'x-route-version': '1',
'x-cypress-run-id': runId,
@@ -372,7 +388,7 @@ module.exports = {
return rp.put({
url: recordRoutes.instanceStdout(options.instanceId),
json: true,
timeout: options.timeout != null ? options.timeout : SIXTY_SECONDS,
timeout: options.timeout ?? SIXTY_SECONDS,
body: {
stdout: options.stdout,
},
@@ -393,7 +409,7 @@ module.exports = {
url: recordRoutes.instanceResults(options.instanceId),
json: true,
encrypt: preflightResult.encrypt,
timeout: options.timeout != null ? options.timeout : SIXTY_SECONDS,
timeout: options.timeout ?? SIXTY_SECONDS,
headers: {
'x-route-version': '1',
'x-cypress-run-id': options.runId,
@@ -452,23 +468,52 @@ module.exports = {
responseCache = {}
},
preflight (preflightInfo) {
sendPreflight (preflightInfo) {
return retryWithBackoff(async (attemptIndex) => {
const preflightBase = process.env.CYPRESS_API_URL ? apiUrl.replace('api', 'api-proxy') : apiUrl
const result = await rp.post({
url: `${preflightBase}preflight`,
body: {
apiUrl,
envUrl: process.env.CYPRESS_API_URL,
...preflightInfo,
},
headers: {
'x-route-version': '1',
'x-cypress-request-attempt': attemptIndex,
},
json: true,
encrypt: 'always',
})
const { timeout, projectRoot } = preflightInfo
preflightInfo = _.omit(preflightInfo, 'timeout', 'projectRoot')
const preflightBaseProxy = apiUrl.replace('api', 'api-proxy')
const envInformation = await getEnvInformationForProjectRoot(projectRoot, process.pid.toString())
const makeReq = ({ baseUrl, agent }) => {
return rp.post({
url: `${baseUrl}preflight`,
body: {
apiUrl,
envUrl: envInformation.envUrl,
dependencies: envInformation.dependencies,
errors: envInformation.errors,
...preflightInfo,
},
headers: {
'x-route-version': '1',
'x-cypress-request-attempt': attemptIndex,
},
timeout: timeout ?? SIXTY_SECONDS,
json: true,
encrypt: 'always',
agent,
})
.catch(RequestErrors.TransformError, (err) => {
// Unroll the error thrown from within the transform
throw err.cause
})
}
const postReqs = async () => {
return makeReq({ baseUrl: preflightBaseProxy, agent: null })
.catch((err) => {
if (err.statusCode === 412) {
throw err
}
return makeReq({ baseUrl: apiUrl, agent })
})
}
const result = await postReqs()
preflightResult = result // { encrypt: boolean, apiUrl: string }
recordRoutes = makeRoutes(result.apiUrl)

View File

@@ -0,0 +1,152 @@
import { exec } from 'child_process'
import { promisify } from 'util'
import base64Url from 'base64url'
import fs from 'fs-extra'
import resolvePackagePath from 'resolve-package-path'
const execAsync = promisify(exec)
// See https://whimsical.com/encryption-logic-BtJJkN7TxacK8kaHDgH1zM for more information on what this is doing
const getProcessBranchForPid = async (pid: string) => {
const { stdout } = await execAsync('ps -eo pid=,ppid=')
const processTree = stdout.split('\n').reduce((acc, line) => {
const [pid, ppid] = line.trim().split(/\s+/)
acc.set(pid, ppid)
return acc
}, new Map())
const currentProcessBranch: string[] = []
while (pid && pid !== '0') {
currentProcessBranch.push(pid)
pid = processTree.get(pid)
}
return currentProcessBranch
}
interface GetCypressEnvUrlFromProcessBranch {
envUrl?: string
error?: {
dependency?: string
name: string
message: string
stack: string
}
}
// See https://whimsical.com/encryption-logic-BtJJkN7TxacK8kaHDgH1zM for more information on what this is doing
const getCypressEnvUrlFromProcessBranch = async (pid: string): Promise<GetCypressEnvUrlFromProcessBranch> => {
let error: { name: string, message: string, stack: string } | undefined
let envUrl: string | undefined
if (process.platform !== 'win32') {
try {
const processBranch = await getProcessBranchForPid(pid)
const { stdout } = await execAsync(`ps eww -p ${processBranch.join(',')} -o pid=,command=`)
const pidEnvUrlMapping = stdout.split('\n').reduce((acc, line) => {
const cypressEnvUrl = line.trim().match(/(\d+)\s.*CYPRESS_API_URL=(\S+)\s/)
if (cypressEnvUrl) {
acc.set(cypressEnvUrl[1], cypressEnvUrl[2])
}
return acc
}, new Map())
const foundPid = processBranch.find((pid) => pidEnvUrlMapping.get(pid))
if (foundPid) {
envUrl = pidEnvUrlMapping.get(foundPid)
}
} catch (err) {
error = err
}
}
return {
envUrl,
error,
}
}
interface DependencyInformation {
maybeCheckProcessTreeIfPresent: string[]
neverCheckProcessTreeIfPresent: string[]
}
// See https://whimsical.com/encryption-logic-BtJJkN7TxacK8kaHDgH1zM for more information on what this is doing
const getEnvInformationForProjectRoot = async (projectRoot: string, pid: string) => {
let dependencies = {}
let errors: { dependency?: string, name: string, message: string, stack: string }[] = []
let envDependencies = process.env.CYPRESS_ENV_DEPENDENCIES
let envUrl = process.env.CYPRESS_API_URL
let checkProcessTree
if (envDependencies) {
const envDependenciesInformation = JSON.parse(base64Url.decode(envDependencies)) as DependencyInformation
const packageToJsonMapping: Record<string, string> = {}
const processDependency = ({ checkOnFound }) => {
return (dependency) => {
try {
const packageJsonPath = resolvePackagePath(dependency, projectRoot)
if (packageJsonPath) {
packageToJsonMapping[dependency] = packageJsonPath
checkProcessTree = checkOnFound
}
} catch (error) {
errors.push({
dependency,
name: error.name,
message: error.message,
stack: error.stack,
})
}
}
}
envDependenciesInformation.maybeCheckProcessTreeIfPresent.forEach(processDependency({ checkOnFound: true }))
envDependenciesInformation.neverCheckProcessTreeIfPresent.forEach(processDependency({ checkOnFound: false }))
const [{ envUrl: processTreeEnvUrl, error: processTreeError }] = await Promise.all([
checkProcessTree ? getCypressEnvUrlFromProcessBranch(pid) : { envUrl: undefined, error: undefined },
...Object.entries(packageToJsonMapping).map(async ([dependency, packageJsonPath]) => {
try {
const packageVersion = (await fs.readJSON(packageJsonPath)).version
dependencies[dependency] = {
version: packageVersion,
}
} catch (error) {
errors.push({
dependency,
name: error.name,
message: error.message,
stack: error.stack,
})
}
}),
])
if (processTreeEnvUrl || processTreeError) {
envUrl = processTreeEnvUrl
if (processTreeError) {
errors.push(processTreeError)
}
}
}
return {
envUrl,
errors,
dependencies,
}
}
export default getEnvInformationForProjectRoot

View File

@@ -267,7 +267,7 @@ const createRun = Promise.method((options = {}) => {
ciBuildId: null,
})
let { projectId, recordKey, platform, git, specPattern, specs, parallel, ciBuildId, group, tags, testingType, autoCancelAfterFailures } = options
let { projectRoot, projectId, recordKey, platform, git, specPattern, specs, parallel, ciBuildId, group, tags, testingType, autoCancelAfterFailures } = options
if (recordKey == null) {
recordKey = env.get('CYPRESS_RECORD_KEY')
@@ -310,6 +310,7 @@ const createRun = Promise.method((options = {}) => {
debugCiInfo('CI provider information %o', ci)
return api.createRun({
projectRoot,
specs,
group,
tags,
@@ -384,9 +385,8 @@ const createRun = Promise.method((options = {}) => {
}
})
}).catch((err) => {
debug('failed creating run with status %d %o', err.statusCode, {
stack: err.stack,
})
debug('failed creating run with status %o',
_.pick(err, ['name', 'message', 'statusCode', 'stack']))
switch (err.statusCode) {
case 401:
@@ -618,6 +618,7 @@ const createRunAndRecordSpecs = (options = {}) => {
}
return createRun({
projectRoot,
git,
specs,
group,

View File

@@ -107,6 +107,7 @@
"randomstring": "1.1.5",
"recast": "0.20.4",
"resolve": "1.17.0",
"resolve-package-path": "4.0.3",
"sanitize-filename": "1.6.3",
"semver": "7.3.2",
"send": "0.17.1",

View File

@@ -1196,7 +1196,7 @@ describe('lib/cypress', () => {
beforeEach(async function () {
await clearCtx()
sinon.stub(api, 'preflight').resolves()
sinon.stub(api, 'sendPreflight').resolves()
sinon.stub(api, 'createRun').resolves()
const createInstanceStub = sinon.stub(api, 'createInstance')

View File

@@ -0,0 +1 @@
!node_modules

View File

@@ -0,0 +1,6 @@
{
"name": "bar",
"version": "2.0.0",
"main": "src/index.js",
"type": "module"
}

View File

@@ -0,0 +1,6 @@
{
"name": "foo",
"version": "1.0.0",
"main": "index.js",
"type": "module"
}

View File

@@ -0,0 +1,8 @@
{
"name": "all-tracked-dependencies",
"version": "1.0.0",
"dependencies": {
"bar": "2.0.0",
"foo": "1.0.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "foo",
"version": "1.0.0",
"main": "index.js",
"type": "module"
}

View File

@@ -0,0 +1,7 @@
{
"name": "all-tracked-dependencies",
"version": "1.0.0",
"dependencies": {
"foo": "1.0.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"name": "bar",
"version": "2.0.0",
"main": "src/index.js",
"type": "module"
}

View File

@@ -0,0 +1,7 @@
{
"name": "all-tracked-dependencies",
"version": "1.0.0",
"dependencies": {
"bar": "2.0.0"
}
}

View File

@@ -0,0 +1,26 @@
import { spawn } from 'child_process'
import path from 'path'
import * as url from 'url'
// eslint-disable-next-line no-console
console.log('child', process.pid, process.ppid, process.env.CHILD_CYPRESS_API_URL)
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const proc = spawn('node', ['grandchild.js'], {
cwd: path.join(__dirname),
stdio: 'inherit',
env: {
...process.env,
CYPRESS_API_URL: process.env.PARENT_CYPRESS_API_URL,
},
})
const timeout = setTimeout(() => {
}, 1e9)
process.on('SIGTERM', () => {
clearTimeout(timeout)
proc.kill()
})

View File

@@ -0,0 +1,10 @@
// eslint-disable-next-line no-console
console.log('grandchild', process.pid, process.ppid, process.env.GRANDCHILD_CYPRESS_API_URL)
const timeout = setTimeout(() => {
}, 1e9)
process.on('SIGTERM', () => {
clearTimeout(timeout)
})

View File

@@ -0,0 +1,26 @@
import { spawn } from 'child_process'
import path from 'path'
import * as url from 'url'
// eslint-disable-next-line no-console
console.log('parent', process.pid, process.ppid, process.env.PARENT_CYPRESS_API_URL)
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const proc = spawn('node', ['child.js'], {
cwd: path.join(__dirname),
stdio: 'inherit',
env: {
...process.env,
CYPRESS_API_URL: process.env.PARENT_CYPRESS_API_URL,
},
})
const timeout = setTimeout(() => {
}, 1e9)
process.on('SIGTERM', () => {
clearTimeout(timeout)
proc.kill()
})

View File

@@ -0,0 +1,5 @@
{
"name": "test-project",
"version": "1.0.0",
"type": "module"
}

View File

@@ -1,6 +1,7 @@
const crypto = require('crypto')
const jose = require('jose')
const base64Url = require('base64url')
const stealthyRequire = require('stealthy-require')
require('../../spec_helper')
@@ -19,6 +20,8 @@ const machineId = require('../../../lib/cloud/machine_id')
const Promise = require('bluebird')
const API_BASEURL = 'http://localhost:1234'
const API_PROD_BASEURL = 'https://api.cypress.io'
const API_PROD_PROXY_BASEURL = 'https://api-proxy.cypress.io'
const CLOUD_BASEURL = 'http://localhost:3000'
const AUTH_URLS = {
'dashboardAuthUrl': 'http://localhost:3000/test-runner.html',
@@ -29,11 +32,12 @@ const makeError = (details = {}) => {
return _.extend(new Error(details.message || 'Some error'), details)
}
const preflightNock = (encrypted = false) => {
const encryptRequest = encryption.encryptRequest
const decryptReqBodyAndRespond = ({ reqBody, resBody }, fn) => {
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
})
const encryptRequest = encryption.encryptRequest
/**
* @type {crypto.KeyObject}
@@ -41,46 +45,64 @@ const preflightNock = (encrypted = false) => {
let _secretKey
sinon.stub(encryption, 'encryptRequest').callsFake(async (params) => {
if (reqBody) {
expect(params.body).to.deep.eq(reqBody)
}
const { secretKey, jwe } = await encryptRequest(params, publicKey)
if (fn) {
encryption.encryptRequest.restore()
}
_secretKey = secretKey
return { secretKey, jwe }
})
nock(API_BASEURL)
.defaultReplyHeaders({ 'x-cypress-encrypted': 'true' })
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-cypress-version', pkg.version)
.post('/preflight', () => true)
.reply(200, async (uri, requestBody) => {
return async (uri, encReqBody) => {
const decryptedSecretKey = crypto.createSecretKey(
crypto.privateDecrypt(
privateKey,
Buffer.from(base64Url.toBase64(requestBody.recipients[0].encrypted_key), 'base64'),
Buffer.from(base64Url.toBase64(encReqBody.recipients[0].encrypted_key), 'base64'),
),
)
const decrypted = await encryption.decryptResponse(requestBody, privateKey)
expect(_secretKey.export().toString('utf8')).to.eq(decryptedSecretKey.export().toString('utf8'))
const enc = new jose.GeneralEncrypt(
Buffer.from(JSON.stringify({ encrypted, apiUrl: decrypted.apiUrl })),
Buffer.from(JSON.stringify(resBody)),
)
enc.setProtectedHeader({ alg: 'A256GCMKW', enc: 'A256GCM', zip: 'DEF' }).addRecipient(decryptedSecretKey)
const jweResponse = await enc.encrypt()
fn && fn()
return jweResponse
})
}
}
const preflightNock = (baseUrl) => {
return nock(baseUrl)
.matchHeader('x-route-version', '1')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-cypress-version', pkg.version)
.post('/preflight')
}
describe('lib/cloud/api', () => {
beforeEach(() => {
api.setPreflightResult({ encrypt: false })
preflightNock(false)
preflightNock(API_BASEURL)
.reply(200, decryptReqBodyAndRespond({
resBody: {
encrypt: false,
apiUrl: `${API_BASEURL}/`,
},
}))
nock(API_BASEURL)
.matchHeader('x-route-version', '2')
@@ -196,17 +218,307 @@ describe('lib/cloud/api', () => {
})
})
context('.preflight', () => {
it('POST /preflight + returns encryption', function () {
context('.sendPreflight', () => {
let prodApi
beforeEach(function () {
this.timeout(30000)
nock.cleanAll()
sinon.restore()
sinon.stub(os, 'platform').returns('linux')
preflightNock(true)
return api.preflight({ projectId: 'abc123' })
process.env.CYPRESS_CONFIG_ENV = 'production'
process.env.CYPRESS_API_URL = 'https://some.server.com'
if (!prodApi) {
prodApi = stealthyRequire(require.cache, () => {
return require('../../../lib/cloud/api')
}, () => {
require('../../../lib/cloud/encryption')
}, module)
}
})
it('POST /preflight to proxy. returns encryption', () => {
preflightNock(API_PROD_PROXY_BASEURL)
.reply(200, decryptReqBodyAndRespond({
reqBody: {
envUrl: 'https://some.server.com',
dependencies: {},
errors: [],
apiUrl: 'https://api.cypress.io/',
projectId: 'abc123',
},
resBody: {
encrypt: true,
apiUrl: `${API_PROD_BASEURL}/`,
},
}))
return prodApi.sendPreflight({ projectId: 'abc123' })
.then((ret) => {
expect(ret).to.deep.eq({ encrypted: true, apiUrl: `${API_BASEURL}/` })
expect(ret).to.deep.eq({ encrypt: true, apiUrl: `${API_PROD_BASEURL}/` })
})
})
it('POST /preflight to proxy, and then api on response status code failure. returns encryption', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(500)
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(200, decryptReqBodyAndRespond({
reqBody: {
envUrl: 'https://some.server.com',
dependencies: {},
errors: [],
apiUrl: 'https://api.cypress.io/',
projectId: 'abc123',
},
resBody: {
encrypt: true,
apiUrl: `${API_PROD_BASEURL}/`,
},
}))
return prodApi.sendPreflight({ projectId: 'abc123' })
.then((ret) => {
scopeProxy.done()
scopeApi.done()
expect(ret).to.deep.eq({ encrypt: true, apiUrl: `${API_PROD_BASEURL}/` })
})
})
it('POST /preflight to proxy, and then api on network failure. returns encryption', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.replyWithError('some request error')
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(200, decryptReqBodyAndRespond({
reqBody: {
envUrl: 'https://some.server.com',
dependencies: {},
errors: [],
apiUrl: 'https://api.cypress.io/',
projectId: 'abc123',
},
resBody: {
encrypt: true,
apiUrl: `${API_PROD_BASEURL}/`,
},
}))
return prodApi.sendPreflight({ projectId: 'abc123' })
.then((ret) => {
scopeProxy.done()
scopeApi.done()
expect(ret).to.deep.eq({ encrypt: true, apiUrl: `${API_PROD_BASEURL}/` })
})
})
it('sets timeout to 60 seconds', () => {
sinon.stub(api.rp, 'post').resolves({})
return api.sendPreflight({})
.then(() => {
expect(api.rp.post).to.be.calledWithMatch({ timeout: 60000 })
})
})
describe('errors', () => {
it('[F1] POST /preflight TimeoutError', () => {
preflightNock(API_BASEURL)
.times(2)
.delayConnection(5000)
.reply(200, {})
return api.sendPreflight({
timeout: 100,
})
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
expect(err.message).to.eq('Error: ESOCKETTIMEDOUT')
})
})
it('[F1] POST /preflight RequestError', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.replyWithError('first request error')
const scopeApi = preflightNock(API_PROD_BASEURL)
.replyWithError('2nd request error')
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).not.to.have.property('statusCode')
expect(err).to.contain({
name: 'RequestError',
message: 'Error: 2nd request error',
})
})
})
it('[F1] POST /preflight statusCode >= 500', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(500)
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(500)
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).to.contain({
name: 'StatusCodeError',
statusCode: 500,
})
})
})
it('[F2] POST /preflight statusCode = 404', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(404)
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(404, '<html>404 not found</html>', {
'Content-Type': 'text/html',
})
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).to.contain({
name: 'StatusCodeError',
statusCode: 404,
})
})
})
it('[F3] POST /preflight statusCode = 422 but decrypt error', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(422, { data: 'very encrypted and secure string' })
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(422, { data: 'very encrypted and secure string' })
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).not.to.have.property('statusCode')
expect(err).to.contain({
name: 'DecryptionError',
message: 'JWE Recipients missing or incorrect type',
})
})
})
it('[F3] POST /preflight statusCode = 200 but decrypt error', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(200, { data: 'very encrypted and secure string' })
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(201, 'very encrypted and secure string')
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).not.to.have.property('statusCode')
expect(err).to.contain({
name: 'DecryptionError',
message: 'General JWE must be an object',
})
})
})
it('[F3] POST /preflight statusCode = 201 but no body', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(200)
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(201)
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
scopeApi.done()
expect(err).not.to.have.property('statusCode')
expect(err).to.contain({
name: 'DecryptionError',
message: 'General JWE must be an object',
})
})
})
it('[F4] POST /preflight statusCode = 412 valid decryption', () => {
const scopeProxy = preflightNock(API_PROD_PROXY_BASEURL)
.reply(412, decryptReqBodyAndRespond({
reqBody: {
envUrl: 'https://some.server.com',
dependencies: {},
errors: [],
apiUrl: 'https://api.cypress.io/',
projectId: 'abc123',
},
resBody: {
message: 'Recording is not working',
errors: [
'attempted to send invalid data',
],
object: {
projectId: 'cy12345',
},
},
}))
const scopeApi = preflightNock(API_PROD_BASEURL)
.reply(200)
return prodApi.sendPreflight({ projectId: 'abc123' })
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
scopeProxy.done()
expect(scopeApi.isDone()).to.be.false
expect(err).to.contain({
name: 'StatusCodeError',
message: '412 - {"message":"Recording is not working","errors":["attempted to send invalid data"],"object":{"projectId":"cy12345"}}',
statusCode: 412,
})
})
})
})
})
@@ -257,6 +569,38 @@ describe('lib/cloud/api', () => {
})
})
it('POST /runs + returns runId with encryption', function () {
nock.cleanAll()
sinon.restore()
sinon.stub(os, 'platform').returns('linux')
preflightNock(API_BASEURL)
.reply(200, decryptReqBodyAndRespond({
resBody: {
encrypt: true,
apiUrl: `${API_BASEURL}/`,
},
}, () => {
nock(API_BASEURL)
.defaultReplyHeaders({ 'x-cypress-encrypted': 'true' })
.matchHeader('x-route-version', '4')
.matchHeader('x-os-name', 'linux')
.matchHeader('x-cypress-version', pkg.version)
.post('/runs')
.reply(200, decryptReqBodyAndRespond({
reqBody: this.buildProps,
resBody: {
runId: 'new-run-id-123',
},
}))
}))
return api.createRun(this.buildProps)
.then((ret) => {
expect(ret).to.deep.eq({ runId: 'new-run-id-123' })
})
})
it('POST /runs failure formatting', function () {
nock(API_BASEURL)
.matchHeader('x-route-version', '4')
@@ -330,6 +674,20 @@ describe('lib/cloud/api', () => {
expect(err.isApiError).to.be.true
})
})
it('tags errors on /preflight', function () {
preflightNock(API_BASEURL)
.times(2)
.reply(500, {})
return api.createRun({})
.then(() => {
throw new Error('should have thrown here')
})
.catch((err) => {
expect(err.isApiError).to.be.true
})
})
})
context('.createInstance', () => {
@@ -952,13 +1310,16 @@ describe('lib/cloud/api', () => {
return api.retryWithBackoff(fn1)
.then(() => {
throw new Error('Should not resolve 499 error')
}).catch((err) => {
})
.catch((err) => {
expect(err.message).to.equal('499 error')
return api.retryWithBackoff(fn2)
}).then(() => {
})
.then(() => {
throw new Error('Should not resolve 600 error')
}).catch((err) => {
})
.catch((err) => {
expect(err.message).to.equal('600 error')
})
})
@@ -1000,19 +1361,19 @@ describe('lib/cloud/api', () => {
expect(errors.warning).to.be.calledThrice
expect(errors.warning.firstCall.args[0]).to.eql('CLOUD_API_RESPONSE_FAILED_RETRYING')
expect(errors.warning.firstCall.args[1]).to.eql({
delay: 30000,
delayMs: 30000,
tries: 3,
response: err,
})
expect(errors.warning.secondCall.args[1]).to.eql({
delay: 60000,
delayMs: 60000,
tries: 2,
response: err,
})
expect(errors.warning.thirdCall.args[1]).to.eql({
delay: 120000,
delayMs: 120000,
tries: 1,
response: err,
})

View File

@@ -0,0 +1,182 @@
import '../../spec_helper'
import getEnvInformationForProjectRoot from '../../../lib/cloud/environment'
import path from 'path'
import base64url from 'base64url'
import { exec } from 'child_process'
import originalResolvePackagePath from 'resolve-package-path'
import proxyquire from 'proxyquire'
describe('lib/cloud/api', () => {
beforeEach(() => {
delete process.env.CYPRESS_API_URL
process.env.CYPRESS_ENV_DEPENDENCIES = base64url.encode(JSON.stringify({
maybeCheckProcessTreeIfPresent: ['foo'],
neverCheckProcessTreeIfPresent: ['bar'],
}))
})
let proc
const spawnProcessTree = async ({
grandParentUrl,
parentUrl,
url,
}: {
grandParentUrl?: string
parentUrl?: string
url?: string
}) => {
return new Promise((resolve) => {
proc = exec(`node ${path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'test-project', 'index.js')}`, {
cwd: path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'test-project'),
env: {
...process.env,
CYPRESS_API_URL: grandParentUrl,
CHILD_CYPRESS_API_URL: parentUrl,
GRANDCHILD_CYPRESS_API_URL: url,
},
})
proc.stdout.on('data', (data) => {
const match = data.toString().match(/grandchild (\d+)/)
if (match) {
resolve(match[1])
}
})
})
}
afterEach(() => {
if (proc) {
proc.kill()
}
})
it('should be able to get the environment for: present CYPRESS_API_URL and all tracked dependencies', async () => {
process.env.CYPRESS_API_URL = 'https://example.com'
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'all-tracked-dependencies'), process.pid.toString())
expect(information).to.deep.eq({
envUrl: 'https://example.com',
dependencies: { bar: { version: '2.0.0' }, foo: { version: '1.0.0' } },
errors: [],
})
})
it('should be able to get the environment for: present CYPRESS_API_URL and a thrown error when tracking dependencies', async () => {
process.env.CYPRESS_API_URL = 'https://example.com'
const resolvePackagePath = sinon.stub()
resolvePackagePath.withArgs('foo', sinon.match.any).throws(new Error('some error'))
resolvePackagePath.withArgs('bar', sinon.match.any).callsFake(originalResolvePackagePath)
const { default: getEnvInfo } = proxyquire('../../../lib/cloud/environment', {
'resolve-package-path': resolvePackagePath,
})
const { errors, ...information } = await getEnvInfo(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'all-tracked-dependencies'), process.pid.toString())
expect(information).to.deep.eq({
envUrl: 'https://example.com',
dependencies: { bar: { version: '2.0.0' } },
})
expect(errors).to.have.length(1)
expect(errors[0].dependency).to.equal('foo')
expect(errors[0].message).to.equal('some error')
expect(errors[0].name).to.equal('Error')
expect(errors[0].stack).to.include('Error: some error')
})
it('should be able to get the environment for: absent CYPRESS_API_URL and all tracked dependencies', async () => {
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'all-tracked-dependencies'), process.pid.toString())
expect(information).to.deep.eq({
envUrl: undefined,
dependencies: { bar: { version: '2.0.0' }, foo: { version: '1.0.0' } },
errors: [],
})
})
it('should be able to get the environment for: absent CYPRESS_API_URL and partial dependencies not matching criteria', async () => {
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-not-matching'), process.pid.toString())
expect(information).to.deep.eq({
envUrl: undefined,
dependencies: { bar: { version: '2.0.0' } },
errors: [],
})
})
context('absent CYPRESS_API_URL and partial dependencies matching criteria', () => {
it('should be able to get the environment for CYPRESS_API_URL defined in grandparent process', async () => {
const pid = await spawnProcessTree({
grandParentUrl: 'https://grandparent.com',
})
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-matching'), pid.toString())
expect(information).to.deep.eq({
envUrl: process.platform !== 'win32' ? 'https://grandparent.com' : undefined,
dependencies: { foo: { version: '1.0.0' } },
errors: [],
})
})
it('should be able to get the environment for CYPRESS_API_URL defined in parent process', async () => {
const pid = await spawnProcessTree({
parentUrl: 'https://parent.com',
})
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-matching'), pid.toString())
expect(information).to.deep.eq({
envUrl: process.platform !== 'win32' ? 'https://parent.com' : undefined,
dependencies: { foo: { version: '1.0.0' } },
errors: [],
})
})
it('should be able to get the environment for CYPRESS_API_URL defined in current process', async () => {
const pid = await spawnProcessTree({
url: 'https://url.com',
})
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-matching'), pid.toString())
expect(information).to.deep.eq({
envUrl: process.platform !== 'win32' ? 'https://url.com' : undefined,
dependencies: { foo: { version: '1.0.0' } },
errors: [],
})
})
it('should be able to get the environment for CYPRESS_API_URL defined in parent process overriding grandparent process', async () => {
const pid = await spawnProcessTree({
grandParentUrl: 'https://grandparent.com',
parentUrl: 'https://parent.com',
})
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-matching'), pid.toString())
expect(information).to.deep.eq({
envUrl: process.platform !== 'win32' ? 'https://parent.com' : undefined,
dependencies: { foo: { version: '1.0.0' } },
errors: [],
})
})
it('should return no envUrl when CYPRESS_API_URL is not defined in any parent process', async () => {
const pid = await spawnProcessTree({})
const information = await getEnvInformationForProjectRoot(path.join(__dirname, '..', '..', 'support', 'fixtures', 'cloud', 'environment', 'partial-dependencies-matching'), pid.toString())
expect(information).to.deep.eq({
envUrl: undefined,
dependencies: { foo: { version: '1.0.0' } },
errors: [],
})
})
})
})

View File

@@ -17,7 +17,7 @@ const initialEnv = _.clone(process.env)
// tested as an e2e/record_spec
describe('lib/modes/record', () => {
beforeEach(() => {
sinon.stub(api, 'preflight').callsFake(async () => {
sinon.stub(api, 'sendPreflight').callsFake(async () => {
api.setPreflightResult({ encrypt: false })
})
})
@@ -306,6 +306,7 @@ describe('lib/modes/record', () => {
expect(commitInfo.commitInfo).to.be.calledWith(projectRoot)
expect(api.createRun).to.be.calledWith({
projectRoot,
group,
parallel,
projectId,

View File

@@ -7,7 +7,9 @@ const path = require('path')
const { setupV8Snapshots } = require('@tooling/v8-snapshot')
const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')
const { buildEntryPointAndCleanup } = require('./binary/binary-cleanup')
const { getIntegrityCheckSource, getBinaryEntryPointSource } = require('./binary/binary-sources')
const { getIntegrityCheckSource, getBinaryEntryPointSource, getEncryptionFileSource, getCloudApiFileSource, validateEncryptionFile } = require('./binary/binary-sources')
const CY_ROOT_DIR = path.join(__dirname, '..')
module.exports = async function (params) {
try {
@@ -58,23 +60,21 @@ module.exports = async function (params) {
if (!['1', 'true'].includes(process.env.DISABLE_SNAPSHOT_REQUIRE)) {
const binaryEntryPointSource = await getBinaryEntryPointSource()
const encryptionFile = path.join(outputFolder, 'packages/server/lib/cloud/encryption.js')
const fileContents = await fs.readFile(encryptionFile, 'utf8')
if (!fileContents.includes(`test: CY_TEST,`)) {
throw new Error(`Expected to find test key in cloud encryption file`)
}
const encryptionFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/encryption.ts')
const encryptionFileSource = await getEncryptionFileSource(encryptionFilePath)
const cloudApiFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/environment.ts')
const cloudApiFileSource = await getCloudApiFileSource(cloudApiFilePath)
await Promise.all([
fs.writeFile(encryptionFile, fileContents.replace(`test: CY_TEST,`, '').replace(/const CY_TEST = `(.*?)`/, '')),
fs.writeFile(encryptionFilePath, encryptionFileSource),
fs.writeFile(cloudApiFilePath, cloudApiFileSource),
fs.writeFile(path.join(outputFolder, 'index.js'), binaryEntryPointSource),
])
const afterReplace = await fs.readFile(encryptionFile, 'utf8')
if (afterReplace.includes('CY_TEST')) {
throw new Error(`Expected test key to be stripped from cloud encryption file`)
}
await Promise.all([
validateEncryptionFile(encryptionFilePath),
validateEncryptionFile(cloudApiFilePath),
])
await flipFuses(
exePathPerPlatform[os.platform()],

View File

@@ -1,4 +1,4 @@
const fs = require('fs')
const fs = require('fs-extra')
const crypto = require('crypto')
const path = require('path')
const esbuild = require('esbuild')
@@ -40,7 +40,53 @@ const getIntegrityCheckSource = (baseDirectory) => {
.replaceAll('CRYPTO_HMAC_DIGEST_TO_STRING', escapeString(crypto.Hmac.prototype.digest.toString()))
}
const getEncryptionFileSource = async (encryptionFilePath) => {
const fileContents = await fs.readFile(encryptionFilePath, 'utf8')
if (!fileContents.includes(`test: CY_TEST,`)) {
throw new Error(`Expected to find test key in cloud encryption file`)
}
return fileContents.replace(`test: CY_TEST,`, '').replace(/const CY_TEST = `(.*?)`/, '')
}
const validateEncryptionFile = async (encryptionFilePath) => {
const afterReplaceEncryption = await fs.readFile(encryptionFilePath, 'utf8')
if (afterReplaceEncryption.includes('CY_TEST')) {
throw new Error(`Expected test key to be stripped from cloud encryption file`)
}
}
const getCloudApiFileSource = async (cloudApiFilePath) => {
const fileContents = await fs.readFile(cloudApiFilePath, 'utf8')
if (!fileContents.includes('process.env.CYPRESS_ENV_DEPENDENCIES')) {
throw new Error(`Expected to find CYPRESS_ENV_DEPENDENCIES in cloud api file`)
}
if (process.env.CYPRESS_ENV_DEPENDENCIES) {
return fileContents.replace('process.env.CYPRESS_ENV_DEPENDENCIES', `'${process.env.CYPRESS_ENV_DEPENDENCIES}'`)
}
return fileContents
}
const validateCloudApiFile = async (cloudApiFilePath) => {
if (process.env.CYPRESS_ENV_DEPENDENCIES) {
const afterReplaceCloudApi = await fs.readFile(cloudApiFilePath, 'utf8')
if (afterReplaceCloudApi.includes('process.env.CYPRESS_ENV_DEPENDENCIES')) {
throw new Error(`Expected process.env.CYPRESS_ENV_DEPENDENCIES to be stripped from cloud api file`)
}
}
}
module.exports = {
getBinaryEntryPointSource,
getIntegrityCheckSource,
getEncryptionFileSource,
getCloudApiFileSource,
validateCloudApiFile,
validateEncryptionFile,
}

View File

@@ -271,9 +271,9 @@ Please log into Cypress Cloud and find your project.
We will list the correct projectId in the 'Settings' tab.
Alternatively, you can create a new project using the Desktop Application.
Alternatively, you can create a new project directly from within the Cypress app.
https://on.cypress.io/dashboard
https://on.cypress.io/cloud
`
@@ -332,11 +332,11 @@ exports['e2e record api interaction errors update instance stdout warns but proc
(Uploading Results)
- Done Uploading (1/1) /foo/bar/.projects/e2e/cypress/screenshots/record_pass.cy.js/yay it passes.png
Warning: We encountered an error talking to our servers.
Warning: We encountered an error communicating with our servers.
This run will not be recorded.
This run will proceed, but will not be recorded.
This error will not alter the exit code.
This error will not affect or change the exit code.
StatusCodeError: 500 - "Internal Server Error"
@@ -498,7 +498,7 @@ The Record Key is missing. Your CI provider is likely not passing private enviro
These results will not be recorded.
This error will not alter the exit code.
This error will not affect or change the exit code.
====================================================================================================
@@ -574,16 +574,7 @@ https://on.cypress.io/run-group-name-not-unique
`
exports['e2e record api interaction errors create run unknown 422 errors and exits when there is an unknown 422 response 1'] = `
We encountered an unexpected error talking to our servers.
There is likely something wrong with the request.
The --tag flag you passed was: nightly
The --group flag you passed was: e2e-tests
The --parallel flag you passed was: true
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 422
@@ -592,20 +583,25 @@ StatusCodeError: 422
"message": "An unknown message here from the server."
}
There is likely something wrong with the request.
The --tag flag you passed was: nightly
The --group flag you passed was: e2e-tests
The --parallel flag you passed was: true
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors create run 500 does not proceed and exits with error when parallelizing 1'] = `
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors create instance 500 does not proceed and exits with error when parallelizing and creating instance 1'] = `
@@ -623,17 +619,15 @@ exports['e2e record api interaction errors create instance 500 does not proceed
│ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors update instance 500 does not proceed and exits with error when parallelizing and updating instance 1'] = `
@@ -690,41 +684,36 @@ exports['e2e record api interaction errors update instance 500 does not proceed
(Uploading Results)
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors api retries on error warns and does not create or update instances 1'] = `
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
We will retry 3 more times in X second(s)...
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
We encountered an unexpected error talking to our servers.
We will retry 2 more times in X second(s)...
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
We encountered an unexpected error talking to our servers.
We will retry 1 more time in X second(s)...
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
====================================================================================================
@@ -739,13 +728,12 @@ StatusCodeError: 500 - "Internal Server Error"
│ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
We will retry 3 more times in X second(s)...
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -813,7 +801,7 @@ The Record Key is missing. Your CI provider is likely not passing private enviro
These results will not be recorded.
This error will not alter the exit code.
This error will not affect or change the exit code.
====================================================================================================
@@ -894,13 +882,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing
`
exports['e2e record api interaction errors create run 402 - unknown error errors and exits when there\'s an unknown 402 error 1'] = `
We encountered an unexpected error talking to our servers.
There is likely something wrong with the request.
The --tag flag you passed was:
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 402
@@ -908,6 +890,10 @@ StatusCodeError: 402
"error": "Something went wrong"
}
There is likely something wrong with the request.
The --tag flag you passed was:
`
exports['e2e record api interaction errors create run 402 - free plan exceeds monthly tests errors and exits when on free plan and over recorded tests limit 1'] = `
@@ -1832,24 +1818,20 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing
`
exports['e2e record api interaction errors create run 500 errors and exits 1'] = `
We encountered an unexpected error talking to our servers.
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors create run 500 when grouping without parallelization errors and exits 1'] = `
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors create instance 500 without parallelization - does not proceed 1'] = `
@@ -1867,9 +1849,7 @@ exports['e2e record api interaction errors create instance 500 without paralleli
│ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
We encountered an unexpected error talking to our servers.
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
@@ -1890,9 +1870,7 @@ exports['e2e record api interaction errors create instance errors and exits on c
│ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
We encountered an unexpected error talking to our servers.
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
@@ -1918,15 +1896,13 @@ exports['e2e record api interaction errors postInstanceTests without paralleliza
Running: a_record.cy.js (1 of 2)
Estimated: X second(s)
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
The --group flag you passed was: foo
The --ciBuildId flag you passed was: 1
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors postInstanceTests with parallelization errors and exits 1'] = `
@@ -1949,17 +1925,15 @@ exports['e2e record api interaction errors postInstanceTests with parallelizatio
Running: a_record.cy.js (1 of 2)
Estimated: X second(s)
We encountered an unexpected error talking to our servers.
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
StatusCodeError: 500 - "Internal Server Error"
`
exports['e2e record api interaction errors postInstanceResults errors and exits in serial 1'] = `
@@ -2016,9 +1990,7 @@ exports['e2e record api interaction errors postInstanceResults errors and exits
(Uploading Results)
We encountered an unexpected error talking to our servers.
The server's response was:
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
@@ -2292,7 +2264,7 @@ exports['e2e record quiet mode respects quiet mode 1'] = `
`
exports['e2e record api interaction errors create run 412 errors and exits when request schema is invalid 1'] = `
Recording this run failed because the request was invalid.
Recording this run failed. The request was invalid.
request should follow postRunRequest@2.0.0 schema
@@ -2572,41 +2544,98 @@ Available browsers found on your system are:
- browser3
`
exports['e2e record /preflight preflight failure renders error messages properly 1'] = `
Recording this run failed because the request was invalid.
exports['e2e record api interaction errors sendPreflight [F1] 500 status code errors with empty body fails after retrying 1'] = `
We encountered an unexpected error communicating with our servers.
Recording this way is no longer supported
StatusCodeError: 500 - "Internal Server Error"
Errors:
We will retry 1 more time in X second(s)...
[
"attempted to send envUrl foo.bar.baz"
]
We encountered an unexpected error communicating with our servers.
Request Sent:
{
"ciBuildId": "ciBuildId123",
"projectId": "cy12345"
}
`
exports['e2e record /preflight preflight failure: unencrypted fails on an unencrypted preflight response 1'] = `
We encountered an unexpected error talking to our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
The server's response was:
`
DecryptionError: JWE Recipients missing or incorrect type
exports['e2e record api interaction errors sendPreflight [F2] 404 status code with JSON body fails without retrying 1'] = `
We could not find a Cypress Cloud project with the projectId: pid123
This projectId came from your cypress-with-project-id.config.js file or an environment variable.
Please log into Cypress Cloud and find your project.
We will list the correct projectId in the 'Settings' tab.
Alternatively, you can create a new project directly from within the Cypress app.
https://on.cypress.io/cloud
`
exports['e2e record /preflight preflight failure: warning message renders preflight warning messages prior to run warnings 1'] = `
exports['e2e record api interaction errors sendPreflight [F2] 404 status code with empty body fails without retrying 1'] = `
We could not find a Cypress Cloud project with the projectId: pid123
This projectId came from your cypress-with-project-id.config.js file or an environment variable.
Please log into Cypress Cloud and find your project.
We will list the correct projectId in the 'Settings' tab.
Alternatively, you can create a new project directly from within the Cypress app.
https://on.cypress.io/cloud
`
exports['e2e record api interaction errors sendPreflight [F3] 201 status code with invalid decryption fails without retrying 1'] = `
We encountered an unexpected error communicating with our servers.
DecryptionError: JWE Recipients missing or incorrect type
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors sendPreflight [F3] 200 status code with empty body fails without retrying 1'] = `
We encountered an unexpected error communicating with our servers.
DecryptionError: General JWE must be an object
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors sendPreflight [F4] 412 status code with valid decryption fails without retrying 1'] = `
Recording this run failed. The request was invalid.
Recording is not working
Errors:
[
"attempted to send invalid data"
]
Request Sent:
{
"projectId": "cy12345"
}
`
exports['e2e record api interaction errors sendPreflight [W1] warning message renders preflight warning messages prior to run warnings 1'] = `
Warning from Cypress Cloud:
----------------------------------------------------------------------
@@ -2691,3 +2720,64 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing
`
exports['e2e record api interaction errors sendPreflight [F1] socket errors fails after retrying 1'] = `
We encountered an unexpected error communicating with our servers.
RequestError: Error: socket hang up
We will retry 1 more time in X second(s)...
We encountered an unexpected error communicating with our servers.
RequestError: Error: socket hang up
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors sendPreflight [F3] 422 status code with invalid decryption fails without retrying 1'] = `
We encountered an unexpected error communicating with our servers.
DecryptionError: JWE Recipients missing or incorrect type
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors sendPreflight [F1] 500 status code errors with body fails after retrying 1'] = `
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
We will retry 1 more time in X second(s)...
We encountered an unexpected error communicating with our servers.
StatusCodeError: 500 - "Internal Server Error"
Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
The --group flag you passed was: foo
The --ciBuildId flag you passed was: ciBuildId123
`
exports['e2e record api interaction errors sendPreflight [F5] 422 status code with valid decryption on createRun errors and exits when group name is in use 1'] = `
You passed the --group flag, but this group name has already been used for this run.
The existing run is: https://cloud.cypress.io/runs/12345
The --group flag you passed was: e2e-tests
If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
https://on.cypress.io/run-group-name-not-unique
`

View File

@@ -213,7 +213,7 @@ This option will not have an effect in Firefox. Tests that rely on web security
Warning: We failed processing this video.
This error will not alter the exit code.
This error will not affect or change the exit code.
TimeoutError: operation timed out
[stack trace lines]

View File

@@ -70,7 +70,7 @@ export const encryptBody = async (req, res, body) => {
}
export const routeHandlers = {
preflight: {
sendPreflight: {
method: 'post',
url: '/preflight',
res: async (req, res) => {

View File

@@ -308,7 +308,7 @@ Bluebird.config({
const diffRe = /Difference\n-{10}\n([\s\S]*)\n-{19}\nSaved snapshot text/m
const expectedAddedVideoSnapshotLines = [
'Warning: We failed processing this video.',
'This error will not alter the exit code.',
'This error will not affect or change the exit code.',
'TimeoutError: operation timed out',
'[stack trace lines]',
]

View File

@@ -1267,7 +1267,7 @@ describe('e2e record', () => {
},
} }))
it('errors and exits when there\'s an unknown 402 error', function () {
it(`errors and exits when there's an unknown 402 error`, function () {
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
@@ -1613,6 +1613,375 @@ describe('e2e record', () => {
})
})
})
describe('sendPreflight', () => {
describe('[F1] socket errors', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return req.socket.destroy(new Error('killed'))
},
},
}))
it('fails after retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F1] 500 status code errors with empty body', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res.sendStatus(500)
},
},
}))
it('fails after retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F1] 500 status code errors with body', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res
.status(500)
.json({ message: 'an error message' })
},
},
}))
it('fails after retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F2] 404 status code with JSON body', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res
.status(404)
.json({ message: 'not found' })
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F2] 404 status code with empty body', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res.sendStatus(404)
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F3] 422 status code with invalid decryption', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res: async (req, res) => {
return res.status(422).json({
message: 'something broke',
})
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F3] 201 status code with invalid decryption', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res
.status(201)
.json({ data: 'very encrypted and secure string' })
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F3] 200 status code with empty body', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res (req, res) {
return res.sendStatus(200)
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F4] 412 status code with valid decryption', () => {
setupStubbedServer(createRoutes({
sendPreflight: {
res: async (req, res) => {
return res.status(412).json(await encryptBody(req, res, {
message: 'Recording is not working',
errors: [
'attempted to send invalid data',
],
object: {
projectId: 'cy12345',
},
}))
},
},
}))
it('fails without retrying', function () {
process.env.API_RETRY_INTERVALS = '1000'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('[F5] 422 status code with valid decryption on createRun', async () => {
const mockServer = setupStubbedServer(createRoutes({
sendPreflight: {
res: async (req, res) => {
return res.json(await encryptBody(req, res, {
encrypt: true,
apiUrl: req.body.apiUrl,
}))
},
},
postRun: {
res: async (req, res) => {
mockServer.setSpecs(req)
return res
.set({ 'x-cypress-encrypted': true })
.status(422)
.json(await encryptBody(req, res, {
code: 'RUN_GROUP_NAME_NOT_UNIQUE',
message: 'Run group name cannot be used again without passing the parallel flag.',
payload: {
runUrl: 'https://cloud.cypress.io/runs/12345',
},
}))
},
},
}))
// the other 422 tests for this are in integration/cypress_spec
it('errors and exits when group name is in use', function () {
process.env.CIRCLECI = '1'
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'e2e-tests',
record: true,
snapshot: true,
expectedExitCode: 1,
})
.then(() => {
const urls = getRequestUrls()
expect(urls).to.deep.eq([
'POST /runs',
])
})
})
})
describe('[W1] warning message', () => {
const mockServer = setupStubbedServer(createRoutes({
sendPreflight: {
res: async (req, res) => {
return res.json(await encryptBody(req, res, {
encrypt: true,
apiUrl: req.body.apiUrl,
warnings: [
{
message: dedent`
----------------------------------------------------------------------
This feature will not be supported soon, please check with Cypress to learn more: https://on.cypress.io/
----------------------------------------------------------------------
`,
},
],
}))
},
},
postRun: {
res (req, res) {
mockServer.setSpecs(req)
return res.status(200).json({
runId,
groupId,
machineId,
runUrl,
tags,
warnings: [{
name: 'foo',
message: 'foo',
code: 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS',
limit: 500,
gracePeriodEnds: '2999-12-31',
orgId: 'org-id-1234',
}],
})
},
},
}))
it('renders preflight warning messages prior to run warnings', async function () {
return await systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
})
})
})
})
})
describe('api interaction warnings', () => {
@@ -1890,160 +2259,4 @@ describe('e2e record', () => {
})
})
})
describe('/preflight', () => {
describe('preflight failure: unencrypted', () => {
setupStubbedServer(createRoutes({
preflight: {
res (req, res) {
return res.json({ apiUrl: 'http://localhost:1234' })
},
},
}))
it('fails on an unencrypted preflight response', async function () {
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('preflight failure 500 server error', () => {
setupStubbedServer(createRoutes({
preflight: {
res (req, res) {
return res.sendStatus(500)
},
},
}))
it('retries on a preflight server error', async function () {
await new Promise((resolve, reject) => {
let sp
systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
ciBuildId: 'ciBuildId123',
onSpawn (spawnResult) {
sp = spawnResult
sp.stdout.on('data', (chunk) => {
const msg = String(chunk)
if (msg.includes('We will retry')) {
resolve()
sp.kill()
}
})
},
}).catch(reject)
})
})
})
describe('preflight failure', () => {
setupStubbedServer(createRoutes({
preflight: {
res: async (req, res) => {
return res.status(412).json(await encryptBody(req, res, {
message: 'Recording this way is no longer supported',
errors: [
'attempted to send envUrl foo.bar.baz',
],
object: {
ciBuildId: 'ciBuildId123',
projectId: 'cy12345',
},
}))
},
},
}))
it('renders error messages properly', async function () {
return systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
expectedExitCode: 1,
})
})
})
describe('preflight failure: warning message', () => {
const mockServer = setupStubbedServer(createRoutes({
preflight: {
res: async (req, res) => {
return res.json(await encryptBody(req, res, {
encrypt: true,
apiUrl: req.body.apiUrl,
warnings: [
{
message: dedent`
----------------------------------------------------------------------
This feature will not be supported soon, please check with Cypress to learn more: https://on.cypress.io/
----------------------------------------------------------------------
`,
},
],
}))
},
},
postRun: {
res (req, res) {
mockServer.setSpecs(req)
return res.status(200).json({
runId,
groupId,
machineId,
runUrl,
tags,
warnings: [{
name: 'foo',
message: 'foo',
code: 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS',
limit: 500,
gracePeriodEnds: '2999-12-31',
orgId: 'org-id-1234',
}],
})
},
},
}))
it('renders preflight warning messages prior to run warnings', async function () {
return await systemTests.exec(this, {
key: 'f858a2bc-b469-4e48-be67-0876339ee7e1',
configFile: 'cypress-with-project-id.config.js',
spec: 'record_pass*',
group: 'foo',
tag: 'nightly',
record: true,
parallel: true,
snapshot: true,
ciBuildId: 'ciBuildId123',
})
})
})
})
})

View File

@@ -25677,6 +25677,13 @@ resolve-options@^1.1.0:
dependencies:
value-or-function "^3.0.0"
resolve-package-path@4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-4.0.3.tgz#31dab6897236ea6613c72b83658d88898a9040aa"
integrity sha512-SRpNAPW4kewOaNUt8VPqhJ0UMxawMwzJD8V7m1cJfdSTK9ieZwS6K7Dabsm4bmLFM96Z5Y/UznrpG5kt1im8yA==
dependencies:
path-root "^0.1.1"
resolve-pkg@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-2.0.0.tgz#ac06991418a7623edc119084edc98b0e6bf05a41"