End-to-end tests and hydration benchmarks
Use managed Playwright suites for complete application behavior and the Node-only hydration benchmark for controlled comparisons between equivalent production deployments.
Test the complete application
Add e2e/products.spec.ts and import the managed fixtures through Brix:
import { expect, test } from "brix/test/e2e";
test("opens a product", async ({ page }) => {
await page.goto("/products/sku-123");
await expect(page.getByRole("heading", { name: "Everyday backpack" })).toBeVisible();
});
No extra dependency, config, package script, or install command is required for an application. Brix starts and stops its development server, prepares Chromium only when tests execute, and stores failure traces and screenshots under .brix/e2e/results. The server inherits the resolved test environment without loading development secrets. Use brix test --env or brix test --stage <stage> only when the suite needs configured provider values.
brix test e2e/products.spec.ts -t "opens a product"
brix test --project e2e
brix test --project e2e --list
brix test --project e2e --headed
brix test e2e/products.spec.ts --debug
Unqualified brix test and brix verify include discovered e2e/ suites. -t maps to Playwright's --grep after E2E selection. Keep a focused selection within one runner and workspace member.
Customize the E2E service and browser
Add brix.e2e.ts only when the managed defaults are insufficient:
import { defineBrixE2EConfig } from "brix/test/e2e";
export default defineBrixE2EConfig({
webServer: [],
use: { baseURL: "https://staging.example.test" },
});
The helper accepts native Playwright configuration. webServer replaces the managed server; use merges with managed browser, trace, and screenshot defaults. You can also configure browser projects, retries, reporting, or a custom test directory. Select --project e2e explicitly when focusing a file outside conventional e2e/ discovery.
Existing playwright.config.* files remain supported, but their authors own server settings and tests must still import from brix/test/e2e for automatic browser preparation. An existing test:e2e package script overrides automatic full-suite dispatch and must not call unqualified brix test recursively. Remote browsers, custom executables, and branded browsers remain caller-provisioned.
Use E2E tests for navigation, real browser/server integration, and behavior before hydration. Use the route harness for fast server-route coverage and component tests for isolated browser interactions.
Compare hydration architectures
runHydrationBenchmark from brix/test/benchmark compares selective islands with a full-page island using caller-supplied production URLs. It does not modify source, build, start, deploy, instrument, or rewrite applications.
Prepare two checkouts with the same Brix pin, lockfile, data, locale, CSS, environment, and application code. Keep selective islands in one. In the other, add "use island"; to the actual page component only if its browser graph contains no server-only code. Build and serve both production applications at separate URLs.
Create an application script:
import { expect, runHydrationBenchmark } from "brix/test/benchmark";
const islandsURL = process.env.ISLANDS_URL;
const fullPageURL = process.env.FULL_PAGE_URL;
if (!islandsURL || !fullPageURL) throw new Error("Set benchmark URLs");
await runHydrationBenchmark({
variants: [
{ name: "islands", baseURL: islandsURL },
{ name: "full-page", baseURL: fullPageURL },
],
runs: 10,
scenarios: [
{
name: "product",
path: "/products/example",
controls: { quantity: '[data-testid="quantity"]' },
verify: async (page) => {
const quantity = page.getByTestId("quantity");
await quantity.fill("2");
await expect(quantity).toHaveValue("2");
return { quantity: await quantity.inputValue() };
},
},
],
});
Run it with brix run scripts/benchmark-hydration.ts. Defaults are ten runs, cold and warm cache modes, desktop plus CPU/network-constrained profiles, a 30-second browser timeout, and unique output under .brix/benchmarks/hydration.
Control selectors must uniquely identify SSR elements inside Brix island boundaries. The probe associates them before mount and observes existing boundary events. It supports repeated and multi-root boundaries and independent slot children; a parent event does not make an unhydrated child ready. Client-only elements, portals, static controls, and elements absent from initial HTML are not valid readiness probes.
verify runs after readiness measurement and must return the same stable JSON-compatible functional snapshot for every sample and variant. Browser errors, ambiguous controls, missing readiness, assertion failures, and snapshot differences fail the run. Verification interactions are excluded from startup timing, so use E2E tests—not the benchmark—to specify pre-hydration behavior or interaction latency.
For lazy or idle controls, add a separate deferred object with named controls and an activate(page) callback. Its readiness includes caller-driven activation and is not automatic startup time. Avoid Playwright request routing because it disables HTTP caching and invalidates warm comparisons.
Interpret benchmark output
summary.md reports median important-control readiness, script execution, JavaScript transfer, and per-control readiness. results.json retains raw samples, environment/profile metadata, boundary events, completion timing, TTFB, FCP, payload sizes, resource inventories, and long-task excess.
Boundary readiness means synchronous mount completion—not paint, effects, data readiness, INP, or responsiveness. Resource Timing zeros can indicate cache hits or unavailable cross-origin timing; configure Timing-Allow-Origin where appropriate and compare encoded, decoded, and transferred values together.
One run is a smoke check only. Two variants × three profiles × two cache modes × ten repetitions produces 120 measured navigations per scenario plus warm priming. Keep the machine quiet, use non-mutating or isolated test accounts, retain compared revisions, and review reports for errors or sensitive paths before sharing them.