- 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
270 lines
11 KiB
Bash
Executable File
270 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# TC-03 — Acteur Créature
|
|
# Usage: ./tc-03-acteur-creature.sh [--navigate]
|
|
# --navigate : force navigation + login
|
|
# Env: URL, PASS (admin password)
|
|
#
|
|
# Atomic: tout polling 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-03 — Acteur Créature ║"
|
|
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-tc03" >/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 ""
|
|
|
|
# ============================================================================
|
|
# TC-03-001: Creature creation + V2 sheet
|
|
# ============================================================================
|
|
echo "--- TC-03-001: Creature creation + sheet ---"
|
|
|
|
RESULT=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
|
|
try {
|
|
const existing = game.actors.getName('TC-03 Test Creature');
|
|
if (existing) await existing.delete();
|
|
|
|
const [actor] = await Actor.createDocuments([{type: 'creature', name: 'TC-03 Test Creature'}]);
|
|
if (!actor) return {error: 'Actor.createDocuments returned null'};
|
|
|
|
const sheet = await actor.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
const sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
|
|
const items = actor.items.contents;
|
|
const caracKeys = Object.keys(actor.system.carac || {});
|
|
const sante = actor.system.sante || {};
|
|
|
|
return {
|
|
ok: true,
|
|
actorId: actor.id,
|
|
sheetName: sheetName,
|
|
totalItems: items.length,
|
|
caracKeys: caracKeys,
|
|
santeVie: {value: sante.vie?.value, max: sante.vie?.max},
|
|
santeEnd: {value: sante.endurance?.value, max: sante.endurance?.max}
|
|
};
|
|
} 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"
|
|
|
|
SH_NAME=$(eval_ok sheetName)
|
|
TOT=$(eval_ok totalItems)
|
|
ACTOR_ID=$(eval_ok actorId)
|
|
CARACS=$(eval_ok caracKeys)
|
|
SANTE=$(eval_ok santeVie)
|
|
SANTE_END=$(eval_ok santeEnd)
|
|
|
|
[ "$TOT" -eq 0 ] 2>/dev/null && pass "TC-03-001a: Creature created (0 default items)" || fail "TC-03-001a: Expected 0 items, got $TOT"
|
|
[ "$SH_NAME" = "RdDCreatureSheetV2" ] 2>/dev/null && pass "TC-03-001b: Sheet is $SH_NAME" || fail "TC-03-001b: Expected RdDCreatureSheetV2, got $SH_NAME"
|
|
echo "$CARACS" | python3 -c "import sys; ks=sys.stdin.read().strip('\"[]').split(','); print(ks)" >/dev/null
|
|
[ -n "$SANTE" ] && [ "$SANTE" != "None" ] && pass "TC-03-001c: sante.vie present" || fail "TC-03-001c: sante.vie missing"
|
|
|
|
echo ""
|
|
echo "--- TC-03-002: Competence creature ---"
|
|
|
|
COMP_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 compData = {
|
|
name: 'Griffe',
|
|
type: 'competencecreature',
|
|
system: {
|
|
carac_value: 12,
|
|
niveau: 4,
|
|
dommages: 6,
|
|
categorie: 'naturelle',
|
|
iscombat: true,
|
|
isnaturelle: true,
|
|
mortalite: 'mortel'
|
|
}
|
|
};
|
|
const [item] = await actor.createEmbeddedDocuments('Item', [compData]);
|
|
if (!item) return {error: 'Item.create returned null'};
|
|
|
|
const isAttaque = item.isAttaque ? item.isAttaque() : false;
|
|
const isParade = item.isParade ? item.isParade() : false;
|
|
const attaque = item.attaqueCreature ? item.attaqueCreature() : null;
|
|
|
|
return {
|
|
ok: true,
|
|
itemName: item.name,
|
|
itemId: item.id,
|
|
isAttaque: isAttaque,
|
|
isParade: isParade,
|
|
hasAttaqueCreature: !!attaque,
|
|
attaqueCategorie: attaque?.categorie
|
|
};
|
|
} catch(e) {
|
|
return {error: e?.message || String(e)};
|
|
}
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
C_ERR=$(echo "$COMP_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',''))" 2>/dev/null || echo "")
|
|
C_ATK=$(echo "$COMP_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('isAttaque',''))" 2>/dev/null || echo "")
|
|
C_PAR=$(echo "$COMP_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('isParade',''))" 2>/dev/null || echo "")
|
|
C_HAS=$(echo "$COMP_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('hasAttaqueCreature',''))" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$C_ERR" ] || [ "$C_ERR" = "None" ]; then
|
|
pass "TC-03-002a: Competence creature created"
|
|
[ "$C_ATK" = "True" ] && pass "TC-03-002b: isAttaque() true" || fail "TC-03-002b: isAttaque() returned $C_ATK"
|
|
[ "$C_HAS" = "True" ] 2>/dev/null && pass "TC-03-002c: attaqueCreature() available" || fail "TC-03-002c: attaqueCreature() missing"
|
|
else
|
|
fail "TC-03-002: $C_ERR"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- TC-03-003: Token bars (sante.vie / sante.endurance) ---"
|
|
|
|
SANTE_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 sante = actor.system.sante || {};
|
|
const pt = actor.prototypeToken;
|
|
|
|
return {
|
|
ok: true,
|
|
santeVie: {value: sante.vie?.value, max: sante.vie?.max},
|
|
santeEnd: {value: sante.endurance?.value, max: sante.endurance?.max},
|
|
tokenDisposition: pt?.disposition,
|
|
tokenSight: pt?.sight?.enabled,
|
|
actorLink: pt?.actorLink
|
|
};
|
|
} catch(e) {
|
|
return {error: e?.message || String(e)};
|
|
}
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
S_ERR=$(echo "$SANTE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('error',''))" 2>/dev/null || echo "")
|
|
S_VIE=$(echo "$SANTE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('santeVie',''))" 2>/dev/null || echo "")
|
|
S_END=$(echo "$SANTE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('santeEnd',''))" 2>/dev/null || echo "")
|
|
S_DISP=$(echo "$SANTE_RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tokenDisposition',''))" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$S_ERR" ] || [ "$S_ERR" = "None" ]; then
|
|
VIE_VAL=$(echo "$S_VIE" | python3 -c "import sys,json; d=json.loads(sys.stdin.read().replace(chr(39),chr(34))); print(d.get('max',''))" 2>/dev/null || echo "")
|
|
END_VAL=$(echo "$S_END" | python3 -c "import sys,json; d=json.loads(sys.stdin.read().replace(chr(39),chr(34))); print(d.get('max',''))" 2>/dev/null || echo "")
|
|
[ -n "$VIE_VAL" ] && [ "$VIE_VAL" != "0" ] && [ "$VIE_VAL" != "None" ] && pass "TC-03-003a: sante.vie.max = $VIE_VAL" || fail "TC-03-003a: sante.vie invalid ($S_VIE)"
|
|
[ -n "$END_VAL" ] && [ "$END_VAL" != "0" ] && [ "$END_VAL" != "None" ] && pass "TC-03-003b: sante.endurance.max = $END_VAL" || fail "TC-03-003b: sante.endurance invalid ($S_END)"
|
|
[ "$S_DISP" = "0" ] 2>/dev/null && pass "TC-03-003c: token disposition = NEUTRAL (0)" || fail "TC-03-003c: token disposition = $S_DISP (expected 0)"
|
|
else
|
|
fail "TC-03-003: $S_ERR"
|
|
fi
|
|
|
|
echo ""
|
|
echo "--- Cleanup ---"
|
|
CLEANUP=$(chrome-devtools evaluate_script "async () => {
|
|
const actor = game.actors.getName('TC-03 Test Creature');
|
|
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
|