3c36a3b57f
Two Copilot findings on PR #24, both correct, both applied. **`test.fail` was masking setup failures.** Applied before navigation, it made any earlier failure — the page not loading, the script not starting, `#status` never updating — an *expected* failure, so CI stayed green through it. That is precisely the check-the-checker defect these tests exist to catch, wearing the name of its cure. Both tests now assert their setup first and apply `test.fail` immediately before the one assertion the known bug owns. Verified by breaking the setup (making `paintStatus` a no-op): before, 2 passed; after, **2 failed**. **Firefox now runs the whole battery, and it passes.** improvise-xf1's acceptance criterion names Chrome, Firefox and Safari, and the reason is real rather than ceremonial: `createSyncAccessHandle` is the one API this design rests on (gotcha 28) and each engine implemented it separately, so Chromium alone was one engine's opinion. `storage_contract` against `OpfsFs` now passes on **two** engines. **Safari is not covered, and the criterion is met for two of three.** Playwright's WebKit build provides no OPFS at all — `navigator.storage.getDirectory()` itself rejects with `UnknownError` — so there is nothing there for `OpfsFs` to be right or wrong about. Rather than delete the project and quietly narrow the claim, it is kept and the spec skips on a **runtime probe**: the day that build gains OPFS the checks start running with no CI change. The skip is conditional on the probe and not on the browser name, and an unexpected skip on any other engine **fails** with "that is a regression, not a known gap" — a silently skipped check being the failure this harness exists to prevent. The shell spec stays Chromium-only on purpose: it is about `web/app.js`'s own logic, which is engine-independent, and running `routeWebSocket` plus the worker handshake three times is slower and flakier to no benefit. 14 passing, 3 skipped, 36s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
103 lines
4.3 KiB
JavaScript
103 lines
4.3 KiB
JavaScript
// `storage_contract` against `OpfsFs`, in a browser (improvise-vvl3/xf1).
|
|
//
|
|
// `OpfsFs` has been compile-checked and never run. Everything found in a
|
|
// browser so far was found by a person pressing keys, which is not a
|
|
// repeatable check. This is.
|
|
//
|
|
// The checks themselves are in Rust (harness/opfs-contract) because that is
|
|
// where `OpfsFs` is and where the contract is; this file drives them and reads
|
|
// the report. Assertions here are about *the report*, deliberately, so a
|
|
// worker that never started or a build that dropped a check fails rather than
|
|
// passing quietly.
|
|
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
/** Run one entry point in the harness worker. */
|
|
async function run(page, cmd) {
|
|
return JSON.parse(await page.evaluate((c) => window.opfs(c), cmd));
|
|
}
|
|
|
|
/** The checks the report must contain, in order. Named here as well as in
|
|
* Rust on purpose: a check deleted on one side has to be deleted on both,
|
|
* which is what stops the battery quietly shrinking. */
|
|
const EXPECTED = [
|
|
"open_and_refresh_see_an_origin_root_holding_no_documents",
|
|
"an_attached_but_unwritten_entry_is_not_a_document",
|
|
"a_directory_entry_is_not_a_document",
|
|
"refresh_walks_every_key_and_sorts_them",
|
|
"read_and_write_are_absolute_at_offset_zero",
|
|
'storage_contract_at_""',
|
|
'storage_contract_at_"docs"',
|
|
"a_delete_is_visible_at_once_and_lands_on_commit",
|
|
];
|
|
|
|
test.describe("OpfsFs in a real browser", () => {
|
|
test.beforeEach(async ({ page, browserName }, testInfo) => {
|
|
await page.goto("/opfs/index.html");
|
|
|
|
// improvise-xf1's acceptance criterion names Chrome, Firefox and Safari.
|
|
// Chromium and Firefox run the whole battery. Playwright's WebKit build
|
|
// provides **no OPFS at all** — `navigator.storage.getDirectory()` itself
|
|
// rejects with `UnknownError` — so there is nothing there for `OpfsFs` to
|
|
// be right or wrong about, and Safari stays uncovered. See the hand-back
|
|
// and `playwright.config.js`.
|
|
//
|
|
// The skip is conditional on the PROBE, not on the browser name, so it
|
|
// cannot quietly swallow a real regression: if Chromium or Firefox ever
|
|
// stops providing OPFS, this fails rather than skipping. A silently
|
|
// skipped check is exactly the failure this harness exists to prevent, so
|
|
// an unexpected skip also fails.
|
|
const opfs = await page.evaluate(async () => {
|
|
try {
|
|
await navigator.storage.getDirectory();
|
|
return "yes";
|
|
} catch (err) {
|
|
return String(err?.name ?? err);
|
|
}
|
|
});
|
|
if (opfs !== "yes") {
|
|
expect(
|
|
browserName,
|
|
`${browserName} lost OPFS (${opfs}) — that is a regression, not a known gap`,
|
|
).toBe("webkit");
|
|
testInfo.skip(true, `this WebKit build provides no OPFS (${opfs})`);
|
|
}
|
|
});
|
|
|
|
// Check the checker, and do it first: a green run against a worker that
|
|
// never started is the failure this repo has a doctrine about. This drives
|
|
// the *same* reporting path every real check uses.
|
|
test("the harness can see a failure", async ({ page }) => {
|
|
const report = await run(page, "run_canary");
|
|
expect(report).toHaveLength(1);
|
|
expect(report[0].name).toBe("the_canary_must_fail");
|
|
expect(report[0].ok).toBe(false);
|
|
expect(report[0].detail).toContain("the harness can see a failure");
|
|
});
|
|
|
|
test("every check in the battery runs and passes", async ({ page }) => {
|
|
const report = await run(page, "run_checks");
|
|
|
|
// The battery is complete before any of it is believed.
|
|
expect(report.map((c) => c.name)).toEqual(EXPECTED);
|
|
|
|
const failed = report.filter((c) => !c.ok);
|
|
expect(
|
|
failed,
|
|
`failing checks:\n${failed.map((c) => ` ${c.name}: ${c.detail}`).join("\n")}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
// The one thing no in-page check can establish. gotcha 28: a delete is
|
|
// visible immediately and lands in OPFS at the end of the batch — so
|
|
// "commit_deletes actually removed it" is only answered by a fresh page.
|
|
// This is also the storage half of improvise-bck's persistence criterion.
|
|
test("what OPFS held survives a reload, and a committed delete does not", async ({
|
|
page,
|
|
}) => {
|
|
expect(await page.evaluate(() => window.opfs("before_reload"))).toBe("staged");
|
|
await page.reload();
|
|
expect(await page.evaluate(() => window.opfs("after_reload"))).toBe("survived");
|
|
});
|
|
});
|