@krizaka/page-audit
L’audit de page de toutes les apps : débordement, carte hors de son panneau, chevauchements et colonnes coupées, sur des routes, largeurs et thèmes, avec un auto-test.
Installer
npm install @krizaka/page-auditAussi publié en sous-chemins : @krizaka/page-audit/settle, @krizaka/page-audit/fixtures, @krizaka/page-audit/journey, @krizaka/page-audit/visual.
Le README ci-dessous est celui du paquet sur npm, en anglais.
The page audit of every Krizaka app, in one place (ADR-013: one source per rule set). It measures a rendered page in a real browser:
horizontal overflow, a card out of its panel, sections covering each other, a column cut by a clipping ancestor, top chrome that
overlaps, content cut inside its box, a token wrapped per character, unnamed buttons, empty headings. Closed <details>, aria-hidden
layers, scrollers and fixed elements are not counted. Library code, not UI: it lives in krizaka-ts.
npm i -D @krizaka/page-audit playwright-core
npx krizaka-page-audit --self-test # proves it on its own fixtures
npx krizaka-page-audit --base http://localhost:3000 --routes /en,/fr --widths 360,390,1440 --themes dark,lightimport { auditRoutes, auditContext, loadSettled, resizeSettled, auditPage, problemsOf } from "@krizaka/page-audit";
const problems = await auditRoutes({ base, routes: ["/en", "/fr"], checks: { h1: true } }); // [] when fineauditContext / loadSettled / resizeSettled open a context with reduced motion and no View Transition, and wait for none to run
before anything is measured (yomizaka#354). prepare(page, route) signs in or seeds; launch gives your own browser.
The dev-preview crawls, in one audit (0.3)
Opt-in through checks (nothing changes for a repo that does not ask): status (HTTP 4xx/5xx or no response), emptyMain, frame
(the signed-in frame, [data-app-frame-slot], forces a scroll), minAction (a button-styled action under N px), boxes (a landmark
wider than its box), landmarks ("public" one footer naming Krizaka and a nav, "app", "auth"), pictures, emptyControls,
hardColors, maps (an empty map, akinaza#488: a GL / Leaflet / [data-kz-map] box under 120 px on a side, a GL map with no canvas,
a Leaflet map with no loaded tile; maps: { required: true } also fails a page that should draw one but has none). Console noise the app tolerates: ignoreConsole: [/…/] ([] counts every error); attempts retries a route.
const routes = await routesFromSitemap(base, { first: ["/", "/docs"], limit: 14 }); // a crawl as the route source (null: nothing answers)
await auditRoutes({
base, routes: [...routes, { url: "/login", chrome: "auth" }, { url: "/chat", session: true, chrome: "app" }],
widths: [{ width: 1440, height: 900 }, { width: 390, height: 844 }],
session: { storageState }, // or { signIn(context, { base, theme }) {…}, routes: async (page) => […] } for pages found once signed in
a11y: { app: "my-app", baseline: "a11y-baseline.json", widths: [390, 1440], session: { app: "my-app-app", baseline: "a11y-baseline.app.json" } },
checks: { status: true, emptyMain: true, minAction: 36, landmarks: "public", frame: true },
});a11y runs axe through @krizaka/a11y-audit (an optional peer) and ratchets with the repo's own baseline file; --a11y-update and
--a11y-init work as there. The CLI takes --sitemap, --status, --empty-main, --min-action, --a11y app:baseline.json.
Visual regression of the journeys (0.4)
@krizaka/page-audit/visual (krizaka/.github#375) compares screenshots of an app's main journeys against Linux baselines, with @playwright/test
as the runner. It freezes what moves (clock, motion, caret, View Transition, locale, time zone, fonts) and leaves the app its routes, its persona and its masks.
import { test, expect } from "@playwright/test";
import { defineJourneyTests } from "@krizaka/page-audit/visual";
defineJourneyTests({
test, expect,
signIn: async (page) => { /* the seeded persona signs in through the form */ },
journeys: [
{ name: "landing", path: "/fr" },
{ name: "home", path: "/fr/app", session: true, mask: ["[data-live]"] },
{ name: "home", path: "/fr/app", session: true, firstPaint: true, viewports: ["phone"] }, // the HTML the server sent
{ name: "home", path: "/fr/app", session: true, firstPaint: { hold: ["**/api/me"] } }, // hydrated, account query pending
],
});Baselines are taken in the official Playwright container by the shared workflows of krizaka/.github (visual-regression.yml to compare,
update-visual-baselines.yml to commit them on a branch); never on a Mac.
The journey kit (0.6)
@krizaka/page-audit/journey is the kit of the customer journeys (ADR-017, krizaka/.github#423). A product writes no copy of it.
- Catalogue:
validateJourneychecks a row ofjourneys/journeys.json(kebab-case id, a promise, a persona of the seeded cast, platforms, steps, a budget of 90 s by default);checkCataloguefails a file without a row, a row without a file, an id twice;journeysByTag(index, "smoke")picks what runs on every pull request;budgetProblemnames a journey over its budget. - Persona sign-in once per worker:
personaSessions({ dir, signIn }).stateFor("eric")signs the persona in on the first call and reuses the browser state after (parallel calls share one sign-in; one file per worker; a failed sign-in is not remembered). - Seeded cast:
seededPersona(cast, "eric")reads a member or names who exists. - Masks:
journeyMaskSelectors({ extra })=time,[data-mask],[data-date],[data-testid$="-id"],[data-run-id]and the journey's own. - Video hooks:
videoContextOptions(journey, { dir })records only the journeys withvideo: true;videoFile(journey, "web")names the file. - Flake policy:
assertNoRetries(playwrightConfig)refuses retries above 0 (also per project);quarantine(entries)holds a named test with its reason and its issue, capped at one.
Exports
Les 14 noms que le paquet exporte, lus dans ses types.
| Nom | Nature | Ce que c’est |
|---|---|---|
auditContext | fonction | A browser context for an audit (reduced motion emulated, no View Transition). |
auditOptions | fonction | The options of an audit's browser context: the caller's, plus reduced motion. |
auditPage | constante | The expression Playwright runs in the page (page.evaluate(auditPage)): the probes, serialised together, then the measure. |
auditRoutes | fonction | |
isPageError | fonction | A blocked third-party resource is not a page error; a script error and a hydration mismatch are. |
loadSettled | fonction | Loads a page, then waits for the faces and for the transitions: what is read afterwards is final. |
problemsOf | fonction | |
resizeSettled | fonction | Resizes the viewport, then waits for the transitions a resize can start. |
routesFromSitemap | fonction | A route source that is a crawl: the paths of the site's own sitemap (a page added there is audited), the first ones always first, at most limit. |
selfTest | fonction | |
settled | fonction | Resolves once no view transition is running (none starts under reduced motion; this makes the wait explicit). |
THEMES | constante | |
WIDTHS | constante | |
withoutViewTransitions | fonction | Runs before any page script: the browser offers no View Transition, so PageTransition (@krizaka/ui) changes the page at once. |
@krizaka/page-audit/settle
| Nom | Nature | Ce que c’est |
|---|---|---|
auditContext | fonction | A browser context for an audit (reduced motion emulated, no View Transition). |
auditOptions | fonction | The options of an audit's browser context: the caller's, plus reduced motion. |
loadSettled | fonction | Loads a page, then waits for the faces and for the transitions: what is read afterwards is final. |
resizeSettled | fonction | Resizes the viewport, then waits for the transitions a resize can start. |
settled | fonction | Resolves once no view transition is running (none starts under reduced motion; this makes the wait explicit). |
withoutViewTransitions | fonction | Runs before any page script: the browser offers no View Transition, so PageTransition (@krizaka/ui) changes the page at once. |
@krizaka/page-audit/fixtures
| Nom | Nature | Ce que c’est |
|---|---|---|
BOX_TOO_WIDE | constante | A landmark whose content is wider than its box. |
BROKEN_PARTS | constante | Broken picture, a tile that draws nothing, an inline hard-coded colour. |
CARD_OVER_NEXT | constante | The binder as it was: the open folder's card is as tall as its grid row (a fixed height) and runs over the next section. |
CHROME_BAD | constante | |
CHROME_FINE | constante | Chrome that fits the family: one footer naming Krizaka, a nav. |
COLUMN_CUT | constante | A two-column layout whose wrapper clips the right column. |
EMPTY_MAIN | constante | A page whose <main> draws nothing. |
FINE | constante | What it should be: the card follows its content, the columns fit, a wide table scrolls inside its own scroller. |
FOLDED_SECTION | constante | The report tab: a section inside a folded details (the closing note) beside sections it must not be taken to cover. |
FRAME_FITS | constante | The same frame, sized to what is left of the screen. |
FRAME_FORCES_SCROLL | constante | The signed-in frame asks 1000 px of a 700 px screen under a 60 px notice (akinaza#444). |
HIDDEN_CONTENT | constante | Not drawn, so not a problem: a chart's text version in a visually hidden wrapper, and the content of a folded <details> (the browser still gives both a box). |
MAP_COLLAPSED | constante | A map that laid out at 0 px: absolute inset-0 in a frame with no height of its own (akinaza#488). |
MAP_FINE | constante | A map box with a size and a canvas, and a Leaflet one with a loaded tile. |
MAP_HIDDEN | constante | A map that is not drawn (display none: a closed tab, a small screen): nothing to measure. |
MAP_NO_CANVAS | constante | A GL map box of a good size where nothing was drawn (no canvas). |
SECTIONS_OVERLAP | constante | Two sections laid out on top of each other. |
SMALL_ACTION | constante | A button-styled action 24 px tall, under the 36 px tap target. |
TOKEN_IN_STRETCHED_ROW | constante | A short token in a table row stretched by a tall neighbour: the cell is tall, its text is one line. |
TOKEN_WRAPPED | constante | An identifier in a narrow cell that breaks every few characters. |
@krizaka/page-audit/journey
| Nom | Nature | Ce que c’est |
|---|---|---|
assertNoRetries | fonction | A flake is a bug, not a retry: retries are 0 (top level and every project). |
budgetProblem | fonction | The message when a run took longer than its journey's budget, undefined within it. |
checkCatalogue | fonction | Files and rows must match: a file with no row, a row with no file, an id twice. |
DEFAULT_BUDGET_SECONDS | constante | |
JOURNEY_MASKS | constante | What changes between two runs of the same build: dates, ids, anything marked data-mask. |
journeyMaskSelectors | fonction | The selectors to mask in a capture of a journey screen. |
journeysByTag | fonction | The rows that carry a tag (smoke runs on every pull request, the rest in the nightly pass). |
personaSessions | fonction | Persona sign-in once per worker. |
quarantine | fonction | The quarantine: a named test, why, and the issue that fixes it. |
QUARANTINE_CAP | constante | |
seededPersona | fonction | A member of the seeded cast (the same people locally and deployed), or an error naming who exists. |
validateJourney | fonction | The problems of one catalogue row (journeys/journeys.json), empty when it is a good journey. |
VIDEO_SIZE | constante | The size of a recorded video: the laptop of the visual matrix. |
videoContextOptions | fonction | Context options that record the run of a journey whose video is true (the source of the product video), none otherwise. |
videoFile | fonction | The name the recorded video is published under. |
@krizaka/page-audit/visual
| Nom | Nature | Ce que c’est |
|---|---|---|
defineJourneyTests | fonction | Registers one test per journey × viewport × theme. |
FREEZE_CSS | constante | The style every capture carries: nothing moves, nothing blinks. |
FROZEN_TIME | constante | The clock of every capture (a Thursday, in winter: no daylight-saving edge). |
initVisualPage | fonction | Runs before the page's own scripts: the theme the app reads, no View Transition. |
prepareVisualPage | fonction | Prepares a page: fixed time, the theme key, no View Transition. |
settleForShot | fonction | Waits until what is on screen is final: the faces, the images, the transitions. |
shotOptions | fonction | The options of toHaveScreenshot: a small tolerance for anti-aliasing, never for a missing element. |
VIEWPORTS | constante | The two sizes of every journey: a laptop and a phone. |
VISUAL_LOCALE | constante | |
VISUAL_THEMES | constante | |
VISUAL_TIME_ZONE | constante | |
visualContextOptions | fonction | The options of a browser context for a capture. |
visualMatrix | fonction | The viewport × theme cells of a journey, with the snapshot name of each (<journey>-<viewport>-<theme>.png). |
Paquet
| Version sur npm | 0.6.0 (npm) |
| Licence | Apache-2.0 |
| Node.js | >=22 |
| Dépendances | aucune |
| Dépendances à fournir | @krizaka/a11y-audit >=0.2.0, playwright-core >=1.40 |
| Code source | page-audit |
Journal des versions
0.6.0
Minor changes
@krizaka/page-audit/journey, the shared journey kit of ADR-017: the catalogue row and its check (validateJourney,checkCatalogue,journeysByTag,budgetProblem), a persona signed in once per worker (personaSessions), the seeded cast (seededPersona), the masks of dates and ids (journeyMaskSelectors), the video hooks of journeys that feed a product video (videoContextOptions,videoFile) and the flake policy (assertNoRetries, a cappedquarantine).
0.5.1
Patch changes
@krizaka/page-audit/visual: a first-paint capture (JavaScript off) no longer hangs for the test timeout:settleForShotdoes not inject the freeze style (Playwright cannot run a script in such a page);animations: "disabled"still freezes CSS animations.
0.5.0
Minor changes
@krizaka/page-audit/visual: visual regression of the main journeys, one source for every app (krizaka/.github#375).defineJourneyTestsregisters one PlaywrighttoHaveScreenshottest per journey × viewport (desktop, phone) × theme (light, dark) with a frozen clock, reduced motion, no View Transition, a fixed locale and time zone, masks for live values, a signed-in persona reused across captures, and a first-paint capture (JavaScript off, or a query held pending) for flashes like krizaka-ui#827.
0.4.0
Minor changes
mapscheck: an empty map fails the audit (a GL / Leaflet /[data-kz-map]box under 120 px on a side, a GL map with no canvas, a Leaflet map with no loaded tile;maps: { required: true }also fails a page that should draw one). Proven on its own fixtures in the self-test (krizaka-ts#199, akinaza#488).
0.3.1
Patch changes
- A token in a table cell is not « wrapped one character per line » because a taller neighbour stretches its row: the check measures the text's own lines, not the cell's box.
0.3.0
Minor changes
- The union of the dev-preview crawls (krizaka-ui#747): opt-in HTTP status, empty
<main>, signed-in frame forced scroll, minimum action height, landmark wider than its box, page chrome (footer, nav), broken pictures, empty tiles and hard-coded inline colours; an optional axe step through@krizaka/a11y-auditwith the repo's own baseline (the ratchet stays per repo); a crawl route source (routesFromSitemap), per-routechromeandsession(stored state or sign-in), per-viewport heights,ignoreConsoleandattempts.
0.2.0
Minor changes
- New package: the page audit of every Krizaka app in one place (overflow, containment, overlap, cut columns, top chrome overlaps, closed
<details>skipped, View Transitions neutralised,auditContext/settle), anauditRoutesAPI over routes x widths x themes, akrizaka-page-auditbin with a--self-teston fixtures. Ported from yomizaka with its tests (krizaka-ui#747).
@krizaka/architecture
La vue C4 d’un dépôt générée depuis son code : conteneurs, composants, imports entre eux et services externes, en JSON et en texte, sur une copie locale.
@krizaka/config
Le kit qualité de chaque dépôt TypeScript : configs ESLint, preset dependency-cruiser, bases tsconfig, Prettier et le cliquet de dette.