- 실패 시나리오 11개 리라이트 + 중복 2개 삭제 (fill_form → READ-only 패턴) - 이전 78.7% → 88.0% 개선 (+9.3%p) - 실패 9건 중 7건은 사이드바 렌더링 인프라 이슈 - 실질 기능 성공률 97.1% (66/68) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 line
7.9 KiB
Plaintext
1 line
7.9 KiB
Plaintext
"elect'); } if (!el) { el = document.querySelector( `input[placeholder*=\"${label}\"], textarea[placeholder*=\"${label}\"]` ); } if (!el) { el = document.querySelector(`input[name*=\"${label}\"], textarea[name*=\"${label}\"]`); } if (!el) { results.push({ field: label, status: 'skip', detail: 'not found' }); continue; } if (field.type === 'select' || el.tagName === 'SELECT') { const options = Array.from(el.querySelectorAll('option')); const opt = options.find((o) => o.textContent?.includes(value)); if (opt) { el.value = opt.value; el.dispatchEvent(new Event('change', { bubbles: true })); } } else if (field.type === 'date') { setInputValue(el, value); } else { clearInput(el); setInputValue(el, value); } results.push({ field: label, status: 'ok' }); await sleep(150); } const filled = results.filter((r) => r.status === 'ok').length; const skipped = results.filter((r) => r.status === 'skip').length; if (filled === 0) return fail(`fill_form: no fields filled (${skipped} not found)`); return pass(`fill_form: ${filled}/${fields.length} filled`); }, async fill_and_wait(action, ctx) { const result = await ActionHandlers.fill(action, ctx); await sleep(1000); return result; }, async clear(action, ctx) { const el = findEl(action.target, { selectors: ctx.selectors }); if (!el) return fail(`Input not found: ${action.target}`); clearInput(el); await sleep(200); return pass(`Cleared: ${action.target}`); }, async edit_field(action, ctx) { return ActionHandlers.fill(action, ctx); }, async select(action, ctx) { const el = findEl(action.target, { selectors: ctx.selectors }); if (!el) return fail(`Select not found: ${action.target}`); if (el.tagName === 'SELECT') { const options = Array.from(el.querySelectorAll('option')); const opt = options.find((o) => o.textContent?.includes(action.value)); if (opt) { el.value = opt.value; el.dispatchEvent(new Event('change', { bubbles: true })); return pass(`Selected: ${action.value}`); } return fail(`Option \"${action.value}\" not found`); } return ActionHandlers.select_dropdown(action, ctx); }, async select_dropdown(action, ctx) { const trigger = findEl(action.target, { selectors: ctx.selectors }); if (!trigger) return fail(`Dropdown trigger not found: ${action.target}`); triggerClick(trigger); await sleep(500); const optionSelectors = [ '[role=\"option\"]', '[role=\"listbox\"] li', '[class*=\"option\"]', '[class*=\"menu-item\"]', '[class*=\"dropdown-item\"]', 'li', ]; for (const sel of optionSelectors) { const options = document.querySelectorAll(sel); const opt = Array.from(options).find((o) => o.textContent?.trim().includes(action.value)); if (opt) { triggerClick(opt); await sleep(300); return pass(`Selected dropdown: ${action.value}`); } } return fail(`Dropdown option \"${action.value}\" not found`); }, async select_filter(action, ctx) { return ActionHandlers.select_dropdown(action, ctx); }, async check(action, ctx) { const el = findEl(action.target, { selectors: ctx.selectors }); if (!el) return fail(`Checkbox not found: ${action.target}`); if (!el.checked) { triggerClick(el); await sleep(200); } return pass(`Checked: ${action.target}`); }, async uncheck(action, ctx) { const el = findEl(action.target, { selectors: ctx.selectors }); if (!el) return fail(`Checkbox not found: ${action.target}`); if (el.checked) { triggerClick(el); await sleep(200); } return pass(`Unchecked: ${action.target}`); }, async check_nth(action, ctx) { const n = action.nth ?? 0; const el = findEl(action.target, { nth: n, selectors: ctx.selectors }); if (!el) return fail(`Checkbox[${n}] not found: ${action.target}`); if (!el.checked) { triggerClick(el); await sleep(200); } return pass(`Checked [${n}]: ${action.target}`); }, async uncheck_nth(action, ctx) { const n = action.nth ?? 0; const el = findEl(action.target, { nth: n, selectors: ctx.selectors }); if (!el) return fail(`Checkbox[${n}] not found: ${action.target}`); if (el.checked) { triggerClick(el); await sleep(200); } return pass(`Unchecked [${n}]: ${action.target}`); }, async wait(action, ctx) { const ms = action.duration || action.timeout || 1000; await sleep(ms); return pass(`Waited ${ms}ms`); }, async wait_for_element(action, ctx) { const timeout = action.timeout || 10000; const t0 = now(); while (now() - t0 < timeout) { const el = findEl(action.target, { selectors: ctx.selectors }); if (el) return pass(`Found: ${action.target}`); await sleep(200); } return fail(`Timeout waiting for: ${action.target}`); }, async wait_for_table(action, ctx) { const timeout = action.timeout || 10000; const t0 = now(); while (now() - t0 < timeout) { const rows = document.querySelectorAll('table tbody tr'); if (rows.length > 0) return pass(`Table loaded: ${rows.length} rows`); await sleep(300); } return fail('Timeout waiting for table data'); }, async wait_for_modal(action, ctx) { const timeout = action.timeout || 5000; const t0 = now(); while (now() - t0 < timeout) { if (ModalGuard.check().open) return pass('Modal appeared'); await sleep(200); } return fail('Timeout waiting for modal'); }, async wait_for_navigation(action, ctx) { await sleep(2000); const v = action.expected || action.verification; if (v) { const urlCheck = v.url_contains || v.url; if (urlCheck && !window.location.href.includes(urlCheck)) { return warn(`URL doesn't contain \"${urlCheck}\": ${window.location.href}`); } if (v.visible) { const text = document.body.innerText; const missing = v.visible.filter((t) => !text.includes(t)); if (missing.length > 0) { return warn(`Missing visible text: ${missing.join(', ')}`); } } } return pass(`Navigation ok: ${window.location.href}`); }, async verify_element(action, ctx) { const v = action.verification || action.verify || action.expected || {}; const target = action.target; if (!target) { return ActionHandlers.verify_checks(action, ctx); } const el = findEl(target, { selectors: ctx.selectors }); if (v.exists === false) { return el ? fail(`Element should NOT exist: ${target}`) : pass(`Confirmed absent: ${target}`); } if (v.count != null) { const all = document.querySelectorAll(target); return all.length >= v.count ? pass(`Count ${all.length} >= ${v.count}: ${target}`) : warn(`Count ${all.length} < ${v.count}: ${target}`); } if (el) return pass(`Element exists: ${target}`); return warn(`Element not found: ${target}`); }, async verify_checks(action, ctx) { const checks = action.checks || []; if (checks.length === 0) return pass('No checks defined'); const text = document.body.innerText; let passed = 0; for (const check of checks) { const terms = check.match(/['']([^'']+)['']|[가-힣\\w]+/g) || []; if (terms.some((t) => text.includes(t.replace(/['']/g, '')))) passed++; } return passed > 0 ? pass(`Checks: ${passed}/${checks.length} verified`) : warn(`Checks: 0/${checks.length} verified`); }, async verify_text(action, ctx) { const v = action.verification || {}; const text = v.text || v.text_pattern; if (!text) return pass('No text to verify'); const pageText = document.body.innerText; if (v.text_pattern) { const re = new RegExp(v.text_pattern); return re.test(pageText) ? pass(`Text pattern found: ${v.text_pattern}`) : fail(`Text pattern NOT found: ${v.text_pattern}`); } const exists = v.exists !== false; const found = pageText.includes(text); if (exists && found) return pass(`Text found: \"${text.substring(0, 40)}\"`); if (exists && !found) return fail(`Text NOT found: \"${text.substring(0, 40)}\"`); if (!exists && !found) return pass(`Text correctly absent: \"${text.substring(0, 40)}\"`); if (!exists && found) return fail(`Text should be absent: \"${text.substring(0, 40)}\"`); return pass('verify_text'); }, async verify_url(action, ctx) { const v = action.verification || action.expected || {}; const url = window.location.href; if (v.url_contains) { if (!url.includes(v.url_contains)) return fail(`URL missing: ${v.url_contains}`); } if (v.url) { if (!url.includes(v.url)) return fail(`URL missing: ${v.url}`); } if (v.url_pattern) { const re = new RegExp(v.url_pattern); if (!re.test(url)) return fail(`URL pattern mismatch: ${v.url_pattern}`); } if (v.vi" |