Skip to content

Session Management ​

CraftDriver supports Playwright-style session persistence, allowing you to save and restore browser state including cookies and localStorage. This is perfect for:

  • Skipping login in tests - Log in once, reuse session across test runs
  • Sharing auth state - Generate auth state in setup, use in parallel tests
  • Debugging - Capture session state at any point

Session management is the user-facing feature. Under the hood, CraftDriver uses the best available WebDriver transport for the browser you launched.

storageState is CraftDriver's native cookies-plus-localStorage format. It is not a promise of Playwright JSON compatibility and does not include IndexedDB, Cache Storage, service workers, or reusable sessionStorage.

State files contain live session cookies. Keep them in a dedicated ignored directory:

text
.auth/

Saving Session State ​

typescript
// Save all cookies and localStorage to a file
await browser.saveState('.auth/session.json');

// Save with options
await browser.saveState('.auth/session.json', {
  includeCookies: true, // default: true
  includeLocalStorage: true, // default: true
  includeSessionStorage: false, // default: false
});

The saved file contains:

json
{
  "cookies": [
    {
      "name": "session",
      "value": "abc123",
      "domain": "example.com",
      "path": "/",
      "secure": true,
      "httpOnly": true,
      "sameSite": "lax"
    }
  ],
  "localStorage": {
    "https://example.com": {
      "theme": "dark",
      "userId": "12345"
    }
  }
}

Loading Session State ​

typescript
// Load state into current browser
await browser.loadState('.auth/session.json');

// Paths and in-memory state objects are equivalent
await browser.loadState(await browser.storage.getState());

Working With State Objects ​

Use the state object directly when you do not want to write a file:

typescript
const state = await browser.storage.getState();
await browser.storage.setState(state);

browser.storage.setState() is an active-page operation: navigate to the state's sole HTTP(S) origin first. browser.loadState() uses the full multi-origin hydrator on BiDi when the state has no sessionStorage; otherwise it uses the same strict active-page rules.

Launching with Pre-loaded State ​

The most common pattern - launch a browser with existing session:

typescript
const browser = await Browser.launch({
  browserName: 'chrome',
  storageState: '.auth/session.json',
});

// Navigate directly to authenticated page
await browser.navigateTo('https://example.com/dashboard');
// Already logged in!

On supported BiDi sessions, cookies and every captured localStorage origin are ready before the first real navigation. A private intercepted document seeds each origin once; application changes are not overwritten on reload.

Browser and Transport Support ​

SurfaceChrome/Chromium BiDiFirefox BiDiChrome/Firefox/Safari Classic
Browser.launch({ storageState })Full cookies + multi-origin localStorageFull cookies + multi-origin localStorageNon-empty state rejected before mutation
browser.newContext({ storageState })Full isolated restoreFull isolated restoreUnavailable (Classic has no user contexts)
context.loadStorageState()Full multi-origin overlayFull multi-origin overlayUnavailable
browser.loadState()Full multi-origin overlay without sessionStorage; strict active page with itSameStrict single-active-origin restore
SaveCookies + current/open-page localStorage originsSameCookies + current-page localStorage

Chrome and Firefox BiDi and the Chrome/Firefox Classic fallback are covered by the integration suite. Chromium uses the same Chrome-family BiDi implementation. Safari is Classic-only and therefore receives the standards-only active-origin contract, not launch-time restore.

WebDriver Classic ​

Classic cannot write arbitrary-origin localStorage at launch. Use the explicit fallback instead:

typescript
const browser = await Browser.launch({ enableBiDi: false });
await browser.navigateTo('https://example.com');
await browser.loadState('.auth/session.json');
await browser.reload(); // the app can now read the restored state

CraftDriver validates the entire snapshot before mutation. about:blank, multiple storage origins, a mismatched cookie domain, or a secure cookie that cannot be set from the active page fails with STATE_INVALID. Non-empty storageState at Classic launch fails with UNSUPPORTED instead of pretending that a partial restore succeeded.

sessionStorage and runtime failure semantics ​

sessionStorage capture is opt-in diagnostic state. Context and launch APIs reject a non-empty sessionStorage section because it cannot be transferred to future tabs. Active-page APIs can restore one matching origin after navigation.

Fresh launch and newContext restoration clean up the new session/context on failure. An overlay into an existing context is not rollback-safe across multiple protocol calls. A runtime failure throws DRIVER_ERROR with detail.phase and detail.partialApplied; use a fresh context when failure isolation matters.

Direct cookie manipulation is also available:

typescript
// Add a cookie
await browser.storage.addCookie({
  name: 'session',
  value: 'abc123',
  domain: 'localhost',
  path: '/',
  secure: false,
  httpOnly: true,
  sameSite: 'Lax',
  expiry: new Date('2025-12-31'), // or Unix timestamp
});

// Get all cookies
const cookies = await browser.storage.getCookies();

// Get cookies for specific domain
const domainCookies = await browser.storage.getCookies({ domain: 'example.com' });

// Set multiple cookies
await browser.storage.setCookies([
  { name: 'theme', value: 'dark', domain: 'example.com', path: '/' },
]);

// Clear all cookies
await browser.storage.clearCookies();

// Clear cookies by filter
await browser.storage.clearCookies({ name: 'session' });
await browser.storage.clearCookies({ domain: 'example.com' });

Example: Login Once, Reuse Session ​

Step 1: Generate Auth State (run once) ​

typescript
// scripts/generate-auth.ts
import { Browser } from 'craftdriver';

async function generateAuth() {
  const browser = await Browser.launch({ browserName: 'chrome' });

  await browser.navigateTo('https://myapp.com/login');
  await browser.fill('#username', process.env.TEST_USER!);
  await browser.fill('#password', process.env.TEST_PASS!);
  await browser.click('#login-btn');

  // Wait for login to complete
  await browser.expect('#dashboard').toBeVisible();

  // Save the authenticated state
  await browser.saveState('.auth/session.json');

  await browser.quit();
  console.log('Auth state saved to .auth/session.json');
}

generateAuth();

Step 2: Use Auth State in Tests ​

typescript
// tests/dashboard.test.ts
import { describe, it, beforeEach, afterEach } from 'vitest';
import { Browser } from 'craftdriver';

describe('Dashboard', () => {
  let browser: Browser;

  beforeEach(async () => {
    // Launch with saved auth - already logged in!
    browser = await Browser.launch({
      browserName: 'chrome',
      storageState: '.auth/session.json',
    });
  });

  afterEach(async () => {
    await browser.quit();
  });

  it('shows user profile', async () => {
    // Go directly to authenticated page
    await browser.navigateTo('https://myapp.com/profile');

    // No login needed - session cookie is already set
    await browser.expect('#username').toHaveText('testuser');
  });

  it('can update settings', async () => {
    await browser.navigateTo('https://myapp.com/settings');
    await browser.click('#dark-mode-toggle');
    await browser.expect('#theme').toHaveText('dark');
  });
});

Complete E2E Example ​

typescript
import { Browser } from 'craftdriver';

async function testLoginPersistence() {
  // First browser: Log in and save state
  let browser = await Browser.launch({ browserName: 'chrome' });

  await browser.navigateTo('http://localhost:3000/login');
  await browser.fill('#username', 'testuser');
  await browser.fill('#password', 'secret123');
  await browser.click('#submit');
  await browser.expect('#welcome').toBeVisible();

  await browser.saveState('.auth/session.json');
  await browser.quit();

  // Second browser: Load state and verify logged in
  browser = await Browser.launch({
    browserName: 'chrome',
    storageState: '.auth/session.json',
  });

  await browser.navigateTo('http://localhost:3000/login');

  // Should already be logged in!
  await browser.expect('#welcome').toContainText('testuser');

  await browser.quit();
}

API Reference ​

Browser Methods ​

MethodDescription
saveState(path, options?)Save cookies and storage to file
loadState(source)Load cookies and storage from a path or object

StorageStateOptions ​

OptionTypeDefaultDescription
includeCookiesbooleantrueInclude cookies in saved state
includeLocalStoragebooleantrueInclude localStorage
includeSessionStoragebooleanfalseInclude sessionStorage
originsstring[]allFilter localStorage/sessionStorage capture by origin. Capture is always scoped to the page's current origin — this only decides whether that one origin is included, it does not collect storage from other origins the page hasn't visited. Cookies are unaffected by this option.

SessionStateManager (browser.storage) ​

MethodDescription
addCookie(cookie)Add a single cookie
getCookies(filter?)Get cookies, optionally filtered
setCookies(cookies)Set multiple cookies
clearCookies(filter?)Clear cookies, optionally filtered
getState(options?)Get current state as object
setState(source)Set active-page state from path or object
saveState(path, options?)Save state to file
loadState(source)Load active-page state from path or object

Released under the MIT License.