1 line
10 KiB
JavaScript
1 line
10 KiB
JavaScript
|
|
window.__C += '\'Navigate back\' };\n },\n\n async menu_navigate(action, ctx) {\n // Complex menu navigation - handled by scrollAndFind + click pattern\n // Expand all menus first\n const expandBtn = Array.from(document.querySelectorAll(\'button\')).find((b) =>\n b.innerText?.includes(\'모두 펼치기\')\n );\n if (expandBtn) {\n expandBtn.click();\n await sleep(1500);\n }\n\n // Find and click level1\n const l1 = action.level1;\n const l2 = action.level2;\n\n if (l1) {\n const l1El = findEl(l1);\n if (l1El) {\n triggerClick(l1El);\n await sleep(500);\n }\n }\n if (l2) {\n const l2El = findEl(l2);\n if (l2El) {\n triggerClick(l2El);\n await sleep(2000);\n }\n }\n\n // Check expected URL\n const v = action.expected || {};\n if (v.url_contains && !window.location.href.includes(v.url_contains)) {\n return warn(`Menu nav: URL missing "${v.url_contains}"`);\n }\n return pass(`Menu navigation: ${l1} > ${l2}`);\n },\n\n // ── Noop ──\n async noop(action, ctx) {\n return pass(\'No action\');\n },\n };\n\n // ─── Result Constructors ────────────────────────────────\n\n function pass(details) {\n return { status: \'pass\', details };\n }\n function fail(details) {\n return { status: \'fail\', details };\n }\n function warn(details) {\n return { status: \'warn\', details };\n }\n\n // ─── Variable Replacement ───────────────────────────────\n\n function replaceVars(str, vars) {\n if (typeof str !== \'string\') return str;\n // Replace {timestamp}\n str = str.replace(/\\{timestamp\\}/g, () => {\n const n = new Date();\n const pad = (v) => v.toString().padStart(2, \'0\');\n return `${n.getFullYear()}${pad(n.getMonth() + 1)}${pad(n.getDate())}_${pad(n.getHours())}${pad(n.getMinutes())}${pad(n.getSeconds())}`;\n });\n // Replace {variableName}\n if (vars) {\n str = str.replace(/\\{(\\w+)\\}/g, (_, key) => vars[key] ?? `{${key}}`);\n }\n return str;\n }\n\n // ─── Retry Engine ───────────────────────────────────────\n\n async function retryAction(handler, action, ctx, maxRetries = 2, delayMs = 500) {\n let lastResult;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n lastResult = await handler(action, ctx);\n if (lastResult.status === \'pass\' || lastResult.status === \'navigation\' || lastResult.status === \'native_required\') {\n return lastResult;\n }\n if (attempt < maxRetries) {\n // Pre-retry actions\n if (lastResult.details?.includes(\'not found\') || lastResult.details?.includes(\'not visible\')) {\n await sleep(delayMs);\n // Try closing overlays\n if (ModalGuard.check().open) await ModalGuard.close();\n } else {\n await sleep(delayMs);\n }\n }\n }\n return lastResult;\n }\n\n // ─── Batch Runner ───────────────────────────────────────\n\n /**\n * Run a batch of steps\n * @param {Array} steps - array of step objects from scenario JSON\n * @param {Object} vars - variables carried between batches\n * @param {Object} config - { selectors }\n * @returns {BatchResult}\n */\n async function runBatch(steps, vars = {}, config = {}) {\n const results = [];\n const ctx = {\n variables: { ...vars },\n selectors: config.selectors || {},\n };\n const startUrl = window.location.href;\n let stoppedReason = \'complete\';\n let stoppedAtIndex = steps.length;\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i];\n const normalized = normalizeStep(step);\n const
|