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
2 changes: 1 addition & 1 deletion .claude/skills/verify-debatelab/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ It also prints the commit the stack started at and whether there are uncommitted
- `followSystemTheme(page)`: new accounts start on the light theme. A recipe that compares light and dark calls this first; it picks System in Settings, so `page.emulateMedia({ colorScheme })` then drives the theme.
- `expectStreamed([...])`: every provider call for those features asked the model to stream, which proves the page showed the answer live.
- `expectProviderCalls([...])`: what reached the provider. Each test writes `provider-calls.json`, and in live mode the fixture fails the test if any chat call was answered by a model other than the pin.
- `evidence`: `step(line)`, `aria(name, locator?)`, `shot(name)` and `json(name, value)`.
- `evidence`: `step(line)`, `aria(name, locator?)`, `shot(name)` and `json(name, value)`. Every `shot` also fails the recipe (softly, so all bad screens are reported) when the page is wider than the screen or a visible image failed to load. Still open the screenshots: this check can't see clipped text, overlaps or wrong colours.
- Automatic failure on any uncaught page error, horizontal overflow, or remote Supabase request.

Every result, source and practice round belongs to a Current Debate or is explicitly one-off. A tool given a motion when no debate exists opens `Start a debate with this motion?`; a motion that differs from the Current Debate's opens `This is a different motion`. Both open after the submit click and before the AI request, so create the `aiResponse()` promise, click, answer with `chooseForMotion`, then await. Or call `startDebate` first, after which tools prefill its motion and open no dialog. The Library's `Add source` is disabled until a debate exists. See `features/current-debate.md`.
Expand Down
22 changes: 21 additions & 1 deletion .claude/skills/verify-debatelab/recipes/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,28 @@ export async function expectProviderCalls(features: string[]) {
return calls;
}

/**
* What no screenshot should show: a page wider than the screen, or an image
* that failed to load. Agents keep evidence they never open (one shipped a
* broken badge image in its own screenshot), so every `shot` checks for both.
* Soft, so one recipe reports every bad screen. Lazy images not yet fetched
* are skipped; an image that tried and got nothing is not.
*/
export async function expectSoundScreen(target: Page, name: string) {
const { overflow, brokenImages } = await target.evaluate(() => ({
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
brokenImages: [...document.images]
.filter((img) => img.currentSrc && img.complete && img.naturalWidth === 0)
.filter((img) => img.getClientRects().length > 0 && getComputedStyle(img).visibility !== "hidden")
.map((img) => img.currentSrc),
}));
expect.soft(overflow, `${name}: horizontal page overflow in px`).toBeLessThanOrEqual(1);
expect.soft(brokenImages, `${name}: images that failed to load`).toEqual([]);
}

export type Evidence = {
dir: string;
/** Full-page screenshot, `<name>.png`. */
/** Full-page screenshot, `<name>.png`, after `expectSoundScreen`. */
shot(name: string, target?: Page): Promise<string>;
/** ARIA snapshot of a region (default: whole page), `<name>.aria.yml`. */
aria(name: string, locator?: Locator): Promise<string>;
Expand Down Expand Up @@ -133,6 +152,7 @@ export const test = stackTest.extend<{ evidence: Evidence }>({
async shot(name, target = page) {
const path = file(`${name}.png`);
await target.screenshot({ path, fullPage: true });
await expectSoundScreen(target, name);
return path;
},
async aria(name, locator = page.locator("body")) {
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/verify-debatelab/recipes/localization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ test("loc-switcher: sign-in and not-found pages carry the language switcher", as
await evidence.shot("02-login-hu");

await page.goto("/no-such-page");
await expect(page.getByRole("heading", { level: 1, name: hu.notFound.title })).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: hu.notFound.message })).toBeVisible();
await expect(page).toHaveTitle(hu.pageHead.notFound.title.replace("{{brand}}", BRAND));
await evidence.shot("03-not-found-hu");
await chooseLanguage(page, "es");
await expect(page).toHaveTitle(es.pageHead.notFound.title.replace("{{brand}}", BRAND));
await expect(page.getByText(es.notFound.message)).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: es.notFound.message })).toBeVisible();
await evidence.shot("04-not-found-es");
});

Expand Down
Loading