🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@rstest/browser

Package Overview
Dependencies
Maintainers
3
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@rstest/browser - npm Package Compare versions

Comparing version
0.10.3
to
0.10.4
dist/browser-container/container-static/js/3.d3d6c6e4cf.js

Sorry, the diff of this file is too big to display

+1
/*! LICENSE: 3.d3d6c6e4cf.js.LICENSE.txt */

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

/*! LICENSE: lib-react.62b27a21db.js.LICENSE.txt */
+2
-1

@@ -6,2 +6,3 @@ const DISPATCH_MESSAGE_TYPE = '__rstest_dispatch__';

const DISPATCH_NAMESPACE_BROWSER = 'browser';
export { DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER, DISPATCH_RESPONSE_TYPE, DISPATCH_RPC_BRIDGE_NAME, DISPATCH_RPC_REQUEST_TYPE };
const DISPATCH_NAMESPACE_SNAPSHOT = 'snapshot';
export { DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER, DISPATCH_NAMESPACE_SNAPSHOT, DISPATCH_RESPONSE_TYPE, DISPATCH_RPC_BRIDGE_NAME, DISPATCH_RPC_REQUEST_TYPE };

@@ -15,3 +15,3 @@ <!doctype html>

</script>
<script defer src="/container-static/js/lib-react.5ae9b90ee5.js"></script><script defer src="/container-static/js/27.099feaf1da.js"></script><script defer src="/container-static/js/index.be8c8d2626.js"></script><link href="/container-static/css/index.5c72297783.css" rel="stylesheet"></head>
<script defer src="/container-static/js/lib-react.62b27a21db.js"></script><script defer src="/container-static/js/3.d3d6c6e4cf.js"></script><script defer src="/container-static/js/index.8fb1588e4a.js"></script><link href="/container-static/css/index.5c72297783.css" rel="stylesheet"></head>
<body>

@@ -18,0 +18,0 @@ <div id="root"></div>

@@ -1,2 +0,2 @@

import { DISPATCH_RESPONSE_TYPE, DISPATCH_RPC_REQUEST_TYPE, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER } from "./626.js";
import { DISPATCH_RESPONSE_TYPE, DISPATCH_RPC_BRIDGE_NAME as protocol_DISPATCH_RPC_BRIDGE_NAME, DISPATCH_RPC_REQUEST_TYPE as protocol_DISPATCH_RPC_REQUEST_TYPE, DISPATCH_MESSAGE_TYPE as protocol_DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER } from "./626.js";
const DEFAULT_RPC_TIMEOUT_MS = 30000;

@@ -37,3 +37,3 @@ const getRpcTimeout = ()=>window.__RSTEST_BROWSER_OPTIONS__?.rpcTimeout ?? DEFAULT_RPC_TIMEOUT_MS;

if (window.parent === window) {
const dispatchBridge = window.__rstest_dispatch_rpc__;
const dispatchBridge = window[protocol_DISPATCH_RPC_BRIDGE_NAME];
if (!dispatchBridge) throw new Error('Dispatch RPC bridge is not available in top-level runner.');

@@ -72,5 +72,5 @@ return new Promise((resolve, reject)=>{

window.parent.postMessage({
type: DISPATCH_MESSAGE_TYPE,
type: protocol_DISPATCH_MESSAGE_TYPE,
payload: {
type: DISPATCH_RPC_REQUEST_TYPE,
type: protocol_DISPATCH_RPC_REQUEST_TYPE,
payload: request

@@ -77,0 +77,0 @@ }

import type { BrowserDispatchRequest } from '../protocol.js';
export declare const getRpcTimeout: () => number;
export declare const createRequestId: (prefix: string) => string;
/**
* Build a runner-lifecycle dispatch request.
*
* Lifecycle events (`file-ready`, `suite-start`, `suite-result`, `case-start`)
* share the dispatch-rpc envelope but are delivered fire-and-forget via
* {@link sendRunnerLifecycle}, so the request id only needs to be unique — it is
* produced by the shared {@link createRequestId} factory rather than a bespoke
* per-runner counter.
*/
export declare const createRunnerLifecycleRequest: (method: string, args: unknown) => BrowserDispatchRequest;
/**
* Deliver a runner-lifecycle request fire-and-forget.
*
* Unlike {@link dispatchRpc}, this never awaits, unwraps, id-matches, or times
* out: the host echoes a response but the runner ignores it. Failures surface
* only through the optional `onError` hook (debug logging at the call site),
* keeping the hot test loop non-blocking.
*/
export declare const sendRunnerLifecycle: (request: BrowserDispatchRequest, onError?: (error: unknown) => void) => void;
export declare const dispatchRpc: <T>({ requestId, request, timeoutMs, timeoutMessage, staleMessage, }: {

@@ -5,0 +24,0 @@ requestId: string;

/**
* Re-export runtime API from @rstest/core/browser-runtime for browser use.
* Re-export runtime API from @rstest/core/internal/browser-runtime for browser use.
* This file is used as an alias target for '@rstest/core' in browser mode.
*
* Uses @rstest/core/browser-runtime which only exports the test APIs
* Uses @rstest/core/internal/browser-runtime which only exports the test APIs
* (describe, it, expect, etc.) without any Node.js dependencies.
*/
export type { Assertion, Mock } from '@rstest/core';
export * from '@rstest/core/browser-runtime';
export * from '@rstest/core/internal/browser-runtime';

@@ -23,3 +23,3 @@ /**

* This is essential for inline snapshot support because the snapshot code
* runs in runner.js (which contains @rstest/core/browser-runtime).
* runs in runner.js (which contains @rstest/core/internal/browser-runtime).
* Without this, stack traces from inline snapshots cannot be mapped back

@@ -26,0 +26,0 @@ * to the original source files.

@@ -1,2 +0,4 @@

import type { Rstest } from '@rstest/core/browser';
import { getNumCpus, parseWorkers } from '@rstest/core/internal/browser';
import type { Rstest } from '@rstest/core/internal/browser';
export { getNumCpus, parseWorkers };
type HeadlessConcurrencyContext = Pick<Rstest, 'command'> & {

@@ -9,5 +11,3 @@ normalizedConfig: {

};
export declare const parseWorkers: (maxWorkers: string | number, numCpus?: number) => number;
export declare const resolveDefaultHeadlessWorkers: (command: HeadlessConcurrencyContext['command'], numCpus?: number) => number;
export declare const getHeadlessConcurrency: (context: HeadlessConcurrencyContext, totalTests: number) => number;
export {};

@@ -1,2 +0,2 @@

import type { Rstest } from '@rstest/core/browser';
export declare const validateBrowserConfig: (context: Rstest) => void;
import type { RstestContext } from '@rstest/core/internal/browser';
export declare const validateBrowserConfig: (context: RstestContext) => void;

@@ -1,2 +0,2 @@

import type { Reporter } from '@rstest/core/browser';
import type { Reporter } from '@rstest/core/internal/browser';
import { HostDispatchRouter } from './dispatchRouter.js';

@@ -3,0 +3,0 @@ import type { BrowserClientMessage, BrowserDispatchHandler, SnapshotRpcRequest } from './protocol.js';

@@ -1,2 +0,2 @@

import { type BrowserTestRunOptions, type BrowserTestRunResult, type ListCommandResult, type Rstest } from '@rstest/core/browser';
import { type BrowserTestRunOptions, type BrowserTestRunResult, type ListCommandResult, type RstestContext } from '@rstest/core/internal/browser';
type LazyCompilationModule = {

@@ -29,3 +29,3 @@ nameForCondition?: () => string | null | undefined;

};
export declare const runBrowserController: (context: Rstest, options?: BrowserTestRunOptions) => Promise<BrowserTestRunResult | void>;
export declare const runBrowserController: (context: RstestContext, options?: BrowserTestRunOptions) => Promise<BrowserTestRunResult | void>;
/**

@@ -45,3 +45,3 @@ * Result from collecting browser tests.

*/
export declare const listBrowserTests: (context: Rstest, options?: {
export declare const listBrowserTests: (context: RstestContext, options?: {
shardedEntries?: Map<string, {

@@ -48,0 +48,0 @@ entries: Record<string, string>;

@@ -1,7 +0,7 @@

import type { BrowserTestRunOptions, BrowserTestRunResult, Rstest } from '@rstest/core/browser';
import type { BrowserTestRunOptions, BrowserTestRunResult, RstestContext } from '@rstest/core/internal/browser';
import { validateBrowserConfig } from './configValidation.js';
import { type ListBrowserTestsResult } from './hostController.js';
export { validateBrowserConfig } from './configValidation.js';
export { BROWSER_VIEWPORT_PRESET_DIMENSIONS, BROWSER_VIEWPORT_PRESET_IDS, resolveBrowserViewportPreset, } from './viewportPresets.js';
export declare function runBrowserTests(context: Rstest, options?: BrowserTestRunOptions): Promise<BrowserTestRunResult | void>;
export declare function listBrowserTests(context: Rstest): Promise<ListBrowserTestsResult>;
export { validateBrowserConfig };
export declare function runBrowserTests(context: RstestContext, options?: BrowserTestRunOptions): Promise<BrowserTestRunResult | void>;
export declare function listBrowserTests(context: RstestContext, options?: Pick<BrowserTestRunOptions, 'shardedEntries'>): Promise<ListBrowserTestsResult>;
export type { BrowserTestRunOptions, BrowserTestRunResult, ListBrowserTestsResult, };

@@ -1,5 +0,5 @@

import type { DevicePreset } from '@rstest/core/browser';
import type { RuntimeConfig, TestFileResult, TestInfo, TestResult } from '@rstest/core/browser-runtime';
import type { BrowserViewport } from '@rstest/core/internal/browser';
import type { RuntimeConfig, TestFileResult, TestInfo, TestResult } from '@rstest/core/internal/browser-runtime';
import type { SnapshotUpdateState } from '@vitest/snapshot';
export type { BrowserLocatorIR, BrowserRpcRequest, SnapshotRpcRequest, } from './rpcProtocol.js';
export type { BrowserLocatorIR, BrowserRpcRequest, SnapshotRpcCall, SnapshotRpcMethod, SnapshotRpcMethodArgs, SnapshotRpcRequest, } from './rpcProtocol.js';
export { validateBrowserRpcRequest } from './rpcProtocol.js';

@@ -16,6 +16,3 @@ export declare const DISPATCH_MESSAGE_TYPE = "__rstest_dispatch__";

export type SerializedRuntimeConfig = RuntimeConfig;
export type BrowserViewport = {
width: number;
height: number;
} | DevicePreset;
export type { BrowserViewport };
export type BrowserProjectRuntime = {

@@ -42,2 +39,20 @@ name: string;

export type BrowserExecutionMode = 'run' | 'collect';
/**
* Wire shape of a `log` client message payload. The host receives this and maps
* it onto core's {@link UserConsoleLog} (notably `level` → `name`); that mapper
* (`hostController.ts` `handleLog`) annotates its result as `UserConsoleLog`, so
* the map→core direction is compiler-checked. Owning the wire shape here as one
* named type keeps the host's input type from drifting away from the producer.
*/
export type BrowserLogPayload = {
level: 'log' | 'warn' | 'error' | 'info' | 'debug';
content: string;
taskId?: string;
taskName?: string;
taskParentNames?: string[];
taskType?: 'file' | 'suite' | 'case';
testPath: string;
type: 'stdout' | 'stderr';
trace?: string;
};
export type BrowserHostConfig = {

@@ -93,13 +108,3 @@ rootPath: string;

type: 'log';
payload: {
level: 'log' | 'warn' | 'error' | 'info' | 'debug';
content: string;
taskId?: string;
taskName?: string;
taskParentNames?: string[];
taskType?: 'file' | 'suite' | 'case';
testPath: string;
type: 'stdout' | 'stderr';
trace?: string;
};
payload: BrowserLogPayload;
} | {

@@ -127,2 +132,24 @@ type: 'fatal';

/**
* Lifecycle methods the runner emits via `dispatchRunnerLifecycle()` as
* dispatch-rpc-requests on the `runner` namespace (as opposed to the
* {@link BrowserClientMessage} types it `send()`s). The runner client imports
* this instead of redeclaring the list, so the emit site cannot drift from the
* host router.
*/
export type RunnerLifecycleMethod = 'file-ready' | 'suite-start' | 'suite-result' | 'case-start';
/**
* {@link BrowserClientMessage} types that are forwarded to the `runner`
* namespace (by message `type`) rather than handled at the transport layer.
* `Extract` keeps this a checked subset of the message union — renaming a
* message type drops it here, surfacing as a missing handler downstream.
*/
type RunnerMessageMethod = Extract<BrowserClientMessage['type'], 'file-start' | 'case-result' | 'file-complete' | 'log' | 'fatal'>;
/**
* Single source of truth for every method handled by the `runner` dispatch
* namespace. The host handler table is keyed by this union (a missing key is a
* compile error), so adding a runner method here forces a matching handler and
* cannot silently no-op at runtime.
*/
export type RunnerDispatchMethod = RunnerLifecycleMethod | RunnerMessageMethod;
/**
* Transport-agnostic envelope used by host routing.

@@ -129,0 +156,0 @@ * `namespace + method + args + target` describes an operation independent of

@@ -0,1 +1,2 @@

import type { BrowserProvider } from '@rstest/core/internal/browser';
import type { BrowserRpcRequest } from '../rpcProtocol.js';

@@ -15,4 +16,10 @@ /**

* - prefer direct passthrough to provider APIs over provider-specific translation
*
* The provider-name union is owned by `@rstest/core` (see `BROWSER_PROVIDERS`)
* because core's CLI `init` templates need it but cannot import this package
* (peer-dependency direction). `providerImplementations` below is keyed by the
* re-exported union, so a provider added in core without an implementation here
* is a compile error.
*/
export type BrowserProvider = 'playwright';
export type { BrowserProvider };
/** Minimal console shape needed by host logging bridge. */

@@ -19,0 +26,0 @@ export type BrowserConsoleMessage = {

@@ -104,30 +104,44 @@ export type BrowserLocatorText = {

/**
* Snapshot RPC request from runner iframe.
* The container will forward these to the host via WebSocket RPC.
* Single source of truth for snapshot RPC methods and their argument shapes.
* The request union, the client sender, both host-side mappers, and every
* exhaustiveness check derive from this map — adding or renaming a method here
* forces a compile error at every site that has not been updated.
*/
export type SnapshotRpcRequest = {
id: string;
method: 'resolveSnapshotPath';
args: {
export type SnapshotRpcMethodArgs = {
resolveSnapshotPath: {
testPath: string;
};
} | {
id: string;
method: 'readSnapshotFile';
args: {
readSnapshotFile: {
filepath: string;
};
} | {
id: string;
method: 'saveSnapshotFile';
args: {
saveSnapshotFile: {
filepath: string;
content: string;
};
} | {
id: string;
method: 'removeSnapshotFile';
args: {
removeSnapshotFile: {
filepath: string;
};
};
export type SnapshotRpcMethod = keyof SnapshotRpcMethodArgs;
/**
* Snapshot RPC request from runner iframe.
* The container will forward these to the host via WebSocket RPC.
*/
export type SnapshotRpcRequest = {
[M in SnapshotRpcMethod]: {
id: string;
method: M;
args: SnapshotRpcMethodArgs[M];
};
}[SnapshotRpcMethod];
/**
* Client-side snapshot RPC call (request without the transport `id`).
* Distributive over {@link SnapshotRpcMethod} so `method` and `args` stay paired
* at the call site — a mismatched pair fails to compile.
*/
export type SnapshotRpcCall = {
[M in SnapshotRpcMethod]: {
method: M;
args: SnapshotRpcMethodArgs[M];
};
}[SnapshotRpcMethod];

@@ -7,2 +7,7 @@ /**

* autocomplete and runtime validation stay consistent.
*
* Upstream source of truth for ids/dimensions is the Chrome DevTools emulated
* device list — mirror each device's `screen.vertical` `{ width, height }`:
* https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/models/emulation/EmulatedDevices.ts
* Last verified in sync (all 17): 2026-04-22, devtools-frontend commit abaac05e.
*/

@@ -9,0 +14,0 @@ export declare const BROWSER_VIEWPORT_PRESET_IDS: readonly ['iPhoneSE', 'iPhoneXR', 'iPhone12Pro', 'iPhone14ProMax', 'Pixel7', 'SamsungGalaxyS8Plus', 'SamsungGalaxyS20Ultra', 'iPadMini', 'iPadAir', 'iPadPro', 'SurfacePro7', 'SurfaceDuo', 'GalaxyZFold5', 'AsusZenbookFold', 'SamsungGalaxyA51A71', 'NestHub', 'NestHubMax'];

{
"name": "@rstest/browser",
"version": "0.10.3",
"version": "0.10.4",
"description": "Browser mode support for Rstest testing framework.",

@@ -51,13 +51,13 @@ "keywords": [

"devDependencies": {
"@rslib/core": "0.21.5",
"@rslib/core": "0.22.0",
"@types/convert-source-map": "^2.0.3",
"@types/picomatch": "^4.0.3",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "7.0.0-dev.20260527.1",
"@vitest/snapshot": "^3.2.4",
"@typescript/native-preview": "7.0.0-dev.20260608.1",
"@vitest/snapshot": "^3.2.6",
"birpc": "^4.0.0",
"picomatch": "^4.0.4",
"playwright": "^1.60.0",
"@rstest/core": "0.10.4",
"@rstest/browser-ui": "0.0.0",
"@rstest/core": "0.10.3",
"@rstest/tsconfig": "0.0.1"

@@ -67,3 +67,3 @@ },

"playwright": "^1.49.1",
"@rstest/core": "^0.10.3"
"@rstest/core": "^0.10.4"
},

@@ -70,0 +70,0 @@ "peerDependenciesMeta": {

@@ -18,2 +18,5 @@ # Browser mode host architecture

LS["headlessLatestRerunScheduler.ts"]
HT["headlessTransport.ts\nattachHeadlessRunnerTransport()"]
CC["concurrency.ts\ngetHeadlessConcurrency()"]
HQ["headedSerialTaskQueue.ts\ncreateHeadedSerialTaskQueue()"]
end

@@ -39,2 +42,5 @@

RL --> SR
HC --> CC
HC --> HQ
HC --> HT

@@ -47,3 +53,3 @@ UR <--> HC

HC -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
HT -."headless bridge:\nexposeFunction(__rstest_dispatch__, __rstest_dispatch_rpc__)".-> Runner
```

@@ -50,0 +56,0 @@

@@ -7,6 +7,11 @@ import type {

DISPATCH_MESSAGE_TYPE,
DISPATCH_NAMESPACE_RUNNER,
DISPATCH_RESPONSE_TYPE,
DISPATCH_RPC_BRIDGE_NAME,
DISPATCH_RPC_REQUEST_TYPE,
} from '../protocol';
// Coincidentally equal to the host-side RUNNER_FRAMES_READY_TIMEOUT_MS and the
// runner's CONFIG_WAIT_TIMEOUT_MS (entry.ts), but a semantically distinct
// default in a different runtime, so deliberately not shared with them.
const DEFAULT_RPC_TIMEOUT_MS = 30_000;

@@ -41,2 +46,59 @@

/**
* Build a runner-lifecycle dispatch request.
*
* Lifecycle events (`file-ready`, `suite-start`, `suite-result`, `case-start`)
* share the dispatch-rpc envelope but are delivered fire-and-forget via
* {@link sendRunnerLifecycle}, so the request id only needs to be unique — it is
* produced by the shared {@link createRequestId} factory rather than a bespoke
* per-runner counter.
*/
export const createRunnerLifecycleRequest = (
method: string,
args: unknown,
): BrowserDispatchRequest => ({
requestId: createRequestId('runner-lifecycle'),
namespace: DISPATCH_NAMESPACE_RUNNER,
method,
args,
});
/**
* Deliver a runner-lifecycle request fire-and-forget.
*
* Unlike {@link dispatchRpc}, this never awaits, unwraps, id-matches, or times
* out: the host echoes a response but the runner ignores it. Failures surface
* only through the optional `onError` hook (debug logging at the call site),
* keeping the hot test loop non-blocking.
*/
export const sendRunnerLifecycle = (
request: BrowserDispatchRequest,
onError?: (error: unknown) => void,
): void => {
if (window.parent === window) {
const dispatchBridge = window[DISPATCH_RPC_BRIDGE_NAME];
if (!dispatchBridge) {
onError?.(
new Error('Dispatch RPC bridge is not available in top-level runner.'),
);
return;
}
void Promise.resolve(dispatchBridge(request)).catch((error: unknown) => {
onError?.(error);
});
return;
}
window.parent.postMessage(
{
type: DISPATCH_MESSAGE_TYPE,
payload: {
type: DISPATCH_RPC_REQUEST_TYPE,
payload: request,
},
},
'*',
);
};
const isDispatchResponse = (

@@ -121,3 +183,3 @@ value: unknown,

if (window.parent === window) {
const dispatchBridge = window.__rstest_dispatch_rpc__;
const dispatchBridge = window[DISPATCH_RPC_BRIDGE_NAME];
if (!dispatchBridge) {

@@ -124,0 +186,0 @@ throw new Error(

@@ -15,3 +15,3 @@ import {

WorkerState,
} from '@rstest/core/browser-runtime';
} from '@rstest/core/internal/browser-runtime';
import {

@@ -21,16 +21,17 @@ createBrowserTaskContext,

globalApis,
RSTEST_ENV_SYMBOL_KEY,
setRealTimers,
} from '@rstest/core/browser-runtime';
unwrapRegex,
} from '@rstest/core/internal/browser-runtime';
import { normalize } from 'pathe';
import type {
BrowserClientMessage,
BrowserDispatchRequest,
BrowserProjectRuntime,
RunnerLifecycleMethod,
} from '../protocol';
import { DISPATCH_MESSAGE_TYPE, RSTEST_CONFIG_MESSAGE_TYPE } from '../protocol';
import {
DISPATCH_MESSAGE_TYPE,
DISPATCH_NAMESPACE_RUNNER,
DISPATCH_RPC_REQUEST_TYPE,
RSTEST_CONFIG_MESSAGE_TYPE,
} from '../protocol';
createRunnerLifecycleRequest,
sendRunnerLifecycle,
} from './dispatchTransport';
import { BrowserSnapshotEnvironment } from './snapshot';

@@ -49,10 +50,2 @@ import {

type RunnerLifecycleMethod =
| 'file-ready'
| 'suite-start'
| 'suite-result'
| 'case-start';
let runnerDispatchRequestId = 0;
/**

@@ -69,3 +62,3 @@ * Debug logger for browser client.

type RuntimeEnvStore = Record<string, string | undefined>;
const RSTEST_ENV_SYMBOL = Symbol.for('rstest.env');
const RSTEST_ENV_SYMBOL = Symbol.for(RSTEST_ENV_SYMBOL_KEY);

@@ -77,16 +70,2 @@ type GlobalWithRuntimeEnv = typeof globalThis &

const REGEXP_FLAG_PREFIX = 'RSTEST_REGEXP:';
const unwrapRegex = (value: string): string | RegExp => {
if (value.startsWith(REGEXP_FLAG_PREFIX)) {
const raw = value.slice(REGEXP_FLAG_PREFIX.length);
const match = raw.match(/^\/(.+)\/([gimuy]*)$/);
if (match) {
const [, pattern, flags] = match;
return new RegExp(pattern!, flags);
}
}
return value;
};
const restoreRuntimeConfig = (

@@ -238,3 +217,3 @@ config: BrowserProjectRuntime['runtimeConfig'],

// Note: This binding may not exist if not using Playwright
window.__rstest_dispatch__?.(message);
window[DISPATCH_MESSAGE_TYPE]?.(message);
};

@@ -246,31 +225,19 @@

): void => {
const request: BrowserDispatchRequest = {
requestId: `runner-lifecycle-${++runnerDispatchRequestId}`,
namespace: DISPATCH_NAMESPACE_RUNNER,
method,
args: payload,
};
if (window.parent === window) {
const dispatchBridge = window.__rstest_dispatch_rpc__;
if (!dispatchBridge) {
debugLog(
'[Runner] Missing dispatch bridge for lifecycle method:',
method,
);
return;
}
void Promise.resolve(dispatchBridge(request)).catch((error: unknown) => {
sendRunnerLifecycle(
createRunnerLifecycleRequest(method, payload),
(error: unknown) => {
debugLog('[Runner] Failed to dispatch lifecycle method:', method, error);
});
return;
}
send({
type: DISPATCH_RPC_REQUEST_TYPE,
payload: request,
});
},
);
};
/** Timeout for waiting for browser config from container (30 seconds) */
/**
* Timeout for waiting for browser config from container (30 seconds).
*
* Coincidentally equal to the RPC default (client/dispatchTransport.ts) and the
* host's RUNNER_FRAMES_READY_TIMEOUT_MS (hostController.ts), but semantically
* distinct and in a different runtime, so deliberately not shared. Implicit
* invariant: this must not exceed the host's frames-ready timeout, or the host
* declares the runner un-ready before it can even receive its config.
*/
const CONFIG_WAIT_TIMEOUT_MS = 30_000;

@@ -277,0 +244,0 @@

/**
* Re-export runtime API from @rstest/core/browser-runtime for browser use.
* Re-export runtime API from @rstest/core/internal/browser-runtime for browser use.
* This file is used as an alias target for '@rstest/core' in browser mode.
*
* Uses @rstest/core/browser-runtime which only exports the test APIs
* Uses @rstest/core/internal/browser-runtime which only exports the test APIs
* (describe, it, expect, etc.) without any Node.js dependencies.

@@ -12,2 +12,2 @@ */

// Re-export all public test APIs
export * from '@rstest/core/browser-runtime';
export * from '@rstest/core/internal/browser-runtime';

@@ -1,2 +0,2 @@

import type { BrowserDispatchRequest, SnapshotRpcRequest } from '../protocol';
import type { BrowserDispatchRequest, SnapshotRpcCall } from '../protocol';
import { DISPATCH_NAMESPACE_SNAPSHOT } from '../protocol';

@@ -14,4 +14,3 @@ import {

requestId: string,
method: SnapshotRpcRequest['method'],
args: SnapshotRpcRequest['args'],
call: SnapshotRpcCall,
): BrowserDispatchRequest => {

@@ -23,4 +22,4 @@ // Snapshot is just one namespace on the shared dispatch RPC channel.

namespace: DISPATCH_NAMESPACE_SNAPSHOT,
method,
args,
method: call.method,
args: call.args,
};

@@ -32,14 +31,10 @@ };

* The container will forward it to the host via WebSocket RPC.
*
* `call` is a {@link SnapshotRpcCall}, so `method` and `args` are validated as a
* pair — a mismatched argument shape fails to compile at the call site.
*/
const sendRpcRequest = <T>(
method: SnapshotRpcRequest['method'],
args: SnapshotRpcRequest['args'],
): Promise<T> => {
const sendRpcRequest = <T>(call: SnapshotRpcCall): Promise<T> => {
const requestId = createRequestId('snapshot-rpc');
const rpcTimeout = getRpcTimeout();
const dispatchRequest = createSnapshotDispatchRequest(
requestId,
method,
args,
);
const dispatchRequest = createSnapshotDispatchRequest(requestId, call);

@@ -51,3 +46,3 @@ return dispatchRpc<T>({

staleMessage: 'Stale snapshot RPC request ignored.',
timeoutMessage: `Snapshot RPC timeout after ${rpcTimeout / 1000}s: ${method}`,
timeoutMessage: `Snapshot RPC timeout after ${rpcTimeout / 1000}s: ${call.method}`,
});

@@ -74,4 +69,5 @@ };

async resolvePath(filepath: string): Promise<string> {
return sendRpcRequest<string>('resolveSnapshotPath', {
testPath: filepath,
return sendRpcRequest<string>({
method: 'resolveSnapshotPath',
args: { testPath: filepath },
});

@@ -85,5 +81,5 @@ }

async saveSnapshotFile(filepath: string, snapshot: string): Promise<void> {
await sendRpcRequest<void>('saveSnapshotFile', {
filepath,
content: snapshot,
await sendRpcRequest<void>({
method: 'saveSnapshotFile',
args: { filepath, content: snapshot },
});

@@ -93,7 +89,13 @@ }

async readSnapshotFile(filepath: string): Promise<string | null> {
return sendRpcRequest<string | null>('readSnapshotFile', { filepath });
return sendRpcRequest<string | null>({
method: 'readSnapshotFile',
args: { filepath },
});
}
async removeSnapshotFile(filepath: string): Promise<void> {
await sendRpcRequest<void>('removeSnapshotFile', { filepath });
await sendRpcRequest<void>({
method: 'removeSnapshotFile',
args: { filepath },
});
}

@@ -100,0 +102,0 @@

@@ -105,3 +105,3 @@ import { originalPositionFor, TraceMap } from '@jridgewell/trace-mapping';

* This is essential for inline snapshot support because the snapshot code
* runs in runner.js (which contains @rstest/core/browser-runtime).
* runs in runner.js (which contains @rstest/core/internal/browser-runtime).
* Without this, stack traces from inline snapshots cannot be mapped back

@@ -108,0 +108,0 @@ * to the original source files.

@@ -1,4 +0,8 @@

import os from 'node:os';
import type { Rstest } from '@rstest/core/browser';
import { getNumCpus, parseWorkers } from '@rstest/core/internal/browser';
import type { Rstest } from '@rstest/core/internal/browser';
// Re-export the shared worker primitives so existing consumers (and unit tests
// importing from `../src/concurrency`) keep a stable import surface.
export { getNumCpus, parseWorkers };
// Shared headless concurrency policy.

@@ -16,20 +20,2 @@ // Keep this in one place so executors reuse the same worker semantics.

const getNumCpus = (): number => {
return os.availableParallelism?.() ?? os.cpus().length;
};
export const parseWorkers = (
maxWorkers: string | number,
numCpus = getNumCpus(),
): number => {
const parsed = Number.parseInt(maxWorkers.toString(), 10);
if (typeof maxWorkers === 'string' && maxWorkers.trim().endsWith('%')) {
const workers = Math.floor((parsed / 100) * numCpus);
return Math.max(workers, 1);
}
return parsed > 0 ? parsed : 1;
};
export const resolveDefaultHeadlessWorkers = (

@@ -36,0 +22,0 @@ command: HeadlessConcurrencyContext['command'],

@@ -1,2 +0,2 @@

import type { Rstest } from '@rstest/core/browser';
import type { RstestContext } from '@rstest/core/internal/browser';
import { resolveBrowserViewportPreset } from './viewportPresets';

@@ -45,3 +45,3 @@

export const validateBrowserConfig = (context: Rstest): void => {
export const validateBrowserConfig = (context: RstestContext): void => {
for (const project of context.projects) {

@@ -48,0 +48,0 @@ const { browser, output } = project.normalizedConfig;

@@ -1,3 +0,7 @@

import type { Reporter } from '@rstest/core/browser';
import type { Reporter } from '@rstest/core/internal/browser';
import { HostDispatchRouter } from './dispatchRouter';
import {
DISPATCH_NAMESPACE_RUNNER,
DISPATCH_NAMESPACE_SNAPSHOT,
} from './protocol';
import type {

@@ -7,2 +11,5 @@ BrowserClientMessage,

BrowserDispatchRequest,
RunnerDispatchMethod,
SnapshotRpcMethod,
SnapshotRpcMethodArgs,
SnapshotRpcRequest,

@@ -56,33 +63,46 @@ } from './protocol';

/**
* Builds a typed {@link SnapshotRpcRequest} from the untrusted wire envelope,
* one builder per method. Keyed by {@link SnapshotRpcMethod}, so adding or
* renaming a method in the union forces a matching entry here — a stale name
* becomes a compile error instead of silently falling through to `null`. The
* `args` casts are the unavoidable trust boundary for inbound wire data.
*/
const snapshotRequestBuilders: {
[M in SnapshotRpcMethod]: (
id: string,
args: BrowserDispatchRequest['args'],
) => Extract<SnapshotRpcRequest, { method: M }>;
} = {
resolveSnapshotPath: (id, args) => ({
id,
method: 'resolveSnapshotPath',
args: args as SnapshotRpcMethodArgs['resolveSnapshotPath'],
}),
readSnapshotFile: (id, args) => ({
id,
method: 'readSnapshotFile',
args: args as SnapshotRpcMethodArgs['readSnapshotFile'],
}),
saveSnapshotFile: (id, args) => ({
id,
method: 'saveSnapshotFile',
args: args as SnapshotRpcMethodArgs['saveSnapshotFile'],
}),
removeSnapshotFile: (id, args) => ({
id,
method: 'removeSnapshotFile',
args: args as SnapshotRpcMethodArgs['removeSnapshotFile'],
}),
};
const toSnapshotRpcRequest = (
request: BrowserDispatchRequest,
): SnapshotRpcRequest | null => {
switch (request.method) {
case 'resolveSnapshotPath':
return {
id: request.requestId,
method: 'resolveSnapshotPath',
args: request.args as { testPath: string },
};
case 'readSnapshotFile':
return {
id: request.requestId,
method: 'readSnapshotFile',
args: request.args as { filepath: string },
};
case 'saveSnapshotFile':
return {
id: request.requestId,
method: 'saveSnapshotFile',
args: request.args as { filepath: string; content: string },
};
case 'removeSnapshotFile':
return {
id: request.requestId,
method: 'removeSnapshotFile',
args: request.args as { filepath: string },
};
default:
return null;
}
const builder = snapshotRequestBuilders[
request.method as SnapshotRpcMethod
] as
| ((id: string, args: BrowserDispatchRequest['args']) => SnapshotRpcRequest)
| undefined;
return builder ? builder(request.requestId, request.args) : null;
};

@@ -99,58 +119,57 @@

router.register('runner', async (request: BrowserDispatchRequest) => {
switch (request.method) {
case 'file-start':
await runnerCallbacks.onTestFileStart(
request.args as RunnerPayload<'file-start'>,
);
break;
case 'file-ready':
await runnerCallbacks.onTestFileReady(
request.args as RunnerDispatchFileReadyPayload,
);
break;
case 'suite-start':
await runnerCallbacks.onTestSuiteStart(
request.args as RunnerDispatchSuiteStartPayload,
);
break;
case 'suite-result':
await runnerCallbacks.onTestSuiteResult(
request.args as RunnerDispatchSuiteResultPayload,
);
break;
case 'case-start':
await runnerCallbacks.onTestCaseStart(
request.args as RunnerDispatchCaseStartPayload,
);
break;
case 'case-result':
await runnerCallbacks.onTestCaseResult(
request.args as RunnerPayload<'case-result'>,
);
break;
case 'file-complete':
await runnerCallbacks.onTestFileComplete(
request.args as RunnerPayload<'file-complete'>,
);
break;
case 'log':
await runnerCallbacks.onLog(request.args as RunnerPayload<'log'>);
break;
case 'fatal':
await runnerCallbacks.onFatal(request.args as RunnerPayload<'fatal'>);
break;
default:
break;
}
});
// Keyed by RunnerDispatchMethod so adding a runner method to that union forces
// a handler entry here (a missing key is a compile error) — the previous
// `switch` with `default: break` silently dropped unhandled methods. The
// `args` casts are checked against each callback's parameter type, so a wrong
// payload type also fails to compile.
const runnerMethodHandlers: {
[M in RunnerDispatchMethod]: (
args: BrowserDispatchRequest['args'],
) => Promise<void>;
} = {
'file-start': (args) =>
runnerCallbacks.onTestFileStart(args as RunnerPayload<'file-start'>),
'file-ready': (args) =>
runnerCallbacks.onTestFileReady(args as RunnerDispatchFileReadyPayload),
'suite-start': (args) =>
runnerCallbacks.onTestSuiteStart(args as RunnerDispatchSuiteStartPayload),
'suite-result': (args) =>
runnerCallbacks.onTestSuiteResult(
args as RunnerDispatchSuiteResultPayload,
),
'case-start': (args) =>
runnerCallbacks.onTestCaseStart(args as RunnerDispatchCaseStartPayload),
'case-result': (args) =>
runnerCallbacks.onTestCaseResult(args as RunnerPayload<'case-result'>),
'file-complete': (args) =>
runnerCallbacks.onTestFileComplete(
args as RunnerPayload<'file-complete'>,
),
log: (args) => runnerCallbacks.onLog(args as RunnerPayload<'log'>),
fatal: (args) => runnerCallbacks.onFatal(args as RunnerPayload<'fatal'>),
};
router.register('snapshot', async (request: BrowserDispatchRequest) => {
const snapshotRequest = toSnapshotRpcRequest(request);
if (!snapshotRequest) {
return undefined;
}
return runSnapshotRpc(snapshotRequest);
});
router.register(
DISPATCH_NAMESPACE_RUNNER,
async (request: BrowserDispatchRequest) => {
// `request.method` is untrusted wire data, so the lookup may miss. Unknown
// methods are ignored for forward-compatibility with newer runners,
// matching the previous `default: break`.
await runnerMethodHandlers[request.method as RunnerDispatchMethod]?.(
request.args,
);
},
);
router.register(
DISPATCH_NAMESPACE_SNAPSHOT,
async (request: BrowserDispatchRequest) => {
const snapshotRequest = toSnapshotRpcRequest(request);
if (!snapshotRequest) {
return undefined;
}
return runSnapshotRpc(snapshotRequest);
},
);
for (const [namespace, handler] of extensionHandlers ?? []) {

@@ -157,0 +176,0 @@ if (router.has(namespace)) {

@@ -0,1 +1,2 @@

import { DISPATCH_MESSAGE_TYPE, DISPATCH_RPC_BRIDGE_NAME } from './protocol';
import type {

@@ -41,4 +42,7 @@ BrowserClientMessage,

__RSTEST_BROWSER_OPTIONS__?: BrowserHostConfig;
__rstest_dispatch__?: (message: BrowserClientMessage) => void;
__rstest_dispatch_rpc__?: (
// Keyed by the sentinel constants so the declared global name and the
// constant are the same source — renaming a constant moves this key with it,
// and every `window[CONST]` access site stays in lockstep automatically.
[DISPATCH_MESSAGE_TYPE]?: (message: BrowserClientMessage) => void;
[DISPATCH_RPC_BRIDGE_NAME]?: (
request: BrowserDispatchRequest,

@@ -45,0 +49,0 @@ ) => Promise<unknown>;

import type {
BrowserHostModule,
BrowserTestRunOptions,
BrowserTestRunResult,
Rstest,
} from '@rstest/core/browser';
RstestContext,
} from '@rstest/core/internal/browser';
import { validateBrowserConfig } from './configValidation';
import {

@@ -12,11 +14,6 @@ type ListBrowserTestsResult,

export { validateBrowserConfig } from './configValidation';
export {
BROWSER_VIEWPORT_PRESET_DIMENSIONS,
BROWSER_VIEWPORT_PRESET_IDS,
resolveBrowserViewportPreset,
} from './viewportPresets';
export { validateBrowserConfig };
export async function runBrowserTests(
context: Rstest,
context: RstestContext,
options?: BrowserTestRunOptions,

@@ -28,7 +25,21 @@ ): Promise<BrowserTestRunResult | void> {

export async function listBrowserTests(
context: Rstest,
context: RstestContext,
options?: Pick<BrowserTestRunOptions, 'shardedEntries'>,
): Promise<ListBrowserTestsResult> {
return listBrowserTestsImpl(context);
// Forward `options` (e.g. `shardedEntries`) so `rstest list --shard` lists
// only the current shard's browser test files, matching the run path.
return listBrowserTestsImpl(context, options);
}
/**
* Compile-time guard: ensure the public host exports satisfy the core-owned
* {@link BrowserHostModule} contract. This catches drift such as a dropped
* `options` argument at the load boundary. No runtime side effect.
*/
void ({
validateBrowserConfig,
runBrowserTests,
listBrowserTests,
} satisfies BrowserHostModule);
export type {

@@ -35,0 +46,0 @@ BrowserTestRunOptions,

@@ -1,2 +0,2 @@

import type { DevicePreset } from '@rstest/core/browser';
import type { BrowserViewport } from '@rstest/core/internal/browser';
import type {

@@ -7,3 +7,3 @@ RuntimeConfig,

TestResult,
} from '@rstest/core/browser-runtime';
} from '@rstest/core/internal/browser-runtime';
import type { SnapshotUpdateState } from '@vitest/snapshot';

@@ -14,2 +14,5 @@

BrowserRpcRequest,
SnapshotRpcCall,
SnapshotRpcMethod,
SnapshotRpcMethodArgs,
SnapshotRpcRequest,

@@ -32,8 +35,6 @@ } from './rpcProtocol';

export type BrowserViewport =
| {
width: number;
height: number;
}
| DevicePreset;
// `BrowserViewport` is a core config type (`@rstest/core` owns the canonical
// definition used by `NormalizedBrowserModeConfig`). Re-export it so the host
// assigns the SAME type across the seam instead of a hand-copied duplicate.
export type { BrowserViewport };

@@ -64,2 +65,21 @@ export type BrowserProjectRuntime = {

/**
* Wire shape of a `log` client message payload. The host receives this and maps
* it onto core's {@link UserConsoleLog} (notably `level` → `name`); that mapper
* (`hostController.ts` `handleLog`) annotates its result as `UserConsoleLog`, so
* the map→core direction is compiler-checked. Owning the wire shape here as one
* named type keeps the host's input type from drifting away from the producer.
*/
export type BrowserLogPayload = {
level: 'log' | 'warn' | 'error' | 'info' | 'debug';
content: string;
taskId?: string;
taskName?: string;
taskParentNames?: string[];
taskType?: 'file' | 'suite' | 'case';
testPath: string;
type: 'stdout' | 'stderr';
trace?: string;
};
export type BrowserHostConfig = {

@@ -108,17 +128,4 @@ rootPath: string;

| { type: 'file-complete'; payload: TestFileResult }
| { type: 'log'; payload: BrowserLogPayload }
| {
type: 'log';
payload: {
level: 'log' | 'warn' | 'error' | 'info' | 'debug';
content: string;
taskId?: string;
taskName?: string;
taskParentNames?: string[];
taskType?: 'file' | 'suite' | 'case';
testPath: string;
type: 'stdout' | 'stderr';
trace?: string;
};
}
| {
type: 'fatal';

@@ -143,2 +150,34 @@ payload: { message: string; stack?: string };

/**
* Lifecycle methods the runner emits via `dispatchRunnerLifecycle()` as
* dispatch-rpc-requests on the `runner` namespace (as opposed to the
* {@link BrowserClientMessage} types it `send()`s). The runner client imports
* this instead of redeclaring the list, so the emit site cannot drift from the
* host router.
*/
export type RunnerLifecycleMethod =
| 'file-ready'
| 'suite-start'
| 'suite-result'
| 'case-start';
/**
* {@link BrowserClientMessage} types that are forwarded to the `runner`
* namespace (by message `type`) rather than handled at the transport layer.
* `Extract` keeps this a checked subset of the message union — renaming a
* message type drops it here, surfacing as a missing handler downstream.
*/
type RunnerMessageMethod = Extract<
BrowserClientMessage['type'],
'file-start' | 'case-result' | 'file-complete' | 'log' | 'fatal'
>;
/**
* Single source of truth for every method handled by the `runner` dispatch
* namespace. The host handler table is keyed by this union (a missing key is a
* compile error), so adding a runner method here forces a matching handler and
* cannot silently no-op at runtime.
*/
export type RunnerDispatchMethod = RunnerLifecycleMethod | RunnerMessageMethod;
/**
* Transport-agnostic envelope used by host routing.

@@ -156,2 +195,7 @@ * `namespace + method + args + target` describes an operation independent of

args?: unknown;
// Routing reads `namespace`, `method`, `runToken`, and `target.sessionId`
// (see dispatchRouter.ts / dispatchBrowserRpcRequest). `target.testFile` and
// `target.projectName` are carried for diagnostics / forward-compatibility and
// are NOT consulted for routing today — adding a routing-relevant field here
// means wiring a reader on the host side, which structural typing won't force.
target?: {

@@ -158,0 +202,0 @@ testFile?: string;

@@ -0,1 +1,2 @@

import type { BrowserProvider } from '@rstest/core/internal/browser';
import type { BrowserRpcRequest } from '../rpcProtocol';

@@ -17,4 +18,10 @@ import { playwrightProviderImplementation } from './playwright';

* - prefer direct passthrough to provider APIs over provider-specific translation
*
* The provider-name union is owned by `@rstest/core` (see `BROWSER_PROVIDERS`)
* because core's CLI `init` templates need it but cannot import this package
* (peer-dependency direction). `providerImplementations` below is keyed by the
* re-exported union, so a provider added in core without an implementation here
* is a compile error.
*/
export type BrowserProvider = 'playwright';
export type { BrowserProvider };

@@ -21,0 +28,0 @@ /** Minimal console shape needed by host logging bridge. */

@@ -182,25 +182,38 @@ export type BrowserLocatorText =

/**
* Single source of truth for snapshot RPC methods and their argument shapes.
* The request union, the client sender, both host-side mappers, and every
* exhaustiveness check derive from this map — adding or renaming a method here
* forces a compile error at every site that has not been updated.
*/
export type SnapshotRpcMethodArgs = {
resolveSnapshotPath: { testPath: string };
readSnapshotFile: { filepath: string };
saveSnapshotFile: { filepath: string; content: string };
removeSnapshotFile: { filepath: string };
};
export type SnapshotRpcMethod = keyof SnapshotRpcMethodArgs;
/**
* Snapshot RPC request from runner iframe.
* The container will forward these to the host via WebSocket RPC.
*/
export type SnapshotRpcRequest =
| {
id: string;
method: 'resolveSnapshotPath';
args: { testPath: string };
}
| {
id: string;
method: 'readSnapshotFile';
args: { filepath: string };
}
| {
id: string;
method: 'saveSnapshotFile';
args: { filepath: string; content: string };
}
| {
id: string;
method: 'removeSnapshotFile';
args: { filepath: string };
};
export type SnapshotRpcRequest = {
[M in SnapshotRpcMethod]: {
id: string;
method: M;
args: SnapshotRpcMethodArgs[M];
};
}[SnapshotRpcMethod];
/**
* Client-side snapshot RPC call (request without the transport `id`).
* Distributive over {@link SnapshotRpcMethod} so `method` and `args` stay paired
* at the call site — a mismatched pair fails to compile.
*/
export type SnapshotRpcCall = {
[M in SnapshotRpcMethod]: {
method: M;
args: SnapshotRpcMethodArgs[M];
};
}[SnapshotRpcMethod];

@@ -7,2 +7,7 @@ /**

* autocomplete and runtime validation stay consistent.
*
* Upstream source of truth for ids/dimensions is the Chrome DevTools emulated
* device list — mirror each device's `screen.vertical` `{ width, height }`:
* https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/models/emulation/EmulatedDevices.ts
* Last verified in sync (all 17): 2026-04-22, devtools-frontend commit abaac05e.
*/

@@ -9,0 +14,0 @@ export const BROWSER_VIEWPORT_PRESET_IDS = [

@@ -1,7 +0,5 @@

import { color, logger } from '@rstest/core/browser';
import { color, isTTY, logger } from '@rstest/core/internal/browser';
const isTTY = (): boolean => Boolean(process.stdin.isTTY && !process.env.CI);
export const isBrowserWatchCliShortcutsEnabled = (): boolean => isTTY('stdin');
export const isBrowserWatchCliShortcutsEnabled = (): boolean => isTTY();
export const getBrowserWatchCliShortcutsHintMessage = (): string => {

@@ -8,0 +6,0 @@ return ` ${color.dim('press')} ${color.bold('q')} ${color.dim('to quit')}\n`;

Sorry, the diff of this file is too big to display

/*! LICENSE: 27.099feaf1da.js.LICENSE.txt */

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

/*! LICENSE: lib-react.5ae9b90ee5.js.LICENSE.txt */

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display