Skip to content

Commit 4463f11

Browse files
committed
test(e2e): add a Vite host and Playwright harness
test:e2e packs the package into a throwaway Vite host built by e2e/helpers.ts and runs the Playwright specs in e2e/ against it, after the existing exports check. CI installs Chromium first.
1 parent 67795c4 commit 4463f11

11 files changed

Lines changed: 177 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
- run: bun run build
2121
- run: bun run contrast-test
2222
- run: bun run dep-guard
23+
- run: bunx playwright install --with-deps chromium
2324
- run: bun run test:e2e
2425
- name: node consumer smoke
2526
run: |

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ coverage/
77
.env.*
88
.DS_Store
99
build/
10+
test-results/
11+
playwright-report/

‎CONTRIBUTING.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ bun run check
1212
Contributors sign the [CLA](CLA.md) on their first PR; the CLA bot explains how.
1313

1414
`bun run test:e2e` checks the built package: every `exports` target exists and the
15-
root entry loads under Node with every optional peer blocked. Run `bun run build`
16-
first. CI also runs `bun run contrast-test` (theme contrast floors, reads
15+
root entry loads under Node with every optional peer blocked, then packs the
16+
package into a Vite host and runs the Playwright specs in `e2e/` against it. Each
17+
scenario is a route in `e2e/fixtures.tsx`. Run `bun run build` first, and
18+
`bunx playwright install chromium` once. CI also runs `bun run contrast-test` (theme contrast floors, reads
1719
`dist/styles.css`) and `bun run dep-guard` (forbidden imports, see `AGENTS.md`)
1820
after the build.
1921

‎bun.lock‎

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎e2e/fixtures.tsx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { ReactNode } from "react";
2+
3+
import { Button } from "@corbits/react-ui";
4+
5+
export const scenarios: Record<string, () => ReactNode> = {
6+
smoke: () => <Button>Ready</Button>,
7+
};

‎e2e/helpers.ts‎

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Bootstraps the Vite host the specs drive: packs this package, installs the
2+
// tarball into a throwaway app in a temp directory (teardown.ts removes it),
3+
// typechecks fixtures.tsx against the packed types and serves it on :4173.
4+
import { execFileSync } from "node:child_process";
5+
import { copyFileSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
6+
import { tmpdir } from "node:os";
7+
import { join } from "node:path";
8+
9+
const root = join(import.meta.dirname, "..");
10+
// Outside node_modules, where Vite would skip prebundling the host's own deps.
11+
export const host = join(tmpdir(), "react-ui-e2e-host");
12+
13+
const packageJson = {
14+
name: "react-ui-e2e-host",
15+
private: true,
16+
type: "module",
17+
// Exact pins instead of a lockfile: bun keeps a stale copy of a rewritten local tarball.
18+
dependencies: {
19+
"@corbits/react-ui": "file:./react-ui.tgz",
20+
"@radix-ui/react-dialog": "1.1.23",
21+
"@radix-ui/react-slot": "1.3.3",
22+
"lucide-react": "1.48.0",
23+
react: "19.3.0",
24+
"react-dom": "19.3.0",
25+
},
26+
devDependencies: {
27+
"@types/react": "19.3.0",
28+
"@types/react-dom": "19.3.0",
29+
typescript: "5.9.3",
30+
vite: "6.4.3",
31+
},
32+
};
33+
34+
const tsconfig = {
35+
compilerOptions: {
36+
target: "ES2022",
37+
lib: ["dom", "dom.iterable", "ES2022"],
38+
jsx: "react-jsx",
39+
module: "esnext",
40+
moduleResolution: "bundler",
41+
strict: true,
42+
noEmit: true,
43+
skipLibCheck: true,
44+
isolatedModules: true,
45+
},
46+
include: ["src"],
47+
};
48+
49+
const viteConfig = `export default {
50+
esbuild: { jsx: "automatic" },
51+
server: { port: 4173, strictPort: true },
52+
};
53+
`;
54+
55+
const indexHtml = `<!doctype html>
56+
<html lang="en">
57+
<head>
58+
<meta charset="UTF-8" />
59+
<title>react-ui e2e host</title>
60+
</head>
61+
<body>
62+
<div id="root"></div>
63+
<script type="module" src="/src/main.tsx"></script>
64+
</body>
65+
</html>
66+
`;
67+
68+
const mainTsx = `import "@corbits/react-ui/styles.css";
69+
70+
import { StrictMode } from "react";
71+
import { createRoot } from "react-dom/client";
72+
73+
import { scenarios } from "./fixtures.js";
74+
75+
const name = window.location.pathname.slice(1);
76+
const Scenario = scenarios[name];
77+
78+
createRoot(document.getElementById("root")!).render(
79+
<StrictMode>{Scenario ? <Scenario /> : <p>Unknown scenario: {name}</p>}</StrictMode>,
80+
);
81+
`;
82+
83+
function run(cwd: string, command: string, ...args: string[]): void {
84+
execFileSync(command, args, { cwd, stdio: "inherit" });
85+
}
86+
87+
if (import.meta.main) {
88+
rmSync(host, { recursive: true, force: true });
89+
mkdirSync(join(host, "src"), { recursive: true });
90+
run(
91+
root,
92+
"bun",
93+
"pm",
94+
"pack",
95+
"--quiet",
96+
"--filename",
97+
join(host, "react-ui.tgz"),
98+
);
99+
writeFileSync(
100+
join(host, "package.json"),
101+
JSON.stringify(packageJson, null, 2),
102+
);
103+
writeFileSync(join(host, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
104+
writeFileSync(join(host, "vite.config.mjs"), viteConfig);
105+
writeFileSync(join(host, "index.html"), indexHtml);
106+
writeFileSync(join(host, "src", "main.tsx"), mainTsx);
107+
copyFileSync(
108+
join(import.meta.dirname, "fixtures.tsx"),
109+
join(host, "src", "fixtures.tsx"),
110+
);
111+
run(host, "bun", "install");
112+
run(host, "bun", "x", "tsc", "--noEmit");
113+
run(host, "bun", "x", "vite");
114+
}

‎e2e/smoke.spec.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test("the host renders a component from the built package", async ({
4+
page,
5+
}) => {
6+
await page.goto("/smoke");
7+
await expect(page.getByRole("button", { name: "Ready" })).toBeVisible();
8+
});

‎e2e/teardown.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { rmSync } from "node:fs";
2+
3+
import { host } from "./helpers";
4+
5+
export default function globalTeardown(): void {
6+
rmSync(host, { recursive: true, force: true });
7+
}

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"format": "oxfmt",
7676
"format:check": "oxfmt --check",
7777
"test": "bun test src --pass-with-no-tests",
78-
"test:e2e": "bun test e2e",
78+
"test:e2e": "bun test ./e2e/exports.test.ts && playwright test",
7979
"check": "bun run typecheck && bun run lint && bun run format:check && bun run test",
8080
"build:css": "tailwindcss -i src/styles.css -o dist/styles.css && cp src/theme.css dist/theme.css",
8181
"dep-guard": "node scripts/dep-guard.mjs",
@@ -91,6 +91,7 @@
9191
"devDependencies": {
9292
"@happy-dom/global-registrator": "^20.11.2",
9393
"@ladle/react": "^5.1.1",
94+
"@playwright/test": "^1.63.0",
9495
"@radix-ui/react-dialog": "^1.1.15",
9596
"@radix-ui/react-dropdown-menu": "^2.1.16",
9697
"@radix-ui/react-slot": "^1.2.3",

‎playwright.config.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
// Serves the Vite host e2e/helpers.ts bootstraps from the packed tarball.
4+
export default defineConfig({
5+
testDir: "e2e",
6+
testMatch: "*.spec.ts",
7+
reporter: "list",
8+
globalTeardown: "./e2e/teardown.ts",
9+
use: { baseURL: "http://localhost:4173" },
10+
projects: [{ name: "chromium", use: devices["Desktop Chrome"] }],
11+
webServer: {
12+
command: "bun e2e/helpers.ts",
13+
url: "http://localhost:4173/smoke",
14+
reuseExistingServer: false,
15+
timeout: 180_000,
16+
},
17+
});

0 commit comments

Comments
 (0)