Recette design du match : Crawford en paysage, « Nouvelle partie » en deux colonnes, « Rejouer »
- Paysage : pendant la partie Crawford, la consigne de la bande centrale commence par « Partie Crawford · » ; la police se resserre si besoin. - Écran « Nouvelle partie » en paysage : deux colonnes au-dessus de la barre d'action (type de partie et longueur à gauche, niveau et conseil du maître à droite), tout tient en 915x412 et en 844x320. - Fin de partie : « Rejouer » relance tout de suite une partie aux mêmes réglages (un match de même longueur après un match), « Partie suivante » dans un match qui continue, lien « Changer les réglages ». - Recette automatique : aucun élément interactif visible sous une barre d'action collée, vérifié sur chaque écran capturé (tests/browser/recette.mjs). - Scénario « Comment rejouer avec les mêmes réglages ? ». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
67bd207337
commit
74693c4277
+92
-12
@@ -13,10 +13,13 @@
|
||||
// joueur joue toujours le premier pion jouable vers sa première case
|
||||
// d'arrivée, prend les doubles et ne double jamais. Captures de la recette
|
||||
// design (Chromium) dans <dossier des captures>/match/ : écran « Nouvelle
|
||||
// partie », score du match et partie Crawford, en 412×915 et 915×412.
|
||||
// partie » (aussi en 844×320), score du match et partie Crawford, en
|
||||
// 412×915 et 915×412. Sur chaque capture, rien d'interactif ne doit être
|
||||
// caché sous la barre d'action collée (recette.mjs).
|
||||
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
import { hiddenUnderActionBar } from "./recette.mjs";
|
||||
|
||||
const [base, browserName, shots] = process.argv.slice(2);
|
||||
const browserType = { chromium, firefox }[browserName];
|
||||
@@ -29,6 +32,7 @@ const dir = `${shots}/match`;
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const PORTRAIT = { width: 412, height: 915 };
|
||||
const LANDSCAPE = { width: 915, height: 412 };
|
||||
const SMALL_LANDSCAPE = { width: 844, height: 320 };
|
||||
// La graine des dés : un match qui passe par une partie Crawford.
|
||||
const SEED = 7;
|
||||
|
||||
@@ -83,14 +87,18 @@ async function until(what, fn, arg, ms = 60_000) {
|
||||
throw new Error(`attente trop longue : ${what}`);
|
||||
}
|
||||
|
||||
/** Une capture de la recette, dans les deux tailles (Chromium seulement). */
|
||||
async function capture(name) {
|
||||
/** Une capture de la recette, dans chaque taille (Chromium seulement),
|
||||
* après la règle commune : rien d'interactif sous la barre d'action. */
|
||||
async function capture(name, sizes = [PORTRAIT, LANDSCAPE]) {
|
||||
if (!full) {
|
||||
return;
|
||||
}
|
||||
for (const size of [PORTRAIT, LANDSCAPE]) {
|
||||
for (const size of sizes) {
|
||||
await page.setViewportSize(size);
|
||||
await sleep(200);
|
||||
const hidden = await hiddenUnderActionBar(page);
|
||||
check(hidden.length === 0,
|
||||
`${name} ${size.width}x${size.height} : caché sous la barre d'action : ${hidden.join(", ")}`);
|
||||
await page.screenshot({ path: `${dir}/${name}-${size.width}x${size.height}.png` });
|
||||
captures++;
|
||||
}
|
||||
@@ -189,7 +197,45 @@ check((await page.getAttribute("#new-game-crawford a", "href")) === "#/glossaire
|
||||
"lien vers la règle de Crawford");
|
||||
check((await page.textContent("#new-game-level-text")).startsWith("Joue simplement"),
|
||||
"description du niveau Débutant");
|
||||
await capture("1-nouvelle-partie");
|
||||
// Paysage : deux colonnes au-dessus de la barre d'action, tout tient sans
|
||||
// défiler, jusqu'en 844×320 ; le type de partie à gauche, le niveau et le
|
||||
// conseil du maître à droite.
|
||||
for (const size of [LANDSCAPE, SMALL_LANDSCAPE]) {
|
||||
await page.setViewportSize(size);
|
||||
await sleep(200);
|
||||
const where = `${size.width}x${size.height}`;
|
||||
const layout = await page.evaluate(() => {
|
||||
const box = (selector) => {
|
||||
const r = document.querySelector(selector).getBoundingClientRect();
|
||||
return { top: r.top, bottom: r.bottom, left: r.left, right: r.right };
|
||||
};
|
||||
return {
|
||||
scroll: document.scrollingElement.scrollHeight,
|
||||
height: innerHeight,
|
||||
bar: box(".new-game-actions"),
|
||||
kinds: box(".segmented"),
|
||||
lengths: box(".lengths"),
|
||||
rule: box("#new-game-crawford"),
|
||||
bars: box(".level-bars"),
|
||||
name: box("#new-game-level-name"),
|
||||
text: box("#new-game-level-text"),
|
||||
advice: box("#new-game-advice"),
|
||||
};
|
||||
});
|
||||
check(layout.scroll <= layout.height + 1,
|
||||
`${where} : l'écran « Nouvelle partie » défile (${layout.scroll} px pour ${layout.height})`);
|
||||
for (const part of ["kinds", "lengths", "rule", "bars", "name", "text", "advice"]) {
|
||||
check(layout[part].top >= 0 && layout[part].bottom <= layout.bar.top + 1,
|
||||
`${where} : ${part} n'est pas au-dessus de la barre d'action : ${JSON.stringify(layout)}`);
|
||||
}
|
||||
for (const part of ["bars", "name", "text", "advice"]) {
|
||||
check(layout[part].left >= layout.kinds.right,
|
||||
`${where} : ${part} n'est pas dans la colonne de droite : ${JSON.stringify(layout)}`);
|
||||
}
|
||||
}
|
||||
await page.setViewportSize(PORTRAIT);
|
||||
await sleep(200);
|
||||
await capture("1-nouvelle-partie", [PORTRAIT, LANDSCAPE, SMALL_LANDSCAPE]);
|
||||
// Fermer ne change rien : on revient à la partie, et les choix sont gardés.
|
||||
const before = await page.evaluate(() => localStorage.getItem("ustagammon.settings"));
|
||||
await page.click("#new-game-close");
|
||||
@@ -240,7 +286,13 @@ for (;;) {
|
||||
const detail = await page.textContent("#panel-detail");
|
||||
const primary = await page.textContent("#panel-primary");
|
||||
log(`partie ${games}${crawford ? " (Crawford)" : ""} : ${title} ; ${detail}`);
|
||||
if (primary === "Nouvelle partie") {
|
||||
const settingsLink = await page.evaluate(() => {
|
||||
const a = document.getElementById("panel-settings");
|
||||
return a.hidden ? null : { text: a.textContent, href: a.getAttribute("href") };
|
||||
});
|
||||
check(settingsLink?.text === "Changer les réglages" && settingsLink.href === "#/nouvelle-partie",
|
||||
`lien « Changer les réglages » : ${JSON.stringify(settingsLink)}`);
|
||||
if (primary === "Rejouer") {
|
||||
check(/gagne(z)? le match$/.test(title) && detail.includes("Score final"),
|
||||
`fin du match : ${title} ; ${detail}`);
|
||||
const [, player, computer] = /vous (\d+), l'ordinateur (\d+)/.exec(detail) ?? [];
|
||||
@@ -254,20 +306,30 @@ for (;;) {
|
||||
`score en paysage : « ${box} »`);
|
||||
await page.setViewportSize(PORTRAIT);
|
||||
await sleep(200);
|
||||
// Après le match, « Nouvelle partie » mène à l'écran des choix.
|
||||
await page.click("#panel-primary");
|
||||
// Après le match, « Changer les réglages » mène à l'écran des
|
||||
// choix ; le fermer ramène au résultat, où « Rejouer » relance
|
||||
// tout de suite un match de même longueur.
|
||||
await page.click("#panel-settings");
|
||||
await until("écran « Nouvelle partie » après le match", () =>
|
||||
location.hash === "#/nouvelle-partie");
|
||||
location.hash === "#/nouvelle-partie" && document.getElementById("panel-veil").hidden);
|
||||
await page.click("#new-game-close");
|
||||
await until("retour au résultat du match", () => location.hash === "#/" &&
|
||||
!document.getElementById("again").hidden);
|
||||
check((await page.textContent("#again")) === "Rejouer",
|
||||
`après le match, barre d'actions : « ${await page.textContent("#again")} »`);
|
||||
log(`match ${matches} fini : ${games} parties, ${crawfordGames} Crawford au total`);
|
||||
await page.click("#again");
|
||||
await until("match rejoué", () => location.hash === "#/" &&
|
||||
!document.body.classList.contains("busy"));
|
||||
const again = await shownMatch();
|
||||
check(again.line === "Match en 3 points" && again.computer === "0" && again.player === "0",
|
||||
`« Rejouer » après le match : ${JSON.stringify(again)}`);
|
||||
if (crawfordGames > 0 && scoreShot) {
|
||||
break;
|
||||
}
|
||||
check(matches < 5, `aucune partie Crawford en ${matches} matchs (graine ${SEED})`);
|
||||
matches++;
|
||||
games = 1;
|
||||
await page.click("#new-game-start");
|
||||
await until("match suivant", () => location.hash === "#/" &&
|
||||
!document.body.classList.contains("busy"));
|
||||
continue;
|
||||
}
|
||||
check(primary === "Partie suivante" && /Match en 3 points : vous \d+, l'ordinateur \d+/.test(detail),
|
||||
@@ -281,6 +343,24 @@ for (;;) {
|
||||
check(next.cube === 0, "partie Crawford : le videau est dessiné");
|
||||
if (!crawfordShot) {
|
||||
crawfordShot = true;
|
||||
check(next.line === "Match en 3 points · partie Crawford",
|
||||
`partie Crawford, portrait : « ${next.line} »`);
|
||||
await page.setViewportSize(LANDSCAPE);
|
||||
await sleep(200);
|
||||
const band = await page.evaluate(() => {
|
||||
const text = document.getElementById("band-text");
|
||||
const pill = document.querySelector("#board .band-pill");
|
||||
return {
|
||||
text: text?.textContent ?? "",
|
||||
fits: text !== null && pill !== null &&
|
||||
text.getBBox().width <= pill.getBBox().width - 16,
|
||||
};
|
||||
});
|
||||
check(band.text.startsWith("Partie Crawford · "),
|
||||
`partie Crawford, paysage : « ${band.text} »`);
|
||||
check(band.fits, `partie Crawford, paysage : la consigne déborde (« ${band.text} »)`);
|
||||
await page.setViewportSize(PORTRAIT);
|
||||
await sleep(200);
|
||||
await capture("3-partie-crawford");
|
||||
}
|
||||
} else if (!scoreShot) {
|
||||
|
||||
Reference in New Issue
Block a user