@afixt/screenshot-utils 0.9.0 → 1.1.0
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/CHANGELOG.md +31 -0
- package/README.md +20 -20
- package/dist/index.js +172 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +172 -16
- package/dist/index.mjs.map +1 -1
- package/dist/types/adapters/cdp.d.ts +28 -0
- package/dist/types/adapters/cdp.d.ts.map +1 -0
- package/dist/types/adapters/index.d.ts +5 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/playwright.d.ts +28 -0
- package/dist/types/adapters/playwright.d.ts.map +1 -0
- package/dist/types/adapters/puppeteer.d.ts +20 -0
- package/dist/types/adapters/puppeteer.d.ts.map +1 -0
- package/dist/types/capture/element.d.ts +20 -0
- package/dist/types/capture/element.d.ts.map +1 -0
- package/dist/types/capture/index.d.ts +8 -0
- package/dist/types/capture/index.d.ts.map +1 -0
- package/dist/types/capture/long-page.d.ts +16 -0
- package/dist/types/capture/long-page.d.ts.map +1 -0
- package/dist/types/capture/page.d.ts +14 -0
- package/dist/types/capture/page.d.ts.map +1 -0
- package/dist/types/capture/responsive.d.ts +20 -0
- package/dist/types/capture/responsive.d.ts.map +1 -0
- package/dist/types/capture/settle.d.ts +14 -0
- package/dist/types/capture/settle.d.ts.map +1 -0
- package/dist/types/capture/viewport.d.ts +14 -0
- package/dist/types/capture/viewport.d.ts.map +1 -0
- package/dist/types/cli/index.d.ts +10 -0
- package/dist/types/cli/index.d.ts.map +1 -0
- package/dist/types/compose/annotate.d.ts +16 -0
- package/dist/types/compose/annotate.d.ts.map +1 -0
- package/dist/types/compose/diff.d.ts +15 -0
- package/dist/types/compose/diff.d.ts.map +1 -0
- package/dist/types/compose/heatmap.d.ts +17 -0
- package/dist/types/compose/heatmap.d.ts.map +1 -0
- package/dist/types/compose/index.d.ts +6 -0
- package/dist/types/compose/index.d.ts.map +1 -0
- package/dist/types/compose/theme.d.ts +17 -0
- package/dist/types/compose/theme.d.ts.map +1 -0
- package/dist/types/errors/adapter.d.ts +33 -0
- package/dist/types/errors/adapter.d.ts.map +1 -0
- package/dist/types/errors/base.d.ts +37 -0
- package/dist/types/errors/base.d.ts.map +1 -0
- package/dist/types/errors/capture.d.ts +109 -0
- package/dist/types/errors/capture.d.ts.map +1 -0
- package/dist/types/errors/compose.d.ts +35 -0
- package/dist/types/errors/compose.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +29 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/errors/optional.d.ts +23 -0
- package/dist/types/errors/optional.d.ts.map +1 -0
- package/dist/types/errors/redaction.d.ts +21 -0
- package/dist/types/errors/redaction.d.ts.map +1 -0
- package/dist/types/errors/tile.d.ts +29 -0
- package/dist/types/errors/tile.d.ts.map +1 -0
- package/dist/types/errors/transform.d.ts +35 -0
- package/dist/types/errors/transform.d.ts.map +1 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/redact/index.d.ts +5 -0
- package/dist/types/redact/index.d.ts.map +1 -0
- package/dist/types/redact/policy.d.ts +29 -0
- package/dist/types/redact/policy.d.ts.map +1 -0
- package/dist/types/redact/redact.d.ts +29 -0
- package/dist/types/redact/redact.d.ts.map +1 -0
- package/dist/types/tile/dzi.d.ts +18 -0
- package/dist/types/tile/dzi.d.ts.map +1 -0
- package/dist/types/tile/index.d.ts +3 -0
- package/dist/types/tile/index.d.ts.map +1 -0
- package/dist/types/transform/convert.d.ts +13 -0
- package/dist/types/transform/convert.d.ts.map +1 -0
- package/dist/types/transform/crop.d.ts +33 -0
- package/dist/types/transform/crop.d.ts.map +1 -0
- package/dist/types/transform/index.d.ts +7 -0
- package/dist/types/transform/index.d.ts.map +1 -0
- package/dist/types/transform/normalize.d.ts +14 -0
- package/dist/types/transform/normalize.d.ts.map +1 -0
- package/dist/types/transform/resize.d.ts +14 -0
- package/dist/types/transform/resize.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/capture/element.js +48 -6
- package/src/capture/page.js +31 -10
- package/src/capture/settle.js +139 -4
- package/src/index.js +3 -0
- package/src/types/index.d.ts +10 -0
package/dist/index.mjs
CHANGED
|
@@ -14,7 +14,7 @@ var require_package = __commonJS({
|
|
|
14
14
|
"package.json"(exports, module) {
|
|
15
15
|
module.exports = {
|
|
16
16
|
name: "@afixt/screenshot-utils",
|
|
17
|
-
version: "0.
|
|
17
|
+
version: "1.0.0",
|
|
18
18
|
description: "Screenshot capture, transform, compose, tile, and redact utilities for the AFixt/Revenant accessibility platform",
|
|
19
19
|
type: "commonjs",
|
|
20
20
|
main: "./dist/index.js",
|
|
@@ -97,7 +97,7 @@ var require_package = __commonJS({
|
|
|
97
97
|
access: "restricted"
|
|
98
98
|
},
|
|
99
99
|
engines: {
|
|
100
|
-
node: ">=
|
|
100
|
+
node: ">=20.9.0"
|
|
101
101
|
},
|
|
102
102
|
keywords: [
|
|
103
103
|
"screenshot",
|
|
@@ -115,7 +115,7 @@ var require_package = __commonJS({
|
|
|
115
115
|
"revenant"
|
|
116
116
|
],
|
|
117
117
|
dependencies: {
|
|
118
|
-
sharp: "0.
|
|
118
|
+
sharp: "^0.35.3"
|
|
119
119
|
},
|
|
120
120
|
peerDependencies: {
|
|
121
121
|
"@napi-rs/canvas": "^0.1.0",
|
|
@@ -146,7 +146,7 @@ var require_package = __commonJS({
|
|
|
146
146
|
"@commitlint/config-conventional": "20.5.3",
|
|
147
147
|
"@eslint/js": "10.0.1",
|
|
148
148
|
"@types/node": "22.19.19",
|
|
149
|
-
"@vitest/coverage-
|
|
149
|
+
"@vitest/coverage-istanbul": "4.1.5",
|
|
150
150
|
"clean-jsdoc-theme": "4.3.2",
|
|
151
151
|
eslint: "10.3.0",
|
|
152
152
|
"eslint-plugin-import-x": "4.16.2",
|
|
@@ -165,6 +165,7 @@ var require_package = __commonJS({
|
|
|
165
165
|
"lint-staged": "17.0.2",
|
|
166
166
|
"markdownlint-cli": "0.48.0",
|
|
167
167
|
"npm-run-all2": "8.0.4",
|
|
168
|
+
openseadragon: "6.0.2",
|
|
168
169
|
pixelmatch: "7.1.0",
|
|
169
170
|
playwright: "1.60.0",
|
|
170
171
|
pngjs: "7.0.0",
|
|
@@ -960,7 +961,10 @@ var require_settle = __commonJS({
|
|
|
960
961
|
const {
|
|
961
962
|
fonts = true,
|
|
962
963
|
images = true,
|
|
964
|
+
backgroundImages = true,
|
|
963
965
|
animations = "reduce",
|
|
966
|
+
network = "idle",
|
|
967
|
+
layoutStableMs = 250,
|
|
964
968
|
maxWait = 5e3,
|
|
965
969
|
idleMs = 200
|
|
966
970
|
} = options;
|
|
@@ -968,7 +972,10 @@ var require_settle = __commonJS({
|
|
|
968
972
|
const report = await adapter.evaluate(runInPage, {
|
|
969
973
|
fonts,
|
|
970
974
|
images,
|
|
975
|
+
backgroundImages,
|
|
971
976
|
animations,
|
|
977
|
+
network,
|
|
978
|
+
layoutStableMs,
|
|
972
979
|
maxWait,
|
|
973
980
|
idleMs
|
|
974
981
|
});
|
|
@@ -980,6 +987,9 @@ var require_settle = __commonJS({
|
|
|
980
987
|
const report = {
|
|
981
988
|
fonts: "skipped",
|
|
982
989
|
images: { total: 0, loaded: 0, failed: 0 },
|
|
990
|
+
backgroundImages: "skipped",
|
|
991
|
+
network: "skipped",
|
|
992
|
+
layoutStable: "skipped",
|
|
983
993
|
animations: "skipped"
|
|
984
994
|
};
|
|
985
995
|
const withTimeout = (promise, ms, label) => Promise.race([
|
|
@@ -1034,6 +1044,103 @@ var require_settle = __commonJS({
|
|
|
1034
1044
|
report.images.total = imgs.length;
|
|
1035
1045
|
await Promise.all(imgs.map(settleOneImage));
|
|
1036
1046
|
};
|
|
1047
|
+
const collectBackgroundUrls = () => {
|
|
1048
|
+
const CAP = 200;
|
|
1049
|
+
const urls = /* @__PURE__ */ new Set();
|
|
1050
|
+
const els = doc.querySelectorAll("*");
|
|
1051
|
+
for (let i = 0; i < els.length && urls.size < CAP; i += 1) {
|
|
1052
|
+
const bg = globalThis.getComputedStyle(els[i]).backgroundImage;
|
|
1053
|
+
if (!bg || bg === "none") {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
for (const match of bg.matchAll(/url\((['"]?)([^'")]+)\1\)/g)) {
|
|
1057
|
+
const url = match[2];
|
|
1058
|
+
if (url && !url.startsWith("data:")) {
|
|
1059
|
+
urls.add(url);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return Array.from(urls).slice(0, CAP);
|
|
1064
|
+
};
|
|
1065
|
+
const settleOneBackgroundUrl = (url) => new Promise((resolve) => {
|
|
1066
|
+
const img = new globalThis.Image();
|
|
1067
|
+
const onload = () => {
|
|
1068
|
+
report.backgroundImages.loaded += 1;
|
|
1069
|
+
resolve();
|
|
1070
|
+
};
|
|
1071
|
+
const onerror = () => {
|
|
1072
|
+
report.backgroundImages.failed += 1;
|
|
1073
|
+
resolve();
|
|
1074
|
+
};
|
|
1075
|
+
img.addEventListener("load", onload, { once: true });
|
|
1076
|
+
img.addEventListener("error", onerror, { once: true });
|
|
1077
|
+
img.src = url;
|
|
1078
|
+
});
|
|
1079
|
+
const settleBackgroundImages = async () => {
|
|
1080
|
+
if (!opts.backgroundImages || !doc) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const list = collectBackgroundUrls();
|
|
1084
|
+
report.backgroundImages = { total: list.length, loaded: 0, failed: 0 };
|
|
1085
|
+
if (list.length === 0) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
await withTimeout(
|
|
1089
|
+
Promise.all(list.map(settleOneBackgroundUrl)),
|
|
1090
|
+
opts.maxWait,
|
|
1091
|
+
"backgroundImages"
|
|
1092
|
+
).catch(() => {
|
|
1093
|
+
});
|
|
1094
|
+
};
|
|
1095
|
+
const settleNetwork = async () => {
|
|
1096
|
+
if (opts.network !== "idle" || !doc) {
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const perf = globalThis.performance;
|
|
1100
|
+
const countResources = () => perf && perf.getEntriesByType ? perf.getEntriesByType("resource").length : 0;
|
|
1101
|
+
const quietMs = 200;
|
|
1102
|
+
const deadline = Date.now() + opts.maxWait;
|
|
1103
|
+
let lastCount = countResources();
|
|
1104
|
+
let lastChange = Date.now();
|
|
1105
|
+
while (Date.now() < deadline) {
|
|
1106
|
+
const count = countResources();
|
|
1107
|
+
if (count !== lastCount) {
|
|
1108
|
+
lastCount = count;
|
|
1109
|
+
lastChange = Date.now();
|
|
1110
|
+
}
|
|
1111
|
+
if (doc.readyState === "complete" && Date.now() - lastChange >= quietMs) {
|
|
1112
|
+
report.network = "idle";
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
await new Promise((resolve) => {
|
|
1116
|
+
setTimeout(resolve, 50);
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
report.network = "timeout";
|
|
1120
|
+
};
|
|
1121
|
+
const settleLayout = async () => {
|
|
1122
|
+
if (!opts.layoutStableMs || opts.layoutStableMs <= 0 || !doc) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const el = doc.documentElement;
|
|
1126
|
+
const deadline = Date.now() + opts.maxWait;
|
|
1127
|
+
let lastHeight = el.scrollHeight;
|
|
1128
|
+
let stableSince = Date.now();
|
|
1129
|
+
while (Date.now() < deadline) {
|
|
1130
|
+
await new Promise((resolve) => {
|
|
1131
|
+
setTimeout(resolve, 50);
|
|
1132
|
+
});
|
|
1133
|
+
const height = el.scrollHeight;
|
|
1134
|
+
if (height !== lastHeight) {
|
|
1135
|
+
lastHeight = height;
|
|
1136
|
+
stableSince = Date.now();
|
|
1137
|
+
} else if (Date.now() - stableSince >= opts.layoutStableMs) {
|
|
1138
|
+
report.layoutStable = "stable";
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
report.layoutStable = "timeout";
|
|
1143
|
+
};
|
|
1037
1144
|
const settleAnimations = async () => {
|
|
1038
1145
|
if (opts.animations === "ignore" || !doc) {
|
|
1039
1146
|
return;
|
|
@@ -1081,9 +1188,12 @@ var require_settle = __commonJS({
|
|
|
1081
1188
|
setTimeout(resolve, opts.idleMs);
|
|
1082
1189
|
});
|
|
1083
1190
|
};
|
|
1191
|
+
await settleNetwork();
|
|
1084
1192
|
await settleFonts();
|
|
1085
1193
|
await settleImages();
|
|
1194
|
+
await settleBackgroundImages();
|
|
1086
1195
|
await settleAnimations();
|
|
1196
|
+
await settleLayout();
|
|
1087
1197
|
await settleIdle();
|
|
1088
1198
|
return report;
|
|
1089
1199
|
}
|
|
@@ -1114,20 +1224,12 @@ var require_page = __commonJS({
|
|
|
1114
1224
|
const start = nowMs();
|
|
1115
1225
|
emit(events, "capture:start", { format, fullPage });
|
|
1116
1226
|
let settleMs = 0;
|
|
1117
|
-
|
|
1227
|
+
let warnings = [];
|
|
1118
1228
|
if (settleOpts) {
|
|
1119
1229
|
const settleStart = nowMs();
|
|
1120
1230
|
const report = await waitForStable(adapter, settleOpts === true ? {} : settleOpts);
|
|
1121
1231
|
settleMs = nowMs() - settleStart;
|
|
1122
|
-
|
|
1123
|
-
warnings.push("fonts did not settle within maxWait");
|
|
1124
|
-
}
|
|
1125
|
-
if (report.animations === "timeout") {
|
|
1126
|
-
warnings.push("animations did not settle within maxWait");
|
|
1127
|
-
}
|
|
1128
|
-
if (report.images.failed > 0) {
|
|
1129
|
-
warnings.push(`${report.images.failed} image(s) failed to load`);
|
|
1130
|
-
}
|
|
1232
|
+
warnings = settleWarnings(report);
|
|
1131
1233
|
}
|
|
1132
1234
|
const metrics = await adapter.getMetrics();
|
|
1133
1235
|
const browserFormat = decideBrowserFormat(format);
|
|
@@ -1167,6 +1269,28 @@ var require_page = __commonJS({
|
|
|
1167
1269
|
});
|
|
1168
1270
|
return result;
|
|
1169
1271
|
}
|
|
1272
|
+
function settleWarnings(report) {
|
|
1273
|
+
const warnings = [];
|
|
1274
|
+
if (report.fonts === "timeout") {
|
|
1275
|
+
warnings.push("fonts did not settle within maxWait");
|
|
1276
|
+
}
|
|
1277
|
+
if (report.animations === "timeout") {
|
|
1278
|
+
warnings.push("animations did not settle within maxWait");
|
|
1279
|
+
}
|
|
1280
|
+
if (report.images.failed > 0) {
|
|
1281
|
+
warnings.push(`${report.images.failed} image(s) failed to load`);
|
|
1282
|
+
}
|
|
1283
|
+
if (report.backgroundImages && report.backgroundImages.failed > 0) {
|
|
1284
|
+
warnings.push(`${report.backgroundImages.failed} background image(s) failed to load`);
|
|
1285
|
+
}
|
|
1286
|
+
if (report.network === "timeout") {
|
|
1287
|
+
warnings.push("network did not reach idle within maxWait");
|
|
1288
|
+
}
|
|
1289
|
+
if (report.layoutStable === "timeout") {
|
|
1290
|
+
warnings.push("layout did not stabilize within maxWait");
|
|
1291
|
+
}
|
|
1292
|
+
return warnings;
|
|
1293
|
+
}
|
|
1170
1294
|
function decideBrowserFormat(format) {
|
|
1171
1295
|
return format === "webp" ? "png" : format;
|
|
1172
1296
|
}
|
|
@@ -1424,6 +1548,7 @@ var require_long_page = __commonJS({
|
|
|
1424
1548
|
var require_element = __commonJS({
|
|
1425
1549
|
"src/capture/element.js"(exports, module) {
|
|
1426
1550
|
var { CaptureTimeoutError } = require_errors();
|
|
1551
|
+
var { waitForStable } = require_settle();
|
|
1427
1552
|
async function captureElement(adapter, options = {}) {
|
|
1428
1553
|
const {
|
|
1429
1554
|
selector,
|
|
@@ -1435,6 +1560,7 @@ var require_element = __commonJS({
|
|
|
1435
1560
|
timeout = 5e3,
|
|
1436
1561
|
format = "png",
|
|
1437
1562
|
quality = 90,
|
|
1563
|
+
waitForStable: settleOpts = false,
|
|
1438
1564
|
events = null
|
|
1439
1565
|
} = options;
|
|
1440
1566
|
if (!selector && !xpath) {
|
|
@@ -1473,6 +1599,7 @@ var require_element = __commonJS({
|
|
|
1473
1599
|
}
|
|
1474
1600
|
}
|
|
1475
1601
|
let viewportRect = lookup.viewportRect;
|
|
1602
|
+
let documentRect = lookup.documentRect;
|
|
1476
1603
|
if (scrollIntoView) {
|
|
1477
1604
|
await adapter.scrollTo({
|
|
1478
1605
|
x: 0,
|
|
@@ -1487,6 +1614,12 @@ var require_element = __commonJS({
|
|
|
1487
1614
|
viewportRect = reread.viewportRect;
|
|
1488
1615
|
}
|
|
1489
1616
|
}
|
|
1617
|
+
const settled = await settleAndReread(adapter, { selector, xpath, timeout, settleOpts });
|
|
1618
|
+
const settleMs = settled.settleMs;
|
|
1619
|
+
if (settled.rect) {
|
|
1620
|
+
viewportRect = settled.rect.viewportRect;
|
|
1621
|
+
documentRect = settled.rect.documentRect;
|
|
1622
|
+
}
|
|
1490
1623
|
const clip = {
|
|
1491
1624
|
x: Math.max(0, Math.round(viewportRect.x - padding)),
|
|
1492
1625
|
y: Math.max(0, Math.round(viewportRect.y - padding)),
|
|
@@ -1508,12 +1641,12 @@ var require_element = __commonJS({
|
|
|
1508
1641
|
format,
|
|
1509
1642
|
bytes,
|
|
1510
1643
|
page: metrics,
|
|
1511
|
-
timing: { settleMs
|
|
1644
|
+
timing: { settleMs, captureMs, encodeMs: 0, totalMs },
|
|
1512
1645
|
warnings: [],
|
|
1513
1646
|
element: {
|
|
1514
1647
|
selector: selector ?? "",
|
|
1515
1648
|
xpath: xpath ?? "",
|
|
1516
|
-
documentRect
|
|
1649
|
+
documentRect,
|
|
1517
1650
|
viewportRect,
|
|
1518
1651
|
verified: Boolean(snippet)
|
|
1519
1652
|
}
|
|
@@ -1521,6 +1654,26 @@ var require_element = __commonJS({
|
|
|
1521
1654
|
emit(events, "capture:complete", { format, totalMs, mode: "element" });
|
|
1522
1655
|
return result;
|
|
1523
1656
|
}
|
|
1657
|
+
async function settleAndReread(adapter, { selector, xpath, timeout, settleOpts }) {
|
|
1658
|
+
if (!settleOpts) {
|
|
1659
|
+
return { settleMs: 0, rect: null };
|
|
1660
|
+
}
|
|
1661
|
+
const settleStart = nowMs();
|
|
1662
|
+
await waitForStable(adapter, settleOpts === true ? {} : settleOpts);
|
|
1663
|
+
const settleMs = nowMs() - settleStart;
|
|
1664
|
+
const reread = await withTimeout(
|
|
1665
|
+
adapter.evaluate(findElementInPage, { selector, xpath }),
|
|
1666
|
+
timeout,
|
|
1667
|
+
{ operation: "adapter.evaluate(findElement after settle)" }
|
|
1668
|
+
);
|
|
1669
|
+
if (reread && reread.found) {
|
|
1670
|
+
return {
|
|
1671
|
+
settleMs,
|
|
1672
|
+
rect: { viewportRect: reread.viewportRect, documentRect: reread.documentRect }
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return { settleMs, rect: null };
|
|
1676
|
+
}
|
|
1524
1677
|
function findElementInPage(args) {
|
|
1525
1678
|
const doc = globalThis.document;
|
|
1526
1679
|
let element;
|
|
@@ -2770,7 +2923,10 @@ var require_index = __commonJS({
|
|
|
2770
2923
|
waitForStable: Object.freeze({
|
|
2771
2924
|
fonts: true,
|
|
2772
2925
|
images: true,
|
|
2926
|
+
backgroundImages: true,
|
|
2773
2927
|
animations: "reduce",
|
|
2928
|
+
network: "idle",
|
|
2929
|
+
layoutStableMs: 250,
|
|
2774
2930
|
maxWait: 5e3,
|
|
2775
2931
|
idleMs: 200
|
|
2776
2932
|
})
|