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
4 changes: 4 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
}
},
"overrides": [
{
"includes": ["**", "!tests/browser/page-errors.mjs"],
"plugins": ["./tests/browser/page-errors.grit"]
},
{
"includes": ["src/shared/design-system/icons/**"],
"linter": {
Expand Down
39 changes: 17 additions & 22 deletions src/bundled/workflows/workflows.journey.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createServer } from "../../../tests/browser/vite-server.mjs";
import react from "@vitejs/plugin-react";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
import { watchPageErrors } from "../../../tests/browser/page-errors.mjs";

let server;
let url;
Expand Down Expand Up @@ -98,8 +99,7 @@ function editorControls(page) {
test("workflow editor preserves YAML, resolves exact saves, retains conflicts and purges access", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.goto(url);
const editor = editorControls(page);
const { button, yaml, tab } = editor;
Expand Down Expand Up @@ -230,7 +230,7 @@ test("workflow editor preserves YAML, resolves exact saves, retains conflicts an
page.evaluate(() => window.workflowFixture.definitions.disposed()),
)
.toBe(true);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

// Real layout and native menu focus cannot be established by jsdom.
Expand Down Expand Up @@ -301,10 +301,10 @@ test("keyboard switches feed enabled-save confirmation and disabled readback", a
page,
browserName,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
const consoleErrors = [];
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
if (message.type() === "error") consoleErrors.push(message.text());
});
// Wide layout: the inspector sits beside the flow, so its controls and the
// footer share one keyboard layer.
Expand Down Expand Up @@ -442,7 +442,7 @@ test("keyboard switches feed enabled-save confirmation and disabled readback", a
await expect(dialog).toContainText("It will run for every new message");
expect(await saves()).toBe(3);
await page.keyboard.press("Escape");
expect(errors).toEqual([]);
expect([...errors.unexplained(), ...consoleErrors]).toEqual([]);
});

test("history stays lazy and paged; acknowledging an unknown run never repeats it", async ({
Expand Down Expand Up @@ -509,8 +509,7 @@ test("history stays lazy and paged; acknowledging an unknown run never repeats i
test("real session page under StrictMode fences community changes, warns before discarding a dirty draft and purges access", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.goto(url.replace("/fixture.html", "/session-fixture.html"));
const editor = editorControls(page);
const { button } = editor;
Expand Down Expand Up @@ -583,14 +582,13 @@ test("real session page under StrictMode fences community changes, warns before
expect(await page.locator("body").innerText()).not.toContain(
"Revoked private text",
);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

test("landing batches 129 channels into two workflow reads", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.goto(url.replace("/fixture.html", "/session-fixture.html?many"));
await expect(
page.getByRole("button", { name: "Open Fixture A helper", exact: true }),
Expand All @@ -600,7 +598,7 @@ test("landing batches 129 channels into two workflow reads", async ({
page.evaluate(() => window.workflowSessionFixture.definitionQueries()),
)
.toBe(2);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

test("landing scan survives channel presentation churn without restarting", async ({
Expand Down Expand Up @@ -1234,8 +1232,7 @@ test("invalid timeout text stays in the draft and blocks saves in both editor mo
test("schedule presets round-trip into YAML and warn before enabling a frequent one", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
// Wide layout keeps the trigger inspector beside the footer tabs.
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(url);
Expand Down Expand Up @@ -1351,14 +1348,13 @@ test("schedule presets round-trip into YAML and warn before enabling a frequent
parseYaml(await page.evaluate(() => window.workflowFixture.input().yaml))
.trigger,
).toEqual({ on: "schedule", interval: "1h" });
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

test("a webhook save shows its one-time secret once and asks before leaving it behind", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.addInitScript(() => {
window.__copied = [];
Object.defineProperty(navigator, "clipboard", {
Expand Down Expand Up @@ -1467,7 +1463,7 @@ test("a webhook save shows its one-time secret once and asks before leaving it b
),
).toBeUndefined();
await expect(button("Save changes")).toBeEnabled();
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

// Browser boundary: the routed page must deliver a real session's late receipt
Expand Down Expand Up @@ -1690,8 +1686,7 @@ test("generic Outbox offers message retry but no workflow replay", async ({
test("a created workflow saves, reads back exactly and reopens unchanged", async ({
page,
}) => {
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(url);
const editor = editorControls(page);
Expand Down Expand Up @@ -1759,7 +1754,7 @@ test("a created workflow saves, reads back exactly and reopens unchanged", async
await tab("YAML").click();
await expect(yaml).toHaveValue(submitted);
expect(await saves()).toBe(1);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});

// Browser-only boundary: nested modal hit testing, focus guards and return
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/agent-control.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { test, expect } from "@playwright/test";
import { npubEncode } from "nostr-tools/nip19";
import { createServer } from "./vite-server.mjs";
import config from "../fixtures/agent-control.vite.mjs";
import { watchPageErrors } from "./page-errors.mjs";

async function closeEditor(page) {
const dialog = page.getByRole("dialog", { name: "Edit agent", exact: true });
Expand Down Expand Up @@ -48,8 +49,7 @@ test("local controls preserve drafts, confirm operations and distinguish disable
logLevel: "error",
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await server.listen();
try {
await page.goto(
Expand Down Expand Up @@ -234,7 +234,7 @@ test("local controls preserve drafts, confirm operations and distinguish disable
await expect(
libraryCard.getByRole("button", { name: /Actions|Start|Edit/ }),
).toHaveCount(0);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/agent-editor-grid.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test, expect } from "@playwright/test";
import { createServer } from "./vite-server.mjs";
import config from "../fixtures/agent-control.vite.mjs";
import { watchPageErrors } from "./page-errors.mjs";

test("existing grid opens the focused editor, selects a model and saves/reopens", async ({
page,
Expand All @@ -12,8 +13,7 @@ test("existing grid opens the focused editor, selects a model and saves/reopens"
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
await server.listen();
const errors = [];
page.on("pageerror", (e) => errors.push(String(e)));
const errors = watchPageErrors(page);
try {
await page.goto(
`http://127.0.0.1:${server.httpServer.address().port}/tests/fixtures/agent-control.html`,
Expand Down Expand Up @@ -312,7 +312,7 @@ test("existing grid opens the focused editor, selects a model and saves/reopens"
.getByRole("textbox", { name: "Name", exact: true })
.press("Escape");
await expect(dialog).toHaveCount(0); // clean Escape still closes
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/agent-models.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test, expect } from "@playwright/test";
import { createServer } from "./vite-server.mjs";
import config from "../fixtures/agent-control.vite.mjs";
import { watchPageErrors } from "./page-errors.mjs";

test("on-demand model search preserves custom drafts and fences cancellation/context changes", async ({
page,
Expand All @@ -12,8 +13,7 @@ test("on-demand model search preserves custom drafts and fences cancellation/con
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
await server.listen();
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
try {
// Install before page timers exist; pause at a fixed later instant below.
await page.clock.install({ time: new Date("2026-01-01T00:00:00Z") });
Expand Down Expand Up @@ -239,7 +239,7 @@ test("on-demand model search preserves custom drafts and fences cancellation/con
window.agentModelsFixture.calls.filter((x) => x === "cancel").length,
),
).toBeGreaterThan(before);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/agents.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { npubEncode } from "nostr-tools/nip19";
import { createServer } from "./vite-server.mjs";
import react from "@vitejs/plugin-react";
import { fileURLToPath } from "node:url";
import { watchPageErrors } from "./page-errors.mjs";

test("Old Buzz library reads the existing library with exact linked keys and session-safe retries", async ({
page,
Expand All @@ -15,8 +16,7 @@ test("Old Buzz library reads the existing library with exact linked keys and ses
logLevel: "error",
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
try {
await server.listen();
await page.goto(
Expand Down Expand Up @@ -240,7 +240,7 @@ test("Old Buzz library reads the existing library with exact linked keys and ses
.click();
await expect(page.getByRole("status")).toContainText("Library cleared");
await expect(page.getByRole("article")).toHaveCount(0);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/avatar-edit.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test, expect } from "./source-fixture.mjs";
import { readFile } from "node:fs/promises";
import { watchPageErrors } from "./page-errors.mjs";

// Browser boundary: nested overlay hit-testing/focus, canvas image preparation,
// lazy shadow-DOM emoji picker, and narrow viewport geometry in both engines.
Expand All @@ -9,8 +10,7 @@ test("shared human and agent avatar upload, scoped save, publication retry and n
const artwork = await readFile(
new URL("../fixtures/design-system/assets/avatar.png", import.meta.url),
);
const errors = [];
page.on("pageerror", (e) => errors.push(e.message));
const errors = watchPageErrors(page);
await page.route("**/api/relay/**", (route) => route.abort());
await page.goto("/tests/fixtures/agent-control.html?avatars");
await page
Expand Down Expand Up @@ -156,5 +156,5 @@ test("shared human and agent avatar upload, scoped save, publication retry and n
"profile",
]);
expect(result.actions[0].payload.edit.picture).toBe(result.agent.picture);
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
});
6 changes: 3 additions & 3 deletions tests/browser/conversation.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { run } from "./run-command.mjs";
import { mkdtemp, cp, readFile, writeFile, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { watchPageErrors } from "./page-errors.mjs";

const root = fileURLToPath(new URL("../../", import.meta.url));
test("independent packed author consumer and native-installed contribution survive removal, replacement and retarget", async ({
Expand Down Expand Up @@ -138,8 +139,7 @@ test("independent packed author consumer and native-installed contribution survi
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
await server.listen();
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.route("**/proof-media/**", (route) =>
route.fulfill({
contentType: "image/svg+xml",
Expand Down Expand Up @@ -480,7 +480,7 @@ test("independent packed author consumer and native-installed contribution survi
.getByRole("button", { name: "Channels" })
.click();
await expect(draft).toHaveJSProperty("value", "B draft");
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
await writeFile(
test.info().outputPath("boundary-proof.json"),
JSON.stringify(
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/custom-emoji-authoring.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createHash } from "node:crypto";
import { fileURLToPath } from "node:url";
import { finalizeEvent, generateSecretKey, getPublicKey } from "nostr-tools";
import { verifyEvent } from "nostr-tools/pure";
import { watchPageErrors } from "./page-errors.mjs";
import { relayBrokerPlugin } from "../../dev/relay-broker.mjs";
import { fixtureAliases, fixtureRelayUrl } from "../relay-config.ts";

Expand Down Expand Up @@ -224,8 +225,7 @@ test("adds custom emoji through the production broker, then uses, replaces, retr
],
});
await server.listen();
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
const origin = `http://127.0.0.1:${server.httpServer.address().port}`;
await page.goto(`${origin}/tests/fixtures/custom-emoji.html`);
const primary = "https://primary.example";
Expand Down Expand Up @@ -458,7 +458,7 @@ test("adds custom emoji through the production broker, then uses, replaces, retr
),
contentType: "application/json",
});
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server?.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/diffs.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { test, expect } from "@playwright/test";
import { createServer } from "./vite-server.mjs";
import react from "@vitejs/plugin-react";
import { fileURLToPath } from "node:url";
import { watchPageErrors } from "./page-errors.mjs";

// Real table geometry/scroll containment, modal portal/focus return and theme
// rendering need a browser. Patch matrices and plugin failures stay in Vitest.
Expand All @@ -25,8 +26,7 @@ test("diff preview expands in both layouts and keeps focus and scroll containmen
});
try {
await server.listen();
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.goto(
`http://127.0.0.1:${server.httpServer.address().port}/tests/fixtures/diffs.html`,
);
Expand Down Expand Up @@ -166,7 +166,7 @@ test("diff preview expands in both layouts and keeps focus and scroll containmen
} finally {
await page.keyboard.up("ArrowRight");
}
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
await server.close();
}
Expand Down
6 changes: 3 additions & 3 deletions tests/browser/emoji.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
import { mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { watchPageErrors } from "./page-errors.mjs";

test("community picker uses keyboard, proxy thumbnails, event-local history and scoped send/reply tags", async ({
browserName,
Expand All @@ -24,8 +25,7 @@ test("community picker uses keyboard, proxy thumbnails, event-local history and
logLevel: "error",
server: { host: "127.0.0.1", port: 0, strictPort: false },
});
const errors = [];
page.on("pageerror", (error) => errors.push(String(error)));
const errors = watchPageErrors(page);
await page.route("**/emoji-media/**", async (route) => {
if (route.request().url().includes("broken.png"))
return route.fulfill({ status: 404, body: "missing" });
Expand Down Expand Up @@ -1029,7 +1029,7 @@ test("community picker uses keyboard, proxy thumbnails, event-local history and
() => window.emojiFixture.report.publications.at(-1).event.content,
),
).toBe("😀 🙏 👏 hello");
expect(errors).toEqual([]);
expect(errors.unexplained()).toEqual([]);
} finally {
try {
await server?.close();
Expand Down
Loading
Loading