Brings all of the develop branch's commits into master. ## What this contains The 9 commits on develop that landed in this round — all XL Vask-related UI fixes plus the 'visible primary-button hover state' visual-diff: - **PR #303** (TRU-5 / AUT-1) — style(AUT-1): visible primary-button hover state + visual diff - **PR #304** (TRU-10 / AUT-6) — fix(invoicing-period): propagate flagged wash start date to Selvvask view - **PR #305** (TRU-12 / AUT-8) — feat(xlvask): render friendly notice for 404 from /modules/xlvask/services/usage/orders - **PR #306** (TRU-9 / AUT-5) — i18n(test): lock in xlvask_review / xlvask_usage_log mirroring to the global v2 fallback - **PR #307** (TRU-13 / AUT-9) — i18n(xlvask_review): translate missing keys for no, sv, de, en - **PR #308** (TRU-15 / AUT-11) — test(e2e): add Playwright smoke test for XL Vask flag → Selvvash navigation - **PR #309** (TRU-11 / AUT-7) — feat(TRU-11): propagate department selector to Selvvask usage query - **PR #310** (TRU-19 / AUT-15) — test(TRU-19): lock self-serve program number range + button registry contract - **PR #311** (TRU-8 / AUT-4) — fix(invoicing-flag-list): explain empty XL Vask hover preview when flag context has no metadata ## Why The XL Vask integration bug surfaced from the user-reported message "XL Vask-registreringen er hverken ignoreret eller knyttet til en ordre i den valgte periode. doesn't show the wash." After dispatching 9 diagnostic + fix tasks and merging all 9 PRs into develop via the OpenSymphony orchestrator running against MiniMax M3, this PR is the canonical release to bring develop's accumulated changes into master. No new code in this PR — just the squash-merged output of the 9 source PRs combined into a single develop→master merge. ## Verification All 9 source PRs passed: - Required CI (Action Runners) - App Store Readiness - Quality lint/i18n/build/unit/e2e suites The required checks on this PR will run the same gate. ## Notes - The api repo has its own equivalent PR/merge — see CHANGELOG for that side. --------- 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;
|
|
});
|