Skip to content

feat: add t.openIsolatedSession() for multi-user e2e tests via CDP#8492

Open
sam-yh wants to merge 1 commit intoDevExpress:masterfrom
sam-yh:upstream-pr
Open

feat: add t.openIsolatedSession() for multi-user e2e tests via CDP#8492
sam-yh wants to merge 1 commit intoDevExpress:masterfrom
sam-yh:upstream-pr

Conversation

@sam-yh
Copy link

@sam-yh sam-yh commented Mar 17, 2026

Note: This feature was developed with the assistance of Claude Opus 4.6 (Anthropic) and should be considered "vibe coded." We have been running it in production for our multi-user e2e test suite and all 40 functional tests pass, but the codebase has not undergone traditional peer review beyond AI-assisted iteration. We welcome feedback on the implementation approach and are happy to iterate.

Summary

Adds t.openIsolatedSession() — the ability to open fully isolated browser sessions within a single test. Each session gets its own Chrome browser context (separate cookies, localStorage, sessionStorage, service workers) via CDP's Target.createBrowserContext().

This enables multi-user e2e tests: doctor + patient in a video call, two users editing the same document, concurrent booking scenarios — all within a single test.

Motivation

Currently, testing multi-user scenarios requires either:

  • Running two separate TestCafe instances and coordinating them externally
  • Using Role switching (which shares the same browser context — cookies bleed between users)
  • Manual browser automation outside TestCafe

This PR provides a native solution: t.openIsolatedSession() returns a second test controller (t2) backed by a completely isolated Chrome browser context.

API

test('Two users interact', async t => {
    const t2 = await t.openIsolatedSession();
    await t2.navigateTo('https://example.com');

    // Each session has separate cookies, storage, DOM
    await t.eval(() => { document.cookie = 'user=alice'; });
    await t2.eval(() => { document.cookie = 'user=bob'; });

    // t2 supports most TestCafe commands
    await t2.click('#btn');
    await t2.typeText('#input', 'text');
    await t2.expect(true).ok();

    // Selector chaining works
    await t2.click(Selector('.btn').withText('Save'));

    // t2.run() makes Selector/ClientFunction evaluate in the isolated tab
    await t2.run(async () => {
        await t.expect(Selector('#element').visible).ok();
    });

    // Automatic cleanup when test ends
});

Supported commands

Command-based: click, rightClick, doubleClick, hover, drag, dragToElement, typeText, pressKey, selectText, scroll, scrollBy, scrollIntoView, dispatchEvent, navigateTo, wait, eval, expect, useRole, getCookies, setCookies, deleteCookies

Direct session methods: setHttpAuth, setWindowBounds, takeScreenshot, takeElementScreenshot, maximizeWindow, resizeWindow, setFilesToUpload, setPageLoadTimeout, switchToIframe, switchToMainWindow

Selector chaining: withText, withExactText, filterVisible, filterHidden, nth, find, parent, child, sibling, nextSibling, prevSibling, withAttribute

Requirements

  • Native Automation mode (--experimental-multiple-windows or nativeAutomation: true)
  • Chrome only (uses CDP Target.createBrowserContext)

Architecture

Three new files, six modified files:

New files:

  • src/test-run/isolated-session.ts — Core runtime class. Holds CDP client, executes commands via CDP
  • src/api/test-controller/isolated.js — The t2 controller. Mirrors TestController's delegatedAPI pattern
  • src/native-automation/isolated-window.ts — Thin NativeAutomationBase wrapper for isolated tabs

Modified files:

  • src/browser/provider/built-in/dedicated/chrome/cdp-client/index.tscreateIsolatedContext(), disposeIsolatedContext()
  • src/browser/provider/built-in/dedicated/chrome/index.jscreateIsolatedSession(), disposeIsolatedSession()
  • src/api/test-controller/index.js_openIsolatedSession$() method
  • src/test-run/index.ts — Isolated session lifecycle (create, dispose, find)
  • src/client-functions/selectors/selector-builder.jscounterMode, getVisibleValueMode properties
  • src/test-run/commands/execute-client-function.js — Same two properties

Key design decisions:

  1. No TestCafe driver injection — all commands execute via CDP directly
  2. No hammerhead proxy — isolated tab is a raw Chrome tab
  3. Separate command queue — t2 has its own execution chain
  4. Automatic cleanup — sessions disposed in TestRun._done()

Tests

40 functional tests in test/functional/fixtures/isolated-sessions/:

Suite Tests Coverage
Basic Isolation 6 Cookie, localStorage, sessionStorage, DOM, multiple sessions, cleanup
Commands 14 click, typeText, hover, doubleClick, pressKey, navigateTo, scroll, eval, wait, expect, dispatchEvent
Selector Chaining 7 Selector object, withText, withExactText, nth, filterVisible, find, withAttribute
t2.run() 7 Selector.exists/visible/innerText, ClientFunction, session restore, sequential runs
Cookies 3 document.cookie isolation, getCookies, deleteCookies
Iframe 3 switchToIframe eval, interact via eval, switchToMainWindow
Screenshots 2 takeScreenshot, takeElementScreenshot
File Upload 1 setFilesToUpload
Window Management 3 maximizeWindow, resizeWindow, setPageLoadTimeout

Run tests:

npx http-server test/functional -p 3000 -s &
node lib/cli/index.js --experimental-multiple-windows "chrome:headless" test/functional/fixtures/isolated-sessions/testcafe-fixtures/basic-isolation-test.js

Documentation

Since TestCafe stores documentation in a private repo, here is the documentation for this feature:

t.openIsolatedSession()IsolatedTestController

Opens a new browser window in a fully isolated Chrome browser context. Returns an IsolatedTestController (commonly named t2) with a subset of the standard TestCafe API.

Requires: Native Automation mode.

The isolated session has completely separate cookies, localStorage, sessionStorage, and service workers from the main session and from other isolated sessions.

Sessions are automatically cleaned up when the test ends. The CDP WebSocket is closed and Target.disposeBrowserContext() destroys the context.

t2.run(callback)

Executes a callback where Selector queries and ClientFunction evaluations target the isolated session's CDP tab. Inside the callback, Selector.exists, Selector.visible, Selector.innerText, and ClientFunction all evaluate in the isolated tab. Action commands (click, typeText, etc.) still require using t2 directly.

await t2.run(async () => {
    // Selectors evaluate in the isolated tab
    await t.expect(Selector('#element').visible).ok();
    // Actions use t2 directly
    await t2.click('#element');
});

@testcafe-need-response-bot testcafe-need-response-bot bot added the STATE: Need response An issue that requires a response or attention from the team. label Mar 17, 2026
Add the ability to open fully isolated browser sessions within a single
test. Each session gets its own Chrome browser context (separate cookies,
localStorage, sessionStorage, service workers) via CDP's
Target.createBrowserContext().

Includes 40 functional tests, selector chaining support, t2.run() for
transparent Selector/ClientFunction evaluation, and automatic cleanup.

Requires Native Automation mode (Chrome only).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

STATE: Need response An issue that requires a response or attention from the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant