feat(workspace): add shared Drive browsing editing and Worker grants

This commit is contained in:
2026-10-09 16:40:50 +09:00
parent c79506b60f
commit e4daa4aaa7
46 changed files with 7957 additions and 35 deletions
@@ -0,0 +1,50 @@
# T-724 browser fixture validation
These tests serve the real static production Web shell against a loopback, in-memory API fixture. They are **not actual Backend/DB/LocalFileSystem authorization or round-trip evidence**. The parent owns the separate real API checks.
## Runtime prerequisites
Deno and Playwright Chromium must be available. The server binds loopback only and owns/cleans up its shared Home fixture subprocess. Tests use an ephemeral port and independent fixture state; no real workspace data, credentials, git operations, environment test switches, or UI/CSS fixture branches are used.
The parent must confirm UI readiness before the production build and browser run. Tests intentionally never build automatically.
```sh
# Narrow API-fixture compatibility checks (safe before UI readiness)
deno test --config web/workspace/test/drive-browser/deno.json --allow-read web/workspace/test/drive-browser/fixture-api.test.ts
# After explicit parent UI-ready signal and successful production build only:
deno test --config web/workspace/test/drive-browser/deno.json --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys web/workspace/test/drive-browser/drive.browser.test.ts
# Responsive audit, with optional frozen production site and artifact suffix:
deno test --config web/workspace/test/drive-browser/deno.json --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys web/workspace/test/drive-browser/responsive.browser.test.ts -- web/workspace/.svelte-kit/t724/verified-build/site final-reviewed
# Official tools/web-ux bundles; no build is performed by this runner.
deno run --config web/workspace/test/drive-browser/deno.json --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys web/workspace/test/drive-browser/capture-visual.ts before <unique-run-suffix>
deno run --config web/workspace/test/drive-browser/deno.json --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys web/workspace/test/drive-browser/capture-visual.ts after <unique-run-suffix> [workdir-relative-production-site]
```
## Fixture API
Start `tools/web-ux/drive-fixture/server.ts <port> <build-root>` with `--allow-net --allow-read --allow-run --allow-env`. It extends the shared Home/settings API fixture through a loopback proxy and implements Drive root/list/metadata/search/read-text/read-chunk/download/mutate/upload/request-status/grants APIs. Response wire types derive from generated Drive DTOs; current client parsers are exercised by the fixture compatibility tests.
- Root ID `9007199254740993` tests lossless decimal references.
- `2`: folder 資料; `3`: Markdown README; `4`: text inside folder 2.
- `5`: valid synthetic 1200×800 `landscape.png` (16,813 compressed bytes), generated as real binary PNG data by `tools/web-ux/drive-fixture/raster.ts`; `6`: empty text; `7`: undecodable PNG; `8`: SVG download-only; `9`: text exceeding 64 KiB; `10`: long Japanese name; `11`: valid 64 KiB NUL text whose escaped JSON exceeds 256 KiB but stays below the separate 512 KiB JSON bound.
- `home-member` and `*readonly*`: reads succeed, mutations are typed HTTP 403. No metadata permission flag is invented. The UI reacts to a denied operation, not speculative permission inference.
- `*denied*`: typed HTTP 403 root/read; `*offline*`/`home-error`: typed HTTP 503; `*empty*`: empty successful root list; `*paged*`: 240 additional nodes.
- `POST /__fixture/fault` JSON `{workspace, mode}`: one-shot `conflict` or committed `unknown` mutation response; persistent `image_error` binary fetch failure.
- `home-owner`/`home-member`/`home-long`: representative authoritative Worker catalogs; REST and subscription projections come from the same generated-shape records. `home-long` includes long display names and opaque Runtime/Worker IDs. `workers-error` intentionally fails catalog reads with HTTP 503.
- `GET /__fixture/log`: observable API request IDs/methods/paths/node IDs/CAS revisions for identity and no-resend assertions.
- `POST /__fixture/reset`: fresh in-memory state.
Request delay/loss tests intercept the API boundary with Playwright and synchronize on gates, not guessed sleeps. They let the fixture commit before dropping/holding the response, then verify receipt query without blind upload retry.
## Visual evidence
Preserved production baseline: `.svelte-kit/t724/before-build/{site,output,build.log}`. Official bundles live below `.svelte-kit/t724/visual/`. Each contains screenshots, console/request/page diagnostics, accessibility snapshots, screenshot hashes, source revision/dirty context, and `review-context.json`.
Home/sidebar and Workers are captured under identical owner/member/error/empty/long conditions, light/dark at 1440/768/390/320. After captures add Drive owner/member/empty/error/denied/paged/Markdown/image/image-error/truncated surfaces, pinned rename/replace revision forms, and owner grant selection/read-only/read-write/revoked states. Expected injected 503/403 diagnostics and superseded breadcrumb metadata aborts remain in official context; `diagnostic-disposition.json` explicitly separates unexpected diagnostics. CLI `completed-with-errors` is not relabeled a visual pass.
The production shell scrolls `main` internally, so `fullPage` does not expand that scroll panel. Capture points use ordinary keyboard focus to show the actual form or the top denial state; no CSS/DOM screenshot override is applied. The responsive audit separately records document/main/table-region geometry, visible keyboard focus, and page errors across the full theme/viewport matrix. Raster checks wait for actual decoding, assert natural dimensions 1200×800, preserve the 3:2 aspect ratio, and constrain the rendered box by both the available width and 60vh height at all four widths/both themes.
Parent and reviewer must open images and inspect context before asserting visual acceptance. In particular, a successful capture command is not sufficient and HTTP 200 does not excuse visible UI errors.
@@ -0,0 +1,113 @@
/// <reference lib="dom" />
// Captures the preserved production baseline; no UI mutations or Backend connection.
import { chromium } from "playwright";
import { dirname, fromFileUrl, join } from "@std/path";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../../..");
const artifactRoot = join(root, "web/workspace/.svelte-kit/t724/before-build");
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const server = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-net",
"--allow-read",
join(root, "tools/web-ux/browser-tests/workspace_home_fixture_server.ts"),
String(port),
join(artifactRoot, "site"),
],
stdout: "null",
stderr: "piped",
}).spawn();
const origin = `http://127.0.0.1:${port}`;
const evidence: unknown[] = [];
try {
await Deno.mkdir(join(artifactRoot, "screenshots"), { recursive: true });
const deadline = Date.now() + 10000;
while (true) {
try {
const response = await fetch(`${origin}/health`);
await response.body?.cancel();
if (response.ok) break;
} catch { /* startup */ }
if (Date.now() > deadline) throw new Error("Home fixture did not start");
await new Promise((resolve) => setTimeout(resolve, 50));
}
const browser = await chromium.launch({ headless: true });
try {
for (const colorScheme of ["light", "dark"] as const) {
for (const width of [1440, 768, 390, 320]) {
const context = await browser.newContext({
viewport: { width, height: 900 },
colorScheme,
reducedMotion: "reduce",
});
try {
const page = await context.newPage();
// Shared Home server predates the sidebar's authoritative /workers fetch.
// Complete that API fixture only, without changing the production shell.
await page.route("**/api/w/*/workers", async (route) => {
const workspaceId = new URL(route.request().url()).pathname.split("/")[3];
await route.fulfill({
json: {
workspace_id: workspaceId,
items: [],
limit: 100,
source: "fixture",
diagnostics: [],
},
});
});
for (const state of ["owner", "member", "error", "empty"]) {
const errors: string[] = [];
const record = (error: Error) => errors.push(String(error));
page.on("pageerror", record);
await page.goto(`${origin}/w/home-${state}`);
if (state === "error") {
await page.locator("main [role=alert]").first().waitFor();
} else {for (
const feed of ["home-attention", "home-active", "home-recent"]
) await page.locator(`[data-feed-ready="${feed}"]`).waitFor();}
await page.evaluate(() => document.fonts.ready);
const filename = `home-sidebar-workers-${state}-${colorScheme}-${width}.png`;
await page.screenshot({
path: join(artifactRoot, "screenshots", filename),
fullPage: true,
});
evidence.push({
filename,
state,
colorScheme,
width,
title: await page.locator("main h1").allTextContents(),
sidebar: await page.locator("aside").allTextContents(),
errors,
overflow: await page.evaluate(() =>
document.documentElement.scrollWidth > innerWidth
),
});
page.off("pageerror", record);
}
} finally {
await context.close();
}
}
}
} finally {
await browser.close();
}
await Deno.writeTextFile(
join(artifactRoot, "capture.json"),
JSON.stringify(
{ authority: "fixture, not actual Backend", evidence },
null,
2,
),
);
console.log(
`Captured ${evidence.length} baseline screenshots at ${artifactRoot}/screenshots`,
);
} finally {
server.kill("SIGTERM");
await server.status;
}
@@ -0,0 +1,427 @@
// Official tools/web-ux capture bundles (console, request, accessibility, context).
// Does not build. Before always uses isolated saved site; after requires explicit parent readiness.
import { dirname, fromFileUrl, join } from "@std/path";
import type { RouteScenario } from "../../../../tools/web-ux/src/types.ts";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../../..");
const stage = Deno.args[0];
if (stage !== "before" && stage !== "after") {
throw new Error("usage: capture-visual.ts before|after");
}
const output = join(root, "web/workspace/.svelte-kit/t724/visual");
await Deno.mkdir(output, { recursive: true });
const runSuffix = Deno.args[1] ? `-${Deno.args[1]}` : "";
for (const scheme of ["light", "dark"]) {
const runId = `t724-${stage}-${scheme}${runSuffix}`;
const routes = ["owner", "member", "empty", "error", "long"].map((
state,
): RouteScenario => ({
id: `home-${state}`,
label: `Home/sidebar Workers ${state}`,
path: `/w/home-${state}`,
goal:
"Compare unchanged Home/sidebar/Workers composition under identical API fixture conditions.",
dataState: state === "error"
? "Intentional 503 for Home merge-requests; visible scoped error expected."
: `Shared Home ${state}; representative authoritative Workers (empty for new workspace).`,
ready: {
kind: "selector",
selector:
".workspace-home:has([data-feed-ready='home-active']):has([data-feed-ready='home-recent'])",
},
capturePoints: [{
id: "initial",
label: "Home and Workers sidebar",
fullPage: true,
}],
}));
for (const state of ["owner", "member", "empty", "error", "long"]) {
routes.push({
id: `workers-${state}`,
label: `Workers ${state}`,
path: state === "error" ? "/w/workers-error/workers" : `/w/home-${state}/workers`,
goal: "Compare the existing Worker management surface with identical authoritative catalogs.",
dataState:
`Shared Home ${state} identity; representative Worker list (empty or 503 when requested). Owner controls follow workspace permission projection.`,
ready: {
kind: "selector",
selector: state === "empty"
? ".workers-page:has-text('No Workers are visible.')"
: state === "error"
? ".workers-page .section-state.error"
: ".workers-page .workers-table tbody tr",
},
capturePoints: [{ id: "initial", label: "Workers", fullPage: true }],
});
}
if (stage === "after") {
routes.push({
id: "drive-form-revision",
label: "Pinned rename and replace form revisions",
path: "/w/home-owner/drive/3",
goal: "Show the form's locked read revision and explicit destination/file controls.",
dataState:
"Revision 1 read from fixture metadata; no mutation is sent by these capture interactions.",
ready: { kind: "selector", selector: '.drive-page[data-drive-ready="true"]' },
capturePoints: [
{
id: "rename",
label: "Rename / move pinned revision",
fullPage: true,
interaction: [
{ action: "click", selector: '.drive-toolbar button:has-text("Rename / move")' },
{
action: "wait",
ready: { kind: "selector", selector: '.drive-form:has-text("Expected revision 1.")' },
},
{ action: "press", selector: ".drive-form input:not([inputmode])", key: "Shift" },
],
},
{
id: "replace",
label: "Replace file pinned revision",
fullPage: true,
interaction: [
{ action: "click", selector: '.drive-form button:has-text("Cancel form")' },
{ action: "click", selector: '.drive-toolbar button:has-text("Replace file")' },
{
action: "wait",
ready: { kind: "selector", selector: '.drive-form:has-text("Expected revision 1.")' },
},
{ action: "press", selector: ".drive-form input[type=file]", key: "Shift" },
],
},
],
});
routes.push({
id: "worker-grants-owner",
label: "Owner Worker Drive grant lifecycle",
path: "/w/home-owner/workers",
goal:
"Use the authoritative Worker catalog to grant read-only/read-write access and revoke it without conflating Profile enablement.",
dataState:
"Representative REST catalog and matching protocol snapshot; in-memory grant API, not actual Backend.",
ready: { kind: "selector", selector: ".drive-grants select" },
capturePoints: [
{
id: "selected",
label: "Selected Worker without grants",
fullPage: true,
interaction: [
{
action: "press",
selector: ".drive-grants .worker-select select",
key: "ArrowDown",
},
{
action: "press",
selector: ".drive-grants .worker-select select",
key: "Enter",
},
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-grants button:has-text("Add Drive grant")',
},
},
],
},
{
id: "read-only",
label: "Read-only grant",
fullPage: true,
interaction: [
{
action: "click",
selector: '.drive-grants button:has-text("Add Drive grant")',
},
{ action: "click", selector: ".drive-grants button[type=submit]" },
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-grants strong:has-text("Read only")',
},
},
],
},
{
id: "read-write",
label: "Read-write grant after explicit revoke",
fullPage: true,
interaction: [
{
action: "click",
selector: '.drive-grants button[aria-label^="Revoke Drive grant"]',
},
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-grants:has-text("No active Drive grants for this Worker.")',
},
},
{
action: "click",
selector: '.drive-grants button:has-text("Add Drive grant")',
},
{
action: "press",
selector: ".drive-grants form select",
key: "ArrowDown",
},
{
action: "press",
selector: ".drive-grants form select",
key: "Enter",
},
{ action: "click", selector: ".drive-grants button[type=submit]" },
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-grants strong:has-text("Read and write")',
},
},
],
},
{
id: "revoked",
label: "Grant revoked",
fullPage: true,
interaction: [
{
action: "click",
selector: '.drive-grants button[aria-label^="Revoke Drive grant"]',
},
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-grants:has-text("No active Drive grants for this Worker.")',
},
},
],
},
],
});
for (
const [state, workspace, nodeId] of [
["owner", "home-owner", ""],
["member", "home-member", ""],
["empty", "home-empty", ""],
["error", "home-error", ""],
["denied", "drive-denied", ""],
["paged", "drive-paged", ""],
["markdown", "home-owner", "/3"],
["image", "home-owner", "/5"],
["image-error", "home-owner", "/7"],
["truncated", "home-owner", "/9"],
]
) {
routes.push({
id: `drive-${state}`,
label: `Drive ${state}`,
path: `/w/${workspace}/drive${nodeId}`,
goal:
"Review real production Drive layout, bounded safe preview and distinct failure states.",
dataState: `In-memory Drive API ${state}, explicitly NOT actual Backend.`,
ready: {
kind: "selector",
selector: state === "image-error"
? "main .drive-page [role=alert]"
: 'main [data-drive-ready="true"]',
},
capturePoints: state === "member"
? [
{
id: "initial",
label: "Member before authority denial",
fullPage: true,
},
{
id: "denied-write",
label: "Member after typed write denial",
fullPage: true,
interaction: [
{
action: "click",
selector: '.drive-page button:has-text("New folder")',
},
{
action: "fill",
selector: ".drive-form input:not([type=file])",
value: "member-denied-folder",
},
{
action: "click",
selector: ".drive-form button[type=submit]",
},
{
action: "wait",
ready: {
kind: "selector",
selector: '.drive-page:has-text("Read only / access denied by Backend")',
},
},
{
action: "press",
selector: ".drive-heading button",
key: "Shift",
},
],
},
]
: [{ id: "initial", label: `Drive ${state}`, fullPage: true }],
});
}
}
const port = scheme === "light" ? 17241 : 17242;
const scenario = {
schemaVersion: 1,
id: runId,
title: `T-724 fixture-only ${stage} ${scheme}`,
baseUrl: `http://127.0.0.1:${port}`,
colorScheme: scheme,
reducedMotion: "reduce",
personas: [{
id: "fixture",
label: "Fixture account; Backend not connected",
auth: { kind: "anonymous" },
}],
viewports: [1440, 768, 390, 320].map((width) => ({
label: String(width),
width,
height: 900,
})),
routes,
processes: [{
id: "drive-fixture",
command: Deno.execPath(),
args: [
"run",
"--allow-net",
"--allow-read",
"--allow-run",
"--allow-env",
join(root, "tools/web-ux/drive-fixture/server.ts"),
String(port),
Deno.args[2]
? join(root, Deno.args[2])
: stage === "before"
? join(root, "web/workspace/.svelte-kit/t724/before-build/site")
: join(root, "web/workspace/build"),
],
readyUrl: `http://127.0.0.1:${port}/health`,
}],
};
const path = join(output, `${stage}-${scheme}.json`);
await Deno.writeTextFile(path, JSON.stringify(scenario, null, 2));
const command = new Deno.Command(Deno.execPath(), {
cwd: root,
args: [
"run",
"--config",
"tools/web-ux/deno.json",
"--allow-env",
"--allow-net",
"--allow-read",
"--allow-write",
"--allow-run",
"--allow-sys",
"tools/web-ux/cli.ts",
"capture",
"--scenario",
path,
"--output",
output,
"--run-id",
runId,
],
stdout: "inherit",
stderr: "inherit",
});
const result = await command.output();
if (!result.success && result.code !== 2) {
throw new Error(
`Official ${stage}/${scheme} capture failed (${result.code}); inspect review-context.`,
);
}
// CLI exit 2 retains intentional HTTP error-state diagnostics rather than hiding them.
// Parent must inspect the contexts; report unexpected errors separately.
const context = JSON.parse(
await Deno.readTextFile(join(output, runId, "review-context.json")),
);
const unexpected = context.captures.flatMap((
capture: {
route: { id: string };
errors: { kind: string; message: string; url?: string }[];
},
) =>
capture.errors.filter((error) => {
// Selection/read-text republishing entry metadata supersedes the in-flight
// breadcrumb lookup. Its abort is intentional and must not become a UI error.
if (
[
"drive-markdown",
"drive-form-revision",
"drive-image",
"drive-image-error",
"drive-truncated",
].includes(capture.route.id) && error.kind === "request" &&
error.message === "net::ERR_ABORTED" &&
error.url?.includes("/home-owner/drive/metadata?")
) return false;
if (
["drive-member", "drive-denied"].includes(capture.route.id) &&
error.kind === "document" &&
error.message ===
"visible UI error: Drive request failed: denied. No empty content or success is inferred from this failure."
) return false;
if (
capture.route.id === "drive-error" && error.kind === "document" &&
error.message ===
"visible UI error: Drive request failed: storage_unavailable. No empty content or success is inferred from this failure."
) return false;
if (capture.route.id === "home-error") {
return !(error.kind === "console" && error.message.includes("503")) &&
!error.url?.includes("/home-error/merge-requests?");
}
if (capture.route.id === "workers-error") {
return !(error.kind === "console" && error.message.includes("503")) &&
!(error.url?.includes("/workers-error/workers") &&
/HTTP 503|net::ERR_ABORTED/.test(error.message));
}
if (capture.route.id === "drive-member") {
return !(error.kind === "console" && error.message.includes("403")) &&
!(error.url?.includes("/home-member/drive/mutate") &&
/HTTP 403|net::ERR_ABORTED/.test(error.message));
}
if (
capture.route.id === "drive-error" ||
capture.route.id === "drive-denied"
) {
return !(error.kind === "console" && /403|503/.test(error.message)) &&
!error.url?.includes("/drive/");
}
return true;
}).map((error) => ({ route: capture.route.id, ...error }))
);
await Deno.writeTextFile(
join(output, runId, "diagnostic-disposition.json"),
JSON.stringify(
{
expected:
"Home reviews/Worker catalog error: injected 503. Drive denied/member/error: injected typed 403/503 and their precise visible failure messages. File metadata ERR_ABORTED is a superseded breadcrumb read, fenced by the UI epoch with no visible breadcrumb failure. All retained in review-context; no diagnostics are removed from official evidence.",
unexpected,
},
null,
2,
),
);
if (unexpected.length) {
throw new Error(
`${unexpected.length} unexpected visual diagnostics; inspect disposition.json`,
);
}
}
@@ -0,0 +1,15 @@
{
"nodeModulesDir": "auto",
"imports": {
"#lib/": "../../src/lib/",
"@std/assert": "jsr:@std/assert@1.0.19",
"@std/path": "jsr:@std/path@1.1.4",
"playwright": "npm:playwright@1.59.1"
},
"lock": false,
"tasks": {
"test:fixture": "deno test --allow-read fixture-api.test.ts",
"test:browser": "deno test --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys drive.browser.test.ts",
"test:responsive": "deno test --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys responsive.browser.test.ts"
}
}
@@ -0,0 +1,715 @@
/// <reference lib="dom" />
import { assert, assertEquals } from "@std/assert";
import { dirname, fromFileUrl, join } from "@std/path";
import { chromium, type Page, type Route } from "playwright";
import { Buffer } from "node:buffer";
import type { FixtureLog } from "../../../../tools/web-ux/drive-fixture/api.ts";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../../..");
const ready = (page: Page) => page.locator('[data-drive-ready="true"]').waitFor();
const button = (page: Page, name: string) => page.getByRole("button", { name, exact: true });
const draft = (page: Page) => page.getByRole("textbox", { name: /^Draft \(expected revision/ });
const preview = (page: Page) => page.getByRole("article", { name: "Current saved content" });
function gate() {
let release!: () => void;
const promise = new Promise<void>((resolve) => release = resolve);
return {
release,
get promise() {
return (async () => {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<void>((_resolve, reject) => {
timer = setTimeout(
() =>
reject(
new Error(
"Expected API gate was not observed/released within 30 seconds",
),
),
30000,
);
}),
]);
} finally {
clearTimeout(timer);
}
})();
},
};
}
async function harness(
run: (
origin: string,
browser: Awaited<ReturnType<typeof chromium.launch>>,
) => Promise<void>,
) {
// Intentionally no build here. Parent must authorize and build production UI first.
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const origin = `http://127.0.0.1:${port}`;
const server = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-read",
"--allow-net",
"--allow-run",
"--allow-env",
join(root, "tools/web-ux/drive-fixture/server.ts"),
String(port),
join(root, "web/workspace/build"),
],
stdout: "null",
stderr: "inherit",
}).spawn();
try {
const deadline = Date.now() + 10000;
while (true) {
try {
const response = await fetch(`${origin}/health`);
await response.body?.cancel();
if (response.ok) break;
} catch { /* startup */ }
if (Date.now() > deadline) {
throw new Error("Drive fixture failed startup");
}
await new Promise((resolve) => setTimeout(resolve, 30));
}
const browser = await chromium.launch({ headless: true });
try {
await run(origin, browser);
} finally {
await browser.close();
}
} finally {
server.kill("SIGTERM");
await server.status;
}
}
async function editor(page: Page, text: string) {
await ready(page);
await button(page, "Edit text").click();
await draft(page).fill(text);
}
async function rootFolder(page: Page) {
await page.getByRole("navigation", { name: "Drive folders" }).getByRole(
"link",
{
name: "Drive",
exact: true,
},
).click();
await page.getByRole("heading", { name: "Drive", exact: true }).waitFor();
await ready(page);
}
async function otherFile(page: Page) {
await rootFolder(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await page.getByRole("link", { name: "notes.txt", exact: true }).click();
await ready(page);
}
async function fault(origin: string, workspace: string, mode: string) {
const response = await fetch(`${origin}/__fixture/fault`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ workspace, mode }),
});
await response.body?.cancel();
}
async function logs(origin: string) {
return await (await fetch(`${origin}/__fixture/log`)).json() as FixtureLog[];
}
async function safeFulfill(
route: Route,
response: Awaited<ReturnType<Route["fetch"]>>,
) {
try {
await route.fulfill({ response });
} catch { /* selection intentionally canceled this old request */ }
}
Deno.test("production Drive two tabs retain losing CAS draft and never retry with a newer revision", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const first = await context.newPage(), second = await context.newPage();
const errors: string[] = [];
first.on("pageerror", (e) => errors.push(String(e)));
second.on("pageerror", (e) => errors.push(String(e)));
try {
await first.goto(`${origin}/w/home-owner/drive/3`);
await second.goto(`${origin}/w/home-owner/drive/3`);
await editor(first, "First actor wins");
await editor(second, "Second actor unsaved draft");
await button(first, "Save").click();
await first.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await button(second, "Save").click();
await second.getByText("Revision conflict — your draft is retained.", {
exact: true,
})
.waitFor();
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
assertEquals(await button(second, "Save").isDisabled(), true);
await button(second, "Refresh").click();
await ready(second);
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
assert(
(await second.getByText("Draft (expected revision 1)", { exact: false })
.count()) > 0,
);
assert(
(await preview(second).textContent())!.includes("First actor wins"),
);
await otherFile(second);
await editor(second, "Independent second-file draft");
await rootFolder(second);
await second.getByRole("link", { name: "README.md", exact: true })
.click();
await ready(second);
await button(second, "Edit text").click();
assertEquals(
await draft(second).inputValue(),
"Second actor unsaved draft",
);
const casWrites = (await logs(origin)).filter((x) => x.path === "/mutate");
assertEquals(casWrites.map((x) => [x.nodeId, x.expectedRevision]), [[
"3",
"1",
], ["3", "1"]]);
assertEquals(errors, []);
} finally {
await context.close();
}
}));
Deno.test("production Drive delayed old read cannot replace another file or workspace content", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const received = gate(), release = gate();
let handled: Promise<void> = Promise.resolve();
await page.route("**/drive/read-text?**", (route) => {
if (new URL(route.request().url()).searchParams.get("id") !== "3") {
return route.continue();
}
handled = (async () => {
const response = await route.fetch();
received.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await received.promise;
await otherFile(page);
await editor(page, "Only current file draft");
release.release();
await handled;
assertEquals(await page.locator("main h1").textContent(), "notes.txt");
assertEquals(await draft(page).inputValue(), "Only current file draft");
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
const mutation = (await logs(origin)).filter((x) => x.path === "/mutate");
assertEquals(
mutation.map((x) => [x.workspace, x.nodeId, x.expectedRevision]),
[[
"home-owner",
"4",
"1",
]],
);
// Full workspace navigation must likewise never resurrect the old workspace.
await page.goto(`${origin}/w/home-member/drive/4`);
await ready(page);
assert((await preview(page).textContent())!.includes("Second document"));
assertEquals(
await page.locator("main .drive-url a").getAttribute("href"),
"/w/home-member/drive/4",
);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive delayed old save and status preserve destination drafts and save identity", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const sent = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/drive/mutate", (route) => {
handled = (async () => {
const response = await route.fetch();
sent.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await editor(page, "Old file transmitted draft");
await button(page, "Save").click();
await sent.promise;
await otherFile(page);
await editor(page, "Destination draft never rebased");
release.release();
await handled;
await page.unroute("**/drive/mutate");
assertEquals(
await draft(page).inputValue(),
"Destination draft never rebased",
);
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await rootFolder(page);
await page.getByRole("link", { name: "README.md", exact: true }).click();
await ready(page);
await page.getByRole("button", { name: "Check request result" }).click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
assert(
(await preview(page).textContent())!.includes(
"Old file transmitted draft",
),
);
await editor(page, "Another old request draft");
await fault(origin, "home-owner", "unknown");
await button(page, "Save").click();
await button(page, "Check request result").waitFor();
const statusSent = gate(), statusRelease = gate();
let statusHandled = Promise.resolve();
await page.route("**/drive/requests/*", (route) => {
statusHandled = (async () => {
const response = await route.fetch();
statusSent.release();
await statusRelease.promise;
await safeFulfill(route, response);
})();
return statusHandled;
});
await button(page, "Check request result").click();
await statusSent.promise;
await otherFile(page);
await editor(page, "Destination draft during old status");
statusRelease.release();
await statusHandled;
assertEquals(
await draft(page).inputValue(),
"Destination draft during old status",
);
assert(
(await preview(page).textContent())!.includes(
"Destination draft never rebased",
),
);
await button(page, "Save").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
const pinnedWrites = (await logs(origin)).filter((x) => x.path === "/mutate");
assertEquals(pinnedWrites.map((x) => [x.nodeId, x.expectedRevision]), [
["3", "1"],
["4", "1"],
["3", "2"],
["4", "2"],
]);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive rename move delete recreate preserve stable identity and missing old URL", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
page.on("dialog", (dialog) => dialog.accept());
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
await button(page, "Rename / move").click();
await page.getByRole("textbox", { name: "Name", exact: true }).fill(
"renamed.md",
);
await page.getByRole("textbox", { name: "Destination folder node ID" })
.fill("2");
await button(page, "Apply rename / move").click();
await page.getByRole("heading", { name: "renamed.md", exact: true })
.waitFor();
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/3");
await page.getByRole("navigation", { name: "Drive folders" }).getByRole(
"link",
{
name: "資料",
},
).click();
await ready(page);
await page.getByRole("link", { name: "renamed.md", exact: true }).click();
await ready(page);
await button(page, "Delete").click();
await page.getByRole("alert").filter({ hasText: /not[_ ]found/i })
.waitFor();
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/3");
await rootFolder(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await button(page, "New Markdown").click();
await page.getByRole("textbox", { name: "Name", exact: true }).fill(
"renamed.md",
);
await page.getByRole("textbox", { name: "Markdown", exact: true }).fill(
"Recreated identity",
);
await button(page, "Create").click();
await page.getByRole("link", { name: "renamed.md", exact: true })
.waitFor();
const href = await page.getByRole("link", {
name: "renamed.md",
exact: true,
}).getAttribute(
"href",
);
assert(href !== "/w/home-owner/drive/3");
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
await page.getByRole("alert").filter({ hasText: /not[_ ]found/i })
.waitFor();
} finally {
await page.close();
}
}));
Deno.test("production Drive upload response loss and navigation cancellation query receipts without resend", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(`${origin}/w/home-owner/drive`);
await ready(page);
await page.route("**/drive/upload?**", async (route) => {
await route.fetch();
await route.abort("failed");
});
await button(page, "Upload").click();
await page.getByLabel("File (maximum 16 MiB)").setInputFiles({
name: "lost-response.txt",
mimeType: "text/plain",
buffer: Buffer.from("Published despite response loss"),
});
await button(page, "Upload file").click();
await button(page, "Check request result").waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
1,
);
await button(page, "Check request result").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await page.unroute("**/drive/upload?**");
await button(page, "Refresh").click();
await ready(page);
await page.getByRole("link", { name: "lost-response.txt" }).waitFor();
const transmitted = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/drive/upload?**", (route) => {
handled = (async () => {
const response = await route.fetch();
transmitted.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
await button(page, "Upload").click();
await page.getByLabel("File (maximum 16 MiB)").setInputFiles({
name: "canceled-response.txt",
mimeType: "text/plain",
buffer: Buffer.from("Transmitted before navigation abort"),
});
await button(page, "Upload file").click();
await transmitted.promise;
await button(page, "Cancel transfer / check outcome").click();
await button(page, "Check request result").waitFor();
await page.getByRole("link", { name: "資料", exact: true }).click();
await page.getByRole("heading", { name: "資料", exact: true }).waitFor();
await ready(page);
release.release();
await handled;
await rootFolder(page);
await button(page, "Check request result").waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
2,
);
await button(page, "Check request result").click();
await page.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
assertEquals(
(await logs(origin)).filter((x) => x.path === "/upload").length,
2,
);
} catch (error) {
console.log("UPLOAD FAILURE UI", await page.locator("main").innerText());
console.log("UPLOAD FAILURE REQUESTS", await logs(origin));
await page.screenshot({
path: join(root, "web/workspace/.svelte-kit/t724/upload-failure.png"),
fullPage: true,
});
throw error;
} finally {
await context.close();
}
}));
Deno.test("production Drive workspace switch fences a delayed previous workspace read", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const page = await context.newPage();
const sent = gate(), release = gate();
let handled = Promise.resolve();
await page.route("**/home-owner/drive/read-text?**", (route) => {
handled = (async () => {
const response = await route.fetch();
sent.release();
await release.promise;
await safeFulfill(route, response);
})();
return handled;
});
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await sent.promise;
await page.getByRole("button", {
name: "Workspace Home Review",
exact: true,
}).first()
.click();
await page.getByRole("menuitem", {
name: "Shared Workspace",
exact: true,
}).click();
await page.waitForURL("**/w/home-member");
await page.locator("aside").getByRole("link", {
name: "Drive",
exact: true,
}).click();
await ready(page);
await page.getByRole("link", { name: "資料", exact: true }).click();
await ready(page);
await page.getByRole("link", { name: "notes.txt", exact: true }).click();
await ready(page);
await editor(page, "Member-only current draft");
release.release();
await handled;
assertEquals(new URL(page.url()).pathname, "/w/home-member/drive/4");
assertEquals(await draft(page).inputValue(), "Member-only current draft");
assert((await preview(page).textContent())!.includes("Second document"));
assertEquals(
(await logs(origin)).filter((x) => x.path === "/mutate").length,
0,
);
} finally {
release.release();
await handled;
await context.close();
}
}));
Deno.test("production Drive denial empty pages truncation unsafe content and image failure remain distinct", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
const external: string[] = [];
page.on("request", (request) => {
if (new URL(request.url()).hostname === "example.invalid") {
external.push(request.url());
}
});
try {
await page.goto(`${origin}/w/home-member/drive/3`);
await editor(page, "Denied member draft");
await button(page, "Save").click();
await page.getByText(
"Read only / access denied by Backend. Editing and upload are unavailable.",
).waitFor();
assertEquals(await draft(page).inputValue(), "Denied member draft");
assertEquals(await button(page, "Save").count(), 0);
assertEquals(await draft(page).getAttribute("readonly"), "");
for (
const [workspace, text] of [["drive-denied", "denied"], [
"drive-offline",
"unavailable",
]]
) {
await page.goto(`${origin}/w/${workspace}/drive`);
await ready(page);
await page.getByRole("alert").filter({ hasText: new RegExp(text, "i") })
.waitFor();
assertEquals(
await page.getByText("This folder is empty.", { exact: true })
.count(),
0,
);
}
await page.goto(`${origin}/w/drive-empty/drive`);
await ready(page);
await page.getByText("This folder is empty.", { exact: true }).waitFor();
await page.goto(`${origin}/w/drive-paged/drive`);
await ready(page);
const firstCount = await page.locator(".drive-table tbody tr").count();
await button(page, "Load more").click();
await ready(page);
assert(
(await page.locator(".drive-table tbody tr").count()) > firstCount,
);
await page.goto(`${origin}/w/home-owner/drive/9`);
await ready(page);
await page.getByText("Preview truncated at 64 KiB.", { exact: false })
.waitFor();
assertEquals(await button(page, "Edit text").count(), 0);
await page.goto(`${origin}/w/home-owner/drive/6`);
await ready(page);
await page.getByText("Empty file.", { exact: true }).waitFor();
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
assertEquals(
await page.locator('main a[href^="javascript:"]').count(),
0,
);
assertEquals(await page.locator("main img[onerror]").count(), 0);
assertEquals(external, []);
const local = page.getByRole("link", {
name: "Local folder",
exact: true,
});
assertEquals(await local.getAttribute("href"), "/w/home-owner/drive/2");
await local.click();
await page.waitForURL("**/w/home-owner/drive/2");
await page.getByRole("heading", { name: "資料", exact: true }).waitFor();
await ready(page);
assertEquals(new URL(page.url()).pathname, "/w/home-owner/drive/2");
await page.goto(`${origin}/w/home-owner/drive/8`);
await ready(page);
await page.getByText("This file type is download-only.", { exact: false })
.waitFor();
assertEquals(
await page.locator("main svg, main iframe, main object").count(),
0,
);
await page.goto(`${origin}/w/home-owner/drive/5`);
await ready(page);
await page.waitForFunction(() => {
const image = document.querySelector("main .drive-image");
return image instanceof HTMLImageElement && image.complete && image.naturalWidth === 1200 &&
image.naturalHeight === 800;
});
assertEquals(
await page.getByRole("img", { name: "landscape.png", exact: true }).evaluate((image) =>
image instanceof HTMLImageElement ? [image.naturalWidth, image.naturalHeight] : []
),
[1200, 800],
);
await page.goto(`${origin}/w/home-owner/drive/7`);
await ready(page);
await page.getByRole("alert").filter({
hasText: "Image could not be decoded",
}).waitFor();
await fault(origin, "home-owner", "image_error");
await page.goto(`${origin}/w/home-owner/drive/5`);
await ready(page);
await page.getByRole("alert").filter({ hasText: /unavailable/i })
.waitFor();
} finally {
await page.close();
}
}));
Deno.test("production Workers owner grants use the authoritative catalog while members have no grant controls", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(String(error)));
try {
await page.goto(`${origin}/w/home-owner/workers`);
await page.getByRole("combobox", { name: "Worker", exact: true })
.selectOption({
label: "Documentation Worker · fixture-runtime / fixture-worker-a",
});
await button(page, "Add Drive grant").waitFor();
await button(page, "Add Drive grant").click();
await button(page, "Create Drive grant").click();
const grants = page.getByRole("region", { name: "Worker Drive grants" });
await grants.getByText("Read only", { exact: true }).waitFor();
await page.getByRole("button", { name: /^Revoke Drive grant/ }).click();
await grants.getByText("No active Drive grants for this Worker.", {
exact: true,
}).waitFor();
await button(page, "Add Drive grant").click();
await page.getByRole("combobox", { name: "Access", exact: true })
.selectOption("read_write");
await button(page, "Create Drive grant").click();
await grants.getByText("Read and write", { exact: true }).waitFor();
await page.getByRole("button", { name: /^Revoke Drive grant/ }).click();
await grants.getByText("No active Drive grants for this Worker.", {
exact: true,
}).waitFor();
const mutations = (await logs(origin)).filter((event) =>
event.path.startsWith("/grants") && event.method !== "GET"
);
assertEquals(
mutations.map((event) => [event.workspace, event.method, event.path]),
[
["home-owner", "POST", "/grants"],
["home-owner", "DELETE", "/grants/1"],
["home-owner", "POST", "/grants"],
["home-owner", "DELETE", "/grants/2"],
],
);
await page.goto(`${origin}/w/home-member/workers`);
await page.locator(".workers-table tbody tr").first().waitFor();
assertEquals(
await page.getByRole("region", { name: "Worker Drive grants" }).count(),
0,
);
assertEquals(await button(page, "Add Drive grant").count(), 0);
assertEquals(errors, []);
} finally {
await page.close();
}
}));
Deno.test("production Drive Markdown safe image placeholders preserve each image alt label", () =>
harness(async (origin, browser) => {
const page = await browser.newPage();
try {
await page.goto(`${origin}/w/home-owner/drive/3`);
await ready(page);
const labels = await page.locator(".drive-inline-image")
.allTextContents();
assert(
labels.some((label) => label.includes("Image: External")),
`External image alt label lost: ${JSON.stringify(labels)}`,
);
assert(
labels.some((label) => label.includes("Image: Image")),
`Local image alt label lost: ${JSON.stringify(labels)}`,
);
assertEquals(await preview(page).locator("img").count(), 0);
} finally {
await page.close();
}
}));
@@ -0,0 +1,140 @@
import { assert, assertEquals, assertRejects } from "@std/assert";
import { createDriveFixture, fixtureRootId } from "../../../../tools/web-ux/drive-fixture/api.ts";
import { createDriveClient, DriveRequestError } from "../../src/lib/workspace/drive/api.ts";
function setup() {
const fixture = createDriveFixture();
const fetchFixture: typeof fetch = async (input, init) => {
const request = new Request(new URL(String(input), "http://127.0.0.1"), init);
const response = await fixture.handler(request);
if (!response) throw new Error(`Unimplemented fixture ${request.url}`);
return response;
};
return { fixture, client: createDriveClient(fetchFixture), fetchFixture };
}
const ref = (workspace_id: string, node_id: string) => ({ workspace_id, node_id });
Deno.test("browser fixture preserves decimal identity, bounded reads, binary and page cursors", async () => {
const { client, fetchFixture } = setup();
const root = await client.root("drive-paged");
assertEquals(root.entry.node_id, fixtureRootId);
const first = await client.list("drive-paged", root.entry, { limit: 100 });
const second = await client.list("drive-paged", root.entry, {
limit: 100,
after: first.next_after,
});
assertEquals(first.entries.length, 100);
assertEquals(second.entries.length, 100);
assert(
!second.entries.some((entry) =>
first.entries.some((other) => other.entry.node_id === entry.entry.node_id)
),
);
const text = await client.readText("drive-paged", ref("drive-paged", "9"));
assertEquals(text.truncated, true);
assert(new TextEncoder().encode(text.text).length <= 65536);
const image = await client.metadata("drive-paged", ref("drive-paged", "5"));
const imageBytes = await client.image("drive-paged", image);
assertEquals(imageBytes.size, image.size);
assert(imageBytes.size < 256 * 1024);
const png = new Uint8Array(await imageBytes.arrayBuffer());
const dimensions = new DataView(png.buffer);
assertEquals([dimensions.getUint32(16), dimensions.getUint32(20)], [1200, 800]);
const escapedWire = await fetchFixture(
"/api/w/drive-paged/drive/read-text?entry_workspace_id=drive-paged&id=11&max_bytes=65536",
);
const escapedLength = (await escapedWire.arrayBuffer()).byteLength;
assert(escapedLength > 256 * 1024 && escapedLength < 512 * 1024);
const escaped = await client.readText("drive-paged", ref("drive-paged", "11"));
assertEquals(escaped.text, "\u0000".repeat(65536));
assertEquals(escaped.truncated, false);
});
Deno.test("browser fixture two revisions conflict without committing the losing request", async () => {
const { client } = setup();
const entry = await client.metadata("home-owner", ref("home-owner", "3"));
await client.mutate("home-owner", "tab-one", {
operation: "update_text",
id: entry.entry,
expected_revision: entry.revision,
text: "first actor",
content_type: "text/markdown",
});
const failure = await assertRejects(
() =>
client.mutate("home-owner", "tab-two", {
operation: "update_text",
id: entry.entry,
expected_revision: entry.revision,
text: "second actor draft",
content_type: "text/markdown",
}),
DriveRequestError,
);
assertEquals((failure as DriveRequestError).code, "conflict");
assertEquals((await client.status("home-owner", "tab-one")).state, "committed");
assertEquals((await client.status("home-owner", "tab-two")).state, "uncommitted");
assertEquals((await client.readText("home-owner", entry.entry)).text, "first actor");
});
Deno.test("browser fixture upload unknown is reconciled by receipt and denied writes never commit", async () => {
const { client, fetchFixture } = setup();
await fetchFixture("/__fixture/fault", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ workspace: "home-owner", mode: "unknown" }),
});
const root = await client.root("home-owner");
const failure = await assertRejects(
() =>
client.upload("home-owner", "upload-one", new Blob(["upload bytes"]), {
operation: "create",
parent: root.entry,
name: "upload.txt",
content_type: "text/plain",
}),
DriveRequestError,
);
assertEquals((failure as DriveRequestError).code, "outcome_unknown");
assertEquals((await client.status("home-owner", "upload-one")).state, "committed");
const memberRoot = await client.root("home-member");
const denied = await assertRejects(
() =>
client.mutate("home-member", "member-one", {
operation: "create_folder",
parent: memberRoot.entry,
name: "denied",
}),
DriveRequestError,
);
assertEquals((denied as DriveRequestError).code, "denied");
assertEquals((await client.status("home-member", "member-one")).state, "uncommitted");
});
Deno.test("browser fixture rename and move retain identity while delete and recreate do not reuse links", async () => {
const { client } = setup();
let entry = await client.metadata("home-owner", ref("home-owner", "3"));
const latest = entry.latest_url;
const moved = await client.mutate("home-owner", "move", {
operation: "relocate",
id: entry.entry,
expected_revision: entry.revision,
parent: ref("home-owner", "2"),
name: "renamed.md",
});
entry = moved.entry!;
assertEquals(entry.latest_url, latest);
await client.mutate("home-owner", "delete", {
operation: "delete",
id: entry.entry,
expected_revision: entry.revision,
});
const replacement = await client.mutate("home-owner", "recreate", {
operation: "create_text",
parent: ref("home-owner", "2"),
name: "renamed.md",
text: "new entity",
content_type: "text/markdown",
});
assert(replacement.entry!.entry.node_id !== entry.entry.node_id);
const missing = await assertRejects(
() => client.metadata("home-owner", entry.entry),
DriveRequestError,
);
assertEquals((missing as DriveRequestError).code, "not_found");
});
@@ -0,0 +1,212 @@
/// <reference lib="dom" />
// Responsive DOM geometry audit over the frozen production shell and fake API.
// Read-only observation: no UI/CSS mutation or actual Backend connection.
import { assert, assertEquals } from "@std/assert";
import { dirname, fromFileUrl, join } from "@std/path";
import { chromium } from "playwright";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../../..");
const auditId = Deno.args[1] ?? "verified";
Deno.test("production Drive and Workers constrain page overflow and expose keyboard grant controls at every viewport", async () => {
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const origin = `http://127.0.0.1:${port}`;
const server = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-read",
"--allow-net",
"--allow-run",
"--allow-env",
join(root, "tools/web-ux/drive-fixture/server.ts"),
String(port),
join(root, Deno.args[0] ?? "web/workspace/build"),
],
stdout: "null",
stderr: "inherit",
}).spawn();
const evidence: unknown[] = [];
try {
const deadline = Date.now() + 10000;
while (true) {
try {
const response = await fetch(`${origin}/health`);
await response.body?.cancel();
if (response.ok) break;
} catch { /* startup */ }
if (Date.now() > deadline) throw new Error("fixture failed startup");
await new Promise((resolve) => setTimeout(resolve, 30));
}
const browser = await chromium.launch({ headless: true });
try {
for (const colorScheme of ["light", "dark"] as const) {
for (const width of [1440, 768, 390, 320]) {
const context = await browser.newContext({
viewport: { width, height: 900 },
colorScheme,
reducedMotion: "reduce",
});
try {
const page = await context.newPage();
const pageErrors: string[] = [];
page.on("pageerror", (e) => pageErrors.push(String(e)));
for (
const [workspace, surface] of [
["home-owner", "workers"],
["home-member", "workers"],
["home-empty", "workers"],
["workers-error", "workers"],
["home-long", "workers"],
["home-owner", "drive"],
["home-member", "drive"],
["home-empty", "drive"],
["home-error", "drive"],
["drive-paged", "drive"],
["home-owner", "drive/10"],
["home-owner", "drive/5"],
]
) {
await page.goto(`${origin}/w/${workspace}/${surface}`);
if (surface.startsWith("drive")) {
await page.locator('[data-drive-ready="true"]').waitFor();
} else if (workspace === "workers-error") {
await page.locator(".workers-page .section-state.error")
.waitFor();
} else if (workspace === "home-empty") {
await page.getByText("No Workers are visible.", { exact: true })
.waitFor();
} else {await page.locator(".workers-table tbody tr").first()
.waitFor();}
if (surface === "drive/5") {
await page.waitForFunction(() => {
const image = document.querySelector("main .drive-image");
return image instanceof HTMLImageElement && image.complete &&
image.naturalWidth === 1200 && image.naturalHeight === 800;
});
}
if (workspace === "home-owner" && surface === "workers") {
await page.getByRole("combobox", {
name: "Worker",
exact: true,
}).selectOption({
label: "Documentation Worker · fixture-runtime / fixture-worker-a",
});
await page.getByRole("button", {
name: "Add Drive grant",
exact: true,
}).click();
await page.getByRole("combobox", {
name: "Access",
exact: true,
}).waitFor();
await page.getByRole("button", {
name: "Refresh grants",
exact: true,
}).press("Tab");
const focus = await page.evaluate(() => ({
tag: document.activeElement?.tagName,
outline: document.activeElement
? getComputedStyle(document.activeElement).outlineStyle
: "none",
}));
assert(
focus.tag === "BUTTON" && focus.outline !== "none",
JSON.stringify(focus),
);
}
if (workspace === "workers-error" && width === 320 && colorScheme === "dark") {
await page.screenshot({
path: join(root, `web/workspace/.svelte-kit/t724/workers-error-${auditId}.png`),
fullPage: false,
});
}
const geometry = await page.evaluate(() => {
const main = document.querySelector("main")! as HTMLElement;
const grants = main.querySelector(".drive-grants");
const boundedTables = [
...main.querySelectorAll(".table-wrap, .drive-table-region"),
].map((el) => ({
width: el.clientWidth,
scrollWidth: el.scrollWidth,
overflowX: getComputedStyle(el).overflowX,
}));
const raster = main.querySelector(".drive-image");
const image = raster instanceof HTMLImageElement
? {
naturalWidth: raster.naturalWidth,
naturalHeight: raster.naturalHeight,
renderedWidth: raster.getBoundingClientRect().width,
renderedHeight: raster.getBoundingClientRect().height,
availableWidth: raster.parentElement!.getBoundingClientRect().width,
heightLimit: innerHeight * 0.6,
}
: null;
return {
image,
pageWidth: document.documentElement.scrollWidth,
viewportWidth: innerWidth,
mainWidth: main.clientWidth,
mainScrollWidth: main.scrollWidth,
documentScrollY: scrollY,
mainScrollTop: main.scrollTop,
mainTop: main.getBoundingClientRect().top,
firstHeadingTop: main.querySelector("h1")?.getBoundingClientRect().top ?? null,
grantsWidth: grants?.getBoundingClientRect().width ?? null,
boundedTables,
};
});
evidence.push({
colorScheme,
width,
workspace,
surface,
...geometry,
});
assert(
geometry.pageWidth <= width,
JSON.stringify(evidence.at(-1)),
);
assert(
geometry.mainScrollWidth <= geometry.mainWidth + 1,
JSON.stringify(evidence.at(-1)),
);
if (surface === "drive/5") {
const image = geometry.image;
assert(image !== null);
assertEquals([image.naturalWidth, image.naturalHeight], [1200, 800]);
assert(image.renderedWidth > 100 && image.renderedHeight > 100);
assert(image.renderedWidth <= image.availableWidth + 1);
assert(image.renderedHeight <= image.heightLimit + 1);
assert(Math.abs(image.renderedWidth / image.renderedHeight - 1.5) < 0.02);
}
if (workspace === "home-member" && surface === "workers") {
assertEquals(
await page.getByRole("region", {
name: "Worker Drive grants",
}).count(),
0,
);
}
}
assertEquals(pageErrors, []);
} finally {
await context.close();
}
}
}
} finally {
await browser.close();
}
} finally {
await Deno.writeTextFile(
join(root, `web/workspace/.svelte-kit/t724/responsive-${auditId}.json`),
JSON.stringify(
{ authority: "fixture, not actual Backend", evidence },
null,
2,
),
);
server.kill("SIGTERM");
await server.status;
}
});