@skrillex1224/playwright-toolkit 3.0.24 → 3.0.25
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/README.md +1 -1
- package/dist/index.cjs +34 -659
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +33 -658
- package/dist/index.js.map +3 -3
- package/index.d.ts +2 -1
- package/package.json +2 -8
- package/scripts/postinstall.js +40 -1
package/dist/index.cjs
CHANGED
|
@@ -495,7 +495,6 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
495
495
|
|
|
496
496
|
// src/internals/screenshot.js
|
|
497
497
|
var import_delay = __toESM(require("delay"), 1);
|
|
498
|
-
var import_jimp = require("jimp");
|
|
499
498
|
|
|
500
499
|
// src/internals/constants.js
|
|
501
500
|
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
@@ -534,23 +533,9 @@ var FORCED_FULLPAGE_TYPE = "jpeg";
|
|
|
534
533
|
var FORCED_FULLPAGE_QUALITY = 50;
|
|
535
534
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
536
535
|
var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
|
|
537
|
-
var STITCH_SCROLL_TARGET_ATTR = "data-pk-stitch-scroll-target";
|
|
538
536
|
var DEFAULT_MAX_HEIGHT = 8e3;
|
|
539
537
|
var DEFAULT_SETTLE_MS = 1e3;
|
|
540
538
|
var DEFAULT_MOBILE_SETTLE_MS = 50;
|
|
541
|
-
var DEFAULT_STITCH_SETTLE_MS = 120;
|
|
542
|
-
var DEFAULT_STITCH_OVERLAP_PX = 24;
|
|
543
|
-
var MIN_VIRTUALIZED_SCROLL_RATIO = 2.2;
|
|
544
|
-
var MIN_SPARSE_SCROLL_RATIO = 4;
|
|
545
|
-
var MIN_STITCH_VISIBLE_HEIGHT_PX = 120;
|
|
546
|
-
var MIN_STITCH_VISIBLE_HEIGHT_RATIO = 0.22;
|
|
547
|
-
var MIN_STITCH_OUTPUT_HEIGHT_RATIO = 0.35;
|
|
548
|
-
var MIN_STITCH_OUTPUT_VIEWPORT_RATIO = 1.15;
|
|
549
|
-
var MOBILE_VIEWPORT_WIDTH_THRESHOLD = 520;
|
|
550
|
-
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 2;
|
|
551
|
-
var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
|
|
552
|
-
var MIN_TRAILING_BLANK_GAP_PX = 320;
|
|
553
|
-
var MIN_TRAILING_BLANK_GAP_RATIO = 0.12;
|
|
554
539
|
var toPositiveNumber = (value, fallback = 0) => {
|
|
555
540
|
const n = Number(value);
|
|
556
541
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -573,22 +558,7 @@ var normalizeQuality = (value, type) => {
|
|
|
573
558
|
if (rounded < 0 || rounded > 100) return void 0;
|
|
574
559
|
return rounded;
|
|
575
560
|
};
|
|
576
|
-
var resolvePageDevice =
|
|
577
|
-
const declared = normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
578
|
-
if (declared === Device.Mobile) return Device.Mobile;
|
|
579
|
-
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
580
|
-
const viewportWidth = Math.round(Number(viewport?.width) || 0);
|
|
581
|
-
if (viewportWidth > 0 && viewportWidth <= MOBILE_VIEWPORT_WIDTH_THRESHOLD) {
|
|
582
|
-
return Device.Mobile;
|
|
583
|
-
}
|
|
584
|
-
return declared;
|
|
585
|
-
};
|
|
586
|
-
var resolveStitchMode = (options = {}) => {
|
|
587
|
-
const raw = options.stitch ?? options.stitching ?? options.strategy;
|
|
588
|
-
if (raw === false || raw === "off" || raw === "none" || raw === "single") return "off";
|
|
589
|
-
if (raw === true || raw === "force" || raw === "stitched") return "force";
|
|
590
|
-
return "auto";
|
|
591
|
-
};
|
|
561
|
+
var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
592
562
|
var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
|
|
593
563
|
const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
|
|
594
564
|
const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
|
|
@@ -935,156 +905,10 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
935
905
|
});
|
|
936
906
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
937
907
|
};
|
|
938
|
-
var measureMeaningfulContentBounds = async (page) => {
|
|
939
|
-
return await page.evaluate(() => {
|
|
940
|
-
const body = document.body;
|
|
941
|
-
if (!body) return null;
|
|
942
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || body.clientWidth || 0;
|
|
943
|
-
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || body.clientHeight || 0;
|
|
944
|
-
const scrollX = window.scrollX || window.pageXOffset || 0;
|
|
945
|
-
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
946
|
-
const bounds = {
|
|
947
|
-
left: Number.POSITIVE_INFINITY,
|
|
948
|
-
top: Number.POSITIVE_INFINITY,
|
|
949
|
-
right: 0,
|
|
950
|
-
bottom: 0,
|
|
951
|
-
nodes: 0
|
|
952
|
-
};
|
|
953
|
-
const isVisible = (el, style, rect) => {
|
|
954
|
-
if (!el || !style || !rect) return false;
|
|
955
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
956
|
-
if (Number(style.opacity) === 0) return false;
|
|
957
|
-
return rect.width > 1 && rect.height > 1;
|
|
958
|
-
};
|
|
959
|
-
const hasFixedAncestor = (el) => {
|
|
960
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
961
|
-
const position = String(window.getComputedStyle(node).position || "").toLowerCase();
|
|
962
|
-
if (position === "fixed") {
|
|
963
|
-
return true;
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
return false;
|
|
967
|
-
};
|
|
968
|
-
const parseRgbColor = (value) => {
|
|
969
|
-
const raw = String(value || "").trim();
|
|
970
|
-
const match = raw.match(/rgba?\(([^)]+)\)/i);
|
|
971
|
-
if (!match) return null;
|
|
972
|
-
const parts = match[1].replace(/\//g, " ").split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
|
|
973
|
-
if (parts.length < 3) return null;
|
|
974
|
-
const normalizeChannel = (part) => {
|
|
975
|
-
if (part.endsWith("%")) {
|
|
976
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part) * 2.55));
|
|
977
|
-
}
|
|
978
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part)));
|
|
979
|
-
};
|
|
980
|
-
const alpha = parts.length >= 4 ? parts[3].endsWith("%") ? Number.parseFloat(parts[3]) / 100 : Number.parseFloat(parts[3]) : 1;
|
|
981
|
-
return {
|
|
982
|
-
r: normalizeChannel(parts[0]),
|
|
983
|
-
g: normalizeChannel(parts[1]),
|
|
984
|
-
b: normalizeChannel(parts[2]),
|
|
985
|
-
a: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1
|
|
986
|
-
};
|
|
987
|
-
};
|
|
988
|
-
const colorLuminance = (color) => {
|
|
989
|
-
const channel = (value) => {
|
|
990
|
-
const ratio = Math.max(0, Math.min(255, value)) / 255;
|
|
991
|
-
return ratio <= 0.03928 ? ratio / 12.92 : ((ratio + 0.055) / 1.055) ** 2.4;
|
|
992
|
-
};
|
|
993
|
-
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
994
|
-
};
|
|
995
|
-
const contrastRatio = (a, b) => {
|
|
996
|
-
const l1 = colorLuminance(a);
|
|
997
|
-
const l2 = colorLuminance(b);
|
|
998
|
-
const lighter = Math.max(l1, l2);
|
|
999
|
-
const darker = Math.min(l1, l2);
|
|
1000
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
1001
|
-
};
|
|
1002
|
-
const resolveBackgroundColor = (el) => {
|
|
1003
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
1004
|
-
const background = parseRgbColor(window.getComputedStyle(node).backgroundColor);
|
|
1005
|
-
if (background && background.a > 0.2) {
|
|
1006
|
-
return background;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return { r: 255, g: 255, b: 255, a: 1 };
|
|
1010
|
-
};
|
|
1011
|
-
const isLowInformationTextPaint = (el, style) => {
|
|
1012
|
-
const color = parseRgbColor(style?.color);
|
|
1013
|
-
if (!color) return false;
|
|
1014
|
-
const opacity = Number(style.opacity);
|
|
1015
|
-
if (color.a <= 0.08 || Number.isFinite(opacity) && opacity <= 0.18) {
|
|
1016
|
-
return true;
|
|
1017
|
-
}
|
|
1018
|
-
const max = Math.max(color.r, color.g, color.b);
|
|
1019
|
-
const min = Math.min(color.r, color.g, color.b);
|
|
1020
|
-
if (!(min >= 224 && max - min <= 24 && color.a >= 0.85)) {
|
|
1021
|
-
return false;
|
|
1022
|
-
}
|
|
1023
|
-
const background = resolveBackgroundColor(el);
|
|
1024
|
-
return colorLuminance(background) > 0.76 && contrastRatio(color, background) < 1.35;
|
|
1025
|
-
};
|
|
1026
|
-
const addRect = (rect) => {
|
|
1027
|
-
if (!rect || rect.width <= 1 || rect.height <= 1) return;
|
|
1028
|
-
const left = Math.max(0, Math.floor(rect.left + scrollX));
|
|
1029
|
-
const top = Math.max(0, Math.floor(rect.top + scrollY));
|
|
1030
|
-
const right = Math.ceil(rect.right + scrollX);
|
|
1031
|
-
const bottom = Math.ceil(rect.bottom + scrollY);
|
|
1032
|
-
if (right <= left || bottom <= top) return;
|
|
1033
|
-
bounds.left = Math.min(bounds.left, left);
|
|
1034
|
-
bounds.top = Math.min(bounds.top, top);
|
|
1035
|
-
bounds.right = Math.max(bounds.right, right);
|
|
1036
|
-
bounds.bottom = Math.max(bounds.bottom, bottom);
|
|
1037
|
-
bounds.nodes += 1;
|
|
1038
|
-
};
|
|
1039
|
-
const addElement = (el, { minArea = 12, text = false } = {}) => {
|
|
1040
|
-
if (!el || el.nodeType !== 1 || hasFixedAncestor(el)) return;
|
|
1041
|
-
const style = window.getComputedStyle(el);
|
|
1042
|
-
if (text && isLowInformationTextPaint(el, style)) return;
|
|
1043
|
-
const rects = Array.from(el.getClientRects ? el.getClientRects() : []);
|
|
1044
|
-
rects.forEach((rect) => {
|
|
1045
|
-
if (!isVisible(el, style, rect)) return;
|
|
1046
|
-
if (rect.width * rect.height < minArea) return;
|
|
1047
|
-
addRect(rect);
|
|
1048
|
-
});
|
|
1049
|
-
};
|
|
1050
|
-
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
|
|
1051
|
-
let visitedTextNodes = 0;
|
|
1052
|
-
while (walker.nextNode() && visitedTextNodes < 6e3) {
|
|
1053
|
-
const node = walker.currentNode;
|
|
1054
|
-
visitedTextNodes += 1;
|
|
1055
|
-
const text = String(node.nodeValue || "").replace(/\s+/g, " ").trim();
|
|
1056
|
-
if (text.length < 2) continue;
|
|
1057
|
-
addElement(node.parentElement, { minArea: 8, text: true });
|
|
1058
|
-
}
|
|
1059
|
-
document.querySelectorAll("img,video,canvas,svg,picture").forEach((el) => {
|
|
1060
|
-
addElement(el, { minArea: 900 });
|
|
1061
|
-
});
|
|
1062
|
-
if (!bounds.nodes || !Number.isFinite(bounds.left)) return null;
|
|
1063
|
-
const paddingX = Math.max(16, Math.min(96, viewportWidth * 0.04));
|
|
1064
|
-
const paddingY = Math.max(24, Math.min(160, viewportHeight * 0.18));
|
|
1065
|
-
return {
|
|
1066
|
-
left: Math.max(0, Math.floor(bounds.left - paddingX)),
|
|
1067
|
-
top: Math.max(0, Math.floor(bounds.top - paddingY)),
|
|
1068
|
-
right: Math.ceil(bounds.right + paddingX),
|
|
1069
|
-
bottom: Math.ceil(bounds.bottom + paddingY),
|
|
1070
|
-
width: Math.max(1, Math.ceil(bounds.right - bounds.left)),
|
|
1071
|
-
height: Math.max(1, Math.ceil(bounds.bottom - bounds.top)),
|
|
1072
|
-
nodes: bounds.nodes,
|
|
1073
|
-
viewport: {
|
|
1074
|
-
width: Math.max(1, Math.ceil(viewportWidth || 1)),
|
|
1075
|
-
height: Math.max(1, Math.ceil(viewportHeight || 1))
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1078
|
-
}).catch((error) => {
|
|
1079
|
-
logger.warning(`\u622A\u56FE\u5185\u5BB9\u8FB9\u754C\u6D4B\u91CF\u5931\u8D25: ${error?.message || error}`);
|
|
1080
|
-
return null;
|
|
1081
|
-
});
|
|
1082
|
-
};
|
|
1083
908
|
var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1084
909
|
const originalViewport = await resolveCurrentViewportSize(page);
|
|
1085
910
|
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1086
|
-
const
|
|
1087
|
-
const preserveViewport = options.preserveViewport ?? device === Device.Mobile;
|
|
911
|
+
const preserveViewport = options.preserveViewport ?? resolvePageDevice(page) === Device.Mobile;
|
|
1088
912
|
const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
|
|
1089
913
|
const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
|
|
1090
914
|
const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
|
|
@@ -1093,21 +917,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1093
917
|
visibleOnly: options.visibleOnly !== false,
|
|
1094
918
|
expandDocumentElements: preserveViewport
|
|
1095
919
|
});
|
|
1096
|
-
const
|
|
1097
|
-
let targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1098
|
-
if (contentBounds?.bottom > 0) {
|
|
1099
|
-
const contentBottom = Math.min(Math.ceil(contentBounds.bottom), maxHeight);
|
|
1100
|
-
const trailingGap = targetHeight - contentBottom;
|
|
1101
|
-
const minTrailingGap = Math.max(
|
|
1102
|
-
MIN_TRAILING_BLANK_GAP_PX,
|
|
1103
|
-
Math.floor(targetHeight * MIN_TRAILING_BLANK_GAP_RATIO),
|
|
1104
|
-
Math.floor(originalViewport.height * 0.45)
|
|
1105
|
-
);
|
|
1106
|
-
if (trailingGap >= minTrailingGap && contentBottom >= originalViewport.height * 0.65) {
|
|
1107
|
-
targetHeight = Math.max(1, contentBottom);
|
|
1108
|
-
logger.info(`\u957F\u622A\u56FE\u88C1\u6389\u4F4E\u4FE1\u606F\u5C3E\u90E8: contentBottom=${contentBottom}, originalHeight=${Math.min(maxScrollHeight, maxHeight)}`);
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
920
|
+
const targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1111
921
|
let viewportResized = false;
|
|
1112
922
|
if (!preserveViewport) {
|
|
1113
923
|
await page.setViewportSize({
|
|
@@ -1127,7 +937,6 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1127
937
|
originalViewport,
|
|
1128
938
|
maxScrollHeight,
|
|
1129
939
|
targetHeight,
|
|
1130
|
-
contentBounds,
|
|
1131
940
|
preserveViewport,
|
|
1132
941
|
viewportResized
|
|
1133
942
|
};
|
|
@@ -1160,436 +969,6 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
1160
969
|
await page.setViewportSize(state2.originalViewport);
|
|
1161
970
|
}
|
|
1162
971
|
};
|
|
1163
|
-
var resolveVirtualizedScrollTarget = async (page, options = {}) => {
|
|
1164
|
-
const stitchMode = resolveStitchMode(options);
|
|
1165
|
-
if (stitchMode === "off") return null;
|
|
1166
|
-
const device = await resolvePageDevice(page);
|
|
1167
|
-
if (stitchMode !== "force" && device !== Device.Mobile) {
|
|
1168
|
-
return null;
|
|
1169
|
-
}
|
|
1170
|
-
return await page.evaluate((config) => {
|
|
1171
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
1172
|
-
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
1173
|
-
const root = document.documentElement;
|
|
1174
|
-
const body = document.body;
|
|
1175
|
-
const scrollingElement = document.scrollingElement;
|
|
1176
|
-
const candidates = [];
|
|
1177
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1178
|
-
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
1179
|
-
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
1180
|
-
const pushCandidate = (el2) => {
|
|
1181
|
-
if (!el2 || seen.has(el2)) return;
|
|
1182
|
-
seen.add(el2);
|
|
1183
|
-
candidates.push(el2);
|
|
1184
|
-
};
|
|
1185
|
-
pushCandidate(scrollingElement);
|
|
1186
|
-
pushCandidate(root);
|
|
1187
|
-
pushCandidate(body);
|
|
1188
|
-
document.querySelectorAll("*").forEach(pushCandidate);
|
|
1189
|
-
const isDocumentElement = (el2) => el2 === root || el2 === body || el2 === scrollingElement;
|
|
1190
|
-
const isVisible = (el2, style, rect) => {
|
|
1191
|
-
if (!el2 || !style || !rect) return false;
|
|
1192
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
1193
|
-
if (Number(style.opacity) === 0) return false;
|
|
1194
|
-
return rect.width > 0 && rect.height > 0;
|
|
1195
|
-
};
|
|
1196
|
-
const hasOverflowValue = (style, values) => {
|
|
1197
|
-
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
1198
|
-
const overflow = String(style?.overflow || "").toLowerCase();
|
|
1199
|
-
return values.has(overflowY) || values.has(overflow);
|
|
1200
|
-
};
|
|
1201
|
-
const looksScrollable = (el2, style) => {
|
|
1202
|
-
if (!el2 || el2.scrollHeight <= el2.clientHeight + 1) return false;
|
|
1203
|
-
if (isDocumentElement(el2)) return true;
|
|
1204
|
-
return hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
1205
|
-
};
|
|
1206
|
-
const textHeightFor = (el2) => {
|
|
1207
|
-
const nodes = isDocumentElement(el2) ? document.querySelectorAll("body *") : el2.querySelectorAll("*");
|
|
1208
|
-
let textHeight = 0;
|
|
1209
|
-
let textNodes = 0;
|
|
1210
|
-
const maxNodes = 3500;
|
|
1211
|
-
for (const node of nodes) {
|
|
1212
|
-
if (textNodes >= maxNodes) break;
|
|
1213
|
-
const text = String(node.textContent || "").replace(/\s+/g, " ").trim();
|
|
1214
|
-
if (text.length < 2) continue;
|
|
1215
|
-
let childHasText = false;
|
|
1216
|
-
for (const child of node.children || []) {
|
|
1217
|
-
if (String(child.textContent || "").replace(/\s+/g, " ").trim().length >= 2) {
|
|
1218
|
-
childHasText = true;
|
|
1219
|
-
break;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
if (childHasText) continue;
|
|
1223
|
-
const style = window.getComputedStyle(node);
|
|
1224
|
-
const rect = node.getBoundingClientRect();
|
|
1225
|
-
if (!isVisible(node, style, rect)) continue;
|
|
1226
|
-
textNodes += 1;
|
|
1227
|
-
textHeight += Math.min(Math.ceil(rect.height || 0), 900);
|
|
1228
|
-
}
|
|
1229
|
-
return { textHeight, textNodes };
|
|
1230
|
-
};
|
|
1231
|
-
const resolveEdgeOcclusion = () => {
|
|
1232
|
-
let top = 0;
|
|
1233
|
-
let bottom = 0;
|
|
1234
|
-
const maxHeight = Math.max(48, viewportHeight * 0.45);
|
|
1235
|
-
document.querySelectorAll("*").forEach((el2) => {
|
|
1236
|
-
const style = window.getComputedStyle(el2);
|
|
1237
|
-
const position = String(style.position || "").toLowerCase();
|
|
1238
|
-
if (position !== "fixed" && position !== "sticky") return;
|
|
1239
|
-
const rect = el2.getBoundingClientRect();
|
|
1240
|
-
if (!isVisible(el2, style, rect)) return;
|
|
1241
|
-
if (rect.height <= 0 || rect.height > maxHeight) return;
|
|
1242
|
-
if (rect.width < viewportWidth * 0.35) return;
|
|
1243
|
-
if (rect.top <= Math.max(16, viewportHeight * 0.08)) {
|
|
1244
|
-
top = Math.max(top, Math.ceil(rect.bottom));
|
|
1245
|
-
}
|
|
1246
|
-
if (rect.bottom >= viewportHeight - Math.max(16, viewportHeight * 0.08)) {
|
|
1247
|
-
bottom = Math.max(bottom, Math.ceil(viewportHeight - rect.top));
|
|
1248
|
-
}
|
|
1249
|
-
});
|
|
1250
|
-
return {
|
|
1251
|
-
top: Math.max(0, Math.min(Math.ceil(top), Math.floor(viewportHeight * 0.45))),
|
|
1252
|
-
bottom: Math.max(0, Math.min(Math.ceil(bottom), Math.floor(viewportHeight * 0.45)))
|
|
1253
|
-
};
|
|
1254
|
-
};
|
|
1255
|
-
let best = null;
|
|
1256
|
-
candidates.forEach((el2) => {
|
|
1257
|
-
const style = window.getComputedStyle(el2);
|
|
1258
|
-
const rect = isDocumentElement(el2) ? {
|
|
1259
|
-
top: 0,
|
|
1260
|
-
bottom: viewportHeight,
|
|
1261
|
-
left: 0,
|
|
1262
|
-
right: viewportWidth,
|
|
1263
|
-
width: viewportWidth,
|
|
1264
|
-
height: viewportHeight
|
|
1265
|
-
} : el2.getBoundingClientRect();
|
|
1266
|
-
if (!isDocumentElement(el2) && !isVisible(el2, style, rect)) return;
|
|
1267
|
-
if (!looksScrollable(el2, style)) return;
|
|
1268
|
-
const scrollHeight = Math.ceil(el2.scrollHeight || 0);
|
|
1269
|
-
const clientHeight = Math.ceil(el2.clientHeight || viewportHeight || 0);
|
|
1270
|
-
if (scrollHeight < Math.max(900, clientHeight * config.minScrollRatio)) return;
|
|
1271
|
-
const visibleTop = Math.max(0, Math.round(rect.top || 0));
|
|
1272
|
-
const visibleBottom = Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight));
|
|
1273
|
-
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
1274
|
-
const minVisibleHeight = Math.max(
|
|
1275
|
-
config.minVisibleHeightPx,
|
|
1276
|
-
Math.floor(viewportHeight * config.minVisibleHeightRatio)
|
|
1277
|
-
);
|
|
1278
|
-
if (!config.force && !isDocumentElement(el2) && visibleHeight < minVisibleHeight) return;
|
|
1279
|
-
const { textHeight, textNodes } = textHeightFor(el2);
|
|
1280
|
-
const sparseRatio = scrollHeight / Math.max(1, textHeight);
|
|
1281
|
-
const sparse = config.force || textNodes > 0 && sparseRatio >= config.minSparseRatio && scrollHeight - textHeight > clientHeight;
|
|
1282
|
-
if (!sparse) return;
|
|
1283
|
-
const visibleWidth = Math.max(1, Math.ceil(rect.width || viewportWidth || 1));
|
|
1284
|
-
const score2 = scrollHeight * Math.min(visibleWidth, viewportWidth || visibleWidth) * Math.min(1, visibleHeight / Math.max(1, viewportHeight));
|
|
1285
|
-
if (!best || score2 > best.score) {
|
|
1286
|
-
best = {
|
|
1287
|
-
el: el2,
|
|
1288
|
-
score: score2,
|
|
1289
|
-
kind: isDocumentElement(el2) ? "document" : "element",
|
|
1290
|
-
scrollHeight,
|
|
1291
|
-
clientHeight,
|
|
1292
|
-
scrollTop: Math.max(0, Math.round(el2.scrollTop || 0)),
|
|
1293
|
-
rect: {
|
|
1294
|
-
top: Math.max(0, Math.round(rect.top || 0)),
|
|
1295
|
-
bottom: Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight)),
|
|
1296
|
-
left: Math.max(0, Math.round(rect.left || 0)),
|
|
1297
|
-
width: Math.max(1, Math.round(rect.width || viewportWidth || 1)),
|
|
1298
|
-
height: Math.max(1, Math.round(rect.height || viewportHeight || 1))
|
|
1299
|
-
},
|
|
1300
|
-
visibleHeight,
|
|
1301
|
-
textHeight,
|
|
1302
|
-
textNodes,
|
|
1303
|
-
sparseRatio
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
});
|
|
1307
|
-
if (!best) return null;
|
|
1308
|
-
document.querySelectorAll(`[${config.attrName}="1"]`).forEach((node) => {
|
|
1309
|
-
node.removeAttribute(config.attrName);
|
|
1310
|
-
});
|
|
1311
|
-
if (best.kind === "element") {
|
|
1312
|
-
best.el.setAttribute(config.attrName, "1");
|
|
1313
|
-
}
|
|
1314
|
-
const { el, score, ...target } = best;
|
|
1315
|
-
return {
|
|
1316
|
-
...target,
|
|
1317
|
-
viewport: {
|
|
1318
|
-
width: Math.max(1, Math.ceil(viewportWidth || best.rect.width || 1)),
|
|
1319
|
-
height: Math.max(1, Math.ceil(viewportHeight || best.rect.height || 1))
|
|
1320
|
-
},
|
|
1321
|
-
edgeOcclusion: resolveEdgeOcclusion()
|
|
1322
|
-
};
|
|
1323
|
-
}, {
|
|
1324
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1325
|
-
force: stitchMode === "force",
|
|
1326
|
-
minScrollRatio: MIN_VIRTUALIZED_SCROLL_RATIO,
|
|
1327
|
-
minSparseRatio: MIN_SPARSE_SCROLL_RATIO,
|
|
1328
|
-
minVisibleHeightPx: MIN_STITCH_VISIBLE_HEIGHT_PX,
|
|
1329
|
-
minVisibleHeightRatio: MIN_STITCH_VISIBLE_HEIGHT_RATIO
|
|
1330
|
-
}).catch((error) => {
|
|
1331
|
-
logger.warning(`\u865A\u62DF\u6EDA\u52A8\u622A\u56FE\u63A2\u6D4B\u5931\u8D25: ${error?.message || error}`);
|
|
1332
|
-
return null;
|
|
1333
|
-
});
|
|
1334
|
-
};
|
|
1335
|
-
var setStitchScrollTop = async (page, target, scrollTop) => {
|
|
1336
|
-
await page.evaluate(({ attrName, kind, top }) => {
|
|
1337
|
-
const el = kind === "document" ? document.scrollingElement || document.documentElement || document.body : document.querySelector(`[${attrName}="1"]`);
|
|
1338
|
-
if (!el) return;
|
|
1339
|
-
el.scrollTop = Math.max(0, Math.round(Number(top) || 0));
|
|
1340
|
-
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1341
|
-
window.dispatchEvent(new Event("scroll"));
|
|
1342
|
-
}, {
|
|
1343
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1344
|
-
kind: target.kind,
|
|
1345
|
-
top: scrollTop
|
|
1346
|
-
});
|
|
1347
|
-
};
|
|
1348
|
-
var cleanupStitchTarget = async (page) => {
|
|
1349
|
-
await page.evaluate((attrName) => {
|
|
1350
|
-
document.querySelectorAll(`[${attrName}="1"]`).forEach((node) => {
|
|
1351
|
-
node.removeAttribute(attrName);
|
|
1352
|
-
});
|
|
1353
|
-
}, STITCH_SCROLL_TARGET_ATTR).catch(() => {
|
|
1354
|
-
});
|
|
1355
|
-
};
|
|
1356
|
-
var cropImage = (image, crop) => image.clone().crop({
|
|
1357
|
-
x: Math.max(0, Math.round(crop.x || 0)),
|
|
1358
|
-
y: Math.max(0, Math.round(crop.y || 0)),
|
|
1359
|
-
w: Math.max(1, Math.round(crop.w || 1)),
|
|
1360
|
-
h: Math.max(1, Math.round(crop.h || 1))
|
|
1361
|
-
});
|
|
1362
|
-
var captureStitchedScrollableScreenshot = async (page, target, options = {}) => {
|
|
1363
|
-
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1364
|
-
const settleMs = Math.max(0, Number(options.stitchSettleMs ?? DEFAULT_STITCH_SETTLE_MS) || 0);
|
|
1365
|
-
const overlapPx = Math.max(0, Math.round(Number(options.stitchOverlapPx ?? DEFAULT_STITCH_OVERLAP_PX) || 0));
|
|
1366
|
-
const viewport = target.viewport || await resolveCurrentViewportSize(page);
|
|
1367
|
-
const rect = target.rect || {
|
|
1368
|
-
top: 0,
|
|
1369
|
-
bottom: viewport.height,
|
|
1370
|
-
left: 0,
|
|
1371
|
-
width: viewport.width,
|
|
1372
|
-
height: viewport.height
|
|
1373
|
-
};
|
|
1374
|
-
const edge = target.edgeOcclusion || { top: 0, bottom: 0 };
|
|
1375
|
-
const topCrop = Math.max(0, Math.min(rect.top, viewport.height - 1));
|
|
1376
|
-
const topOverlay = Math.max(topCrop, Math.min(edge.top || 0, viewport.height - 1));
|
|
1377
|
-
const bottomOverlay = Math.max(0, Math.min(edge.bottom || 0, viewport.height - topOverlay - 1));
|
|
1378
|
-
const middleCropY = Math.max(topCrop, topOverlay);
|
|
1379
|
-
const middleCropBottom = Math.max(
|
|
1380
|
-
middleCropY + 1,
|
|
1381
|
-
Math.min(rect.bottom || viewport.height, viewport.height - bottomOverlay)
|
|
1382
|
-
);
|
|
1383
|
-
const middleCropHeight = Math.max(1, middleCropBottom - middleCropY);
|
|
1384
|
-
const scrollStep = Math.max(120, middleCropHeight - overlapPx);
|
|
1385
|
-
const maxScrollTop = Math.max(0, Math.ceil(target.scrollHeight - target.clientHeight));
|
|
1386
|
-
const positions = [];
|
|
1387
|
-
for (let top = 0; top < maxScrollTop; top += scrollStep) {
|
|
1388
|
-
positions.push(Math.round(top));
|
|
1389
|
-
}
|
|
1390
|
-
if (!positions.includes(maxScrollTop)) {
|
|
1391
|
-
positions.push(maxScrollTop);
|
|
1392
|
-
}
|
|
1393
|
-
if (positions.length === 0) {
|
|
1394
|
-
positions.push(0);
|
|
1395
|
-
}
|
|
1396
|
-
const segments = [];
|
|
1397
|
-
let totalHeight = 0;
|
|
1398
|
-
let outputWidth = Math.max(1, Math.round(viewport.width || rect.width || 1));
|
|
1399
|
-
try {
|
|
1400
|
-
for (let index = 0; index < positions.length && totalHeight < maxHeight; index += 1) {
|
|
1401
|
-
const isFirst = index === 0;
|
|
1402
|
-
const isLast = index === positions.length - 1;
|
|
1403
|
-
await setStitchScrollTop(page, target, positions[index]);
|
|
1404
|
-
if (settleMs > 0) {
|
|
1405
|
-
await (0, import_delay.default)(settleMs);
|
|
1406
|
-
}
|
|
1407
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1408
|
-
type: options.type || "png",
|
|
1409
|
-
quality: options.quality,
|
|
1410
|
-
timeout: options.timeout
|
|
1411
|
-
});
|
|
1412
|
-
const image = await import_jimp.Jimp.read(buffer);
|
|
1413
|
-
outputWidth = Math.min(outputWidth, image.bitmap.width);
|
|
1414
|
-
const cropY = isFirst ? 0 : middleCropY;
|
|
1415
|
-
const cropBottom = isLast ? image.bitmap.height : middleCropBottom;
|
|
1416
|
-
let cropHeight = Math.max(1, Math.min(image.bitmap.height, cropBottom) - cropY);
|
|
1417
|
-
const remaining = maxHeight - totalHeight;
|
|
1418
|
-
if (cropHeight > remaining) {
|
|
1419
|
-
cropHeight = remaining;
|
|
1420
|
-
}
|
|
1421
|
-
segments.push(cropImage(image, {
|
|
1422
|
-
x: 0,
|
|
1423
|
-
y: cropY,
|
|
1424
|
-
w: outputWidth,
|
|
1425
|
-
h: cropHeight
|
|
1426
|
-
}));
|
|
1427
|
-
totalHeight += cropHeight;
|
|
1428
|
-
}
|
|
1429
|
-
const canvas = new import_jimp.Jimp({
|
|
1430
|
-
width: outputWidth,
|
|
1431
|
-
height: Math.max(1, totalHeight),
|
|
1432
|
-
color: 4294967295
|
|
1433
|
-
});
|
|
1434
|
-
let y = 0;
|
|
1435
|
-
for (const segment of segments) {
|
|
1436
|
-
canvas.composite(segment, 0, y);
|
|
1437
|
-
y += segment.bitmap.height;
|
|
1438
|
-
}
|
|
1439
|
-
logger.info(
|
|
1440
|
-
`\u865A\u62DF\u6EDA\u52A8\u5206\u6BB5\u622A\u56FE: segments=${segments.length}, height=${totalHeight}, scrollHeight=${target.scrollHeight}, textNodes=${target.textNodes}, sparseRatio=${Number(target.sparseRatio || 0).toFixed(2)}`
|
|
1441
|
-
);
|
|
1442
|
-
const expectedHeight = Math.min(maxHeight, Math.max(target.scrollHeight || 0, viewport.height || 0));
|
|
1443
|
-
const minPlausibleHeight = Math.max(
|
|
1444
|
-
Math.floor((viewport.height || rect.height || 0) * MIN_STITCH_OUTPUT_VIEWPORT_RATIO),
|
|
1445
|
-
Math.floor(expectedHeight * MIN_STITCH_OUTPUT_HEIGHT_RATIO)
|
|
1446
|
-
);
|
|
1447
|
-
if (positions.length > 1 && expectedHeight > (viewport.height || rect.height || 0) * 1.3 && totalHeight < minPlausibleHeight) {
|
|
1448
|
-
logger.warning(
|
|
1449
|
-
`\u865A\u62DF\u6EDA\u52A8\u5206\u6BB5\u622A\u56FE\u7ED3\u679C\u8FC7\u77ED\uFF0C\u964D\u7EA7\u666E\u901A\u957F\u622A\u56FE: segments=${segments.length}, height=${totalHeight}, expectedMin=${minPlausibleHeight}, scrollHeight=${target.scrollHeight}, rect=${rect.width}x${rect.height}@${rect.top}`
|
|
1450
|
-
);
|
|
1451
|
-
return null;
|
|
1452
|
-
}
|
|
1453
|
-
return await canvas.getBuffer(import_jimp.JimpMime.png);
|
|
1454
|
-
} finally {
|
|
1455
|
-
await setStitchScrollTop(page, target, target.scrollTop || 0).catch(() => {
|
|
1456
|
-
});
|
|
1457
|
-
await cleanupStitchTarget(page);
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
var isLightBlankPixel = ({ r, g, b }) => {
|
|
1461
|
-
const max = Math.max(r, g, b);
|
|
1462
|
-
const min = Math.min(r, g, b);
|
|
1463
|
-
return max >= 238 && max - min <= 18;
|
|
1464
|
-
};
|
|
1465
|
-
var isDarkBlankPixel = ({ r, g, b }) => {
|
|
1466
|
-
const max = Math.max(r, g, b);
|
|
1467
|
-
const min = Math.min(r, g, b);
|
|
1468
|
-
return max <= 34 && max - min <= 10;
|
|
1469
|
-
};
|
|
1470
|
-
var isLowInfoPixel = ({ r, g, b }) => {
|
|
1471
|
-
const max = Math.max(r, g, b);
|
|
1472
|
-
const min = Math.min(r, g, b);
|
|
1473
|
-
return max - min <= 12;
|
|
1474
|
-
};
|
|
1475
|
-
var analyzeScreenshotBuffer = async (buffer) => {
|
|
1476
|
-
const image = await import_jimp.Jimp.read(buffer);
|
|
1477
|
-
const width = image.bitmap.width;
|
|
1478
|
-
const height = image.bitmap.height;
|
|
1479
|
-
const stepY = Math.max(1, Math.floor(height / 900));
|
|
1480
|
-
const stepX = Math.max(1, Math.floor(width / 420));
|
|
1481
|
-
const rows = [];
|
|
1482
|
-
let lightRows = 0;
|
|
1483
|
-
let darkRows = 0;
|
|
1484
|
-
let lowInfoRows = 0;
|
|
1485
|
-
let loadingLikeRows = 0;
|
|
1486
|
-
const rowFlags = [];
|
|
1487
|
-
for (let y = 0; y < height; y += stepY) {
|
|
1488
|
-
let light = 0;
|
|
1489
|
-
let dark = 0;
|
|
1490
|
-
let lowInfo = 0;
|
|
1491
|
-
let edge = 0;
|
|
1492
|
-
let count = 0;
|
|
1493
|
-
let prev = null;
|
|
1494
|
-
for (let x = 0; x < width; x += stepX) {
|
|
1495
|
-
const rgba = (0, import_jimp.intToRGBA)(image.getPixelColor(x, y));
|
|
1496
|
-
count += 1;
|
|
1497
|
-
if (isLightBlankPixel(rgba)) light += 1;
|
|
1498
|
-
if (isDarkBlankPixel(rgba)) dark += 1;
|
|
1499
|
-
if (isLowInfoPixel(rgba)) lowInfo += 1;
|
|
1500
|
-
if (prev) {
|
|
1501
|
-
const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
|
|
1502
|
-
if (diff > 45) edge += 1;
|
|
1503
|
-
}
|
|
1504
|
-
prev = rgba;
|
|
1505
|
-
}
|
|
1506
|
-
const stat = {
|
|
1507
|
-
y,
|
|
1508
|
-
lightRatio: light / Math.max(1, count),
|
|
1509
|
-
darkRatio: dark / Math.max(1, count),
|
|
1510
|
-
lowInfoRatio: lowInfo / Math.max(1, count),
|
|
1511
|
-
edgeRatio: edge / Math.max(1, count - 1)
|
|
1512
|
-
};
|
|
1513
|
-
rows.push(stat);
|
|
1514
|
-
const lightBlank = stat.lightRatio >= 0.965 && stat.edgeRatio <= 0.015;
|
|
1515
|
-
const darkBlank = stat.darkRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1516
|
-
const lowInfoBlank = stat.lowInfoRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1517
|
-
const loadingLike = stat.darkRatio >= 0.88 && stat.edgeRatio <= 0.025;
|
|
1518
|
-
if (lightBlank) lightRows += 1;
|
|
1519
|
-
if (darkBlank) darkRows += 1;
|
|
1520
|
-
if (lowInfoBlank) lowInfoRows += 1;
|
|
1521
|
-
if (loadingLike) loadingLikeRows += 1;
|
|
1522
|
-
rowFlags.push({ lightBlank, darkBlank, lowInfoBlank, loadingLike });
|
|
1523
|
-
}
|
|
1524
|
-
const sampledRows = Math.max(1, rows.length);
|
|
1525
|
-
return {
|
|
1526
|
-
image,
|
|
1527
|
-
width,
|
|
1528
|
-
height,
|
|
1529
|
-
lightBlankRowsRatio: lightRows / sampledRows,
|
|
1530
|
-
darkBlankRowsRatio: darkRows / sampledRows,
|
|
1531
|
-
lowInfoRowsRatio: lowInfoRows / sampledRows,
|
|
1532
|
-
loadingLikeRowsRatio: loadingLikeRows / sampledRows,
|
|
1533
|
-
rowFlags
|
|
1534
|
-
};
|
|
1535
|
-
};
|
|
1536
|
-
var resolveScreenshotQualityIssue = (analysis) => {
|
|
1537
|
-
if (!analysis) return null;
|
|
1538
|
-
if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
|
|
1539
|
-
return "loading-like-dark-screenshot";
|
|
1540
|
-
}
|
|
1541
|
-
return null;
|
|
1542
|
-
};
|
|
1543
|
-
var cropBufferToContentBounds = async (buffer, bounds, options = {}) => {
|
|
1544
|
-
if (!bounds || bounds.nodes <= 0) return buffer;
|
|
1545
|
-
const image = options.image || await import_jimp.Jimp.read(buffer);
|
|
1546
|
-
const width = image.bitmap.width;
|
|
1547
|
-
const height = image.bitmap.height;
|
|
1548
|
-
const safeLeft = Math.max(0, Math.min(width - 1, Math.floor(bounds.left || 0)));
|
|
1549
|
-
const safeRight = Math.max(safeLeft + 1, Math.min(width, Math.ceil(bounds.right || width)));
|
|
1550
|
-
const safeTop = Math.max(0, Math.min(height - 1, Math.floor(bounds.top || 0)));
|
|
1551
|
-
const safeBottom = Math.max(safeTop + 1, Math.min(height, Math.ceil(bounds.bottom || height)));
|
|
1552
|
-
const cropW = safeRight - safeLeft;
|
|
1553
|
-
const cropH = safeBottom - safeTop;
|
|
1554
|
-
const shouldCropX = cropW > 80 && cropW <= width * 0.82 && (safeLeft > width * 0.04 || width - safeRight > width * 0.04);
|
|
1555
|
-
const shouldCropY = cropH > 160 && cropH <= height * 0.88 && height - safeBottom > Math.max(320, height * 0.1);
|
|
1556
|
-
if (!shouldCropX && !shouldCropY) {
|
|
1557
|
-
return buffer;
|
|
1558
|
-
}
|
|
1559
|
-
const x = shouldCropX ? safeLeft : 0;
|
|
1560
|
-
const y = shouldCropY ? safeTop : 0;
|
|
1561
|
-
const w = shouldCropX ? cropW : width;
|
|
1562
|
-
const h = shouldCropY ? cropH : height;
|
|
1563
|
-
logger.info(`\u5185\u5BB9\u611F\u77E5\u88C1\u526A\u622A\u56FE: x=${x}, y=${y}, w=${w}, h=${h}, original=${width}x${height}`);
|
|
1564
|
-
return await cropImage(image, { x, y, w, h }).getBuffer(import_jimp.JimpMime.png);
|
|
1565
|
-
};
|
|
1566
|
-
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1567
|
-
const stitchedTarget = await resolveVirtualizedScrollTarget(page, options);
|
|
1568
|
-
if (stitchedTarget) {
|
|
1569
|
-
const buffer = await captureStitchedScrollableScreenshot(page, stitchedTarget, options);
|
|
1570
|
-
if (buffer) {
|
|
1571
|
-
return buffer;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1575
|
-
try {
|
|
1576
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1577
|
-
fullPage: true,
|
|
1578
|
-
type: options.type || "png",
|
|
1579
|
-
quality: options.quality,
|
|
1580
|
-
timeout: options.timeout,
|
|
1581
|
-
maxClipHeight: state2.targetHeight
|
|
1582
|
-
});
|
|
1583
|
-
return await cropBufferToContentBounds(buffer, state2.contentBounds);
|
|
1584
|
-
} finally {
|
|
1585
|
-
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1586
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1587
|
-
});
|
|
1588
|
-
if (options.restore) {
|
|
1589
|
-
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
};
|
|
1593
972
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
1594
973
|
const fullPage = Boolean(options.fullPage);
|
|
1595
974
|
const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
|
|
@@ -1644,35 +1023,23 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
1644
1023
|
}
|
|
1645
1024
|
};
|
|
1646
1025
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
|
|
1656
|
-
return null;
|
|
1026
|
+
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1027
|
+
try {
|
|
1028
|
+
return await capturePageScreenshot(page, {
|
|
1029
|
+
fullPage: true,
|
|
1030
|
+
type: options.type || "png",
|
|
1031
|
+
quality: options.quality,
|
|
1032
|
+
timeout: options.timeout,
|
|
1033
|
+
maxClipHeight: state2.targetHeight
|
|
1657
1034
|
});
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
if (
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
if (attempt < attempts && retryDelayMs > 0) {
|
|
1666
|
-
logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
|
|
1667
|
-
await (0, import_delay.default)(retryDelayMs);
|
|
1035
|
+
} finally {
|
|
1036
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1037
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1038
|
+
});
|
|
1039
|
+
if (options.restore) {
|
|
1040
|
+
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1668
1041
|
}
|
|
1669
1042
|
}
|
|
1670
|
-
if (lastIssue && lastAnalysis) {
|
|
1671
|
-
logger.warning(
|
|
1672
|
-
`\u622A\u56FE\u8D28\u91CF\u4ECD\u5F02\u5E38(${lastIssue})\uFF0C\u8FD4\u56DE\u6700\u4F73\u53EF\u5F97\u7ED3\u679C: light=${lastAnalysis.lightBlankRowsRatio.toFixed(2)}, dark=${lastAnalysis.darkBlankRowsRatio.toFixed(2)}, loading=${lastAnalysis.loadingLikeRowsRatio.toFixed(2)}`
|
|
1673
|
-
);
|
|
1674
|
-
}
|
|
1675
|
-
return lastBuffer;
|
|
1676
1043
|
};
|
|
1677
1044
|
|
|
1678
1045
|
// src/errors.js
|
|
@@ -2824,6 +2191,7 @@ var RuntimeEnv = {
|
|
|
2824
2191
|
|
|
2825
2192
|
// src/apify-kit.js
|
|
2826
2193
|
var logger3 = createInternalLogger("ApifyKit");
|
|
2194
|
+
var hasFleetAtHomeFlag = () => String(process.env.FLEET_AT_HOME || "").trim() !== "";
|
|
2827
2195
|
var resolveRuntimeContext = (input) => {
|
|
2828
2196
|
const rememberedState = RuntimeEnv.peekRememberedState();
|
|
2829
2197
|
const state2 = rememberedState || RuntimeEnv.parseInput(input || {});
|
|
@@ -2924,6 +2292,13 @@ async function createApifyKit() {
|
|
|
2924
2292
|
let lastPage = null;
|
|
2925
2293
|
const getRuntimeContext = () => resolveRuntimeContext(actorInput);
|
|
2926
2294
|
return {
|
|
2295
|
+
/**
|
|
2296
|
+
* 判断当前是否运行在受管自动化环境中。
|
|
2297
|
+
* Apify 云端保持使用 SDK 原生判断;本地 Chromium Cluster 使用 FLEET_AT_HOME 标记。
|
|
2298
|
+
*/
|
|
2299
|
+
isAtHome() {
|
|
2300
|
+
return Actor2.isAtHome() || hasFleetAtHomeFlag();
|
|
2301
|
+
},
|
|
2927
2302
|
/**
|
|
2928
2303
|
* 核心封装:执行步骤,带自动日志确认、失败截图处理和重试机制
|
|
2929
2304
|
*
|
|
@@ -5936,7 +5311,7 @@ var DefaultLaunch = {
|
|
|
5936
5311
|
log: logOptions = null,
|
|
5937
5312
|
runInHeadfulMode = false,
|
|
5938
5313
|
debugMode = false,
|
|
5939
|
-
|
|
5314
|
+
isRunningAtHome = false,
|
|
5940
5315
|
launcher = null,
|
|
5941
5316
|
preNavigationHooks = [],
|
|
5942
5317
|
postNavigationHooks = [],
|
|
@@ -5985,7 +5360,7 @@ var DefaultLaunch = {
|
|
|
5985
5360
|
};
|
|
5986
5361
|
const crawlerBaseOptions = {
|
|
5987
5362
|
...DEFAULT_CRAWLER_BASE_OPTIONS,
|
|
5988
|
-
headless: !runInHeadfulMode ||
|
|
5363
|
+
headless: !runInHeadfulMode || isRunningAtHome,
|
|
5989
5364
|
// 有 core.fingerprint 时走固定回放;否则退回 Crawlee 默认指纹模式。
|
|
5990
5365
|
browserPoolOptions: replayBrowserPoolOptions || {
|
|
5991
5366
|
useFingerprints: true,
|
|
@@ -6362,7 +5737,7 @@ var CloakLaunch = {
|
|
|
6362
5737
|
log: logOptions = null,
|
|
6363
5738
|
debugMode = false,
|
|
6364
5739
|
runInHeadfulMode = false,
|
|
6365
|
-
|
|
5740
|
+
isRunningAtHome = false,
|
|
6366
5741
|
launcher = null,
|
|
6367
5742
|
runtimeState = null,
|
|
6368
5743
|
fingerprintPlatform = "",
|
|
@@ -6378,7 +5753,7 @@ var CloakLaunch = {
|
|
|
6378
5753
|
const normalizedCloakOptions = normalizeObject2(cloakOptions);
|
|
6379
5754
|
const activeBrowsers = /* @__PURE__ */ new Set();
|
|
6380
5755
|
const patchedBrowsers = /* @__PURE__ */ new WeakSet();
|
|
6381
|
-
const defaultArgs =
|
|
5756
|
+
const defaultArgs = isRunningAtHome ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
|
|
6382
5757
|
const replayContext = resolveReplayableCloakProfile(runtimeState, {
|
|
6383
5758
|
fingerprintPlatform,
|
|
6384
5759
|
cloakOptions: normalizedCloakOptions
|
|
@@ -6392,7 +5767,7 @@ var CloakLaunch = {
|
|
|
6392
5767
|
const hasExplicitProxy = hasOwn(normalizedCloakOptions, "proxy");
|
|
6393
5768
|
const proxyLaunchState = hasExplicitProxy ? resolveLaunchTraffic({ proxyConfiguration, debugMode, useMeter: false }) : resolveLaunchTraffic({ proxyConfiguration, debugMode });
|
|
6394
5769
|
const proxy = hasExplicitProxy ? normalizedCloakOptions.proxy : proxyLaunchState.launchProxy;
|
|
6395
|
-
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode ||
|
|
5770
|
+
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningAtHome;
|
|
6396
5771
|
const enableByPassLogger = Boolean(logOptions && logOptions.enable);
|
|
6397
5772
|
const mergedCloakOptions = {
|
|
6398
5773
|
...normalizedCloakOptions,
|
|
@@ -10349,7 +9724,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
10349
9724
|
};
|
|
10350
9725
|
|
|
10351
9726
|
// src/internals/compression.js
|
|
10352
|
-
var
|
|
9727
|
+
var import_jimp = require("jimp");
|
|
10353
9728
|
var logger15 = createInternalLogger("Compression");
|
|
10354
9729
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
10355
9730
|
var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
|
|
@@ -10416,10 +9791,10 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
10416
9791
|
image.resize({
|
|
10417
9792
|
w: width,
|
|
10418
9793
|
h: height,
|
|
10419
|
-
mode:
|
|
9794
|
+
mode: import_jimp.ResizeStrategy.BILINEAR
|
|
10420
9795
|
});
|
|
10421
9796
|
}
|
|
10422
|
-
const buffer = await image.getBuffer(
|
|
9797
|
+
const buffer = await image.getBuffer(import_jimp.JimpMime.jpeg, { quality });
|
|
10423
9798
|
return {
|
|
10424
9799
|
buffer,
|
|
10425
9800
|
bytes: getBase64BytesFromBuffer(buffer),
|
|
@@ -10431,7 +9806,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
10431
9806
|
};
|
|
10432
9807
|
};
|
|
10433
9808
|
var compressImageBuffer = async (buffer, compression) => {
|
|
10434
|
-
const sourceImage = await
|
|
9809
|
+
const sourceImage = await import_jimp.Jimp.read(buffer);
|
|
10435
9810
|
const maxQuality = toJpegQuality(compression.quality);
|
|
10436
9811
|
const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
|
|
10437
9812
|
let quality = maxQuality;
|