shots: Mac App Store screenshots (mac size, APP_DESKTOP to the macOS version), a landscape layout (Mac window or bare; Mac, Play Chromebook, Play tablets), safe margins in every template checked by shots check from the render's layout boxes, a contact sheet timeout that scales with images, sizes and load; pitfall 80
This commit is contained in:
parent
30488c512d
commit
e59539fec8
26 changed files with 532 additions and 67 deletions
74
tests/browser/landscape.browser.ts
Normal file
74
tests/browser/landscape.browser.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// The screenshot templates in a real page: the landscape layout (Mac window or bare frame, copy column) renders with
|
||||
// its device inside the canvas, and every template keeps its text and logo inside the safe margins (7 % of the width
|
||||
// left / right, 5 % of the height at the top), measured from the page's own layout (body.dataset.boxes). A page with
|
||||
// the margins taken away fails the same rule. Gated like feature-fit: skipped without Chromium.
|
||||
import { afterAll, describe, expect, test } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { boxOutside, DEFAULT_PHONE, DEFAULT_TABLET, marginViolations, renderDims, type TextBox } from '../../cli/shots/lib';
|
||||
import { AFTER_ALL_MS, launchTestBrowser, scaled } from '../helpers/browser';
|
||||
|
||||
const shared = await launchTestBrowser('playwright-core');
|
||||
const { browser } = shared;
|
||||
if (!browser) console.warn(`landscape / safe-margin render test skipped: no Chromium installed (${shared.skipWhy}). Run \`bunx playwright install chromium\`.`);
|
||||
afterAll(() => shared.close(), AFTER_ALL_MS);
|
||||
|
||||
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
|
||||
const dir = join(import.meta.dir, '..', '..', 'cli', 'shots');
|
||||
|
||||
async function render(style: 'game' | 'app', w: number, h: number, o: { landscape?: boolean; frame?: string; hero?: boolean; css?: string; title?: [string, string] } = {}) {
|
||||
const page = await browser.newPage({ viewport: { width: w, height: h } });
|
||||
try {
|
||||
let html = readFileSync(join(dir, style === 'app' ? 'template-app.html' : 'template.html'), 'utf8');
|
||||
if (o.css) html = html.replace('</style>', `${o.css}</style>`);
|
||||
const job = { style, phone: DEFAULT_PHONE, tablet: DEFAULT_TABLET, landscape: { w: 1440, h: 900, bar: 0 }, side: 'left', logo: !!o.hero, appName: 'Demo',
|
||||
fonts: { title: 'X', body: 'Y' }, number: 1, scene: { id: 'a', hero: !!o.hero, frames: [{ src: 'a.png' }] },
|
||||
palette: { bg: ['#123456', '#345678', '#567890'], glow: '#ffffff', ink: '#000000', shadow: 'rgba(0,0,0,.4)' },
|
||||
copy: { eyebrow: 'Daily puzzles', title: o.title ?? ['Match three', 'Win big'], sub: 'Hundreds of levels, no wifi needed.' } };
|
||||
await page.route('http://t.test/**', (r: { request(): { url(): string }; fulfill(o: unknown): Promise<void> }) => {
|
||||
const u = new URL(r.request().url());
|
||||
if (u.pathname.endsWith('.html')) return r.fulfill({ contentType: 'text/html', body: html });
|
||||
if (u.pathname === '/job.json') return r.fulfill({ contentType: 'application/json', body: JSON.stringify(job) });
|
||||
if (u.pathname.startsWith('/frames/') || u.pathname === '/logo') return r.fulfill({ contentType: 'image/png', body: PNG });
|
||||
return r.fulfill({ contentType: 'text/css', body: '' });
|
||||
});
|
||||
const dev = o.landscape ? `&device=landscape&frame=${o.frame ?? 'window'}` : '';
|
||||
await page.goto(`http://t.test/t.html?s=a&w=${w}&h=${h}&lang=en${dev}`);
|
||||
await page.waitForFunction("document.body.dataset.ready === '1'", null, { timeout: scaled(15000) });
|
||||
const read = async (k: string) => JSON.parse((await page.evaluate(`document.body.dataset.${k}`)) ?? '[]');
|
||||
return { boxes: await read('boxes') as TextBox[], devices: await read('devices') as { x: number; y: number; w: number; h: number }[],
|
||||
frameUrl: await page.evaluate("document.querySelector('.lwin img, .tel img.app')?.getAttribute('src') ?? ''") as string,
|
||||
titlebar: await page.evaluate("!!document.querySelector('.lwin:not(.bare) .tb')") as boolean };
|
||||
} finally { await page.close(); }
|
||||
}
|
||||
|
||||
describe.skipIf(!browser)('landscape layout', () => {
|
||||
for (const style of ['game', 'app'] as const) for (const [size, frame] of [['mac', 'window'], ['chromebook', 'none'], ['tab10', 'none']] as const) test(`${style} ${size} (${frame})`, async () => {
|
||||
const d = renderDims(size, { landscape: { playTablets: true } });
|
||||
const r = await render(style, d.w, d.h, { landscape: true, frame });
|
||||
expect(r.frameUrl).toContain('/landscape/');
|
||||
expect(r.titlebar).toBe(frame === 'window');
|
||||
expect(r.devices).toHaveLength(1);
|
||||
expect(boxOutside(r.devices[0]!, d.w, d.h)).toEqual([]);
|
||||
// copy on the left, device on the right
|
||||
expect(Math.max(...r.boxes.map(b => b.x + b.w))).toBeLessThan(r.devices[0]!.x);
|
||||
expect(r.boxes.length).toBeGreaterThanOrEqual(3);
|
||||
expect(marginViolations(r.boxes, d.w, d.h)).toEqual([]);
|
||||
}, scaled(30_000));
|
||||
});
|
||||
|
||||
describe.skipIf(!browser)('safe margins on the portrait templates', () => {
|
||||
for (const style of ['game', 'app'] as const) for (const [w, h] of [[1320, 2868], [1080, 1920], [2064, 2752]] as const) test(`${style} ${w}x${h}, hero with logo and a scene`, async () => {
|
||||
for (const hero of [true, false]) {
|
||||
const r = await render(style, w, h, { hero, title: ['Wonderful long words', 'Everywhere'] });
|
||||
expect(r.boxes.length).toBeGreaterThan(0);
|
||||
if (hero) expect(r.boxes.some(b => b.el === 'logo')).toBe(true);
|
||||
expect(marginViolations(r.boxes, w, h)).toEqual([]);
|
||||
}
|
||||
}, scaled(30_000));
|
||||
test('a fixture with the margins taken away fails', async () => {
|
||||
const r = await render('game', 1080, 1920, { hero: true, css: '.top { padding: 0 !important; top: 0 !important } .logo { width: 100% !important }' });
|
||||
const v = marginViolations(r.boxes, 1080, 1920);
|
||||
expect(v.some(m => m.startsWith('logo is too close to the edge (left 0 px, right 0 px, top 0 px'))).toBe(true);
|
||||
}, scaled(30_000));
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue