@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 +190 -0
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +190 -0
- package/dist/index.js.map +2 -2
- package/index.d.ts +2 -0
- package/package.json +1 -1
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
|
*
|