Docs

Run Playwright tests

Point Playwright at the real, signed-in Chrome of a person or a test machine. One package, two variables, and the rest is Playwright as you already write it.

Before you start

Three things have to be true:

  • A business account with CDP on its plan. Personal accounts do not have it. Add CDP browsers on the Billing page.
  • CDP switched on for the browser. An admin opens the browser from the Browsers page and presses Switch CDP on. Each browser with it on uses one of your CDP slots.
  • A token that can use that browser. The same kind of token your AI apps use, from the Tokens page. A browser shared with you to view only will not work.

Quickstart

Install @tabtunnel/playwright. It connects Playwright to your browser in place of launching a local Chromium, and handles signing in with your token.

terminal
pnpm add -D @tabtunnel/playwright @playwright/test
# or: npm i -D @tabtunnel/playwright @playwright/test

Set two variables. The token is the one from the dashboard's Tokens page.

terminal
export TABTUNNEL_URL=https://tabtunnel.dev
export TABTUNNEL_TOKEN=tt_live_…

Then import test from here instead of from @playwright/test, and write the test you would have written anyway:

example.spec.ts
import { test, expect } from '@tabtunnel/playwright/test'

test('the dashboard loads', async ({ page }) => {
  await page.goto('https://app.example.com/')
  await expect(page.locator('h1')).toHaveText('Welcome back')
})

That is the whole of it. page, context, locators and expect are Playwright's own and behave as they always do — the browser fixture is the only one replaced, and it is worker-scoped, so a worker connects once and every test in it gets a fresh context and page on that connection.

Without the test runner

connect() hands you a Playwright Browser and asks for nothing else. Use it from a script, a job, or any runner that is not @playwright/test.

connect.ts
import { connect } from '@tabtunnel/playwright'

const browser = await connect()   // or connect({ url, token, browser, foreground, timeout })
const page = await browser.newPage()
await page.goto('https://example.com/')
console.log(await page.title())
await browser.close()             // disconnects; TabTunnel closes the tabs it opened

An explicit option wins over the environment variable of the same name.

Check it before you run anything

terminal
npx tabtunnel-playwright doctor

It reads the same variables and tells you which browser your tests would use, or why they cannot connect — without starting anything. Run it first whenever a suite will not connect.

Variables it reads

TABTUNNEL_URLThe address above, starting https://. Plain http:// is accepted only for localhost.
TABTUNNEL_TOKENYour tt_live_… token. It is never printed.
TABTUNNEL_BROWSEROptional. Which browser to use, by its id — shown on the extension's “connected” screen, and the last part of the browser's page address in the dashboard. Leave it out if only one browser you can use has CDP on.
TABTUNNEL_FOREGROUNDOptional. 1 opens pages in front, where the person at the browser can watch.
TABTUNNEL_TIMEOUT_MSOptional. How long to wait for a connection, in milliseconds. Default 30 000, which covers a browser that has to reconnect first.

What this browser is not

It is a real person's Chrome, with their profile, driven through the extension. So it behaves differently from the fresh Chromium Playwright usually launches:

  • Contexts share cookies and storage. newContext() works, but every context is the same profile, so tests are not isolated from each other.
  • No video, no trace, no HAR. Those only work in a browser Playwright launched itself. Screenshots work.
  • No per-context proxy or permissions. The browser's own network and settings are what there is.
  • Downloads land in the user's Downloads folder, as their Chrome is configured.
  • Popups are not seen. A tab a page opens with window.open does not reach your test, and no page event fires.
  • Opening DevTools on a tab your test is using disconnects that tab. Chrome allows one debugger per tab, and the person at the keyboard wins.
  • Slower. Every command travels through TabTunnel and the extension — tens of milliseconds rather than one. Keep expect timeouts generous.
  • Few workers per browser. Two is a good number; eight is not.
  • The browser may ask before visiting a site. Unless it was set up with Allow everything, a goto to a new site asks the person at the browser and the test waits. For a browser kept for testing, choose Allow everything.

When it will not connect

Each of these comes back with a code and a message saying what to do.

CDP_DISABLEDCDP is off for that browser. Open it from Browsers and press Switch CDP on. If it is already on, check Billing — your plan may have no CDP browsers left.
NO_CDP_BROWSERNone of the browsers your token can use has CDP on. Switch it on for one, or set TABTUNNEL_BROWSER to the one you meant.
AMBIGUOUS_BROWSERMore than one browser could be used. Set TABTUNNEL_BROWSER to pick one.
READ_ONLYThat browser is shared with you to view only. Ask its owner to let you use it.
BROWSER_OFFLINEChrome is closed, or the extension is signed out. Open Chrome and check the extension.
SESSION_CEILINGToo many connections are already open with this token. Use fewer workers, or stop a run that is still going.

When a connection closes mid-run

A running connection is checked every ten seconds and closed, with a reason, as soon as it may no longer be used:

pausedThe browser's owner paused it. It works again once they resume it.
revokedYour token was stopped, or you can no longer use this browser. Create a new token, or ask the owner.
cdp_disabledCDP was switched off for the browser, or your plan no longer includes it.
read_onlyThe browser is now shared with you to view only.
slow_consumer · event_floodA page sent events faster than your test could read them. Usually a page that logs or animates heavily; run fewer tests at once against it.

Is something here wrong, or missing? Tell us.