@afixt/screenshot-utils 1.0.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 +36 -2
- package/dist/index.js +169 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +169 -14
- package/dist/index.mjs.map +1 -1
- package/dist/types/capture/element.d.ts.map +1 -1
- package/dist/types/capture/page.d.ts.map +1 -1
- package/dist/types/capture/settle.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/capture/element.js +45 -2
- package/src/capture/page.js +31 -10
- package/src/capture/settle.js +136 -1
- package/src/index.js +3 -0
- package/src/types/index.d.ts +10 -0
package/dist/index.mjs
CHANGED
|
@@ -97,7 +97,7 @@ var require_package = __commonJS({
|
|
|
97
97
|
access: "restricted"
|
|
98
98
|
},
|
|
99
99
|
engines: {
|
|
100
|
-
node: ">=20.
|
|
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",
|
|
@@ -961,7 +961,10 @@ var require_settle = __commonJS({
|
|
|
961
961
|
const {
|
|
962
962
|
fonts = true,
|
|
963
963
|
images = true,
|
|
964
|
+
backgroundImages = true,
|
|
964
965
|
animations = "reduce",
|
|
966
|
+
network = "idle",
|
|
967
|
+
layoutStableMs = 250,
|
|
965
968
|
maxWait = 5e3,
|
|
966
969
|
idleMs = 200
|
|
967
970
|
} = options;
|
|
@@ -969,7 +972,10 @@ var require_settle = __commonJS({
|
|
|
969
972
|
const report = await adapter.evaluate(runInPage, {
|
|
970
973
|
fonts,
|
|
971
974
|
images,
|
|
975
|
+
backgroundImages,
|
|
972
976
|
animations,
|
|
977
|
+
network,
|
|
978
|
+
layoutStableMs,
|
|
973
979
|
maxWait,
|
|
974
980
|
idleMs
|
|
975
981
|
});
|
|
@@ -981,6 +987,9 @@ var require_settle = __commonJS({
|
|
|
981
987
|
const report = {
|
|
982
988
|
fonts: "skipped",
|
|
983
989
|
images: { total: 0, loaded: 0, failed: 0 },
|
|
990
|
+
backgroundImages: "skipped",
|
|
991
|
+
network: "skipped",
|
|
992
|
+
layoutStable: "skipped",
|
|
984
993
|
animations: "skipped"
|
|
985
994
|
};
|
|
986
995
|
const withTimeout = (promise, ms, label) => Promise.race([
|
|
@@ -1035,6 +1044,103 @@ var require_settle = __commonJS({
|
|
|
1035
1044
|
report.images.total = imgs.length;
|
|
1036
1045
|
await Promise.all(imgs.map(settleOneImage));
|
|
1037
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
|
+
};
|
|
1038
1144
|
const settleAnimations = async () => {
|
|
1039
1145
|
if (opts.animations === "ignore" || !doc) {
|
|
1040
1146
|
return;
|
|
@@ -1082,9 +1188,12 @@ var require_settle = __commonJS({
|
|
|
1082
1188
|
setTimeout(resolve, opts.idleMs);
|
|
1083
1189
|
});
|
|
1084
1190
|
};
|
|
1191
|
+
await settleNetwork();
|
|
1085
1192
|
await settleFonts();
|
|
1086
1193
|
await settleImages();
|
|
1194
|
+
await settleBackgroundImages();
|
|
1087
1195
|
await settleAnimations();
|
|
1196
|
+
await settleLayout();
|
|
1088
1197
|
await settleIdle();
|
|
1089
1198
|
return report;
|
|
1090
1199
|
}
|
|
@@ -1115,20 +1224,12 @@ var require_page = __commonJS({
|
|
|
1115
1224
|
const start = nowMs();
|
|
1116
1225
|
emit(events, "capture:start", { format, fullPage });
|
|
1117
1226
|
let settleMs = 0;
|
|
1118
|
-
|
|
1227
|
+
let warnings = [];
|
|
1119
1228
|
if (settleOpts) {
|
|
1120
1229
|
const settleStart = nowMs();
|
|
1121
1230
|
const report = await waitForStable(adapter, settleOpts === true ? {} : settleOpts);
|
|
1122
1231
|
settleMs = nowMs() - settleStart;
|
|
1123
|
-
|
|
1124
|
-
warnings.push("fonts did not settle within maxWait");
|
|
1125
|
-
}
|
|
1126
|
-
if (report.animations === "timeout") {
|
|
1127
|
-
warnings.push("animations did not settle within maxWait");
|
|
1128
|
-
}
|
|
1129
|
-
if (report.images.failed > 0) {
|
|
1130
|
-
warnings.push(`${report.images.failed} image(s) failed to load`);
|
|
1131
|
-
}
|
|
1232
|
+
warnings = settleWarnings(report);
|
|
1132
1233
|
}
|
|
1133
1234
|
const metrics = await adapter.getMetrics();
|
|
1134
1235
|
const browserFormat = decideBrowserFormat(format);
|
|
@@ -1168,6 +1269,28 @@ var require_page = __commonJS({
|
|
|
1168
1269
|
});
|
|
1169
1270
|
return result;
|
|
1170
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
|
+
}
|
|
1171
1294
|
function decideBrowserFormat(format) {
|
|
1172
1295
|
return format === "webp" ? "png" : format;
|
|
1173
1296
|
}
|
|
@@ -1425,6 +1548,7 @@ var require_long_page = __commonJS({
|
|
|
1425
1548
|
var require_element = __commonJS({
|
|
1426
1549
|
"src/capture/element.js"(exports, module) {
|
|
1427
1550
|
var { CaptureTimeoutError } = require_errors();
|
|
1551
|
+
var { waitForStable } = require_settle();
|
|
1428
1552
|
async function captureElement(adapter, options = {}) {
|
|
1429
1553
|
const {
|
|
1430
1554
|
selector,
|
|
@@ -1436,6 +1560,7 @@ var require_element = __commonJS({
|
|
|
1436
1560
|
timeout = 5e3,
|
|
1437
1561
|
format = "png",
|
|
1438
1562
|
quality = 90,
|
|
1563
|
+
waitForStable: settleOpts = false,
|
|
1439
1564
|
events = null
|
|
1440
1565
|
} = options;
|
|
1441
1566
|
if (!selector && !xpath) {
|
|
@@ -1474,6 +1599,7 @@ var require_element = __commonJS({
|
|
|
1474
1599
|
}
|
|
1475
1600
|
}
|
|
1476
1601
|
let viewportRect = lookup.viewportRect;
|
|
1602
|
+
let documentRect = lookup.documentRect;
|
|
1477
1603
|
if (scrollIntoView) {
|
|
1478
1604
|
await adapter.scrollTo({
|
|
1479
1605
|
x: 0,
|
|
@@ -1488,6 +1614,12 @@ var require_element = __commonJS({
|
|
|
1488
1614
|
viewportRect = reread.viewportRect;
|
|
1489
1615
|
}
|
|
1490
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
|
+
}
|
|
1491
1623
|
const clip = {
|
|
1492
1624
|
x: Math.max(0, Math.round(viewportRect.x - padding)),
|
|
1493
1625
|
y: Math.max(0, Math.round(viewportRect.y - padding)),
|
|
@@ -1509,12 +1641,12 @@ var require_element = __commonJS({
|
|
|
1509
1641
|
format,
|
|
1510
1642
|
bytes,
|
|
1511
1643
|
page: metrics,
|
|
1512
|
-
timing: { settleMs
|
|
1644
|
+
timing: { settleMs, captureMs, encodeMs: 0, totalMs },
|
|
1513
1645
|
warnings: [],
|
|
1514
1646
|
element: {
|
|
1515
1647
|
selector: selector ?? "",
|
|
1516
1648
|
xpath: xpath ?? "",
|
|
1517
|
-
documentRect
|
|
1649
|
+
documentRect,
|
|
1518
1650
|
viewportRect,
|
|
1519
1651
|
verified: Boolean(snippet)
|
|
1520
1652
|
}
|
|
@@ -1522,6 +1654,26 @@ var require_element = __commonJS({
|
|
|
1522
1654
|
emit(events, "capture:complete", { format, totalMs, mode: "element" });
|
|
1523
1655
|
return result;
|
|
1524
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
|
+
}
|
|
1525
1677
|
function findElementInPage(args) {
|
|
1526
1678
|
const doc = globalThis.document;
|
|
1527
1679
|
let element;
|
|
@@ -2771,7 +2923,10 @@ var require_index = __commonJS({
|
|
|
2771
2923
|
waitForStable: Object.freeze({
|
|
2772
2924
|
fonts: true,
|
|
2773
2925
|
images: true,
|
|
2926
|
+
backgroundImages: true,
|
|
2774
2927
|
animations: "reduce",
|
|
2928
|
+
network: "idle",
|
|
2929
|
+
layoutStableMs: 250,
|
|
2775
2930
|
maxWait: 5e3,
|
|
2776
2931
|
idleMs: 200
|
|
2777
2932
|
})
|