Merge pull request #21298 from cypress-io/zachw/merge-develop-into-master

This commit is contained in:
Jess
2022-05-03 10:17:40 -04:00
committed by GitHub
111 changed files with 1001 additions and 879 deletions
-2
View File
@@ -103,8 +103,6 @@ system-tests/fixtures/large-img
scripts/support
binary-url.json
# Allows us to dynamically create eslint rules that override the default for Decaffeinate scripts
.eslintrc.js
cli/visual-snapshots
# Created by https://www.gitignore.io/api/osx,git,node,windows,intellij,linux
-14
View File
@@ -8,20 +8,6 @@
"processId": "${command:PickProcess}",
"continueOnAttach": true
},
{
"type": "node",
"request": "launch",
"name": "test: active",
"runtimeExecutable": "yarn",
"runtimeArgs": [
"test-debug-package"
],
"args": [
"${file}"
],
"port": 5566,
"console": "integratedTerminal"
},
{
"type": "node",
"request": "attach",
-39
View File
@@ -1,39 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "rename yarn workspace packages",
"type": "shell",
"command": "node ./scripts/rename-workspace-packages --file ${file}",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "decaffeinate-bulk file",
"type": "shell",
"command": "yarn decaffeinate-bulk convert --file ${file}",
"problemMatcher": []
},
{
"label": "decaffeinate-bulk multiple files",
"type": "shell",
"command": "yarn decaffeinate-bulk convert --file ${file} ${file}",
"problemMatcher": []
},
{
"label": "decaffeinate-bulk dir",
"type": "shell",
"command": "yarn decaffeinate-bulk --dir ${fileDirname} convert",
"problemMatcher": []
}
]
}
-28
View File
@@ -1,28 +0,0 @@
exports['mocha snapshot captures mocha output 1'] = `
command: npm run test-mocha
code: 0
failed: false
killed: false
signal: null
timedOut: false
stdout:
-------
> cypress@x.y.z test-mocha <folder path>
> mocha --reporter spec scripts/spec.js
mocha sanity check
Y works
1 passing (<time>ms)
-------
stderr:
-------
-------
`
+2 -2
View File
@@ -1,4 +1,4 @@
{
"chrome:beta": "101.0.4951.41",
"chrome:stable": "100.0.4896.127"
"chrome:beta": "102.0.5005.27",
"chrome:stable": "101.0.4951.41"
}
+19 -20
View File
@@ -29,8 +29,7 @@ mainBuildFilters: &mainBuildFilters
only:
- develop
- 10.0-release
- feature-multidomain
- unify-1449-beta-slug-length
- check-results-for-env
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -39,8 +38,7 @@ macWorkflowFilters: &mac-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ feature-multidomain, << pipeline.git.branch >> ]
- equal: [ unify-1449-beta-slug-length, << pipeline.git.branch >> ]
- equal: [ check-results-for-env, << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -50,8 +48,7 @@ windowsWorkflowFilters: &windows-workflow-filters
or:
- equal: [ master, << pipeline.git.branch >> ]
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ feature-multidomain, << pipeline.git.branch >> ]
- equal: [ unify-1449-beta-slug-length, << pipeline.git.branch >> ]
- equal: [ check-results-for-env, << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -204,7 +201,6 @@ commands:
name: Restore system tests node_modules cache
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-
update_cached_system_tests_deps:
description: 'Update the cached node_modules for projects in "system-tests/projects/**"'
@@ -218,10 +214,10 @@ commands:
- restore_cache:
name: Restore cache state, to check for known modules cache existence
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }}
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- run:
name: Send root honeycomb event for this CI build
command: cd system-tests/scripts && node ./send-root-honecomb-event.js
command: cd system-tests/scripts && node ./send-root-honeycomb-event.js
- run:
name: Bail if specific cache exists
command: |
@@ -241,11 +237,11 @@ commands:
name: Save system tests node_modules cache
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
paths:
- ~/.cache/cy-system-tests-node-modules
- /tmp/cy-system-tests-node-modules
- run: touch /tmp/system_tests_node_modules_installed
- save_cache:
name: Save system tests node_modules cache state key
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }}
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
paths:
- /tmp/system_tests_node_modules_installed
@@ -265,7 +261,7 @@ commands:
command: echo $PLATFORM > platform_key
- restore_cache:
name: Restore cache state, to check for known modules cache existence
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-state-{{ checksum "circle_cache_key" }}
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
- run:
name: Bail if cache exists
command: |
@@ -281,6 +277,9 @@ commands:
- run:
name: Install Node Modules
command: |
# avoid installing Percy's Chromium every time we use @percy/cli
# https://docs.percy.io/docs/caching-asset-discovery-browser-in-ci
PERCY_POSTINSTALL_BROWSER=true \
yarn --prefer-offline --frozen-lockfile --cache-folder ~/.yarn
no_output_timeout: 20m
- prepare-modules-cache:
@@ -307,7 +306,7 @@ commands:
- run: touch node_modules_installed
- save_cache:
name: Saving node-modules cache state key
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-state-{{ checksum "circle_cache_key" }}
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
paths:
- node_modules_installed
- save_cache:
@@ -1006,6 +1005,9 @@ jobs:
- run:
name: Top level packages
command: yarn list --depth=0 || true
- run:
name: Check env canaries
command: node ./scripts/circle-env.js --check-canaries
- build-and-persist
- store-npm-logs
@@ -1082,8 +1084,6 @@ jobs:
parallelism: 1
steps:
- restore_cached_workspace
# make sure mocha runs
- run: yarn test-mocha
- when:
condition:
# several snapshots fails for windows due to paths.
@@ -1091,15 +1091,11 @@ jobs:
equal: [ *windows-executor, << parameters.executor >> ]
steps:
- run: yarn test-scripts scripts/**/*spec.js
# make sure our snapshots are compared correctly
- run: yarn test-mocha-snapshot
- unless:
condition:
equal: [ *windows-executor, << parameters.executor >> ]
steps:
- run: yarn test-scripts
# make sure our snapshots are compared correctly
- run: yarn test-mocha-snapshot
# make sure packages with TypeScript can be transpiled to JS
- run: yarn lerna run build-prod --stream
# run unit tests from each individual package
@@ -1658,7 +1654,7 @@ jobs:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "unify-1449-beta-slug-length" && "$CIRCLE_BRANCH" != "feature-multidomain" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "check-results-for-env" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -2057,6 +2053,7 @@ linux-workflow: &linux-workflow
jobs:
- node_modules_install
- build:
context: test-runner:env-canary
requires:
- node_modules_install
- lint:
@@ -2360,6 +2357,7 @@ mac-workflow: &mac-workflow
- build:
name: darwin-build
context: test-runner:env-canary
executor: mac
resource_class: macos.x86.medium.gen2
requires:
@@ -2400,6 +2398,7 @@ windows-workflow: &windows-workflow
- build:
name: windows-build
context: test-runner:env-canary
executor: windows
resource_class: windows.medium
requires:
+26 -31
View File
@@ -56,29 +56,20 @@ of Cypress. You can see the progress of the test projects by opening the status
In the following instructions, "X.Y.Z" is used to denote the [next version of Cypress being published](./next-version.md).
1. Confirm that every issue labeled [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) has a ZenHub release set. **Tip:** there is a command in [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to list and check such issues. Without a ZenHub release issues will not be included in the right changelog.
2. Create or review the release-specific documentation and changelog in [cypress-documentation](https://github.com/cypress-io/cypress-documentation). If there is not already a release-specific PR open, create one.
- Use [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to generate a starting point for the changelog, based off of ZenHub:
```shell
cd packages/issues-in-release
yarn do:changelog --release <release label>
```
- Ensure the changelog is up-to-date and has the correct date.
- Merge any release-specific documentation changes into the main release PR.
- You can view the doc's [branch deploy preview](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md#pull-requests) by clicking 'Details' on the PR's `netlify-cypress-docs/deploy-preview` GitHub status check.
3. `develop` should contain all of the changes made in `master`. However, this occasionally may not be the case.
1. `develop` should contain all of the changes made in `master`. However, this occasionally may not be the case.
- Ensure that `master` does not have any additional commits that are not on `develop`.
- Ensure all auto-generated pull requests designed to merge master into develop have been successfully merged.
- If there are additional commits necessary to merge `master` to `develop`, submit, get approvals on, and merge a new PR
4. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
2. Confirm that every issue labeled [stage: pending release](https://github.com/cypress-io/cypress/issues?q=label%3A%22stage%3A+pending+release%22+is%3Aclosed) has a ZenHub release set. **Tip:** there is a command in [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to list and check such issues. Without a ZenHub release issues will not be included in the right changelog. Also ensure that every closed issue in any obsolete releases are moved to the appropriate release in ZehHub. For example, if the open releases are 9.5.5 and 9.6.0, the current release is 9.6.0, then all closed issues marked as 9.5.5 should be moved to 9.6.0. Ensure that there are no commits on `develop` since the last release that are user facing and aren't marked with the current release.
5. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.tgz`, publishing can proceed.
3. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
6. [Set up](https://cypress-io.atlassian.net/wiki/spaces/INFRA/pages/1534853121/AWS+SSO+Cypress) an AWS SSO profile with the [Team-CypressApp-Prod](https://cypress-io.atlassian.net/wiki/spaces/INFRA/pages/1534853121/AWS+SSO+Cypress#Team-CypressApp-Prod) role. The release scripts assumes the name of your profile is `production`. If you have setup your credentials under a different profile, be sure to set the `AWS_PROFILE` environment variable. Log into AWS SSO with `aws sso login --profile <name_of_profile>`.
4. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.tgz`, publishing can proceed.
7. Use the `prepare-release-artifacts` script (Mac/Linux only) to prepare the latest commit to a stable release. When you run this script, the following happens:
5. [Set up](https://cypress-io.atlassian.net/wiki/spaces/INFRA/pages/1534853121/AWS+SSO+Cypress) an AWS SSO profile with the [Team-CypressApp-Prod](https://cypress-io.atlassian.net/wiki/spaces/INFRA/pages/1534853121/AWS+SSO+Cypress#Team-CypressApp-Prod) role. The release scripts assumes the name of your profile is `production`. If you have setup your credentials under a different profile, be sure to set the `AWS_PROFILE` environment variable. Log into AWS SSO with `aws sso login --profile <name_of_profile>`.
6. Use the `prepare-release-artifacts` script (Mac/Linux only) to prepare the latest commit to a stable release. When you run this script, the following happens:
* the binaries for `<commit sha>` are moved from `beta` to the `desktop` folder for `<new target version>` in S3
* the Cloudflare cache for this version is purged
* the pre-prod `cypress.tgz` NPM package is converted to a stable NPM package ready for release
@@ -89,22 +80,22 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
You can pass `--dry-run` to see the commands this would run under the hood.
8. Validate you are logged in to `npm` with `npm whoami`. Otherwise log in with `npm login`.
7. Validate you are logged in to `npm` with `npm whoami`. Otherwise log in with `npm login`.
9. Publish the generated npm package under the `dev` tag, using your personal npm account.
8. Publish the generated npm package under the `dev` tag, using your personal npm account.
```shell
npm publish /tmp/cypress-prod.tgz --tag dev
```
10. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output:
9. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output:
```shell
dist-tags:
dev: 3.4.0 latest: 3.3.2
```
11. Test `cypress@X.Y.Z` to make sure everything is working.
10. Test `cypress@X.Y.Z` to make sure everything is working.
- Install the new version: `npm install -g cypress@X.Y.Z`
- Run a quick, manual smoke test:
- `cypress open`
@@ -113,6 +104,16 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
- [cypress-realworld-app](https://github.com/cypress-io/cypress-realworld-app) uses yarn and represents a typical consumer implementation.
- Optionally, do more thorough tests, for example test the new version of Cypress against the Cypress dashboard repo.
11. Create or review the release-specific documentation and changelog in [cypress-documentation](https://github.com/cypress-io/cypress-documentation). If there is not already a release-specific PR open, create one. This PR must be merged, built, and deployed before moving to the next step.
- Use [`release-automations`](https://github.com/cypress-io/release-automations)'s `issues-in-release` tool to generate a starting point for the changelog, based off of ZenHub:
```shell
cd packages/issues-in-release
yarn do:changelog --release <release label>
```
- Ensure the changelog is up-to-date and has the correct date.
- Merge any release-specific documentation changes into the main release PR.
- You can view the doc's [branch deploy preview](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md#pull-requests) by clicking 'Details' on the PR's `netlify-cypress-docs/deploy-preview` GitHub status check.
12. Make the new npm version the "latest" version by updating the dist-tag `latest` to point to the new version:
```shell
@@ -134,24 +135,18 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
- Create a new patch release (and a new minor release, if this is a minor release) in ZenHub, and schedule them both to be completed 2 weeks from the current date.
- Move all issues that are still open from the current release to the appropriate future release.
17. Bump `version` in [`package.json`](package.json), commit it to `develop`, tag it with the version, and push the tag up:
17. Bump `version` in [`package.json`](package.json), submit, get approvals on, and merge a new PR for the change. After it merges:
```shell
git commit -am "release X.Y.Z [skip ci]"
git checkout develop
git pull origin develop
git log --pretty=oneline
# copy sha of the previous commit
git tag -a vX.Y.Z <sha>
git push origin vX.Y.Z
```
18. Merge `develop` into `master` and push both branches up. Note: pushing to `master` will automatically publish any independent npm packages that have not yet been published.
```shell
git push origin develop
git checkout master
git merge develop
git push origin master
```
18. Submit, get approvals on, and merge a new PR that merges `develop` to `master`
19. Inside of [cypress-io/release-automations][release-automations]:
- Publish GitHub release to [cypress-io/cypress/releases](https://github.com/cypress-io/cypress/releases) using package `set-releases`:
+3 -9
View File
@@ -48,12 +48,8 @@
"pretest": "yarn ensure-deps",
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"",
"test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"pretest-e2e": "yarn ensure-deps",
"test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"test-mocha": "mocha --reporter spec scripts/spec.js",
"test-mocha-snapshot": "mocha scripts/mocha-snapshot-spec.js",
"test-npm-package-release-script": "npx lerna exec --scope \"@cypress/*\" -- npx --no-install semantic-release --dry-run",
"test-s3-api": "node -r ./packages/ts/register scripts/binary/s3-api-demo.ts",
"test-system": "yarn workspace @tooling/system-tests test",
"test-scripts": "mocha -r packages/ts/register --reporter spec 'scripts/unit/**/*spec.js'",
"test-scripts-watch": "yarn test-scripts --watch --watch-extensions 'ts,js'",
@@ -62,7 +58,7 @@
"pretest-watch": "yarn ensure-deps",
"test-watch": "lerna exec yarn test-watch --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
"type-check": "yarn lerna exec yarn type-check --scope @tooling/system-tests && node scripts/type_check",
"verify:mocha:results": "node ./scripts/verify_mocha_results",
"verify:mocha:results": "node ./scripts/verify-mocha-results",
"prewatch": "yarn ensure-deps",
"watch": "lerna exec yarn watch --parallel --stream",
"prepare": "husky install"
@@ -77,8 +73,8 @@
"@cypress/questions-remain": "1.0.1",
"@cypress/request": "2.88.10",
"@cypress/request-promise": "4.2.6",
"@percy/cli": "1.0.0-beta.48",
"@percy/cypress": "^3.1.0",
"@percy/cli": "1.1.0",
"@percy/cypress": "^3.1.1",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/git": "9.0.0",
"@types/bluebird": "3.5.29",
@@ -125,9 +121,7 @@
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"execa": "4.0.0",
"execa-wrap": "1.4.0",
"filesize": "4.1.2",
"find-package-json": "1.2.0",
"fs-extra": "9.1.0",
"gift": "0.10.2",
"glob": "7.1.6",
+4
View File
@@ -51,6 +51,10 @@ Note: you may need to enable "Verbose" or "Debug" log levels inside the browser'
Note: when creating a patch, make sure there is no `package-lock.json` file! Also rename the patch to have ".dev.patch" extension.
## Cross-origin Testing
Working on cross-origin testing or reviewing a PR related to it? Check out the [Cross-origin Testing Technical Overview](./cross-origin-testing.md).
<!-- ## Catalog of Events
TODO: this data is accurate but also somewhat out of date.
+151
View File
@@ -0,0 +1,151 @@
# Cross-origin Testing Technical Overview
The goal of this document is to give a technical overview of the architecture behind the **cy.origin()** command, which enables cross-origin testing in Cypress.
## Definitions
See [Node.jss URL doc](https://nodejs.org/api/url.html#url-strings-and-url-objects) for a handy breakdown of URL parts
- **domain**: A hostname without the subdomain. (e.g. `example.com`, `example.co.uk`, `localhost`)
- **origin**: The combination of the protocol, hostname, and port of a URL. For the purposes of Cypress, the subdomain is irrelevant. (e.g. `http://example.com:3500`)
- **top**: The main window/frame of the browser
- **primary origin**: The origin that top is on
- **secondary origin**: Any origin that is not the primary origin
- **primary driver**: The Cypress driver that run in **top** on the primary origin
- **secondary driver**: Any Cypress driver that run in a **spec bridge**, interacting with a secondary origin
## Frame architecture (single origin)
When testing a single origin, all 3 frames are loaded on that origin.
Components:
```mermaid
graph TD;
top["top frame: domain1.com"]-->specFrame["spec frame: domain1.com"];
top-->aut["AUT frame: domain1.com"];
```
**top** communicates directly and synchronously with the **spec frame** and the **AUT frame**. The **spec frame** runs the spec, which uses the driver to run commands and interact with the **AUT**.
In a single test (`it` + hooks), the **AUT** must remain on the same origin or a cross-origin error will occur, as **top** can no longer interact with the **AUT** in that circumstance. Different tests can visit different origins. In this case, we change **top** to the new origin, which also runs the **spec frame** on that origin. This navigation causes the spec to run again. We skip any already-run tests and resume.
## Frame architecture (multiple origins)
Lets say the primary origin is `domain1.com` and the secondary origin is `domain2.com`. The test has visited `domain1.com` and then issued a click that caused the **AUT** to navigate to `domain2.com`.
Since the **AUT** is no longer on the same origin as **top**, they can no longer communicate synchronously. In order to facilitate cross-origin testing, we create another iframe we call the **spec bridge**. It exists as a sibling to the **AUT** (meaning they share the same parent frame, not to be confused with a DOM sibling). It contains a version of the driver tailored to cross-origin testing, with a different entry-point to the primary driver, but containing mostly the same code.
The **spec bridge** is run on `domain2.com` to match the **AUT**. This and being a sibling allows the spec bridge to communicate directly with the **AUT**. The **spec bridge** communicates *asynchronously* with **top** via the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).
The **spec bridge** remains in the DOM from the moment its created until the browser is refreshed or closed, the same as the the **spec frame** and **AUT**.
Heres what the components look like now:
```mermaid
graph TD;
top["top frame: domain1.com"]-->specFrame["spec frame: domain1.com"];
top-->aut["AUT frame: domain2.com"];
top-->specBridge["spec bridge: domain2.com"];
```
## cy.origin()
Refer to the [public documentation on cy.origin()](https://on.cypress.io/origin) for all details on the user-facing API.
The main responsibilities of **cy.origin()** are to create the **spec bridge** for the specified origin and facilitate communication between the primary driver and the secondary driver in that **spec bridge**.
## Cross-origin communication
Communication between the **primary driver** and any **secondary drivers** occurs through the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). We abstract over the postMessage API with **cross-origin communicators**.
A **PrimaryOriginCommunicator** instance exists in the **primary driver** which receives messages from any **secondary drivers** and can send messages to either a single **secondary driver** for a particular origin or to all **secondary drivers** that currently exist.
Each **secondary driver** has a **SpecBridgeCommunicator** instance that can receive from and send messages to the **primary driver**.
One of the main responsibilities of the communicators is to prepare/preprocess data for serialization. All data going over the postMessage API gets serialized by the [structured clone algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). In order to either prevent or catch serialization errors and ensure all relevant data is properly transmitted, we preprocess data such as the subject, logs, snapshots, and errors themselves.
## Automation / Proxy
Browser automation APIs and the proxy play a small but critical role in facilitating cross-origin testing.
The proxy intercepts http requests from all sources. In order to detect cross-origin navigation of **AUT**, its necessary to know that a request came specifically from the **AUT frame** and not **top**, a nested iframe, or elsewhere. To achieve this, we use the browser automation APIs to add a `X-Cypress-Is-AUT-Frame` header to any requests from the AUT.
Allowing the proxy to know if a request is from the **AUT** enables us to do two things when it recognizes that its not the **primary origin**:
- Delay the response. This allows us to communicate with the **primary driver** to set up the **spec bridge** and run the users callback function via **cy.origin()**. Then the driver notifies the proxy that it can allow the response through. This enables users to listen to the `window:before:load` event in the callback function, since the page will not load until after any such listeners are set up.
- Inject code into the request thats tailored to cross-origin testing.
### Cross-origin navigation timing
Its possible for the users test to navigate to a different origin in two different ways.
1. An action causes the navigation (click, submit, etc). In this case, delaying the response is necessary for the aforementioned reasons.
2. The user explicitly navigates via **cy.visit()**. In this case, its not necessary to delay the response, since the navigation has not happened yet, and all setup can be done before the navigation occurs.
## State syncing
Since each **spec bridge / secondary driver** is a new instance and operates in its own execution context, all state held by the driver has to be manually synced. The need to serialize data between drivers means that not all data *can* be synced, so some rules have been enacted to maintain consistency.
### Cypress.*.defaults()
All **defaults()** (e.g. **Cypress.Screenshot.defaults()**) methods do not sync their data between origins. This is because callback functions cannot be properly serialized. We could shuttle message back-and-forth to call the callbacks in the **primary origin**, but then certain arguments like DOM elements could not be serialized when calling those callbacks. While some data accepted by **defaults()** methods are serializable, we feel its better to have clear, consistent behavior and not sync any data.
This means that any global state set up by **defaults()** methods exists independently in each origin. Since a **spec bridge** persists throughout the spec run once its created, that global state persists as well between **cy.origin()** calls for the same origin.
The consequence of this is that users will need to call any given **defaults()** method again inside the **cy.origin()** callback if they wish to have the same behavior in that **secondary origin** as in the **primary origin**.
### Cypress.config() / Cypress.env()
Config and env values are synced both ways between **primary** and **secondary** origins. All built-in config values are inherently serializable since they are passed between the server and the browser.
Its possible for users to set custom key/value pairs where the value could be unserializable. In that case, the value is not synced and it behaves similar to state set by **defaults()** methods, where it persists only in the execution context of that origin.
The config and env values are synced into the **secondary driver** before the **cy.origin()** callback is called. They are synced back to the **primary driver** when the callback and any commands run inside of it are finished.
### Internal state
Various internal state values (used throughout the codebase via `Cypress.state()`, `cy.state()` and `state()`) are sent from the **primary driver** into the **secondary driver** before the **cy.origin()** callback is called. These include values such the viewport width, viewport height, and the stability of the page.
A read-only version of the `runnable` is also sent. While the actual `runnable` instance is a singleton only used by the **primary driver**, certain stateful values it holds are necessary for various parts of the **secondary driver** to function correctly.
The full, up-to-date list of state values sent can be found in the **cy.origin()** implementation.
## Events
All event listeners are only bound to the execution context of the origin in which they are defined. Events occurring in one origin do not trigger event handlers in a different origin. This is because some event handlers accept arguments that are not serializable. Even though some event handlers do rely on unserializable arguments, for consistencys sake, events are bound to their origin.
Similar to **defaults()** methods needing to be called again, some events may need to be rebound within the **cy.origin()** callback in order to achieve the same behavior in that **secondary origin** as in the **primary origin**.
## Cookies
Having the **AUT** on a different origin than **top** causes issues with cookies being set for the origin in the **AUT**. Cookies have a **SameSite** attribute that can be set to **Strict**, **Lax**, ****or **None**. If **Strict** or **Lax**, cookies will not be set when the site is in an iframe on a different origin from the **top frame**.
In order to counteract this, we currently coerce the **SameSite** value of all cross-origin cookies to be **None**. This is not particularly ideal, since it could lead to unexpected behavior if cookies are set even though they should not be.
There are plans to change the implementation so that coercing the cookies is not necessary, so this behavior will change, but cookies will still need special handling for them to work as expected with a site whose origin is secondary.
## Unsupported APIs
Certain APIs are currently not supported in the **cy.origin()** callback. Depending on the API, we may or may not implement support for them in the future.
### cy.origin()
Nesting **cy.origin()** inside the callback is not currently not supported, but support will likely be added in the future. In most use-cases, the desired functionality of nesting it can be achieved calling **cy.origin()** back-to-back at the top level of the test.
### cy.session() / Cypress.session.*
**cy.session()** and related APIs are likely not necessary inside the callback and should be used at the top-level of the test instead of in the **cy.origin()** callback. However, if there are use-cases discovered where its necessary, we may implement support for it.
### cy.intercept()
We will likely add support for **cy.intercept()** within the **cy.origin()** callback in the future.
### Deprecated commands / methods
All deprecated APIs are not supported in the **cy.origin()** callback and we do not plan to ever add support for them. If a user attempts to use one, we throw an error that points them to the preferred API that superseded it. The following are deprecated APIs that are not supported:
- **cy.route()**: Superseded by **cy.intercept()**
- **cy.server()**: Superseded by **cy.intercept()**
- **Cypress.Server.defaults()**: Superseded by **cy.intercept()**
- **Cypress.Cookies.preserveOnce()**: Superseded by sessions
@@ -11,7 +11,7 @@
<div>
Go to different origin:
<a data-cy="cross-origin-secondary-link"
href="http://www.foobar.com:3500/fixtures/multi-domain-secondary.html">http://www.foobar.com:3500/fixtures/multi-domain-secondary.html</a>
href="http://www.foobar.com:3500/fixtures/secondary-origin.html">http://www.foobar.com:3500/fixtures/secondary-origin.html</a>
<a data-cy="dom-link" href="http://www.foobar.com:3500/fixtures/dom.html">http://www.foobar.com:3500/fixtures/dom.html</a>
<a data-cy="scrolling-link" href="http://www.foobar.com:3500/fixtures/scrolling.html">http://www.foobar.com:3500/fixtures/scrolling.html</a>
<a data-cy="request-link" href="http://www.foobar.com:3500/fixtures/request.html">http://www.foobar.com:3500/fixtures/request.html</a>
@@ -14,7 +14,7 @@
<button data-cy="reload">reload</button>
<a data-cy="hashChange" href='#hashChange'>hashChange</a>
<a data-cy="cross-origin-page"
href="/fixtures/multi_domain.html">/fixtures/multi_domain.html</a>
href="/fixtures/primary-origin.html">/fixtures/primary-origin.html</a>
<script>
if (window.Cypress) {
@@ -1269,6 +1269,36 @@ describe('src/cy/commands/assertions', () => {
})
})
// TODO: this suite should be merged with the suite above
describe('message formatting', () => {
const expectMarkdown = (test, message, done) => {
cy.then(() => {
test()
})
cy.on('log:added', (attrs, log) => {
if (attrs.name === 'assert') {
cy.removeAllListeners('log:added')
expect(log.get('message')).to.eq(message)
done()
}
})
}
// https://github.com/cypress-io/cypress/issues/19116
it('text with backslashes', (done) => {
const text = '"<OE_D]dQ\\'
expectMarkdown(
() => expect(text).to.equal(text),
`expected **"<OE_D]dQ\\\\** to equal **"<OE_D]dQ\\\\**`,
done,
)
})
})
context('chai overrides', () => {
beforeEach(function () {
this.$body = cy.$$('body')
@@ -1319,6 +1349,15 @@ describe('src/cy/commands/assertions', () => {
cy.get('#escape-quotes').should('contain', 'shouldn\'t')
})
// https://github.com/cypress-io/cypress/issues/19116
it('escapes backslashes', () => {
const $span = '<span id="escape-backslashes">"&lt;OE_D]dQ\\</span>'
cy.$$($span).appendTo(cy.$$('body'))
cy.get('#escape-backslashes').should('contain', '"<OE_D]dQ\\')
})
})
describe('#match', () => {
@@ -2158,7 +2158,7 @@ describe('src/cy/commands/navigation', () => {
expect(err.message).to.equal(stripIndent`\
Timed out after waiting \`3000ms\` for your remote page to load on origin(s):\n
- \`http://localhost:3500\`\n
A cross-origin request for \`http://www.foobar.com:3500/fixtures/multi-domain-secondary.html\` was detected.\n
A cross-origin request for \`http://www.foobar.com:3500/fixtures/secondary-origin.html\` was detected.\n
A command that triggers cross-origin navigation must be immediately followed by a \`cy.origin()\` command:\n
\`cy.origin(\'http://foobar.com:3500\', () => {\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
@@ -2192,7 +2192,7 @@ describe('src/cy/commands/navigation', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -1224,6 +1224,12 @@ describe('src/cy/commands/querying', () => {
cy.contains(/\'/)
})
// https://github.com/cypress-io/cypress/issues/19116
it('handles backslashes', () => {
$('<div id="backslashes">"&lt;OE_D]dQ\\</div>').appendTo(cy.$$('body'))
cy.get('#backslashes').contains('"<OE_D]dQ\\')
})
describe('should(\'not.exist\')', () => {
it('returns null when no content exists', () => {
cy.contains('alksjdflkasjdflkajsdf').should('not.exist').then(($el) => {
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin actions', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
})
it('.type()', () => {
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin aliasing', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin assertions', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin connectors', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin cookies', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin files', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin local storage', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin location', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -15,7 +15,7 @@ context('cy.origin location', () => {
it('.location()', () => {
cy.origin('http://foobar.com:3500', () => {
cy.location().should((location) => {
expect(location.href).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(location.href).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
expect(location.origin).to.equal('http://www.foobar.com:3500')
})
})
@@ -23,7 +23,7 @@ context('cy.origin location', () => {
it('.url()', () => {
cy.origin('http://foobar.com:3500', () => {
cy.url().should('equal', 'http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.url().should('equal', 'http://www.foobar.com:3500/fixtures/secondary-origin.html')
})
})
@@ -86,7 +86,7 @@ context('cy.origin location', () => {
expect(consoleProps.Command).to.equal('url')
expect(consoleProps.Yielded).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(consoleProps.Yielded).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
})
})
})
@@ -11,7 +11,7 @@ context('cy.origin log', () => {
logs.push(log)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin misc', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -3,14 +3,14 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin navigation', () => {
it('.go()', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
cy.go('back')
cy.location('pathname').should('include', 'multi-domain-secondary.html')
cy.location('pathname').should('include', 'secondary-origin.html')
cy.go('forward')
cy.location('pathname').should('include', 'dom.html')
@@ -18,7 +18,7 @@ context('cy.origin navigation', () => {
})
it('.reload()', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -38,7 +38,7 @@ context('cy.origin navigation', () => {
cy.on('window:before:load', primaryCyBeforeLoadSpy)
cy.on('window:load', primaryCyLoadSpy)
cy.visit('/fixtures/multi-domain.html', {
cy.visit('/fixtures/primary-origin.html', {
onBeforeLoad: primaryVisitBeforeLoadSpy,
onLoad: primaryVisitLoadSpy,
}).then(() => {
@@ -75,10 +75,10 @@ context('cy.origin navigation', () => {
})
it('supports visiting primary first', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
@@ -86,7 +86,7 @@ context('cy.origin navigation', () => {
it('supports skipping visiting primary first', () => {
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
@@ -94,10 +94,10 @@ context('cy.origin navigation', () => {
// TODO: we don't support nested cy.origin yet...
it.skip('supports nesting a third origin', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
@@ -108,7 +108,7 @@ context('cy.origin navigation', () => {
})
it('supports navigating to secondary through button and then visiting', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
@@ -149,12 +149,12 @@ context('cy.origin navigation', () => {
})
it('supports hash change within secondary', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html#hashchange')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html#hashchange')
cy.location('hash').should('equal', '#hashchange')
})
@@ -162,14 +162,14 @@ context('cy.origin navigation', () => {
it('navigates back to primary', () => {
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').should('have.text', 'http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.location('href').should('equal', 'http://localhost:3500/fixtures/multi-domain.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').should('have.text', 'http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.location('href').should('equal', 'http://localhost:3500/fixtures/primary-origin.html')
})
it('errors when visiting a new origin within origin', (done) => {
@@ -178,7 +178,7 @@ context('cy.origin navigation', () => {
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')\`
\` cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')\`
\` <commands targeting http://www.foobar.com:3500 go here>\`
\`})\`\n
\`cy.origin('http://idp.com:3500', () => {\`
@@ -196,7 +196,7 @@ context('cy.origin navigation', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -211,7 +211,7 @@ context('cy.origin navigation', () => {
expect(e.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
In order to visit a different origin, you can enable the \`experimentalSessionAndOrigin\` flag and use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/multi-domain.html')\`
\`cy.visit('http://localhost:3500/fixtures/primary-origin.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://www.foobar.com:3500/fixtures/dom.html')\`
@@ -228,17 +228,17 @@ context('cy.origin navigation', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
// this call should error since we can't visit a cross-origin
cy.visit('http://www.foobar.com:3500/fixtures/dom.html')
})
it('supports the query string option', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html', { qs: { foo: 'bar' } })
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html', { qs: { foo: 'bar' } })
cy.location('search').should('equal', '?foo=bar')
})
@@ -264,10 +264,10 @@ context('cy.origin navigation', () => {
// manually remove the spec bridge iframe to ensure Cypress.state('window') is not already set
window.top?.document.getElementById('Spec\ Bridge:\ foobar.com')?.remove()
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
@@ -276,17 +276,17 @@ context('cy.origin navigation', () => {
it('succeeds when the secondary is already defined but the AUT is still on the primary', () => {
// setup the secondary to be on the secondary origin
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
// update the AUT to be on the primary origin
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
// verify there aren't any issues when the AUT is on primary but the spec bridge is on secondary (cross-origin)
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
@@ -302,9 +302,9 @@ context('cy.origin navigation', () => {
cy.on('window:load', secondaryCyLoadSpy)
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html').then(() => {
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html').then(() => {
expect(secondaryCyLoadSpy).to.have.been.calledOnce
expect(secondaryCyLoadSpy.args[0][0].location.href).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(secondaryCyLoadSpy.args[0][0].location.href).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
})
})
@@ -314,16 +314,16 @@ context('cy.origin navigation', () => {
})
it('supports redirecting from primary to secondary in cy.origin', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
})
it('supports redirecting from secondary to primary outside of cy.origin', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.visit('http://www.foobar.com:3500/redirect?href=http://localhost:3500/fixtures/generic.html')
})
@@ -341,7 +341,7 @@ context('cy.origin navigation', () => {
done()
})
cy.visit('http://localhost:3500/fixtures/multi-domain.html')
cy.visit('http://localhost:3500/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('/redirect?href=http://localhost:3500/fixtures/generic.html')
@@ -362,7 +362,7 @@ context('cy.origin navigation', () => {
done()
})
cy.visit('http://localhost:3500/fixtures/multi-domain.html')
cy.visit('http://localhost:3500/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('http://localhost:3500/fixtures/generic.html')
@@ -374,7 +374,7 @@ context('cy.origin navigation', () => {
expect(e.message).to.equal(stripIndent`\
\`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin.\n
You likely forgot to use \`cy.origin()\`:\n
\`cy.visit('http://localhost:3500/fixtures/multi-domain.html')\`
\`cy.visit('http://localhost:3500/fixtures/primary-origin.html')\`
\`<commands targeting http://localhost:3500 go here>\`\n
\`cy.origin('http://foobar.com:3500', () => {\`
\` cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/generic.html')\`
@@ -391,7 +391,7 @@ context('cy.origin navigation', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.visit('http://localhost:3500/redirect?href=http://www.foobar.com:3500/fixtures/generic.html')
})
@@ -415,7 +415,7 @@ context('cy.origin navigation', () => {
// attaches the auth options for the foobar origin even from another origin
cy.origin('http://www.idp.com:3500', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.window().then((win) => {
win.location.href = 'http://www.foobar.com:3500/basic_auth'
@@ -426,7 +426,7 @@ context('cy.origin navigation', () => {
cy.get('body').should('have.text', 'basic auth worked')
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
// attaches the auth options for the foobar origin from the top-level
cy.window().then((win) => {
@@ -449,7 +449,7 @@ context('cy.origin navigation', () => {
})
cy.window().then((win) => {
win.location.href = 'http://www.foobar.com:3500/fixtures/multi-domain.html'
win.location.href = 'http://www.foobar.com:3500/fixtures/primary-origin.html'
})
cy.origin('http://foobar.com:3500', () => {
@@ -460,33 +460,33 @@ context('cy.origin navigation', () => {
})
it('succeeds when visiting local file server first', { baseUrl: undefined }, () => {
cy.visit('cypress/fixtures/multi-domain.html')
cy.visit('cypress/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('/fixtures/multi-domain-secondary.html')
cy.visit('/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
})
it('handles visit failures', { baseUrl: undefined }, (done) => {
cy.on('fail', (e) => {
expect(e.message).to.include('failed trying to load:\n\nhttp://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(e.message).to.include('failed trying to load:\n\nhttp://www.foobar.com:3500/fixtures/secondary-origin.html')
expect(e.message).to.include('500: Internal Server Error')
done()
})
cy.intercept('*/multi-domain-secondary.html', { statusCode: 500 })
cy.intercept('*/secondary-origin.html', { statusCode: 500 })
cy.visit('cypress/fixtures/multi-domain.html')
cy.visit('cypress/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {
cy.visit('fixtures/multi-domain-secondary.html')
cy.visit('fixtures/secondary-origin.html')
})
})
})
it('supports navigating through changing the window.location.href', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -510,7 +510,7 @@ context('cy.origin navigation', () => {
})
it('.go()', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -531,7 +531,7 @@ context('cy.origin navigation', () => {
})
it('.reload()', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -550,10 +550,10 @@ context('cy.origin navigation', () => {
})
it('visit()', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.origin('http://foobar.com:3500', () => {
cy.visit('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.visit('http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.get('[data-cy="dom-check"]').should('have.text', 'From a secondary origin')
})
@@ -562,12 +562,12 @@ context('cy.origin navigation', () => {
const { consoleProps, ...attrs } = findCrossOriginLogs('visit', logs, 'foobar.com')
expect(attrs.name).to.equal('visit')
expect(attrs.message).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(attrs.message).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
expect(consoleProps.Command).to.equal('visit')
expect(consoleProps).to.have.property('Cookies Set').that.is.an('object')
expect(consoleProps).to.have.property('Redirects').that.is.an('object')
expect(consoleProps).to.have.property('Resolved Url').that.equals('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(consoleProps).to.have.property('Resolved Url').that.equals('http://www.foobar.com:3500/fixtures/secondary-origin.html')
})
})
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin network requests', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="request-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin querying', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin shadow dom', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="shadow-dom-link"]').click()
})
@@ -16,7 +16,7 @@ context('cy.origin screenshot', () => {
cy.viewport(600, 200)
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="screenshots-link"]').click()
})
@@ -74,7 +74,7 @@ context('cy.origin screenshot', () => {
duration: 100,
}
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="screenshots-link"]').click()
})
@@ -287,7 +287,7 @@ context('cy.origin screenshot', () => {
logs.set(attrs.id, log)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="screenshots-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin spies, stubs, and clock', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin traversal', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -1,6 +1,6 @@
context('cy.origin unsupported commands', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -5,7 +5,7 @@ context('cy.origin viewport', () => {
// change the viewport in the primary first
cy.viewport(320, 480)
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -27,7 +27,7 @@ context('cy.origin viewport', () => {
context('with out pre-set viewport', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -135,7 +135,7 @@ context('cy.origin viewport', () => {
cy.viewport(320, 480)
cy.window().then((win) => {
win.location.href = 'http://localhost:3500/fixtures/multi-domain.html'
win.location.href = 'http://localhost:3500/fixtures/primary-origin.html'
})
})
@@ -160,7 +160,7 @@ context('cy.origin viewport', () => {
})
cy.window().then((win) => {
win.location.href = 'http://www.idp.com:3500/fixtures/multi-domain.html'
win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html'
})
cy.origin('http://idp.com:3500', () => {
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin waiting', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -2,7 +2,7 @@ import { findCrossOriginLogs } from '../../../../support/utils'
context('cy.origin window', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="dom-link"]').click()
})
@@ -11,7 +11,7 @@
}
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -7,7 +7,7 @@ describe('cy.origin - cookie login', () => {
it('works in a session', () => {
cy.session('ZJohnson', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('[data-cy="cookie-login"]').click()
cy.origin('http://foobar.com:3500', () => {
cy.get('[data-cy="username"]').type('ZJohnson')
@@ -25,7 +25,7 @@ describe('cy.origin - cookie login', () => {
describe('SameSite handling', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('[data-cy="cookie-login"]').click()
})
@@ -2,7 +2,7 @@ const { assertLogLength } = require('../../../support/utils')
describe('cy.origin Cypress API', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -229,7 +229,7 @@ describe('cy.origin Cypress API', () => {
})
})
it('throws an error when a user attempts to call Cypress.session.clearAllSavedSessions() inside of multi-domain', (done) => {
it('throws an error when a user attempts to call Cypress.session.clearAllSavedSessions() inside of cy.origin', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`Cypress.session.*` methods are not supported in the `cy.switchToDomain()` callback. Consider using them outside of the callback instead.')
expect(err.docsUrl).to.equal('https://on.cypress.io/session-api')
@@ -1,6 +1,6 @@
describe('cy.origin', () => {
it('window:before:load event', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.on('window:before:load', (win: {testPrimaryOriginBeforeLoad: boolean}) => {
win.testPrimaryOriginBeforeLoad = true
})
@@ -20,7 +20,7 @@ describe('cy.origin', () => {
.should('equal', 'Window Before Load Called')
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.window().its('testPrimaryOriginBeforeLoad').should('be.true')
cy.window().its('testSecondaryWindowBeforeLoad').should('be.undefined')
@@ -28,7 +28,7 @@ describe('cy.origin', () => {
describe('post window load events', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -57,7 +57,7 @@ describe('cy.origin', () => {
})
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.wrap(afterWindowBeforeUnload)
})
@@ -72,7 +72,7 @@ describe('cy.origin', () => {
})
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.wrap(afterWindowUnload)
})
@@ -8,7 +8,7 @@ describe('cy.origin logging', () => {
logs.push(attrs)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -45,7 +45,7 @@ describe('cy.origin logging', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
// @ts-ignore
@@ -67,7 +67,7 @@ describe('cy.origin logging', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
const options = { args: { div: Cypress.$('div') } }
@@ -98,7 +98,7 @@ describe('cy.origin logging', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -130,7 +130,7 @@ describe('cy.origin logging', () => {
done()
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -21,7 +21,7 @@ describe('navigation events', () => {
logs.push(log)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -32,7 +32,7 @@ describe('navigation events', () => {
const listener = () => {
cy.location().should((loc) => {
expect(loc.host).to.equal('www.foobar.com:3500')
expect(loc.pathname).to.equal('/fixtures/multi-domain-secondary.html')
expect(loc.pathname).to.equal('/fixtures/secondary-origin.html')
expect(loc.hash).to.equal('#hashChange')
})
@@ -92,7 +92,7 @@ describe('navigation events', () => {
const listener = (win) => {
times++
expect(win.location.host).to.equal('www.foobar.com:3500')
expect(win.location.pathname).to.equal('/fixtures/multi-domain-secondary.html')
expect(win.location.pathname).to.equal('/fixtures/secondary-origin.html')
if (times === 2) {
cy.removeListener('window:load', listener)
@@ -129,13 +129,13 @@ describe('navigation events', () => {
times++
if (times === 1) {
expect(win.location.host).to.equal('www.foobar.com:3500')
expect(win.location.pathname).to.equal('/fixtures/multi-domain-secondary.html')
expect(win.location.pathname).to.equal('/fixtures/secondary-origin.html')
}
if (times === 2) {
cy.removeListener('window:load', listener)
expect(win.location.host).to.equal('www.foobar.com:3500')
expect(win.location.pathname).to.equal('/fixtures/multi-domain.html')
expect(win.location.pathname).to.equal('/fixtures/primary-origin.html')
resolve()
}
}
@@ -144,7 +144,7 @@ describe('navigation events', () => {
})
cy.get('a[data-cy="cross-origin-page"]').click()
cy.get('a[data-cy="cross-origin-secondary-link').invoke('text').should('equal', 'http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
cy.get('a[data-cy="cross-origin-secondary-link').invoke('text').should('equal', 'http://www.foobar.com:3500/fixtures/secondary-origin.html')
cy.wrap(afterWindowLoad).then(() => {
return logs.map((log) => ({ name: log.get('name'), message: log.get('message') }))
})
@@ -154,7 +154,7 @@ describe('navigation events', () => {
assertLogLength(secondaryLogs, 13)
expect(secondaryLogs[5].get('name')).to.eq('page load')
expect(secondaryLogs[6].get('name')).to.eq('new url')
expect(secondaryLogs[6].get('message')).to.eq('http://www.foobar.com:3500/fixtures/multi-domain.html')
expect(secondaryLogs[6].get('message')).to.eq('http://www.foobar.com:3500/fixtures/primary-origin.html')
})
})
})
@@ -164,7 +164,7 @@ describe('navigation events', () => {
cy.origin('http://foobar.com:3500', () => {
const afterUrlChanged = new Promise<void>((resolve) => {
cy.once('url:changed', (url) => {
expect(url).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(url).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
resolve()
})
})
@@ -181,12 +181,12 @@ describe('navigation events', () => {
const listener = (url) => {
times++
if (times === 1) {
expect(url).to.equal('http://www.foobar.com:3500/fixtures/multi-domain-secondary.html')
expect(url).to.equal('http://www.foobar.com:3500/fixtures/secondary-origin.html')
}
if (times === 2) {
cy.removeListener('url:changed', listener)
expect(url).to.equal('http://www.foobar.com:3500/fixtures/multi-domain.html')
expect(url).to.equal('http://www.foobar.com:3500/fixtures/primary-origin.html')
resolve()
}
}
@@ -212,7 +212,7 @@ describe('navigation events', () => {
// @ts-ignore / session support is needed for visiting about:blank between tests
describe('event timing', () => {
it('does not timeout when receiving a delaying:html event after cy.origin has started, but before the spec bridge is ready', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', () => {
@@ -221,7 +221,7 @@ describe('event timing', () => {
// This command is run from localhost against the cross-origin aut. Updating href is one of the few allowed commands. See https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#location
cy.window().then((win) => {
win.location.href = 'http://www.idp.com:3500/fixtures/multi-domain.html'
win.location.href = 'http://www.idp.com:3500/fixtures/primary-origin.html'
})
cy.origin('http://idp.com:3500', () => {
@@ -8,7 +8,7 @@ describe('cy.origin', () => {
expect(primaryViewport).to.deep.equal(expectedViewport)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
cy.origin('http://foobar.com:3500', { args: expectedViewport }, (expectedViewport) => {
@@ -20,7 +20,7 @@ describe('cy.origin', () => {
context('withBeforeEach', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -1,7 +1,7 @@
// @ts-ignore
describe('cy.origin - rerun', { }, () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -1,6 +1,6 @@
describe('cy.origin - uncaught errors', () => {
beforeEach(() => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="errors-link"]').click()
})
@@ -3,7 +3,7 @@ describe('cy.origin', () => {
it('succeeds on a localhost domain name', () => {
cy.origin('localhost', () => undefined)
cy.then(() => {
const expectedSrc = `https://localhost/__cypress/multi-domain-iframes`
const expectedSrc = `https://localhost/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://localhost') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -13,7 +13,7 @@ describe('cy.origin', () => {
it('succeeds on an ip address', () => {
cy.origin('127.0.0.1', () => undefined)
cy.then(() => {
const expectedSrc = `https://127.0.0.1/__cypress/multi-domain-iframes`
const expectedSrc = `https://127.0.0.1/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://127.0.0.1') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -25,7 +25,7 @@ describe('cy.origin', () => {
it.skip('succeeds on an ipv6 address', () => {
cy.origin('0000:0000:0000:0000:0000:0000:0000:0001', () => undefined)
cy.then(() => {
const expectedSrc = `https://[::1]/__cypress/multi-domain-iframes`
const expectedSrc = `https://[::1]/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://[::1]') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -35,7 +35,7 @@ describe('cy.origin', () => {
it('succeeds on a unicode domain', () => {
cy.origin('はじめよう.みんな', () => undefined)
cy.then(() => {
const expectedSrc = `https://xn--p8j9a0d9c9a.xn--q9jyb4c/__cypress/multi-domain-iframes`
const expectedSrc = `https://xn--p8j9a0d9c9a.xn--q9jyb4c/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://xn--p8j9a0d9c9a.xn--q9jyb4c') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -45,7 +45,7 @@ describe('cy.origin', () => {
it('succeeds on a complete origin', () => {
cy.origin('http://foobar1.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar1.com:3500/__cypress/multi-domain-iframes`
const expectedSrc = `http://foobar1.com:3500/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar1.com:3500') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -55,7 +55,7 @@ describe('cy.origin', () => {
it('succeeds on a complete origin using https', () => {
cy.origin('https://foobar2.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar2.com:3500/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar2.com:3500/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar2.com:3500') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -65,7 +65,7 @@ describe('cy.origin', () => {
it('succeeds on a hostname and port', () => {
cy.origin('foobar3.com:3500', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar3.com:3500/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar3.com:3500/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar3.com:3500') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -75,7 +75,7 @@ describe('cy.origin', () => {
it('succeeds on a protocol and hostname', () => {
cy.origin('http://foobar4.com', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar4.com/__cypress/multi-domain-iframes`
const expectedSrc = `http://foobar4.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar4.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -85,7 +85,7 @@ describe('cy.origin', () => {
it('succeeds on a subdomain', () => {
cy.origin('app.foobar5.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar5.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar5.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar5.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -95,7 +95,7 @@ describe('cy.origin', () => {
it('succeeds when only domain is passed', () => {
cy.origin('foobar6.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar6.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar6.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar6.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -105,7 +105,7 @@ describe('cy.origin', () => {
it('succeeds on a url with path', () => {
cy.origin('http://www.foobar7.com/login', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar7.com/__cypress/multi-domain-iframes`
const expectedSrc = `http://foobar7.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar7.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -115,7 +115,7 @@ describe('cy.origin', () => {
it('succeeds on a url with a hash', () => {
cy.origin('http://www.foobar8.com/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar8.com/__cypress/multi-domain-iframes`
const expectedSrc = `http://foobar8.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar8.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -125,7 +125,7 @@ describe('cy.origin', () => {
it('succeeds on a url with a path and hash', () => {
cy.origin('http://www.foobar9.com/login/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `http://foobar9.com/__cypress/multi-domain-iframes`
const expectedSrc = `http://foobar9.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar9.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -135,7 +135,7 @@ describe('cy.origin', () => {
it('succeeds on a domain with path', () => {
cy.origin('foobar10.com/login', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar10.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar10.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar10.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -145,7 +145,7 @@ describe('cy.origin', () => {
it('succeeds on a domain with a hash', () => {
cy.origin('foobar11.com/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar11.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar11.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar11.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -155,7 +155,7 @@ describe('cy.origin', () => {
it('succeeds on a domain with a path and hash', () => {
cy.origin('foobar12.com/login/#hash', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar12.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar12.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar12.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -165,7 +165,7 @@ describe('cy.origin', () => {
it('succeeds on a public suffix with a subdomain', () => {
cy.origin('app.foobar.herokuapp.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://foobar.herokuapp.com/__cypress/multi-domain-iframes`
const expectedSrc = `https://foobar.herokuapp.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar.herokuapp.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -175,7 +175,7 @@ describe('cy.origin', () => {
it('succeeds on a machine name', () => {
cy.origin('machine-name', () => undefined)
cy.then(() => {
const expectedSrc = `https://machine-name/__cypress/multi-domain-iframes`
const expectedSrc = `https://machine-name/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://machine-name') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
@@ -203,7 +203,7 @@ describe('cy.origin', () => {
cy.origin('http://www.bar.com:3500', () => undefined)
cy.origin('http://www.app.foobar.com:3500', () => {
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
})
})
@@ -10,7 +10,7 @@ describe('cy.origin yields', () => {
logs.push(log)
})
cy.visit('/fixtures/multi-domain.html')
cy.visit('/fixtures/primary-origin.html')
cy.get('a[data-cy="cross-origin-secondary-link"]').click()
})
@@ -0,0 +1,25 @@
// @see https://github.com/cypress-io/cypress/issues/17805
describe('issue 17805', { experimentalSessionAndOrigin: true }, () => {
it('recreates session on spec reload in open mode', () => {
let validateFlag = false
cy.session('persist_session', () => {
validateFlag = true
},
{
validate () {
if (validateFlag) {
return true
}
return false
},
})
})
after(() => {
if (cy.$$('.commands-container li.command:first', top.document).text().includes('(new)')) {
top.location.reload()
}
})
})
@@ -408,7 +408,7 @@ describe('Log Serialization', () => {
expect($el[0].id).to.equal('button-inside-a')
expect($el[0].textContent).to.equal('click button')
// most of the consoleProps logic is tested in the e2e/multi-domain folder. focus in this test will be mostly snapshot serialization
// most of the consoleProps logic is tested in the e2e/commands/origin folder. focus in this test will be mostly snapshot serialization
expect(consoleProps['Applied To']).to.be.instanceOf(HTMLFormElement)
expect(consoleProps['Applied To']).to.have.property('id').that.equals('button-inside-a')
expect(consoleProps['Applied To']).to.have.property('textContent').that.equals('click button')
+1 -1
View File
@@ -5,7 +5,7 @@
"scripts": {
"clean-deps": "rm -rf node_modules",
"cypress:open": "node ../../scripts/cypress open",
"cypress:run": "node ../../scripts/cypress run --spec \"cypress/integration/*/*\",\"cypress/integration/*/!(multi-domain)/**/*\"",
"cypress:run": "node ../../scripts/cypress run --spec \"cypress/integration/*/*\",\"cypress/integration/*/!(origin)/**/*\"",
"cypress:open-experimentalSessionAndOrigin": "node ../../scripts/cypress open --config experimentalSessionAndOrigin=true",
"cypress:run-experimentalSessionAndOrigin": "node ../../scripts/cypress run --config experimentalSessionAndOrigin=true",
"postinstall": "patch-package",
+11 -1
View File
@@ -8,6 +8,7 @@ import sinonChai from '@cypress/sinon-chai'
import $dom from '../dom'
import $utils from '../cypress/utils'
import { escapeBackslashes, escapeQuotes } from '../util/escape'
import $errUtils from '../cypress/error_utils'
import $stackUtils from '../cypress/stack_utils'
import $chaiJquery from '../cypress/chai_jquery'
@@ -29,6 +30,8 @@ const trailingWhitespaces = /\s*'\*\*/g
const whitespace = /\s/g
const valueHasLeadingOrTrailingWhitespaces = /\*\*'\s+|\s+'\*\*/g
const imageMarkdown = /!\[.*?\]\(.*?\)/g
const doubleslashRe = /\\\\/g
const escapedDoubleslashRe = /__double_slash__/g
type CreateFunc = ((specWindow, state, assertFn) => ({
chai: Chai.ChaiStatic
@@ -103,6 +106,9 @@ chai.use((chai, u) => {
return
})
const escapeDoubleSlash = (str: string) => str.replace(doubleslashRe, '__double_slash__')
const restoreDoubleSlash = (str: string) => str.replace(escapedDoubleslashRe, '\\\\')
// remove any single quotes between our **,
// except escaped quotes, empty strings and number strings.
const removeOrKeepSingleQuotesBetweenStars = (message) => {
@@ -277,7 +283,9 @@ chai.use((chai, u) => {
return _super.apply(this, arguments)
}
const escText = $utils.escapeQuotes(text)
const escText = escapeQuotes(
escapeBackslashes(text),
)
const selector = `:contains('${escText}'), [type='submit'][value~='${escText}']`
@@ -457,7 +465,9 @@ chai.use((chai, u) => {
let message = chaiUtils.getMessage(this, customArgs as Chai.AssertionArgs)
const actual = chaiUtils.getActual(this, customArgs as Chai.AssertionArgs)
message = escapeDoubleSlash(message)
message = removeOrKeepSingleQuotesBetweenStars(message)
message = restoreDoubleSlash(message)
message = escapeMarkdown(message)
try {
+3
View File
@@ -30,6 +30,8 @@ import * as Location from './location'
import * as Misc from './misc'
import * as Origin from './origin'
import * as Popups from './popups'
import * as Navigation from './navigation'
@@ -69,6 +71,7 @@ export const allCommands = {
LocalStorage,
Location,
Misc,
Origin,
Popups,
Navigation,
...Querying,
@@ -1,13 +1,13 @@
import Bluebird from 'bluebird'
import $errUtils from '../../cypress/error_utils'
import $stackUtils from '../../cypress/stack_utils'
import $errUtils from '../../../cypress/error_utils'
import $stackUtils from '../../../cypress/stack_utils'
import { Validator } from './validator'
import { createUnserializableSubjectProxy } from './unserializable_subject_proxy'
import { serializeRunnable } from './util'
import { preprocessConfig, preprocessEnv, syncConfigToCurrentOrigin, syncEnvToCurrentOrigin } from '../../util/config'
import { $Location } from '../../cypress/location'
import { LogUtils } from '../../cypress/log'
import logGroup from '../logGroup'
import { preprocessConfig, preprocessEnv, syncConfigToCurrentOrigin, syncEnvToCurrentOrigin } from '../../../util/config'
import { $Location } from '../../../cypress/location'
import { LogUtils } from '../../../cypress/log'
import logGroup from '../../logGroup'
const reHttp = /^https?:\/\//
@@ -22,7 +22,7 @@ const normalizeOrigin = (urlOrDomain) => {
return $Location.normalize(origin)
}
export function addCommands (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) {
export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: Cypress.State, config: Cypress.InternalConfig) => {
let timeoutId
const communicator = Cypress.primaryOriginCommunicator
@@ -1,4 +1,4 @@
import $errUtils from '../../cypress/error_utils'
import $errUtils from '../../../cypress/error_utils'
// These properties are required to avoid failing prior to attempting to use the subject.
// If Symbol.toStringTag is passed through to the target we will not properly fail the 'cy.invoke' command.
@@ -1,5 +1,5 @@
import $utils from '../../cypress/utils'
import $errUtils from '../../cypress/error_utils'
import $utils from '../../../cypress/utils'
import $errUtils from '../../../cypress/error_utils'
import { difference, isPlainObject, isString } from 'lodash'
const validOptionKeys = Object.freeze(['args'])
@@ -209,7 +209,7 @@ export default function (Commands, Cypress, cy) {
* 1) if we only need currentOrigin localStorage, access sync
* 2) if cross-origin http, we need to load in iframe from our proxy that will intercept all http reqs at /__cypress/automation/*
* and postMessage() the localStorage value to us
* 3) if cross-origin https, since we pass-thru https conntections in the proxy, we need to
* 3) if cross-origin https, since we pass-thru https connections in the proxy, we need to
* send a message telling our proxy server to intercept the next req to the https domain,
* then follow 2)
*/
@@ -638,7 +638,7 @@ export default function (Commands, Cypress, cy) {
// we have a saved session on the server AND setup matches
if (serverStoredSession && serverStoredSession.setup === existingSession.setup.toString()) {
_.extend(existingSession, serverStoredSession)
_.extend(existingSession, _.omit(serverStoredSession, 'setup'))
existingSession.hydrated = true
} else {
onValidationError = throwValidationError
+1 -1
View File
@@ -37,7 +37,7 @@ import ProxyLogging from './cypress/proxy-logging'
import * as $Events from './cypress/events'
import $Keyboard from './cy/keyboard'
import * as resolvers from './cypress/resolvers'
import { PrimaryOriginCommunicator, SpecBridgeCommunicator } from './multi-domain/communicator'
import { PrimaryOriginCommunicator, SpecBridgeCommunicator } from './cross-origin/communicator'
const debug = debugFn('cypress:driver:cypress')
-2
View File
@@ -1,7 +1,6 @@
import _ from 'lodash'
import { allCommands } from '../cy/commands'
import { addCommand as addNetstubbingCommand } from '../cy/net-stubbing'
import { addCommands as addCrossOriginCommands } from '../cy/multi-domain'
import $errUtils from './error_utils'
import $stackUtils from './stack_utils'
@@ -10,7 +9,6 @@ const builtInCommands = [
// @ts-ignore
..._.toArray(allCommands).map((c) => c.default || c),
addNetstubbingCommand,
addCrossOriginCommands,
]
const getTypeByPrevSubject = (prevSubject) => {
-7
View File
@@ -10,7 +10,6 @@ import { $Location } from './location'
const tagOpen = /\[([a-z\s='"-]+)\]/g
const tagClosed = /\[\/([a-z]+)\]/g
const quotesRe = /('|")/g
const defaultOptions = {
delay: 10,
@@ -267,12 +266,6 @@ export default {
}
},
escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
},
normalizeNumber (num) {
const parsed = Number(num)
+5 -2
View File
@@ -4,7 +4,8 @@ import $document from '../document'
import $jquery from '../jquery'
import { getTagName } from './elementHelpers'
import { isWithinShadowRoot, getShadowElementFromPoint } from './shadow'
import { normalizeWhitespaces, escapeQuotes } from './utils'
import { normalizeWhitespaces } from './utils'
import { escapeQuotes, escapeBackslashes } from '../../util/escape'
/**
* Find Parents relative to an initial element
@@ -228,7 +229,9 @@ export const getContainsSelector = (text, filter = '', options: {
} = {}) => {
const $expr = $.expr[':']
const escapedText = escapeQuotes(text)
const escapedText = escapeQuotes(
escapeBackslashes(text),
)
// they may have written the filter as
// comma separated dom els, so we want to search all
@@ -5,7 +5,6 @@ import $window from '../window'
import $document from '../document'
const whitespaces = /\s+/g
const quotesRe = /('|")/g
// When multiple space characters are considered as a single whitespace in all tags except <pre>.
export const normalizeWhitespaces = (elem) => {
@@ -29,12 +28,6 @@ export const isSelector = ($el: JQuery<HTMLElement>, selector) => {
return $el.is(selector)
}
export function escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
}
export function switchCase (value, casesObj, defaultKey = 'default') {
if (_.has(casesObj, value)) {
return _.result(casesObj, value)
+13
View File
@@ -0,0 +1,13 @@
const quotesRe = /('|")/g
const backslashRe = /\\/g
export function escapeQuotes (text) {
// convert to str and escape any single
// or double quotes
return (`${text}`).replace(quotesRe, '\\$1')
}
export function escapeBackslashes (text) {
// convert to str and escape any backslashes
return (`${text}`).replace(backslashRe, '\\\\')
}
+2 -2
View File
@@ -66,8 +66,8 @@ declare namespace Cypress {
state: State
events: Events
emit: (event: string, payload?: any) => void
primaryOriginCommunicator: import('../src/multi-domain/communicator').PrimaryOriginCommunicator
specBridgeCommunicator: import('../src/multi-domain/communicator').SpecBridgeCommunicator
primaryOriginCommunicator: import('../src/cross-origin/communicator').PrimaryOriginCommunicator
specBridgeCommunicator: import('../src/cross-origin/communicator').SpecBridgeCommunicator
mocha: $Mocha
configure: (config: Cypress.ObjectLike) => void
isCrossOriginSpecBridge: boolean
@@ -296,7 +296,7 @@ describe('http/response-middleware', function () {
...props.res,
},
req: {
proxiedUrl: 'http://127.0.0.1:3501/multi-domain.html',
proxiedUrl: 'http://127.0.0.1:3501/primary-origin.html',
headers: {},
...props.req,
},
@@ -580,7 +580,7 @@ describe('http/response-middleware', function () {
...props.res,
},
req: {
proxiedUrl: 'http://127.0.0.1:3501/multi-domain.html',
proxiedUrl: 'http://127.0.0.1:3501/primary-origin.html',
headers: {},
cookies: {
'__cypress.initial': true,
@@ -735,7 +735,7 @@ describe('http/response-middleware', function () {
},
req: {
isAUTFrame: true,
proxiedUrl: 'http://www.foobar.com/multi-domain.html',
proxiedUrl: 'http://www.foobar.com/primary-origin.html',
},
res: {
append: appendStub,
@@ -758,7 +758,7 @@ describe('http/response-middleware', function () {
},
req: {
isAUTFrame: true,
proxiedUrl: 'http://www.foobar.com/multi-domain.html',
proxiedUrl: 'http://www.foobar.com/primary-origin.html',
},
res: {
append: appendStub,
@@ -903,7 +903,7 @@ describe('http/response-middleware', function () {
...props.res,
},
req: {
proxiedUrl: 'http://127.0.0.1:3501/multi-domain.html',
proxiedUrl: 'http://127.0.0.1:3501/primary-origin.html',
headers: {},
...props.req,
},
@@ -1,2 +1,2 @@
// this is the entry point for the cross-origin version of the driver
import '@packages/driver/src/multi-domain/cypress'
import '@packages/driver/src/cross-origin/cypress'
+1 -1
View File
@@ -188,7 +188,7 @@ export default class Iframes extends Component {
// container since it needs to match the size of the top window for screenshots
$container: $(document.body),
className: 'spec-bridge-iframe',
src: `${location.originPolicy}/${this.props.config.namespace}/multi-domain-iframes`,
src: `${location.originPolicy}/${this.props.config.namespace}/spec-bridge-iframes`,
})
}
+2 -2
View File
@@ -88,7 +88,7 @@ const crossOriginConfig: webpack.Configuration = {
mode: 'production',
...getSimpleConfig(),
entry: {
cypress_cross_origin_runner: [path.resolve(__dirname, 'src/multi-domain.js')],
cypress_cross_origin_runner: [path.resolve(__dirname, 'src/cross-origin.js')],
},
output: {
path: path.resolve(__dirname, 'dist'),
@@ -114,7 +114,7 @@ const crossOriginInjectionConfig: webpack.Configuration = {
...getSimpleConfig(),
mode: 'production',
entry: {
injection_cross_origin: [path.resolve(__dirname, 'injection/multi-domain.js')],
injection_cross_origin: [path.resolve(__dirname, 'injection/cross-origin.js')],
},
output: {
path: path.resolve(__dirname, 'dist'),
+1 -1
View File
@@ -51,7 +51,7 @@ module.exports = {
},
handleCrossOriginIframe (req, res) {
const iframePath = cwd('lib', 'html', 'multi-domain-iframe.html')
const iframePath = cwd('lib', 'html', 'spec-bridge-iframe.html')
const domain = req.hostname
const iframeOptions = {
+2 -2
View File
@@ -27,7 +27,7 @@ export async function getPathsAndIds () {
const projectRoots: string[] = await cache.getProjectRoots()
// this assumes that the configFile for a cached project is 'cypress.json'
// https://git.io/JeGyF
// https://github.com/cypress-io/cypress/pull/3246/files/774af917ce73d8129b8208a1d8c7b24f60e85557#r299968521
return Promise.all(projectRoots.map(async (projectRoot) => {
return {
path: projectRoot,
@@ -136,7 +136,7 @@ export function remove (path) {
export async function add (path, options) {
// don't cache a project if a non-default configFile is set
// https://git.io/JeGyF
// https://github.com/cypress-io/cypress/pull/3246/files/774af917ce73d8129b8208a1d8c7b24f60e85557#r299968521
if (settings.configFile(options) !== 'cypress.json') {
return Promise.resolve({ path })
}
+1 -1
View File
@@ -129,7 +129,7 @@ export const createRoutesE2E = ({
res.sendFile(file, { etag: false })
})
routesE2E.get('/__cypress/multi-domain-iframes', (req, res) => {
routesE2E.get('/__cypress/spec-bridge-iframes', (req, res) => {
debug('handling cross-origin iframe for domain: %s', req.hostname)
files.handleCrossOriginIframe(req, res)
-26
View File
@@ -1,26 +0,0 @@
const fs = require('fs-extra')
const glob = require('glob')
const Promise = require('bluebird')
const globAsync = Promise.promisify(glob)
const endingNewLines = /\s+$/g
const contentBetweenBackticksRe = /\`([\s\S]+?)\`/g
globAsync('packages/server/__snapshots__/*')
.map((file) => {
return fs.readFile(file, 'utf8')
.then((str) => {
return str
.replace(
contentBetweenBackticksRe,
'`\n$1\n`',
)
.split('`\n\n\n').join('`\n\n')
.split('\n\n\n\n`').join('\n\n\n`')
.split(endingNewLines).join('\n')
})
.then((str) => {
return fs.writeFile(file, str)
})
})
-21
View File
@@ -1,21 +0,0 @@
// ignore TS errors - we are importing from CoffeeScript files
// @ts-ignore
import uploadUtils from './util/upload'
import { s3helpers } from './s3-api'
const aws = uploadUtils.getS3Credentials()
const s3 = s3helpers.makeS3(aws)
const bucket = aws.bucket
const key = 'beta/binary/3.3.0/darwin-x64/circle-develop-455046b928c861d4457b2ec5426a51de1fda74fd-102212/cypress.zip'
/*
a little demo showing how user metadata can be set and read on a S3 object.
*/
s3helpers.setUserMetadata(bucket, key, {
user: 'bar',
}, s3)
.then(() => {
return s3helpers.getUserMetadata(bucket, key, s3)
}).then(console.log, console.error)
+68
View File
@@ -0,0 +1,68 @@
/* eslint-disable no-console */
const fs = require('fs').promises
async function loadInternalTaskData () {
const filename = process.env.CIRCLE_INTERNAL_CONFIG
if (!filename) throw new Error('Missing CIRCLE_INTERNAL_CONFIG environment variable, cannot load Circle task data.')
const taskDataJson = await fs.readFile(filename, 'utf8')
try {
return JSON.parse(taskDataJson)
} catch (err) {
throw new Error(`An error occurred while parsing the Circle task data: ${err}`)
}
}
// check if the project env canary and context canary are both present to verify that this script is reading the right env
async function checkCanaries () {
if (!process.env.CI) console.warn('This script will not work outside of CI.')
const circleEnv = await readCircleEnv()
if (Object.keys(circleEnv).length === 0) {
return console.warn('CircleCI env empty, assuming this is a contributor PR. Not checking for canary variables.')
}
if (!circleEnv.MAIN_CANARY) throw new Error('Missing MAIN_CANARY.')
if (!circleEnv.CONTEXT_CANARY) throw new Error('Missing CONTEXT_CANARY. Does this job have the test-runner:env-canary context?')
}
// Returns a map of environment variables defined for this job. `readCircleEnv()` differs from `process.env` - it will
// only return environment variables explicitly specified for this job by CircleCI project env and contexts
// NOTE: this Circle API is not stable, and yet it is the only way to access this information.
async function readCircleEnv () {
const taskData = await loadInternalTaskData()
try {
// if this starts failing, try SSHing into a CircleCI job and see what changed in the $CIRCLE_INTERNAL_CONFIG file's schema
const circleEnv = taskData['Dispatched']['TaskInfo']['Environment']
if (!circleEnv) throw new Error('No Environment object was found.')
// last-ditch effort to check that an empty circle env is accurately reflecting process.env (external PRs)
if (process.env.CACHE_VERSION && Object.keys(circleEnv).length === 0) {
throw new Error('CACHE_VERSION is set, but circleEnv is empty')
}
return circleEnv
} catch (err) {
throw new Error(`An error occurred when reading the environment from Circle task data: ${err}`)
}
}
module.exports = {
readCircleEnv,
_checkCanaries: checkCanaries,
}
if (require.main === module) {
if (process.argv.includes('--check-canaries')) {
checkCanaries()
} else {
console.error(`No options were passed, but ${__filename} was invoked as a script.`)
process.exit(1)
}
}
-22
View File
@@ -1,22 +0,0 @@
const execa = require('execa-wrap')
const snapshot = require('snap-shot-it')
function normalize (s) {
// assuming the test command tests this spec file
// and the command is hardcoded in package.json
// using forward slashes
return s.replace(process.cwd(), '<folder path>')
.replace(/passing \(\d+ms\)/, 'passing (<time>ms)')
.replace(/cypress@(\d+\.\d+\.\d+)/, 'cypress@x.y.z')
.replace(/✓/g, 'Y') // Mocha check on Mac
.replace(/√/g, 'Y') // Mocha check on Windows
}
/* eslint-env mocha */
describe('mocha snapshot', () => {
it('captures mocha output', () => {
return execa('npm', ['run', 'test-mocha'])
.then(normalize)
.then(snapshot)
})
})
-24
View File
@@ -1,24 +0,0 @@
const fs = require('fs')
const glob = require('glob')
const minimist = require('minimist')
const path = require('path')
const args = minimist(process.argv.slice(2))
const packages = glob.sync('*/', {
cwd: path.join(process.cwd(), 'packages'),
})
.map((str) => {
return str.replace('/', '\\/')
})
.join('|')
const pattern = `(\'|")(.*\\.\\.\\/)(${packages})`
const re = new RegExp(pattern, 'g')
const text = fs.readFileSync(args.file, 'utf8')
const replacedText = text.replace(re, '$1@packages/$3')
fs.writeFileSync(args.file, replacedText)
-4
View File
@@ -1,4 +0,0 @@
/* eslint-env mocha */
describe('mocha sanity check', () => {
it('works', () => {})
})
-30
View File
@@ -1,30 +0,0 @@
const cp = require('child_process')
const path = require('path')
const chalk = require('chalk')
const finder = require('find-package-json')
const DEFAULT_SCRIPT = 'yarn test --inspect-brk=5566'
const file = process.argv[2]
// walks up from the file until it finds the first
// package.json in a parent folder
const { value: pkg, filename } = finder(file).next()
const script = pkg.scripts['test-debug'] || DEFAULT_SCRIPT
const [cmd, ...args] = script.split(' ')
const log = (k, v) => {
// eslint-disable-next-line
console.log(chalk.yellow(k), chalk.cyan(v))
}
log('Node version:', process.version)
log('Debug script:', script)
log('Debugging test file:', file)
cp.spawn(cmd, args.concat(file), {
cwd: path.dirname(filename),
stdio: 'inherit',
})
-50
View File
@@ -1,50 +0,0 @@
/* eslint-disable no-console */
const minimist = require('minimist')
const os = require('os')
const la = require('lazy-ass')
const fs = require('fs-extra')
const is = require('check-more-types')
const execa = require('execa')
const { getNameAndBinary } = require('./utils')
const options = minimist(process.argv)
const cwd = options.cwd || '/tmp/testing'
fs.ensureDirSync(cwd)
const spawnOpts = {
cwd,
shell: os.platform() === 'win32' ? 'bash.exe' : '/bin/bash',
stdio: 'inherit',
}
const { npm, binary } = getNameAndBinary(process.argv)
la(is.unemptyString(npm), 'missing npm url')
la(is.unemptyString(binary), 'missing binary url')
console.log('Create Dummy Project')
execa('npm init -y', spawnOpts)
.then(console.log)
.then(() => {
console.log('testing NPM from', npm)
console.log('and binary from', binary)
console.log('in', cwd)
return execa(`npm install ${npm}`, {
...spawnOpts,
env: {
CYPRESS_INSTALL_BINARY: binary,
},
}).then(console.log)
})
.then(() => {
console.log('Verify Cypress binary')
return execa('$(yarn bin cypress) verify', spawnOpts)
.then(console.log)
})
.catch((e) => {
console.error(e)
process.exit(1)
})
-32
View File
@@ -1,32 +0,0 @@
// This file exists because mocha doesn't return non-zero value when there is a failed test.
// When https://github.com/mochajs/mocha/issues/3893 is fixed, it will be removed.
const { spawn } = require('child_process')
const Promise = require('bluebird')
// Test result on console.
let log = ''
const proc = spawn(`node`, ['./node_modules/.bin/mocha', 'src/**/*.spec.*'], {})
proc.stdout.on('data', (data) => {
log += data
})
proc.stdout.on('end', async () => {
await Promise.delay(500)
const result = log.match(/\d+ passing.*\n\s*(\d+) failing/)
const numFailing = result && parseInt(result[1], 10)
if (!isNaN(numFailing)) {
process.exit(numFailing)
}
process.exit(0)
})
// Show result on console.
spawn(`node`, ['./node_modules/.bin/mocha', '"src/*.spec.*" "src/**/*.spec.*"'], {
stdio: 'inherit',
shell: true,
})
+68
View File
@@ -0,0 +1,68 @@
const fs = require('fs').promises
const sinon = require('sinon')
const { expect } = require('chai')
const { _checkCanaries } = require('../circle-env')
describe('circle-env', () => {
let cachedEnv = { ...process.env }
afterEach(() => {
sinon.restore()
Object.assign(process.env, cachedEnv)
})
beforeEach(() => {
delete process.env.CACHE_VERSION
process.env.CI = 'true'
process.env.CIRCLE_INTERNAL_CONFIG = '/foo.json'
})
context('with missing canaries', () => {
it('fails', async () => {
sinon.stub(fs, 'readFile')
.withArgs('/foo.json').resolves(JSON.stringify({
Dispatched: { TaskInfo: { Environment: { somekey: 'someval' } } },
}))
try {
await _checkCanaries()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.include('Missing MAIN_CANARY')
}
})
context('with no circleEnv', () => {
beforeEach(() => {
sinon.stub(fs, 'readFile')
.withArgs('/foo.json').resolves(JSON.stringify({
Dispatched: { TaskInfo: { Environment: {} } },
}))
})
it('passes', async () => {
await _checkCanaries()
})
it('fails if CACHE_VERSION does exist', async () => {
process.env.CACHE_VERSION = 'foo'
try {
await _checkCanaries()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.include('CACHE_VERSION is set, but circleEnv is empty')
}
})
})
})
it('passes with canaries', async () => {
sinon.stub(fs, 'readFile')
.withArgs('/foo.json').resolves(JSON.stringify({
Dispatched: { TaskInfo: { Environment: { MAIN_CANARY: 'true', CONTEXT_CANARY: 'true' } } },
}))
await _checkCanaries()
})
})
+91
View File
@@ -0,0 +1,91 @@
const fs = require('fs').promises
const sinon = require('sinon')
const { expect } = require('chai')
const { verifyMochaResults } = require('../verify-mocha-results')
describe('verify-mocha-results', () => {
let cachedEnv = { ...process.env }
if (process.platform === 'win32') {
// skip the rest of the tests
return it('fails on windows', async () => {
try {
await verifyMochaResults()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.equal('verifyMochaResults not supported on Windows')
}
})
}
afterEach(() => {
sinon.restore()
Object.assign(process.env, cachedEnv)
})
beforeEach(() => {
process.env.CIRCLE_INTERNAL_CONFIG = '/foo.json'
sinon.stub(fs, 'readFile')
.withArgs('/foo.json').resolves(JSON.stringify({
Dispatched: { TaskInfo: { Environment: { somekey: 'someval' } } },
}))
sinon.stub(fs, 'readdir').withArgs('/tmp/cypress/junit').resolves([
'report.xml',
])
})
it('does not fail with normal report', async () => {
fs.readFile
.withArgs('/tmp/cypress/junit/report.xml')
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">')
await verifyMochaResults()
})
context('env checking', () => {
it('checks for protected env and fails and removes results when found', async () => {
const spy = sinon.stub(fs, 'rm').withArgs('/tmp/cypress/junit', { recursive: true, force: true })
fs.readFile
.withArgs('/tmp/cypress/junit/report.xml')
.resolves('<testsuites name="foo" time="1" tests="10" failures="0">someval')
try {
await verifyMochaResults()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.include('somekey').and.not.include('someval')
expect(spy.getCalls().length).to.equal(1)
}
})
})
context('test result checking', () => {
it('checks for non-passing tests and fails when found', async () => {
fs.readFile
.withArgs('/tmp/cypress/junit/report.xml')
.resolves('<testsuites name="foo" time="1" tests="10" failures="3">')
try {
await verifyMochaResults()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.include('Expected the number of failures to be equal to 0')
}
})
it('checks for 0 tests run and fails when found', async () => {
fs.readFile
.withArgs('/tmp/cypress/junit/report.xml')
.resolves('<testsuites name="foo" time="1" tests="0" failures="0">')
try {
await verifyMochaResults()
throw new Error('should not reach')
} catch (err) {
expect(err.message).to.include('Expected the total number of tests to be >0')
}
})
})
})
+123
View File
@@ -0,0 +1,123 @@
/* eslint-disable no-console */
// this is a safety script to ensure that Mocha tests ran, by checking:
// 1. that there are N test results in the reports dir (or at least 1, if N is not set)
// 2. each of them contains 0 failures and >0 tests
// additionally, it checks that no secrets are in the reports, since CI does not scrub
// reports for environment variables
// usage: yarn verify:mocha:results <N>
const Bluebird = require('bluebird')
const fs = require('fs').promises
const la = require('lazy-ass')
const path = require('path')
const { readCircleEnv } = require('./circle-env')
const RESULT_REGEX = /<testsuites name="([^"]+)" time="([^"]+)" tests="([^"]+)" failures="([^"]+)"(?: skipped="([^"]+)"|)>/
const REPORTS_PATH = '/tmp/cypress/junit'
const expectedResultCount = Number(process.argv[process.argv.length - 1])
const parseResult = (xml) => {
const [name, time, tests, failures, skipped] = RESULT_REGEX.exec(xml).slice(1)
return {
name, time, tests: Number(tests), failures: Number(failures), skipped: Number(skipped || 0),
}
}
const total = { tests: 0, failures: 0, skipped: 0 }
console.log(`Looking for reports in ${REPORTS_PATH}`)
// some env is ok in reports. this is based off of what Circle doesn't mask in stdout:
// https://circleci.com/blog/keep-environment-variables-private-with-secret-masking/
function isWhitelistedEnv (key, value) {
return ['true', 'false', 'TRUE', 'FALSE'].includes(value)
|| ['nodejs_version', 'CF_DOMAIN'].includes(key)
|| value.length < 4
}
async function checkReportFile (filename, circleEnv) {
console.log(`Checking that ${filename} contains a valid report...`)
let xml; let result
try {
xml = await fs.readFile(path.join(REPORTS_PATH, filename))
} catch (err) {
throw new Error(`Unable to read the report in ${filename}: ${err.message}`)
}
try {
result = parseResult(xml)
} catch (err) {
throw new Error(`Error parsing result: ${err.message}. File contents:\n\n${xml}`)
}
const { name, time, tests, failures, skipped } = result
console.log(`Report parsed successfully. Name: ${name}\tTests ran: ${tests}\tFailing: ${failures}\tSkipped: ${skipped}\tTotal time: ${time}`)
la(tests > 0, 'Expected the total number of tests to be >0, but it was', tests, 'instead.')
la(failures === 0, 'Expected the number of failures to be equal to 0, but it was', failures, '. This stage should not have been reached. Check why the failed test stage did not cause this entire build to fail.')
for (const key in circleEnv) {
const value = circleEnv[key]
if (!isWhitelistedEnv(key, value) && xml.includes(value)) {
await fs.rm(REPORTS_PATH, { recursive: true, force: true })
throw new Error(`Report contained the value of ${key}, which is a CI environment variable. This means that a failing test is exposing environment variables. Test reports will not be persisted for this job.`)
}
}
total.tests += tests
total.failures += failures
total.skipped += skipped
}
async function checkReportFiles (filenames) {
let circleEnv
try {
circleEnv = await readCircleEnv()
} catch (err) {
// set SKIP_CIRCLE_ENV to bypass, for local development
if (!process.env.SKIP_CIRCLE_ENV) throw err
circleEnv = {}
}
await Bluebird.mapSeries(filenames, (f) => checkReportFile(f, circleEnv))
console.log('All reports are valid.')
console.log(`Total tests ran: ${total.tests}\tTotal failing: ${total.failures}\tTotal skipped: ${total.skipped}`)
}
async function verifyMochaResults () {
if (process.platform === 'win32') throw new Error('verifyMochaResults not supported on Windows')
try {
const filenames = await fs.readdir(REPORTS_PATH)
const resultCount = filenames.length
console.log(`Found ${resultCount} files in ${REPORTS_PATH}:`, filenames)
if (!expectedResultCount) {
console.log('Expecting at least 1 report...')
la(resultCount > 0, 'Expected at least 1 report, but found', resultCount, '. Verify that all tests ran as expected.')
} else {
console.log(`Expecting exactly ${expectedResultCount} reports...`)
la(expectedResultCount === resultCount, 'Expected', expectedResultCount, 'reports, but found', resultCount, '. Verify that all tests ran as expected.')
}
await checkReportFiles(filenames)
} catch (err) {
throw new Error(`Problem reading from ${REPORTS_PATH}: ${err.message}`)
}
}
if (require.main === module) verifyMochaResults()
module.exports = { verifyMochaResults }
-80
View File
@@ -1,80 +0,0 @@
/* eslint-disable no-console */
// this is a safety script to ensure that Mocha tests ran, by checking:
// 1. that there are N test results in the reports dir (or at least 1, if N is not set)
// 2. each of them contains 0 failures and >0 tests
// usage: yarn verify:mocha:results <N>
const Bluebird = require('bluebird')
const fse = Bluebird.promisifyAll(require('fs-extra'))
const la = require('lazy-ass')
const path = require('path')
const RESULT_REGEX = /<testsuites name="([^"]+)" time="([^"]+)" tests="([^"]+)" failures="([^"]+)"(?: skipped="([^"]+)"|)>/
const REPORTS_PATH = '/tmp/cypress/junit'
const expectedResultCount = Number(process.argv[process.argv.length - 1])
const parseResult = (xml) => {
const [name, time, tests, failures, skipped] = RESULT_REGEX.exec(xml).slice(1)
return {
name, time, tests: Number(tests), failures: Number(failures), skipped: Number(skipped || 0),
}
}
const total = { tests: 0, failures: 0, skipped: 0 }
console.log(`Looking for reports in ${REPORTS_PATH}`)
fse.readdir(REPORTS_PATH)
.catch((err) => {
throw new Error(`Problem reading from ${REPORTS_PATH}: ${err.message}`)
})
.then((files) => {
const resultCount = files.length
console.log(`Found ${resultCount} files in ${REPORTS_PATH}:`, files)
if (!expectedResultCount) {
console.log('Expecting at least 1 report...')
la(resultCount > 0, 'Expected at least 1 report, but found', resultCount, '. Verify that all tests ran as expected.')
} else {
console.log(`Expecting exactly ${expectedResultCount} reports...`)
la(expectedResultCount === resultCount, 'Expected', expectedResultCount, 'reports, but found', resultCount, '. Verify that all tests ran as expected.')
}
return Bluebird.mapSeries(files, (file) => {
console.log(`Checking that ${file} contains a valid report...`)
return fse.readFile(path.join(REPORTS_PATH, file))
.catch((err) => {
throw new Error(`Unable to read the report in ${file}: ${err.message}`)
})
.then((xml) => {
try {
return parseResult(xml)
} catch (err) {
throw new Error(`Error parsing result: ${err.message}. File contents:\n\n${xml}`)
}
})
.then(({ name, time, tests, failures, skipped }) => {
console.log(`Report parsed successfully. Name: ${name}\tTests ran: ${tests}\tFailing: ${failures}\tSkipped: ${skipped}\tTotal time: ${time}`)
la(tests > 0, 'Expected the total number of tests to be >0, but it was', tests, 'instead.')
la(failures === 0, 'Expected the number of failures to be equal to 0, but it was', failures, '. This stage should not have been reached. Check why the failed test stage did not cause this entire build to fail.')
total.tests += tests
total.failures += failures
total.skipped += skipped
})
})
})
.then(() => {
console.log('All reports are valid.')
console.log(`Total tests ran: ${total.tests}\tTotal failing: ${total.failures}\tTotal skipped: ${total.skipped}`)
})
.catch((err) => {
console.error(err)
process.exit(1)
})
@@ -7,15 +7,15 @@ exports['e2e cy.origin errors / captures the stack trace correctly for errors in
Cypress: 1.2.3
Browser: FooBrowser 88
Specs: 1 found (multi_domain_error_spec.ts)
Searched: cypress/integration/multi_domain_error_spec.ts
Specs: 1 found (cy_origin_error_spec.ts)
Searched: cypress/integration/cy_origin_error_spec.ts
Experiments: experimentalSessionAndOrigin=true
Running: multi_domain_error_spec.ts (1 of 1)
Running: cy_origin_error_spec.ts (1 of 1)
cy.origin
@@ -44,20 +44,20 @@ exports['e2e cy.origin errors / captures the stack trace correctly for errors in
Screenshots: 1
Video: true
Duration: X seconds
Spec Ran: multi_domain_error_spec.ts
Spec Ran: cy_origin_error_spec.ts
(Screenshots)
- /XXX/XXX/XXX/cypress/screenshots/multi_domain_error_spec.ts/cy.origin -- tries t (1280x720)
o find an element that doesn't exist and fails (failed).png
- /XXX/XXX/XXX/cypress/screenshots/cy_origin_error_spec.ts/cy.origin -- tries to f (1280x720)
ind an element that doesn't exist and fails (failed).png
(Video)
- Started processing: Compressing to 32 CRF
- Finished processing: /XXX/XXX/XXX/cypress/videos/multi_domain_error_spec.ts.mp4 (X second)
- Finished processing: /XXX/XXX/XXX/cypress/videos/cy_origin_error_spec.ts.mp4 (X second)
====================================================================================================
@@ -67,7 +67,7 @@ exports['e2e cy.origin errors / captures the stack trace correctly for errors in
Spec Tests Passing Failing Pending Skipped
multi_domain_error_spec.ts XX:XX 1 - 1 - -
cy_origin_error_spec.ts XX:XX 1 - 1 - -
1 of 1 failed (100%) XX:XX 1 - 1 - -

Some files were not shown because too many files have changed in this diff Show More