Aller au contenu
Krizaka TypeScript
Krizaka TypeScript

@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

bashTerminal
npm install @krizaka/page-audit

Aussi 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,light
import { auditRoutes, auditContext, loadSettled, resizeSettled, auditPage, problemsOf } from "@krizaka/page-audit";
const problems = await auditRoutes({ base, routes: ["/en", "/fr"], checks: { h1: true } }); // [] when fine

auditContext / 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: validateJourney checks a row of journeys/journeys.json (kebab-case id, a promise, a persona of the seeded cast, platforms, steps, a budget of 90 s by default); checkCatalogue fails a file without a row, a row without a file, an id twice; journeysByTag(index, "smoke") picks what runs on every pull request; budgetProblem names 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 with video: 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.

NomNatureCe que c’est
auditContextfonctionA browser context for an audit (reduced motion emulated, no View Transition).
auditOptionsfonctionThe options of an audit's browser context: the caller's, plus reduced motion.
auditPageconstanteThe expression Playwright runs in the page (page.evaluate(auditPage)): the probes, serialised together, then the measure.
auditRoutesfonction
isPageErrorfonctionA blocked third-party resource is not a page error; a script error and a hydration mismatch are.
loadSettledfonctionLoads a page, then waits for the faces and for the transitions: what is read afterwards is final.
problemsOffonction
resizeSettledfonctionResizes the viewport, then waits for the transitions a resize can start.
routesFromSitemapfonctionA 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.
selfTestfonction
settledfonctionResolves once no view transition is running (none starts under reduced motion; this makes the wait explicit).
THEMESconstante
WIDTHSconstante
withoutViewTransitionsfonctionRuns before any page script: the browser offers no View Transition, so PageTransition (@krizaka/ui) changes the page at once.

@krizaka/page-audit/settle

NomNatureCe que c’est
auditContextfonctionA browser context for an audit (reduced motion emulated, no View Transition).
auditOptionsfonctionThe options of an audit's browser context: the caller's, plus reduced motion.
loadSettledfonctionLoads a page, then waits for the faces and for the transitions: what is read afterwards is final.
resizeSettledfonctionResizes the viewport, then waits for the transitions a resize can start.
settledfonctionResolves once no view transition is running (none starts under reduced motion; this makes the wait explicit).
withoutViewTransitionsfonctionRuns before any page script: the browser offers no View Transition, so PageTransition (@krizaka/ui) changes the page at once.

@krizaka/page-audit/fixtures

NomNatureCe que c’est
BOX_TOO_WIDEconstanteA landmark whose content is wider than its box.
BROKEN_PARTSconstanteBroken picture, a tile that draws nothing, an inline hard-coded colour.
CARD_OVER_NEXTconstanteThe 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_BADconstante
CHROME_FINEconstanteChrome that fits the family: one footer naming Krizaka, a nav.
COLUMN_CUTconstanteA two-column layout whose wrapper clips the right column.
EMPTY_MAINconstanteA page whose &lt;main> draws nothing.
FINEconstanteWhat it should be: the card follows its content, the columns fit, a wide table scrolls inside its own scroller.
FOLDED_SECTIONconstanteThe report tab: a section inside a folded details (the closing note) beside sections it must not be taken to cover.
FRAME_FITSconstanteThe same frame, sized to what is left of the screen.
FRAME_FORCES_SCROLLconstanteThe signed-in frame asks 1000 px of a 700 px screen under a 60 px notice (akinaza#444).
HIDDEN_CONTENTconstanteNot drawn, so not a problem: a chart's text version in a visually hidden wrapper, and the content of a folded &lt;details> (the browser still gives both a box).
MAP_COLLAPSEDconstanteA map that laid out at 0 px: absolute inset-0 in a frame with no height of its own (akinaza#488).
MAP_FINEconstanteA map box with a size and a canvas, and a Leaflet one with a loaded tile.
MAP_HIDDENconstanteA map that is not drawn (display none: a closed tab, a small screen): nothing to measure.
MAP_NO_CANVASconstanteA GL map box of a good size where nothing was drawn (no canvas).
SECTIONS_OVERLAPconstanteTwo sections laid out on top of each other.
SMALL_ACTIONconstanteA button-styled action 24 px tall, under the 36 px tap target.
TOKEN_IN_STRETCHED_ROWconstanteA short token in a table row stretched by a tall neighbour: the cell is tall, its text is one line.
TOKEN_WRAPPEDconstanteAn identifier in a narrow cell that breaks every few characters.

@krizaka/page-audit/journey

NomNatureCe que c’est
assertNoRetriesfonctionA flake is a bug, not a retry: retries are 0 (top level and every project).
budgetProblemfonctionThe message when a run took longer than its journey's budget, undefined within it.
checkCataloguefonctionFiles and rows must match: a file with no row, a row with no file, an id twice.
DEFAULT_BUDGET_SECONDSconstante
JOURNEY_MASKSconstanteWhat changes between two runs of the same build: dates, ids, anything marked data-mask.
journeyMaskSelectorsfonctionThe selectors to mask in a capture of a journey screen.
journeysByTagfonctionThe rows that carry a tag (smoke runs on every pull request, the rest in the nightly pass).
personaSessionsfonctionPersona sign-in once per worker.
quarantinefonctionThe quarantine: a named test, why, and the issue that fixes it.
QUARANTINE_CAPconstante
seededPersonafonctionA member of the seeded cast (the same people locally and deployed), or an error naming who exists.
validateJourneyfonctionThe problems of one catalogue row (journeys/journeys.json), empty when it is a good journey.
VIDEO_SIZEconstanteThe size of a recorded video: the laptop of the visual matrix.
videoContextOptionsfonctionContext options that record the run of a journey whose video is true (the source of the product video), none otherwise.
videoFilefonctionThe name the recorded video is published under.

@krizaka/page-audit/visual

NomNatureCe que c’est
defineJourneyTestsfonctionRegisters one test per journey × viewport × theme.
FREEZE_CSSconstanteThe style every capture carries: nothing moves, nothing blinks.
FROZEN_TIMEconstanteThe clock of every capture (a Thursday, in winter: no daylight-saving edge).
initVisualPagefonctionRuns before the page's own scripts: the theme the app reads, no View Transition.
prepareVisualPagefonctionPrepares a page: fixed time, the theme key, no View Transition.
settleForShotfonctionWaits until what is on screen is final: the faces, the images, the transitions.
shotOptionsfonctionThe options of toHaveScreenshot: a small tolerance for anti-aliasing, never for a missing element.
VIEWPORTSconstanteThe two sizes of every journey: a laptop and a phone.
VISUAL_LOCALEconstante
VISUAL_THEMESconstante
VISUAL_TIME_ZONEconstante
visualContextOptionsfonctionThe options of a browser context for a capture.
visualMatrixfonctionThe viewport × theme cells of a journey, with the snapshot name of each (&lt;journey>-&lt;viewport>-&lt;theme>.png).

Paquet

Version sur npm0.6.0 (npm)
LicenceApache-2.0
Node.js>=22
Dépendancesaucune
Dépendances à fournir@krizaka/a11y-audit >=0.2.0, playwright-core >=1.40
Code sourcepage-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 capped quarantine).

0.5.1

Patch changes

  • @krizaka/page-audit/visual: a first-paint capture (JavaScript off) no longer hangs for the test timeout: settleForShot does 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). defineJourneyTests registers one Playwright toHaveScreenshot test 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

  • maps check: 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-audit with the repo's own baseline (the ratchet stays per repo); a crawl route source (routesFromSitemap), per-route chrome and session (stored state or sign-in), per-viewport heights, ignoreConsole and attempts.

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), an auditRoutes API over routes x widths x themes, a krizaka-page-audit bin with a --self-test on fixtures. Ported from yomizaka with its tests (krizaka-ui#747).

Sur cette page