A Cypress Alternative for Tests That Open New Tabs

· TestDriver Team

A Cypress alternative that follows links into real new tabs with plain-language steps instead of selectors.

Above: the test below running against a public practice page, sped up with idle time removed.

You can replace Cypress scripts with tests that describe each step in plain language, including links that open a new tab. TestDriver matches each step against a screenshot, so:

  • you test the new tab your users actually see, instead of forcing the link to stay in one tab
  • you don’t write or maintain selectors
  • your test reads like the steps you’d give a teammate

TestDriver generates and runs black-box end-to-end tests. It starts your app in a fresh virtual machine, looks at the screen and uses a real mouse and keyboard, so it never needs selectors or access to your code. Each test is a normal Vitest file, and every run is recorded as a replay.

The example uses the-internet.herokuapp.com/windows, a public practice page whose link opens a new tab. The test clicks it, checks the new tab, then switches back. The full project is on GitHub: testdriverai/example-cypress-alternative.

Cypress vs. TestDriver

Cypress runs inside the browser and drives one tab. For multiple tabs, its trade-offs page points to the separate @cypress/puppeteer plugin. The common workaround is to remove the link’s target attribute so the new page loads in the same tab:

// Cypress (illustrative, not run)
cy.visit("https://the-internet.herokuapp.com/windows");
cy.get('a[href="/windows/new"]').invoke("removeAttr", "target").click();
cy.get("h3").should("have.text", "New Window");

The same steps from the TestDriver test that ran:

await testdriver.find("the Click Here link below the Opening a new window heading").click();
await testdriver.find("the New Window heading", { timeout: 15000 });
await testdriver.assert("the browser shows a second tab and the page says New Window");
await testdriver.find("the first browser tab, titled The Internet, in the tab strip").click();

The Cypress workaround is quick, and it’s enough when you only care about the page the link points to. It depends on a CSS selector, and it changes the page so the new tab never opens. The plugin route adds a second API inside your test. TestDriver depends on what’s on screen, so it clicks the real tab strip like a user. For a broader comparison, see Cypress alternatives.

TestDriver tests are JavaScript, like Cypress tests, so the language stays the same.

Watch the full replay

The replay shows every step with its screenshot and log, including both tabs in the tab strip.

The test

/**
 * Clicks a link that opens a new browser tab, checks the new tab's content,
 * then switches back to the first tab the way a user would.
 */
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/vitest/hooks";

describe("New tab (Cypress alternative)", () => {
  it("follows a link into a new tab and comes back", async (context) => {
    const testdriver = TestDriver(context);

    // Open the practice page in Chrome.
    await testdriver.provision.chrome({ url: "https://the-internet.herokuapp.com/windows" });

    // Click the link that opens a new tab.
    await testdriver.find("the Click Here link below the Opening a new window heading", { timeout: 30000 }).click();

    // Wait for the new tab, then check what it shows.
    await testdriver.find("the New Window heading", { timeout: 15000 });
    const newTab = await testdriver.assert(
      "the browser shows a second tab and the page says New Window",
    );
    expect(newTab).toBeTruthy();

    // Go back to the first tab.
    await testdriver.find("the first browser tab, titled The Internet, in the tab strip").click();

    // Check the original page is showing again.
    await testdriver.find("the Opening a new window heading", { timeout: 15000 });
    const firstTab = await testdriver.assert(
      "the page shows the Opening a new window heading with the Click Here link",
    );
    expect(firstTab).toBeTruthy();

    // Hold the final screen so the end of the replay shows the result.
    await testdriver.wait(3000);
  });
});

How it works

Open the page

provision.chrome() opens the page in a real Chrome window in a Linux sandbox. The link opens its tab the normal way, because nothing in the page is changed.

Describe elements by what users see

Each find() names the label and where it sits, like “the first browser tab, titled The Internet, in the tab strip”. The tab strip is part of the browser, not the page, and TestDriver finds it the same way. See locating elements.

Wait for results, then assert

find() with a timeout keeps looking until the new tab’s heading appears, so there are no fixed sleeps. assert() checks a plain-language statement against the screen and returns true or false. See making assertions.

Run it yourself

Clone the example repo, or set up TestDriver in your own project and run the test:

npx testdriverai init
npx vitest run cypress-alternative.test.mjs

To point it at your own app:

  • Change the url in provision.chrome() to the page with your link.
  • Rewrite the find() descriptions to match your link text and your new tab’s heading.
  • Add an assert() for what each tab should show, such as a signed-in name or a receipt.

FAQ

Do I need selectors or test IDs? No. Steps are matched on screenshots, so there are no IDs or data-testid attributes to add or keep in sync.

Can I keep my Cypress tests? Yes. Both are JavaScript, so they can live in the same repo. Start with the flows that need workarounds in Cypress, like new tabs and pop-up windows, and keep the rest.

Can I run it in CI? Yes. It’s a normal Vitest file and the browser runs in TestDriver’s cloud sandbox. See CI/CD.

Automate and scale manual testing with AI

TestDriver uses computer-use AI to test any app - write tests in plain English and run them anywhere.