lecture 14 of 160 completed

The walls, and what to do when you hit one

Multiple tabs, multiple origins, downloads, iframes. A senior names the wall accurately and decides what to do about it.

The first lecture in this track explained why Cypress has walls. This one is the practical list, and more importantly, what to do at each one.

The senior behaviour being taught here is not memorising workarounds. It is naming the limit accurately, choosing deliberately, and being able to say "this is the wrong tool for this test" without it being an attack on the tool.

Multiple tabs. Not supported, and will not be.

A test cannot control a second tab, because a tab cannot control another tab.

What to do: check the link's target instead of following it (should('have.attr', 'target', '_blank')). If the new tab's content matters, remove the target attribute in the test so it opens in place, which is slightly artificial and usually acceptable. If the flow really does require two tabs interacting, use Playwright for that test. One test in another tool is a normal engineering decision, not a defeat.

Multiple origins. Supported through cy.origin, with real constraints.

Covered in the auth lecture. Workable for a small number of tests, painful as a general pattern.

Native browser dialogs. Partially supported.

Cypress auto-accepts window.confirm and gives you an event to assert on the message. window.prompt needs stubbing. The file picker cannot be opened at all; you attach the file to the input directly, which you already know.

Downloads. Workable, awkward.

Cypress can read the downloaded file from disk with cy.readFile. It is inconsistent across browsers and headless modes. For anything critical, checking the export endpoint with cy.request is steadier and tests the same logic.

Iframes. No first-class support, and a real workaround exists.

Cypress cannot chain into an iframe directly. Same-origin frames can be reached:

cy.get('iframe[title="Card details"]')
  .its('0.contentDocument.body')
  .should('not.be.empty')
  .then(cy.wrap)
  .find('[data-testid="card-number"]')
  .type('4111111111111111');

Cross-origin frames (a real Stripe field, for instance) cannot be entered at all. That is the same-origin policy again, and no plugin can change it. Teams testing payment flows usually use the provider's test mode with a stubbed frame, or accept that this one journey is covered by a different tool.

Hovering that depends on real mouse events. Cypress dispatches synthetic events. Most hover states work. CSS-only :hover effects and anything requiring true native input may not.

Now the decision framework, which is the actual content of this lecture.

When you hit a wall, work through four questions in order.

One: is the test valuable enough to justify a workaround? Some are not. A test for a link opening in a new tab is usually satisfied by checking the attribute.

Two: is there a supported workaround with acceptable cost? cy.origin for one login test, yes. cy.origin for two hundred tests, no.

Three: can the test move to a cheaper level? Iframe content is often testable as a component test, or the underlying behaviour as an API test. Moving down is frequently better than fighting sideways.

Four: should this specific test use a different tool? Sometimes yes. A handful of Playwright tests alongside a Cypress suite is a legitimate architecture, not a failure. The cost is a second toolchain to maintain, and that cost is real, so it should be a decision with a named owner rather than something that happens by accident.

What not to do, and I have watched both: spend two weeks fighting a limitation nobody documented for you, or quietly delete the test and let the coverage gap go unrecorded. If you decide a journey cannot be automated in Cypress, write that down where the team can see it. An unrecorded gap is worse than a known one.

Remember this: name the wall, choose deliberately among the four options, and record the decision.

Reference: Trade-offs.