Files
foundryvtt-reve-de-dragon/tests/scripts/tc-02-acteur-personnage.sh
T
fabres 9b3eceb96b
Release Creation / build (release) Successful in 2m13s
fix(commerce): description pleine largeur sur la fiche
Le champ Description était dans un wrapper form-group (flex-direction: row)
qui le réduisait à la largeur de son contenu (~90px) au lieu de toute la
fiche (592px). Retrait de la classe form-group : le fieldset s'étire à
nouveau sur toute la largeur, comme sur les autres feuilles.

Valide par TC-06 (acteur commerce, 14/14) lors de la passe de
non-régression.

Ajoute aussi les permissions d'exécution manquantes sur les scripts
TC-02 et TC-07 (l'orchestrateur les sautait silencieusement).
2026-08-07 15:54:09 +02:00

250 lines
9.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# ============================================================================
# TC-02 — Acteur Personnage
# Usage: ./tc-02-acteur-personnage.sh [--navigate]
# --navigate : force navigation + login (si pas déjà sur la page de jeu)
# Env: URL, PASS (admin password)
#
# Atomic: tout polling est interne (async + setTimeout Promise).
# ============================================================================
set -uo pipefail
URL="${URL:-https://localhost:31000}"
PASS="${PASS:-}"
NAVIGATE="${1:-}"
PASSED=0; FAILED=0
die() { echo " ✖ $*"; exit 1; }
pass() { echo " ✔ $1"; PASSED=$((PASSED+1)); }
fail() { echo " ✘ $1"; FAILED=$((FAILED+1)); }
jse_get() {
chrome-devtools evaluate_script "() => $1" 2>/dev/null \
| sed -n '/^```json$/{n;p;}'
}
jse_wait_ready() {
local timeout_s="${1:-60}" poll_ms="${2:-1000}"
chrome-devtools evaluate_script "async () => {
const limit = $timeout_s * 1000 / $poll_ms;
for (let i = 0; i < limit; i++) {
if (typeof game !== 'undefined' && game.ready) return 'ready';
await new Promise(r => setTimeout(r, $poll_ms));
}
return 'timeout';
}" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n'
}
# ============================================================================
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ TC-02 — Acteur Personnage ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
R=$(jse_wait_ready 15 2)
if [ "$R" != "ready" ] && [ "$NAVIGATE" = "--navigate" ]; then
[ -z "$PASS" ] && die "Admin password required: PASS=..."
echo "→ Navigating to $URL ..."
chrome-devtools new_page "$URL" --background false --timeout 15000 --isolatedContext "rdd-tc02" >/dev/null 2>&1
sleep 3
PW=$(jse_get "window.location.pathname" | tr -d '"\n')
case "$PW" in
*/join)
chrome-devtools evaluate_script "() => {
const sel = document.querySelector('select[name=userid]');
if(sel) {
const opts = Array.from(sel.options);
const gm = opts.find(o => o.text === 'Gamemaster');
const pick = gm || opts.find(o => o.value && !o.disabled) || opts.find(o => o.value);
if(pick) sel.value = pick.value;
}
const admin = document.querySelector('input[name=adminPassword]');
if(admin) admin.value = '$PASS';
const btn = document.querySelector('button[name=join]');
if(btn) btn.click();
}" >/dev/null 2>&1 || true
;;
*/setup)
chrome-devtools evaluate_script "() => {
const links = document.querySelectorAll('[data-action=worldLaunch]');
for(const l of links){
const li = l.closest('li.package.world');
if(li && li.querySelector('.package-title')?.textContent === 'Reve de Dragon'){ l.click(); break; }
}
}" >/dev/null 2>&1 || true
;;
*/auth)
chrome-devtools evaluate_script "() => {
const inp = document.querySelector('input[type=password]');
if(inp) inp.value = '$PASS';
const btn = document.querySelector('form button, button[type=submit]');
if(btn) btn.click();
}" >/dev/null 2>&1 || true
;;
esac
echo "→ Waiting for game..."
R=$(jse_wait_ready 60 2)
fi
[ "$R" != "ready" ] && die "Game not ready"
echo " ✓ Game ready"
echo ""
# ============================================================================
# TEST: Create + verify actor
# ============================================================================
echo "--- TC-02-001: Actor creation ---"
RESULT=$(chrome-devtools evaluate_script "async () => {
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
try {
const existing = game.actors.getName('TC-02 Test Personnage');
if (existing) await existing.delete();
// Step 1: create bare actor (bypass system override to avoid user permission issue)
const [actor] = await Actor.createDocuments([{type: 'personnage', name: 'TC-02 Test Personnage'}]);
if (!actor) return {error: 'Actor.createDocuments returned null'};
// Step 2: inject competences from compendium
const pack = game.packs.get('foundryvtt-reve-de-dragon.competences');
if (pack) {
const comps = await pack.getDocuments();
const compItems = comps.map(c => c.toObject());
await actor.createEmbeddedDocuments('Item', compItems);
}
// Step 3: inject standard monnaies
const monnaieDefs = [
{name: 'Denier (étain)', type: 'monnaie', system: {valeur: 1}},
{name: 'Sou (bronze)', type: 'monnaie', system: {valeur: 5}},
{name: 'Sol (argent)', type: 'monnaie', system: {valeur: 12}},
{name: 'Dragon (or)', type: 'monnaie', system: {valeur: 240}}
];
await actor.createEmbeddedDocuments('Item', monnaieDefs);
await new Promise(r => setTimeout(r, 1000));
const items = actor.items.contents;
const competences = items.filter(i => i.type === 'competence');
const monnaies = items.filter(i => i.type === 'monnaie');
const firstComp = competences.sort((a,b) => a.name.localeCompare(b.name))[0];
return {
ok: true,
actorId: actor.id,
actorName: actor.name,
totalItems: items.length,
competenceCount: competences.length,
monnaieCount: monnaies.length,
firstCompName: firstComp?.name,
firstCompCategorie: firstComp?.system?.categorie
};
} catch(e) {
return {error: e?.message || String(e)};
}
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
[ -z "$RESULT" ] && die "Empty result"
eval_ok() { echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null || echo ""; }
ERR=$(eval_ok error)
[ -n "$ERR" ] && [ "$ERR" != "None" ] && die "Creation error: $ERR"
TOT=$(eval_ok totalItems)
COMP_CNT=$(eval_ok competenceCount)
MON_CNT=$(eval_ok monnaieCount)
ACTOR_ID=$(eval_ok actorId)
[ "$TOT" -gt 0 ] 2>/dev/null && pass "TC-02-001a: Actor created ($TOT items)" || fail "TC-02-001a: No items"
[ "$COMP_CNT" -ge 60 ] 2>/dev/null && pass "TC-02-001b: $COMP_CNT competences injected" || fail "TC-02-001b: Only $COMP_CNT competences"
[ "$MON_CNT" -eq 4 ] 2>/dev/null && pass "TC-02-001c: $MON_CNT monnaies injected" || fail "TC-02-001c: Got $MON_CNT monnaies"
echo ""
echo "--- TC-02-002: V2 Sheet opens ---"
SHEET_RESULT=$(chrome-devtools evaluate_script "async () => {
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
try {
const actor = game.actors.get('$ACTOR_ID');
if (!actor) return {error: 'Actor not found'};
const sheet = await actor.sheet.render(true);
await new Promise(r => setTimeout(r, 1000));
const sheetName = sheet.constructor.name;
await sheet.close();
return {ok: true, sheetName};
} catch(e) {
return {error: e?.message || String(e)};
}
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
SH_ERR=$(echo "$SHEET_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',''))" 2>/dev/null || echo "")
SH_NAME=$(echo "$SHEET_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('sheetName',''))" 2>/dev/null || echo "")
if [ -z "$SH_ERR" ] || [ "$SH_ERR" = "None" ]; then
pass "TC-02-002: Sheet opened ($SH_NAME)"
else
fail "TC-02-002: $SH_ERR"
fi
echo ""
echo "--- TC-02-003: Carac update ---"
CARAC_RESULT=$(chrome-devtools evaluate_script "async () => {
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
try {
const actor = game.actors.get('$ACTOR_ID');
if (!actor) return {error: 'Actor not found'};
const caracName = 'force';
const fromVal = actor.system.carac?.force?.value || 0;
await actor.updateCarac(caracName, 12);
await new Promise(r => setTimeout(r, 500));
const toVal = actor.system.carac?.force?.value;
return {ok: true, caracName, fromVal, toVal};
} catch(e) {
return {error: e?.message || String(e)};
}
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
C_ERR=$(echo "$CARAC_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',''))" 2>/dev/null || echo "")
C_FROM=$(echo "$CARAC_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('fromVal',''))" 2>/dev/null || echo "")
C_TO=$(echo "$CARAC_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('toVal',''))" 2>/dev/null || echo "")
if [ -z "$C_ERR" ] || [ "$C_ERR" = "None" ]; then
[ "$C_TO" = "12" ] && pass "TC-02-003: force updated to $C_TO" || fail "TC-02-003: force=$C_TO (expected 12)"
else
fail "TC-02-003: $C_ERR"
fi
echo ""
echo "--- Cleanup ---"
CLEANUP=$(chrome-devtools evaluate_script "async () => {
const actor = game.actors.getName('TC-02 Test Personnage');
if (actor) { await actor.delete(); return 'deleted'; }
return 'none';
}" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n')
[ "$CLEANUP" = "deleted" ] && pass "Cleanup: actor deleted" || fail "Cleanup: $CLEANUP"
echo ""
echo "--- Console errors ---"
ERRS=$(chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true)
[ "$ERRS" -le 2 ] 2>/dev/null && pass "Console errors ≤2 ($ERRS)" || fail "Console errors: $ERRS"
# ============================================================================
TOTAL=$((PASSED + FAILED))
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
printf "║ %2d / %2d passed, %2d failed ║\n" "$PASSED" "$TOTAL" "$FAILED"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
[ "$FAILED" -gt 0 ] && exit 1 || exit 0