window.__C += 'avigate\',\n directNavigation: \'navigate\',\n navigateViaMenuClick: \'menu_navigate\',\n refresh: \'reload\',\n // Wait variants\n waitForModal: \'wait_for_modal\',\n waitForNavigation: \'wait_for_navigation\',\n waitForTable: \'wait_for_table\',\n // Verify variants\n verify_elements: \'verify_element\',\n verify_table_data: \'verify_table\',\n verify_table_structure: \'verify_table\',\n verify_field: \'verify_element\',\n verify_checkbox: \'verify_element\',\n verify_action_buttons: \'verify_element\',\n verify_pagination: \'verify_element\',\n verify_summary: \'verify_text\',\n verify_totals: \'verify_text\',\n verify_search_result: \'verify_data\',\n verify_row_count: \'verify_table\',\n verify_vendor_info: \'verify_detail\',\n verify_detail_info: \'verify_detail\',\n verify_data_update: \'verify_data\',\n verify_transactions_update: \'verify_data\',\n verify_transaction_table: \'verify_table\',\n verify_calculated_value: \'verify_text\',\n verify_toast: \'verify_toast\',\n verifyButtonExists: \'verify_element\',\n verifyUrl: \'verify_url\',\n verifyNoErrorPage: \'verify_url_stability\',\n verify: \'verify_element\',\n // Select variants\n select_option: \'select_dropdown\',\n select_or_click: \'select_dropdown\',\n combobox: \'select_dropdown\',\n // Modal\n closeModal: \'close_modal\',\n close_modal: \'close_modal\',\n modalClose: \'close_modal\',\n openModal: \'wait_for_modal\',\n checkModalOpen: \'wait_for_modal\',\n fillInModal: \'fill\',\n selectInModal: \'select_dropdown\',\n // Other\n confirm_dialog: \'click_dialog_confirm\',\n delete: \'click\',\n store: \'save_url\',\n getCurrentUrl: \'save_url\',\n clearSearch: \'clear\',\n blur: \'blur\',\n login: \'fill_form\',\n keypress: \'press_key\',\n press: \'press_key\',\n pressKey: \'press_key\',\n resize: \'noop\',\n log: \'noop\',\n manualVerification: \'noop\',\n generateTimestamp: \'generate_timestamp\',\n random: \'generate_timestamp\',\n setupDownloadListener: \'noop\',\n saveDownloadedFile: \'noop\',\n verifyDownload: \'noop\',\n verifyDownloadedFile: \'noop\',\n download: \'click\',\n // Date variants\n change_date: \'fill\',\n change_date_range: \'fill\',\n date_range: \'fill\',\n date: \'fill\',\n datepicker: \'fill\',\n setDateRange: \'fill\',\n timepicker: \'fill\',\n // Composite/special\n composite: \'noop\',\n hierarchy: \'noop\',\n permission: \'noop\',\n ifStillFailed: \'noop\',\n tryAlternativeUrls: \'noop\',\n element: \'verify_element\',\n elementExists: \'verify_element\',\n tableExists: \'verify_table\',\n tabsExist: \'verify_element\',\n error_message: \'verify_text\',\n warning: \'noop\',\n url: \'verify_url\',\n URL_STABILITY: \'verify_url_stability\',\n checkFor404: \'verify_url_stability\',\n // Expect/assert response\n expectResponse: \'noop\',\n assertResponse: \'noop\',\n apiResponse: \'noop\',\n findRow: \'click_row\',\n scroll: \'scrollAndFind\',\n radio: \'check\',\n toggle_switch: \'check\',\n capture: \'capture\',\n screenshot: \'capture\',\n drag_start: \'noop\',\n drag_over: \'noop\',\n drag_end: \'noop\',\n };\n return ALIASES[type] || type;\n }\n\n /**\n * Normalize a step into unified format:\n * Returns { stepId, name, subActions: [{type, target, value, ...}], critical, phase, verification }\n */\n function normalizeStep(step) {\n const stepId = step.id || step.step || 0;\n const name = step.name || step.description || `Step ${stepId}`;\n const critical = step.critical || false;\n const phase = step.phase || null;\n const verification = step.verification || step.verify || step.expected || null;\n\n let subActions = [];\n\n // Format B: actions array\n if (Array.isArray(step.actions)) {\n subActions = step.actions.map((a) => ({\n ...a,\n type: normalizeActionType(a.type),\n }));\n }\n // Format A: single action\n else if (step.action) {\n subActions = [\n {\n type: normalizeActionType(step.action),\n target: step.target,\n value: step.value,\n variable: step.variable,\n pattern: step.pattern,\n nth: step.nth,\n clear: step.clear,\n fields: step.fields,\n checks: step.checks,\n search: step.search,\n level1: step.level1,\n level2: step.level2,\n script: step.script,\n duration: step.duration,\n verification: step.verification,\n verify: step.verify,\n expected: step.expected,\n critical: step.critical,\n },\n ];\n }\n // No action defined - might be just verification\n else {\n subActions = [\n {\n type: \'noop\',\n verification,\n },\n ];\n }\n\n return { stepId, name, subActions, critical, phase, verification };\n }\n\n // ─── Input Helpers ──────────────────────────────────────\n\n /** Set value on an input/textarea using React-compatible events */\n function setInputValue(el, value) {\n if (!el) return false;\n\n const isTextarea = el instanceof HTMLTextAreaElement;\n const proto = isTextarea ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;\n const nativeSetter = Object.getOwnPropertyDescriptor(proto, \'value\')?.set;\n\n // Method 1: React internal __reactProps$ onChange (most reliable for React controlled components)\n const reactPropsKey = Object.keys(el).find(k => k.startsWith(\'__reactProps$\'));\n if (reactPropsKey && el[reactPropsKey] && typeof el[reactPropsKey].onChange === \'function\') {\n if (nativeSetter) nativeSetter.call(el, value);\n else el.value = value;\n el[reactPropsKey].onChange({ target: el, currentTarget: el });\n if (el.value === value) return true;\n }\n\n // Method 2: execCommand(\'insertText\') - goes through browser native input pipeline\n el.focus();\n el.select();\n const execResult = document.execCommand(\'insertText\', false, value);\n if (execResult && el.value === value) return true;\n\n // Method 3: native setter + _valueTracker reset + events (fallback)\n if (nativeSetter) nativeSetter.call(el, value);\n else el.value = value;\n const tracker = el._valueTracker;\n if (tracker) tracker.setValue(\'\');\n el.dispatchEvent(new Event(\'input\', { bubbles: true }));\n el.dispatchEvent(new Event(\'change\', { bubbles: true }));\n return true;\n }\n\n /** Clear an input */\n function clearInput(el) {\n if (!el) return false;\n el.focus();\n setInputValue(el, \'\');\n return true;\n }\n\n /** Trigger a click reliably */\n function triggerClick(el) {\n if (!el) return false;\n scrollIntoView(el);\n el.focus && el.focus();\n el.click();\n return true;\n }\n\n // ─── Action Handlers ────────────────────────────────────\n\n const ActionHandlers = {\n // ── Click group ──\n async click(action, ctx) {\n const el = findEl(action.target, { selectors: ctx.selectors });\n if (!el) return fail(`Element not found: ${action.target}`);\n scrollIntoView(el);\n await sleep(100);\n triggerClick(el);\n await sleep(300);\n return pass(`Clicked: ${action.target}`);\n },\n\n async click_nth(action, ctx) {\n const n = action.nth ?? 0;\n const el = findEl(action.target, { nth: n, selectors: ctx.selectors });\n if (!el) return fail(`Element[${n}] not found: ${action.target}`);\n scrollIntoView(el);\n triggerClick(el);\n await sleep(300);\n return pass(`Clicked [${n}]: ${action.target}`);\n },\n\n async click_row(action, ctx) {\n // Click a table row containing text\n const text = action.target || action.value;\n const rows = document.querySelectorAll(\'table tbody tr\');\n const row = Array.from(rows).find((r) => r.innerText?.includes(text));\n if (!row) return fail(`Row with "${text}" not found`);\n scrollIntoView(row);\n row.click();\n await sleep(500);\n return pass(`Clicked row: ${text}`);\n },\n\n async click_first_row(action, ctx) {\n const row = document.querySelector(\'table tbody tr\');\n if (!row) return fail(\'No table rows found\');\n scrollIntoView(row);\n row.click();\n await sleep(500);\n return pass(\'Clicked first row\');\n },\n\n async click_button(action, ctx) {\n // Find button by text\n const text = action.value || action.target;\n const btns = Array.from(document.querySelectorAll(\'button, [role="button"]\'));\n const btn = btns.find((b) => b.innerText?.trim().includes(text));\n if (!btn) return fail(`Button "${text}" not found`);\n scrollIntoView(btn);\n triggerClick(btn);\n await sleep(300);\n return pass(`Clicked button: ${text}`);\n },\n\n async click_dialog_confirm(action, ctx) {\n await sleep(300);\n const dialog =\n document.querySelector(\'[role="alertdialog"]\') ||\n document.querySelector(\'[role="dialog"]\');\n if (!dialog) return fail(\'No dialog found\');\n\n const confirmTexts = [\'확인\', \'예\', \'삭제\', \'OK\', \'Yes\', \'Confirm\'];\n const btns = Array.from(dialog.querySelectorAll(\'button\'));\n const btn = btns.find((b) =>\n confirmTexts.some((t) => b.innerText?.trim().includes(t))\n );\n if (!btn) return fail(\'Confirm button not found in dialog\');\n triggerClick(btn);\n await sleep(500);\n return pass(\'Confirmed dialog\');\n },\n\n // ── Fill group ──\n async fill(action, ctx) {\n let el = findEl(action.target, { selectors: ctx.selectors });\n if (!el) return fail(`Input not found: ${action.target}`);\n scrollIntoView(el);\n el.focus();\n if (action.clear !== false) clearInput(el);\n let value = action.value ?? \'\';\n // Replace {timestamp} placeholder\n value = replaceVars(value, ctx.variables);\n setInputValue(el, value);\n await sleep(200);\n return pass(`Filled "${action.target}" with "${value.substring(0, 30)}"`);\n },\n\n async fill_nth(action, ctx) {\n const n = action.nth ?? 0;\n const el = findEl(action.target, { nth: n, selectors: ctx.selectors });\n if (!el) return fail(`Input[${n}] not found: ${action.target}`);\n el.focus();\n clearInput(el);\n const value = replaceVars(action.value ?? \'\', ctx.variables);\n setInputValue(el, value);\n await sleep(200);\n return pass(`Filled [${n}] with "${value.substring(0, 30)}"`);\n },\n\n async fill_form(action, ctx) {\n const fields = action.fields || [];\n const results = [];\n for (const field of fields) {\n const label = field.name || field.label;\n let value = replaceVars(field.value ?? \'\', ctx.variables);\n\n // Try to find by label text → associated input\n let el = null;\n\n // Try label[for] → input\n const labels = Array.from(document.querySelectorAll(\'label\'));\n const matchLabel = labels.find((l) => l.textContent?.includes(label));\n if (matchLabel) {\n const forId = matchLabel.getAttribute(\'for\');\n if (forId) el = document.getElementById(forId);\n if (!el) el = matchLabel.querySelector(\'input, textarea, select\');\n if (!el) el = matchLabel.parentElement?.querySelector(\'input, textarea, select\');\n }\n\n // Fallback: placeholder search\n if (!el) {\n el = document.querySe';