@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.js
CHANGED
|
@@ -468,7 +468,6 @@ function createInternalLogger(moduleName, explicitLogger) {
|
|
|
468
468
|
|
|
469
469
|
// src/internals/screenshot.js
|
|
470
470
|
import delay from "delay";
|
|
471
|
-
import { Jimp, JimpMime, intToRGBA } from "jimp";
|
|
472
471
|
|
|
473
472
|
// src/internals/constants.js
|
|
474
473
|
var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
@@ -507,23 +506,9 @@ var FORCED_FULLPAGE_TYPE = "jpeg";
|
|
|
507
506
|
var FORCED_FULLPAGE_QUALITY = 50;
|
|
508
507
|
var SUPPORTED_TYPES = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
509
508
|
var EXPANDED_SCROLLABLE_CLASS = "__pk_expanded__";
|
|
510
|
-
var STITCH_SCROLL_TARGET_ATTR = "data-pk-stitch-scroll-target";
|
|
511
509
|
var DEFAULT_MAX_HEIGHT = 8e3;
|
|
512
510
|
var DEFAULT_SETTLE_MS = 1e3;
|
|
513
511
|
var DEFAULT_MOBILE_SETTLE_MS = 50;
|
|
514
|
-
var DEFAULT_STITCH_SETTLE_MS = 120;
|
|
515
|
-
var DEFAULT_STITCH_OVERLAP_PX = 24;
|
|
516
|
-
var MIN_VIRTUALIZED_SCROLL_RATIO = 2.2;
|
|
517
|
-
var MIN_SPARSE_SCROLL_RATIO = 4;
|
|
518
|
-
var MIN_STITCH_VISIBLE_HEIGHT_PX = 120;
|
|
519
|
-
var MIN_STITCH_VISIBLE_HEIGHT_RATIO = 0.22;
|
|
520
|
-
var MIN_STITCH_OUTPUT_HEIGHT_RATIO = 0.35;
|
|
521
|
-
var MIN_STITCH_OUTPUT_VIEWPORT_RATIO = 1.15;
|
|
522
|
-
var MOBILE_VIEWPORT_WIDTH_THRESHOLD = 520;
|
|
523
|
-
var DEFAULT_QUALITY_RETRY_ATTEMPTS = 2;
|
|
524
|
-
var DEFAULT_QUALITY_RETRY_DELAY_MS = 2500;
|
|
525
|
-
var MIN_TRAILING_BLANK_GAP_PX = 320;
|
|
526
|
-
var MIN_TRAILING_BLANK_GAP_RATIO = 0.12;
|
|
527
512
|
var toPositiveNumber = (value, fallback = 0) => {
|
|
528
513
|
const n = Number(value);
|
|
529
514
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -546,22 +531,7 @@ var normalizeQuality = (value, type) => {
|
|
|
546
531
|
if (rounded < 0 || rounded > 100) return void 0;
|
|
547
532
|
return rounded;
|
|
548
533
|
};
|
|
549
|
-
var resolvePageDevice =
|
|
550
|
-
const declared = normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
551
|
-
if (declared === Device.Mobile) return Device.Mobile;
|
|
552
|
-
const viewport = await resolveCurrentViewportSize(page).catch(() => null);
|
|
553
|
-
const viewportWidth = Math.round(Number(viewport?.width) || 0);
|
|
554
|
-
if (viewportWidth > 0 && viewportWidth <= MOBILE_VIEWPORT_WIDTH_THRESHOLD) {
|
|
555
|
-
return Device.Mobile;
|
|
556
|
-
}
|
|
557
|
-
return declared;
|
|
558
|
-
};
|
|
559
|
-
var resolveStitchMode = (options = {}) => {
|
|
560
|
-
const raw = options.stitch ?? options.stitching ?? options.strategy;
|
|
561
|
-
if (raw === false || raw === "off" || raw === "none" || raw === "single") return "off";
|
|
562
|
-
if (raw === true || raw === "force" || raw === "stitched") return "force";
|
|
563
|
-
return "auto";
|
|
564
|
-
};
|
|
534
|
+
var resolvePageDevice = (page) => normalizeDevice(page?.[PageRuntimeStateKey]?.device);
|
|
565
535
|
var buildFullPageClip = (metrics, viewport, maxClipHeight) => {
|
|
566
536
|
const contentSize = metrics && typeof metrics === "object" ? metrics.contentSize || null : null;
|
|
567
537
|
const width = Math.max(1, Math.ceil(viewport.width || contentSize?.width || 1));
|
|
@@ -908,156 +878,10 @@ var restoreAffixedElementsForExpandedScreenshot = async (page) => {
|
|
|
908
878
|
});
|
|
909
879
|
}, EXPANDED_SCROLLABLE_CLASS);
|
|
910
880
|
};
|
|
911
|
-
var measureMeaningfulContentBounds = async (page) => {
|
|
912
|
-
return await page.evaluate(() => {
|
|
913
|
-
const body = document.body;
|
|
914
|
-
if (!body) return null;
|
|
915
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || body.clientWidth || 0;
|
|
916
|
-
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || body.clientHeight || 0;
|
|
917
|
-
const scrollX = window.scrollX || window.pageXOffset || 0;
|
|
918
|
-
const scrollY = window.scrollY || window.pageYOffset || 0;
|
|
919
|
-
const bounds = {
|
|
920
|
-
left: Number.POSITIVE_INFINITY,
|
|
921
|
-
top: Number.POSITIVE_INFINITY,
|
|
922
|
-
right: 0,
|
|
923
|
-
bottom: 0,
|
|
924
|
-
nodes: 0
|
|
925
|
-
};
|
|
926
|
-
const isVisible = (el, style, rect) => {
|
|
927
|
-
if (!el || !style || !rect) return false;
|
|
928
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
929
|
-
if (Number(style.opacity) === 0) return false;
|
|
930
|
-
return rect.width > 1 && rect.height > 1;
|
|
931
|
-
};
|
|
932
|
-
const hasFixedAncestor = (el) => {
|
|
933
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
934
|
-
const position = String(window.getComputedStyle(node).position || "").toLowerCase();
|
|
935
|
-
if (position === "fixed") {
|
|
936
|
-
return true;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
return false;
|
|
940
|
-
};
|
|
941
|
-
const parseRgbColor = (value) => {
|
|
942
|
-
const raw = String(value || "").trim();
|
|
943
|
-
const match = raw.match(/rgba?\(([^)]+)\)/i);
|
|
944
|
-
if (!match) return null;
|
|
945
|
-
const parts = match[1].replace(/\//g, " ").split(/[,\s]+/).map((part) => part.trim()).filter(Boolean);
|
|
946
|
-
if (parts.length < 3) return null;
|
|
947
|
-
const normalizeChannel = (part) => {
|
|
948
|
-
if (part.endsWith("%")) {
|
|
949
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part) * 2.55));
|
|
950
|
-
}
|
|
951
|
-
return Math.max(0, Math.min(255, Number.parseFloat(part)));
|
|
952
|
-
};
|
|
953
|
-
const alpha = parts.length >= 4 ? parts[3].endsWith("%") ? Number.parseFloat(parts[3]) / 100 : Number.parseFloat(parts[3]) : 1;
|
|
954
|
-
return {
|
|
955
|
-
r: normalizeChannel(parts[0]),
|
|
956
|
-
g: normalizeChannel(parts[1]),
|
|
957
|
-
b: normalizeChannel(parts[2]),
|
|
958
|
-
a: Number.isFinite(alpha) ? Math.max(0, Math.min(1, alpha)) : 1
|
|
959
|
-
};
|
|
960
|
-
};
|
|
961
|
-
const colorLuminance = (color) => {
|
|
962
|
-
const channel = (value) => {
|
|
963
|
-
const ratio = Math.max(0, Math.min(255, value)) / 255;
|
|
964
|
-
return ratio <= 0.03928 ? ratio / 12.92 : ((ratio + 0.055) / 1.055) ** 2.4;
|
|
965
|
-
};
|
|
966
|
-
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
967
|
-
};
|
|
968
|
-
const contrastRatio = (a, b) => {
|
|
969
|
-
const l1 = colorLuminance(a);
|
|
970
|
-
const l2 = colorLuminance(b);
|
|
971
|
-
const lighter = Math.max(l1, l2);
|
|
972
|
-
const darker = Math.min(l1, l2);
|
|
973
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
974
|
-
};
|
|
975
|
-
const resolveBackgroundColor = (el) => {
|
|
976
|
-
for (let node = el; node && node.nodeType === 1; node = node.parentElement) {
|
|
977
|
-
const background = parseRgbColor(window.getComputedStyle(node).backgroundColor);
|
|
978
|
-
if (background && background.a > 0.2) {
|
|
979
|
-
return background;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
return { r: 255, g: 255, b: 255, a: 1 };
|
|
983
|
-
};
|
|
984
|
-
const isLowInformationTextPaint = (el, style) => {
|
|
985
|
-
const color = parseRgbColor(style?.color);
|
|
986
|
-
if (!color) return false;
|
|
987
|
-
const opacity = Number(style.opacity);
|
|
988
|
-
if (color.a <= 0.08 || Number.isFinite(opacity) && opacity <= 0.18) {
|
|
989
|
-
return true;
|
|
990
|
-
}
|
|
991
|
-
const max = Math.max(color.r, color.g, color.b);
|
|
992
|
-
const min = Math.min(color.r, color.g, color.b);
|
|
993
|
-
if (!(min >= 224 && max - min <= 24 && color.a >= 0.85)) {
|
|
994
|
-
return false;
|
|
995
|
-
}
|
|
996
|
-
const background = resolveBackgroundColor(el);
|
|
997
|
-
return colorLuminance(background) > 0.76 && contrastRatio(color, background) < 1.35;
|
|
998
|
-
};
|
|
999
|
-
const addRect = (rect) => {
|
|
1000
|
-
if (!rect || rect.width <= 1 || rect.height <= 1) return;
|
|
1001
|
-
const left = Math.max(0, Math.floor(rect.left + scrollX));
|
|
1002
|
-
const top = Math.max(0, Math.floor(rect.top + scrollY));
|
|
1003
|
-
const right = Math.ceil(rect.right + scrollX);
|
|
1004
|
-
const bottom = Math.ceil(rect.bottom + scrollY);
|
|
1005
|
-
if (right <= left || bottom <= top) return;
|
|
1006
|
-
bounds.left = Math.min(bounds.left, left);
|
|
1007
|
-
bounds.top = Math.min(bounds.top, top);
|
|
1008
|
-
bounds.right = Math.max(bounds.right, right);
|
|
1009
|
-
bounds.bottom = Math.max(bounds.bottom, bottom);
|
|
1010
|
-
bounds.nodes += 1;
|
|
1011
|
-
};
|
|
1012
|
-
const addElement = (el, { minArea = 12, text = false } = {}) => {
|
|
1013
|
-
if (!el || el.nodeType !== 1 || hasFixedAncestor(el)) return;
|
|
1014
|
-
const style = window.getComputedStyle(el);
|
|
1015
|
-
if (text && isLowInformationTextPaint(el, style)) return;
|
|
1016
|
-
const rects = Array.from(el.getClientRects ? el.getClientRects() : []);
|
|
1017
|
-
rects.forEach((rect) => {
|
|
1018
|
-
if (!isVisible(el, style, rect)) return;
|
|
1019
|
-
if (rect.width * rect.height < minArea) return;
|
|
1020
|
-
addRect(rect);
|
|
1021
|
-
});
|
|
1022
|
-
};
|
|
1023
|
-
const walker = document.createTreeWalker(body, NodeFilter.SHOW_TEXT);
|
|
1024
|
-
let visitedTextNodes = 0;
|
|
1025
|
-
while (walker.nextNode() && visitedTextNodes < 6e3) {
|
|
1026
|
-
const node = walker.currentNode;
|
|
1027
|
-
visitedTextNodes += 1;
|
|
1028
|
-
const text = String(node.nodeValue || "").replace(/\s+/g, " ").trim();
|
|
1029
|
-
if (text.length < 2) continue;
|
|
1030
|
-
addElement(node.parentElement, { minArea: 8, text: true });
|
|
1031
|
-
}
|
|
1032
|
-
document.querySelectorAll("img,video,canvas,svg,picture").forEach((el) => {
|
|
1033
|
-
addElement(el, { minArea: 900 });
|
|
1034
|
-
});
|
|
1035
|
-
if (!bounds.nodes || !Number.isFinite(bounds.left)) return null;
|
|
1036
|
-
const paddingX = Math.max(16, Math.min(96, viewportWidth * 0.04));
|
|
1037
|
-
const paddingY = Math.max(24, Math.min(160, viewportHeight * 0.18));
|
|
1038
|
-
return {
|
|
1039
|
-
left: Math.max(0, Math.floor(bounds.left - paddingX)),
|
|
1040
|
-
top: Math.max(0, Math.floor(bounds.top - paddingY)),
|
|
1041
|
-
right: Math.ceil(bounds.right + paddingX),
|
|
1042
|
-
bottom: Math.ceil(bounds.bottom + paddingY),
|
|
1043
|
-
width: Math.max(1, Math.ceil(bounds.right - bounds.left)),
|
|
1044
|
-
height: Math.max(1, Math.ceil(bounds.bottom - bounds.top)),
|
|
1045
|
-
nodes: bounds.nodes,
|
|
1046
|
-
viewport: {
|
|
1047
|
-
width: Math.max(1, Math.ceil(viewportWidth || 1)),
|
|
1048
|
-
height: Math.max(1, Math.ceil(viewportHeight || 1))
|
|
1049
|
-
}
|
|
1050
|
-
};
|
|
1051
|
-
}).catch((error) => {
|
|
1052
|
-
logger.warning(`\u622A\u56FE\u5185\u5BB9\u8FB9\u754C\u6D4B\u91CF\u5931\u8D25: ${error?.message || error}`);
|
|
1053
|
-
return null;
|
|
1054
|
-
});
|
|
1055
|
-
};
|
|
1056
881
|
var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1057
882
|
const originalViewport = await resolveCurrentViewportSize(page);
|
|
1058
883
|
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1059
|
-
const
|
|
1060
|
-
const preserveViewport = options.preserveViewport ?? device === Device.Mobile;
|
|
884
|
+
const preserveViewport = options.preserveViewport ?? resolvePageDevice(page) === Device.Mobile;
|
|
1061
885
|
const defaultSettleMs = preserveViewport ? DEFAULT_MOBILE_SETTLE_MS : DEFAULT_SETTLE_MS;
|
|
1062
886
|
const requestedSettleMs = Math.max(0, Number(options.settleMs ?? defaultSettleMs) || 0);
|
|
1063
887
|
const settleMs = preserveViewport ? Math.min(requestedSettleMs, DEFAULT_MOBILE_SETTLE_MS) : requestedSettleMs;
|
|
@@ -1066,21 +890,7 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1066
890
|
visibleOnly: options.visibleOnly !== false,
|
|
1067
891
|
expandDocumentElements: preserveViewport
|
|
1068
892
|
});
|
|
1069
|
-
const
|
|
1070
|
-
let targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1071
|
-
if (contentBounds?.bottom > 0) {
|
|
1072
|
-
const contentBottom = Math.min(Math.ceil(contentBounds.bottom), maxHeight);
|
|
1073
|
-
const trailingGap = targetHeight - contentBottom;
|
|
1074
|
-
const minTrailingGap = Math.max(
|
|
1075
|
-
MIN_TRAILING_BLANK_GAP_PX,
|
|
1076
|
-
Math.floor(targetHeight * MIN_TRAILING_BLANK_GAP_RATIO),
|
|
1077
|
-
Math.floor(originalViewport.height * 0.45)
|
|
1078
|
-
);
|
|
1079
|
-
if (trailingGap >= minTrailingGap && contentBottom >= originalViewport.height * 0.65) {
|
|
1080
|
-
targetHeight = Math.max(1, contentBottom);
|
|
1081
|
-
logger.info(`\u957F\u622A\u56FE\u88C1\u6389\u4F4E\u4FE1\u606F\u5C3E\u90E8: contentBottom=${contentBottom}, originalHeight=${Math.min(maxScrollHeight, maxHeight)}`);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
893
|
+
const targetHeight = Math.min(maxScrollHeight, maxHeight);
|
|
1084
894
|
let viewportResized = false;
|
|
1085
895
|
if (!preserveViewport) {
|
|
1086
896
|
await page.setViewportSize({
|
|
@@ -1100,7 +910,6 @@ var prepareExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
|
1100
910
|
originalViewport,
|
|
1101
911
|
maxScrollHeight,
|
|
1102
912
|
targetHeight,
|
|
1103
|
-
contentBounds,
|
|
1104
913
|
preserveViewport,
|
|
1105
914
|
viewportResized
|
|
1106
915
|
};
|
|
@@ -1133,436 +942,6 @@ var restoreExpandedFullPageScreenshot = async (page, state2 = {}) => {
|
|
|
1133
942
|
await page.setViewportSize(state2.originalViewport);
|
|
1134
943
|
}
|
|
1135
944
|
};
|
|
1136
|
-
var resolveVirtualizedScrollTarget = async (page, options = {}) => {
|
|
1137
|
-
const stitchMode = resolveStitchMode(options);
|
|
1138
|
-
if (stitchMode === "off") return null;
|
|
1139
|
-
const device = await resolvePageDevice(page);
|
|
1140
|
-
if (stitchMode !== "force" && device !== Device.Mobile) {
|
|
1141
|
-
return null;
|
|
1142
|
-
}
|
|
1143
|
-
return await page.evaluate((config) => {
|
|
1144
|
-
const viewportWidth = window.innerWidth || document.documentElement?.clientWidth || document.body?.clientWidth || 0;
|
|
1145
|
-
const viewportHeight = window.innerHeight || document.documentElement?.clientHeight || document.body?.clientHeight || 0;
|
|
1146
|
-
const root = document.documentElement;
|
|
1147
|
-
const body = document.body;
|
|
1148
|
-
const scrollingElement = document.scrollingElement;
|
|
1149
|
-
const candidates = [];
|
|
1150
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1151
|
-
const scrollableOverflow = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
1152
|
-
const clippingOverflow = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
1153
|
-
const pushCandidate = (el2) => {
|
|
1154
|
-
if (!el2 || seen.has(el2)) return;
|
|
1155
|
-
seen.add(el2);
|
|
1156
|
-
candidates.push(el2);
|
|
1157
|
-
};
|
|
1158
|
-
pushCandidate(scrollingElement);
|
|
1159
|
-
pushCandidate(root);
|
|
1160
|
-
pushCandidate(body);
|
|
1161
|
-
document.querySelectorAll("*").forEach(pushCandidate);
|
|
1162
|
-
const isDocumentElement = (el2) => el2 === root || el2 === body || el2 === scrollingElement;
|
|
1163
|
-
const isVisible = (el2, style, rect) => {
|
|
1164
|
-
if (!el2 || !style || !rect) return false;
|
|
1165
|
-
if (style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return false;
|
|
1166
|
-
if (Number(style.opacity) === 0) return false;
|
|
1167
|
-
return rect.width > 0 && rect.height > 0;
|
|
1168
|
-
};
|
|
1169
|
-
const hasOverflowValue = (style, values) => {
|
|
1170
|
-
const overflowY = String(style?.overflowY || "").toLowerCase();
|
|
1171
|
-
const overflow = String(style?.overflow || "").toLowerCase();
|
|
1172
|
-
return values.has(overflowY) || values.has(overflow);
|
|
1173
|
-
};
|
|
1174
|
-
const looksScrollable = (el2, style) => {
|
|
1175
|
-
if (!el2 || el2.scrollHeight <= el2.clientHeight + 1) return false;
|
|
1176
|
-
if (isDocumentElement(el2)) return true;
|
|
1177
|
-
return hasOverflowValue(style, scrollableOverflow) || hasOverflowValue(style, clippingOverflow);
|
|
1178
|
-
};
|
|
1179
|
-
const textHeightFor = (el2) => {
|
|
1180
|
-
const nodes = isDocumentElement(el2) ? document.querySelectorAll("body *") : el2.querySelectorAll("*");
|
|
1181
|
-
let textHeight = 0;
|
|
1182
|
-
let textNodes = 0;
|
|
1183
|
-
const maxNodes = 3500;
|
|
1184
|
-
for (const node of nodes) {
|
|
1185
|
-
if (textNodes >= maxNodes) break;
|
|
1186
|
-
const text = String(node.textContent || "").replace(/\s+/g, " ").trim();
|
|
1187
|
-
if (text.length < 2) continue;
|
|
1188
|
-
let childHasText = false;
|
|
1189
|
-
for (const child of node.children || []) {
|
|
1190
|
-
if (String(child.textContent || "").replace(/\s+/g, " ").trim().length >= 2) {
|
|
1191
|
-
childHasText = true;
|
|
1192
|
-
break;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
if (childHasText) continue;
|
|
1196
|
-
const style = window.getComputedStyle(node);
|
|
1197
|
-
const rect = node.getBoundingClientRect();
|
|
1198
|
-
if (!isVisible(node, style, rect)) continue;
|
|
1199
|
-
textNodes += 1;
|
|
1200
|
-
textHeight += Math.min(Math.ceil(rect.height || 0), 900);
|
|
1201
|
-
}
|
|
1202
|
-
return { textHeight, textNodes };
|
|
1203
|
-
};
|
|
1204
|
-
const resolveEdgeOcclusion = () => {
|
|
1205
|
-
let top = 0;
|
|
1206
|
-
let bottom = 0;
|
|
1207
|
-
const maxHeight = Math.max(48, viewportHeight * 0.45);
|
|
1208
|
-
document.querySelectorAll("*").forEach((el2) => {
|
|
1209
|
-
const style = window.getComputedStyle(el2);
|
|
1210
|
-
const position = String(style.position || "").toLowerCase();
|
|
1211
|
-
if (position !== "fixed" && position !== "sticky") return;
|
|
1212
|
-
const rect = el2.getBoundingClientRect();
|
|
1213
|
-
if (!isVisible(el2, style, rect)) return;
|
|
1214
|
-
if (rect.height <= 0 || rect.height > maxHeight) return;
|
|
1215
|
-
if (rect.width < viewportWidth * 0.35) return;
|
|
1216
|
-
if (rect.top <= Math.max(16, viewportHeight * 0.08)) {
|
|
1217
|
-
top = Math.max(top, Math.ceil(rect.bottom));
|
|
1218
|
-
}
|
|
1219
|
-
if (rect.bottom >= viewportHeight - Math.max(16, viewportHeight * 0.08)) {
|
|
1220
|
-
bottom = Math.max(bottom, Math.ceil(viewportHeight - rect.top));
|
|
1221
|
-
}
|
|
1222
|
-
});
|
|
1223
|
-
return {
|
|
1224
|
-
top: Math.max(0, Math.min(Math.ceil(top), Math.floor(viewportHeight * 0.45))),
|
|
1225
|
-
bottom: Math.max(0, Math.min(Math.ceil(bottom), Math.floor(viewportHeight * 0.45)))
|
|
1226
|
-
};
|
|
1227
|
-
};
|
|
1228
|
-
let best = null;
|
|
1229
|
-
candidates.forEach((el2) => {
|
|
1230
|
-
const style = window.getComputedStyle(el2);
|
|
1231
|
-
const rect = isDocumentElement(el2) ? {
|
|
1232
|
-
top: 0,
|
|
1233
|
-
bottom: viewportHeight,
|
|
1234
|
-
left: 0,
|
|
1235
|
-
right: viewportWidth,
|
|
1236
|
-
width: viewportWidth,
|
|
1237
|
-
height: viewportHeight
|
|
1238
|
-
} : el2.getBoundingClientRect();
|
|
1239
|
-
if (!isDocumentElement(el2) && !isVisible(el2, style, rect)) return;
|
|
1240
|
-
if (!looksScrollable(el2, style)) return;
|
|
1241
|
-
const scrollHeight = Math.ceil(el2.scrollHeight || 0);
|
|
1242
|
-
const clientHeight = Math.ceil(el2.clientHeight || viewportHeight || 0);
|
|
1243
|
-
if (scrollHeight < Math.max(900, clientHeight * config.minScrollRatio)) return;
|
|
1244
|
-
const visibleTop = Math.max(0, Math.round(rect.top || 0));
|
|
1245
|
-
const visibleBottom = Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight));
|
|
1246
|
-
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
1247
|
-
const minVisibleHeight = Math.max(
|
|
1248
|
-
config.minVisibleHeightPx,
|
|
1249
|
-
Math.floor(viewportHeight * config.minVisibleHeightRatio)
|
|
1250
|
-
);
|
|
1251
|
-
if (!config.force && !isDocumentElement(el2) && visibleHeight < minVisibleHeight) return;
|
|
1252
|
-
const { textHeight, textNodes } = textHeightFor(el2);
|
|
1253
|
-
const sparseRatio = scrollHeight / Math.max(1, textHeight);
|
|
1254
|
-
const sparse = config.force || textNodes > 0 && sparseRatio >= config.minSparseRatio && scrollHeight - textHeight > clientHeight;
|
|
1255
|
-
if (!sparse) return;
|
|
1256
|
-
const visibleWidth = Math.max(1, Math.ceil(rect.width || viewportWidth || 1));
|
|
1257
|
-
const score2 = scrollHeight * Math.min(visibleWidth, viewportWidth || visibleWidth) * Math.min(1, visibleHeight / Math.max(1, viewportHeight));
|
|
1258
|
-
if (!best || score2 > best.score) {
|
|
1259
|
-
best = {
|
|
1260
|
-
el: el2,
|
|
1261
|
-
score: score2,
|
|
1262
|
-
kind: isDocumentElement(el2) ? "document" : "element",
|
|
1263
|
-
scrollHeight,
|
|
1264
|
-
clientHeight,
|
|
1265
|
-
scrollTop: Math.max(0, Math.round(el2.scrollTop || 0)),
|
|
1266
|
-
rect: {
|
|
1267
|
-
top: Math.max(0, Math.round(rect.top || 0)),
|
|
1268
|
-
bottom: Math.min(viewportHeight, Math.round(rect.bottom || viewportHeight)),
|
|
1269
|
-
left: Math.max(0, Math.round(rect.left || 0)),
|
|
1270
|
-
width: Math.max(1, Math.round(rect.width || viewportWidth || 1)),
|
|
1271
|
-
height: Math.max(1, Math.round(rect.height || viewportHeight || 1))
|
|
1272
|
-
},
|
|
1273
|
-
visibleHeight,
|
|
1274
|
-
textHeight,
|
|
1275
|
-
textNodes,
|
|
1276
|
-
sparseRatio
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
if (!best) return null;
|
|
1281
|
-
document.querySelectorAll(`[${config.attrName}="1"]`).forEach((node) => {
|
|
1282
|
-
node.removeAttribute(config.attrName);
|
|
1283
|
-
});
|
|
1284
|
-
if (best.kind === "element") {
|
|
1285
|
-
best.el.setAttribute(config.attrName, "1");
|
|
1286
|
-
}
|
|
1287
|
-
const { el, score, ...target } = best;
|
|
1288
|
-
return {
|
|
1289
|
-
...target,
|
|
1290
|
-
viewport: {
|
|
1291
|
-
width: Math.max(1, Math.ceil(viewportWidth || best.rect.width || 1)),
|
|
1292
|
-
height: Math.max(1, Math.ceil(viewportHeight || best.rect.height || 1))
|
|
1293
|
-
},
|
|
1294
|
-
edgeOcclusion: resolveEdgeOcclusion()
|
|
1295
|
-
};
|
|
1296
|
-
}, {
|
|
1297
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1298
|
-
force: stitchMode === "force",
|
|
1299
|
-
minScrollRatio: MIN_VIRTUALIZED_SCROLL_RATIO,
|
|
1300
|
-
minSparseRatio: MIN_SPARSE_SCROLL_RATIO,
|
|
1301
|
-
minVisibleHeightPx: MIN_STITCH_VISIBLE_HEIGHT_PX,
|
|
1302
|
-
minVisibleHeightRatio: MIN_STITCH_VISIBLE_HEIGHT_RATIO
|
|
1303
|
-
}).catch((error) => {
|
|
1304
|
-
logger.warning(`\u865A\u62DF\u6EDA\u52A8\u622A\u56FE\u63A2\u6D4B\u5931\u8D25: ${error?.message || error}`);
|
|
1305
|
-
return null;
|
|
1306
|
-
});
|
|
1307
|
-
};
|
|
1308
|
-
var setStitchScrollTop = async (page, target, scrollTop) => {
|
|
1309
|
-
await page.evaluate(({ attrName, kind, top }) => {
|
|
1310
|
-
const el = kind === "document" ? document.scrollingElement || document.documentElement || document.body : document.querySelector(`[${attrName}="1"]`);
|
|
1311
|
-
if (!el) return;
|
|
1312
|
-
el.scrollTop = Math.max(0, Math.round(Number(top) || 0));
|
|
1313
|
-
el.dispatchEvent(new Event("scroll", { bubbles: true }));
|
|
1314
|
-
window.dispatchEvent(new Event("scroll"));
|
|
1315
|
-
}, {
|
|
1316
|
-
attrName: STITCH_SCROLL_TARGET_ATTR,
|
|
1317
|
-
kind: target.kind,
|
|
1318
|
-
top: scrollTop
|
|
1319
|
-
});
|
|
1320
|
-
};
|
|
1321
|
-
var cleanupStitchTarget = async (page) => {
|
|
1322
|
-
await page.evaluate((attrName) => {
|
|
1323
|
-
document.querySelectorAll(`[${attrName}="1"]`).forEach((node) => {
|
|
1324
|
-
node.removeAttribute(attrName);
|
|
1325
|
-
});
|
|
1326
|
-
}, STITCH_SCROLL_TARGET_ATTR).catch(() => {
|
|
1327
|
-
});
|
|
1328
|
-
};
|
|
1329
|
-
var cropImage = (image, crop) => image.clone().crop({
|
|
1330
|
-
x: Math.max(0, Math.round(crop.x || 0)),
|
|
1331
|
-
y: Math.max(0, Math.round(crop.y || 0)),
|
|
1332
|
-
w: Math.max(1, Math.round(crop.w || 1)),
|
|
1333
|
-
h: Math.max(1, Math.round(crop.h || 1))
|
|
1334
|
-
});
|
|
1335
|
-
var captureStitchedScrollableScreenshot = async (page, target, options = {}) => {
|
|
1336
|
-
const maxHeight = toPositiveInteger(options.maxHeight, DEFAULT_MAX_HEIGHT);
|
|
1337
|
-
const settleMs = Math.max(0, Number(options.stitchSettleMs ?? DEFAULT_STITCH_SETTLE_MS) || 0);
|
|
1338
|
-
const overlapPx = Math.max(0, Math.round(Number(options.stitchOverlapPx ?? DEFAULT_STITCH_OVERLAP_PX) || 0));
|
|
1339
|
-
const viewport = target.viewport || await resolveCurrentViewportSize(page);
|
|
1340
|
-
const rect = target.rect || {
|
|
1341
|
-
top: 0,
|
|
1342
|
-
bottom: viewport.height,
|
|
1343
|
-
left: 0,
|
|
1344
|
-
width: viewport.width,
|
|
1345
|
-
height: viewport.height
|
|
1346
|
-
};
|
|
1347
|
-
const edge = target.edgeOcclusion || { top: 0, bottom: 0 };
|
|
1348
|
-
const topCrop = Math.max(0, Math.min(rect.top, viewport.height - 1));
|
|
1349
|
-
const topOverlay = Math.max(topCrop, Math.min(edge.top || 0, viewport.height - 1));
|
|
1350
|
-
const bottomOverlay = Math.max(0, Math.min(edge.bottom || 0, viewport.height - topOverlay - 1));
|
|
1351
|
-
const middleCropY = Math.max(topCrop, topOverlay);
|
|
1352
|
-
const middleCropBottom = Math.max(
|
|
1353
|
-
middleCropY + 1,
|
|
1354
|
-
Math.min(rect.bottom || viewport.height, viewport.height - bottomOverlay)
|
|
1355
|
-
);
|
|
1356
|
-
const middleCropHeight = Math.max(1, middleCropBottom - middleCropY);
|
|
1357
|
-
const scrollStep = Math.max(120, middleCropHeight - overlapPx);
|
|
1358
|
-
const maxScrollTop = Math.max(0, Math.ceil(target.scrollHeight - target.clientHeight));
|
|
1359
|
-
const positions = [];
|
|
1360
|
-
for (let top = 0; top < maxScrollTop; top += scrollStep) {
|
|
1361
|
-
positions.push(Math.round(top));
|
|
1362
|
-
}
|
|
1363
|
-
if (!positions.includes(maxScrollTop)) {
|
|
1364
|
-
positions.push(maxScrollTop);
|
|
1365
|
-
}
|
|
1366
|
-
if (positions.length === 0) {
|
|
1367
|
-
positions.push(0);
|
|
1368
|
-
}
|
|
1369
|
-
const segments = [];
|
|
1370
|
-
let totalHeight = 0;
|
|
1371
|
-
let outputWidth = Math.max(1, Math.round(viewport.width || rect.width || 1));
|
|
1372
|
-
try {
|
|
1373
|
-
for (let index = 0; index < positions.length && totalHeight < maxHeight; index += 1) {
|
|
1374
|
-
const isFirst = index === 0;
|
|
1375
|
-
const isLast = index === positions.length - 1;
|
|
1376
|
-
await setStitchScrollTop(page, target, positions[index]);
|
|
1377
|
-
if (settleMs > 0) {
|
|
1378
|
-
await delay(settleMs);
|
|
1379
|
-
}
|
|
1380
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1381
|
-
type: options.type || "png",
|
|
1382
|
-
quality: options.quality,
|
|
1383
|
-
timeout: options.timeout
|
|
1384
|
-
});
|
|
1385
|
-
const image = await Jimp.read(buffer);
|
|
1386
|
-
outputWidth = Math.min(outputWidth, image.bitmap.width);
|
|
1387
|
-
const cropY = isFirst ? 0 : middleCropY;
|
|
1388
|
-
const cropBottom = isLast ? image.bitmap.height : middleCropBottom;
|
|
1389
|
-
let cropHeight = Math.max(1, Math.min(image.bitmap.height, cropBottom) - cropY);
|
|
1390
|
-
const remaining = maxHeight - totalHeight;
|
|
1391
|
-
if (cropHeight > remaining) {
|
|
1392
|
-
cropHeight = remaining;
|
|
1393
|
-
}
|
|
1394
|
-
segments.push(cropImage(image, {
|
|
1395
|
-
x: 0,
|
|
1396
|
-
y: cropY,
|
|
1397
|
-
w: outputWidth,
|
|
1398
|
-
h: cropHeight
|
|
1399
|
-
}));
|
|
1400
|
-
totalHeight += cropHeight;
|
|
1401
|
-
}
|
|
1402
|
-
const canvas = new Jimp({
|
|
1403
|
-
width: outputWidth,
|
|
1404
|
-
height: Math.max(1, totalHeight),
|
|
1405
|
-
color: 4294967295
|
|
1406
|
-
});
|
|
1407
|
-
let y = 0;
|
|
1408
|
-
for (const segment of segments) {
|
|
1409
|
-
canvas.composite(segment, 0, y);
|
|
1410
|
-
y += segment.bitmap.height;
|
|
1411
|
-
}
|
|
1412
|
-
logger.info(
|
|
1413
|
-
`\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)}`
|
|
1414
|
-
);
|
|
1415
|
-
const expectedHeight = Math.min(maxHeight, Math.max(target.scrollHeight || 0, viewport.height || 0));
|
|
1416
|
-
const minPlausibleHeight = Math.max(
|
|
1417
|
-
Math.floor((viewport.height || rect.height || 0) * MIN_STITCH_OUTPUT_VIEWPORT_RATIO),
|
|
1418
|
-
Math.floor(expectedHeight * MIN_STITCH_OUTPUT_HEIGHT_RATIO)
|
|
1419
|
-
);
|
|
1420
|
-
if (positions.length > 1 && expectedHeight > (viewport.height || rect.height || 0) * 1.3 && totalHeight < minPlausibleHeight) {
|
|
1421
|
-
logger.warning(
|
|
1422
|
-
`\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}`
|
|
1423
|
-
);
|
|
1424
|
-
return null;
|
|
1425
|
-
}
|
|
1426
|
-
return await canvas.getBuffer(JimpMime.png);
|
|
1427
|
-
} finally {
|
|
1428
|
-
await setStitchScrollTop(page, target, target.scrollTop || 0).catch(() => {
|
|
1429
|
-
});
|
|
1430
|
-
await cleanupStitchTarget(page);
|
|
1431
|
-
}
|
|
1432
|
-
};
|
|
1433
|
-
var isLightBlankPixel = ({ r, g, b }) => {
|
|
1434
|
-
const max = Math.max(r, g, b);
|
|
1435
|
-
const min = Math.min(r, g, b);
|
|
1436
|
-
return max >= 238 && max - min <= 18;
|
|
1437
|
-
};
|
|
1438
|
-
var isDarkBlankPixel = ({ r, g, b }) => {
|
|
1439
|
-
const max = Math.max(r, g, b);
|
|
1440
|
-
const min = Math.min(r, g, b);
|
|
1441
|
-
return max <= 34 && max - min <= 10;
|
|
1442
|
-
};
|
|
1443
|
-
var isLowInfoPixel = ({ r, g, b }) => {
|
|
1444
|
-
const max = Math.max(r, g, b);
|
|
1445
|
-
const min = Math.min(r, g, b);
|
|
1446
|
-
return max - min <= 12;
|
|
1447
|
-
};
|
|
1448
|
-
var analyzeScreenshotBuffer = async (buffer) => {
|
|
1449
|
-
const image = await Jimp.read(buffer);
|
|
1450
|
-
const width = image.bitmap.width;
|
|
1451
|
-
const height = image.bitmap.height;
|
|
1452
|
-
const stepY = Math.max(1, Math.floor(height / 900));
|
|
1453
|
-
const stepX = Math.max(1, Math.floor(width / 420));
|
|
1454
|
-
const rows = [];
|
|
1455
|
-
let lightRows = 0;
|
|
1456
|
-
let darkRows = 0;
|
|
1457
|
-
let lowInfoRows = 0;
|
|
1458
|
-
let loadingLikeRows = 0;
|
|
1459
|
-
const rowFlags = [];
|
|
1460
|
-
for (let y = 0; y < height; y += stepY) {
|
|
1461
|
-
let light = 0;
|
|
1462
|
-
let dark = 0;
|
|
1463
|
-
let lowInfo = 0;
|
|
1464
|
-
let edge = 0;
|
|
1465
|
-
let count = 0;
|
|
1466
|
-
let prev = null;
|
|
1467
|
-
for (let x = 0; x < width; x += stepX) {
|
|
1468
|
-
const rgba = intToRGBA(image.getPixelColor(x, y));
|
|
1469
|
-
count += 1;
|
|
1470
|
-
if (isLightBlankPixel(rgba)) light += 1;
|
|
1471
|
-
if (isDarkBlankPixel(rgba)) dark += 1;
|
|
1472
|
-
if (isLowInfoPixel(rgba)) lowInfo += 1;
|
|
1473
|
-
if (prev) {
|
|
1474
|
-
const diff = Math.abs(rgba.r - prev.r) + Math.abs(rgba.g - prev.g) + Math.abs(rgba.b - prev.b);
|
|
1475
|
-
if (diff > 45) edge += 1;
|
|
1476
|
-
}
|
|
1477
|
-
prev = rgba;
|
|
1478
|
-
}
|
|
1479
|
-
const stat = {
|
|
1480
|
-
y,
|
|
1481
|
-
lightRatio: light / Math.max(1, count),
|
|
1482
|
-
darkRatio: dark / Math.max(1, count),
|
|
1483
|
-
lowInfoRatio: lowInfo / Math.max(1, count),
|
|
1484
|
-
edgeRatio: edge / Math.max(1, count - 1)
|
|
1485
|
-
};
|
|
1486
|
-
rows.push(stat);
|
|
1487
|
-
const lightBlank = stat.lightRatio >= 0.965 && stat.edgeRatio <= 0.015;
|
|
1488
|
-
const darkBlank = stat.darkRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1489
|
-
const lowInfoBlank = stat.lowInfoRatio >= 0.965 && stat.edgeRatio <= 0.012;
|
|
1490
|
-
const loadingLike = stat.darkRatio >= 0.88 && stat.edgeRatio <= 0.025;
|
|
1491
|
-
if (lightBlank) lightRows += 1;
|
|
1492
|
-
if (darkBlank) darkRows += 1;
|
|
1493
|
-
if (lowInfoBlank) lowInfoRows += 1;
|
|
1494
|
-
if (loadingLike) loadingLikeRows += 1;
|
|
1495
|
-
rowFlags.push({ lightBlank, darkBlank, lowInfoBlank, loadingLike });
|
|
1496
|
-
}
|
|
1497
|
-
const sampledRows = Math.max(1, rows.length);
|
|
1498
|
-
return {
|
|
1499
|
-
image,
|
|
1500
|
-
width,
|
|
1501
|
-
height,
|
|
1502
|
-
lightBlankRowsRatio: lightRows / sampledRows,
|
|
1503
|
-
darkBlankRowsRatio: darkRows / sampledRows,
|
|
1504
|
-
lowInfoRowsRatio: lowInfoRows / sampledRows,
|
|
1505
|
-
loadingLikeRowsRatio: loadingLikeRows / sampledRows,
|
|
1506
|
-
rowFlags
|
|
1507
|
-
};
|
|
1508
|
-
};
|
|
1509
|
-
var resolveScreenshotQualityIssue = (analysis) => {
|
|
1510
|
-
if (!analysis) return null;
|
|
1511
|
-
if (analysis.loadingLikeRowsRatio >= 0.92 || analysis.darkBlankRowsRatio >= 0.72) {
|
|
1512
|
-
return "loading-like-dark-screenshot";
|
|
1513
|
-
}
|
|
1514
|
-
return null;
|
|
1515
|
-
};
|
|
1516
|
-
var cropBufferToContentBounds = async (buffer, bounds, options = {}) => {
|
|
1517
|
-
if (!bounds || bounds.nodes <= 0) return buffer;
|
|
1518
|
-
const image = options.image || await Jimp.read(buffer);
|
|
1519
|
-
const width = image.bitmap.width;
|
|
1520
|
-
const height = image.bitmap.height;
|
|
1521
|
-
const safeLeft = Math.max(0, Math.min(width - 1, Math.floor(bounds.left || 0)));
|
|
1522
|
-
const safeRight = Math.max(safeLeft + 1, Math.min(width, Math.ceil(bounds.right || width)));
|
|
1523
|
-
const safeTop = Math.max(0, Math.min(height - 1, Math.floor(bounds.top || 0)));
|
|
1524
|
-
const safeBottom = Math.max(safeTop + 1, Math.min(height, Math.ceil(bounds.bottom || height)));
|
|
1525
|
-
const cropW = safeRight - safeLeft;
|
|
1526
|
-
const cropH = safeBottom - safeTop;
|
|
1527
|
-
const shouldCropX = cropW > 80 && cropW <= width * 0.82 && (safeLeft > width * 0.04 || width - safeRight > width * 0.04);
|
|
1528
|
-
const shouldCropY = cropH > 160 && cropH <= height * 0.88 && height - safeBottom > Math.max(320, height * 0.1);
|
|
1529
|
-
if (!shouldCropX && !shouldCropY) {
|
|
1530
|
-
return buffer;
|
|
1531
|
-
}
|
|
1532
|
-
const x = shouldCropX ? safeLeft : 0;
|
|
1533
|
-
const y = shouldCropY ? safeTop : 0;
|
|
1534
|
-
const w = shouldCropX ? cropW : width;
|
|
1535
|
-
const h = shouldCropY ? cropH : height;
|
|
1536
|
-
logger.info(`\u5185\u5BB9\u611F\u77E5\u88C1\u526A\u622A\u56FE: x=${x}, y=${y}, w=${w}, h=${h}, original=${width}x${height}`);
|
|
1537
|
-
return await cropImage(image, { x, y, w, h }).getBuffer(JimpMime.png);
|
|
1538
|
-
};
|
|
1539
|
-
var captureExpandedFullPageScreenshotOnce = async (page, options = {}) => {
|
|
1540
|
-
const stitchedTarget = await resolveVirtualizedScrollTarget(page, options);
|
|
1541
|
-
if (stitchedTarget) {
|
|
1542
|
-
const buffer = await captureStitchedScrollableScreenshot(page, stitchedTarget, options);
|
|
1543
|
-
if (buffer) {
|
|
1544
|
-
return buffer;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1548
|
-
try {
|
|
1549
|
-
const buffer = await capturePageScreenshot(page, {
|
|
1550
|
-
fullPage: true,
|
|
1551
|
-
type: options.type || "png",
|
|
1552
|
-
quality: options.quality,
|
|
1553
|
-
timeout: options.timeout,
|
|
1554
|
-
maxClipHeight: state2.targetHeight
|
|
1555
|
-
});
|
|
1556
|
-
return await cropBufferToContentBounds(buffer, state2.contentBounds);
|
|
1557
|
-
} finally {
|
|
1558
|
-
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1559
|
-
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1560
|
-
});
|
|
1561
|
-
if (options.restore) {
|
|
1562
|
-
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
};
|
|
1566
945
|
var capturePageScreenshot = async (page, options = {}) => {
|
|
1567
946
|
const fullPage = Boolean(options.fullPage);
|
|
1568
947
|
const type = fullPage ? FORCED_FULLPAGE_TYPE : normalizeType(options.type);
|
|
@@ -1617,35 +996,23 @@ var capturePageScreenshot = async (page, options = {}) => {
|
|
|
1617
996
|
}
|
|
1618
997
|
};
|
|
1619
998
|
var captureExpandedFullPageScreenshot = async (page, options = {}) => {
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
logger.warning(`\u622A\u56FE\u8D28\u91CF\u5206\u6790\u5931\u8D25: ${error?.message || error}`);
|
|
1629
|
-
return null;
|
|
999
|
+
const state2 = await prepareExpandedFullPageScreenshot(page, options);
|
|
1000
|
+
try {
|
|
1001
|
+
return await capturePageScreenshot(page, {
|
|
1002
|
+
fullPage: true,
|
|
1003
|
+
type: options.type || "png",
|
|
1004
|
+
quality: options.quality,
|
|
1005
|
+
timeout: options.timeout,
|
|
1006
|
+
maxClipHeight: state2.targetHeight
|
|
1630
1007
|
});
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
if (
|
|
1636
|
-
|
|
1637
|
-
}
|
|
1638
|
-
if (attempt < attempts && retryDelayMs > 0) {
|
|
1639
|
-
logger.warning(`\u622A\u56FE\u7591\u4F3C\u5F02\u5E38(${issue})\uFF0C\u7B49\u5F85\u540E\u91CD\u8BD5: attempt=${attempt}/${attempts}`);
|
|
1640
|
-
await delay(retryDelayMs);
|
|
1008
|
+
} finally {
|
|
1009
|
+
await restoreAffixedElementsForExpandedScreenshot(page).catch((error) => {
|
|
1010
|
+
logger.warning(`\u79FB\u52A8\u7AEF\u5438\u9644\u5143\u7D20\u6062\u590D\u5931\u8D25: ${error?.message || error}`);
|
|
1011
|
+
});
|
|
1012
|
+
if (options.restore) {
|
|
1013
|
+
await restoreExpandedFullPageScreenshot(page, state2);
|
|
1641
1014
|
}
|
|
1642
1015
|
}
|
|
1643
|
-
if (lastIssue && lastAnalysis) {
|
|
1644
|
-
logger.warning(
|
|
1645
|
-
`\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)}`
|
|
1646
|
-
);
|
|
1647
|
-
}
|
|
1648
|
-
return lastBuffer;
|
|
1649
1016
|
};
|
|
1650
1017
|
|
|
1651
1018
|
// src/errors.js
|
|
@@ -2796,6 +2163,7 @@ var RuntimeEnv = {
|
|
|
2796
2163
|
|
|
2797
2164
|
// src/apify-kit.js
|
|
2798
2165
|
var logger3 = createInternalLogger("ApifyKit");
|
|
2166
|
+
var hasFleetAtHomeFlag = () => String(process.env.FLEET_AT_HOME || "").trim() !== "";
|
|
2799
2167
|
var resolveRuntimeContext = (input) => {
|
|
2800
2168
|
const rememberedState = RuntimeEnv.peekRememberedState();
|
|
2801
2169
|
const state2 = rememberedState || RuntimeEnv.parseInput(input || {});
|
|
@@ -2896,6 +2264,13 @@ async function createApifyKit() {
|
|
|
2896
2264
|
let lastPage = null;
|
|
2897
2265
|
const getRuntimeContext = () => resolveRuntimeContext(actorInput);
|
|
2898
2266
|
return {
|
|
2267
|
+
/**
|
|
2268
|
+
* 判断当前是否运行在受管自动化环境中。
|
|
2269
|
+
* Apify 云端保持使用 SDK 原生判断;本地 Chromium Cluster 使用 FLEET_AT_HOME 标记。
|
|
2270
|
+
*/
|
|
2271
|
+
isAtHome() {
|
|
2272
|
+
return Actor2.isAtHome() || hasFleetAtHomeFlag();
|
|
2273
|
+
},
|
|
2899
2274
|
/**
|
|
2900
2275
|
* 核心封装:执行步骤,带自动日志确认、失败截图处理和重试机制
|
|
2901
2276
|
*
|
|
@@ -5908,7 +5283,7 @@ var DefaultLaunch = {
|
|
|
5908
5283
|
log: logOptions = null,
|
|
5909
5284
|
runInHeadfulMode = false,
|
|
5910
5285
|
debugMode = false,
|
|
5911
|
-
|
|
5286
|
+
isRunningAtHome = false,
|
|
5912
5287
|
launcher = null,
|
|
5913
5288
|
preNavigationHooks = [],
|
|
5914
5289
|
postNavigationHooks = [],
|
|
@@ -5957,7 +5332,7 @@ var DefaultLaunch = {
|
|
|
5957
5332
|
};
|
|
5958
5333
|
const crawlerBaseOptions = {
|
|
5959
5334
|
...DEFAULT_CRAWLER_BASE_OPTIONS,
|
|
5960
|
-
headless: !runInHeadfulMode ||
|
|
5335
|
+
headless: !runInHeadfulMode || isRunningAtHome,
|
|
5961
5336
|
// 有 core.fingerprint 时走固定回放;否则退回 Crawlee 默认指纹模式。
|
|
5962
5337
|
browserPoolOptions: replayBrowserPoolOptions || {
|
|
5963
5338
|
useFingerprints: true,
|
|
@@ -6334,7 +5709,7 @@ var CloakLaunch = {
|
|
|
6334
5709
|
log: logOptions = null,
|
|
6335
5710
|
debugMode = false,
|
|
6336
5711
|
runInHeadfulMode = false,
|
|
6337
|
-
|
|
5712
|
+
isRunningAtHome = false,
|
|
6338
5713
|
launcher = null,
|
|
6339
5714
|
runtimeState = null,
|
|
6340
5715
|
fingerprintPlatform = "",
|
|
@@ -6350,7 +5725,7 @@ var CloakLaunch = {
|
|
|
6350
5725
|
const normalizedCloakOptions = normalizeObject2(cloakOptions);
|
|
6351
5726
|
const activeBrowsers = /* @__PURE__ */ new Set();
|
|
6352
5727
|
const patchedBrowsers = /* @__PURE__ */ new WeakSet();
|
|
6353
|
-
const defaultArgs =
|
|
5728
|
+
const defaultArgs = isRunningAtHome ? ["--no-sandbox", "--disable-setuid-sandbox"] : [];
|
|
6354
5729
|
const replayContext = resolveReplayableCloakProfile(runtimeState, {
|
|
6355
5730
|
fingerprintPlatform,
|
|
6356
5731
|
cloakOptions: normalizedCloakOptions
|
|
@@ -6364,7 +5739,7 @@ var CloakLaunch = {
|
|
|
6364
5739
|
const hasExplicitProxy = hasOwn(normalizedCloakOptions, "proxy");
|
|
6365
5740
|
const proxyLaunchState = hasExplicitProxy ? resolveLaunchTraffic({ proxyConfiguration, debugMode, useMeter: false }) : resolveLaunchTraffic({ proxyConfiguration, debugMode });
|
|
6366
5741
|
const proxy = hasExplicitProxy ? normalizedCloakOptions.proxy : proxyLaunchState.launchProxy;
|
|
6367
|
-
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode ||
|
|
5742
|
+
const headless = hasOwn(normalizedCloakOptions, "headless") ? normalizedCloakOptions.headless : !runInHeadfulMode || isRunningAtHome;
|
|
6368
5743
|
const enableByPassLogger = Boolean(logOptions && logOptions.enable);
|
|
6369
5744
|
const mergedCloakOptions = {
|
|
6370
5745
|
...normalizedCloakOptions,
|
|
@@ -10321,7 +9696,7 @@ var watermarkifyScreenshotBuffer = async (buffer, meta, page = null, options = {
|
|
|
10321
9696
|
};
|
|
10322
9697
|
|
|
10323
9698
|
// src/internals/compression.js
|
|
10324
|
-
import { Jimp
|
|
9699
|
+
import { Jimp, JimpMime, ResizeStrategy } from "jimp";
|
|
10325
9700
|
var logger15 = createInternalLogger("Compression");
|
|
10326
9701
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
10327
9702
|
var DEFAULT_SCREENSHOT_OUTPUT_TYPE = "jpeg";
|
|
@@ -10391,7 +9766,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
10391
9766
|
mode: ResizeStrategy.BILINEAR
|
|
10392
9767
|
});
|
|
10393
9768
|
}
|
|
10394
|
-
const buffer = await image.getBuffer(
|
|
9769
|
+
const buffer = await image.getBuffer(JimpMime.jpeg, { quality });
|
|
10395
9770
|
return {
|
|
10396
9771
|
buffer,
|
|
10397
9772
|
bytes: getBase64BytesFromBuffer(buffer),
|
|
@@ -10403,7 +9778,7 @@ var encodeJpeg = async (sourceImage, compression, scale, quality) => {
|
|
|
10403
9778
|
};
|
|
10404
9779
|
};
|
|
10405
9780
|
var compressImageBuffer = async (buffer, compression) => {
|
|
10406
|
-
const sourceImage = await
|
|
9781
|
+
const sourceImage = await Jimp.read(buffer);
|
|
10407
9782
|
const maxQuality = toJpegQuality(compression.quality);
|
|
10408
9783
|
const minQuality = Math.min(maxQuality, toJpegQuality(compression.minQuality));
|
|
10409
9784
|
let quality = maxQuality;
|