How Cypress Handles the Application Under Test (AUT) Iframe and Cross-Origin Communication
Cypress isolates the tested application inside a dedicated AUT iframe that communicates bidirectionally with the test runner via a spec bridge and postMessage, using the X-Cypress-Is-AUT-Frame header to manage cross-origin security boundaries.
The cypress-io/cypress repository implements a sophisticated iframe-based architecture to sandbox the Application Under Test (AUT) while maintaining robust control over cross-origin navigation and DOM manipulation. This design allows Cypress to test applications running on different domains without violating browser security policies, leveraging specific message protocols and custom HTTP headers to coordinate between the runner and the isolated iframe.
AUT Iframe Architecture and Isolation
Cypress creates a strict boundary between its own UI and the application being tested by mounting the AUT inside a separate iframe element. This isolation prevents style collisions and JavaScript conflicts while enabling the driver to inject necessary automation scripts.
Creating the AUT Iframe
When a test begins, the AutIframe class defined in packages/app/src/runner/aut-iframe.ts instantiates an iframe with the ID aut-iframe (or aut-iframes-container for multi-frame scenarios). The runner serves the iframe content from the static HTML template located at packages/server/lib/html/iframe.html. The packages/driver/src/cypress.ts file coordinates this instantiation, ensuring the iframe is appended to the runner's DOM only after the Cypress server is ready to proxy requests.
The AutIframe class handles mounting, remounting, and tearing down the iframe between tests, maintaining a clean state for each spec without reloading the entire runner window.
The Spec Bridge Injection
To establish communication between the runner and the AUT, Cypress injects a script known as the Spec Bridge into the iframe. This bridge, defined in packages/server/lib/html/spec-bridge-iframe.html, exposes a Cypress object on the AUT's window and establishes a connection to window.parent via postMessage. The bridge acts as a proxy, forwarding commands from the test code (cy.* methods) into the isolated iframe and returning results back to the runner.
Cross-Origin Communication Protocol
Because the AUT frequently runs on a different origin than the Cypress UI (e.g., localhost:3000 vs. localhost:8080), the framework must navigate Same-Origin Policy restrictions while maintaining control over navigation and network stubbing.
The X-Cypress-Is-AUT-Frame Header
Cypress identifies requests originating from the AUT iframe by injecting a custom HTTP request header: X-Cypress-Is-AUT-Frame. According to the source code in packages/server/test/unit/browsers/chrome_spec.ts, this header is added to every request initiated by resources loaded inside the AUT iframe. Before proxying the request to the actual target server, Cypress middleware strips this internal header to prevent leakage to external systems while using it internally to route responses correctly through the automation proxy.
Message Passing via postMessage
Once both the runner and the AUT iframe are loaded, they exchange JSON messages via window.postMessage. The server-side controller in packages/server/lib/controllers/iframes.ts implements handlers for specific protocol messages including:
get:aut:url– Retrieves the current URL of the AUT iframereload:aut:frame– Triggers a reload of the AUT iframenavigate:aut:history– Manipulates the history API within the AUTget:aut:title– Fetches the document title from the AUT
These messages allow the driver to control navigation and state without direct access to cross-origin contentDocument properties.
Security Boundaries and DOM Access
Cypress maintains security boundaries by restricting DOM manipulation to the iframe's content document only when same-origin policies permit, or by using the spec bridge for cross-origin interactions. Test commands resolve against the AUT iframe's document rather than the runner's DOM.
// Example: Accessing the AUT iframe from test code
cy.get('iframe.aut-iframe')
.its('0.contentDocument.body')
.then(cy.wrap) // Wrap the AUT's body for Cypress commands
.within(() => {
cy.get('#login').type('admin')
cy.get('button.submit').click()
})
// Internal message passing to the AUT (simplified from controllers/iframes.ts)
window.top.postMessage({
event: 'get:aut:url',
data: {}
}, '*')
// Server-side header handling (conceptual implementation)
if (req.headers['x-cypress-is-aut-frame']) {
// Strip the internal header before proxying
delete req.headers['x-cypress-is-aut-frame']
}
proxy.web(req, res, { target: targetUrl })
Summary
- Isolation: Cypress mounts the AUT in a dedicated iframe (
aut-iframe) managed by theAutIframeclass inpackages/app/src/runner/aut-iframe.ts, using templates frompackages/server/lib/html/iframe.html. - Bridge: The Spec Bridge injected via
packages/server/lib/html/spec-bridge-iframe.htmlexposes the Cypress API inside the AUT and handlespostMessagecommunication. - Cross-Origin: The
X-Cypress-Is-AUT-Frameheader identifies AUT requests for special handling, as tested inpackages/server/test/unit/browsers/chrome_spec.ts. - Protocol: Messages like
get:aut:urlandreload:aut:frameare processed bypackages/server/lib/controllers/iframes.tsto control navigation without violating same-origin policies. - Security: Direct DOM access is scoped to the iframe's
contentDocument, with cross-origin interactions mediated through the message bridge.
Frequently Asked Questions
How does Cypress prevent the AUT iframe from accessing the runner's DOM?
Cypress runs the AUT in a sandboxed iframe with a different origin or strict isolation context. The spec bridge only exposes specific postMessage handlers rather than the full parent window, preventing the AUT from traversing up to window.parent and accessing Cypress internals. Additionally, the runner's UI runs in a separate JavaScript context from the AUT iframe's content.
What is the purpose of the X-Cypress-Is-AUT-Frame header?
This header allows the Cypress proxy server to distinguish requests made by resources inside the AUT iframe from requests made by the runner itself. According to the cypress-io/cypress source code, the header is added by the browser automation layer and then stripped by the server middleware before forwarding to the target application, ensuring the header never reaches external servers while enabling internal routing logic.
How does Cypress handle cross-origin navigation in the AUT iframe?
When the AUT navigates to a different origin, Cypress intercepts the navigation via the navigate:aut:history message protocol implemented in packages/server/lib/controllers/iframes.ts. The driver communicates with the AUT through the spec bridge using postMessage rather than direct contentWindow access, bypassing same-origin restrictions that would otherwise block cross-domain iframe manipulation.
Can I access elements inside the AUT iframe directly from my test code?
Yes, but only through Cypress commands that internally resolve to the iframe's content. You should use cy.get() to target the iframe, then use .its('0.contentDocument.body') and cy.wrap() to scope subsequent commands within the AUT's DOM, as shown in the code examples above. This pattern ensures Cypress's automatic waiting and retry logic remains active while respecting the iframe boundary.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →