In a Cypress suite, how far should you go to cover a flow that opens a new tab?
answer
- ask what the tab would prove
- the contract ends at the URL
- cheap coverage first, always
- a dependency needs a named owner
- write the boundary down once
basics
~20 sDecide by what the second tab would prove. Assert the handoff and request the URL when the destination is not yours; reach for the puppeteer plugin only when real value lives on the other side and no cheaper seam reaches it.
solid answer
~50 sAsk what following the tab proves that asserting it would not. A statements page's responsibility is to render the right `href`, ask for a new tab, and have the document served to the right account — all reachable with an attribute assertion plus a `cy.request()`, at almost no cost. What only a second tab proves is that a third-party viewer rendered a PDF, and that is the piece you do not own. So the tiers are: assert the handoff by default; add a request when you need the document proven; adopt `@cypress/puppeteer` only when both pages must be alive at once and no cheaper seam reaches the behaviour. That last tier is a beta, Chromium-only dependency whose failures do not read like Cypress failures, so it wants a named owner, small factual handlers, and assertions kept back in the spec.
go deeper
Know the cheap option exists and reach for it first: assert the link's target and href rather than trying to follow it anywhere.
Be able to lay out the tiers in order of cost and say what each one actually proves about the application.
Argue the case with the failure modes in mind: a beta, Chromium-only dependency adds a class of flake your on-call has to learn to read, and that has to be worth something.
Own the boundary itself — where your suite's responsibility ends, who owns the escape hatch, and what evidence would make you remove it again.
## Start from what the tab proves, not from what it costs The reflex when a bank statement's **Open PDF** link opens a new tab is to ask "how do I follow it in Cypress?". That is the second question. The first is: *what would following it prove that asserting it would not?* Break the flow into the pieces that can fail: - The statements page renders the right destination for the right statement. - The browser is asked to open it in a new tab, with the right `rel` attributes. - The document is served, with the right status and content type, to the right account. - The third-party viewer renders a PDF. Only the last piece needs a second tab, and it is the piece your team does not own. If the viewer is a third-party product, the contract your suite is responsible for ends at the URL you hand over. Saying that clearly is most of the answer. ## Three tiers, and what each costs to keep | tier | what it costs | what it buys | |---|---|---| | assert `target` and `href` | one assertion, brittle only to your own markup | the handoff contract, on every statement row, in milliseconds | | add `cy.request()` on the href | one more call, needs the fixture or account to exist | the document is really served, with the status you expect | | drive the tab with `@cypress/puppeteer` | a beta 0.x dependency, Chromium-only, Node-side code, its own retry tuning, and failures that do not read like Cypress failures | proof that the second page actually did something | The gradient is steep. The first tier is nearly free and covers the majority of real regressions — a broken statement id, a stale route, a missing `noopener`. The third tier costs a dependency, a second mental model, and a class of flake your team has to learn to read. ## Signals the plugin is warranted Reach for it when the value genuinely lives across the boundary and no cheaper seam gets there: - **Both pages must be alive at once.** A token minted in the first tab is spent in the second, or the viewer posts a message back that the statements page reacts to. - **You own the destination and it is only reachable this way.** If the destination is your own page and reachable by URL, a separate spec that visits it directly is cheaper and reads better. - **The regression you are covering has actually happened.** A plugin adopted for a hypothetical is a dependency nobody remembers the reason for. - **Your browser matrix already fits.** The plugin runs on Chromium-family browsers only, so a suite that must also pass elsewhere needs a plan for those runs, not a skipped test that quietly becomes permanent. ## Signals it is not - The destination is a third-party product. You end up asserting somebody else's markup, and their release breaks your build. - You are reaching for it because "the click did not do anything". That is the attribute assertion's job, done more cheaply. - The behaviour is reachable from JavaScript. If the app calls `window.open()`, stubbing it in `cy.visit()`'s `onBeforeLoad` proves the exact URL handed over, without a tab, a plugin, or a dependency. - Nobody on the team wants to own the Node-side handler. Code that only one person can debug is a liability in a suite everybody has to keep green. ## Making the decision hold 1. **Write the boundary down once**, next to the suite: our specs assert the handoff; the viewer has its own coverage. A rule that lives in one person's head gets relitigated on every pull request. 2. **Give the escape hatch one owner and one shape.** If the plugin is in, keep the handlers small and factual and put every assertion back in the spec, so a failure still reads like a Cypress failure to the person on call. 3. **Revisit when the ceiling stops being the binding constraint.** If half the suite is queueing behind Node-side tab handlers, the honest conclusion is not "tune the handlers" — it is that the flow shape and the coverage plan need to change. ## The answer an interviewer is listening for Not a technique, but a boundary. A strong answer names what the app is actually responsible for, buys the cheap coverage for all of it, and treats the second tab as a deliberate, justified exception with a named owner — rather than as the default response to any flow that opens one.
- The destination page is one of your own routes. Does that change the decision?It usually makes the plugin unnecessary. If you own the page and it is reachable by URL, a separate spec that visits it directly is cheaper, faster and easier to debug than driving it from a Node-side handler. Keep the attribute assertion on the link so the handoff is still covered, and let the destination have its own spec.
- How would you know later that adopting the puppeteer plugin was the wrong call?Watch who touches it. If handler changes always land on the same one person, if failures get triaged as "the puppeteer thing again" rather than as product bugs, or if the tests it enables never catch a real regression, the dependency is costing more than it returns and the coverage belongs back at the handoff.
saying these in an interview costs you the question
- Treats driving the second tab as the default answer
- Adopts the plugin without naming a regression it catches
- Asserts third-party viewer markup as if the team owned it
- Ignores that the plugin is Chromium-family only
- Leaves a skipped tab test in place indefinitely