| name | verify |
| description | Build/launch/drive recipe for verifying Awawa Quest (static browser game) end-to-end. |
Verifying Awawa Quest
Static site, no build step. Everything is plain script tags (no ES modules), so it
also works from file://, but drive it over HTTP:
http-server -p 8123 -s
Drive with Playwright + the preinstalled Chromium
(executablePath: '/opt/pw-browsers/chromium-1194/chrome-linux/chrome',
global module at /opt/node22/lib/node_modules/playwright).
Flows worth driving
- Title →
[data-action="new-game"] → pick a starter card
([data-action="pick-starter"][data-arg="Sunnyrax"]).
- Hub → click
[data-action="explore"] repeatedly until .battle-stage exists
(~62% chance per click; cap at ~15 tries).
- Battle → click
[data-action="battle-move"]; wait for the action buttons to
re-render before the next click (battle events animate for a few seconds):
waitForFunction(() => document.getElementById('battle-actions')?.querySelector('[data-action]')).
Battle is over when [data-action="battle-end"] appears.
- Catch:
battle-menu arg catch → battle-catch. Menus: dex/shop/party/zones
via show-dex / show-shop / show-party / show-zones.
- Persistence probe:
page.reload() → [data-action="continue-game"] should
exist and restore coins/party (localStorage key awawa-quest-save-v1).
- Elder Trial:
[data-action="elder-challenge"] on the hub. Catch/Run are
disabled buttons during trials. First win grants state.badges[zoneId]; a
rematch win logs "concedes the rematch". To win the cliffs elder reliably,
seed a save with a Leaf attacker (type advantage vs Rock).
- Golden variants are 1/40 — for deterministic tests force one via
page.evaluate(() => { Battle.start(Game.firstHealthy(), Game.makeAwawa('Pebbawa', 3, {golden:true})); UI.showBattle(); }).
- Journal/milestones:
show-journal; milestones auto-claim via
Game.checkMilestones() and surface as toasts. Move editor:
party → Info → party-moves → moveedit-toggle/moveedit-save.
- Old-save migration: writing a pre-badges save (no
badges/milestones/
dex.golden keys) then Continue must not throw — Game.load() migrates.
- Trainer battles: ~12% of explores; force deterministically via
page.evaluate(() => { const t = { def: TRAINERS.cliffs[0], queue: TRAINERS.cliffs[0].team.map(sp => Game.makeAwawa(sp, 4)), idx: 0 }; Battle.start(Game.firstHealthy(), t.queue[0], { trainer: t }); UI.showBattle(); }).
Assert: .team-pips renders, catch/run disabled, defeating a mon logs
"sends out" + per-knockout XP, final win pays def.coins and bumps
stats.trainerWins.
- Held charms: buy under the shop's "Held Charms" section, equip via
party → Info →
party-held → held-set (arg idx|Item Name).
Swapping must return the previous charm to the bag.
- Day/night:
Game.state.time {phase, steps}; phase flips every 10 explores
and on Rest. Assert .phase-chip text and the --phase-tint CSS var.
Distribution check: set phase 3 (night) and sample Game.rollEncounter
on the oasis — Dream types should dominate (~65%+).
- Rival: seed a save with
badges: {cliffs:true}, rival: {fights:0} — the
next explore MUST be Scree (Battle.cur.trainer.rival === true), fielding
the counter line for state.starter. A win bumps rival.fights.
- Scream Tower: needs all 5 badges (set
endingSeen: true in test saves to
skip the ending flow). Hub shows tower-enter. Win floor →
interstitial with tower-next/tower-leave; flee ends the run back to
the hub; tower.best persists; loss halves coins like a normal wipe.
- Release gate (run all of these before shipping):
- Data integrity sweep in page.evaluate: every learnset move ∈ MOVES,
every encounter/trainer/elder/rival species ∈ SPECIES, dex numbers
contiguous,
awawaSVG(name) contains no "undefined" for all species.
- PWA: fetch manifest.webmanifest + sw.js (200), and
navigator.serviceWorker.getRegistrations() non-empty on localhost.
- Keyboard: press "2" on starter screen picks starter #2, "E" explores,
"1" picks first battle action, Enter continues.
- Daily Scream Run (
daily-enter): party fully heals between stages;
species/goldens are date-seeded (levels = maxPartyLv−6+3i). Clearing
all 5 sets daily.bestToday=5, streak, pays once/day. Drive with a
DIVERSE Lv-45 trio — one attacker can legitimately lose the ramp.
- Saves:
Game.exportCode() → AWAWA1. prefix; importCode round-trips;
corrupting the main key then reloading recovers from
awawa-quest-save-backup.
- Moonlit Isles: Travel shows a region divider; lagoon rolls
Tidalrax/Mistrawa/Lunawa; beat Tidemother Naia with a Dream attacker
(Wind elder) for the Tide Badge.
- Regenerate icons/cover after visual changes:
scratchpad gen-assets.js pattern (icons/, docs/). Bump sw.js
CACHE_VERSION on every release.
Gotchas
-
CRITICAL for test drivers: the game registers beforeunload/visibilitychange
handlers that call Game.save(). Any crafted localStorage.setItem(SAVE_KEY, …)
followed by page.reload() gets silently clobbered by the in-memory state.
Always set Game.state = null in the same page.evaluate before writing a
seeded save.
-
Collect console type=error and pageerror — the game has no error overlay.
-
confirm() dialogs guard new-game-over-save, release, and save wipe; register
a dialog handler if driving those paths.
-
Audio is WebAudio behind a user gesture; harmless headless.