@qing3a/flow-rpa-engine 0.5.2 → 0.5.7
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/behavior.d.ts +25 -6
- package/dist/behavior.js +51 -7
- package/dist/behavior.js.map +1 -1
- package/dist/cdp/city-switch.d.ts +63 -0
- package/dist/cdp/city-switch.js +276 -0
- package/dist/cdp/city-switch.js.map +1 -0
- package/dist/cdp/page.d.ts +22 -3
- package/dist/cdp/page.js +282 -42
- package/dist/cdp/page.js.map +1 -1
- package/dist/executor.d.ts +39 -4
- package/dist/executor.js +187 -19
- package/dist/executor.js.map +1 -1
- package/dist/mask.d.ts +4 -0
- package/dist/mask.js +5 -1
- package/dist/mask.js.map +1 -1
- package/dist/record.d.ts +24 -3
- package/dist/record.js +12 -0
- package/dist/record.js.map +1 -1
- package/dist/region.d.ts +30 -0
- package/dist/region.js +39 -0
- package/dist/region.js.map +1 -0
- package/dist/validate.js +23 -0
- package/dist/validate.js.map +1 -1
- package/package.json +2 -1
package/dist/cdp/page.d.ts
CHANGED
|
@@ -17,7 +17,12 @@ import type { HitFingerprint } from '../behavior.js';
|
|
|
17
17
|
* T1 simcheck:模拟质量评分(验收工具,不进生产链路)。
|
|
18
18
|
*/
|
|
19
19
|
/** 页面内 DOM 辅助(window.__rpaDom) */
|
|
20
|
-
export declare const DOM_SOURCE = "\nwindow.__rpaDom = (function() {\n function isVisible(el) {\n if (!el || el.nodeType !== 1) return false;\n const s = window.getComputedStyle(el);\n if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) === 0) return false;\n const r = el.getBoundingClientRect();\n return r.width > 0 && r.height > 0;\n }\n function byText(text) {\n const needle = String(text).trim();\n const visible = Array.from(document.querySelectorAll('body *')).filter(isVisible);\n const leaf = visible.filter(el => el.children.length === 0);\n const exact = leaf.filter(el => (el.textContent || '').trim() === needle);\n if (exact.length > 0) return exact;\n return leaf.filter(el => (el.textContent || '').includes(needle));\n }\n function safeQuery(sel) {\n try {\n return Array.from(document.querySelectorAll(sel)).filter(isVisible);\n } catch (e) {\n return [];\n }\n }\n function getBySels(selector) {\n // A3 \u6570\u7EC4\u94FE\uFF08#17 \u4FEE\u590D\uFF09\uFF1A\u4F9D\u6B21\u5C1D\u8BD5\uFF0C\u7B2C\u4E00\u4E2A\u547D\u4E2D\u7684\u8FD4\u56DE\n if (Array.isArray(selector)) {\n for (const s of selector) {\n const nodes = getBySels(s);\n if (nodes.length > 0) return nodes;\n }\n return [];\n }\n const sel = String(selector);\n if (sel.startsWith('text:')) return byText(sel.slice(5));\n // #16-B \u7F16\u53F7\u5BFB\u5740\uFF08\u865A\u62DF\u7A7A\u95F4\u89C6\u89C9\uFF09\uFF1A#N = numberedElements \u5217\u8868\u7B2C N \u4E2A\u53EF\u89C1\u4EA4\u4E92\u5143\u7D20\uFF081 \u8D77\uFF09\u3002\n // \u5FEB\u7167\u7F16\u53F7\u4E0E\u5BFB\u5740\u5171\u7528\u540C\u4E00\u679A\u4E3E \u2192 Agent/\u65E5\u5FD7\u53EF\u300C\u770B\u7F16\u53F7\u76F4\u63A5\u70B9\u300D\uFF1B\u7F16\u53F7\u968F DOM \u53D8\u5316\uFF0C\u64CD\u4F5C\u65F6\u73B0\u53D6\u3002\n const numMatch = sel.match(/^#(d+)$/);\n if (numMatch) {\n const idx = Number(numMatch[1]) - 1;\n const tags = 'a,button,input,select,textarea,[role=\"button\"],[role=\"link\"],[contenteditable=\"true\"]';\n const nodes = Array.from(document.querySelectorAll(tags)).filter(isVisible);\n return idx >= 0 && idx < nodes.length ? [nodes[idx]] : [];\n }\n // \u6DF7\u5408\u4E32\uFF08CSS \u4E0E text: \u9017\u53F7\u5206\u9694\uFF0C\u5982 \"a.search-btn, text:\u641C\u7D22\"\uFF09\uFF1A\u9010\u6BB5\u5C1D\u8BD5\u5408\u5E76\u53BB\u91CD\n // \uFF08#17 \u4FEE\u590D\uFF1Atext: \u6BB5\u4E0D\u80FD\u518D\u6DF7\u8FDB querySelectorAll\u2014\u2014\u6574\u4F53\u4F1A SyntaxError \u5931\u6548\uFF09\n const parts = sel.split(',').map((p) => p.trim()).filter(Boolean);\n if (parts.length > 1 || parts.some((p) => p.startsWith('text:'))) {\n const out = [];\n for (const p of parts) {\n const nodes = p.startsWith('text:') ? byText(p.slice(5)) : safeQuery(p);\n for (const n of nodes) {\n if (isVisible(n) && !out.includes(n)) out.push(n);\n }\n }\n return out;\n }\n return safeQuery(sel);\n }\n function pick(selector, index) {\n const nodes = getBySels(selector);\n return nodes[index] || nodes[0] || null;\n }\n function scrollIntoView(selector, index) {\n const el = pick(selector, index);\n if (!el) return false;\n el.scrollIntoView({ block: 'center', inline: 'center' });\n return true;\n }\n function textOf(selector, index) {\n const el = pick(selector, index);\n return el ? (el.innerText || el.textContent || '') : '';\n }\n function focusInput(selector, index) {\n const el = pick(selector, index);\n if (!el) return false;\n if (typeof el.select === 'function') el.select();\n el.focus();\n return true;\n }\n function pageText() {\n let t = document.body ? document.body.innerText : '';\n const inputs = Array.from(document.querySelectorAll('input, textarea, [contenteditable=\"true\"]'));\n for (const el of inputs) {\n const v = el.value || el.textContent || '';\n if (v) t += '\\n' + v;\n }\n return t;\n }\n function numberedElements(maxN) {\n const tags = 'a,button,input,select,textarea,[role=\"button\"],[role=\"link\"],[contenteditable=\"true\"]';\n const nodes = Array.from(document.querySelectorAll(tags)).filter(isVisible);\n const cap = maxN || 50;\n return nodes.slice(0, cap).map(el => ({\n tag: el.tagName.toLowerCase(),\n text: (el.innerText || el.value || el.textContent || '').trim().slice(0, 40)\n }));\n }\n // #16-A \u865A\u62DF\u7A7A\u95F4\u89C6\u89C9\uFF1A\u5143\u7D20\u8BED\u4E49\u63CF\u8FF0\uFF08\u906E\u6321\u68C0\u6D4B/\u65E5\u5FD7\u7528\uFF09\n function describeEl(el) {\n if (!el || el.nodeType !== 1) return null;\n const t = (el.innerText || el.value || el.textContent || '').trim();\n return {\n tag: el.tagName.toLowerCase(),\n cls: (el.className || '').toString().slice(0, 60),\n text: t.slice(0, 30),\n role: el.getAttribute && el.getAttribute('role'),\n };\n }\n // P3 \u8BED\u4E49\u951A\u70B9\uFF1A\u547D\u4E2D\u5143\u7D20\u6307\u7EB9\uFF08\u52A8\u4F5C\u6210\u529F\u65F6\u91C7\u96C6\uFF0C\u4F9B\u9009\u62E9\u5668\u5931\u6548\u65F6\u751F\u6210\u66FF\u4EE3\u5B9A\u4F4D\uFF09\u3002\n // \u53EA\u6709\u53EF\u91CD\u5B9A\u4F4D\u7684\u8BED\u4E49\u7279\u5F81\uFF08tag/\u6587\u672C\u622A\u65AD/placeholder/aria-label/name\uFF09\u2014\u2014\u65E0\u5750\u6807\u65E0\u539F\u6587\u3002\n // \u63A9\u7801\u524D\u79FB\uFF08\u94C1\u5F8B\uFF09\uFF1A\u8868\u5355\u5143\u7D20\uFF08input/textarea/select\uFF09\u7EDD\u4E0D\u53D6 value\u2014\u2014\u90A3\u662F\u7528\u6237\u8F93\u5165\u503C\n // \uFF08\u641C\u7D22\u8BCD/\u7535\u8BDD\u7B49\u654F\u611F\u539F\u6587\uFF09\uFF0C\u843D\u76D8\u5373\u8FDD\u89C4\uFF1B\u8868\u5355\u951A\u70B9\u53EA\u7528 placeholder/name/aria-label\u3002\n function describeHit(selector, index) {\n const el = pick(selector, index);\n if (!el) return null;\n const tag = el.tagName.toLowerCase();\n const isForm = tag === 'input' || tag === 'textarea' || tag === 'select';\n const t = isForm ? '' : (el.innerText || el.textContent || '').trim().slice(0, 30);\n const get = (k) => { try { return el.getAttribute(k) || undefined; } catch (e) { return undefined; } };\n return {\n tag,\n ...(t ? { text: t } : {}),\n ...(get('placeholder') ? { placeholder: get('placeholder') } : {}),\n ...(get('aria-label') ? { ariaLabel: get('aria-label') } : {}),\n ...(get('name') ? { name: get('name') } : {}),\n };\n }\n // #16-B/P2 \u533A\u57DF\u8BC6\u522B\uFF1A\u76EE\u6807\u5143\u7D20\u5411\u4E0A\u904D\u5386\u7956\u5148\uFF08\u22645 \u5C42\uFF09\uFF0C\u7528\u884C\u4E3A\u89C4\u5219\u5339\u914D\u51FA\u533A\u57DF\u6807\u7B7E\u3002\n // \u89C4\u5219\uFF08behavior.rules.json \u968F\u6D41\u7A0B\u5305\u5206\u53D1\uFF0C\u5F15\u64CE\u96F6\u4E1A\u52A1\u903B\u8F91\uFF09\uFF1A\u5168\u90E8 match \u6761\u4EF6\u6EE1\u8DB3\u624D\u547D\u4E2D\u3002\n // \u8FD4\u56DE { label, ruleId, ancestry }\u2014\u2014ancestry \u4E3A\u7956\u5148\u94FE\u6458\u8981\uFF08\u6297\u6539\u7248\u91CD\u5B9A\u4F4D\u7528\uFF09\u3002\n function identifyRegion(selector, index, rulesJson) {\n const target = pick(selector, index);\n if (!target) return null;\n let rules = [];\n try { rules = JSON.parse(rulesJson || '[]'); } catch (e) { rules = []; }\n const ancestry = [];\n let el = target;\n for (let d = 0; d < 5 && el; d++) {\n if (el !== target && el.nodeType === 1) {\n const cls = (el.className || '').toString().slice(0, 40);\n ancestry.push(el.tagName.toLowerCase() + (cls ? '.' + cls : ''));\n }\n // \u7528\u89C4\u5219\u5339\u914D\u5F53\u524D\u7956\u5148\n for (const r of rules) {\n const m = r.match || {};\n let hit = true;\n if (m.textContains && !((el.innerText || '').includes(m.textContains))) hit = false;\n if (m.textContainsAll && Array.isArray(m.textContainsAll)) {\n const txt = el.innerText || '';\n for (const kw of m.textContainsAll) {\n if (!txt.includes(kw)) { hit = false; break; }\n }\n }\n if (m.hasInput && !el.querySelector('input, textarea')) hit = false;\n if (m.hasButton && !el.querySelector('button, a[role=\"button\"], [role=\"button\"]')) hit = false;\n if (m.ancestorTag && !(el.tagName || '').toLowerCase().includes(m.ancestorTag)) hit = false;\n if (m.ancestorClass && !((el.className || '').toString().includes(m.ancestorClass))) hit = false;\n if (hit) {\n return { label: r.label, ruleId: r.id, ancestry: ancestry.slice(0, 3) };\n }\n }\n el = el.parentElement;\n }\n return { label: undefined, ruleId: undefined, ancestry: ancestry.slice(0, 3) };\n }\n // #16-A \u70B9\u51FB\u524D\u906E\u6321\u68C0\u6D4B\uFF1A\u8FD4\u56DE (x,y) \u5904\u6700\u9876\u5C42\u5143\u7D20 + \u8BE5\u5143\u7D20\u662F\u5426\u5C5E\u4E8E\u76EE\u6807\uFF08\u76EE\u6807\u6216\u5176\u540E\u4EE3\uFF09\n function hitTestAt(selector, index, x, y) {\n const target = pick(selector, index);\n if (!target) return { found: false };\n const top = document.elementFromPoint(x, y);\n if (!top) return { found: true, hit: false, top: null };\n const hit = top === target || target.contains(top);\n return {\n found: true,\n hit,\n top: describeEl(top),\n target: describeEl(target),\n };\n }\n // #16-A \u5BF9\u8BDD\u6846\u5173\u95ED\uFF1A\u906E\u6321\u8005\uFF08dialog-layer \u7B49\u906E\u7F69\uFF09\u5185\u7684\u5173\u95ED\u6309\u94AE\u5750\u6807\uFF08\u4F9B\u5F15\u64CE\u70B9\u51FB\u5173\u95ED\u540E\u91CD\u8BD5\uFF09\n function findDialogClose(selector, index) {\n const target = pick(selector, index);\n if (!target) return null;\n const cx = Math.round(target.getBoundingClientRect().left + target.getBoundingClientRect().width / 2);\n const cy = Math.round(target.getBoundingClientRect().top + target.getBoundingClientRect().height / 2);\n const top = document.elementFromPoint(cx, cy);\n if (!top) return null;\n // \u627E\u6700\u9876\u5C42\u5143\u7D20\u4F5C\u4E3A\u906E\u6321\u8005\uFF08dialog-layer/\u906E\u7F69\uFF09\uFF0C\u5728\u5176\u5185\u627E\u5173\u95ED\u6309\u94AE\n let blocker = top;\n while (blocker && blocker !== document.body) {\n const s = getComputedStyle(blocker);\n if ((s.position === 'fixed' || s.position === 'absolute') && parseFloat(s.zIndex) >= 100) break;\n blocker = blocker.parentElement;\n }\n if (!blocker || blocker === document.body) return null;\n // \u5173\u95ED\u6309\u94AE\u5019\u9009\uFF1Aclass \u542B close / \u56FE\u6807\u6309\u94AE / \u6587\u672C \u00D7\n const closeBtns = [...blocker.querySelectorAll('a,button,[role=\"button\"],i,span,div')].filter((el) => {\n const cls = (el.className || '').toString();\n const txt = (el.innerText || '').trim();\n const r = el.getBoundingClientRect();\n return r.width > 8 && r.height > 8 &&\n (/close|close-btn|icon-close|dialog-close|del|btn-close/i.test(cls) || txt === '\u00D7' || txt === '\u2715' || txt === 'X' || txt === '\u5173\u95ED');\n });\n const btn = closeBtns[0];\n if (!btn) return null;\n const r = btn.getBoundingClientRect();\n return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2), cls: (btn.className || '').toString().slice(0, 40) };\n }\n // #16-A \u539F\u5B50\u5316\u906E\u6321\u68C0\u6D4B\uFF1A\u4E0E rect \u91C7\u96C6\u540C\u4E00\u6B21\u6C42\u503C\uFF08\u6D88\u9664\u300C\u91C7\u96C6-\u68C0\u6D4B\u300D\u4E24\u6B21\u6C42\u503C\u95F4\u7684 DOM \u7ADE\u6001\uFF09\u3002\n // \u8FD4\u56DE\u6BCF\u4E2A\u53EF\u89C1\u76EE\u6807\u7684 rect + \u4E2D\u5FC3\u70B9\u547D\u4E2D\u4FE1\u606F + \u5B89\u5168\u70B9\u51FB\u70B9\uFF1A\n // hit=true \u4E2D\u5FC3\u70B9\u547D\u4E2D\u76EE\u6807\u6216\u5176\u975E\u4EA4\u4E92\u540E\u4EE3\uFF08\u5B89\u5168\u70B9\u51FB\u70B9\uFF09\n // hit=false \u4E2D\u5FC3\u70B9\u88AB\u771F\u5B9E\u906E\u6321\uFF08\u6D6E\u52A8\u5C42/\u5F39\u5C42/\u5176\u5B83\u5143\u7D20\u8986\u76D6\uFF09\n // danger=true \u4E2D\u5FC3\u70B9\u843D\u5728\u76EE\u6807\u5185\u7684\u3010\u53EF\u4EA4\u4E92\u5B50\u5143\u7D20\u3011\uFF08\u94FE\u63A5/\u6309\u94AE\u7B49\uFF09\u4E0A\u2014\u2014\u70B9\u51FB\u4F1A\u89E6\u53D1\u5B50\u5143\u7D20\u800C\u975E\u76EE\u6807\n // safe \u77E9\u5F62\u5185\u626B\u63CF\u5230\u7684\u7B2C\u4E00\u4E2A\u300C\u5C5E\u4E8E\u76EE\u6807\u4E14\u975E\u4EA4\u4E92\u5B50\u5143\u7D20\u300D\u7684\u70B9\uFF08\u4E2D\u5FC3\u88AB\u76D6/\u5371\u9669\u65F6\u6362\u70B9\u7528\uFF09\uFF1B\n // \u65E0\u5B89\u5168\u70B9\u5219 null\uFF08\u6574\u4E2A\u76EE\u6807\u88AB\u76D6 \u2192 \u5F15\u64CE\u4FA7 ESC \u5173\u95ED\u6D6E\u52A8\u5C42\u91CD\u8BD5\uFF09\n // top=null\uFF08elementFromPoint \u65E0\u7ED3\u679C\uFF0C\u5982\u5143\u7D20\u90E8\u5206\u5728\u89C6\u53E3\u5916/\u6E32\u67D3\u8FB9\u7F18\uFF09\u2192 \u4E0D\u89C6\u4E3A\u906E\u6321\uFF0Chit=true\u3002\n function clickableRects(selector) {\n const d = window.__rpaDom;\n const nodes = d && d.getBySels ? d.getBySels(selector) : [];\n const vw = innerWidth || document.documentElement.clientWidth;\n const vh = innerHeight || document.documentElement.clientHeight;\n const out = [];\n const INTERACTIVE = 'a,button,[role=\"button\"],[role=\"link\"],input,select,textarea,[onclick],label';\n const isSafeHit = (el, top) => {\n if (!top) return false;\n if (top === el) return true;\n if (el.contains(top)) {\n return !(top.closest && top.closest(INTERACTIVE) === top);\n }\n return false;\n };\n for (let i = 0; i < nodes.length; i++) {\n const el = nodes[i];\n const t = el.getBoundingClientRect();\n const fullW = Math.max(1, t.width);\n const fullH = Math.max(1, t.height);\n const x = Math.max(t.left, 0);\n const y = Math.max(t.top, 0);\n const w = Math.max(0, Math.min(t.right, vw) - x);\n const h = Math.max(0, Math.min(t.bottom, vh) - y);\n if (w <= 0 || h <= 0) continue;\n // #16-A \u53EF\u89C1\u6BD4\u4F8B\uFF1A\u89C6\u53E3\u5185\u9762\u79EF / \u603B\u9762\u79EF\u3002\u7A84\u89C6\u53E3\u4E0B\u5927\u90E8\u5206\u5728\u89C6\u53E3\u5916\u7684\u5143\u7D20\n // \uFF08\u5982\u9876\u90E8\u5BFC\u822A\u53F3\u4FA7\u7684\u641C\u7D22\u6309\u94AE\uFF09\u70B9\u51FB\u5750\u6807\u4F1A\u843D\u5728\u89C6\u53E3\u5916/\u8FB9\u7F18 \u2192 CDP input \u6302\u8D77\u6216\u70B9\u7A7A\u3002\n // \u53EF\u89C1\u6BD4\u4F8B < 50% \u2192 \u6807\u8BB0 partiallyVisible\uFF0C\u5F15\u64CE\u4FA7\u62A5\u300C\u76EE\u6807\u5927\u90E8\u5206\u5728\u89C6\u53E3\u5916\u300D\u800C\u975E\u5C1D\u8BD5\u70B9\u51FB\u3002\n const visibleRatio = (w * h) / (fullW * fullH);\n const partiallyVisible = visibleRatio < 0.5;\n if (partiallyVisible) {\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: false, danger: false, safe: null, partiallyVisible: true, top: null, target: describeEl(el),\n });\n continue;\n }\n const cx = Math.round(x + w / 2);\n const cy = Math.round(y + h / 2);\n const top = document.elementFromPoint(cx, cy);\n if (!top) {\n // elementFromPoint \u65E0\u7ED3\u679C\uFF1A\u53EF\u89C1\u6BD4\u4F8B\u8DB3\u591F\uFF08\u226550%\uFF09\u4F46\u53D6\u4E0D\u5230\u9876\u5C42\u5143\u7D20 \u2192 \u6E32\u67D3\u8FB9\u754C\u5BB9\u5FCD\uFF0C\u4E0D\u963B\u585E\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: true, danger: false, safe: null, partiallyVisible: false, top: null, target: describeEl(el),\n });\n continue;\n }\n const belongs = top === el || el.contains(top);\n const isInteractiveChild = top !== el && el.contains(top) && top.closest && top.closest(INTERACTIVE) === top;\n // \u4E2D\u5FC3\u88AB\u76D6/\u5371\u9669 \u2192 \u626B\u63CF\u77E9\u5F62\u5185\u90E8\u627E\u5B89\u5168\u70B9\uFF085\u00D75 \u7F51\u683C\uFF0C\u907F\u5F00\u4EA4\u4E92\u5B50\u5143\u7D20\u4E0E\u906E\u6321\uFF09\n let safe = null;\n if (!(belongs && !isInteractiveChild)) {\n for (let gy = 1; gy <= 4 && !safe; gy++) {\n for (let gx = 1; gx <= 4 && !safe; gx++) {\n const sx = Math.round(x + (w * gx) / 5);\n const sy = Math.round(y + (h * gy) / 5);\n const st = document.elementFromPoint(sx, sy);\n if (st && isSafeHit(el, st)) safe = { x: sx, y: sy };\n }\n }\n }\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: belongs && !isInteractiveChild,\n danger: isInteractiveChild,\n safe,\n partiallyVisible: false,\n top: describeEl(top),\n target: describeEl(el),\n });\n }\n return out;\n }\n // #16-A \u76EE\u6807\u77E9\u5F62\u5185\u627E\u5B89\u5168\u70B9\u51FB\u70B9\uFF1A\u7F51\u683C\u91C7\u6837\uFF085\u00D75\uFF09\uFF0C\u8FD4\u56DE\u7B2C\u4E00\u4E2A\u300C\u5C5E\u4E8E\u76EE\u6807\u4E14\u4E0D\u5728\u53EF\u4EA4\u4E92\u5B50\u5143\u7D20\u4E0A\u300D\u7684\u70B9\u3002\n // \u5927\u5BB9\u5668\u5361\u7247\uFF08\u5982 job-card-wrap\uFF09\u4E2D\u5FC3\u5E38\u843D\u5728\u516C\u53F8\u94FE\u63A5/\u804C\u4F4D\u8BE6\u60C5\u94FE\u63A5\u4E0A\uFF0C\u76F4\u63A5\u70B9\u4F1A\u8BEF\u8DF3\u8F6C\uFF1B\n // \u5B89\u5168\u70B9\u907F\u5F00\u4EA4\u4E92\u5B50\u5143\u7D20\uFF08a/button/[onclick]\uFF09\uFF0C\u53EA\u843D\u5728\u6587\u672C/\u7A7A\u767D\u533A\u57DF\uFF08\u70B9\u51FB\u4ECD\u7531\u7236\u7EA7\u4E8B\u4EF6\u5192\u6CE1\u5904\u7406\uFF09\u3002\n function safePointIn(selector, index) {\n const el = pick(selector, index);\n if (!el) return null;\n const t = el.getBoundingClientRect();\n const INTERACTIVE = 'a,button,[role=\"button\"],[role=\"link\"],input,select,textarea,[onclick],label';\n for (let gy = 2; gy <= 3; gy++) {\n for (let gx = 2; gx <= 3; gx++) {\n const cx = Math.round(t.left + (t.width * gx) / 5);\n const cy = Math.round(t.top + (t.height * gy) / 5);\n const top = document.elementFromPoint(cx, cy);\n if (!top) continue;\n if (top === el || el.contains(top)) {\n const bad = top !== el && top.closest && top.closest(INTERACTIVE) === top;\n if (!bad) return { x: cx, y: cy };\n }\n }\n }\n // \u515C\u5E95\uFF1A\u4E2D\u5FC3\u70B9\uFF08\u53EF\u80FD\u5371\u9669\uFF0C\u7531\u5F15\u64CE\u4FA7\u518D\u5224\u65AD\uFF09\n return { x: Math.round(t.left + t.width / 2), y: Math.round(t.top + t.height / 2) };\n }\n return { getBySels, scrollIntoView, textOf, focusInput, pageText, numberedElements, describeEl, describeHit, hitTestAt, clickableRects, safePointIn, identifyRegion, findDialogClose };\n})();\ntrue\n";
|
|
20
|
+
export declare const DOM_SOURCE = "\nwindow.__rpaDom = (function() {\n function isVisible(el) {\n if (!el || el.nodeType !== 1) return false;\n const s = window.getComputedStyle(el);\n if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) === 0) return false;\n const r = el.getBoundingClientRect();\n return r.width > 0 && r.height > 0;\n }\n function byText(text) {\n const needle = String(text).trim();\n const visible = Array.from(document.querySelectorAll('body *')).filter(isVisible);\n const leaf = visible.filter(el => el.children.length === 0);\n const exact = leaf.filter(el => (el.textContent || '').trim() === needle);\n if (exact.length > 0) return exact;\n return leaf.filter(el => (el.textContent || '').includes(needle));\n }\n function safeQuery(sel) {\n try {\n return Array.from(document.querySelectorAll(sel)).filter(isVisible);\n } catch (e) {\n return [];\n }\n }\n // T014 P0-3\uFF1A\u533A\u57DF\u89C4\u5219\u547D\u4E2D\u5224\u5B9A\uFF08\u5355\u4E00\u5B9E\u73B0\u2014\u2014identifyRegion \u4E0E region: \u8868\u8FBE\u5F0F\u5171\u7528\uFF0C\u9632\u903B\u8F91\u6F02\u79FB\uFF09\u3002\n // T016 P1-4\uFF08\u5B9E\u8DD1\u4FEE\u590D\uFF09\uFF1AtextContains \u7528 textContent \u800C\u975E innerText\u2014\u2014innerText \u5F3A\u5236\u540C\u6B65\u91CD\u6392\uFF0C\n // \u8BE6\u60C5\u9762\u677F\u6253\u5F00\u540E DOM \u6570\u5343\u5143\u7D20 \u00D7 regionContainerOf \u5168\u904D\u5386 = \u5355\u6B21 evaluate \u6570\u5341\u79D2\uFF08waitSelector\n // \u8F6E\u8BE2\u8D85\u65F6\u5047\u5931\u8D25\uFF09\uFF1BtextContent \u4E0D\u89E6\u53D1\u91CD\u6392\uFF0C\u8BED\u4E49\u7B49\u4EF7\uFF08\u89C4\u5219=\u6587\u672C\u5305\u542B\uFF0C\u4E0D\u4F9D\u8D56\u6E32\u67D3\u5B8C\u6210\u6001\uFF09\u3002\n function ruleHits(rule, el) {\n const m = rule.match || {};\n let hit = true;\n if (m.textContains && !((el.textContent || '').includes(m.textContains))) hit = false;\n if (m.textContainsAll && Array.isArray(m.textContainsAll)) {\n const txt = el.textContent || '';\n for (const kw of m.textContainsAll) {\n if (!txt.includes(kw)) { hit = false; break; }\n }\n }\n if (m.hasInput && !el.querySelector('input, textarea')) hit = false;\n if (m.hasButton && !el.querySelector('button, a[role=\"button\"], [role=\"button\"]')) hit = false;\n if (m.ancestorTag && !(el.tagName || '').toLowerCase().includes(m.ancestorTag)) hit = false;\n if (m.ancestorClass && !((el.className || '').toString().includes(m.ancestorClass))) hit = false;\n return hit;\n }\n // T014 P0-1\uFF1A\u5143\u7D20\u6240\u5C5E\u533A\u57DF\u6807\u7B7E\uFF08\u8D70\u5143\u7D20\u81EA\u8EAB\u2192\u7956\u5148\uFF0C\u9996\u4E2A\u89C4\u5219\u547D\u4E2D\u5373\u8FD4\u56DE\uFF1B\u65E0 \u2192 null\uFF09\n function regionLabelOf(el, rules) {\n let cur = el;\n for (let d = 0; d < 5 && cur; d++) {\n for (const r of rules) {\n if (ruleHits(r, cur)) return { label: r.label, ruleId: r.id };\n }\n cur = cur.parentElement;\n }\n return null;\n }\n // T014 P0-3 + T016 P1-3\uFF1A\u533A\u57DF\u4E3B\u5BB9\u5668\u89E3\u6790\uFF08region:<\u6807\u7B7E> \u4E0E\u5BFB\u5740\u5171\u7528\uFF09\uFF1A\n // self-match \u5019\u9009\uFF08\u9762\u79EF < \u89C6\u53E3\uFF09\u4E2D\uFF0C\u53D6\u300C\u6709\u5B9E\u8D28\u7236\u5019\u9009\u7684\u6700\u6D45\u5B9E\u8D28\u5019\u9009\u300D\u2014\u2014\u8BE6\u60C5\u533A\u6587\u672C\u94FE\n // container \u2283 box \u2283 body \u2283 h3\uFF1Abox \u7684\u7236 container \u662F\u5B9E\u8D28\u5019\u9009\u3001body \u7684\u7236 box \u4E5F\u662F\uFF0C\u53D6\u6700\u6D45\n // = box\uFF08\u542B\u5934\u90E8\u7ECF\u9A8C/\u5B66\u5386/\u85AA\u8D44 + \u6B63\u6587\uFF0CT016 P1-3 \u4FEE\u590D\u2014\u2014\u539F\u300C\u6700\u6DF1\u5B9E\u8D28\u300D\u9009\u5230 body \u4E22\u5934\u90E8\uFF09\uFF1B\n // \u4E14\u907F\u5F00\u542B\u8FB9\u680F/\u6C42\u804C\u610F\u5411\u7684\u5305\u88F9\u5C42 container\uFF08USAGE-ISSUES #16\uFF09\uFF1B\n // \u65E0\u5B9E\u8D28\u7236\u5019\u9009\uFF08\u5217\u8868 ul \u81EA\u8EAB\u3001\u77ED\u6587\u672C\u641C\u7D22\u8868\u5355\uFF09\u2192 \u56DE\u9000\u6700\u5927\u9762\u79EF\u3002\n function regionContainerOf(label, rules) {\n const vw = innerWidth || document.documentElement.clientWidth || 1000;\n const vh = innerHeight || document.documentElement.clientHeight || 800;\n const vpArea = vw * vh;\n const matches = [];\n // T016 P1-4\uFF08\u5B9E\u8DD1\u4FEE\u590D\uFF09\uFF1A\u5168\u904D\u5386\u53EA\u7528\u6837\u5F0F\u5C5E\u6027\u8FC7\u6EE4\uFF08getComputedStyle \u4E0D\u89E6\u53D1\u5E03\u5C40\uFF09\uFF0C\n // rect \u4EC5\u5728\u89C4\u5219\u547D\u4E2D\u540E\u8BFB\u53D6\uFF08\u547D\u4E2D\u5143\u7D20\u6781\u5C11\uFF09\u2014\u2014\u907F\u514D\u8BE6\u60C5\u9762\u677F\u6253\u5F00\u540E\u5BF9\u6570\u5343\u5143\u7D20\u5404\u8BFB\u4E00\u6B21\n // getBoundingClientRect/innerText \u7684 O(n) \u540C\u6B65\u91CD\u6392\uFF08waitSelector \u8F6E\u8BE2 evaluate \u6570\u5341\u79D2\u5047\u8D85\u65F6\uFF09\u3002\n for (const el of Array.from(document.querySelectorAll('body *'))) {\n if (!(el.textContent || '').trim()) continue;\n const s = getComputedStyle(el);\n if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) === 0) continue;\n for (const r of rules) {\n if (r.label === label && ruleHits(r, el)) {\n const rect = el.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (area > 0 && area < vpArea) matches.push({ el, area });\n break;\n }\n }\n }\n if (matches.length === 0) return null;\n const depthOf = (el) => {\n let d = 0;\n let cur = el.parentElement;\n while (cur) { d++; cur = cur.parentElement; }\n return d;\n };\n // \u5B9E\u8D28\u5019\u9009\uFF1A\u6587\u672C \u2265100 \u5B57\uFF08\u77ED\u6587\u672C\u533A\u57DF\u5982\u641C\u7D22\u8868\u5355 \u2192 \u7528\u5168\u90E8\u5019\u9009\uFF09\n const pool = matches.filter((m) => (m.el.textContent || '').trim().length >= 100);\n const candidates = pool.length > 0 ? pool : matches;\n // \u6709\u5B9E\u8D28\u7236\u5019\u9009\u7684\uFF08\u5D4C\u5957\u5728\u53E6\u4E00\u4E2A\u5B9E\u8D28\u5019\u9009\u5185\u2014\u2014\u5185\u5BB9\u5BB9\u5668\u800C\u975E\u5305\u88F9\u5C42\uFF09\n const withParent = candidates.filter((m) => candidates.some((o) => o.el !== m.el && containsNode(o.el, m.el)));\n let best = null;\n if (withParent.length > 0) {\n // \u6700\u6D45\u8005\uFF08\u8BE6\u60C5\u533A box \u6DF1\u4E8E container \u4F46\u6D45\u4E8E body\uFF09\n for (const m of withParent) {\n if (!best || depthOf(m.el) < depthOf(best.el)) best = m;\n }\n } else {\n for (const m of candidates) {\n if (!best || m.area > best.area) best = m;\n }\n }\n return best ? best.el : null;\n }\n // a \u662F\u5426 b \u7684\u7956\u5148\uFF08region \u5BB9\u5668\u300C\u6709\u5B9E\u8D28\u7236\u5019\u9009\u300D\u5224\u5B9A\u7528\uFF09\n function containsNode(a, b) {\n let cur = b.parentElement;\n while (cur) {\n if (cur === a) return true;\n cur = cur.parentElement;\n }\n return false;\n }\n function getBySels(selector) {\n // A3 \u6570\u7EC4\u94FE\uFF08#17 \u4FEE\u590D\uFF09\uFF1A\u4F9D\u6B21\u5C1D\u8BD5\uFF0C\u7B2C\u4E00\u4E2A\u547D\u4E2D\u7684\u8FD4\u56DE\n if (Array.isArray(selector)) {\n for (const s of selector) {\n const nodes = getBySels(s);\n if (nodes.length > 0) return nodes;\n }\n return [];\n }\n const sel = String(selector);\n if (sel.startsWith('text:')) return byText(sel.slice(5));\n // T014 P0-3 + T016\uFF1Aregion:<\u6807\u7B7E>[ >> [\u5B50\u9009\u62E9\u5668] >> <n>] \u2014\u2014 \u5F15\u64CE\u5185\u90E8\u533A\u57DF\u5BFB\u5740\u8868\u8FBE\u5F0F\u3002\n // \u89C4\u5219\u7531 setRegionRules \u6CE8\u5165\uFF08\u968F\u6D41\u7A0B\u5305\u5206\u53D1\uFF0C\u5F15\u64CE\u96F6\u4E1A\u52A1\u903B\u8F91\uFF1B\u4EF2\u88C1\uFF1A\u6CE8\u5165\u89C4\u5219\u5DF2\u6309\u6863\u6848 seen \u91CD\u6392\uFF09\u3002\n // \u4E3B\u5BB9\u5668\u89E3\u6790\uFF08regionContainerOf\uFF09\uFF1Aself-match \u5019\u9009\uFF08\u9762\u79EF < \u89C6\u53E3\uFF09\u4E2D\u53D6\u300C\u6709\u5B9E\u8D28\u7236\u5019\u9009\u7684\u6700\u6D45\n // \u5B9E\u8D28\u5019\u9009\u300D\uFF08\u8BE6\u60C5\u533A box \u542B\u5934\u90E8+\u6B63\u6587\uFF0C\u89C1 USAGE-ISSUES #16/#21 \u4E0E T016 P1-3\uFF09\uFF1B\u65E0 \u2192 \u6700\u5927\u9762\u79EF\u3002\n // \u5BFB\u5740\u8BED\u4E49\uFF08T016 P0-1 \u4FEE\u8BA2\u2014\u2014\u730E\u5934 0.5.6 \u9A8C\u6536\uFF1A\u679A\u4E3E\u5168\u90E8\u53EF\u4EA4\u4E92\u5143\u7D20\u4F1A\u70B9\u5230\u5361\u7247\u5185\u516C\u53F8\u94FE\u63A5\uFF09\uFF1A\n // 'region:<\u6807\u7B7E> >>' = \u533A\u57DF\u4E3B\u5BB9\u5668\u7684**\u5361\u7247\u7EA7\u76F4\u63A5\u5B50\u9879**\uFF08\u53EF\u89C1 + \u6709\u5B9E\u8D28\u6587\u672C\uFF1B\n // \u5217\u8868\u533A = ul.rec-job-list \u7684\u76F4\u63A5\u5B50\u9879 li/div \u5361\u7247\uFF0Cindex \u4F5C\u7528\u4E8E\u5361\u7247\uFF09\n // 'region:<\u6807\u7B7E> >> <n>' = \u7B2C n \u4E2A\uFF080 \u8D77\uFF09\u5361\u7247\u7EA7\u5B50\u9879\n // 'region:<\u6807\u7B7E> >> <\u5B50\u9009\u62E9\u5668> >>' = \u533A\u57DF\u5185\u5339\u914D\u5B50\u9009\u62E9\u5668\u7684\u5168\u90E8\u53EF\u89C1\u5143\u7D20\uFF08\u6DF7\u5408\u5BFB\u5740\uFF09\n // 'region:<\u6807\u7B7E> >> <\u5B50\u9009\u62E9\u5668> >> <n>' = \u5176\u4E2D\u7B2C n \u4E2A\n if (sel.startsWith('region:')) {\n const rest = sel.slice(7).trim();\n const parts = rest.split('>>').map((p) => p.trim());\n const label = parts[0] || '';\n const rules = (window.__rpaDom && window.__rpaDom._regionRules) || [];\n const container = regionContainerOf(label, rules);\n if (!container) return [];\n if (parts.length === 1) return [container];\n const sub = parts.length >= 3 ? parts[1] : '';\n const idxPart = parts.length >= 3 ? parts[2] : parts[1] ?? '';\n let nodes;\n if (sub) {\n nodes = Array.from(container.querySelectorAll(sub)).filter(isVisible);\n } else {\n // \u5361\u7247\u7EA7\u76F4\u63A5\u5B50\u9879\uFF08T016 P0-1\uFF1A\u907F\u514D\u70B9\u5230\u5361\u7247\u5185\u516C\u53F8\u94FE\u63A5\u7B49\u5185\u90E8\u53EF\u4EA4\u4E92\u5143\u7D20\uFF09\n // T016 P1-4\uFF1AtextContent\uFF08\u4E0D\u5F3A\u5236\u91CD\u6392\uFF1BinnerText \u5728\u8BE6\u60C5\u9762\u677F\u6253\u5F00\u540E\u62D6\u6162 evaluate\uFF09\n nodes = Array.from(container.children).filter(isVisible).filter((el) => (el.textContent || '').trim().length > 0);\n }\n if (idxPart === '') return nodes;\n const n = Number(idxPart);\n return Number.isInteger(n) && n >= 0 && n < nodes.length ? [nodes[n]] : [];\n }\n // #16-B \u7F16\u53F7\u5BFB\u5740\uFF08\u865A\u62DF\u7A7A\u95F4\u89C6\u89C9\uFF09\uFF1A#N = numberedElements \u5217\u8868\u7B2C N \u4E2A\u53EF\u89C1\u4EA4\u4E92\u5143\u7D20\uFF081 \u8D77\uFF09\u3002\n // \u5FEB\u7167\u7F16\u53F7\u4E0E\u5BFB\u5740\u5171\u7528\u540C\u4E00\u679A\u4E3E \u2192 Agent/\u65E5\u5FD7\u53EF\u300C\u770B\u7F16\u53F7\u76F4\u63A5\u70B9\u300D\uFF1B\u7F16\u53F7\u968F DOM \u53D8\u5316\uFF0C\u64CD\u4F5C\u65F6\u73B0\u53D6\u3002\n const numMatch = sel.match(/^#(d+)$/);\n if (numMatch) {\n const idx = Number(numMatch[1]) - 1;\n const tags = 'a,button,input,select,textarea,[role=\"button\"],[role=\"link\"],[contenteditable=\"true\"]';\n const nodes = Array.from(document.querySelectorAll(tags)).filter(isVisible);\n return idx >= 0 && idx < nodes.length ? [nodes[idx]] : [];\n }\n // \u6DF7\u5408\u4E32\uFF08CSS \u4E0E text: \u9017\u53F7\u5206\u9694\uFF0C\u5982 \"a.search-btn, text:\u641C\u7D22\"\uFF09\uFF1A\u9010\u6BB5\u5C1D\u8BD5\u5408\u5E76\u53BB\u91CD\n // \uFF08#17 \u4FEE\u590D\uFF1Atext: \u6BB5\u4E0D\u80FD\u518D\u6DF7\u8FDB querySelectorAll\u2014\u2014\u6574\u4F53\u4F1A SyntaxError \u5931\u6548\uFF09\n const parts = sel.split(',').map((p) => p.trim()).filter(Boolean);\n if (parts.length > 1 || parts.some((p) => p.startsWith('text:'))) {\n const out = [];\n for (const p of parts) {\n const nodes = p.startsWith('text:') ? byText(p.slice(5)) : safeQuery(p);\n for (const n of nodes) {\n if (isVisible(n) && !out.includes(n)) out.push(n);\n }\n }\n return out;\n }\n return safeQuery(sel);\n }\n function pick(selector, index) {\n const nodes = getBySels(selector);\n return nodes[index] || nodes[0] || null;\n }\n function scrollIntoView(selector, index) {\n const el = pick(selector, index);\n if (!el) return false;\n el.scrollIntoView({ block: 'center', inline: 'center' });\n return true;\n }\n // T012 P1-2\uFF1A\u8F7B\u91CF\u62DF\u4EBA\u6EDA\u52A8\u2014\u2014\u5206\u6BB5\u6E10\u8FDB\u6EDA\u52A8\uFF082~5 \u6BB5\uFF0C\u6BB5\u95F4 30~120ms \u968F\u673A\u8282\u594F\uFF09\uFF0C\n // \u7EC8\u4F4D\u4E0E scrollIntoView \u4E00\u81F4\uFF08block:center\uFF09\u2014\u2014\u53EA\u6539\u8282\u594F\u4E0D\u6539\u843D\u70B9\uFF0C\u529F\u80FD\u4E0D\u56DE\u5F52\u3002\n // T016 P1-4\uFF08\u5B9E\u8DD1\u4FEE\u590D\uFF09\uFF1A\u6539\u4E3A**\u540C\u6B65**\u5B9E\u73B0\u3002\u539F\u5F02\u6B65\u7248\u4F9D\u8D56\u9875\u9762 setTimeout \u94FE\uFF0C\u800C\u9875\u9762\u4E3B\u7EBF\u7A0B\n // \u7E41\u5FD9\uFF08\u8BE6\u60C5\u9762\u677F\u6E32\u67D3/\u6BCF\u6B65 pageText \u540C\u6B65\u91CD\u6392\uFF09\u65F6 setTimeout \u88AB\u6781\u7AEF\u8282\u6D41 \u2192 promise \u6C38\u4E0D\n // resolve \u2192 CDP evaluate awaitPromise \u6302\u8D77\uFF08\u4EC5\u9760 60s \u547D\u4EE4\u8D85\u65F6\u515C\u5E95\uFF0C\u8868\u73B0\u4E3A\u5076\u53D1\u5361\u6B7B\uFF09\u3002\n // \u540C\u6B65\u7248\u843D\u70B9\u4E0D\u53D8\uFF08\u5206\u6BB5\u7F13\u52A8\u6539\u4E3A\u76F4\u63A5 scrollTo + scrollIntoView block:center\uFF09\uFF0C\n // \u8FD4\u56DE true\uFF08\u4E0E\u65E7\u540C\u6B65\u5931\u8D25\u5206\u652F false \u517C\u5BB9\uFF09\uFF1Bevaluate awaitPromise \u5BF9\u540C\u6B65\u503C\u7ACB\u5373\u8FD4\u56DE\u3002\n function scrollIntoViewRhythm(selector, index) {\n const el = pick(selector, index);\n if (!el) return false;\n const vh = innerHeight || document.documentElement.clientHeight || 600;\n const rect = el.getBoundingClientRect();\n const curTop = pageYOffset || document.documentElement.scrollTop || 0;\n const targetY = curTop + rect.top - (vh - Math.min(rect.height, vh)) / 2;\n window.scrollTo(0, Math.round(targetY));\n el.scrollIntoView({ block: 'center', inline: 'center' });\n return true;\n }\n function textOf(selector, index) {\n const el = pick(selector, index);\n return el ? (el.innerText || el.textContent || '') : '';\n }\n function focusInput(selector, index) {\n const el = pick(selector, index);\n if (!el) return false;\n if (typeof el.select === 'function') el.select();\n el.focus();\n return true;\n }\n function pageText() {\n let t = document.body ? document.body.innerText : '';\n const inputs = Array.from(document.querySelectorAll('input, textarea, [contenteditable=\"true\"]'));\n for (const el of inputs) {\n const v = el.value || el.textContent || '';\n if (v) t += '\\n' + v;\n }\n return t;\n }\n function numberedElements(maxN) {\n const tags = 'a,button,input,select,textarea,[role=\"button\"],[role=\"link\"],[contenteditable=\"true\"]';\n const nodes = Array.from(document.querySelectorAll(tags)).filter(isVisible);\n const cap = maxN || 50;\n return nodes.slice(0, cap).map(el => ({\n tag: el.tagName.toLowerCase(),\n text: (el.innerText || el.value || el.textContent || '').trim().slice(0, 40)\n }));\n }\n // #16-A \u865A\u62DF\u7A7A\u95F4\u89C6\u89C9\uFF1A\u5143\u7D20\u8BED\u4E49\u63CF\u8FF0\uFF08\u906E\u6321\u68C0\u6D4B/\u65E5\u5FD7\u7528\uFF09\n function describeEl(el) {\n if (!el || el.nodeType !== 1) return null;\n const t = (el.innerText || el.value || el.textContent || '').trim();\n return {\n tag: el.tagName.toLowerCase(),\n cls: (el.className || '').toString().slice(0, 60),\n text: t.slice(0, 30),\n role: el.getAttribute && el.getAttribute('role'),\n };\n }\n // P3 \u8BED\u4E49\u951A\u70B9\uFF1A\u547D\u4E2D\u5143\u7D20\u6307\u7EB9\uFF08\u52A8\u4F5C\u6210\u529F\u65F6\u91C7\u96C6\uFF0C\u4F9B\u9009\u62E9\u5668\u5931\u6548\u65F6\u751F\u6210\u66FF\u4EE3\u5B9A\u4F4D\uFF09\u3002\n // \u53EA\u6709\u53EF\u91CD\u5B9A\u4F4D\u7684\u8BED\u4E49\u7279\u5F81\uFF08tag/\u6587\u672C\u622A\u65AD/placeholder/aria-label/name\uFF09\u2014\u2014\u65E0\u5750\u6807\u65E0\u539F\u6587\u3002\n // \u63A9\u7801\u524D\u79FB\uFF08\u94C1\u5F8B\uFF09\uFF1A\u8868\u5355\u5143\u7D20\uFF08input/textarea/select\uFF09\u7EDD\u4E0D\u53D6 value\u2014\u2014\u90A3\u662F\u7528\u6237\u8F93\u5165\u503C\n // \uFF08\u641C\u7D22\u8BCD/\u7535\u8BDD\u7B49\u654F\u611F\u539F\u6587\uFF09\uFF0C\u843D\u76D8\u5373\u8FDD\u89C4\uFF1B\u8868\u5355\u951A\u70B9\u53EA\u7528 placeholder/name/aria-label\u3002\n // T014 P1-4\uFF1A\u9644\u52A0\u6240\u5728\u533A\u57DF\u6807\u7B7E\uFF08region\uFF09\u2014\u2014\u951A\u70B9\u53EA\u5728\u540C\u4E00\u533A\u57DF\u590D\u7528\uFF08\u89C4\u5219\u7531 setRegionRules \u6CE8\u5165\uFF09\u3002\n function describeHit(selector, index) {\n const el = pick(selector, index);\n if (!el) return null;\n const tag = el.tagName.toLowerCase();\n const isForm = tag === 'input' || tag === 'textarea' || tag === 'select';\n const t = isForm ? '' : (el.innerText || el.textContent || '').trim().slice(0, 30);\n const get = (k) => { try { return el.getAttribute(k) || undefined; } catch (e) { return undefined; } };\n const rules = (window.__rpaDom && window.__rpaDom._regionRules) || [];\n const regionHit = rules.length > 0 ? regionLabelOf(el, rules) : null;\n return {\n tag,\n ...(t ? { text: t } : {}),\n ...(get('placeholder') ? { placeholder: get('placeholder') } : {}),\n ...(get('aria-label') ? { ariaLabel: get('aria-label') } : {}),\n ...(get('name') ? { name: get('name') } : {}),\n ...(regionHit ? { region: regionHit.label } : {}),\n };\n }\n // #16-B/P2 \u533A\u57DF\u8BC6\u522B\uFF1A\u76EE\u6807\u5143\u7D20\u5411\u4E0A\u904D\u5386\u7956\u5148\uFF08\u22645 \u5C42\uFF09\uFF0C\u7528\u884C\u4E3A\u89C4\u5219\u5339\u914D\u51FA\u533A\u57DF\u6807\u7B7E\u3002\n // \u89C4\u5219\uFF08behavior.rules.json \u968F\u6D41\u7A0B\u5305\u5206\u53D1\uFF0C\u5F15\u64CE\u96F6\u4E1A\u52A1\u903B\u8F91\uFF09\uFF1A\u5168\u90E8 match \u6761\u4EF6\u6EE1\u8DB3\u624D\u547D\u4E2D\u3002\n // \u8FD4\u56DE { label, ruleId, ancestry }\u2014\u2014ancestry \u4E3A\u7956\u5148\u94FE\u6458\u8981\uFF08\u6297\u6539\u7248\u91CD\u5B9A\u4F4D\u7528\uFF09\u3002\n // T014\uFF1A\u6761\u4EF6\u5224\u5B9A\u7EDF\u4E00\u8D70 ruleHits\uFF08\u4E0E region: \u8868\u8FBE\u5F0F\u5171\u7528\uFF0C\u9632\u6F02\u79FB\uFF09\u3002\n function identifyRegion(selector, index, rulesJson) {\n const target = pick(selector, index);\n if (!target) return null;\n let rules = [];\n try { rules = JSON.parse(rulesJson || '[]'); } catch (e) { rules = []; }\n const ancestry = [];\n let el = target;\n for (let d = 0; d < 5 && el; d++) {\n if (el !== target && el.nodeType === 1) {\n const cls = (el.className || '').toString().slice(0, 40);\n ancestry.push(el.tagName.toLowerCase() + (cls ? '.' + cls : ''));\n }\n // \u7528\u89C4\u5219\u5339\u914D\u5F53\u524D\u7956\u5148\n for (const r of rules) {\n if (ruleHits(r, el)) {\n return { label: r.label, ruleId: r.id, ancestry: ancestry.slice(0, 3) };\n }\n }\n el = el.parentElement;\n }\n return { label: undefined, ruleId: undefined, ancestry: ancestry.slice(0, 3) };\n }\n // T014 P0-3\uFF1A\u6CE8\u5165\u533A\u57DF\u89C4\u5219\uFF08behavior.rules.json \u968F\u6D41\u7A0B\u5305\u5206\u53D1\uFF1Bregion: \u8868\u8FBE\u5F0F\u4E0E describeHit\n // \u533A\u57DF\u6807\u6CE8\u5171\u7528\uFF1B\u5F15\u64CE\u96F6\u4E1A\u52A1\u903B\u8F91\u2014\u2014\u53EA\u5B58\u89C4\u5219 JSON\uFF09\u3002\u5E42\u7B49\uFF1A\u91CD\u590D\u6CE8\u5165\u8986\u76D6\u3002\n function setRegionRules(rulesJson) {\n let rules = [];\n try { rules = JSON.parse(rulesJson || '[]'); } catch (e) { rules = []; }\n window.__rpaDom._regionRules = rules;\n return true;\n }\n // #16-A \u70B9\u51FB\u524D\u906E\u6321\u68C0\u6D4B\uFF1A\u8FD4\u56DE (x,y) \u5904\u6700\u9876\u5C42\u5143\u7D20 + \u8BE5\u5143\u7D20\u662F\u5426\u5C5E\u4E8E\u76EE\u6807\uFF08\u76EE\u6807\u6216\u5176\u540E\u4EE3\uFF09\n function hitTestAt(selector, index, x, y) {\n const target = pick(selector, index);\n if (!target) return { found: false };\n const top = document.elementFromPoint(x, y);\n if (!top) return { found: true, hit: false, top: null };\n const hit = top === target || target.contains(top);\n return {\n found: true,\n hit,\n top: describeEl(top),\n target: describeEl(target),\n };\n }\n // #16-A \u5BF9\u8BDD\u6846\u5173\u95ED\uFF1A\u906E\u6321\u8005\uFF08dialog-layer \u7B49\u906E\u7F69\uFF09\u5185\u7684\u5173\u95ED\u6309\u94AE\u5750\u6807\uFF08\u4F9B\u5F15\u64CE\u70B9\u51FB\u5173\u95ED\u540E\u91CD\u8BD5\uFF09\n function findDialogClose(selector, index) {\n const target = pick(selector, index);\n if (!target) return null;\n const cx = Math.round(target.getBoundingClientRect().left + target.getBoundingClientRect().width / 2);\n const cy = Math.round(target.getBoundingClientRect().top + target.getBoundingClientRect().height / 2);\n const top = document.elementFromPoint(cx, cy);\n if (!top) return null;\n // \u627E\u6700\u9876\u5C42\u5143\u7D20\u4F5C\u4E3A\u906E\u6321\u8005\uFF08dialog-layer/\u906E\u7F69\uFF09\uFF0C\u5728\u5176\u5185\u627E\u5173\u95ED\u6309\u94AE\n let blocker = top;\n while (blocker && blocker !== document.body) {\n const s = getComputedStyle(blocker);\n if ((s.position === 'fixed' || s.position === 'absolute') && parseFloat(s.zIndex) >= 100) break;\n blocker = blocker.parentElement;\n }\n if (!blocker || blocker === document.body) return null;\n // \u5173\u95ED\u6309\u94AE\u641C\u7D22\u8303\u56F4\uFF1A\u906E\u6321\u8005\u81EA\u8EAB + \u5176\u5BF9\u8BDD\u6846\u7956\u5148\uFF08T014 \u9636\u6BB5 2 \u5B9E\u8DD1\u53D1\u73B0\uFF1A\u6B8B\u7559\u5BF9\u8BDD\u6846\u7684\n // .dialog-layer \u906E\u7F69\u4E0B\u5173\u95ED\u6309\u94AE\u5728 dialog-wrap \u5185\u2014\u2014v-leave \u5361\u4F4F\u7684\u5BF9\u8BDD\u6846\u906E\u6321\u6574\u9875\u65F6\uFF0C\n // \u5F15\u64CE\u70B9\u51FB\u5176\u5173\u95ED\u6309\u94AE\u515C\u5E95\u5173\u95ED\uFF0C\u91CD\u8BD5\u76EE\u6807\u70B9\u51FB\uFF09\u3002\n let scope = blocker;\n let walk = blocker.parentElement;\n while (walk && walk !== document.body) {\n if (/(dialog|layer|mask|modal|popup|panel)/i.test((walk.className || '').toString()) && walk.getBoundingClientRect().width > 0) {\n scope = walk;\n }\n walk = walk.parentElement;\n }\n // \u5173\u95ED\u6309\u94AE\u5019\u9009\uFF1Aclass \u542B close / \u56FE\u6807\u6309\u94AE / \u6587\u672C \u00D7\n const closeBtns = [...scope.querySelectorAll('a,button,[role=\"button\"],i,span,div')].filter((el) => {\n const cls = (el.className || '').toString();\n const txt = (el.innerText || '').trim();\n const r = el.getBoundingClientRect();\n return r.width > 8 && r.height > 8 &&\n (/close|close-btn|icon-close|dialog-close|del|btn-close/i.test(cls) || txt === '\u00D7' || txt === '\u2715' || txt === 'X' || txt === '\u5173\u95ED');\n });\n const btn = closeBtns[0];\n if (!btn) return null;\n const r = btn.getBoundingClientRect();\n return { x: Math.round(r.left + r.width / 2), y: Math.round(r.top + r.height / 2), cls: (btn.className || '').toString().slice(0, 40) };\n }\n // #16-A \u539F\u5B50\u5316\u906E\u6321\u68C0\u6D4B\uFF1A\u4E0E rect \u91C7\u96C6\u540C\u4E00\u6B21\u6C42\u503C\uFF08\u6D88\u9664\u300C\u91C7\u96C6-\u68C0\u6D4B\u300D\u4E24\u6B21\u6C42\u503C\u95F4\u7684 DOM \u7ADE\u6001\uFF09\u3002\n // \u8FD4\u56DE\u6BCF\u4E2A\u53EF\u89C1\u76EE\u6807\u7684 rect + \u4E2D\u5FC3\u70B9\u547D\u4E2D\u4FE1\u606F + \u5B89\u5168\u70B9\u51FB\u70B9\uFF1A\n // hit=true \u4E2D\u5FC3\u70B9\u547D\u4E2D\u76EE\u6807\u6216\u5176\u975E\u4EA4\u4E92\u540E\u4EE3\uFF08\u5B89\u5168\u70B9\u51FB\u70B9\uFF09\n // hit=false \u4E2D\u5FC3\u70B9\u88AB\u771F\u5B9E\u906E\u6321\uFF08\u6D6E\u52A8\u5C42/\u5F39\u5C42/\u5176\u5B83\u5143\u7D20\u8986\u76D6\uFF09\n // danger=true \u4E2D\u5FC3\u70B9\u843D\u5728\u76EE\u6807\u5185\u7684\u3010\u53EF\u4EA4\u4E92\u5B50\u5143\u7D20\u3011\uFF08\u94FE\u63A5/\u6309\u94AE\u7B49\uFF09\u4E0A\u2014\u2014\u70B9\u51FB\u4F1A\u89E6\u53D1\u5B50\u5143\u7D20\u800C\u975E\u76EE\u6807\n // safe \u77E9\u5F62\u5185\u626B\u63CF\u5230\u7684\u7B2C\u4E00\u4E2A\u300C\u5C5E\u4E8E\u76EE\u6807\u4E14\u975E\u4EA4\u4E92\u5B50\u5143\u7D20\u300D\u7684\u70B9\uFF08\u4E2D\u5FC3\u88AB\u76D6/\u5371\u9669\u65F6\u6362\u70B9\u7528\uFF09\uFF1B\n // \u65E0\u5B89\u5168\u70B9\u5219 null\uFF08\u6574\u4E2A\u76EE\u6807\u88AB\u76D6 \u2192 \u5F15\u64CE\u4FA7 ESC \u5173\u95ED\u6D6E\u52A8\u5C42\u91CD\u8BD5\uFF09\n // top=null\uFF08elementFromPoint \u65E0\u7ED3\u679C\uFF0C\u5982\u5143\u7D20\u90E8\u5206\u5728\u89C6\u53E3\u5916/\u6E32\u67D3\u8FB9\u7F18\uFF09\u2192 \u4E0D\u89C6\u4E3A\u906E\u6321\uFF0Chit=true\u3002\n function clickableRects(selector) {\n const d = window.__rpaDom;\n const nodes = d && d.getBySels ? d.getBySels(selector) : [];\n const vw = innerWidth || document.documentElement.clientWidth;\n const vh = innerHeight || document.documentElement.clientHeight;\n const out = [];\n const INTERACTIVE = 'a,button,[role=\"button\"],[role=\"link\"],input,select,textarea,[onclick],label';\n const isSafeHit = (el, top) => {\n if (!top) return false;\n if (top === el) return true;\n if (el.contains(top)) {\n return !(top.closest && top.closest(INTERACTIVE) === top);\n }\n return false;\n };\n for (let i = 0; i < nodes.length; i++) {\n const el = nodes[i];\n const t = el.getBoundingClientRect();\n const fullW = Math.max(1, t.width);\n const fullH = Math.max(1, t.height);\n const x = Math.max(t.left, 0);\n const y = Math.max(t.top, 0);\n const w = Math.max(0, Math.min(t.right, vw) - x);\n const h = Math.max(0, Math.min(t.bottom, vh) - y);\n if (w <= 0 || h <= 0) continue;\n // #16-A \u53EF\u89C1\u6BD4\u4F8B\uFF1A\u89C6\u53E3\u5185\u9762\u79EF / \u603B\u9762\u79EF\u3002\u7A84\u89C6\u53E3\u4E0B\u5927\u90E8\u5206\u5728\u89C6\u53E3\u5916\u7684\u5143\u7D20\n // \uFF08\u5982\u9876\u90E8\u5BFC\u822A\u53F3\u4FA7\u7684\u641C\u7D22\u6309\u94AE\uFF09\u70B9\u51FB\u5750\u6807\u4F1A\u843D\u5728\u89C6\u53E3\u5916/\u8FB9\u7F18 \u2192 CDP input \u6302\u8D77\u6216\u70B9\u7A7A\u3002\n // \u53EF\u89C1\u6BD4\u4F8B < 50% \u2192 \u6807\u8BB0 partiallyVisible\uFF0C\u5F15\u64CE\u4FA7\u62A5\u300C\u76EE\u6807\u5927\u90E8\u5206\u5728\u89C6\u53E3\u5916\u300D\u800C\u975E\u5C1D\u8BD5\u70B9\u51FB\u3002\n const visibleRatio = (w * h) / (fullW * fullH);\n const partiallyVisible = visibleRatio < 0.5;\n if (partiallyVisible) {\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: false, danger: false, safe: null, partiallyVisible: true, top: null, target: describeEl(el),\n });\n continue;\n }\n const cx = Math.round(x + w / 2);\n const cy = Math.round(y + h / 2);\n const top = document.elementFromPoint(cx, cy);\n if (!top) {\n // elementFromPoint \u65E0\u7ED3\u679C\uFF1A\u53EF\u89C1\u6BD4\u4F8B\u8DB3\u591F\uFF08\u226550%\uFF09\u4F46\u53D6\u4E0D\u5230\u9876\u5C42\u5143\u7D20 \u2192 \u6E32\u67D3\u8FB9\u754C\u5BB9\u5FCD\uFF0C\u4E0D\u963B\u585E\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: true, danger: false, safe: null, partiallyVisible: false, top: null, target: describeEl(el),\n });\n continue;\n }\n // T014 \u9636\u6BB5 2 \u5B9E\u8DD1\u53D1\u73B0\uFF1A\u6B8B\u7559\u5BF9\u8BDD\u6846 mask\uFF08visibility:hidden / opacity:0 \u7684\u5168\u5C4F\u906E\u7F69\u2014\u2014\n // zhipin \u57CE\u5E02\u9009\u62E9\u5668\u5173\u95ED\u540E .dialog-layer \u7559\u5728 DOM\uFF09\u4F1A\u88AB elementFromPoint \u547D\u4E2D\uFF0C\u4F46\u771F\u5B9E\n // \u6307\u9488\u4E8B\u4EF6\u7A7F\u900F\uFF08\u4E0D\u53EF\u89C1\u5143\u7D20\u4E0D\u53C2\u4E0E\u547D\u4E2D\u6D4B\u8BD5\uFF09\u2192 \u4E0D\u6784\u6210\u771F\u5B9E\u906E\u6321\uFF0C\u89C6\u4E3A hit\uFF08\u9632 ESC \u6B7B\u5FAA\u73AF\uFF09\u3002\n const topStyle = getComputedStyle(top);\n if (topStyle.visibility === 'hidden' || parseFloat(topStyle.opacity) === 0) {\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: true, danger: false, safe: null, partiallyVisible: false, top: null, target: describeEl(el),\n });\n continue;\n }\n const belongs = top === el || el.contains(top);\n const isInteractiveChild = top !== el && el.contains(top) && top.closest && top.closest(INTERACTIVE) === top;\n // \u4E2D\u5FC3\u88AB\u76D6/\u5371\u9669 \u2192 \u626B\u63CF\u77E9\u5F62\u5185\u90E8\u627E\u5B89\u5168\u70B9\uFF085\u00D75 \u7F51\u683C\uFF0C\u907F\u5F00\u4EA4\u4E92\u5B50\u5143\u7D20\u4E0E\u906E\u6321\uFF09\n let safe = null;\n if (!(belongs && !isInteractiveChild)) {\n for (let gy = 1; gy <= 4 && !safe; gy++) {\n for (let gx = 1; gx <= 4 && !safe; gx++) {\n const sx = Math.round(x + (w * gx) / 5);\n const sy = Math.round(y + (h * gy) / 5);\n const st = document.elementFromPoint(sx, sy);\n if (st && isSafeHit(el, st)) safe = { x: sx, y: sy };\n }\n }\n }\n out.push({\n x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h),\n hit: belongs && !isInteractiveChild,\n danger: isInteractiveChild,\n safe,\n partiallyVisible: false,\n top: describeEl(top),\n target: describeEl(el),\n });\n }\n return out;\n }\n // #16-A \u76EE\u6807\u77E9\u5F62\u5185\u627E\u5B89\u5168\u70B9\u51FB\u70B9\uFF1A\u7F51\u683C\u91C7\u6837\uFF085\u00D75\uFF09\uFF0C\u8FD4\u56DE\u7B2C\u4E00\u4E2A\u300C\u5C5E\u4E8E\u76EE\u6807\u4E14\u4E0D\u5728\u53EF\u4EA4\u4E92\u5B50\u5143\u7D20\u4E0A\u300D\u7684\u70B9\u3002\n // \u5927\u5BB9\u5668\u5361\u7247\uFF08\u5982 job-card-wrap\uFF09\u4E2D\u5FC3\u5E38\u843D\u5728\u516C\u53F8\u94FE\u63A5/\u804C\u4F4D\u8BE6\u60C5\u94FE\u63A5\u4E0A\uFF0C\u76F4\u63A5\u70B9\u4F1A\u8BEF\u8DF3\u8F6C\uFF1B\n // \u5B89\u5168\u70B9\u907F\u5F00\u4EA4\u4E92\u5B50\u5143\u7D20\uFF08a/button/[onclick]\uFF09\uFF0C\u53EA\u843D\u5728\u6587\u672C/\u7A7A\u767D\u533A\u57DF\uFF08\u70B9\u51FB\u4ECD\u7531\u7236\u7EA7\u4E8B\u4EF6\u5192\u6CE1\u5904\u7406\uFF09\u3002\n function safePointIn(selector, index) {\n const el = pick(selector, index);\n if (!el) return null;\n const t = el.getBoundingClientRect();\n const INTERACTIVE = 'a,button,[role=\"button\"],[role=\"link\"],input,select,textarea,[onclick],label';\n for (let gy = 2; gy <= 3; gy++) {\n for (let gx = 2; gx <= 3; gx++) {\n const cx = Math.round(t.left + (t.width * gx) / 5);\n const cy = Math.round(t.top + (t.height * gy) / 5);\n const top = document.elementFromPoint(cx, cy);\n if (!top) continue;\n if (top === el || el.contains(top)) {\n const bad = top !== el && top.closest && top.closest(INTERACTIVE) === top;\n if (!bad) return { x: cx, y: cy };\n }\n }\n }\n // \u515C\u5E95\uFF1A\u4E2D\u5FC3\u70B9\uFF08\u53EF\u80FD\u5371\u9669\uFF0C\u7531\u5F15\u64CE\u4FA7\u518D\u5224\u65AD\uFF09\n return { x: Math.round(t.left + t.width / 2), y: Math.round(t.top + t.height / 2) };\n }\n return { getBySels, scrollIntoView, scrollIntoViewRhythm, textOf, focusInput, pageText, numberedElements, describeEl, describeHit, hitTestAt, clickableRects, safePointIn, identifyRegion, findDialogClose, setRegionRules, _domVersion: 3 };\n})();\ntrue\n";
|
|
21
|
+
/**
|
|
22
|
+
* T007 P0-1:input 事件补派发(insertText 改 DOM value 不触发 input 事件——React 受控组件
|
|
23
|
+
* onChange 同步 state 需要;BATCH-D:导出为 const,mock 回归可直接求值,避免测试复制漂移)。
|
|
24
|
+
*/
|
|
25
|
+
export declare const INPUT_EVENT_DISPATCH = "(() => {\n const el = document.activeElement;\n if (el && typeof el.dispatchEvent === 'function') {\n el.dispatchEvent(new Event('input', { bubbles: true }));\n }\n})()";
|
|
21
26
|
/** ENV_CHECK 检测注入脚本(A1;页面端一次 evaluate 返回全部信号) */
|
|
22
27
|
export declare function envCheckSource(envCheck: EnvCheckConfig): string;
|
|
23
28
|
/** ENV_CHECK 配置(flow.json 顶层可选字段;平台可配置,引擎不硬编码) */
|
|
@@ -44,9 +49,13 @@ export declare class CdpPage implements PageOps {
|
|
|
44
49
|
private human?;
|
|
45
50
|
/** ENV_CHECK 配置(flow.json 顶层 envCheck;缺省不检测) */
|
|
46
51
|
private envCheck?;
|
|
52
|
+
/** T014 P0-3:区域规则 JSON(behavior.rules.json 的 regionRules 数组;region: 表达式/区域标注用) */
|
|
53
|
+
private regionRulesJson;
|
|
47
54
|
constructor(port: number, sim: SimProfile);
|
|
48
55
|
/** 设置 ENV_CHECK 配置(A1;流程加载时注入) */
|
|
49
56
|
setEnvCheck(config: EnvCheckConfig | undefined): void;
|
|
57
|
+
/** T014 P0-3:设置区域规则(behavior.rules.json 的 regionRules 数组;region: 表达式与 describeHit 区域标注用) */
|
|
58
|
+
setRegionRules(rulesJson: string): void;
|
|
50
59
|
/** 确保浏览器调试实例在跑(端口不可连则拉起 Edge;edgePath 空则直接报错) */
|
|
51
60
|
ensureBrowser(edgePath: string, userDataDir: string): Promise<void>;
|
|
52
61
|
/**
|
|
@@ -71,6 +80,8 @@ export declare class CdpPage implements PageOps {
|
|
|
71
80
|
private currentClient;
|
|
72
81
|
/** 注入 DOM 辅助(幂等:页面已有 __rpaDom 则跳过) */
|
|
73
82
|
private ensureDom;
|
|
83
|
+
/** 注入城市对话框 DOM 辅助(T012 P0-1;幂等:已有 cityLabelPos 则跳过) */
|
|
84
|
+
private ensureCityDom;
|
|
74
85
|
/** JS 字符串字面量(selector 插值安全) */
|
|
75
86
|
private jsStr;
|
|
76
87
|
/** ENV_CHECK 检测(A1 D-V2:每步执行前;命中 → 抛 verify 类别错误 + category=env_check) */
|
|
@@ -78,6 +89,14 @@ export declare class CdpPage implements PageOps {
|
|
|
78
89
|
/** 观察通道 DOM 快照(契约口径:文本快照摘要 + 编号元素) */
|
|
79
90
|
snapshotDom(): Promise<DomSnapshot>;
|
|
80
91
|
navigate(url: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* T012 P0-1:地区切换「界面操作优先」——zhipin 城市选择器 UI 路径,URL 参数仅兜底。
|
|
94
|
+
* 返回 true = UI 切换成功(或已是目标城市);false = 调用方回退 URL 导航。
|
|
95
|
+
* 导航可靠性优先:任何一步失败/超时都返回 false,绝不因拟人引入新失败。
|
|
96
|
+
* 仅当目标 URL 为 zhipin 且带 city 参数时尝试;其余直接返回 false(行为不变)。
|
|
97
|
+
* 决策流在 city-switch.runCitySwitchUi(接口驱动,mock 可确定性测试——BATCH-D)。
|
|
98
|
+
*/
|
|
99
|
+
private trySwitchCityViaUi;
|
|
81
100
|
click(selector: string, index: number): Promise<void>;
|
|
82
101
|
/** P2 区域识别:目标元素所在区域的语义描述(DOM_SOURCE.identifyRegion;旁路,失败返回 null) */
|
|
83
102
|
identifyRegion(selector: string, index: number, rulesJson: string): Promise<{
|
|
@@ -108,9 +127,9 @@ export declare class CdpPage implements PageOps {
|
|
|
108
127
|
}
|
|
109
128
|
/**
|
|
110
129
|
* T005 P0-2:路由命中登录页特征(host+path;平台无关特征词:/login /passport /captcha /web/user/ 等)。
|
|
111
|
-
*
|
|
130
|
+
* T014 P0-2:定义上移至 region.ts 统一实现(登录区双重特征),此处再导出保兼容(测试/调用方不变)。
|
|
112
131
|
*/
|
|
113
|
-
export
|
|
132
|
+
export { isLoginRoute } from '../region.js';
|
|
114
133
|
/**
|
|
115
134
|
* T005 P1-3:报错文案选择器消毒——selector 含占位符形态({...} 未替换变量 / <...> 掩码残留)时不泄漏
|
|
116
135
|
* 原文,显示友好描述(避免用户看到「点击 <keyword> 后页面跳转」这类占位符泄漏)。
|