241 lines
7.3 KiB
JavaScript
241 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import process from 'node:process';
|
|
import { chromium, devices } from 'playwright';
|
|
|
|
const baseUrl = process.env.BASE_URL ?? 'http://fotospiel-app.test';
|
|
const eventSlug = process.env.EVENT_SLUG ?? 'demo-starter-wedding';
|
|
const notificationFallback = process.env.NOTIFICATION_ID ?? 'demo-notification';
|
|
const helpFallback = process.env.HELP_SLUG ?? 'demo-help';
|
|
const outputDir =
|
|
process.env.OUTPUT_DIR
|
|
?? path.join(process.cwd(), 'test-results', 'mobile-pwa-screenshots', timestamp());
|
|
|
|
const skipParam = 'consent=skip';
|
|
|
|
const publicRoutes = [
|
|
'/event-admin',
|
|
'/event-admin/login',
|
|
'/event-admin/mobile/login',
|
|
'/event-admin/help',
|
|
'/event-admin/forgot-password',
|
|
'/event-admin/reset-password/demo-token',
|
|
'/event-admin/start',
|
|
'/event-admin/logout',
|
|
'/event-admin/auth/callback',
|
|
];
|
|
|
|
const authedRoutes = [
|
|
'/event-admin/dashboard',
|
|
'/event-admin/live',
|
|
'/event-admin/events',
|
|
'/event-admin/events/new',
|
|
`/event-admin/events/${eventSlug}`,
|
|
`/event-admin/events/${eventSlug}/recap`,
|
|
`/event-admin/events/${eventSlug}/edit`,
|
|
`/event-admin/events/${eventSlug}/photos`,
|
|
`/event-admin/events/${eventSlug}/members`,
|
|
`/event-admin/events/${eventSlug}/tasks`,
|
|
`/event-admin/events/${eventSlug}/invites`,
|
|
`/event-admin/events/${eventSlug}/branding`,
|
|
`/event-admin/events/${eventSlug}/photobooth`,
|
|
`/event-admin/events/${eventSlug}/guest-notifications`,
|
|
`/event-admin/events/${eventSlug}/toolkit`,
|
|
'/event-admin/mobile/events',
|
|
`/event-admin/mobile/events/${eventSlug}`,
|
|
`/event-admin/mobile/events/${eventSlug}/branding`,
|
|
'/event-admin/mobile/events/new',
|
|
`/event-admin/mobile/events/${eventSlug}/edit`,
|
|
`/event-admin/mobile/events/${eventSlug}/qr`,
|
|
`/event-admin/mobile/events/${eventSlug}/qr/customize`,
|
|
`/event-admin/mobile/events/${eventSlug}/control-room`,
|
|
`/event-admin/mobile/events/${eventSlug}/photos`,
|
|
`/event-admin/mobile/events/${eventSlug}/live-show`,
|
|
`/event-admin/mobile/events/${eventSlug}/live-show/settings`,
|
|
`/event-admin/mobile/events/${eventSlug}/recap`,
|
|
`/event-admin/mobile/events/${eventSlug}/analytics`,
|
|
`/event-admin/mobile/events/${eventSlug}/members`,
|
|
`/event-admin/mobile/events/${eventSlug}/tasks`,
|
|
`/event-admin/mobile/events/${eventSlug}/photobooth`,
|
|
`/event-admin/mobile/events/${eventSlug}/guest-notifications`,
|
|
'/event-admin/mobile/notifications',
|
|
'/event-admin/mobile/profile',
|
|
'/event-admin/mobile/profile/account',
|
|
'/event-admin/mobile/help',
|
|
'/event-admin/mobile/billing',
|
|
'/event-admin/mobile/billing/shop',
|
|
'/event-admin/mobile/settings',
|
|
'/event-admin/mobile/exports',
|
|
'/event-admin/mobile/dashboard',
|
|
'/event-admin/mobile/tasks',
|
|
'/event-admin/mobile/uploads',
|
|
];
|
|
|
|
const welcomeRoutes = [
|
|
'/event-admin/mobile/welcome',
|
|
'/event-admin/mobile/welcome/packages',
|
|
'/event-admin/mobile/welcome/summary',
|
|
'/event-admin/mobile/welcome/event',
|
|
];
|
|
|
|
const device = devices['iPhone 12'];
|
|
|
|
function timestamp() {
|
|
const now = new Date();
|
|
const pad = (value) => String(value).padStart(2, '0');
|
|
return [
|
|
now.getFullYear(),
|
|
pad(now.getMonth() + 1),
|
|
pad(now.getDate()),
|
|
'-',
|
|
pad(now.getHours()),
|
|
pad(now.getMinutes()),
|
|
pad(now.getSeconds()),
|
|
].join('');
|
|
}
|
|
|
|
function withSkip(url) {
|
|
if (url.includes(skipParam)) {
|
|
return url;
|
|
}
|
|
return url.includes('?') ? `${url}&${skipParam}` : `${url}?${skipParam}`;
|
|
}
|
|
|
|
function slugify(input) {
|
|
return input
|
|
.replace(/^https?:\/\//, '')
|
|
.replace(/\?.*$/, '')
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^a-zA-Z0-9-_./]/g, '')
|
|
.replace(/[\\/]+/g, '_')
|
|
.replace(/_{2,}/g, '_')
|
|
.replace(/^_+|_+$/g, '');
|
|
}
|
|
|
|
async function ensureDir(dir) {
|
|
if (!fs.existsSync(dir)) {
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function waitForDemoAuth(page) {
|
|
await page.waitForFunction(() => Boolean(window.fotospielDemoAuth?.loginAs), null, {
|
|
timeout: 10000,
|
|
});
|
|
}
|
|
|
|
async function loginAs(page, tenantKey) {
|
|
await page.goto(withSkip(`${baseUrl}/event-admin/login`), { waitUntil: 'load' });
|
|
await waitForDemoAuth(page);
|
|
await page.evaluate(async (key) => {
|
|
await window.fotospielDemoAuth?.loginAs(key);
|
|
}, tenantKey);
|
|
await page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
async function captureRoute(page, route, tag, log) {
|
|
const url = withSkip(`${baseUrl}${route}`);
|
|
try {
|
|
await page.goto(url, { waitUntil: 'load' });
|
|
await page.waitForTimeout(700);
|
|
const file = path.join(outputDir, `${tag}__${slugify(route)}.png`);
|
|
await page.screenshot({ path: file, fullPage: true });
|
|
log.push({ url, file, status: 'ok' });
|
|
} catch (error) {
|
|
log.push({ url, status: 'error', error: String(error) });
|
|
}
|
|
}
|
|
|
|
async function captureDerivedRoute(page, baseRoute, matchPrefix, fallback, tag, log) {
|
|
const baseUrlWithSkip = withSkip(`${baseUrl}${baseRoute}`);
|
|
await page.goto(baseUrlWithSkip, { waitUntil: 'load' });
|
|
await page.waitForTimeout(700);
|
|
|
|
const href = await page.evaluate((prefix) => {
|
|
const anchors = Array.from(document.querySelectorAll('a[href]'));
|
|
const match = anchors.map((anchor) => anchor.getAttribute('href') || '').find((value) => value.includes(prefix));
|
|
return match || '';
|
|
}, matchPrefix);
|
|
|
|
const route = href && href.startsWith('/') ? href : fallback;
|
|
await captureRoute(page, route, tag, log);
|
|
}
|
|
|
|
async function main() {
|
|
await ensureDir(outputDir);
|
|
|
|
const log = [];
|
|
const browser = await chromium.launch();
|
|
const context = await browser.newContext({ ...device, locale: 'de-DE' });
|
|
await context.addInitScript(() => {
|
|
try {
|
|
window.localStorage.setItem('fotospiel.consent.skip', '1');
|
|
window.localStorage.setItem('fotospiel-dev-switcher-collapsed', '1');
|
|
} catch (error) {
|
|
// ignore storage errors
|
|
}
|
|
});
|
|
|
|
const page = await context.newPage();
|
|
|
|
for (const route of publicRoutes) {
|
|
await captureRoute(page, route, 'public', log);
|
|
}
|
|
|
|
await loginAs(page, process.env.TENANT_KEY ?? 'cust-starter-wedding');
|
|
|
|
for (const route of authedRoutes) {
|
|
await captureRoute(page, route, 'authed', log);
|
|
}
|
|
|
|
await captureDerivedRoute(
|
|
page,
|
|
'/event-admin/mobile/notifications',
|
|
'/event-admin/mobile/notifications/',
|
|
`/event-admin/mobile/notifications/${notificationFallback}`,
|
|
'authed',
|
|
log,
|
|
);
|
|
|
|
await captureDerivedRoute(
|
|
page,
|
|
'/event-admin/mobile/help',
|
|
'/event-admin/mobile/help/',
|
|
`/event-admin/mobile/help/${helpFallback}`,
|
|
'authed',
|
|
log,
|
|
);
|
|
|
|
await context.close();
|
|
|
|
const emptyContext = await browser.newContext({ ...device, locale: 'de-DE' });
|
|
await emptyContext.addInitScript(() => {
|
|
try {
|
|
window.localStorage.setItem('fotospiel.consent.skip', '1');
|
|
window.localStorage.setItem('fotospiel-dev-switcher-collapsed', '1');
|
|
} catch (error) {
|
|
// ignore storage errors
|
|
}
|
|
});
|
|
|
|
const emptyPage = await emptyContext.newPage();
|
|
await loginAs(emptyPage, process.env.EMPTY_TENANT_KEY ?? 'cust-standard-empty');
|
|
|
|
for (const route of welcomeRoutes) {
|
|
await captureRoute(emptyPage, route, 'welcome', log);
|
|
}
|
|
|
|
await emptyContext.close();
|
|
await browser.close();
|
|
|
|
fs.writeFileSync(path.join(outputDir, 'run-log.json'), JSON.stringify(log, null, 2));
|
|
const okCount = log.filter((entry) => entry.status === 'ok').length;
|
|
console.log(`Saved ${okCount} screenshots to ${outputDir}`);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error('Screenshot capture failed', error);
|
|
process.exit(1);
|
|
});
|