* fix: update newProject ref when switching between organizations in SelectCloudProjectModal (#25730) * chore: debug page tooltip distance and artifact border (#25727) * misc: debug page tooltip distance and artifact border * add changelog entry * fix CT test * fix: Improve error handling around calls to `this.next` in middleware (#25702) * chore: update changelog validation example (#25742) * misc: improve debug loading text wrap responsiveness (#25703) * misc: Increase max failures in IATR badge to 99 (#25737) * chore: exclude collaborator issues/PRs from triage project (#25769) * feat: add --auto-cancel-after-failures flag (#25237) Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: Matt Schile <mschile@cypress.io> Co-authored-by: Ryan Pei <ryanppei@gmail.com> Co-authored-by: Emily Rohrbough <emilyrohrbough@yahoo.com> * chore: Update v8 snapshot cache (#25592) * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * Update update_v8_snapshot_cache.yml * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache --------- Co-authored-by: cypress-bot[bot] <2f0651858c6e38e0+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: cypress-bot[bot] <47117332+cypress-bot[bot]@users.noreply.github.com> * fix: implement new graphql fields for spec counts (#25757) Co-authored-by: Stokes Player <stokes@cypress.io> Co-authored-by: Mike Plummer <mike-plummer@users.noreply.github.com> * feat: Bundle cy.origin() dependencies at runtime (#25626) Co-authored-by: cypress-bot[bot] <2f0651858c6e38e0+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io> * chore: remove zenhub from release process (#25701) Co-authored-by: Matt Schile <mschile@cypress.io> * feat: add Cypress.Commands.overwriteQuery (#25674) * feat: add Cypress.Commands.overwriteQuery Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: Zach Bloomquist <git@chary.us> * fix: spawn child process with process.env in macOS arm64 (#25753) Co-authored-by: Matt Schile <mschile@cypress.io> Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: Zach Bloomquist <github@chary.us> * chore: lint system tests in CI (#25673) * fix: Suppress filesystem errors during glob search (#25774) * chore: issue with ts-loader missing in binary and problematic esbuild norewrite construct (#25797) * chore: update changelog linting (#25809) * docs(guides): add more detail to code-signing (#25794) Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> * chore: update workflows.yml to include the v8 snapshot update branch (#25784) Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> * chore: internal request preflight (#25772) --------- Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: cypress-bot[bot] <2f0651858c6e38e0+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: Matt Henkes <mjhenkes@gmail.com> Co-authored-by: Zach Bloomquist <git@chary.us> * chore: bump for 12.6.0 release (#25812) * chore: release @cypress/webpack-batteries-included-preprocessor-v2.4.0 [skip ci] * chore: release @cypress/webpack-preprocessor-v5.17.0 [skip ci] * test: skip flaky GitDataSource test (#25825) * chore: making our add-to-triage-board workflow reusable within the Cypress-io org (#25820) * chore: Making our add to triage workflow callable from other projects inside the Cypress-io org in Github * chore: updated cypress-example-kitchensink version (#25828) * fix: duplicate and expired cookies (#25761) * chore: add regression tests for duplicate cookies and bad expiry times * avoid prepending domain with dot for cookies that are set with the server side jar. This is to avoid the cookie being duplicated if it is set or overridden in a different context (request that can actually set the cookie or via document.domain) * feat: use cookie.toString() in the cookie patch to more accurately set cookies on the document, which should include other properties besides key=value * fix: add logic to handle expired cookies in the document.cookie patch, as well as in CDP * chore: build binary for cookie fixes for users to test * chore: change name of fixture to something more accurate * chore: comment why we are using the toughCookie toString method in the patch * [run ci] * chore: add changelog entry * [run ci] * fix: revert back to key=value when getting document.cookie as those are the only values are displayed (oversight on my end) * [run ci] * chore: make compatible with cypress.require * fix: add tests for hostOnly/non hostOnly cookies to make sure property gets sent up to automation client correctly. No longer need custom cookie prop to determine destination * [run ci] * fix: stale unit test * chore: adjust comments * [run ci] * fix: bad domain logic * [run ci] * chore: remove irrelevant comment * [run ci] * fix: adjust cookie login text to spec hostOnly cookie within the cookie patch. This should yield the same behavior as we are bound to same origin within the spec bridge * [run ci] * [run ci] * fix: allow for cookies on request of same key to take precedence over cookies in the jar, regardless of how many hierachy cookies exist in the jar * chore: fix cookie misc tests for cy.origin (dont run cy.origin) * [run ci] * chore: skip misc cookie tests in webkit as headless behavior doesn't clear cookies between tests correctly * Revert "fix: allow for cookies on request of same key to take precedence over cookies in the jar, regardless of how many hierachy cookies exist in the jar" This reverts commit17de1883ab. * [run ci] * chore: split changelog entry into two parts * chore: update logic to remove else statement and add comments * [run ci] * chore: readd windows snapshot branch in workflows * [run ci] * chore: fix workflows from bad merge * [run ci] * Revert "chore: split changelog entry into two parts" This reverts commit4352ef5f3e. * [run ci] * fix: Fix type definitions for cy.reload() (#25779) Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> * misc: Debug header updates (#25823) * fix: allow running tests outside Vite project root folder (#25801) * fix: allow running tests outside Vite project root folder * update snapshots * add changelog entry --------- Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> * fix: mount component in [data-cy-root] (#25807) * fix(angular): mount component in [data-cy-root] * fix e2e test * add changelog entry * changelog [skip ci] * changelog --------- Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> * chore: updating add to triage baord github action to use org secret (#25868) * chore: updating add to triage board github action to use org secret * chore: release @cypress/angular-v2.0.2 [skip ci] * chore: release @cypress/vite-dev-server-v5.0.3 [skip ci] * chore: Update v8 snapshot cache (#25822) Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io> * feat: support host only cookies (#25853) * feat: allow setCookie API to take a hostOnly option * chore: add jsdoc/typescript description to render to users * chore: add changelog entry * [run ci] * chore: fix types * chore: fix cookie login tests * chore: update e2e cookie system tests * [run ci] * chore: fix cookie command tests. localhost cookies are calculated as hostOnly, which is consistent with how cypress works today * chore: fix system tests for cookies. * [run ci] * chore: fix system tests * chore: skip hostOnly assertions in webkit (for now) * [run ci] * chore: add property definitions to setCookieOptions * [run ci] * chore: add comments to hostOnly prop in firefox when setting a cookie * fix(webpack-dev-server): touch component-index during onSpecsChange to avoid writing to app file (#25861) * testing: try disabling uTimesSync and see what happens * build binaries [run ci] * fix: touch component index file instead of browser.js * build binaries [run ci] * update test * fix test * add test for custom HTML file in config * use existing component index in webpack-dev-server unit tests --------- Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> * chore: release @cypress/webpack-dev-server-v3.2.4 [skip ci] * chore: improve types for server automation cookie client (#25836) * chore: improve types for automation cookies * [run ci] * fix: the cookie_behavior tests by syncing cookies immediately if … (#25855) * fix: fix the cookie_behavior tests by syncing cookies immediately if the application is already stable * chore: add changelog entry * [run ci] * chore: address comments from code review * feat: Public API for CT Framework Definitions (#25780) * chore: rework component onboarding in launchpad (#25713) * chore: refactoring and types * rework source of frameworks * revert rename * fix tests * fix more tests * types * update code * use same public API internally * rename interfaces * rename * work on dev server api * fix types * fix test * attempt to support getDevServerConfig * tests * add function to define framework [skip ci] * rework a lot of types * fix test * update tests and types * refactor * revert changes * lint * fix test * revert * remove * add "community" label [skip ci] * refactor * types * lint * fix bug * update function name * address feedback * improve types with Pick * refactor using type guard * correct label --------- Co-authored-by: Zachary Williams <ZachJW34@gmail.com> * chore: typing error * feat: scan for 3rd party ct plugins (#25749) * chore: refactoring and types * rework source of frameworks * revert rename * fix tests * fix more tests * types * update code * use same public API internally * rename interfaces * rename * work on dev server api * fix types * fix test * attempt to support getDevServerConfig * tests * add function to define framework [skip ci] * rework a lot of types * fix test * update tests and types * refactor * revert changes * lint * fix test * revert * remove * add "community" label [skip ci] * refactor * types * lint * fix bug * update function name * address feedback * feat: scan for 3rd party ct plugins * add e2e test * unit tests [run ci] * tweak resolution * rebase, address comments * fix windows paths * remove .gitignore * fix test --------- Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com> * lint config * spacing * try fix race cond * fix import error * build binary * try update snapshot * try using require * support namespaced definitions (#25804) * remove category * add icon prop * support esm -> cjs compiled typescript * fix test * misc: add CTA footer to launchpad framework dropdown (#25831) * remove test project dependencies * rebase * windows * windows again * add changelog entry * changelog * revert workflow * remove worklfow --------- Co-authored-by: Zachary Williams <ZachJW34@gmail.com> Co-authored-by: Adam Stone-Lord <adams@cypress.io> * chore: release @cypress/webpack-dev-server-v3.3.0 [skip ci] * fix: Add missing error message when `req.continue` is used incorrectly (#25884) --------- Co-authored-by: Adam Stone-Lord <adams@cypress.io> Co-authored-by: Zachary Williams <ZachJW34@gmail.com> Co-authored-by: Mike Plummer <mike-plummer@users.noreply.github.com> Co-authored-by: Matt Schile <mschile@cypress.io> Co-authored-by: Alejandro Estrada <estrada9166@gmail.com> Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com> Co-authored-by: Ryan Pei <ryanppei@gmail.com> Co-authored-by: Emily Rohrbough <emilyrohrbough@yahoo.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: cypress-bot[bot] <2f0651858c6e38e0+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Ryan Manuel <ryanm@cypress.io> Co-authored-by: cypress-bot[bot] <47117332+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Mark Noonan <mark@cypress.io> Co-authored-by: Stokes Player <stokes@cypress.io> Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com> Co-authored-by: Zach Bloomquist <git@chary.us> Co-authored-by: willmsC <50909991+willmsC@users.noreply.github.com> Co-authored-by: Zach Bloomquist <github@chary.us> Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> Co-authored-by: Tim Griesser <tgriesser10@gmail.com> Co-authored-by: Matt Henkes <mjhenkes@gmail.com> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Ben M <benm@cypress.io> Co-authored-by: Bill Glesias <bglesias@gmail.com> Co-authored-by: Podles <78863563+podlesny-j@users.noreply.github.com> Co-authored-by: Paolo Caleffi <p.caleffi@dreamonkey.com> Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
14 KiB
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.js’s URL doc for a handy breakdown of URL parts
- domain: The hostname portion of a URL. (e.g.
example.com,www.example.com,www.example.co.uk,localhost) - superdomain: A domain without the subdomain. (e.g.
example.com,example.co.uk,localhost) - origin: The combination of the protocol, hostname, and port of a URL (e.g.
http://www.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
- AUT: App Under Test - the user's app currently being tested
- AUT frame: The iframe used to run the user's app
- spec frame: The iframe used to run the spec file
Frame architecture (single origin)
When testing a single origin, all 3 frames are loaded on that origin.
Components:
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)
Let’s 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.
The spec bridge remains in the DOM from the moment it’s created until the browser is refreshed or closed, the same as the the spec frame and AUT frame.
Here’s what the components look like now:
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() 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.
The callback function the user passes to cy.origin() is stringified by the primary driver, sent to the origin-matching secondary driver via postMessage, then evaluated. Its commands are run by the secondary driver, since it can communicate synchronously with the cross-origin AUT.
Cross-origin communication
Communication between the primary driver and any secondary drivers occurs through the postMessage API. 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. 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.
For more information, please see the Cross-origin Serialization Explainer
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, it’s 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 recognize that it’s not the primary origin and inject code into the request that’s tailored to cross-origin testing.
Cross-origin navigation timing
It’s possible for the user’s test to navigate to a different origin in a couple different ways.
Action-triggered navigation
An action causes the navigation (click, submit, etc).
If a user wishes to utilize the window:before:load or window:load events, they must register listeners before the navigation. This means they need to run cy.origin() before any navigation-causing actions.
// the app hasn't navigated to domain2.com at this point, but we need to
// set up listeners before the navigation happens and the page loads
cy.origin('http://domain2.com', () => {
cy.on('window:before:load', (win) => {
// do something before page load
})
cy.on('window:load', (win) => {
// do something after page load
})
})
cy.visit('http://domain1.com')
cy.get('a[href=http://domain2.com]').click() // <- navigates to domain2.com
cy.origin('http://domain2.com', () => {
// further testing in domain2.com
})
Explicit cy.visit()
The user explicitly navigates via cy.visit().
The visit can exist before the cy.origin() call or inside its callback. However, if before cy.origin(), it can't use the onBeforeLoad or onLoad callbacks that cy.visit() can accept because its not possible to serialize and send the window object to them.
cy.visit('http://domain1.com')
cy.visit('http://domain2.com', { // <- cross-origin visit
onBeforeLoad (win) {}, // defining either of these here results
onLoad (win) {}, // in an error
})
cy.visit('http://domain1.com')
cy.origin('http://domain2.com', () => {
cy.visit('http://domain2.com', { // <- cross-origin visit
onBeforeLoad (win) {}, // these work just fine when inside
onLoad (win) {}, // the cross-origin callback
})
})
Visiting inside the callback also allows setting up window:before:load and window:load listeners in the same callback, since it will ensure the listeners are registered before the page loads.
cy.visit('http://domain1.com')
cy.origin('http://domain2.com', () => {
cy.on('window:before:load', (win) => {
// do something before page load
})
cy.on('window:load', (win) => {
// do something after page load
})
cy.visit('http://domain2.com')
})
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 it’s 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 it’s 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.
It’s 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 consistency’s 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 that would normally be set when a user's app is run outside of Cypress are not set due to it being rendered in an iframe.
In order to counteract this, we utilize the proxy to capture cookies from cross-origin responses, store them in our own server-side cookie jar, set them in the browser with automation, and then attach them to cross-origin requests where appropriate. This simulates how cookies behave outside of Cypress. We also patch document.cookie client-side to simulate how it would behave outside of Cypress.
We patch XMLHttpRequest and fetch client-side to capture their withCredentials and credentials values (respectively), since they influence whether or not cookies should be attached to requests.
Dependencies
Users can utilize Cypress.require() to include dependencies. It's functionally the same as the CommonJS require(). At runtime, before evaluating the cy.origin() callback, we send it to the server, replace references to Cypress.require() with require(), and run it through the default preprocessor (currently webpack) to bundle any dependencies with it. We send that bundle back to the cross-origin driver and evaluate it.
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 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 it’s necessary, we may implement support for it.
cy.intercept()
Most use-cases for cy.intercept() can be accomplished by using it outside of the cy.origin() callback. Since there may be use-cases where setting up a response, for example, requires the scope within the cy.origin() callback, we will likely add support for cy.intercept() in the future.