skip to content

A suite ported to Cypress kept its driver-shaped session helper. What does that cost?

level: seniorimportance: should knowfreq 46%

answer

  1. Ask what the wrapper is wrapping
  2. Who owns the browser in this runner?
  3. No binary, no session object, no quit
  4. Every spec file loads the bundle again
  5. A constructor parameter with nothing to pass

basics

~20 s

In Cypress there is no browser session object to wrap: Cypress launches and disposes of the browser itself. The wrapper's start and quit calls do nothing, anything it caches dies at the spec boundary, and its methods cannot return values.

solid answer

~50 s

A port to Cypress deletes browser lifecycle code rather than translating it. Cypress launches a browser it detects on the machine — `cypress info` lists them, `cypress run --browser chrome` picks one — and needs no driver binary, so there is no object for a helper to hold. Keeping the wrapper costs three things. Its `start()` and `quit()` become no-ops, or worse, hooks that redo work Cypress already performs between tests. State it caches on an instance does not survive the spec boundary, because the support file and the spec bundle are loaded again for every spec file; caching a signed-in state is what `cy.session()` is for. And its methods cannot hand a value back to the test, because `cy` commands are queued rather than executed inline. The honest port keeps the page-object classes, drops the constructor argument, and calls `cy` directly.

code

javascript · 24 lines
javascript
// cypress/support/pages/runDashboard.js
export class RunDashboard {
  visit() {
    cy.visit('/runs')
    return this
  }

  rerunFailed() {
    cy.contains('button', 'Re-run failed').click()
    return this
  }

  specRows() {
    return cy.get('[data-cy=spec-row]')
  }
}

// cypress/e2e/runs.cy.js
const dashboard = new RunDashboard()

it('queues the failed specs again', () => {
  dashboard.visit().rerunFailed()
  dashboard.specRows().should('have.length', 3)
})

go deeper

for a junior

Be ready to say that Cypress launches and closes the browser for you, so there is no driver object to create or quit. Knowing that beforeEach is for app setup rather than browser setup is the point to land.

for a middle

Explain what replaces each piece of the old plumbing: browser choice at the command line, launch options in a before:browser:launch handler, and page objects that call cy directly because there is nothing to pass into their constructor.

for a senior

Show that you would review a ported harness for abstractions that survived without a job, and be able to explain why an instance-level cache silently rebuilds itself in every spec file. That symptom only shows up as run time, not as a failure.

for a principal

Own the rule that decides which inherited layers live: a class that encodes application knowledge stays, one that encodes browser-session knowledge goes. Be ready to defend deleting a layer people are attached to.

## The driver is not replaced, it is removed Cypress runs your test code inside the browser rather than driving one over a wire, so there is no session object, no protocol client and no driver binary anywhere in the picture. `cypress info` prints the browsers Cypress detected on the machine, `cypress run --browser chrome` picks one for a run, and launch arguments and preferences go into a `before:browser:launch` handler inside `setupNodeEvents`. **Nothing on that list is a handle a test or a helper can hold.** The migration guidance is blunt about it: the browser lifecycle code is obsolete and should be removed rather than translated. What makes that hard in practice is not the deletion. It is that in a mature suite the driver handle has grown roots — it is a constructor parameter on every page object, a field on a base class, a per-worker value in the parallel harness, and the first argument of a dozen helpers. ## Why the wrapper survives the port A session wrapper is the last thing a port deletes because it never fails loudly. Ported to Cypress it compiles, the specs pass, and it goes on looking like architecture. What it is actually doing is: - holding nothing, because there is no session object to hold; - running `start()` and `quit()` methods that either do nothing or redo work Cypress already performs between tests; - taking a parameter every call site must still pass, and in TypeScript still declaring a type for something that no longer exists; - standing between the failing line and the test, so the Command Log entry and the stack frame both point at the wrapper rather than at the step that broke. ## The costs, concretely 1. **Dead lifecycle hooks.** A `before` that "starts the browser" and an `after` that "quits" it are noise at best. At worst the teardown clears state that Cypress's own per-test reset would have cleared anyway, and the suite ends up with two competing stories about when state is fresh. 2. **A cache that is not one.** State stored on a helper instance does not cross the spec boundary: Cypress loads the support file and then the spec bundle for **every spec file**, so a helper that "signs in once for the whole suite" signs in once per spec and nobody notices until somebody measures the run. `cy.session()` is the mechanism that genuinely caches, with `cacheAcrossSpecs` for the wider case. 3. **Methods that cannot return.** A wrapper method cannot hand a number or an element back to the test, because `cy` commands are queued rather than executed inline. Either the method yields the chain for the caller to continue, or the value is read inside a `.then()` where it is used. 4. **A wide blast radius.** Every test goes through the wrapper, so any change to it touches everything, and it is the first frame a reader has to skip past when a spec fails. ## What the harness looks like afterwards The page-object classes themselves usually survive the move: the pattern ports directly, and only the method bodies change from remote calls to `cy` commands. What changes is the plumbing around them. | Old harness element | After the port | |---|---| | driver creation in setup | deleted — Cypress launches the browser | | driver disposal in teardown | deleted — Cypress disposes of it | | driver passed into a page object | deleted — `cy` is a global | | driver binary management | deleted — `cypress info` reports what was detected | | browser options and preferences | a `before:browser:launch` handler in `setupNodeEvents` | | a signed-in state cached on the harness | `cy.session()` | | setup that put the app in a state | `beforeEach` with `cy.visit()`, `cy.request()`, `cy.task()` | | a repeated flow every spec needs | a custom command via `Cypress.Commands.add()` | `afterEach` does keep a job, but a narrower one: undoing things Cypress does not own, such as a record the test created through the API or a flag it flipped on the server. ## When keeping the old shape is right Not every inherited abstraction is dead weight. A helper that encodes **knowledge about the application** — how a run is queued, which statuses are terminal, what a valid spec row looks like — is worth keeping under any runner; it just stops taking a driver. A helper that encodes **knowledge about the browser session** was never yours to own once Cypress took the browser. That is the test to apply class by class, and it is a faster way through a large port than arguing about the pattern in the abstract.

  • What does teardown legitimately still do after a port to Cypress?
    Almost nothing browser-related. `afterEach` is left for things Cypress does not own — deleting a record the test created through `cy.request()` or `cy.task()`, or restoring a feature flag on the server. Anything that closed, reset or disposed of the browser goes, because Cypress already does that between tests.
  • A helper method needs the current run count for a later step. How does that shape change?
    It stops being a return value. The helper yields the chain and the caller continues it — `dashboard.specRows().should('have.length', 3)` — or the value is read inside a `.then()` at the point of use. A method that tries to hand a number back to synchronous test code hands back a chainable object instead.

saying these in an interview costs you the question

  • Translates driver setup and teardown instead of deleting it
  • Keeps a start and quit pair in before and after hooks
  • Expects a helper instance to cache state across spec files
  • Thinks a helper method can return an element to the test
  • Believes Cypress needs a driver binary per browser