fix(soak): multi-hole round transitions, token refresh, dashboard wiring

- Session loop now handles round_over by clicking #ss-next-btn (the
  scoresheet modal button) instead of exiting early. Waits for next
  round or game_over before continuing.
- SessionPool detects expired tokens on acquire and re-logins
  automatically, writing fresh credentials to .env.stresstest.
- Added 2s post-game delay before goto('/') so the server can process
  game completion before WebSocket disconnect.
- Wired dashboard metrics (games_completed, moves_total, errors),
  activity log entries, and player tiles for both populate and stress
  scenarios.
- Bumped screencast resolution to 960x540 and set headless viewport
  to 960x800 for better click-to-watch framing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-17 20:37:24 -04:00
parent ccc2f3b559
commit 70498b1c33
6 changed files with 121 additions and 7 deletions

View File

@@ -266,12 +266,49 @@ export class SessionPool {
const context = await targetBrowser.newContext({
...this.opts.contextOptions,
baseURL: this.opts.targetUrl,
...(useHeaded ? { viewport: { width: 960, height: 900 } } : {}),
viewport: useHeaded
? { width: 960, height: 900 }
: { width: 960, height: 800 },
});
await this.injectAuth(context, account);
const page = await context.newPage();
await page.goto(this.opts.targetUrl);
// Verify the token is valid — if expired, re-login and reload
const controlsVisible = await page
.waitForSelector('#lobby-game-controls:not(.hidden)', {
state: 'attached',
timeout: 5000,
})
.then(() => true)
.catch(() => false);
if (!controlsVisible) {
this.opts.logger.warn('token_expired_relogin', { account: account.key });
const freshToken = await loginAccount(
this.opts.targetUrl,
account.username,
account.password,
);
account.token = freshToken;
writeCredFile(this.opts.credFile, this.accounts);
await context.addInitScript(
({ token, username }) => {
window.localStorage.setItem('authToken', token);
window.localStorage.setItem(
'authUser',
JSON.stringify({ id: '', username, role: 'user', email_verified: true }),
);
},
{ token: freshToken, username: account.username },
);
await page.goto(this.opts.targetUrl);
await page.waitForSelector('#lobby-game-controls:not(.hidden)', {
state: 'attached',
timeout: 10000,
});
}
// Best-effort tile placement. window.moveTo is often a no-op on
// modern Chromium (especially under Wayland), so we don't rely on
// it — the viewport sized above is what the user actually sees.