@godxjp/ui 32.11.1 → 32.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +1 -1
- package/agent/DESIGN.md +1 -1
- package/agent/START-HERE.md +1 -1
- package/agent/components/Tree.json +1 -1
- package/agent/components.json +1 -1
- package/agent/index.json +2 -2
- package/agent/llms.txt +3 -3
- package/dist/components/data-display/tree.js +36 -7
- package/dist/contracts/measurement.json +1 -1
- package/dist/styles/layers.json +1 -1
- package/dist/styles/layout.css +2 -1
- package/dist/styles/navigation-layout.css +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/scripts/cli.mjs +6 -1
- package/scripts/visual-audit.mjs +260 -11
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -85,7 +85,7 @@ const HELP = {
|
|
|
85
85
|
--dry-run list every change without writing
|
|
86
86
|
What it cannot decide (a component move that changes props, an AppProvider that relied on the
|
|
87
87
|
old vi default) is printed as a "note" for you to finish.`,
|
|
88
|
-
"visual-audit": `godxjp-ui visual-audit [--format json] [--strict] [--widths WxH,…] [--storage-state FILE] <baseUrl> [route …]
|
|
88
|
+
"visual-audit": `godxjp-ui visual-audit [--format json] [--strict] [--widths WxH,…] [--storage-state FILE] [--text-spacing] [--text-zoom 200] [--screenshots DIR] <baseUrl> [route …]
|
|
89
89
|
|
|
90
90
|
Runtime audit (Playwright) against an app you are ALREADY running locally, at six widths.
|
|
91
91
|
baseUrl the running app, e.g. http://localhost:5173
|
|
@@ -95,6 +95,11 @@ const HELP = {
|
|
|
95
95
|
--storage-state a Playwright storageState JSON (cookies + localStorage) so signed-in routes
|
|
96
96
|
are audited signed in; save one with
|
|
97
97
|
npx playwright codegen --save-storage=state.json <url>
|
|
98
|
+
--text-spacing also render each width with the WCAG 1.4.12 text-spacing override, then C01
|
|
99
|
+
--text-zoom N also render each width with text resized to N% (WCAG 1.4.4; e.g. 200)
|
|
100
|
+
Each reports the geometry delta against the unmodified render; a delta of
|
|
101
|
+
zero is an error ("override not applied"), never a pass
|
|
102
|
+
--screenshots a directory for the base + override render of each evidence mode
|
|
98
103
|
--strict exit non-zero on any finding, not only errors
|
|
99
104
|
--format json machine-readable: findings (viewport, element, threshold + origin), unmapped
|
|
100
105
|
items (reviewer required), per-width rule status + reviewer checklist
|
package/scripts/visual-audit.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* godxjp-ui VISUAL audit — the runtime counterpart to the static ui-audit.mjs. Static audit
|
|
4
4
|
* (ui-audit.mjs) reads SOURCE with regexes — zero deps, fast, runs on every save.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
6
|
+
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import {
|
|
9
10
|
VISUAL_RULES,
|
|
@@ -60,6 +61,7 @@ export function buildResult({
|
|
|
60
61
|
viewports = [],
|
|
61
62
|
matrix = [],
|
|
62
63
|
unmapped = [],
|
|
64
|
+
evidence,
|
|
63
65
|
} = {}) {
|
|
64
66
|
const productErrors = findings.filter((f) => f.severity === "error");
|
|
65
67
|
const warnings = findings.filter((f) => f.severity === "warn");
|
|
@@ -92,6 +94,8 @@ export function buildResult({
|
|
|
92
94
|
findings,
|
|
93
95
|
unmapped,
|
|
94
96
|
matrix,
|
|
97
|
+
// Present only when an evidence mode ran (gh#1316): one entry per route × width × mode.
|
|
98
|
+
...(evidence ? { evidence } : null),
|
|
95
99
|
errors,
|
|
96
100
|
};
|
|
97
101
|
}
|
|
@@ -1469,6 +1473,195 @@ export function collectProbes(opts) {
|
|
|
1469
1473
|
}
|
|
1470
1474
|
/* c8 ignore stop */
|
|
1471
1475
|
|
|
1476
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
1477
|
+
// Evidence modes (gh#1316): `--text-spacing` (WCAG 2.2 SC 1.4.12) and `--text-zoom 200` (SC 1.4.4).
|
|
1478
|
+
// A consumer reported "W12 0/0/0" from captures byte-identical to the base render — the override
|
|
1479
|
+
// never applied, and nothing said so. Each mode here measures the render BEFORE and AFTER the
|
|
1480
|
+
// override; an override that moves nothing is an infrastructure error ("override not applied"),
|
|
1481
|
+
// never a pass. Only an applied override runs the C01 content-loss probe.
|
|
1482
|
+
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
1483
|
+
|
|
1484
|
+
/** The SC 1.4.12 values, unlayered and `!important`, so they sit above every `@layer`. */
|
|
1485
|
+
export const TEXT_SPACING_CSS =
|
|
1486
|
+
"*, *::before, *::after { line-height: 1.5 !important; letter-spacing: 0.12em !important; " +
|
|
1487
|
+
"word-spacing: 0.16em !important; } p { margin-block-end: 2em !important; }";
|
|
1488
|
+
|
|
1489
|
+
/** Parse `--text-zoom 200` (a percent, 110–400). Throws on garbage. */
|
|
1490
|
+
export function parseTextZoom(spec) {
|
|
1491
|
+
const n = Number(spec);
|
|
1492
|
+
if (!Number.isInteger(n) || n < 110 || n > 400)
|
|
1493
|
+
throw new Error(`--text-zoom: "${spec ?? ""}" is not a percent between 110 and 400`);
|
|
1494
|
+
return n;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
/**
|
|
1498
|
+
* Did the override reach the page? (pure). `before`/`after` are `snapshotText()` results of the
|
|
1499
|
+
* SAME document (same text-bearing elements, same order). Applied = the override's computed values
|
|
1500
|
+
* reached at least one text element AND the geometry moved; either half alone is not enough — a
|
|
1501
|
+
* computed value with zero geometry change is a render that was never re-laid out, and geometry
|
|
1502
|
+
* that moved without the values is something else moving.
|
|
1503
|
+
*/
|
|
1504
|
+
export function textOverrideVerdict(mode, before, after, { zoom = 200 } = {}) {
|
|
1505
|
+
const n = Math.min(before.texts.length, after.texts.length);
|
|
1506
|
+
let moved = 0;
|
|
1507
|
+
let maxShiftPx = 0;
|
|
1508
|
+
let took = 0;
|
|
1509
|
+
for (let i = 0; i < n; i++) {
|
|
1510
|
+
const a = before.texts[i];
|
|
1511
|
+
const b = after.texts[i];
|
|
1512
|
+
const shift = Math.max(
|
|
1513
|
+
Math.abs(a.x - b.x),
|
|
1514
|
+
Math.abs(a.y - b.y),
|
|
1515
|
+
Math.abs(a.w - b.w),
|
|
1516
|
+
Math.abs(a.h - b.h),
|
|
1517
|
+
);
|
|
1518
|
+
if (shift > 0.5) moved++;
|
|
1519
|
+
maxShiftPx = Math.max(maxShiftPx, shift);
|
|
1520
|
+
const ok =
|
|
1521
|
+
mode === "text-spacing"
|
|
1522
|
+
? Math.abs(b.letterSpacingPx - 0.12 * b.fontPx) <= 0.5 &&
|
|
1523
|
+
Math.abs(b.wordSpacingPx - 0.16 * b.fontPx) <= 0.5
|
|
1524
|
+
: Math.abs(b.fontPx - (a.fontPx * zoom) / 100) <= 0.5;
|
|
1525
|
+
if (ok) took++;
|
|
1526
|
+
}
|
|
1527
|
+
const delta = {
|
|
1528
|
+
textElements: n,
|
|
1529
|
+
movedTextElements: moved,
|
|
1530
|
+
maxShiftPx: +maxShiftPx.toFixed(1),
|
|
1531
|
+
documentHeightPx: +(after.docHeight - before.docHeight).toFixed(1),
|
|
1532
|
+
documentWidthPx: +(after.docWidth - before.docWidth).toFixed(1),
|
|
1533
|
+
};
|
|
1534
|
+
const appliedRatio = n ? +(took / n).toFixed(3) : 0;
|
|
1535
|
+
let reason = null;
|
|
1536
|
+
if (n === 0) reason = "no text to measure";
|
|
1537
|
+
else if (took === 0) reason = `the ${mode} values reached 0 of ${n} text elements`;
|
|
1538
|
+
else if (moved === 0 && delta.documentHeightPx === 0 && delta.documentWidthPx === 0)
|
|
1539
|
+
reason = "zero geometry delta against the unmodified render";
|
|
1540
|
+
return { applied: reason === null, reason, appliedRatio, delta };
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
/** Text-bearing elements' boxes + computed text metrics, in document order. Runs in the page. */
|
|
1544
|
+
/* c8 ignore start — runs inside the browser, exercised by the visual-audit-text-evidence test. */
|
|
1545
|
+
export function snapshotText() {
|
|
1546
|
+
const se = document.scrollingElement || document.documentElement;
|
|
1547
|
+
const owners = [];
|
|
1548
|
+
const seen = new Set();
|
|
1549
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
1550
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
1551
|
+
const p = n.parentElement;
|
|
1552
|
+
if (!n.data.trim() || !p || seen.has(p) || p.closest("script, style, noscript, template"))
|
|
1553
|
+
continue;
|
|
1554
|
+
seen.add(p);
|
|
1555
|
+
owners.push(p);
|
|
1556
|
+
}
|
|
1557
|
+
const texts = owners.map((el) => {
|
|
1558
|
+
const r = el.getBoundingClientRect();
|
|
1559
|
+
const s = getComputedStyle(el);
|
|
1560
|
+
const fontPx = parseFloat(s.fontSize) || 0;
|
|
1561
|
+
const px = (v) => (v === "normal" ? 0 : parseFloat(v) || 0);
|
|
1562
|
+
return {
|
|
1563
|
+
x: r.left + se.scrollLeft,
|
|
1564
|
+
y: r.top + se.scrollTop,
|
|
1565
|
+
w: r.width,
|
|
1566
|
+
h: r.height,
|
|
1567
|
+
fontPx,
|
|
1568
|
+
letterSpacingPx: px(s.letterSpacing),
|
|
1569
|
+
wordSpacingPx: px(s.wordSpacing),
|
|
1570
|
+
};
|
|
1571
|
+
});
|
|
1572
|
+
return { texts, docHeight: se.scrollHeight, docWidth: se.scrollWidth };
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
/** Append the SC 1.4.12 sheet LAST in <head>. A CSP without 'unsafe-inline' blocks it — measured. */
|
|
1576
|
+
export function injectTextSpacing(css) {
|
|
1577
|
+
const style = document.createElement("style");
|
|
1578
|
+
style.setAttribute("data-visual-audit", "text-spacing");
|
|
1579
|
+
style.textContent = css;
|
|
1580
|
+
document.head.appendChild(style);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Text-only zoom (SC 1.4.4): every element's computed font size × factor, as one appended sheet
|
|
1585
|
+
* keyed by the size each element had — px-sized text grows too, line boxes and widths stay
|
|
1586
|
+
* author-controlled, which is exactly what a browser's text-size setting does.
|
|
1587
|
+
*/
|
|
1588
|
+
export function injectTextZoom(percent) {
|
|
1589
|
+
const sizes = new Set();
|
|
1590
|
+
for (const el of document.querySelectorAll("body, body *")) {
|
|
1591
|
+
const px = parseFloat(getComputedStyle(el).fontSize);
|
|
1592
|
+
if (!px) continue;
|
|
1593
|
+
const key = String(Math.round(px * 100) / 100);
|
|
1594
|
+
el.setAttribute("data-visual-audit-fz", key);
|
|
1595
|
+
sizes.add(key);
|
|
1596
|
+
}
|
|
1597
|
+
const style = document.createElement("style");
|
|
1598
|
+
style.setAttribute("data-visual-audit", "text-zoom");
|
|
1599
|
+
style.textContent = [...sizes]
|
|
1600
|
+
.map(
|
|
1601
|
+
(k) =>
|
|
1602
|
+
`[data-visual-audit-fz="${k}"] { font-size: ${(Number(k) * percent) / 100}px !important; }`,
|
|
1603
|
+
)
|
|
1604
|
+
.join("\n");
|
|
1605
|
+
document.head.appendChild(style);
|
|
1606
|
+
}
|
|
1607
|
+
/* c8 ignore stop */
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* One evidence cell: reload the route clean, measure, inject, measure again, judge, and run C01
|
|
1611
|
+
* only when the override applied. Screenshots (base + override) when `screenshots` is a dir.
|
|
1612
|
+
*/
|
|
1613
|
+
/* c8 ignore start — browser glue; exercised by the visual-audit-text-evidence spawn test. */
|
|
1614
|
+
async function runEvidence(page, url, viewport, mode, { zoom, screenshots }) {
|
|
1615
|
+
const vp = viewportLabel(viewport);
|
|
1616
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
|
|
1617
|
+
await page.evaluate(() => document.fonts?.ready);
|
|
1618
|
+
const shot = async (suffix) => {
|
|
1619
|
+
if (!screenshots) return null;
|
|
1620
|
+
const slug = url
|
|
1621
|
+
.replace(/^https?:\/\/[^/]+/, "")
|
|
1622
|
+
.replace(/[^\w-]+/g, "_")
|
|
1623
|
+
.replace(/^_+|_+$/g, "");
|
|
1624
|
+
const file = path.join(screenshots, `${slug || "root"}-${vp}-${mode}-${suffix}.png`);
|
|
1625
|
+
await page.screenshot({ path: file, fullPage: true });
|
|
1626
|
+
return file;
|
|
1627
|
+
};
|
|
1628
|
+
const before = await page.evaluate(snapshotText);
|
|
1629
|
+
const base = await shot("base");
|
|
1630
|
+
if (mode === "text-spacing") await page.evaluate(injectTextSpacing, TEXT_SPACING_CSS);
|
|
1631
|
+
else await page.evaluate(injectTextZoom, zoom);
|
|
1632
|
+
await page.evaluate(
|
|
1633
|
+
() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))),
|
|
1634
|
+
);
|
|
1635
|
+
const after = await page.evaluate(snapshotText);
|
|
1636
|
+
const override = await shot("override");
|
|
1637
|
+
const verdict = textOverrideVerdict(mode, before, after, { zoom });
|
|
1638
|
+
const entry = {
|
|
1639
|
+
url,
|
|
1640
|
+
viewport: vp,
|
|
1641
|
+
mode,
|
|
1642
|
+
...(mode === "text-zoom" ? { zoomPercent: zoom } : null),
|
|
1643
|
+
applied: verdict.applied,
|
|
1644
|
+
appliedRatio: verdict.appliedRatio,
|
|
1645
|
+
delta: verdict.delta,
|
|
1646
|
+
c01: "not-run",
|
|
1647
|
+
screenshots: screenshots ? { base, override } : null,
|
|
1648
|
+
};
|
|
1649
|
+
if (!verdict.applied)
|
|
1650
|
+
return {
|
|
1651
|
+
entry,
|
|
1652
|
+
findings: [],
|
|
1653
|
+
error: { url, viewport: vp, stage: mode, message: `override not applied: ${verdict.reason}` },
|
|
1654
|
+
};
|
|
1655
|
+
const opts = { width: viewport.width, compactBelow: COMPACT_BELOW, desktopFrom: DESKTOP_FROM };
|
|
1656
|
+
const probed = evaluateProbes(await page.evaluate(collectProbes, opts), { url, viewport });
|
|
1657
|
+
const findings = probed.findings
|
|
1658
|
+
.filter((f) => f.rule === "c01-content-reachable")
|
|
1659
|
+
.map((f) => ({ ...f, condition: mode, message: `[${mode}] ${f.message}` }));
|
|
1660
|
+
entry.c01 = probed.cell.rules["c01-content-reachable"];
|
|
1661
|
+
return { entry, findings, error: null };
|
|
1662
|
+
}
|
|
1663
|
+
/* c8 ignore stop */
|
|
1664
|
+
|
|
1472
1665
|
/**
|
|
1473
1666
|
* Run the probes for one route × viewport, including the interactions a static read cannot
|
|
1474
1667
|
* answer: does the avatar open a menu, does the overflow menu hold actions, does a declared
|
|
@@ -1570,12 +1763,18 @@ async function loadDeps() {
|
|
|
1570
1763
|
* (page/context/browser) is guaranteed via finally on BOTH success and failure.
|
|
1571
1764
|
*/
|
|
1572
1765
|
/* c8 ignore start — the browser-driving glue; exercised by scripts/visual-audit-smoke.mjs in CI. */
|
|
1573
|
-
async function audit(
|
|
1766
|
+
async function audit(
|
|
1767
|
+
targets,
|
|
1768
|
+
{ chromium },
|
|
1769
|
+
viewports = DEFAULT_VIEWPORTS,
|
|
1770
|
+
{ storageState, modes = [], zoom = 200, screenshots } = {},
|
|
1771
|
+
) {
|
|
1574
1772
|
const findings = [];
|
|
1575
1773
|
const errors = [];
|
|
1576
1774
|
const matrix = [];
|
|
1577
1775
|
const unmapped = [];
|
|
1578
1776
|
const timings = [];
|
|
1777
|
+
const evidence = modes.length ? [] : undefined;
|
|
1579
1778
|
let browser;
|
|
1580
1779
|
let context;
|
|
1581
1780
|
try {
|
|
@@ -1714,6 +1913,22 @@ async function audit(targets, { chromium }, viewports = DEFAULT_VIEWPORTS, { sto
|
|
|
1714
1913
|
message: `audit engine failed: ${e.message}`,
|
|
1715
1914
|
});
|
|
1716
1915
|
}
|
|
1916
|
+
// 4) evidence modes (gh#1316) — each on a fresh load, after the base probes.
|
|
1917
|
+
for (const mode of modes) {
|
|
1918
|
+
try {
|
|
1919
|
+
const ev = await runEvidence(page, url, viewport, mode, { zoom, screenshots });
|
|
1920
|
+
evidence.push(ev.entry);
|
|
1921
|
+
findings.push(...ev.findings);
|
|
1922
|
+
if (ev.error) errors.push(ev.error);
|
|
1923
|
+
} catch (e) {
|
|
1924
|
+
errors.push({
|
|
1925
|
+
url,
|
|
1926
|
+
viewport: vp,
|
|
1927
|
+
stage: mode,
|
|
1928
|
+
message: `${mode} failed: ${e.message}`,
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1717
1932
|
}
|
|
1718
1933
|
timings.push({ url, viewports: viewports.length, ms: Date.now() - started });
|
|
1719
1934
|
}
|
|
@@ -1729,6 +1944,7 @@ async function audit(targets, { chromium }, viewports = DEFAULT_VIEWPORTS, { sto
|
|
|
1729
1944
|
viewports,
|
|
1730
1945
|
matrix,
|
|
1731
1946
|
unmapped,
|
|
1947
|
+
evidence,
|
|
1732
1948
|
});
|
|
1733
1949
|
result.timings = timings;
|
|
1734
1950
|
return result;
|
|
@@ -1790,6 +2006,12 @@ function finish(result, { asJson, strict, pages }) {
|
|
|
1790
2006
|
.join(" · ")} · checklist ${cell.checklist.length} pending${C.reset}`,
|
|
1791
2007
|
);
|
|
1792
2008
|
}
|
|
2009
|
+
for (const ev of result.evidence || []) {
|
|
2010
|
+
const d = ev.delta;
|
|
2011
|
+
console.log(
|
|
2012
|
+
`${ev.applied ? C.dim : C.red}${ev.mode} ${ev.url} @${ev.viewport}: ${ev.applied ? "applied" : "NOT APPLIED"} (values on ${Math.round(ev.appliedRatio * 100)}% of text) — ${d.movedTextElements}/${d.textElements} text elements moved, max ${d.maxShiftPx}px, document height ${d.documentHeightPx >= 0 ? "+" : ""}${d.documentHeightPx}px · C01 ${ev.c01}${ev.screenshots ? ` · ${ev.screenshots.override}` : ""}${C.reset}`,
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
1793
2015
|
for (const e of result.errors) {
|
|
1794
2016
|
console.log(
|
|
1795
2017
|
`${C.red}infra${C.reset} ${C.bold}${e.url ?? "(bootstrap)"}${C.reset} ${C.dim}[${e.stage}]${C.reset}`,
|
|
@@ -1852,16 +2074,38 @@ async function main() {
|
|
|
1852
2074
|
return;
|
|
1853
2075
|
}
|
|
1854
2076
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
)
|
|
2077
|
+
// Evidence modes (gh#1316): `--text-spacing` (SC 1.4.12), `--text-zoom <percent>` (SC 1.4.4),
|
|
2078
|
+
// `--screenshots <dir>` for the base + override render of each.
|
|
2079
|
+
const modes = [];
|
|
2080
|
+
if (args.includes("--text-spacing")) modes.push("text-spacing");
|
|
2081
|
+
const tzIdx = args.indexOf("--text-zoom");
|
|
2082
|
+
let zoom = 200;
|
|
2083
|
+
if (tzIdx !== -1) {
|
|
2084
|
+
try {
|
|
2085
|
+
zoom = parseTextZoom(args[tzIdx + 1]);
|
|
2086
|
+
} catch (e) {
|
|
2087
|
+
finish(buildBootstrapError(e.message), { asJson, strict, pages: 0 });
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
modes.push("text-zoom");
|
|
2091
|
+
}
|
|
2092
|
+
const shIdx = args.indexOf("--screenshots");
|
|
2093
|
+
const screenshots = shIdx === -1 ? undefined : args[shIdx + 1];
|
|
2094
|
+
if (shIdx !== -1) {
|
|
2095
|
+
try {
|
|
2096
|
+
if (!screenshots || screenshots.startsWith("--")) throw new Error("no directory given");
|
|
2097
|
+
mkdirSync(screenshots, { recursive: true });
|
|
2098
|
+
} catch (e) {
|
|
2099
|
+
finish(buildBootstrapError(`--screenshots: ${e.message}`), { asJson, strict, pages: 0 });
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
const VALUED = ["--format", "--widths", "--storage-state", "--text-zoom", "--screenshots"];
|
|
2105
|
+
const urls = args.filter((a, i) => !a.startsWith("--") && !VALUED.includes(args[i - 1]));
|
|
1862
2106
|
if (urls.length === 0) {
|
|
1863
2107
|
const msg =
|
|
1864
|
-
"no baseUrl given — usage: visual-audit.mjs [--format json] [--strict] [--widths 390x844,1280x900] [--storage-state state.json] <baseUrl> [route …] " +
|
|
2108
|
+
"no baseUrl given — usage: visual-audit.mjs [--format json] [--strict] [--widths 390x844,1280x900] [--storage-state state.json] [--text-spacing] [--text-zoom 200] [--screenshots DIR] <baseUrl> [route …] " +
|
|
1865
2109
|
"(baseUrl is the running app; extra args are routes appended to it)";
|
|
1866
2110
|
finish(buildBootstrapError(msg), { asJson, strict, pages: 0 });
|
|
1867
2111
|
return;
|
|
@@ -1882,7 +2126,12 @@ async function main() {
|
|
|
1882
2126
|
}
|
|
1883
2127
|
|
|
1884
2128
|
try {
|
|
1885
|
-
const result = await audit(targets, deps, viewports, {
|
|
2129
|
+
const result = await audit(targets, deps, viewports, {
|
|
2130
|
+
storageState,
|
|
2131
|
+
modes,
|
|
2132
|
+
zoom,
|
|
2133
|
+
screenshots: screenshots && path.resolve(screenshots),
|
|
2134
|
+
});
|
|
1886
2135
|
finish(result, { asJson, strict, pages: targets.length });
|
|
1887
2136
|
} catch (e) {
|
|
1888
2137
|
// A launch/setup failure = infrastructure error (never "zero violations").
|