Skip to content

Commit 6ba524b

Browse files
authored
feat(web): compare evaluation runs by opponent (#106)
1 parent 1eaa50e commit 6ba524b

9 files changed

Lines changed: 1374 additions & 4 deletions

File tree

‎tests/browser/comparison.spec.mjs‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { expect, test } from "@playwright/test";
2+
import { readFile } from "node:fs/promises";
3+
const earlier = JSON.parse(await readFile(new URL("../../web/apps/site/src/report-example.json", import.meta.url), "utf8"));
4+
const later = JSON.parse(await readFile(new URL("./fixtures/candidate-tournament.json", import.meta.url), "utf8"));
5+
const failed = JSON.parse(await readFile(new URL("./fixtures/failed-tournament.json", import.meta.url), "utf8"));
6+
const file = value => ({ name: value.run_id + ".json", mimeType: "application/json", buffer: Buffer.from(JSON.stringify(value)) });
7+
const upload = (page, side, value) => page.getByLabel("Open run " + side + " report", { exact: true }).setInputFiles(file(value));
8+
9+
async function chooseSnapshots(page) {
10+
await upload(page, "A", earlier);
11+
await upload(page, "B", later);
12+
await page.getByLabel("Model in run A", { exact: true }).selectOption("example:policy-v1");
13+
await page.getByLabel("Model in run B", { exact: true }).selectOption("example:policy-v2");
14+
}
15+
16+
test("compare native policy snapshots against shared opponents and switch models", async ({ page }) => {
17+
await page.goto("/#/reports");
18+
await page.getByRole("link", { name: "Compare runs", exact: true }).click();
19+
await expect(page.getByRole("heading", { name: "Compare evaluations", exact: true })).toBeVisible();
20+
await chooseSnapshots(page);
21+
await expect(page.getByRole("status").filter({ hasText: "2 shared opponents" })).toBeVisible();
22+
const table = page.getByRole("region", { name: "Comparison by opponent", exact: true });
23+
const minimax = table.getByRole("row").filter({ hasText: "builtin:minimax:3" });
24+
await expect(minimax).toContainText("50.0%");
25+
await expect(minimax).toContainText("0.0%");
26+
await expect(minimax).toContainText("-50.0 pp");
27+
const random = table.getByRole("row").filter({ hasText: "builtin:random" });
28+
await expect(random).toContainText("0.0 pp");
29+
await page.getByLabel("Model in run B", { exact: true }).selectOption("builtin:random");
30+
await expect(page.getByRole("status").filter({ hasText: "1 shared opponent" })).toBeVisible();
31+
await expect(table).toContainText("No shared matchup");
32+
});
33+
34+
test("different environments, position sets and schedules never produce a score change", async ({ page }) => {
35+
await page.goto("/#/reports/compare");
36+
await chooseSnapshots(page);
37+
const changed = structuredClone(later);
38+
changed.config = { max_moves: 10 };
39+
await upload(page, "B", changed);
40+
await expect(page.locator(".comparison-conditions")).toContainText("Settings differ or are incomplete: game options");
41+
await expect(page.locator(".comparison-delta")).toHaveCount(0);
42+
changed.config = {};
43+
delete changed.position_set;
44+
await upload(page, "B", changed);
45+
await expect(page.locator(".comparison-conditions")).toContainText("position-set provenance missing");
46+
await expect(page.locator(".comparison-delta")).toHaveCount(0);
47+
changed.position_set = { id: "other-starts", sha256: "a".repeat(64) };
48+
await upload(page, "B", changed);
49+
await expect(page.locator(".comparison-conditions")).toContainText("position set");
50+
await expect(page.locator(".comparison-delta")).toHaveCount(0);
51+
changed.position_set = null;
52+
changed.matches.forEach(match => { match.seed = String(BigInt(match.seed) + 1n); });
53+
await upload(page, "B", changed);
54+
await expect(page.getByRole("region", { name: "Comparison by opponent", exact: true })).toContainText("Schedules differ");
55+
await expect(page.locator(".comparison-delta")).toHaveCount(0);
56+
});
57+
58+
test("failed games remain unrated and invalid replacement files preserve selected runs", async ({ page }) => {
59+
const writes = [];
60+
page.on("request", request => { if (request.method() !== "GET") writes.push(request.url()); });
61+
await page.goto("/#/reports/compare");
62+
await upload(page, "A", failed);
63+
await upload(page, "B", failed);
64+
await page.getByLabel("Model in run A", { exact: true }).selectOption("example:broken");
65+
await page.getByLabel("Model in run B", { exact: true }).selectOption("example:broken");
66+
const table = page.getByRole("region", { name: "Comparison by opponent", exact: true });
67+
await expect(table).toContainText("No rated games");
68+
await expect(table.getByText("2 excluded", { exact: true })).toHaveCount(2);
69+
await expect(page.locator(".comparison-delta")).toHaveCount(0);
70+
await upload(page, "B", { ...failed, format_version: 2 });
71+
await expect(page.getByRole("alert")).toContainText("format_version 1");
72+
await expect(page.getByLabel("Model in run B", { exact: true })).toHaveValue("example:broken");
73+
await expect(table).toContainText("2 excluded");
74+
expect(writes).toEqual([]);
75+
});
76+
77+
test("comparison works by keyboard on mobile without overflowing the page", async ({ page }) => {
78+
await page.setViewportSize({ width: 360, height: 800 });
79+
await page.goto("/#/reports/compare");
80+
await chooseSnapshots(page);
81+
await page.getByRole("region", { name: "Comparison by opponent", exact: true }).focus();
82+
await expect(page.getByRole("region", { name: "Comparison by opponent", exact: true })).toBeFocused();
83+
await page.keyboard.press("ArrowRight");
84+
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
85+
await page.getByRole("region", { name: "Run A", exact: true }).getByText("Run ID and game options", { exact: true }).focus();
86+
await page.keyboard.press("Enter");
87+
await expect(page.getByRole("region", { name: "Run A", exact: true }).locator("details")).toHaveAttribute("open", "");
88+
});

0 commit comments

Comments
 (0)