darkwatch/site/screenshots.js
Aaron Wood e1cf22cae4 feat: add Playwright screenshot script for brochure
Adds site/package.json and site/screenshots.js to automate capturing
7 named screenshots of the running Darkwatch app via Playwright/Chromium.
Also adds site/node_modules/ to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 18:34:07 -04:00

147 lines
5.3 KiB
JavaScript

import { chromium } from 'playwright';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { mkdirSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SHOT_DIR = resolve(__dirname, 'assets', 'screenshots');
const APP = 'http://localhost:5173';
const DM_EMAIL = 'dm@darkwatch.test';
const DM_PASS = 'password';
const ONLY = process.argv.includes('--only')
? process.argv[process.argv.indexOf('--only') + 1]
: null;
mkdirSync(SHOT_DIR, { recursive: true });
// ── helpers ────────────────────────────────────────────────────────────────
async function shot(page, name, fn) {
if (ONLY && name !== ONLY) return;
console.log(` capturing ${name}...`);
await fn(page);
await page.screenshot({
path: resolve(SHOT_DIR, `${name}.png`),
fullPage: false,
});
console.log(`${name}.png`);
}
async function login(page) {
await page.goto(`${APP}/login`);
await page.fill('input[type="email"]', DM_EMAIL);
await page.fill('input[type="password"]', DM_PASS);
await page.click('button[type="submit"]');
await page.waitForURL(`${APP}/`);
}
async function openCampaign(page) {
// Seeded campaign is named "Tomb of the Serpent King"
await page.click('text=Tomb of the Serpent King');
await page.waitForURL(/\/campaign\/\d+/);
await page.waitForTimeout(1500); // socket connect + data load
}
// ── captures ───────────────────────────────────────────────────────────────
async function captureCharacterSheet(page) {
// DM view — click the first character card to open full sheet
// Selector: first .characterCard element in the card grid
// Adjust class name if different in the actual DOM
const card = page.locator('[class*="characterCard"]').first();
await card.click();
await page.waitForTimeout(500);
}
async function captureCharacterCreation(page) {
// Open character creation wizard and advance to step 2 (stat rolling)
await page.click('button:has-text("New Character")');
await page.waitForTimeout(400);
// Fill required fields on step 1 so Next is enabled
const nameInput = page.locator('input[placeholder*="name" i]').first();
await nameInput.fill('Screenshot Hero');
// Advance to step 2
await page.click('button:has-text("Next")');
await page.waitForTimeout(400);
}
async function captureDiceRoll(page) {
// Roll a d20 — find the d20 button in the dice panel
await page.click('button:has-text("d20")');
await page.waitForTimeout(2000); // wait for 3D animation to settle
}
async function captureSpellcasting(page) {
// Open the Spells tab/panel for the first Wizard or Priest character
// The spell panel may be a tab within the character sheet
await page.click('text=Spells');
await page.waitForTimeout(400);
}
async function captureAtmosphere(page) {
// Open atmosphere panel and enable fog
await page.click('button[title*="tmosphere" i], button:has-text("Atmosphere")');
await page.waitForTimeout(300);
await page.click('button:has-text("Fog")');
await page.waitForTimeout(600); // wait for fog to render
}
async function captureInitiativeActive(page) {
// Click the Combat button to open the start combat modal
await page.click('button:has-text("Combat")');
await page.waitForTimeout(300);
// Start combat with whatever defaults are present
await page.click('button:has-text("Start Combat")');
await page.waitForTimeout(500);
// Roll initiative for enemies (DM roll)
const rollBtn = page.locator('button:has-text("Roll")').first();
await rollBtn.click();
await page.waitForTimeout(400);
}
async function captureDmCards(page) {
// The DM compact card grid is the default campaign view.
// Navigate back to campaign root to ensure no modals are open.
const url = page.url();
await page.goto(url);
await page.waitForTimeout(1500); // let socket reconnect
}
// ── main ───────────────────────────────────────────────────────────────────
async function run() {
console.log('Starting Darkwatch screenshot capture...');
console.log(`App: ${APP}`);
if (ONLY) console.log(`Only: ${ONLY}`);
console.log('');
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
});
const page = await context.newPage();
await login(page);
console.log('✓ Logged in as DM');
await openCampaign(page);
console.log('✓ Campaign open');
console.log('');
await shot(page, 'dm-cards', captureDmCards);
await shot(page, 'character-sheet', captureCharacterSheet);
await shot(page, 'character-creation',captureCharacterCreation);
await shot(page, 'dice-roll', captureDiceRoll);
await shot(page, 'spellcasting', captureSpellcasting);
await shot(page, 'atmosphere', captureAtmosphere);
await shot(page, 'initiative-active', captureInitiativeActive);
await browser.close();
console.log('');
console.log('Done! Screenshots saved to site/assets/screenshots/');
}
run().catch(err => {
console.error(err);
process.exit(1);
});