- rdd_test_plan.md : plan global, 227 cas, 33 sections - TC-01 : infrastructure coeur (15/15) - TC-02 : acteur personnage (7/7) - TC-03 : acteur creature (11/11) - TC-04 : acteur entite + subtypes + possession (14/14) - TC-05 : acteur vehicule + structure/resistance (10/10) - TC-06 : acteur commerce + achat/vente simules (14/14) Scripts atomiques, async polling, page-state race-free
171 lines
7.4 KiB
Bash
Executable File
171 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# TC-01 — Infrastructure Cœur
|
|
# Usage: ./tc-01-infrastructure.sh [--navigate]
|
|
# --navigate : force navigation + login (si pas déjà sur la page de jeu)
|
|
# Env: URL, PASS (admin password), PASS_USER (user password)
|
|
#
|
|
# Atomic: aucun bash loop sur evaluate_script. Tout polling est interne
|
|
# (async + setTimeout Promise) pour éviter les races de page.
|
|
# ============================================================================
|
|
set -uo pipefail
|
|
|
|
URL="${URL:-https://localhost:31000}"
|
|
PASS="${PASS:-}"
|
|
WORLD="${WORLD:-Reve de Dragon}"
|
|
NAVIGATE="${1:-}"
|
|
|
|
PASSED=0; FAILED=0
|
|
|
|
die() { echo " ✖ $*"; exit 1; }
|
|
pass() { echo " ✔ $1"; PASSED=$((PASSED+1)); }
|
|
fail() { echo " ✘ $1"; FAILED=$((FAILED+1)); }
|
|
|
|
# Extract JSON value from evaluate_script output
|
|
jse_get() {
|
|
chrome-devtools evaluate_script "() => $1" 2>/dev/null \
|
|
| sed -n '/^```json$/{n;p;}'
|
|
}
|
|
|
|
# Async polling: single evaluate_script call, no bash loop
|
|
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-01 — Infrastructure Cœur ║"
|
|
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 (isolated context) ..."
|
|
chrome-devtools new_page "$URL" --background false --timeout 15000 --isolatedContext "rdd-tests" >/dev/null 2>&1
|
|
sleep 3
|
|
|
|
# Login: single evaluate_script per action, no bash loop
|
|
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 === '$WORLD'){ 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
|
|
|
|
# Wait for game: single async evaluate_script, no bash loop
|
|
echo "→ Waiting for game..."
|
|
R=$(jse_wait_ready 60 2)
|
|
fi
|
|
|
|
[ "$R" != "ready" ] && die "Game not ready"
|
|
echo " ✓ Game ready"
|
|
echo ""
|
|
|
|
# ============================================================================
|
|
# TESTS — atomic single evaluate_script
|
|
# ============================================================================
|
|
JSON_RESULT=$(chrome-devtools evaluate_script "() => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
return {
|
|
sysId: game.system.id,
|
|
rdd: typeof game.system.rdd,
|
|
settingCount: Array.from(game.settings.settings.keys())
|
|
.filter(k => k.startsWith('foundryvtt-reve-de-dragon')).length,
|
|
actorClass: CONFIG.Actor.documentClass.name,
|
|
itemClass: CONFIG.Item.documentClass.name,
|
|
aeClass: CONFIG.ActiveEffect.documentClass.name,
|
|
sheetP: Object.keys(CONFIG.Actor.sheetClasses).includes('personnage'),
|
|
sheetC: Object.keys(CONFIG.Actor.sheetClasses).includes('creature'),
|
|
sheetE: Object.keys(CONFIG.Actor.sheetClasses).includes('entite'),
|
|
itemSheets: Object.keys(CONFIG.Item.sheetClasses).length,
|
|
title: game.system.title,
|
|
migration: (() => { try { return game.settings.get('foundryvtt-reve-de-dragon','systemMigrationVersion'); } catch(e) { return null; } })(),
|
|
socket: typeof game.socket,
|
|
dice: typeof game.system.rdd.Dice
|
|
};
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
[ -z "$JSON_RESULT" ] && die "Empty test results (game lost?)"
|
|
|
|
eval_ok() {
|
|
local k="$1"
|
|
echo "$JSON_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$k',''))" 2>/dev/null || echo ""
|
|
}
|
|
|
|
echo "--- TC-01-001: Console errors ---"
|
|
ERRS=$(chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true)
|
|
[ "$ERRS" -le 2 ] 2>/dev/null && pass "TC-01-001: Console errors ≤2 ($ERRS)" || fail "TC-01-001: Errors=$ERRS"
|
|
|
|
assert_eq() { local t="$1" a="$2" e="$3"
|
|
if [ "$a" = "$e" ]; then pass "$t"; else fail "$t (got '$a')"; fi
|
|
}
|
|
assert_gt() { local t="$1" a="$2" e="$3"
|
|
if [ "$a" -gt "$e" ] 2>/dev/null; then pass "$t"; else fail "$t ($a <= $e)"; fi
|
|
}
|
|
|
|
assert_eq "TC-01-002: system.id" "$(eval_ok sysId)" "foundryvtt-reve-de-dragon"
|
|
assert_eq "TC-01-003: game.system.rdd" "$(eval_ok rdd)" "object"
|
|
assert_gt "TC-01-004: Settings > 9" "$(eval_ok settingCount)" 9
|
|
assert_eq "TC-01-005a: Actor class" "$(eval_ok actorClass)" "RdDBaseActor"
|
|
assert_eq "TC-01-005b: Item class" "$(eval_ok itemClass)" "RdDItem"
|
|
assert_eq "TC-01-005c: ActiveEffect class" "$(eval_ok aeClass)" "RdDActiveEffect"
|
|
assert_eq "TC-01-006a: personnage sheet" "$(eval_ok sheetP)" "True"
|
|
assert_eq "TC-01-006b: creature sheet" "$(eval_ok sheetC)" "True"
|
|
assert_eq "TC-01-006c: entite sheet" "$(eval_ok sheetE)" "True"
|
|
assert_gt "TC-01-007: >=44 item sheets" "$(eval_ok itemSheets)" 43
|
|
assert_eq "TC-01-008: Title" "$(eval_ok title)" "Rêve de Dragon"
|
|
|
|
MV=$(eval_ok migration)
|
|
[ -n "$MV" ] && [ "$MV" != "null" ] && pass "TC-01-009: Migration $MV" || fail "TC-01-009: No migration"
|
|
|
|
assert_eq "TC-01-010: Socket" "$(eval_ok socket)" "object"
|
|
assert_eq "TC-01-Extra: game.system.rdd.Dice" "$(eval_ok dice)" "function"
|
|
|
|
# ============================================================================
|
|
TOTAL=$((PASSED + FAILED))
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
printf "║ %2d / %2d passed, %2d failed ║\n" "$PASSED" "$TOTAL" "$FAILED"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
[ "$FAILED" -gt 0 ] && exit 1 || exit 0
|