Documentation Index
Fetch the complete documentation index at: https://docs.deep.space/llms.txt
Use this file to discover all available pages before exploring further.
import {
test, expect,
loadAllTestAccounts, pickTestAccounts, findTestAccountByName,
ensureStorageState, newSignedInContext,
getStatePathForEmail, readCachedState,
} from 'deepspace/testing'
import type {
MultiplayerUser, UsersFixture, TestAccount, EnsureStorageStateOptions,
} from 'deepspace/testing'
Imported only inside Playwright spec files.
test and expect
Re-exports from Playwright with the users fixture pre-installed:
import { test, expect } from 'deepspace/testing'
test('A sends, B sees', async ({ users }) => {
const [alice, bob] = await users(2)
// ...
})
The fixture caches storageState per account, so each test account signs in once per machine - not once per test.
Requires baseURL in tests/playwright.config.ts. The scaffold sets this; tests error with users fixture requires a baseURL if it’s missing.
users(N | string[], options?) - the fixture
type UsersFixture = (
selector: number | string[],
options?: { label?: string },
) => Promise<MultiplayerUser[]>
interface MultiplayerUser {
context: BrowserContext
page: Page
email: string
name: string
/** Test account user ID, if known from the accounts registry. */
userId?: string
}
// First N accounts by createdAt
const [a, b] = await users(2)
// Specific accounts by name
const [alice, bob] = await users(['Alice', 'Bob'])
// First N filtered by label
const [team] = await users(1, { label: 'team-fixture' })
Contexts auto-close when the test finishes - no manual cleanup needed for contexts. You still need to clean up records you create during the test (see Testing guide → Test data cleanup).
Escape hatches
When the fixture is too high-level, import the underlying helpers directly:
| Helper | Signature |
|---|
loadAllTestAccounts() | () => TestAccount[] - every cached account (sync) |
pickTestAccounts(n, opts?) | (n: number, opts?: { label?: string }) => TestAccount[] (sync); throws if not enough accounts |
findTestAccountByName(name) | (name: string) => TestAccount (sync); throws if not found |
ensureStorageState(browser, account, baseURL, options?) | Sign in once and return the cached storageState path |
newSignedInContext(browser, account, baseURL, options?) | One-liner for a signed-in BrowserContext |
getStatePathForEmail(email) | Direct cache-path lookup for an email |
readCachedState(email) | Direct cache read by email |
All three account loaders read from ~/.deepspace/test-accounts.json synchronously - they don’t return promises.
TestAccount
interface TestAccount {
email: string
password: string
name?: string
label?: string | null
id?: string
userId?: string
createdAt?: number
}
Loaded from ~/.deepspace/test-accounts.json, populated by npx deepspace test-accounts create. createdAt is a numeric epoch millisecond timestamp, not an ISO string.
EnsureStorageStateOptions
interface EnsureStorageStateOptions {
/** Max age of a cached state file before we re-sign in. Default 7 days. */
maxAgeMs?: number
/** Force a fresh sign-in even if the cache is fresh. */
force?: boolean
}
ensureStorageState / newSignedInContext signatures
function ensureStorageState(
browser: Browser,
account: { email: string; password: string },
baseURL: string,
options?: EnsureStorageStateOptions,
): Promise<string>
function newSignedInContext(
browser: Browser,
account: { email: string; password: string },
baseURL: string,
options?: EnsureStorageStateOptions,
): Promise<BrowserContext>
browser is always the first argument, then the account record, then baseURL, then options.
Patterns
Multiplayer test
import { test, expect } from 'deepspace/testing'
test('shared state syncs', async ({ users }) => {
const [a, b] = await users(2)
await a.page.goto('/board')
await b.page.goto('/board')
await a.page.getByTestId('add-card-btn').click()
await a.page.getByTestId('card-title-input').fill('Hello')
await a.page.getByTestId('save-card-btn').click()
await expect(b.page.getByText('Hello')).toBeVisible()
})
Reusing a signed-in context outside the fixture
import { test } from '@playwright/test'
import { ensureStorageState, loadAllTestAccounts } from 'deepspace/testing'
test('custom flow', async ({ browser }) => {
const accounts = loadAllTestAccounts()
const statePath = await ensureStorageState(browser, accounts[0], 'http://localhost:5173')
const context = await browser.newContext({ storageState: statePath })
// ... use context.newPage(), etc.
await context.close()
})
See also