@skrillex1224/playwright-toolkit 2.1.37 → 2.1.39

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/dist/index.cjs CHANGED
@@ -57,10 +57,220 @@ var Status = {
57
57
  var FAILED_KEY_SEPARATOR = "::<@>::";
58
58
  var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
59
59
 
60
- // src/internal/logger.js
60
+ // src/logger.js
61
61
  var import_crawlee = require("crawlee");
62
+ var formatLine = (prefix, icon, message) => {
63
+ const parts = [];
64
+ if (prefix) parts.push(`[${prefix}]`);
65
+ if (icon) parts.push(icon);
66
+ if (message) parts.push(message);
67
+ return parts.join(" ").trim();
68
+ };
69
+ var ANSI = {
70
+ reset: "\x1B[0m",
71
+ gray: "\x1B[90m",
72
+ red: "\x1B[31m",
73
+ green: "\x1B[32m",
74
+ yellow: "\x1B[33m",
75
+ blue: "\x1B[34m",
76
+ cyan: "\x1B[36m"
77
+ };
78
+ var colorize = (text, color) => {
79
+ if (!text || !color) return text;
80
+ return `${color}${text}${ANSI.reset}`;
81
+ };
82
+ var createBaseLogger = (prefix = "") => {
83
+ const name = prefix ? String(prefix) : "";
84
+ return {
85
+ info: (message) => import_crawlee.log.info(colorize(formatLine(name, "\u{1F4D6}", message), ANSI.cyan)),
86
+ success: (message) => import_crawlee.log.info(colorize(formatLine(name, "\u2705", message), ANSI.green)),
87
+ warning: (message) => import_crawlee.log.warning(colorize(formatLine(name, "\u26A0\uFE0F", message), ANSI.yellow)),
88
+ error: (message) => import_crawlee.log.error(colorize(formatLine(name, "\u274C", message), ANSI.red)),
89
+ debug: (message) => import_crawlee.log.debug(colorize(formatLine(name, "\u{1F539}", message), ANSI.gray)),
90
+ start: (message) => import_crawlee.log.info(colorize(formatLine(name, "\u{1F537}", message), ANSI.blue))
91
+ };
92
+ };
93
+ var STEP_PREFIX = "\u6B65\u9AA4:";
94
+ var STEP_SEPARATOR = " | ";
95
+ var STEP_EMOJIS = [
96
+ { match: "\u4EFB\u52A1", emoji: "\u{1F9ED}" },
97
+ { match: "\u8FD0\u884C\u6A21\u5F0F", emoji: "\u{1F9E9}" },
98
+ { match: "\u767B\u5F55", emoji: "\u{1F510}" },
99
+ { match: "\u73AF\u5883", emoji: "\u{1F9EA}" },
100
+ { match: "\u8F93\u5165", emoji: "\u2328\uFE0F" },
101
+ { match: "\u53D1\u9001", emoji: "\u{1F4E4}" },
102
+ { match: "\u54CD\u5E94\u76D1\u542C", emoji: "\u{1F4E1}" },
103
+ { match: "\u7B49\u5F85\u54CD\u5E94", emoji: "\u23F3" },
104
+ { match: "\u6D41\u5F0F", emoji: "\u{1F9F5}" },
105
+ { match: "\u5F15\u7528", emoji: "\u{1F4CE}" },
106
+ { match: "\u622A\u56FE", emoji: "\u{1F5BC}\uFE0F" },
107
+ { match: "\u5206\u4EAB\u94FE\u63A5", emoji: "\u{1F517}" },
108
+ { match: "\u6570\u636E\u63A8\u9001", emoji: "\u{1F4E6}" },
109
+ { match: "\u5F39\u7A97", emoji: "\u{1FA9F}" }
110
+ ];
111
+ var STATUS_EMOJIS = [
112
+ { match: "\u5F00\u59CB", emoji: "\u{1F680}" },
113
+ { match: "\u5B8C\u6210", emoji: "\u2705" },
114
+ { match: "\u6210\u529F", emoji: "\u2705" },
115
+ { match: "\u5931\u8D25", emoji: "\u274C" },
116
+ { match: "\u8DF3\u8FC7", emoji: "\u23ED\uFE0F" },
117
+ { match: "\u8D85\u65F6", emoji: "\u23F1\uFE0F" },
118
+ { match: "\u91CD\u8BD5", emoji: "\u{1F501}" },
119
+ { match: "\u7ED3\u675F", emoji: "\u{1F3C1}" },
120
+ { match: "\u5DF2\u914D\u7F6E", emoji: "\u{1F9F7}" },
121
+ { match: "\u5DF2\u68C0\u6D4B", emoji: "\u{1F50E}" }
122
+ ];
123
+ var toErrorMessage = (error) => {
124
+ if (!error) return "";
125
+ if (error instanceof Error) return error.message;
126
+ if (typeof error === "string") return error;
127
+ try {
128
+ return JSON.stringify(error);
129
+ } catch {
130
+ return String(error);
131
+ }
132
+ };
133
+ var decorateLabel = (label, mappings) => {
134
+ if (!label) return "";
135
+ const mapping = mappings.find((item) => label.includes(item.match));
136
+ if (!mapping) return label;
137
+ return `${mapping.emoji} ${label}`;
138
+ };
139
+ var normalizeSnippet = (snippet, maxLen = 120) => {
140
+ if (!snippet) return "";
141
+ const text = String(snippet).replace(/\s+/g, " ").trim();
142
+ if (!text) return "";
143
+ const cleaned = text.replace(/"/g, "'");
144
+ if (cleaned.length <= maxLen) return cleaned;
145
+ return `${cleaned.slice(0, maxLen)}...`;
146
+ };
147
+ var buildStepLine = (step, status, details = []) => {
148
+ const parts = [];
149
+ const decoratedStep = step ? decorateLabel(step, STEP_EMOJIS) : "";
150
+ const base = decoratedStep ? `${STEP_PREFIX} ${decoratedStep}` : STEP_PREFIX;
151
+ parts.push(base.trim());
152
+ if (status) parts.push(decorateLabel(status, STATUS_EMOJIS));
153
+ const detailParts = details.filter(Boolean);
154
+ if (detailParts.length > 0) {
155
+ parts.push(...detailParts);
156
+ }
157
+ return parts.join(STEP_SEPARATOR);
158
+ };
159
+ var createThrottle = () => {
160
+ const lastMap = /* @__PURE__ */ new Map();
161
+ return (key, intervalMs, fn) => {
162
+ const now = Date.now();
163
+ const last = lastMap.get(key) || 0;
164
+ if (now - last >= intervalMs) {
165
+ lastMap.set(key, now);
166
+ fn();
167
+ }
168
+ };
169
+ };
170
+ var createTemplateLogger = (baseLogger = createBaseLogger()) => {
171
+ const throttle = createThrottle();
172
+ const info = (line) => baseLogger.info(line);
173
+ const success = (line) => baseLogger.success(line);
174
+ const warning = (line) => baseLogger.warning(line);
175
+ const error = (line) => baseLogger.error(line);
176
+ const debug = (line) => baseLogger.debug(line);
177
+ const start = (line) => baseLogger.start(line);
178
+ const stepInfo = (step, status, details = []) => info(buildStepLine(step, status, details));
179
+ const stepSuccess = (step, status, details = []) => success(buildStepLine(step, status, details));
180
+ const stepWarn = (step, status, details = []) => warning(buildStepLine(step, status, details));
181
+ const stepError = (step, status, details = []) => error(buildStepLine(step, status, details));
182
+ const stepStart = (step, status, details = []) => start(buildStepLine(step, status, details));
183
+ return {
184
+ step: (step, status, details, level = "info") => {
185
+ if (level === "error") return stepError(step, status, details);
186
+ if (level === "warn" || level === "warning") return stepWarn(step, status, details);
187
+ return stepInfo(step, status, details);
188
+ },
189
+ taskStart: (url) => stepStart("\u4EFB\u52A1", "\u5F00\u59CB", [url ? `url=${url}` : ""]),
190
+ taskSuccess: () => stepSuccess("\u4EFB\u52A1", "\u5B8C\u6210"),
191
+ taskFail: (url, err) => stepError("\u4EFB\u52A1", "\u5931\u8D25", [
192
+ url ? `url=${url}` : "",
193
+ err ? `err=${toErrorMessage(err)}` : ""
194
+ ]),
195
+ runtimeHeadless: () => stepWarn("\u8FD0\u884C\u6A21\u5F0F", "Apify \u73AF\u5883\u5F3A\u5236\u65E0\u5934"),
196
+ loginInjectSuccess: (detail) => stepSuccess("\u767B\u5F55\u6001\u6CE8\u5165", "\u6210\u529F", [detail ? `detail=${detail}` : ""]),
197
+ loginInjectSkip: (reason) => stepWarn("\u767B\u5F55\u6001\u6CE8\u5165", "\u8DF3\u8FC7", [reason ? `\u539F\u56E0=${reason}` : ""]),
198
+ loginInjectFail: (err) => stepError("\u767B\u5F55\u6001\u6CE8\u5165", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
199
+ loginVerifySuccess: (detail) => stepSuccess("\u767B\u5F55\u9A8C\u8BC1", "\u6210\u529F", [detail ? `detail=${detail}` : ""]),
200
+ loginVerifySkip: (reason) => stepWarn("\u767B\u5F55\u9A8C\u8BC1", "\u8DF3\u8FC7", [reason ? `\u539F\u56E0=${reason}` : ""]),
201
+ loginVerifyFail: (err) => stepError("\u767B\u5F55\u9A8C\u8BC1", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
202
+ envCheckSuccess: (detail) => stepSuccess("\u73AF\u5883\u68C0\u67E5", "\u6210\u529F", [detail ? `detail=${detail}` : ""]),
203
+ envCheckFail: (err) => stepError("\u73AF\u5883\u68C0\u67E5", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
204
+ inputQuery: (query) => stepStart("\u8F93\u5165\u67E5\u8BE2", "\u5F00\u59CB", [query ? `query=${query}` : ""]),
205
+ sendAction: () => stepInfo("\u53D1\u9001\u8BF7\u6C42", "\u70B9\u51FB\u53D1\u9001"),
206
+ responseListenStart: (label, timeoutSec) => stepStart("\u54CD\u5E94\u76D1\u542C", "\u5F00\u59CB", [
207
+ label ? `\u76EE\u6807=${label}` : "",
208
+ timeoutSec ? `timeout=${timeoutSec}s` : ""
209
+ ]),
210
+ responseListenReady: (label) => stepInfo("\u54CD\u5E94\u76D1\u542C", "\u5DF2\u914D\u7F6E", [label ? `\u76EE\u6807=${label}` : ""]),
211
+ responseListenDetected: (label) => stepSuccess("\u54CD\u5E94\u76D1\u542C", "\u5DF2\u68C0\u6D4B", [label ? `\u76EE\u6807=${label}` : ""]),
212
+ responseListenTimeout: (label, err) => stepError("\u54CD\u5E94\u76D1\u542C", "\u8D85\u65F6", [
213
+ label ? `\u76EE\u6807=${label}` : "",
214
+ err ? `err=${toErrorMessage(err)}` : ""
215
+ ]),
216
+ responseListenEnd: (label) => stepInfo("\u54CD\u5E94\u76D1\u542C", "\u7ED3\u675F", [label ? `\u76EE\u6807=${label}` : ""]),
217
+ streamChunk: (length, snippet) => throttle(
218
+ "stream-chunk",
219
+ 2e3,
220
+ () => stepInfo("\u6D41\u5F0F\u7247\u6BB5", "", [
221
+ length !== void 0 ? `len=${length}` : "",
222
+ snippet ? `preview="${normalizeSnippet(snippet)}"` : ""
223
+ ])
224
+ ),
225
+ streamEventsParsed: (count) => throttle(
226
+ "stream-events",
227
+ 4e3,
228
+ () => stepInfo("\u6D41\u5F0F\u4E8B\u4EF6\u89E3\u6790", "\u5B8C\u6210", [count !== void 0 ? `count=${count}` : ""])
229
+ ),
230
+ streamCompleteEvent: () => stepSuccess("\u6D41\u5F0F\u4E8B\u4EF6", "\u5B8C\u6210\u4E8B\u4EF6\u5DF2\u6355\u83B7"),
231
+ streamEnd: () => stepInfo("\u6D41\u5F0F\u54CD\u5E94", "\u7ED3\u675F"),
232
+ responseWaitStart: (label) => stepStart("\u7B49\u5F85\u54CD\u5E94", "\u5F00\u59CB", [label ? `\u76EE\u6807=${label}` : ""]),
233
+ responseWaitSuccess: (label) => stepSuccess("\u7B49\u5F85\u54CD\u5E94", "\u5B8C\u6210", [label ? `\u76EE\u6807=${label}` : ""]),
234
+ responseWaitFail: (label, err) => stepWarn("\u7B49\u5F85\u54CD\u5E94", "\u5931\u8D25", [
235
+ label ? `\u76EE\u6807=${label}` : "",
236
+ err ? `err=${toErrorMessage(err)}` : ""
237
+ ]),
238
+ responseWaitRetry: (label, attempt) => stepWarn("\u7B49\u5F85\u54CD\u5E94", "\u91CD\u8BD5", [
239
+ label ? `\u76EE\u6807=${label}` : "",
240
+ attempt !== void 0 ? `\u5C1D\u8BD5=${attempt}` : ""
241
+ ]),
242
+ referenceExpandStart: (label) => stepStart("\u5F15\u7528\u5C55\u5F00", "\u5F00\u59CB", [label ? `\u76EE\u6807=${label}` : ""]),
243
+ referenceExpandFail: (err) => stepWarn("\u5F15\u7528\u5C55\u5F00", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
244
+ screenshotStart: () => stepStart("\u622A\u56FE", "\u5F00\u59CB"),
245
+ screenshotSuccess: () => stepSuccess("\u622A\u56FE", "\u6210\u529F"),
246
+ screenshotFail: (err) => stepWarn("\u622A\u56FE", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
247
+ shareStart: () => stepStart("\u5206\u4EAB\u94FE\u63A5", "\u5F00\u59CB"),
248
+ shareProgress: (label) => stepInfo("\u5206\u4EAB\u94FE\u63A5", "\u6B65\u9AA4", [label ? `\u6B65\u9AA4=${label}` : ""]),
249
+ shareSuccess: (link) => stepSuccess("\u5206\u4EAB\u94FE\u63A5", "\u6210\u529F", [link ? `\u94FE\u63A5=${link}` : ""]),
250
+ shareFail: (err) => stepWarn("\u5206\u4EAB\u94FE\u63A5", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
251
+ shareSkip: (reason) => stepWarn("\u5206\u4EAB\u94FE\u63A5", "\u8DF3\u8FC7", [reason ? `\u539F\u56E0=${reason}` : ""]),
252
+ dataPushSuccess: (label) => stepSuccess("\u6570\u636E\u63A8\u9001", "\u6210\u529F", [label ? `\u8BF4\u660E=${label}` : ""]),
253
+ dataPushFail: (err) => stepError("\u6570\u636E\u63A8\u9001", "\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
254
+ popupDetected: (detail) => stepWarn("\u5F39\u7A97\u5904\u7406", "\u68C0\u6D4B\u5230\u906E\u7F69", [detail ? `detail=${detail}` : ""]),
255
+ popupCloseAttempt: (detail) => stepInfo("\u5F39\u7A97\u5904\u7406", "\u5C1D\u8BD5\u5173\u95ED", [detail ? `detail=${detail}` : ""]),
256
+ popupCloseSuccess: () => stepSuccess("\u5F39\u7A97\u5904\u7406", "\u5173\u95ED\u5B8C\u6210"),
257
+ popupCloseFail: (err) => stepWarn("\u5F39\u7A97\u5904\u7406", "\u5173\u95ED\u5931\u8D25", [err ? `err=${toErrorMessage(err)}` : ""]),
258
+ info: (message) => info(message),
259
+ success: (message) => success(message),
260
+ warning: (message) => warning(message),
261
+ error: (message) => error(message),
262
+ debug: (message) => debug(message),
263
+ start: (message) => start(message)
264
+ };
265
+ };
266
+ var Logger = {
267
+ ...createBaseLogger(),
268
+ useTemplate: () => createTemplateLogger()
269
+ };
270
+
271
+ // src/internal/logger.js
62
272
  function createLogger(moduleName) {
63
- const prefix = `[${moduleName}]`;
273
+ const baseLogger = createBaseLogger(moduleName);
64
274
  return {
65
275
  /**
66
276
  * 方法开始日志
@@ -69,7 +279,7 @@ function createLogger(moduleName) {
69
279
  */
70
280
  start(methodName, params = "") {
71
281
  const paramStr = params ? ` (${params})` : "";
72
- import_crawlee.log.info(`${prefix} \u{1F537} ${methodName} \u5F00\u59CB${paramStr}`);
282
+ baseLogger.start(`${methodName} \u5F00\u59CB${paramStr}`);
73
283
  },
74
284
  /**
75
285
  * 方法成功日志
@@ -78,7 +288,7 @@ function createLogger(moduleName) {
78
288
  */
79
289
  success(methodName, result = "") {
80
290
  const resultStr = result ? ` (${result})` : "";
81
- import_crawlee.log.info(`${prefix} \u2705 ${methodName} \u5B8C\u6210${resultStr}`);
291
+ baseLogger.success(`${methodName} \u5B8C\u6210${resultStr}`);
82
292
  },
83
293
  /**
84
294
  * 方法失败日志
@@ -87,28 +297,28 @@ function createLogger(moduleName) {
87
297
  */
88
298
  fail(methodName, error) {
89
299
  const message = error instanceof Error ? error.message : error;
90
- import_crawlee.log.error(`${prefix} \u274C ${methodName} \u5931\u8D25: ${message}`);
300
+ baseLogger.error(`${methodName} \u5931\u8D25: ${message}`);
91
301
  },
92
302
  /**
93
303
  * 调试日志
94
304
  * @param {string} message - 详情
95
305
  */
96
306
  debug(message) {
97
- import_crawlee.log.debug(`${prefix} \u{1F539} ${message}`);
307
+ baseLogger.debug(message);
98
308
  },
99
309
  /**
100
310
  * 警告日志
101
311
  * @param {string} message - 警告信息
102
312
  */
103
313
  warn(message) {
104
- import_crawlee.log.warning(`${prefix} \u26A0\uFE0F ${message}`);
314
+ baseLogger.warning(message);
105
315
  },
106
316
  /**
107
317
  * 普通信息日志
108
318
  * @param {string} message - 信息
109
319
  */
110
320
  info(message) {
111
- import_crawlee.log.info(`${prefix} \u{1F4D6} ${message}`);
321
+ baseLogger.info(message);
112
322
  }
113
323
  };
114
324
  }
@@ -357,6 +567,8 @@ var Utils = {
357
567
  * @param {import('playwright').Page} page - Playwright page 对象
358
568
  * @param {Object} [options] - 配置选项
359
569
  * @param {number} [options.buffer] - 额外缓冲高度 (默认: 视口高度的一半)
570
+ * @param {boolean} [options.restore] - 截图后是否恢复页面高度和样式 (默认: false)
571
+ * @param {number} [options.maxHeight] - 最大截图高度 (默认: 8000px)
360
572
  * @returns {Promise<string>} - base64 编码的 PNG 图片
361
573
  */
362
574
  async fullPageScreenshot(page, options = {}) {
@@ -364,15 +576,17 @@ var Utils = {
364
576
  const originalViewport = page.viewportSize();
365
577
  const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
366
578
  const buffer = options.buffer ?? defaultBuffer;
579
+ const restore = options.restore ?? false;
580
+ const maxHeight = options.maxHeight ?? 8e3;
367
581
  try {
368
582
  const maxScrollHeight = await page.evaluate(() => {
369
- let maxHeight = document.body.scrollHeight;
583
+ let maxHeight2 = document.body.scrollHeight;
370
584
  document.querySelectorAll("*").forEach((el) => {
371
585
  const style = window.getComputedStyle(el);
372
586
  const overflowY = style.overflowY;
373
587
  if ((overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight) {
374
- if (el.scrollHeight > maxHeight) {
375
- maxHeight = el.scrollHeight;
588
+ if (el.scrollHeight > maxHeight2) {
589
+ maxHeight2 = el.scrollHeight;
376
590
  }
377
591
  el.dataset.pkOrigOverflow = el.style.overflow;
378
592
  el.dataset.pkOrigHeight = el.style.height;
@@ -383,11 +597,12 @@ var Utils = {
383
597
  el.style.maxHeight = "none";
384
598
  }
385
599
  });
386
- return maxHeight;
600
+ return maxHeight2;
387
601
  });
602
+ const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
388
603
  await page.setViewportSize({
389
604
  width: originalViewport?.width || 1280,
390
- height: maxScrollHeight + buffer
605
+ height: targetHeight
391
606
  });
392
607
  await (0, import_delay.default)(1e3);
393
608
  const buffer_ = await page.screenshot({
@@ -397,19 +612,21 @@ var Utils = {
397
612
  logger2.success("fullPageScreenshot", `captured ${Math.round(buffer_.length / 1024)} KB`);
398
613
  return buffer_.toString("base64");
399
614
  } finally {
400
- await page.evaluate(() => {
401
- document.querySelectorAll(".__pk_expanded__").forEach((el) => {
402
- el.style.overflow = el.dataset.pkOrigOverflow || "";
403
- el.style.height = el.dataset.pkOrigHeight || "";
404
- el.style.maxHeight = el.dataset.pkOrigMaxHeight || "";
405
- delete el.dataset.pkOrigOverflow;
406
- delete el.dataset.pkOrigHeight;
407
- delete el.dataset.pkOrigMaxHeight;
408
- el.classList.remove("__pk_expanded__");
615
+ if (restore) {
616
+ await page.evaluate(() => {
617
+ document.querySelectorAll(".__pk_expanded__").forEach((el) => {
618
+ el.style.overflow = el.dataset.pkOrigOverflow || "";
619
+ el.style.height = el.dataset.pkOrigHeight || "";
620
+ el.style.maxHeight = el.dataset.pkOrigMaxHeight || "";
621
+ delete el.dataset.pkOrigOverflow;
622
+ delete el.dataset.pkOrigHeight;
623
+ delete el.dataset.pkOrigMaxHeight;
624
+ el.classList.remove("__pk_expanded__");
625
+ });
409
626
  });
410
- });
411
- if (originalViewport) {
412
- await page.setViewportSize(originalViewport);
627
+ if (originalViewport) {
628
+ await page.setViewportSize(originalViewport);
629
+ }
413
630
  }
414
631
  }
415
632
  }
@@ -1532,6 +1749,181 @@ function isIgnorableError(error) {
1532
1749
  return msg.includes("already handled") || msg.includes("Target closed") || msg.includes("closed");
1533
1750
  }
1534
1751
 
1752
+ // src/mutation.js
1753
+ var import_uuid2 = require("uuid");
1754
+ var logger9 = createLogger("Mutation");
1755
+ function generateKey(prefix) {
1756
+ return `__${prefix}_${(0, import_uuid2.v4)().replace(/-/g, "_")}`;
1757
+ }
1758
+ var Mutation = {
1759
+ /**
1760
+ * 等待 DOM 元素稳定(无变化)
1761
+ * 使用 MutationObserver 监控指定元素,当元素持续一段时间无变化时 resolve
1762
+ *
1763
+ * @param {import('playwright').Page} page - Playwright page 对象
1764
+ * @param {string | string[]} selectors - 要监控的 CSS 选择器,单个或多个
1765
+ * @param {Object} [options] - 配置选项
1766
+ * @param {number} [options.stableTime] - 无变化持续时间后 resolve (毫秒, 默认: 5000)
1767
+ * @param {number} [options.timeout] - 整体超时时间 (毫秒, 默认: 60000)
1768
+ * @param {Function} [options.onMutation] - 变化时的回调钩子 (mutationCount: number) => void
1769
+ * @returns {Promise<{ mutationCount: number, stableTime: number }>} - 返回变化次数和稳定时长
1770
+ */
1771
+ async waitForStable(page, selectors, options = {}) {
1772
+ const selectorList = Array.isArray(selectors) ? selectors : [selectors];
1773
+ const stableTime = options.stableTime ?? 5e3;
1774
+ const timeout = options.timeout ?? 6e4;
1775
+ const onMutation = options.onMutation;
1776
+ logger9.start("waitForStable", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668, \u7A33\u5B9A\u65F6\u95F4=${stableTime}ms`);
1777
+ const eventName = generateKey("pk_mut_evt");
1778
+ const callbackName = generateKey("pk_mut_cb");
1779
+ if (onMutation) {
1780
+ try {
1781
+ await page.exposeFunction(callbackName, (count) => {
1782
+ try {
1783
+ onMutation(count);
1784
+ } catch (e) {
1785
+ }
1786
+ });
1787
+ } catch (e) {
1788
+ }
1789
+ }
1790
+ const result = await page.evaluate(
1791
+ async ({ selectorList: selectorList2, stableTime: stableTime2, timeout: timeout2, eventName: eventName2, callbackName: callbackName2, hasCallback }) => {
1792
+ return new Promise((resolve, reject) => {
1793
+ let mutationCount = 0;
1794
+ let stableTimer = null;
1795
+ let timeoutTimer = null;
1796
+ const observers = [];
1797
+ const cleanup = () => {
1798
+ observers.forEach((obs) => obs.disconnect());
1799
+ if (stableTimer) clearTimeout(stableTimer);
1800
+ if (timeoutTimer) clearTimeout(timeoutTimer);
1801
+ };
1802
+ const resetStableTimer = () => {
1803
+ if (stableTimer) clearTimeout(stableTimer);
1804
+ stableTimer = setTimeout(() => {
1805
+ cleanup();
1806
+ resolve({ mutationCount, stableTime: stableTime2 });
1807
+ }, stableTime2);
1808
+ };
1809
+ timeoutTimer = setTimeout(() => {
1810
+ cleanup();
1811
+ reject(new Error(`waitForStable \u8D85\u65F6 (${timeout2}ms), \u5DF2\u68C0\u6D4B\u5230 ${mutationCount} \u6B21\u53D8\u5316`));
1812
+ }, timeout2);
1813
+ selectorList2.forEach((selector) => {
1814
+ const elements = document.querySelectorAll(selector);
1815
+ elements.forEach((element) => {
1816
+ const observer = new MutationObserver((mutations) => {
1817
+ mutationCount += mutations.length;
1818
+ if (hasCallback && window[callbackName2]) {
1819
+ window[callbackName2](mutationCount);
1820
+ }
1821
+ resetStableTimer();
1822
+ });
1823
+ observer.observe(element, {
1824
+ childList: true,
1825
+ subtree: true,
1826
+ characterData: true,
1827
+ attributes: true
1828
+ });
1829
+ observers.push(observer);
1830
+ });
1831
+ });
1832
+ if (observers.length === 0) {
1833
+ cleanup();
1834
+ resolve({ mutationCount: 0, stableTime: 0 });
1835
+ return;
1836
+ }
1837
+ resetStableTimer();
1838
+ });
1839
+ },
1840
+ { selectorList, stableTime, timeout, eventName, callbackName, hasCallback: !!onMutation }
1841
+ );
1842
+ logger9.success("waitForStable", `DOM \u7A33\u5B9A, \u603B\u5171 ${result.mutationCount} \u6B21\u53D8\u5316`);
1843
+ return result;
1844
+ },
1845
+ /**
1846
+ * 创建一个持续监控 DOM 变化的监控器
1847
+ *
1848
+ * @param {import('playwright').Page} page - Playwright page 对象
1849
+ * @param {string | string[]} selectors - 要监控的 CSS 选择器
1850
+ * @param {Object} [options] - 配置选项
1851
+ * @param {Function} [options.onMutation] - 变化时的回调 (mutationCount: number) => void
1852
+ * @returns {Promise<{ stop: () => Promise<{ totalMutations: number }> }>} - 返回停止函数
1853
+ */
1854
+ async createMonitor(page, selectors, options = {}) {
1855
+ const selectorList = Array.isArray(selectors) ? selectors : [selectors];
1856
+ const onMutation = options.onMutation;
1857
+ logger9.start("createMonitor", `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668`);
1858
+ const monitorKey = generateKey("pk_mon");
1859
+ const callbackName = generateKey("pk_mon_cb");
1860
+ const cleanerName = generateKey("pk_mon_clean");
1861
+ if (onMutation) {
1862
+ try {
1863
+ await page.exposeFunction(callbackName, (count) => {
1864
+ try {
1865
+ onMutation(count);
1866
+ } catch (e) {
1867
+ }
1868
+ });
1869
+ } catch (e) {
1870
+ }
1871
+ }
1872
+ await page.evaluate(({ selectorList: selectorList2, monitorKey: monitorKey2, callbackName: callbackName2, cleanerName: cleanerName2, hasCallback }) => {
1873
+ const monitor = {
1874
+ observers: [],
1875
+ totalMutations: 0,
1876
+ running: true
1877
+ };
1878
+ selectorList2.forEach((selector) => {
1879
+ const elements = document.querySelectorAll(selector);
1880
+ elements.forEach((element) => {
1881
+ const observer = new MutationObserver((mutations) => {
1882
+ if (!monitor.running) return;
1883
+ monitor.totalMutations += mutations.length;
1884
+ if (hasCallback && window[callbackName2]) {
1885
+ window[callbackName2](monitor.totalMutations);
1886
+ }
1887
+ });
1888
+ observer.observe(element, {
1889
+ childList: true,
1890
+ subtree: true,
1891
+ characterData: true,
1892
+ attributes: true
1893
+ });
1894
+ monitor.observers.push(observer);
1895
+ });
1896
+ });
1897
+ window[monitorKey2] = monitor;
1898
+ window[cleanerName2] = () => {
1899
+ monitor.running = false;
1900
+ monitor.observers.forEach((obs) => obs.disconnect());
1901
+ const total = monitor.totalMutations;
1902
+ delete window[monitorKey2];
1903
+ delete window[cleanerName2];
1904
+ return total;
1905
+ };
1906
+ }, { selectorList, monitorKey, callbackName, cleanerName, hasCallback: !!onMutation });
1907
+ logger9.success("createMonitor", "\u76D1\u63A7\u5668\u5DF2\u542F\u52A8");
1908
+ return {
1909
+ stop: async () => {
1910
+ let totalMutations = 0;
1911
+ try {
1912
+ totalMutations = await page.evaluate((cleanerName2) => {
1913
+ if (window[cleanerName2]) {
1914
+ return window[cleanerName2]();
1915
+ }
1916
+ return 0;
1917
+ }, cleanerName);
1918
+ } catch (e) {
1919
+ }
1920
+ logger9.success("createMonitor.stop", `\u76D1\u63A7\u5DF2\u505C\u6B62, \u5171 ${totalMutations} \u6B21\u53D8\u5316`);
1921
+ return { totalMutations };
1922
+ }
1923
+ };
1924
+ }
1925
+ };
1926
+
1535
1927
  // index.js
1536
1928
  var usePlaywrightToolKit = () => {
1537
1929
  return {
@@ -1545,7 +1937,9 @@ var usePlaywrightToolKit = () => {
1545
1937
  Captcha,
1546
1938
  Sse,
1547
1939
  Errors: errors_exports,
1548
- Interception
1940
+ Interception,
1941
+ Mutation,
1942
+ Logger
1549
1943
  };
1550
1944
  };
1551
1945
  // Annotate the CommonJS export names for ESM import in node: