Progression (1h) : courbe du taux d'erreur, parties terminées, Créer des exercices #63
@@ -203,7 +203,7 @@ export function createProgressionView(options: ProgressionViewOptions): Progress
|
||||
}).setAttribute("role", "status");
|
||||
}
|
||||
|
||||
// « Créer des exercices » : la feuille du prompt (1f), secondaire.
|
||||
// « Créer des exercices » : la feuille du prompt, étape 1f ; bouton secondaire.
|
||||
if (points.length > 0) {
|
||||
const actions = element(body, "div", { className: "progression-actions" });
|
||||
const exercises = element(actions, "a", {
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ Repris du cadrage (modules, écrans clés, IA générative en V1) et des ADR 001
|
||||
| 1e. Fin de partie et analyse (fait) | Bilan (ADR 0011) : résultat, coups par appréciation, taux d'erreur comparé à la chance, « le coup à revoir » ; écran d'analyse : graphe d'équité (toucher un point ouvre la position), liste des erreurs filtrable, visualiseur pas à pas coup joué contre meilleur coup, « Rejouer d'ici » | 1a, 1b, 1d |
|
||||
| 1f. Prompts à copier (fait) | Gabarits versionnés et testés (`docs/prompts/` ou équivalent, source unique), bouton « Demander à une IA » sur le plateau, le conseil du maître, l'analyse et la fin de partie ; le moteur décide, l'IA explique | 1d, 1e |
|
||||
| 1g. Accueil, tutoriel et leçons (fait) | Accueil (reprendre, nouvelle partie, prochaine leçon, progression résumée), tutoriel des règles interactif et illustré (0e.5), cinq premières leçons numérotées (ADR 0011) avec positions et « à vous de jouer », progression locale ; contenus CC BY-SA 4.0 livrés sans relecture préalable, sous les garde-fous du moteur (décision du 2026-11-09) | 0e.5, 1a |
|
||||
| 1h. Progression | Historique des parties et courbe du taux d'erreur | 1b, 1e |
|
||||
| 1h. Progression (fait) | Historique des parties et courbe du taux d'erreur | 1b, 1e |
|
||||
| 1i. Plateau soigné | Dessin du plateau aligné sur la charte (`docs/identite/charte/board.js`), animations des pions, glisser-déposer en plus du toucher, double toucher pour le seul coup évident | 0e.3 |
|
||||
| 1j. Plan de test | `docs/guide-test-etape-1.md` : parcours du débutant et du joueur de club sur le téléphone, avec ce qu'il faut rapporter | toutes |
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
# Progression (1h)
|
||||
|
||||
- **Date** : 2026-11-03
|
||||
- **Étape** : 1h (`docs/etape-1.md`)
|
||||
- **Statut** : prévu (après 1e)
|
||||
- **Exécutant** : sous-agent general-purpose, dans un worktree
|
||||
|
||||
## Objectif
|
||||
|
||||
Mettre en place la progression du cadrage pour la V1 : l'historique des parties et la courbe du taux d'erreur.
|
||||
|
||||
## Consignes
|
||||
|
||||
1. **Écran `#/progression`.** Il est conforme à la charte et s'ajoute au tableau de l'ADR 0013. Il montre :
|
||||
- la courbe du taux d'erreur par partie terminée, en SVG à la main, sans dépendance ;
|
||||
- la liste des parties, reliée à leur bilan et à leur analyse (1e) ;
|
||||
- un résumé en une phrase.
|
||||
2. **Accueil.** Il reçoit la « ligne de progression » de la charte.
|
||||
3. **Données.** Elles proviennent des analyses enregistrées : rien n'est recalculé si l'analyse existe. Une partie non analysée est analysée en arrière-plan.
|
||||
4. **Tests.**
|
||||
- Scénarios Gherkin.
|
||||
- `make lint`, `make test`, `make test-browser` avec trois parties jouées, et captures.
|
||||
|
||||
## Critères de réussite
|
||||
|
||||
Après trois parties, la courbe a trois points égaux aux taux d'erreur des bilans. Les trois commandes passent.
|
||||
|
||||
## Résultat
|
||||
|
||||
À remplir à la clôture.
|
||||
@@ -45,8 +45,8 @@ Fonctionnalité: Suivre ma progression
|
||||
|
||||
Scénario: Comment créer des exercices sur mes erreurs ?
|
||||
« Créer des exercices » prépare une question pour une IA : vos erreurs
|
||||
les plus coûteuses sur vos dernières parties, avec leur coût d'après le
|
||||
moteur, ce qui y revient (pions frappés, points faits par le coup du
|
||||
les plus coûteuses sur vos dernières parties, avec leur coût selon le
|
||||
maître, ce qui y revient (pions frappés, points faits par le coup du
|
||||
maître et pas par le vôtre), et la position de la plus coûteuse. Votre
|
||||
niveau n'y figure qu'à partir de 20 décisions analysées en tout.
|
||||
|
||||
|
||||
+14
-4
@@ -20,10 +20,13 @@
|
||||
# cadre, flèches du tutoriel, en thème clair et sombre), captures dans
|
||||
# build/browser-shots/retours-joueurs/. Les leçons (tests/browser/lecons.mjs :
|
||||
# carte de l'accueil, liste, explication, exercice refusé puis réussi,
|
||||
# progression gardée), captures dans build/browser-shots/lecons/.
|
||||
# progression gardée), captures dans build/browser-shots/lecons/. La
|
||||
# progression (tests/browser/progression.mjs : cas vide, trois parties, courbe
|
||||
# égale aux taux des bilans, ligne de l'accueil, « Créer des exercices »),
|
||||
# captures dans build/browser-shots/progression/.
|
||||
#
|
||||
# BROWSERS=chromium et SCRIPTS=accueil (ou play, match, maitre, bilan,
|
||||
# prompts, nouvelle-partie, retours-joueurs, lecons) restreignent le test.
|
||||
# prompts, nouvelle-partie, retours-joueurs, lecons, progression) restreignent le test.
|
||||
#
|
||||
# Outils : l'image de Playwright (navigateurs) et le paquet playwright,
|
||||
# installé une fois par npm dans build/browser-tools/ (réseau nécessaire la
|
||||
@@ -36,7 +39,7 @@ cd "$(dirname "$0")/.."
|
||||
image="${PLAYWRIGHT_IMAGE:?lancer via make test-browser}"
|
||||
browsers="${BROWSERS:-chromium firefox}"
|
||||
# Les scripts à lancer (tests/browser/<nom>.mjs), tous par défaut.
|
||||
scripts="${SCRIPTS:-play accueil match maitre bilan prompts nouvelle-partie retours-joueurs lecons}"
|
||||
scripts="${SCRIPTS:-play accueil match maitre bilan prompts nouvelle-partie retours-joueurs lecons progression}"
|
||||
|
||||
unset MAKEFLAGS MFLAGS MAKELEVEL
|
||||
make -s package >/dev/null
|
||||
@@ -75,7 +78,7 @@ rm -rf build/browser-shots
|
||||
mkdir -p build/browser-shots
|
||||
cp tests/browser/play.mjs tests/browser/accueil.mjs tests/browser/match.mjs tests/browser/recette.mjs \
|
||||
tests/browser/maitre.mjs tests/browser/bilan.mjs tests/browser/prompts.mjs tests/browser/nouvelle-partie.mjs \
|
||||
tests/browser/retours-joueurs.mjs tests/browser/lecons.mjs "$tools/"
|
||||
tests/browser/retours-joueurs.mjs tests/browser/lecons.mjs tests/browser/progression.mjs "$tools/"
|
||||
status=0
|
||||
for browser in $browsers; do
|
||||
# Réseau de l'hôte : le site est sur 127.0.0.1. Le service worker
|
||||
@@ -145,5 +148,12 @@ for browser in $browsers; do
|
||||
-v "$PWD/$tools:/tools" -v "$PWD/build/browser-shots:/shots" -w /tools "$image" \
|
||||
node lecons.mjs "http://localhost:$port/" "$browser" /shots || status=1
|
||||
fi
|
||||
if [[ " $scripts " == *" progression "* ]]; then
|
||||
# La progression : courbe du taux d'erreur, parties, exercices ;
|
||||
# captures dans build/browser-shots/progression/.
|
||||
docker run --rm --init --ipc=host --network host --user "$(id -u):$(id -g)" -e HOME=/tmp \
|
||||
-v "$PWD/$tools:/tools" -v "$PWD/build/browser-shots:/shots" -w /tools "$image" \
|
||||
node progression.mjs "http://localhost:$port/" "$browser" /shots || status=1
|
||||
fi
|
||||
done
|
||||
exit "$status"
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
// progression.mjs -- l'écran de progression (étape 1h) dans un vrai
|
||||
// navigateur sans écran (Playwright), sur le site servi par l'image de
|
||||
// production (charte, ecrans-etape-1.md, « 1h. Progression »).
|
||||
//
|
||||
// Copyright (C) 2026 les auteurs d'Ustagammon.
|
||||
// Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||||
//
|
||||
// Usage : node progression.mjs <adresse du site> <chromium|firefox> <dossier des captures>
|
||||
// Lancé par tests/browser.sh. L'écran sans partie terminée, puis trois
|
||||
// fins de course courtes jouées par l'interface, depuis un lien de
|
||||
// position, avec des dés imposés (comme bilan.mjs) : le joueur a 6-1 et
|
||||
// deux pions, sur 6 et 1 ; il joue 6/5/off (grosse erreur, l'ordinateur
|
||||
// double et le joueur passe), puis 6/off 1/off (gagne), puis de nouveau
|
||||
// 6/5/off. Vérifie que la ligne de l'accueil mène à #/progression, que la
|
||||
// courbe a trois points égaux, dans l'ordre, aux taux d'erreur des trois
|
||||
// bilans, la phrase du maître, la liste reliée aux bilans et aux analyses,
|
||||
// et « Créer des exercices » (feuille du prompt). Rien d'interactif sous
|
||||
// une barre d'action, aucune erreur ni violation de la politique de
|
||||
// sécurité. Captures (Chromium) dans <dossier des captures>/progression/,
|
||||
// en 412×915 et 915×412.
|
||||
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { chromium, firefox } from "playwright";
|
||||
import { hiddenUnderActionBar, tallButtons } from "./recette.mjs";
|
||||
|
||||
const [base, browserName, shots] = process.argv.slice(2);
|
||||
const browserType = { chromium, firefox }[browserName];
|
||||
if (!base || !browserType || !shots) {
|
||||
console.error("usage : node progression.mjs <adresse> <chromium|firefox> <dossier des captures>");
|
||||
process.exit(2);
|
||||
}
|
||||
const full = browserName === "chromium";
|
||||
const dir = `${shots}/progression`;
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const PORTRAIT = { width: 412, height: 915 };
|
||||
const LANDSCAPE = { width: 915, height: 412 };
|
||||
const SIZES = [PORTRAIT, LANDSCAPE];
|
||||
// Le joueur au trait : un pion sur 6, un sur 1 ; l'ordinateur : un sur 1,
|
||||
// un sur 2 (positions de gnubg, chacun vu de lui-même).
|
||||
const BEAROFF = "BQAACAIAAAAAAA";
|
||||
|
||||
const problems = [];
|
||||
const log = (text) => console.log(`[${browserName}] ${text}`);
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
let captures = 0;
|
||||
|
||||
function check(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
const browser = await browserType.launch();
|
||||
const context = await browser.newContext({
|
||||
viewport: PORTRAIT,
|
||||
deviceScaleFactor: 1,
|
||||
locale: "fr-FR",
|
||||
reducedMotion: "reduce",
|
||||
});
|
||||
await context.addInitScript(() => {
|
||||
// Les dés imposés d'abord (un dé par valeur), puis une graine fixe.
|
||||
window.__dice = [];
|
||||
let state = 5;
|
||||
Math.random = () => {
|
||||
const die = window.__dice.shift();
|
||||
if (die !== undefined) {
|
||||
return (die - 0.5) / 6;
|
||||
}
|
||||
state = (state * 1103515245 + 12345) % 2147483648;
|
||||
return state / 2147483648;
|
||||
};
|
||||
window.__csp = [];
|
||||
document.addEventListener("securitypolicyviolation", (e) =>
|
||||
window.__csp.push(`${e.effectiveDirective} ${e.blockedURI}`),
|
||||
);
|
||||
});
|
||||
const page = await context.newPage();
|
||||
page.on("pageerror", (error) => problems.push(`erreur de la page : ${error.message}`));
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
problems.push(`console : ${message.text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
/** Attend une condition dans la page (sans page.waitForFunction, qui passe
|
||||
* par eval, interdit par la politique de sécurité du site). */
|
||||
async function until(what, fn, arg, ms = 120_000) {
|
||||
for (let waited = 0; waited < ms; waited += 100) {
|
||||
if (await page.evaluate(fn, arg)) {
|
||||
return;
|
||||
}
|
||||
await sleep(100);
|
||||
}
|
||||
await page.screenshot({ path: `${dir}/${browserName}-bloque.png` }).catch(() => {});
|
||||
throw new Error(`attente trop longue : ${what}`);
|
||||
}
|
||||
|
||||
const idle = () => until("écran libre", () => !document.body.classList.contains("busy"));
|
||||
|
||||
/** Une capture de la recette dans les deux tailles (Chromium seulement),
|
||||
* après la règle commune ; settle replace l'écran après le changement de
|
||||
* taille. */
|
||||
async function capture(name, settle = async () => {}) {
|
||||
if (!full) {
|
||||
return;
|
||||
}
|
||||
for (const size of SIZES) {
|
||||
await page.setViewportSize(size);
|
||||
await sleep(300);
|
||||
await settle();
|
||||
const hidden = await hiddenUnderActionBar(page);
|
||||
check(
|
||||
hidden.length === 0,
|
||||
`${name} ${size.width}x${size.height} : caché sous la barre d'action : ${hidden.join(", ")}`,
|
||||
);
|
||||
const tall = await tallButtons(page);
|
||||
check(tall.length === 0, `${name} ${size.width}x${size.height} : boutons étirés : ${tall.join(", ")}`);
|
||||
await page.screenshot({ path: `${dir}/${name}-${size.width}x${size.height}.png`, fullPage: true });
|
||||
captures++;
|
||||
}
|
||||
await page.setViewportSize(PORTRAIT);
|
||||
await sleep(300);
|
||||
}
|
||||
|
||||
/** Une nouvelle partie à partir d'une position, joueur au trait. */
|
||||
async function startFrom(position) {
|
||||
await page.goto(`${base}#/position/${encodeURIComponent(position)}`);
|
||||
await until("position lue", () => {
|
||||
const play = document.getElementById("position-play");
|
||||
return !play.hidden && !play.disabled;
|
||||
});
|
||||
await page.click("#position-play");
|
||||
await until(
|
||||
"partie commencée",
|
||||
() =>
|
||||
location.hash === "#/" &&
|
||||
!document.body.classList.contains("busy") &&
|
||||
!document.getElementById("roll").hidden,
|
||||
);
|
||||
}
|
||||
|
||||
const place = (point) => (point === 0 ? "off-player" : `p${point}`);
|
||||
|
||||
/** Lance les dés imposés et joue un coup, demi-coup par demi-coup. */
|
||||
async function play(dice, halfMoves) {
|
||||
await page.evaluate((d) => {
|
||||
window.__dice.push(...d);
|
||||
}, dice);
|
||||
await page.click("#roll");
|
||||
await idle();
|
||||
for (const [from, to] of halfMoves) {
|
||||
await page.click(`#board .hit[data-place="${place(from)}"]`);
|
||||
await idle();
|
||||
const land = page.locator(`#board .hit.can-land[data-place="${place(to)}"]`);
|
||||
if ((await land.count()) > 0) {
|
||||
await land.click();
|
||||
await idle();
|
||||
}
|
||||
}
|
||||
check(!(await page.locator("#confirm").isHidden()), `coup incomplet : ${JSON.stringify(halfMoves)}`);
|
||||
await page.click("#confirm");
|
||||
}
|
||||
|
||||
/** Le bilan de la partie qui vient de finir : son identifiant et son taux
|
||||
* d'erreur (celui de la jauge « Votre jeu »). */
|
||||
async function summaryRate() {
|
||||
await until(
|
||||
"bilan prêt",
|
||||
() =>
|
||||
/^#\/parties\/[a-z0-9-]+\/bilan$/.test(location.hash) &&
|
||||
document.getElementById("summary")?.dataset.ready === "true",
|
||||
);
|
||||
return page.evaluate(() => ({
|
||||
id: location.hash.split("/")[2],
|
||||
rate: document.getElementById("gauge-play").dataset.errorRate,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Une partie : le joueur joue 6/5/off (puis passe le double) ou 6/off
|
||||
* 1/off. */
|
||||
async function oneGame(mistake) {
|
||||
await startFrom(BEAROFF);
|
||||
if (mistake) {
|
||||
await play([6, 1], [[6, 5], [5, 0]]);
|
||||
await until("double de l'ordinateur", () => !document.getElementById("drop").hidden);
|
||||
await page.click("#drop");
|
||||
} else {
|
||||
await play([6, 1], [[6, 0], [1, 0]]);
|
||||
}
|
||||
return summaryRate();
|
||||
}
|
||||
|
||||
/** L'écran de progression lu, prêt (toutes ses parties analysées). */
|
||||
async function progressionReady() {
|
||||
await until(
|
||||
"progression prête",
|
||||
() =>
|
||||
location.hash === "#/progression" &&
|
||||
!document.getElementById("progression").hidden &&
|
||||
document.getElementById("progression").dataset.ready === "true",
|
||||
null,
|
||||
300_000,
|
||||
);
|
||||
}
|
||||
|
||||
// --------------------------------------------------- Aucune partie terminée
|
||||
|
||||
await page.goto(`${base}#/progression`);
|
||||
await progressionReady();
|
||||
const empty = await page.evaluate(() => ({
|
||||
summary: document.getElementById("progression-summary").textContent,
|
||||
start: document.getElementById("progression-new-game")?.getAttribute("href") ?? null,
|
||||
curve: document.getElementById("progression-curve") !== null,
|
||||
list: document.getElementById("progression-list") !== null,
|
||||
}));
|
||||
check(
|
||||
empty.summary.startsWith("Aucune partie terminée pour l'instant") &&
|
||||
empty.start === "#/nouvelle-partie" &&
|
||||
!empty.curve &&
|
||||
!empty.list,
|
||||
`cas vide : ${JSON.stringify(empty)}`,
|
||||
);
|
||||
await capture("1-vide");
|
||||
log("progression sans partie terminée : phrase et « Nouvelle partie »");
|
||||
|
||||
// ----------------------------------------------------- Trois parties jouées
|
||||
|
||||
const games = [];
|
||||
for (const mistake of [true, false, true]) {
|
||||
games.push(await oneGame(mistake));
|
||||
}
|
||||
check(
|
||||
games.every((g) => g.id && g.rate !== undefined && !Number.isNaN(Number(g.rate))),
|
||||
`bilans : ${JSON.stringify(games)}`,
|
||||
);
|
||||
check(Number(games[0].rate) > 0 && Number(games[1].rate) === 0, `taux des bilans : ${JSON.stringify(games)}`);
|
||||
log(`trois parties jouées, taux des bilans : ${games.map((g) => g.rate).join(", ")}`);
|
||||
|
||||
// La ligne de l'accueil mène à la progression.
|
||||
await page.goto(`${base}#/accueil`);
|
||||
await until("accueil", () => {
|
||||
const line = document.getElementById("home-progress");
|
||||
return (
|
||||
!document.getElementById("home").hidden &&
|
||||
line !== null &&
|
||||
!line.hidden &&
|
||||
line.textContent.includes("3 parties jouées")
|
||||
);
|
||||
});
|
||||
const line = await page.evaluate(() => {
|
||||
const a = document.getElementById("home-progress");
|
||||
return { tag: a.tagName, href: a.getAttribute("href"), text: a.textContent };
|
||||
});
|
||||
check(line.tag === "A" && line.href === "#/progression", `ligne de l'accueil : ${JSON.stringify(line)}`);
|
||||
await capture("3-accueil-ligne", async () => {
|
||||
await page.locator("#home-progress").scrollIntoViewIfNeeded();
|
||||
});
|
||||
log(`accueil : « ${line.text} » mène à #/progression`);
|
||||
await page.click("#home-progress");
|
||||
await progressionReady();
|
||||
|
||||
// La courbe : trois points, dans l'ordre des parties, égaux aux taux des
|
||||
// bilans.
|
||||
const shown = await page.evaluate(() => ({
|
||||
summary: document.getElementById("progression-summary").textContent,
|
||||
dots: [...document.querySelectorAll("#progression-curve .progression-dot")].map((c) => ({
|
||||
id: c.getAttribute("data-id"),
|
||||
rate: c.getAttribute("data-rate"),
|
||||
last: c.classList.contains("last"),
|
||||
})),
|
||||
items: [...document.querySelectorAll("#progression-list .progression-item")].map((li) => ({
|
||||
id: li.dataset.id,
|
||||
summary: li.querySelector(".progression-game").getAttribute("href"),
|
||||
analysis: li.querySelector(".progression-analysis").getAttribute("href"),
|
||||
rate: li.querySelector(".progression-rate")?.dataset.rate,
|
||||
})),
|
||||
glossary: document.getElementById("progression-glossary")?.getAttribute("href"),
|
||||
exercises: document.getElementById("progression-exercises")?.getAttribute("href"),
|
||||
}));
|
||||
check(shown.dots.length === 3, `courbe : ${shown.dots.length} points`);
|
||||
for (const [i, game] of games.entries()) {
|
||||
const dot = shown.dots[i];
|
||||
check(dot.id === game.id && dot.rate === game.rate, `point ${i + 1} : ${JSON.stringify(dot)} contre ${JSON.stringify(game)}`);
|
||||
}
|
||||
check(shown.dots[2].last && !shown.dots[0].last, "le dernier point n'est pas mis en avant");
|
||||
check(shown.summary.startsWith("Sur vos 3 dernières parties"), `phrase : ${shown.summary}`);
|
||||
check(!shown.summary.includes("!"), `phrase : ${shown.summary}`);
|
||||
check(shown.items.length === 3 && shown.items[0].id === games[2].id, `liste : ${JSON.stringify(shown.items)}`);
|
||||
for (const item of shown.items) {
|
||||
const game = games.find((g) => g.id === item.id);
|
||||
check(
|
||||
item.summary === `#/parties/${item.id}/bilan` &&
|
||||
item.analysis === `#/parties/${item.id}/analyse` &&
|
||||
item.rate === game.rate,
|
||||
`ligne : ${JSON.stringify(item)}`,
|
||||
);
|
||||
}
|
||||
check(shown.glossary === "#/glossaire/taux-d-erreur", `lien du glossaire : ${shown.glossary}`);
|
||||
check(shown.exercises === "#/progression/ia", `exercices : ${shown.exercises}`);
|
||||
await capture("2-trois-parties");
|
||||
log(`courbe : ${shown.dots.map((d) => d.rate).join(", ")} ; « ${shown.summary} »`);
|
||||
|
||||
// Une ligne ouvre le bilan ; le fermer ramène à la progression.
|
||||
await page.click(`#progression-list .progression-item[data-id="${games[0].id}"] .progression-game`);
|
||||
await until(
|
||||
"bilan depuis la progression",
|
||||
([id]) =>
|
||||
location.hash === `#/parties/${id}/bilan` &&
|
||||
document.getElementById("summary")?.dataset.ready === "true",
|
||||
[games[0].id],
|
||||
);
|
||||
await page.click("#summary-close");
|
||||
await progressionReady();
|
||||
log("une ligne ouvre le bilan, la croix ramène à la progression");
|
||||
|
||||
// « Créer des exercices » : la feuille du prompt.
|
||||
await page.click("#progression-exercises");
|
||||
await until(
|
||||
"prompt des exercices",
|
||||
() =>
|
||||
location.hash === "#/progression/ia" &&
|
||||
document.getElementById("prompt-page")?.dataset.ready === "true",
|
||||
);
|
||||
const prompt = await page.textContent("#prompt-text");
|
||||
check(prompt.includes("Voici mes 3 dernières parties"), `prompt : ${prompt}`);
|
||||
check(prompt.includes("Ce qui revient dans ces erreurs"), `prompt : ${prompt}`);
|
||||
check(prompt.includes("Je débute au backgammon."), `prompt : ${prompt}`);
|
||||
check(!/[{}]/.test(prompt), `repère non rempli : ${prompt}`);
|
||||
await page.keyboard.press("Escape");
|
||||
await until("retour à la progression", () => location.hash === "#/progression");
|
||||
log("« Créer des exercices » : prompt avec les erreurs et leurs thèmes");
|
||||
|
||||
const csp = await page.evaluate(() => window.__csp);
|
||||
for (const violation of csp) {
|
||||
problems.push(`politique de sécurité : ${violation}`);
|
||||
}
|
||||
await browser.close();
|
||||
if (problems.length > 0) {
|
||||
for (const problem of problems) {
|
||||
console.error(`[${browserName}] ${problem}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
log(`progression : tout est conforme (${captures} captures)`);
|
||||
Reference in New Issue
Block a user