echo "(body edit needs verification — see above)" ## Screenshots <!-- agent-screenshots:start -->     <!-- agent-screenshots:end --> --------- Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io> Co-authored-by: openhands <openhands@all-hands.dev>
143 lines
4.6 KiB
JavaScript
143 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// AUT-1 smoke run — capture the before/after visual diff for the
|
|
// `primary-button hover state` change in `src/assets/main.css`.
|
|
//
|
|
// Renders tests/visual-previews/AUT-1/preview.html in a real Chromium
|
|
// against the project's bundled Bulma stylesheet, then takes two
|
|
// screenshots:
|
|
//
|
|
// desktop-before.png — Bulma defaults, button idle (no hover effect).
|
|
// desktop-after.png — With the AUT-1 CSS rules applied, button
|
|
// hovered (lift + brightness shift visible).
|
|
// mobile-before.png — Same as desktop-before, captured at the
|
|
// Pixel 5 viewport.
|
|
// mobile-after.png — Same as desktop-after, captured at the
|
|
// Pixel 5 viewport.
|
|
//
|
|
// Output is written to tests/visual-previews/AUT-1/. The script is
|
|
// idempotent: existing files are overwritten, not appended to.
|
|
//
|
|
// Invoke with: `node scripts/aut-1-capture-hover-preview.mjs`
|
|
// (Chromium must already be installed via `npm run test:e2e:install`.)
|
|
|
|
import { chromium } from "playwright";
|
|
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname } from "node:path";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
const PREVIEW_HTML = path.join(
|
|
REPO_ROOT,
|
|
"tests/visual-previews/AUT-1/preview.html",
|
|
);
|
|
const OUTPUT_DIR = path.join(REPO_ROOT, "tests/visual-previews/AUT-1");
|
|
|
|
// The AUT-1 hover rules — kept in lockstep with the diff in
|
|
// src/assets/main.css. We inject these via a <style> tag on the
|
|
// "after" pass and leave them off for the "before" pass so the
|
|
// screenshots show the same Bulma theme but with vs without the new
|
|
// hover effect.
|
|
const AUT1_HOVER_CSS = `
|
|
.button.is-primary {
|
|
transition:
|
|
filter 0.15s ease,
|
|
transform 0.15s ease;
|
|
}
|
|
.button.is-primary:hover,
|
|
.button.is-primary.is-hovered {
|
|
filter: brightness(1.08);
|
|
transform: translateY(-1px);
|
|
}
|
|
.button.is-primary:active,
|
|
.button.is-primary.is-active {
|
|
filter: brightness(0.95);
|
|
transform: translateY(0);
|
|
}
|
|
.button.is-primary:focus-visible {
|
|
filter: brightness(1.04);
|
|
transform: translateY(-1px);
|
|
}
|
|
`;
|
|
|
|
async function captureVariant({ browser, viewport, label, withHoverCss, hover }) {
|
|
const context = await browser.newContext({
|
|
viewport,
|
|
deviceScaleFactor: 2,
|
|
});
|
|
const page = await context.newPage();
|
|
await page.goto(pathToFileUrl(PREVIEW_HTML));
|
|
if (withHoverCss) {
|
|
await page.addStyleTag({ content: AUT1_HOVER_CSS });
|
|
}
|
|
const target = page.locator(
|
|
hover
|
|
? '[data-testid="primary-button-hover"]'
|
|
: '[data-testid="primary-button-idle"]',
|
|
);
|
|
const stage = page.locator(
|
|
hover ? "#stage-hover" : "#stage-idle",
|
|
);
|
|
if (hover) {
|
|
await target.hover();
|
|
// Wait for the 0.15s transition to settle.
|
|
await page.waitForTimeout(220);
|
|
}
|
|
const filename = `${label}-${withHoverCss ? "after" : "before"}.png`;
|
|
const destination = path.join(OUTPUT_DIR, filename);
|
|
await stage.screenshot({ path: destination, type: "png" });
|
|
await context.close();
|
|
return destination;
|
|
}
|
|
|
|
function pathToFileUrl(filePath) {
|
|
// Playwright's `file://` URLs need absolute paths. On POSIX this is
|
|
// straightforward; on Windows this helper keeps the script cross-platform
|
|
// should it ever run there.
|
|
const absolute = path.resolve(filePath);
|
|
return absolute.startsWith("/") ? `file://${absolute}` : `file:///${absolute}`;
|
|
}
|
|
|
|
async function main() {
|
|
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
const browser = await chromium.launch({ headless: true });
|
|
try {
|
|
const viewports = [
|
|
{ label: "desktop", viewport: { width: 1280, height: 720 } },
|
|
{
|
|
label: "mobile",
|
|
viewport: { width: 393, height: 851 },
|
|
},
|
|
];
|
|
for (const { label, viewport } of viewports) {
|
|
for (const withHoverCss of [false, true]) {
|
|
for (const hover of [false, true]) {
|
|
// We only want idle on the "before" pass and hover on the
|
|
// "after" pass — skip the two redundant combinations.
|
|
const isIdleShot = !hover;
|
|
const isBeforeShot = !withHoverCss;
|
|
if (isIdleShot !== isBeforeShot) continue;
|
|
const file = await captureVariant({
|
|
browser,
|
|
viewport,
|
|
label,
|
|
withHoverCss,
|
|
hover,
|
|
});
|
|
// eslint-disable-next-line no-console
|
|
console.log("wrote", path.relative(REPO_ROOT, file));
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
// eslint-disable-next-line no-console
|
|
console.error("aut-1 capture failed:", err);
|
|
process.exitCode = 1;
|
|
});
|