@skrillex1224/playwright-toolkit 2.1.165 → 2.1.166

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
@@ -2803,6 +2803,196 @@ var Mutation = {
2803
2803
  logger11.success("waitForStable", `DOM \u7A33\u5B9A, \u603B\u5171 ${result.mutationCount} \u6B21\u53D8\u5316${result.wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
2804
2804
  return result;
2805
2805
  },
2806
+ /**
2807
+ * 等待跨 root DOM 元素稳定(主文档 + iframe 内容)
2808
+ * 通过轮询快照检测变化,适配 iframe / shadow 场景
2809
+ *
2810
+ * @param {import('playwright').Page} page - Playwright page 对象
2811
+ * @param {string | string[]} selectors - 要监控的 CSS 选择器,单个或多个(建议传 iframe 选择器)
2812
+ * @param {Object} [options] - 配置选项(签名与 waitForStable 保持一致)
2813
+ * @param {number} [options.initialTimeout] - 等待元素出现的超时 (毫秒, 默认: 60000)
2814
+ * @param {number} [options.stableTime] - 无变化持续时间后 resolve (毫秒, 默认: 10000)
2815
+ * @param {number} [options.timeout] - 整体超时时间 (毫秒, 默认: 180000)
2816
+ * @param {Function} [options.onMutation] - 变化时的回调钩子
2817
+ * @returns {Promise<{ mutationCount: number, stableTime: number, wasPaused: boolean }>}
2818
+ */
2819
+ async waitForStableAcrossRoots(page, selectors, options = {}) {
2820
+ const selectorList = Array.isArray(selectors) ? selectors : [selectors];
2821
+ const selectorQuery = selectorList.join(",");
2822
+ const initialTimeout = options.initialTimeout ?? 60 * 1e3;
2823
+ const waitForStableTime = options.stableTime ?? 10 * 1e3;
2824
+ const overallTimeout = options.timeout ?? 180 * 1e3;
2825
+ const onMutation = options.onMutation;
2826
+ const pollInterval = 500;
2827
+ const sleep = (ms) => new Promise((resolve) => {
2828
+ setTimeout(resolve, ms);
2829
+ });
2830
+ const truncate = (value, max = 800) => {
2831
+ const text = String(value || "");
2832
+ if (text.length <= max) return text;
2833
+ return `${text.slice(0, max)}...`;
2834
+ };
2835
+ const buildState = async () => {
2836
+ return await page.evaluate(({ selectorList: selectorList2 }) => {
2837
+ const normalizeText = (value) => String(value || "").replace(/\s+/g, " ").trim();
2838
+ const tail = (value, max = 512) => {
2839
+ const text = String(value || "");
2840
+ if (text.length <= max) return text;
2841
+ return text.slice(text.length - max);
2842
+ };
2843
+ const safeFrameId = (frameEl) => {
2844
+ const id = String(frameEl?.id || "").trim();
2845
+ if (id) return id;
2846
+ const name = String(frameEl?.name || "").trim();
2847
+ if (name) return name;
2848
+ return "no-id";
2849
+ };
2850
+ const items = [];
2851
+ selectorList2.forEach((selector) => {
2852
+ let nodes = [];
2853
+ try {
2854
+ nodes = Array.from(document.querySelectorAll(selector));
2855
+ } catch {
2856
+ return;
2857
+ }
2858
+ nodes.forEach((node, index) => {
2859
+ const isIframe = node?.tagName === "IFRAME";
2860
+ let text = "";
2861
+ let html = "";
2862
+ let source = "main";
2863
+ let path = `${selector}[${index}]`;
2864
+ if (isIframe) {
2865
+ source = "iframe";
2866
+ path = `${selector}[${index}]::iframe(${safeFrameId(node)})`;
2867
+ try {
2868
+ const frameDoc = node.contentDocument;
2869
+ const frameRoot = frameDoc?.body || frameDoc?.documentElement;
2870
+ if (frameRoot) {
2871
+ text = normalizeText(frameRoot.innerText || frameRoot.textContent || "");
2872
+ html = normalizeText(frameRoot.innerHTML || "");
2873
+ }
2874
+ } catch {
2875
+ }
2876
+ } else {
2877
+ text = normalizeText(node?.innerText || node?.textContent || "");
2878
+ html = normalizeText(node?.innerHTML || node?.outerHTML || "");
2879
+ }
2880
+ const snapshot = text || html;
2881
+ items.push({
2882
+ selector,
2883
+ source,
2884
+ path,
2885
+ text,
2886
+ html,
2887
+ snapshot
2888
+ });
2889
+ });
2890
+ });
2891
+ const snapshotKey = items.map((item) => `${item.path}:${item.snapshot.length}:${tail(item.snapshot, 512)}`).join("||");
2892
+ const summaryText = items.map((item) => item.snapshot || item.text).join("\n").trim();
2893
+ const summaryHtml = items.map((item) => item.html).join("\n");
2894
+ const hasMatched = items.length > 0;
2895
+ const primary = items.length > 0 ? items[items.length - 1] : null;
2896
+ return {
2897
+ hasMatched,
2898
+ snapshotKey,
2899
+ text: summaryText,
2900
+ html: summaryHtml,
2901
+ snapshotLength: summaryText.length,
2902
+ itemCount: items.length,
2903
+ primaryPath: primary?.path || "",
2904
+ mutationNodes: items.map((item) => ({
2905
+ html: item.html,
2906
+ text: item.snapshot || item.text,
2907
+ mutationType: item.source
2908
+ }))
2909
+ };
2910
+ }, { selectorList });
2911
+ };
2912
+ const invokeMutationCallback = async (context) => {
2913
+ if (!onMutation) return "__CONTINUE__";
2914
+ try {
2915
+ const result = await onMutation(context);
2916
+ return result === null || result === void 0 ? "__CONTINUE__" : "__PAUSE__";
2917
+ } catch {
2918
+ return "__CONTINUE__";
2919
+ }
2920
+ };
2921
+ logger11.start(
2922
+ "waitForStableAcrossRoots",
2923
+ `\u76D1\u63A7 ${selectorList.length} \u4E2A\u9009\u62E9\u5668(\u8DE8 root), \u7A33\u5B9A\u65F6\u95F4=${waitForStableTime}ms`
2924
+ );
2925
+ if (initialTimeout > 0) {
2926
+ try {
2927
+ await page.waitForSelector(selectorQuery, { timeout: initialTimeout });
2928
+ logger11.info(`waitForStableAcrossRoots \u5DF2\u68C0\u6D4B\u5230\u5143\u7D20: ${selectorQuery}`);
2929
+ } catch (e) {
2930
+ logger11.warning(`waitForStableAcrossRoots \u521D\u59CB\u7B49\u5F85\u8D85\u65F6 (${initialTimeout}ms): ${selectorQuery}`);
2931
+ throw e;
2932
+ }
2933
+ }
2934
+ let state = await buildState();
2935
+ if (!state?.hasMatched) {
2936
+ logger11.warning("waitForStableAcrossRoots \u672A\u627E\u5230\u53EF\u76D1\u63A7\u7684\u5143\u7D20");
2937
+ return { mutationCount: 0, stableTime: 0, wasPaused: false };
2938
+ }
2939
+ let mutationCount = 0;
2940
+ let stableSince = 0;
2941
+ let isPaused = false;
2942
+ let wasPaused = false;
2943
+ let lastSnapshotKey = state.snapshotKey;
2944
+ const applyPauseSignal = (signal) => {
2945
+ const nextPaused = signal === "__PAUSE__";
2946
+ if (nextPaused) {
2947
+ if (!isPaused) wasPaused = true;
2948
+ isPaused = true;
2949
+ stableSince = 0;
2950
+ return;
2951
+ }
2952
+ isPaused = false;
2953
+ stableSince = Date.now();
2954
+ };
2955
+ const initialSignal = await invokeMutationCallback({
2956
+ mutationCount: 0,
2957
+ html: state.html || "",
2958
+ text: state.text || "",
2959
+ mutationNodes: state.mutationNodes || []
2960
+ });
2961
+ applyPauseSignal(initialSignal);
2962
+ const deadline = Date.now() + overallTimeout;
2963
+ let lastState = state;
2964
+ while (Date.now() < deadline) {
2965
+ await sleep(pollInterval);
2966
+ lastState = await buildState();
2967
+ if (!lastState?.hasMatched) {
2968
+ continue;
2969
+ }
2970
+ if (lastState.snapshotKey !== lastSnapshotKey) {
2971
+ lastSnapshotKey = lastState.snapshotKey;
2972
+ mutationCount += 1;
2973
+ logger11.info(
2974
+ `waitForStableAcrossRoots \u53D8\u5316#${mutationCount}, len=${lastState.snapshotLength}, path=${lastState.primaryPath || "unknown"}, preview="${truncate(lastState.text, 120)}"`
2975
+ );
2976
+ const signal = await invokeMutationCallback({
2977
+ mutationCount,
2978
+ html: lastState.html || "",
2979
+ text: lastState.text || "",
2980
+ mutationNodes: lastState.mutationNodes || []
2981
+ });
2982
+ applyPauseSignal(signal);
2983
+ continue;
2984
+ }
2985
+ if (!isPaused && stableSince > 0 && Date.now() - stableSince >= waitForStableTime) {
2986
+ logger11.success("waitForStableAcrossRoots", `DOM \u7A33\u5B9A, \u603B\u5171 ${mutationCount} \u6B21\u53D8\u5316${wasPaused ? ", \u66FE\u6682\u505C\u8BA1\u65F6" : ""}`);
2987
+ return {
2988
+ mutationCount,
2989
+ stableTime: waitForStableTime,
2990
+ wasPaused
2991
+ };
2992
+ }
2993
+ }
2994
+ throw new Error(`waitForStableAcrossRoots \u8D85\u65F6 (${overallTimeout}ms), \u5DF2\u68C0\u6D4B\u5230 ${mutationCount} \u6B21\u53D8\u5316, isPaused=${isPaused}`);
2995
+ },
2806
2996
  /**
2807
2997
  * 创建一个持续监控 DOM 变化的监控器(默认仅监听新增 DOM)
2808
2998
  *