fix: show Workdir removal causes and refresh ordinary retries

This commit is contained in:
2026-10-09 16:35:26 +09:00
parent 4337f415f9
commit c889c9a68d
11 changed files with 1381 additions and 177 deletions
@@ -0,0 +1,140 @@
// Synthetic API boundary only; the production route and shell own all rendering.
export function workdirFixture() {
let mountReleased = false;
const deleted = new Set<string>();
const requests: {
workspace: string;
method: string;
path: string;
body?: unknown;
}[] = [];
const summary = (
id: string,
status = "active",
cleanliness = "clean",
occupied = false,
) => ({
working_directory_id: id,
display_name: id === "retry-dir" ? "Cleanup retry — distributed development checkout" : id,
source: { kind: "repository", repository_key: "main" },
materializer_kind: "runtime_git_clone",
status,
cleanliness,
current_ref: "a".repeat(40),
current_selector: "develop",
occupied_by: occupied
? {
runtime_id: "fixture-runtime",
worker_id: "worker-1",
display_name: "Active Worker",
linked_at: "2026-01-01T00:00:00Z",
}
: null,
});
const items = (workspace: string) =>
workspace === "home-empty" ? [] : [
...(deleted.has(workspace) ? [] : [summary("retry-dir", "cleanup_pending", "unknown")]),
summary("dirty-dir", "active", "dirty"),
summary("occupied-dir", "active", "clean", true),
];
const plan = (workspace: string) => ({
workspace_id: workspace,
runtime_id: "fixture-runtime",
generated_at: "2026-01-01T00:00:00Z",
revision: "plan-1",
digest: "digest-1",
workers: [],
diagnostics: [],
workdirs: items(workspace).map((item) => ({
target_id: `workdir:${item.working_directory_id}`,
action: item.cleanliness === "dirty" ? "workdir_dirty_discard" : "workdir_clean_cleanup",
workdir_id: item.working_directory_id,
runtime_id: "fixture-runtime",
repository_key: "main",
reason: "Ordinary cleanup rechecks current conditions",
linked_worker_ids: item.occupied_by ? ["worker-1"] : [],
linked_running_worker_ids: item.occupied_by ? ["worker-1"] : [],
running_linked: Boolean(item.occupied_by),
pinned_linked: false,
file_status: item.status === "cleanup_pending" ? "pending" : "present",
cleanliness: item.cleanliness,
blocking_reason: item.occupied_by ? "Workdir is linked to a running Worker" : null,
})),
});
return async (request: Request): Promise<Response | null> => {
const url = new URL(request.url);
if (
url.pathname === "/fixture-workdirs-control" && request.method === "POST"
) {
const control = await request.json();
mountReleased = control.mountReleased === true;
if (control.reset) {
deleted.clear();
requests.length = 0;
}
return Response.json({ ok: true });
}
if (url.pathname === "/fixture-workdirs") {
return Response.json({ requests });
}
const match = url.pathname.match(/^\/api\/w\/([^/]+)(\/runtimes.*)$/);
if (!match) return null;
const [, workspace, path] = match;
if (path === "/runtimes") {
return Response.json({
workspace_id: workspace,
limit: 100,
source: "fixture",
diagnostics: [],
items: [],
});
}
if (!path.startsWith("/runtimes/fixture-runtime/")) return null;
const body = request.method !== "GET" ? await request.json() : undefined;
requests.push({ workspace, method: request.method, path, body });
if (path.endsWith("/working-directories")) {
return Response.json({
workspace_id: workspace,
items: items(workspace),
diagnostics: [],
});
}
if (path.endsWith("/cleanup-plan")) return Response.json(plan(workspace));
// Baseline route uses cleanup-executions; same retained outcome for before captures.
if (path.endsWith("/cleanup-executions")) {
return Response.json({
workspace_id: workspace,
runtime_id: "fixture-runtime",
executed_at: "2026-01-01T00:00:00Z",
diagnostics: [],
plan_after: plan(workspace),
results: [{
target_id: "workdir:retry-dir",
action: "workdir_clean_cleanup",
status: "retained",
message: "Provider cleanup failed",
}],
});
}
if (
path.endsWith("/working-directories/retry-dir") &&
request.method === "DELETE"
) {
if (workspace === "home-error") {
return Response.json({
message: "Synthetic private host path must not render",
}, { status: 503 });
}
if (mountReleased) deleted.add(workspace);
return Response.json({
working_directory_id: "retry-dir",
disposition: mountReleased ? "removed" : "attention_required",
retryable: !mountReleased,
failure_category: mountReleased ? null : "mount_present",
});
}
return Response.json({ error: "Unexpected fixture Workdir request" }, {
status: 404,
});
};
}
@@ -0,0 +1,456 @@
/// <reference lib="dom" />
import { assert, assertEquals, assertStringIncludes } from "@std/assert";
import { dirname, fromFileUrl, join } from "@std/path";
import { chromium, type Page } from "playwright";
import { startOwnedProcesses, stopOwnedProcesses } from "../src/processes.ts";
const root = join(dirname(fromFileUrl(import.meta.url)), "../../..");
const evidence = join(root, "target/web-ux/t730-browser");
async function noOverflow(page: Page) {
assertEquals(
await page.locator("main").evaluate((main) => ({
page: document.documentElement.scrollWidth > innerWidth,
main: main.scrollWidth > main.clientWidth,
})),
{ page: false, main: false },
);
}
Deno.test("ordinary removal refreshes failed and successful inventory, supports exact retry, and preserves permission and live guards across viewports and themes", async () => {
await Deno.mkdir(evidence, { recursive: true });
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const url = `http://127.0.0.1:${port}`;
const server = await startOwnedProcesses(
[{
id: "workdirs",
command: Deno.execPath(),
cwd: root,
args: [
"run",
"--allow-net",
"--allow-read",
"tools/web-ux/browser-tests/workspace_home_fixture_server.ts",
String(port),
"web/workspace/build",
],
readyUrl: `${url}/health`,
}],
join(root, "tools/web-ux/scenarios/workdir-removal-light.json"),
join(evidence, "process-logs"),
[],
);
const observations: unknown[] = [];
const injectedConsoleErrors: string[] = [];
const injectedUnexpectedErrors: string[] = [];
const observeInjectedFailure = (page: Page) => {
page.on("console", (message) => {
if (message.type() === "error") {
injectedConsoleErrors.push(message.text());
}
});
page.on(
"pageerror",
(error) => injectedUnexpectedErrors.push(String(error)),
);
page.on(
"requestfailed",
(request) => injectedUnexpectedErrors.push(request.url()),
);
};
const control = async (value: unknown) => {
const response = await fetch(`${url}/fixture-workdirs-control`, {
method: "POST",
body: JSON.stringify(value),
});
assert(response.ok);
await response.body?.cancel();
};
const state = async () => await (await fetch(`${url}/fixture-workdirs`)).json();
const browser = await chromium.launch({ headless: true });
try {
for (const colorScheme of ["light", "dark"] as const) {
for (const width of [1440, 768, 390, 320]) {
await control({ reset: true });
const context = await browser.newContext({
viewport: { width, height: 900 },
colorScheme,
reducedMotion: "reduce",
});
try {
const page = await context.newPage();
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(String(error)));
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
page.on("requestfailed", (request) => errors.push(request.url()));
const route = `${url}/w/home-owner/settings/runtimes/fixture-runtime/workdirs`;
await page.goto(route);
const deletion = page.getByRole("button", {
name: "Delete retry-dir",
exact: true,
});
await deletion.waitFor();
assert(
await deletion.isEnabled(),
"past pending/unknown state permanently disabled ordinary removal",
);
assert(
await page.getByRole("button", {
name: "Delete dirty-dir",
exact: true,
}).isDisabled(),
);
assert(
await page.getByRole("button", {
name: "Delete occupied-dir",
exact: true,
}).isDisabled(),
);
await noOverflow(page);
assert(
await deletion.evaluate((button) => {
const r = button.getBoundingClientRect();
return r.left >= 0 && r.right <= innerWidth;
}),
"Delete is outside initial viewport",
);
await page.screenshot({
path: join(evidence, `${colorScheme}-${width}-initial.png`),
});
// Hold the actual request boundary to verify duplicate gestures cannot dispatch another DELETE.
let release!: () => void;
const gate = new Promise<void>((resolve) => release = resolve);
let observed!: () => void;
const entered = new Promise<void>((resolve) => observed = resolve);
let deletes = 0;
await page.route(
"**/working-directories/retry-dir",
async (intercept) => {
deletes++;
observed();
await gate;
await intercept.continue();
},
);
await deletion.focus();
await page.keyboard.press("Enter");
await page.getByRole("button", {
name: "Delete retry-dir",
exact: true,
}).filter({ hasText: "Deleting…" }).waitFor();
assert(await deletion.isDisabled());
await page.keyboard.press("Enter");
await entered;
assertEquals(deletes, 1);
await page.screenshot({
path: join(evidence, `${colorScheme}-${width}-deleting.png`),
});
release();
await page.getByRole("alert").filter({
hasText: "mounted filesystem",
}).waitFor();
const retry = page.getByRole("button", {
name: "Retry Delete retry-dir",
exact: true,
});
await retry.waitFor();
await page.waitForFunction(() =>
!(document.querySelector(
"button[aria-label='Retry Delete retry-dir']",
) as HTMLButtonElement)?.disabled
);
const failed = await state();
const beforeGets = failed.requests.filter((
r: { workspace: string; method: string },
) => r.workspace === "home-owner" && r.method === "GET");
assertEquals(
beforeGets.filter((r: { path: string }) => r.path.endsWith("/working-directories"))
.length,
2,
);
assertEquals(
beforeGets.filter((r: { path: string }) => r.path.endsWith("/cleanup-plan")).length,
2,
);
assertEquals(await page.locator("tbody tr").count(), 3);
await noOverflow(page);
await page.screenshot({
path: join(evidence, `${colorScheme}-${width}-mount-failure.png`),
});
await control({ mountReleased: true });
await retry.focus();
await page.keyboard.press("Enter");
await page.getByRole("status").filter({
hasText: "Deletion confirmed",
}).waitFor();
await page.waitForFunction(() => document.querySelectorAll("tbody tr").length === 2);
assertEquals(
await page.getByRole("button", { name: /retry-dir/ }).count(),
0,
);
assert(
await page.getByRole("button", {
name: "Delete dirty-dir",
exact: true,
}).isDisabled(),
);
const final = await state();
const mutations = final.requests.filter((r: { method: string }) => r.method === "DELETE");
assertEquals(mutations.length, 2);
assertEquals(
mutations[0],
mutations[1],
"ordinary retry changed its request",
);
const gets = final.requests.filter((
r: { workspace: string; method: string },
) => r.workspace === "home-owner" && r.method === "GET");
assertEquals(
gets.filter((r: { path: string }) => r.path.endsWith("/working-directories")).length,
3,
);
assertEquals(
gets.filter((r: { path: string }) => r.path.endsWith("/cleanup-plan")).length,
3,
);
await page.screenshot({
path: join(evidence, `${colorScheme}-${width}-removed.png`),
});
await page.goto(
`${url}/w/home-member/settings/runtimes/fixture-runtime/workdirs`,
);
await page.locator("tbody tr").first().waitFor();
assertEquals(
await page.getByRole("button", { name: /Delete/ }).count(),
0,
);
await noOverflow(page);
await page.screenshot({
path: join(evidence, `${colorScheme}-${width}-member.png`),
});
await page.goto(
`${url}/w/home-empty/settings/runtimes/fixture-runtime/workdirs`,
);
await page.getByText("No workdirs are visible for this Runtime.")
.waitFor();
await noOverflow(page);
assertEquals(errors, []);
observations.push({
colorScheme,
width,
requests: final.requests,
errors,
});
} finally {
await context.close();
}
}
}
// Lost/HTTP failure responses still trigger fresh inventory and eligibility. Never echo provider bodies.
await control({ reset: true });
const page = await browser.newPage();
observeInjectedFailure(page);
await page.goto(
`${url}/w/home-error/settings/runtimes/fixture-runtime/workdirs`,
);
await page.getByRole("button", { name: "Delete retry-dir", exact: true })
.click();
await page.getByRole("alert").filter({ hasText: "provider is unavailable" })
.waitFor();
await page.waitForFunction(() =>
!(document.querySelector(
"button[aria-label='Delete retry-dir']",
) as HTMLButtonElement)?.disabled
);
assert(
!(await page.locator("main").innerText()).includes("Synthetic private"),
);
const failed = await state();
assertEquals(
failed.requests.filter((r: { method: string; path: string }) =>
r.method === "GET" && r.path.endsWith("/working-directories")
).length,
2,
);
assertEquals(
failed.requests.filter((r: { method: string; path: string }) =>
r.method === "GET" && r.path.endsWith("/cleanup-plan")
).length,
2,
);
await page.screenshot({ path: join(evidence, "http-failure.png") });
await page.close();
// Plan refresh failure cannot leave stale delete authority enabled; manual Refresh recovers it.
await control({ reset: true });
const stale = await browser.newPage();
observeInjectedFailure(stale);
let planGets = 0;
let planFails = true;
await stale.route("**/cleanup-plan", async (route) => {
planGets++;
if (planGets > 1 && planFails) {
await route.fulfill({
status: 503,
json: { message: "Synthetic private path" },
});
} else await route.continue();
});
await stale.goto(
`${url}/w/home-owner/settings/runtimes/fixture-runtime/workdirs`,
);
await stale.getByRole("button", { name: "Delete retry-dir", exact: true })
.click();
await stale.getByRole("alert").filter({ hasText: "could not be refreshed" })
.waitFor();
assert(
await stale.getByRole("button", {
name: "Retry Delete retry-dir",
exact: true,
}).isDisabled(),
);
assert(
!(await stale.locator("main").innerText()).includes("Synthetic private"),
);
await stale.screenshot({ path: join(evidence, "refresh-failure.png") });
planFails = false;
await stale.getByRole("button", { name: "Refresh", exact: true }).click();
await stale.waitForFunction(() =>
!(document.querySelector(
"button[aria-label='Retry Delete retry-dir']",
) as HTMLButtonElement)?.disabled
);
await stale.close();
// Current dirty changes after a failure override a retryable historical result.
await control({ reset: true });
const changed = await browser.newPage();
let changedGets = 0;
await changed.route("**/cleanup-plan", async (route) => {
const response = await route.fetch();
const plan = await response.json();
if (++changedGets > 1) plan.workdirs[0].action = "workdir_dirty_discard";
await route.fulfill({ response, json: plan });
});
await changed.goto(
`${url}/w/home-owner/settings/runtimes/fixture-runtime/workdirs`,
);
await changed.getByRole("button", { name: "Delete retry-dir", exact: true })
.click();
await changed.getByRole("button", {
name: "Retry Delete retry-dir",
exact: true,
}).waitFor();
await changed.waitForFunction(() =>
document.querySelector(".workdir-identity .removal-guard")?.textContent
?.includes("Changes are protected")
);
assert(
await changed.getByRole("button", {
name: "Retry Delete retry-dir",
exact: true,
}).isDisabled(),
);
assertStringIncludes(
await changed.locator("main").innerText(),
"Changes are protected",
);
await changed.screenshot({
path: join(evidence, "new-changes-protected.png"),
});
await changed.close();
// A malformed response may follow a real side effect. Inventory still removes the now-absent row.
await control({ reset: true, mountReleased: true });
const lost = await browser.newPage();
observeInjectedFailure(lost);
await lost.route("**/working-directories/retry-dir", async (route) => {
await route.fetch();
await route.fulfill({
status: 200,
json: {
message: "Synthetic private path",
failure_category: "/private/path token=synthetic",
},
});
});
await lost.goto(
`${url}/w/home-owner/settings/runtimes/fixture-runtime/workdirs`,
);
await lost.getByRole("button", { name: "Delete retry-dir", exact: true })
.click();
await lost.getByRole("alert").filter({
hasText: "Removal could not be confirmed",
}).waitFor();
await lost.waitForFunction(() => document.querySelectorAll("tbody tr").length === 2);
assert(
!(await lost.locator("main").innerText()).includes("Synthetic private"),
);
assert(!(await lost.locator("main").innerText()).includes("/private/path"));
await lost.screenshot({
path: join(evidence, "malformed-response-refreshed.png"),
});
await lost.close();
// Even a failed initial inventory can recover into a confirmed empty list with manual Refresh.
const emptyRecovery = await browser.newPage();
observeInjectedFailure(emptyRecovery);
let inventoryFails = true;
await emptyRecovery.route(
"**/runtimes/fixture-runtime/working-directories",
async (route) => {
if (inventoryFails) {
await route.fulfill({
status: 503,
json: { message: "Synthetic private path" },
});
} else await route.continue();
},
);
await emptyRecovery.goto(
`${url}/w/home-empty/settings/runtimes/fixture-runtime/workdirs`,
);
await emptyRecovery.getByText("Workdir inventory is unavailable.")
.waitFor();
inventoryFails = false;
await emptyRecovery.getByRole("button", { name: "Refresh", exact: true })
.click();
await emptyRecovery.getByText("No workdirs are visible for this Runtime.")
.waitFor();
await emptyRecovery.close();
assertEquals(injectedUnexpectedErrors, []);
assert(
injectedConsoleErrors.every((message) => message.includes("503")),
"unexpected injected failure console diagnostic",
);
await Deno.writeTextFile(
join(evidence, "evidence.json"),
JSON.stringify(
{
observations,
injectedConsoleErrors,
injectedUnexpectedErrors,
failureRefresh: failed.requests,
refreshFailureRecovered: true,
newChangesProtected: true,
malformedResponseReconciled: true,
emptyInventoryRecovered: true,
expectedFailureDiagnostics: [
"Synthetic DELETE HTTP 503 on home-error",
"Synthetic cleanup-plan HTTP 503 after removal",
"Malformed DELETE result after fixture physical removal",
"Synthetic initial inventory HTTP 503",
],
},
null,
2,
),
);
} finally {
await browser.close();
assertEquals(await stopOwnedProcesses(server), []);
}
});
@@ -2,6 +2,8 @@ import { extname, join, normalize } from "jsr:@std/path@1.1.4";
import { dashboardFixture } from "../../../web/workspace/src/lib/workspace/home/dashboard.test-fixtures.ts";
import { settingsFixtureHandler } from "../../../web/workspace/src/lib/workspace/settings/identity.test-fixtures.ts";
import { workdirFixture } from "./workdir_fixture.ts";
const workdirsFixture = workdirFixture();
const settingsFixture = settingsFixtureHandler();
const port = Number(Deno.args[0]);
@@ -30,6 +32,8 @@ const mime: Record<string, string> = {
Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
const url = new URL(request.url);
if (url.pathname === "/health") return new Response("ok");
const workdirsResponse = await workdirsFixture(request);
if (workdirsResponse) return workdirsResponse;
const settingsResponse = await settingsFixture(request);
if (settingsResponse) return settingsResponse;
if (url.pathname === "/api/workspaces") {
+1 -1
View File
@@ -11,7 +11,7 @@
"web-ux": "deno run --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys cli.ts",
"check": "deno check cli.ts src/*.ts tests/*.ts browser-tests/*.ts",
"test": "deno test --allow-env --allow-read --allow-write --allow-run --allow-sys tests",
"test:browser": "deno test --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys browser-tests/ticket_shell_test.ts browser-tests/capture_smoke_test.ts browser-tests/memory_document_shell_test.ts browser-tests/console_history_shell_test.ts browser-tests/workspace_home_shell_test.ts browser-tests/workspace_settings_shell_test.ts browser-tests/console_pending_shell_test.ts browser-tests/console_live_shell_test.ts browser-tests/console_live_admission_shell_test.ts"
"test:browser": "deno test --allow-env --allow-net --allow-read --allow-write --allow-run --allow-sys browser-tests/ticket_shell_test.ts browser-tests/capture_smoke_test.ts browser-tests/memory_document_shell_test.ts browser-tests/console_history_shell_test.ts browser-tests/workspace_home_shell_test.ts browser-tests/workspace_settings_shell_test.ts browser-tests/workdir_removal_shell_test.ts browser-tests/console_pending_shell_test.ts browser-tests/console_live_shell_test.ts browser-tests/console_live_admission_shell_test.ts"
},
"fmt": {
"lineWidth": 100,
@@ -0,0 +1,117 @@
{
"schemaVersion": 1,
"id": "workdir-removal-dark",
"title": "Ordinary Workdir removal and guarded retry",
"baseUrl": "http://127.0.0.1:15290",
"colorScheme": "dark",
"reducedMotion": "reduce",
"personas": [
{
"id": "fixture",
"label": "Synthetic permission projections",
"auth": {
"kind": "anonymous"
}
}
],
"viewports": [
{
"label": "desktop",
"width": 1440,
"height": 900
},
{
"label": "narrow",
"width": 768,
"height": 900
},
{
"label": "mobile",
"width": 390,
"height": 844
}
],
"routes": [
{
"id": "owner",
"label": "Owner removal",
"path": "/w/home-owner/settings/runtimes/fixture-runtime/workdirs",
"goal": "Remove a failed-cleanup Workdir using ordinary retry without enabling dirty or occupied removal.",
"dataState": "Pending/unknown retry plus dirty and occupied guards. First attempt finds a mount.",
"ready": {
"kind": "selector",
"selector": "main table tbody tr"
},
"capturePoints": [
{
"id": "initial",
"label": "Current inventory"
},
{
"id": "failed-attempt",
"label": "Removal blocked by mount",
"interaction": [
{
"action": "click",
"selector": "button[aria-label='Delete retry-dir']"
}
],
"ready": {
"kind": "selector",
"selector": "[role='alert']"
}
}
]
},
{
"id": "member",
"label": "No Runtime management permission",
"path": "/w/home-member/settings/runtimes/fixture-runtime/workdirs",
"goal": "Read inventory without owner-only removal controls.",
"dataState": "Management permission false.",
"ready": {
"kind": "selector",
"selector": "main table tbody tr"
},
"capturePoints": [
{
"id": "initial",
"label": "Read only inventory"
}
]
},
{
"id": "empty",
"label": "Empty inventory",
"path": "/w/home-empty/settings/runtimes/fixture-runtime/workdirs",
"goal": "Recognize no remaining Workdirs.",
"dataState": "Empty list.",
"ready": {
"kind": "selector",
"selector": "main:has-text('No workdirs')"
},
"capturePoints": [
{
"id": "initial",
"label": "Empty inventory"
}
]
}
],
"processes": [
{
"id": "workdir-fixture",
"command": "deno",
"cwd": "..",
"args": [
"run",
"--allow-net",
"--allow-read",
"browser-tests/workspace_home_fixture_server.ts",
"15290",
"../../web/workspace/build"
],
"readyUrl": "http://127.0.0.1:15290/health"
}
]
}
@@ -0,0 +1,81 @@
{
"schemaVersion": 1,
"id": "workdir-removal-light",
"title": "Ordinary Workdir removal and guarded retry",
"baseUrl": "http://127.0.0.1:15290",
"colorScheme": "light",
"reducedMotion": "reduce",
"personas": [
{
"id": "fixture",
"label": "Synthetic permission projections",
"auth": { "kind": "anonymous" }
}
],
"viewports": [
{ "label": "desktop", "width": 1440, "height": 900 },
{ "label": "narrow", "width": 768, "height": 900 },
{ "label": "mobile", "width": 390, "height": 844 }
],
"routes": [
{
"id": "owner",
"label": "Owner removal",
"path": "/w/home-owner/settings/runtimes/fixture-runtime/workdirs",
"goal": "Remove a failed-cleanup Workdir using ordinary retry without enabling dirty or occupied removal.",
"dataState": "Pending/unknown retry plus dirty and occupied guards. First attempt finds a mount.",
"ready": { "kind": "selector", "selector": "main table tbody tr" },
"capturePoints": [
{ "id": "initial", "label": "Current inventory" },
{
"id": "failed-attempt",
"label": "Removal blocked by mount",
"interaction": [
{
"action": "click",
"selector": "button[aria-label='Delete retry-dir']"
}
],
"ready": { "kind": "selector", "selector": "[role='alert']" }
}
]
},
{
"id": "member",
"label": "No Runtime management permission",
"path": "/w/home-member/settings/runtimes/fixture-runtime/workdirs",
"goal": "Read inventory without owner-only removal controls.",
"dataState": "Management permission false.",
"ready": { "kind": "selector", "selector": "main table tbody tr" },
"capturePoints": [{ "id": "initial", "label": "Read only inventory" }]
},
{
"id": "empty",
"label": "Empty inventory",
"path": "/w/home-empty/settings/runtimes/fixture-runtime/workdirs",
"goal": "Recognize no remaining Workdirs.",
"dataState": "Empty list.",
"ready": {
"kind": "selector",
"selector": "main:has-text('No workdirs')"
},
"capturePoints": [{ "id": "initial", "label": "Empty inventory" }]
}
],
"processes": [
{
"id": "workdir-fixture",
"command": "deno",
"cwd": "..",
"args": [
"run",
"--allow-net",
"--allow-read",
"browser-tests/workspace_home_fixture_server.ts",
"15290",
"../../web/workspace/build"
],
"readyUrl": "http://127.0.0.1:15290/health"
}
]
}