153 lines
8.2 KiB
TypeScript
153 lines
8.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
// Screenshots of every desktop screen in light and dark, from the Vite dev server in fixture mode (no Tauri, no store
|
|
// accounts), plus a few interaction checks (command palette, toast progress, keyboard focus). Dev tool only.
|
|
// cd apps/desktop && VITE_FIXTURES=1 bunx vite --port 1431 &
|
|
// bun scripts/screens.ts --base http://127.0.0.1:1431 --out <dir> [--only portfolio,app:health] [--themes light,dark]
|
|
// Uses playwright (root devDependency) with the installed Google Chrome (channel "chrome").
|
|
import { mkdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { chromium, type Page } from 'playwright';
|
|
|
|
const arg = (k: string, d: string) => { const i = process.argv.indexOf(`--${k}`); return i >= 0 ? process.argv[i + 1] ?? d : d; };
|
|
const base = arg('base', 'http://127.0.0.1:1431');
|
|
const out = arg('out', join(import.meta.dir, '..', '.screens'));
|
|
const only = arg('only', '').split(',').filter(Boolean);
|
|
const themes = arg('themes', 'light,dark').split(',');
|
|
const lang = arg('lang', 'en');
|
|
const suffix = arg('suffix', '');
|
|
const prefix = arg('prefix', 'd3');
|
|
|
|
export const SCREENS: [string, string][] = [
|
|
['portfolio', 'portfolio'], ['releases', 'app:releases'], ['releases-2', 'app:releases:2'], ['catalog', 'app:catalog'], ['listings', 'app:listings'],
|
|
['health', 'app:health'], ['reviews', 'app:reviews'], ['testflight', 'app:testflight'], ['checks', 'app:checks'],
|
|
['apps', 'apps'], ['accounts', 'accounts'], ['dependencies', 'dependencies'], ['settings', 'settings'],
|
|
['ignored', 'ignored'], ['store-app', 'app:releases:3'], ['store-app-reviews', 'app:reviews:3'], ['store-app-single', 'app:releases:7'],
|
|
['settings-pairing', 'settings:pairing'], ['settings-refresh', 'settings:refresh'], ['settings-privacy', 'settings:privacy'],
|
|
['palette', 'palette'], ['welcome-1', 'welcome:1'], ['welcome-2', 'welcome:2'], ['welcome-3', 'welcome:3'], ['welcome-4', 'welcome:4'], ['welcome-5', 'welcome:5'],
|
|
['welcome-6', 'welcome:6'], ['prices', 'app:prices'], ['art', 'app:art'], ['growth', 'app:growth'], ['growth-2', 'app:growth:2'], ['appinfo', 'app:appinfo'],
|
|
['appinfo-2', 'app:appinfo:2'], ['store-app-growth', 'app:growth:3'],
|
|
];
|
|
|
|
async function settle(page: Page) {
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(900);
|
|
}
|
|
|
|
mkdirSync(out, { recursive: true });
|
|
const browser = await chromium.launch({ channel: 'chrome', headless: true });
|
|
const files: string[] = [];
|
|
for (const theme of themes) {
|
|
const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, colorScheme: theme === 'dark' ? 'dark' : 'light', reducedMotion: 'reduce' });
|
|
const page = await ctx.newPage();
|
|
const errors: string[] = [];
|
|
page.on('pageerror', e => errors.push(String(e)));
|
|
page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
|
|
for (const [name, start] of SCREENS) {
|
|
if (only.length && !only.includes(name)) continue;
|
|
await page.goto(`${base}/?start=${encodeURIComponent(start)}&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
const f = join(out, `${prefix}-${name}${suffix}-${theme}${lang === 'en' ? '' : `-${lang}`}.png`);
|
|
await page.screenshot({ path: f });
|
|
files.push(f);
|
|
}
|
|
if (!only.length || only.includes('toast')) {
|
|
// a background job: Read the stores, caught mid-way with its progress toast
|
|
await page.goto(`${base}/?start=portfolio&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /Read the stores|Mağazaları oku/ }).first().click();
|
|
await page.waitForTimeout(900);
|
|
const f = join(out, `${prefix}-toast${suffix}-${theme}${lang === 'en' ? '' : `-${lang}`}.png`);
|
|
await page.screenshot({ path: f });
|
|
files.push(f);
|
|
}
|
|
const shot = async (name: string) => {
|
|
const f = join(out, `${prefix}-${name}${suffix}-${theme}${lang === 'en' ? '' : `-${lang}`}.png`);
|
|
await page.screenshot({ path: f });
|
|
files.push(f);
|
|
};
|
|
if (!only.length || only.includes('accounts-tested')) {
|
|
// every account checked: one Play service account was never invited (permission missing, with the fix)
|
|
await page.goto(`${base}/?start=accounts&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /Test all accounts|Tüm hesapları sına/ }).click();
|
|
await page.waitForTimeout(900);
|
|
await page.getByText(/Sample Labs · Play/).first().scrollIntoViewIfNeeded();
|
|
await page.evaluate(() => document.getElementById('content')?.scrollBy({ top: 260 }));
|
|
await page.waitForTimeout(200);
|
|
await shot('accounts-tested');
|
|
}
|
|
if (!only.length || only.includes('portfolio-filter')) {
|
|
// one studio, sorted by name, no grouping, with a search
|
|
await page.goto(`${base}/?start=portfolio&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.locator('.fchip', { hasText: 'Example Games' }).click();
|
|
await page.getByPlaceholder(/Search apps|Uygulama, bundle/).fill('o');
|
|
await page.waitForTimeout(300);
|
|
await shot('portfolio-filter');
|
|
await page.locator('.fchip').first().click();
|
|
await page.getByPlaceholder(/Search apps|Uygulama, bundle/).fill('');
|
|
}
|
|
if (!only.length || only.includes('tf-edit')) {
|
|
// TestFlight: What to Test editor of the newest build
|
|
await page.goto(`${base}/?start=app:testflight&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /What to Test|Neyi test etmeli/ }).first().click();
|
|
await page.getByRole('textbox').first().fill('New combo meter and the calmer night palette. Please try level 31 at a 20x combo.');
|
|
await page.waitForTimeout(200);
|
|
await page.evaluate(() => document.getElementById('content')?.scrollBy({ top: 330 }));
|
|
await page.waitForTimeout(200);
|
|
await shot('tf-edit');
|
|
}
|
|
if (!only.length || only.includes('reply-edit')) {
|
|
// Reviews: the reply editor of the approved draft, edited (not saved yet)
|
|
await page.goto(`${base}/?start=app:reviews&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.locator('textarea.reply-edit').fill('Sorry about the lost streak! The fix for the crash at high combos is in 2.4.0, in review now. Thank you for the level number.');
|
|
await page.waitForTimeout(200);
|
|
await shot('reply-edit');
|
|
}
|
|
if (!only.length || only.includes('write-toast')) {
|
|
// a write running: halt the Play rollout (owner action), caught with its progress toast
|
|
await page.goto(`${base}/?start=app:releases&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /^Halt$|^Durdur$/ }).first().click();
|
|
await page.waitForTimeout(450);
|
|
await shot('write-toast');
|
|
}
|
|
if (!only.length || only.includes('import')) {
|
|
// Import apps running, with its progress toast
|
|
await page.goto(`${base}/?start=accounts&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /^Import apps$|^Uygulamaları içe aktar$/ }).first().click();
|
|
await page.waitForTimeout(700);
|
|
await shot('import');
|
|
}
|
|
if (!only.length || only.includes('prices-diff')) {
|
|
// a price sheet compared with the stores, with the owner's Apply
|
|
await page.goto(`${base}/?start=app:prices&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /Compare store\/prices.csv|store\/prices.csv karşılaştır/ }).click();
|
|
await page.waitForTimeout(1500);
|
|
await shot('prices-diff');
|
|
}
|
|
if (!only.length || only.includes('export-menu')) {
|
|
// the top-right Export menu of a screen
|
|
await page.goto(`${base}/?start=app:reviews&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.getByRole('button', { name: /^Export$|^Dışa aktar$/ }).click();
|
|
await page.waitForTimeout(300);
|
|
await shot('export-menu');
|
|
}
|
|
if (!only.length || only.includes('art-lightbox')) {
|
|
// one render opened large
|
|
await page.goto(`${base}/?start=app:art&theme=${theme}&lang=${lang}`);
|
|
await settle(page);
|
|
await page.locator('.art-pic').first().click();
|
|
await page.waitForTimeout(500);
|
|
await shot('art-lightbox');
|
|
}
|
|
if (errors.length) console.error(`${theme}: page errors:\n ${[...new Set(errors)].join('\n ')}`);
|
|
await ctx.close();
|
|
}
|
|
await browser.close();
|
|
console.log(files.join('\n'));
|