// One screenshot per page, per project in playwright.config.mjs, of the // side setup.mjs serves: compare.mjs shoots the side it compares against // first, as the snapshots, then this one against them. The pages' paths are // known once setup.mjs has made them, so they are read in each test; a page // one side does not have is skipped there. Pages whose HTML is the same on // both sides, which compare.mjs lists, are skipped before a browser page is // opened. import { test, expect } from '@playwright/test' import { existsSync, readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { signedIn } from './fixtures.mjs' import { base, now, viewers } from './world.mjs' const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..') const read = (file, otherwise) => { const path = resolve(root, 'target/shots', file) return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : otherwise } const unchanged = new Set(read('unchanged.json', [])) const views = read('views.json', {}) const names = [ 'home', 'search', 'start', 'terms', 'privacy', 'guidelines', 'profile', 'profile-fork', 'profile-repos', 'profile-activity', 'repository', 'repository-watch', 'directory', 'file', 'markdown', 'markdown-plain', 'diagram', 'image', 'commits', 'commit', 'commit-merged', 'pulls', 'pulls-closed', 'pull', 'pull-open', 'issues', 'issues-closed', 'issue', 'issue-subscribe', 'issue-closed', 'issue-fixed', 'fork', 'empty', 'missing', 'check', 'queue', 'checks', 'pull-queued', 'pull-ejected', 'stats', 'settings', 'settings-branches', 'settings-labels', 'settings-access', 'settings-actions', 'releases', 'releases-image', 'releases-empty', ] for (const name of names) { test.describe(() => { test.skip(unchanged.has(name), 'unchanged') test.skip(!views[name], 'not on this side') test.skip( Boolean(viewers[name] && process.env.SHOTS_LIVE), 'signed in, which only the recording draws', ) test(name, async ({ page, timezoneId }) => { await page.clock.setFixedTime(new Date(now)) // The browser has said its zone before, as setup.mjs's asking does, so // the server puts the times in it. await page .context() .addCookies([{ name: 'tz', value: encodeURIComponent(timezoneId), url: base }]) // And one signed in, who it is, which the stand-in answers as. if (viewers[name]) { const [cookie, value] = signedIn(viewers[name]).split('=') await page.context().addCookies([{ name: cookie, value, url: base }]) } await page.goto(views[name], { waitUntil: 'load' }) // The pages paint their highlighting once they are running. await page.locator('html[data-ready]').waitFor({ state: 'attached' }) // And their diagrams once they are drawn. await page.waitForFunction(() => !document.querySelector('[data-drawing]')) // And what they ask for after drawing, once it has come. await page.waitForFunction(() => !document.querySelector('[data-loading]')) // A DID is new each run, as the PLC directory makes one per account, // and a command naming this server names the port it is on: they are // covered, so the rest can match to the pixel. So is the squash // merge's hash: its message names the pull's at:// URI, whose DID and // key are new each world, so recording the fixtures again would move // every page that shows it. const merged = views['commit-merged']?.split('/').pop().slice(0, 7) const mask = [ page.locator('.did'), page.locator('.cmd', { hasText: /127\.0\.0\.1:\d+/ }), ...(merged ? [page.locator('.sha, .hash', { hasText: merged })] : []), ] await expect(page).toHaveScreenshot(`${name}.png`, { fullPage: true, mask, maskColor: '#80808a', }) }) }) }