Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions docs/assets/rsc-runtime-workbench/fidelity-ledger.md

Large diffs are not rendered by default.

Binary file removed docs/assets/rsc-runtime-workbench/mobile.png
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "pnpm test:unit && pnpm test:integration",
"test:unit": "rstest --config rstest.unit.config.ts",
"test:integration": "pnpm build && pnpm test:integration:run",
"test:integration:run": "AGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 rstest --config rstest.integration.config.ts && AGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 rstest --config rstest.integration-serial.config.ts",
"test:integration:run": "AGENT_BUNDLE_WORKBENCH_PREBUILT=1 AGENT_BUNDLE_PACKAGE_PREBUILT=1 rstest --config rstest.integration.config.ts",
"test:watch": "rstest --config rstest.config.ts --watch",
"lint": "rslint .",
"typecheck": "tsc --noEmit && tsc --project packages/workbench/tsconfig.json",
Expand Down
4 changes: 4 additions & 0 deletions packages/agent-bundle/tests/public-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const packageRoot = join(workspaceRoot, 'packages/agent-bundle');
let buildPromise: Promise<void> | undefined;

const buildPackage = async (): Promise<void> => {
// The integration pool runs this file on parallel workers that share the
// built dist directories, so a root rebuild here would race every reader.
// `test:integration:run` builds once up front and sets the prebuilt seam.
if (process.env['AGENT_BUNDLE_PACKAGE_PREBUILT'] === '1') return;
buildPromise ??= execFile('pnpm', ['build'], {
cwd: workspaceRoot,
}).then(() => undefined);
Expand Down
12 changes: 8 additions & 4 deletions packages/workbench/tests/support/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import type { Server } from 'node:http';
* Teardown must not wait for the browser to release its connections: Node's
* `server.close()` blocks while any connection still has a request in flight,
* which hangs test teardown on two-core CI runners where the page can hold a
* request open at close time. Destroying connections first keeps `close()`
* deterministic regardless of browser state.
* request open at close time. Start `close()` first so the listener stops
* accepting new connections (a reconnecting browser could otherwise slip in a
* fresh request after the sweep), then destroy the held connections while the
* close completion is pending — the order Node's HTTP docs prescribe for
* `closeAllConnections()`.
*/
export const closeServer = async (server: Server): Promise<void> => {
server.closeAllConnections();
await new Promise<void>((resolve, reject) => {
const closed = new Promise<void>((resolve, reject) => {
server.close((error) => error === undefined ? resolve() : reject(error));
});
server.closeAllConnections();
await closed;
};
16 changes: 0 additions & 16 deletions rstest.integration-serial.config.ts

This file was deleted.

45 changes: 12 additions & 33 deletions rstest.integration-tests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* Test files that run real builds (Rslib/Rsbuild), pack and install the
* package, spawn child processes, or drive a browser. These share
* process-wide build caches and output paths, so they run serialized on one
* worker while every other file runs on the parallel unit pool.
* Test files that run real builds (Rslib/Rsbuild), spawn child processes, or
* drive a browser. They run through rstest.integration.config.ts: per-test
* fixtures via `mkdtemp`, servers on ephemeral ports, and only READS of the
* prebuilt shared artifacts (`packages/{agent-bundle,workbench}/dist`), so
* the pool can use multiple workers.
*
* A new test file belongs here the moment it runs `build()` from src/api or
* src/build (directly or via tests/support/build.ts), imports
* tests/support/workbench-e2e.ts or packed-release-harness.ts, spawns a
* process, or launches a browser; otherwise it defaults to the unit pool.
* tests/support/workbench-e2e.ts, spawns a process, or launches a browser;
* otherwise it defaults to the unit pool. Files that `npm pack` the package
* or import packed-release-harness.ts belong in packedTestFiles below.
*/
export const integrationTestFiles: readonly string[] = [
'packages/agent-bundle/tests/agent-api.test.ts',
Expand Down Expand Up @@ -50,7 +52,6 @@ export const integrationTestFiles: readonly string[] = [
'packages/workbench/tests/mcp-page-app-browser.test.ts',
'packages/workbench/tests/mcp-session-timeout.e2e.test.ts',
'packages/workbench/tests/overview.e2e.test.ts',
'packages/workbench/tests/packed-release.e2e.test.ts',
'packages/workbench/tests/playground-real.e2e.test.ts',
'packages/workbench/tests/rsbuild-closure.test.ts',
'packages/workbench/tests/rsbuild-workbench.test.ts',
Expand All @@ -62,38 +63,15 @@ export const integrationTestFiles: readonly string[] = [
'packages/workbench/tests/workbench-dev-command.test.ts',
];

/**
* Integration files that WRITE to workspace-shared locations and therefore
* cannot run alongside other integration files:
*
* - packed-release.e2e can run a root `pnpm build` (rewriting
* `packages/{agent-bundle,rsc-runtime,workbench}/dist`) when
* AGENT_BUNDLE_PACKAGE_PREBUILT is unset, and always runs `npm pack`
* plus a packed dev server on a pre-reserved (not ephemeral) port.
*
* They run on one worker via rstest.integration-serial.config.ts after the
* parallel pool finishes.
*/
export const serialIntegrationTestFiles: readonly string[] = [
'packages/workbench/tests/packed-release.e2e.test.ts',
];

/**
* Integration files safe on parallel workers: they create per-test fixtures
* with `mkdtemp`, bind servers on ephemeral ports (`port: 0` or rsbuild's
* silent free-port fallback), and only READ the prebuilt shared artifacts
* (`packages/workbench/dist`, `packages/agent-bundle/dist`).
*/
export const parallelIntegrationTestFiles: readonly string[] =
integrationTestFiles.filter((file) => !serialIntegrationTestFiles.includes(file));

/**
* Pack-and-install tests: each one runs `npm pack` (and usually a clean
* `npm install` of the tarball), which dominates the serialized integration
* pool. They run through the root `test:packed` / `test:packed:native`
* scripts instead — CI's release-gates job (`check:release`) and the
* native-host-smoke workflow keep them covered — and stay excluded from the
* parallel unit pool.
* parallel unit pool. packed-release.e2e lives here (not in the integration
* pool) so `pnpm test` and `check:release` don't each run the same long
* packed-browser suite; `rstest.config.ts` keeps `test:packed` on one worker.
*/
export const packedTestFiles: readonly string[] = [
'packages/agent-bundle/tests/dev-workbench-packaging.test.ts',
Expand All @@ -102,4 +80,5 @@ export const packedTestFiles: readonly string[] = [
'packages/agent-bundle/tests/public-api-packed.test.ts',
'packages/agent-bundle/tests/release-audit.test.ts',
'packages/agent-bundle/tests/rsc-runtime-optional-packaging.test.ts',
'packages/workbench/tests/packed-release.e2e.test.ts',
];
10 changes: 4 additions & 6 deletions rstest.integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { availableParallelism } from 'node:os';

import { defineConfig } from '@rstest/core';

import { parallelIntegrationTestFiles } from './rstest.integration-tests.ts';
import { integrationTestFiles } from './rstest.integration-tests.ts';
import { withAgentBundleRslibConfig } from './rstest.rslib.ts';

/**
Expand All @@ -25,14 +25,12 @@ const maxWorkers = Number.isSafeInteger(overrideWorkers) && overrideWorkers >= 1

/**
* Build- and process-running tests that only read workspace-shared artifacts;
* files that WRITE shared locations run serialized afterwards through
* rstest.integration-serial.config.ts (rstest has no per-project pool or
* isolate settings, so the split lives in two configs chained by
* `test:integration:run`).
* files that WRITE shared locations (root builds, `npm pack`) run through the
* single-worker `test:packed` script instead (see rstest.integration-tests.ts).
*/
export default defineConfig({
extends: withAgentBundleRslibConfig(),
include: [...parallelIntegrationTestFiles],
include: [...integrationTestFiles],
pool: { maxWorkers },
// Concurrent Chrome + dev-server + rsbuild pairs contend for cores, so
// parallel runs double the polling budgets (see tests/support/time-scale.ts)
Expand Down
Loading