Skip to content

Commit 04af48e

Browse files
committed
feat: cleanup browser, mcp, liveviewer, run-test and tests
1 parent e380846 commit 04af48e

File tree

151 files changed

+5355
-5844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+5355
-5844
lines changed

.github/scripts/e2e.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import assert from "node:assert/strict";
2+
import { createServer } from "node:http";
3+
import * as path from "node:path";
4+
import { fileURLToPath } from "node:url";
5+
import * as Effect from "effect/Effect";
6+
import * as Schema from "effect/Schema";
7+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
8+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
9+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
10+
import { TestReport } from "@expect/shared/models";
11+
import { Console, Layer, Stream } from "effect";
12+
import { Reporter, GitRepoRoot } from "@expect/supervisor";
13+
import { RrVideo } from "@expect/browser";
14+
import * as fs from "node:fs";
15+
16+
const __dirname = dirname(fileURLToPath(import.meta.url));
17+
18+
const WEBSITE_OUT_DIR = join(__dirname, "../../apps/website/out");
19+
const CLI_PATH = join(__dirname, "../../apps/cli/dist/index.js");
20+
const TIMEOUT_MS = 900_000;
21+
const TEST_CASE = process.env.TEST_CASE ?? "website";
22+
const ARTIFACTS_DIR = `/tmp/test-artifacts/${TEST_CASE}`;
23+
24+
const WEBSITE_INSTRUCTION = `Test the expect.dev marketing website at http://localhost:3000.
25+
Run each item below as a separate test step. If a step fails, record the failure with evidence but continue to the next step.
26+
27+
1. Homepage loads — navigate to http://localhost:3000, verify the page renders with a hero section and install commands visible.
28+
2. View demo — click the "View demo" button/link on the homepage, verify it navigates to /replay?demo=true and the replay player loads with demo content.
29+
3. Replay controls — on the /replay?demo=true page, verify play/pause button works, speed selector is present, and step list is visible.
30+
4. Copy button — go back to the homepage, click the copy button next to the install command, verify the clipboard contains the expected command text.
31+
5. Theme toggle — click the theme toggle to switch to dark mode, verify the background color changes. Switch back to light mode.
32+
6. Footer links — verify the footer contains links to GitHub (github.com/millionco/expect) and X (x.com/aidenybai) with target="_blank".
33+
7. Legal pages — navigate to /terms, /privacy, and /security in sequence. Verify each page loads with text content.
34+
8. Mobile viewport — resize the viewport to 375x812, navigate to the homepage, verify the page renders without horizontal scrollbar and key content is visible.`;
35+
36+
const DOGFOOD_INSTRUCTION = `Visit http://localhost:7681 which shows the expect CLI running in a web terminal (xterm.js). \
37+
This is an interactive terminal UI for a browser testing tool. Test the FULL workflow: \
38+
(1) Verify the TUI renders with a logo/header and input prompt. \
39+
(2) Type a test instruction like 'test the homepage at http://localhost:3000' into the input field and submit it. \
40+
(3) The CLI should generate a test plan — verify the plan review screen appears with test steps. \
41+
(4) Approve the plan (press Enter or the confirm key). \
42+
(5) Watch the execution progress — verify steps are being executed with status updates. \
43+
(6) Wait for completion and verify the results screen shows pass/fail outcomes. \
44+
Note: This is a terminal rendered in xterm.js. Type by clicking the terminal and using keyboard input.`;
45+
46+
const TEST_INSTRUCTION = TEST_CASE === "dogfood" ? DOGFOOD_INSTRUCTION : WEBSITE_INSTRUCTION;
47+
48+
const layerServer = Layer.effectDiscard(
49+
Effect.acquireRelease(
50+
Effect.promise(() =>
51+
import("serve-handler").then(({ default: handler }) => {
52+
const server = createServer((req, res) =>
53+
handler(req, res, { public: WEBSITE_OUT_DIR })
54+
);
55+
return new Promise<typeof server>((resolve) =>
56+
server.listen(3000, () => resolve(server))
57+
);
58+
})
59+
),
60+
(server) =>
61+
Effect.promise(
62+
() => new Promise<void>((resolve) => server.close(() => resolve()))
63+
)
64+
)
65+
);
66+
67+
const main = Effect.gen(function* () {
68+
const reporter = yield* Reporter;
69+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
70+
const stdout = yield* ChildProcess.make("node", [
71+
CLI_PATH,
72+
"--ci",
73+
"--verbose",
74+
"--reporter",
75+
"json",
76+
"--timeout",
77+
String(TIMEOUT_MS),
78+
"--test-id",
79+
TEST_CASE,
80+
"-m",
81+
TEST_INSTRUCTION,
82+
]).pipe(
83+
spawner.streamString,
84+
Stream.tap((line) => Console.log(line)),
85+
Stream.runCollect,
86+
Effect.map((lines) => lines.join("\n"))
87+
);
88+
89+
const report = yield* Schema.decodeEffect(TestReport.json)(stdout);
90+
91+
const resultsDir = "/tmp/expect-results";
92+
fs.mkdirSync(resultsDir, { recursive: true });
93+
fs.writeFileSync(join(resultsDir, `${TEST_CASE}.json`), stdout);
94+
95+
console.log(`\nTest Report: ${report.status}`);
96+
console.log(`Title: ${report.title}`);
97+
console.log(`Summary: ${report.summary}`);
98+
console.log(`Steps: ${report.steps.length}`);
99+
for (const step of report.steps) {
100+
const icon =
101+
step.status === "passed" ? "✓" : step.status === "failed" ? "✗" : "⏭";
102+
console.log(` ${icon} ${step.title} (${step.status})`);
103+
}
104+
105+
console.log("\nAssertions:");
106+
assert.ok(
107+
report.status === "passed" || report.status === "failed",
108+
"status is passed or failed"
109+
);
110+
assert.ok(report.title.length > 0, "title is non-empty");
111+
assert.ok(report.summary.length > 0, "summary is non-empty");
112+
assert.ok(report.steps.length > 0, "has at least one step");
113+
114+
fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
115+
yield* reporter.exportVideo(report, {
116+
exportPathOverride: join(ARTIFACTS_DIR, `${TEST_CASE}.mp4`),
117+
}).pipe(
118+
Effect.catchTag("RrVideoConvertError", (error) => {
119+
console.log(`Video conversion failed (non-fatal): ${error.message}`);
120+
return Effect.void;
121+
}),
122+
Effect.catchTag("PlatformError", (error) => {
123+
console.log(`Video conversion failed (non-fatal): ${error.message}`);
124+
return Effect.void;
125+
})
126+
);
127+
128+
yield* report.assertSuccess();
129+
}).pipe(
130+
Effect.provide(Reporter.layer),
131+
Effect.provide(RrVideo.layer),
132+
Effect.provide(Layer.succeed(GitRepoRoot, process.cwd())),
133+
Effect.provide(NodeServices.layer)
134+
);
135+
136+
const mainWithServer = TEST_CASE === "website"
137+
? main.pipe(Effect.provide(layerServer))
138+
: main;
139+
140+
NodeRuntime.runMain(mainWithServer);

.github/scripts/validate-scenario.sh

Lines changed: 0 additions & 145 deletions
This file was deleted.

.github/workflows/e2e.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ jobs:
3333
GITHUB_ACTIONS: "1"
3434
- name: Build browser runtime
3535
run: pnpm --filter @expect/browser run build
36+
- name: Build website
37+
run: pnpm --filter @expect/website run build
3638
- name: Install Playwright browsers
3739
run: npx playwright install --with-deps chromium webkit firefox
3840
- name: Run E2E tests

0 commit comments

Comments
 (0)