Mes parties : scénarios, glossaire, test navigateur sur IndexedDB
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
274fbf6252
commit
31815134e5
+85
-4
@@ -12,6 +12,7 @@
|
||||
// premier problème, ou si la page signale une erreur (dont une violation de
|
||||
// la politique de sécurité du contenu).
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
|
||||
const [base, browserName, shots] = process.argv.slice(2);
|
||||
@@ -78,9 +79,49 @@ function check(condition, message) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Les coups joués de chaque camp, d'après le journal enregistré. */
|
||||
/** Le journal de la partie en cours, tel qu'enregistré dans IndexedDB
|
||||
* (app/src/storage/indexeddb-backend.ts), ou null. */
|
||||
async function readJournal() {
|
||||
return page.evaluate(
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
const open = indexedDB.open("ustagammon");
|
||||
open.onerror = () => reject(open.error);
|
||||
open.onsuccess = () => {
|
||||
const db = open.result;
|
||||
const tx = db.transaction(["settings", "journals"]);
|
||||
const current = tx.objectStore("settings").get("current");
|
||||
current.onsuccess = () => {
|
||||
const id = current.result?.value;
|
||||
const journal = id === undefined ? null : tx.objectStore("journals").get(id);
|
||||
if (journal === null) {
|
||||
db.close();
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
journal.onsuccess = () => {
|
||||
db.close();
|
||||
resolve(journal.result?.journal ?? null);
|
||||
};
|
||||
};
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** Le journal enregistré, une fois les enregistrements en cours terminés
|
||||
* (l'application enregistre sans attendre) : deux lectures identiques. */
|
||||
async function savedJournal() {
|
||||
return page.evaluate(() => JSON.parse(localStorage.getItem("ustagammon.current-game")));
|
||||
let last = await readJournal();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
const next = await readJournal();
|
||||
if (JSON.stringify(next) === JSON.stringify(last)) {
|
||||
return next;
|
||||
}
|
||||
last = next;
|
||||
}
|
||||
throw new Error("le journal enregistré ne se stabilise pas");
|
||||
}
|
||||
|
||||
function countMoves(journal, side) {
|
||||
@@ -123,8 +164,7 @@ async function waitForPlayer() {
|
||||
return (
|
||||
`${busy} ; « ${text("info")} » ; « ${text("prompt")} » ; moteur : ` +
|
||||
`${engine.sent} commandes, ${engine.received} réponses, dernière ` +
|
||||
`${JSON.stringify(engine.last)} ; journal ` +
|
||||
localStorage.getItem("ustagammon.current-game")
|
||||
`${JSON.stringify(engine.last)}`
|
||||
);
|
||||
})
|
||||
.catch((error) => `page illisible (${error.message})`);
|
||||
@@ -487,6 +527,47 @@ if (computerMs.length > 0) {
|
||||
}
|
||||
await shot("4-fin-de-partie");
|
||||
|
||||
step("mes parties");
|
||||
// Mes parties (IndexedDB) : une deuxième partie commencée, les deux
|
||||
// listées, la première exportée en SGF puis reprise, après rechargement.
|
||||
{
|
||||
const finished = await savedJournal();
|
||||
await page.click("#again");
|
||||
await waitForPlayer();
|
||||
const second = await savedJournal();
|
||||
check(second.head !== finished.head, "la nouvelle partie n'a pas été enregistrée");
|
||||
await page.click("#games-button");
|
||||
await page.waitForSelector("#games-dialog[open] .games-item");
|
||||
const items = page.locator("#games-dialog .games-item");
|
||||
check((await items.count()) === 2, `${await items.count()} parties listées au lieu de 2`);
|
||||
const oldest = items.nth(1);
|
||||
await oldest.locator('[data-action="branches"]').click();
|
||||
await oldest.locator(".games-branch").first().waitFor();
|
||||
const branchCount = await oldest.locator(".games-branch").count();
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download"),
|
||||
oldest.locator('[data-action="export"]').click(),
|
||||
]);
|
||||
const sgf = readFileSync(await download.path(), "utf8");
|
||||
writeFileSync(`${shots}/${browserName}-partie.sgf`, sgf);
|
||||
check(sgf.startsWith("(;FF[4]GM[6]"), `export SGF inattendu : ${sgf.slice(0, 40)}`);
|
||||
check(
|
||||
(sgf.match(/;[BW]\[\d\d/g) ?? []).length === countMoves(finished, "player") + countMoves(finished, "computer"),
|
||||
"export SGF : nombre de coups différent du journal",
|
||||
);
|
||||
await shot("4b-mes-parties");
|
||||
await collectViolations();
|
||||
await Promise.all([page.waitForEvent("load"), oldest.locator('[data-action="resume"]').click()]);
|
||||
await waitForPlayer();
|
||||
const resumed = await savedJournal();
|
||||
check(resumed.head === finished.head, "la première partie n'a pas été reprise");
|
||||
check((await page.textContent("#prompt")) === result, "écran différent après la reprise");
|
||||
log(
|
||||
`mes parties : 2 parties listées (${branchCount} branche(s) pour la première), ` +
|
||||
`SGF exporté (${sgf.length} octets), première partie reprise après rechargement`,
|
||||
);
|
||||
}
|
||||
|
||||
step("aide");
|
||||
// Aide : scénario et glossaire lus dans les copies de docs/.
|
||||
await page.click("#help-button");
|
||||
|
||||
Reference in New Issue
Block a user