- 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
314 lines
13 KiB
Bash
Executable File
314 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# TC-04 — Acteur Entité
|
|
# Usage: ./tc-04-acteur-entite.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-04 — Acteur Entité ║"
|
|
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-tc04" >/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 ""
|
|
|
|
# Cleanup helper: delete named actors
|
|
cleanup_actors() {
|
|
local names=("$@")
|
|
chrome-devtools evaluate_script "async () => {
|
|
for (const n of $(printf '["%s"]' "${names[@]}")) {
|
|
const a = game.actors.getName(n);
|
|
if (a) await a.delete();
|
|
}
|
|
return 'ok';
|
|
}" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
# ============================================================================
|
|
# TC-04-001: Entité incarnée
|
|
# ============================================================================
|
|
echo "--- TC-04-001: Entité incarnée ---"
|
|
cleanup_actors "TC-04 Entité Incarnée"
|
|
|
|
RESULT=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
try {
|
|
const [actor] = await Actor.createDocuments([{
|
|
type: 'entite',
|
|
name: 'TC-04 Entité Incarnée',
|
|
system: { definition: { typeentite: 'incarne', categorieentite: 'reve' } }
|
|
}]);
|
|
if (!actor) return {error: 'create failed'};
|
|
const sheet = await actor.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
const sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
return {
|
|
ok: true, actorId: actor.id,
|
|
sheetName: sheetName,
|
|
typeentite: actor.system.definition?.typeentite,
|
|
categorieentite: actor.system.definition?.categorieentite,
|
|
items: actor.items.contents.length
|
|
};
|
|
} catch(e) { return {error: e?.message || String(e)}; }
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
eval_ok() { echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
|
|
[ -z "$(eval_ok error)" ] 2>/dev/null || [ "$(eval_ok error)" = "None" ] || die "TC-04-001: $(eval_ok error)"
|
|
SH_NAME=$(eval_ok sheetName)
|
|
TTYPE=$(eval_ok typeentite)
|
|
CATEG=$(eval_ok categorieentite)
|
|
E1_ID=$(eval_ok actorId)
|
|
|
|
[ "$SH_NAME" = "RdDActorEntiteSheetV2" ] && pass "TC-04-001a: Sheet $SH_NAME" || fail "TC-04-001a: Expected RdDActorEntiteSheetV2 got $SH_NAME"
|
|
[ "$TTYPE" = "incarne" ] && pass "TC-04-001b: typeentite=$TTYPE" || fail "TC-04-001b: typeentite=$TTYPE (expected incarne)"
|
|
[ "$CATEG" = "reve" ] && pass "TC-04-001c: categorieentite=$CATEG" || fail "TC-04-001c: categorieentite=$CATEG (expected reve)"
|
|
|
|
echo ""
|
|
echo "--- TC-04-002: Entité non-incarnée ---"
|
|
cleanup_actors "TC-04 Entité Non-Incarnée"
|
|
|
|
RESULT2=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
try {
|
|
const [actor] = await Actor.createDocuments([{
|
|
type: 'entite',
|
|
name: 'TC-04 Entité Non-Incarnée',
|
|
system: { definition: { typeentite: 'nonincarne', categorieentite: 'cauchemar' } }
|
|
}]);
|
|
if (!actor) return {error: 'create failed'};
|
|
const sheet = await actor.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
const sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
return {
|
|
ok: true, actorId: actor.id,
|
|
sheetName: sheetName,
|
|
typeentite: actor.system.definition?.typeentite,
|
|
items: actor.items.contents.length
|
|
};
|
|
} catch(e) { return {error: e?.message || String(e)}; }
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
eval2() { echo "$RESULT2" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
|
|
[ -z "$(eval2 error)" ] 2>/dev/null || [ "$(eval2 error)" = "None" ] || die "TC-04-002: $(eval2 error)"
|
|
SH2=$(eval2 sheetName)
|
|
TT2=$(eval2 typeentite)
|
|
|
|
[ "$SH2" = "RdDActorEntiteSheetV2" ] && pass "TC-04-002a: Sheet $SH2" || fail "TC-04-002a: Expected RdDActorEntiteSheetV2 got $SH2"
|
|
[ "$TT2" = "nonincarne" ] && pass "TC-04-002b: typeentite=$TT2" || fail "TC-04-002b: typeentite=$TT2 (expected nonincarne)"
|
|
|
|
echo ""
|
|
echo "--- TC-04-003: Entité blurette ---"
|
|
cleanup_actors "TC-04 Entité Blurette"
|
|
|
|
RESULT3=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
try {
|
|
const [actor] = await Actor.createDocuments([{
|
|
type: 'entite',
|
|
name: 'TC-04 Entité Blurette',
|
|
system: { definition: { typeentite: 'blurette', categorieentite: 'cauchemar' } }
|
|
}]);
|
|
if (!actor) return {error: 'create failed'};
|
|
const sheet = await actor.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
const sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
return {
|
|
ok: true, actorId: actor.id,
|
|
sheetName: sheetName,
|
|
typeentite: actor.system.definition?.typeentite,
|
|
items: actor.items.contents.length
|
|
};
|
|
} catch(e) { return {error: e?.message || String(e)}; }
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
eval3() { echo "$RESULT3" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
|
|
[ -z "$(eval3 error)" ] 2>/dev/null || [ "$(eval3 error)" = "None" ] || die "TC-04-003: $(eval3 error)"
|
|
SH3=$(eval3 sheetName)
|
|
TT3=$(eval3 typeentite)
|
|
|
|
[ "$SH3" = "RdDActorEntiteSheetV2" ] && pass "TC-04-003a: Sheet $SH3" || fail "TC-04-003a: Expected RdDActorEntiteSheetV2 got $SH3"
|
|
[ "$TT3" = "blurette" ] && pass "TC-04-003b: typeentite=$TT3" || fail "TC-04-003b: typeentite=$TT3 (expected blurette)"
|
|
|
|
echo ""
|
|
echo "--- TC-04-004: Possession ---"
|
|
|
|
cleanup_actors "TC-04 Victime" "TC-04 Possesseur"
|
|
|
|
RESULT4=$(chrome-devtools evaluate_script "async () => {
|
|
if (typeof game === 'undefined' || !game.ready) return {error: 'NOT_READY'};
|
|
try {
|
|
// Create a personnage actor (victim)
|
|
const [victime] = await Actor.createDocuments([{type: 'personnage', name: 'TC-04 Victime'}]);
|
|
if (!victime) return {error: 'victime create failed'};
|
|
|
|
// Create an entite actor (possesseur)
|
|
const [entite] = await Actor.createDocuments([{
|
|
type: 'entite',
|
|
name: 'TC-04 Possesseur',
|
|
system: { definition: { typeentite: 'incarne', categorieentite: 'reve' } }
|
|
}]);
|
|
if (!entite) return {error: 'entite create failed'};
|
|
|
|
// Create possession item on the victim
|
|
const [possession] = await victime.createEmbeddedDocuments('Item', [{
|
|
name: 'Possession de TC-04 Possesseur',
|
|
type: 'possession',
|
|
system: {
|
|
entiteid: entite.id,
|
|
entitetokenid: entite.id,
|
|
victimeid: victime.id,
|
|
victimetokenid: victime.id,
|
|
compteur: 0,
|
|
typepossession: '',
|
|
possede: false
|
|
}
|
|
}]);
|
|
if (!possession) return {error: 'possession create failed'};
|
|
|
|
const sheet = await victime.sheet.render(true);
|
|
await new Promise(r => setTimeout(r, 800));
|
|
const sheetName = sheet.constructor.name;
|
|
await sheet.close();
|
|
|
|
return {
|
|
ok: true,
|
|
victimeId: victime.id,
|
|
entiteId: entite.id,
|
|
possessionId: possession.id,
|
|
possessionName: possession.name,
|
|
possessionEntiteId: possession.system.entiteid,
|
|
possessionVictimeId: possession.system.victimeid,
|
|
possessionCompteur: possession.system.compteur,
|
|
victimeSheet: sheetName
|
|
};
|
|
} catch(e) { return {error: e?.message || String(e)}; }
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}')
|
|
|
|
eval4() { echo "$RESULT4" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; }
|
|
|
|
E4_ERR=$(eval4 error)
|
|
[ -z "$E4_ERR" ] || [ "$E4_ERR" = "None" ] || die "TC-04-004: $E4_ERR"
|
|
|
|
PNAME=$(eval4 possessionName)
|
|
PEID=$(eval4 possessionEntiteId)
|
|
PVID=$(eval4 possessionVictimeId)
|
|
PCOMPT=$(eval4 possessionCompteur)
|
|
VSH=$(eval4 victimeSheet)
|
|
|
|
[ -n "$PNAME" ] && pass "TC-04-004a: Possession créée ($PNAME)" || fail "TC-04-004a: Possession non créée"
|
|
[ -n "$PEID" ] && pass "TC-04-004b: entiteid lié" || fail "TC-04-004b: entiteid manquant"
|
|
[ -n "$PVID" ] && pass "TC-04-004c: victimeid lié" || fail "TC-04-004c: victimeid manquant"
|
|
[ "$PCOMPT" = "0" ] && pass "TC-04-004d: compteur initial = $PCOMPT" || fail "TC-04-004d: compteur = $PCOMPT (expected 0)"
|
|
[ "$VSH" = "RdDActorSheetV2" ] && pass "TC-04-004e: Feuille victime = $VSH" || fail "TC-04-004e: Feuille victime = $VSH (expected RdDActorSheetV2)"
|
|
|
|
echo ""
|
|
echo "--- Cleanup ---"
|
|
cleanup_actors "TC-04 Entité Incarnée" "TC-04 Entité Non-Incarnée" "TC-04 Entité Blurette" "TC-04 Victime" "TC-04 Possesseur" > /dev/null 2>&1 || true
|
|
CLEANUP=$(chrome-devtools evaluate_script "async () => {
|
|
const names = ['TC-04 Entité Incarnée','TC-04 Entité Non-Incarnée','TC-04 Entité Blurette','TC-04 Victime','TC-04 Possesseur'];
|
|
let c = 0;
|
|
for (const n of names) { const a = game.actors.getName(n); if (a) { await a.delete(); c++; } }
|
|
return 'deleted ' + c;
|
|
}" 2>/dev/null | sed -n '/^```json$/{n;p;}' | tr -d '"\n')
|
|
echo " $CLEANUP" && pass "Cleanup: done" || fail "Cleanup: $CLEANUP"
|
|
|
|
echo ""
|
|
echo "--- Console errors ---"
|
|
ERRS=$(chrome-devtools list_console_messages --types error 2>/dev/null | grep -c '^msgid=' || true)
|
|
[ "$ERRS" -le 3 ] 2>/dev/null && pass "Console errors ≤3 ($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
|