diff --git a/.claude/skills/web-test/SKILL.md b/.claude/skills/web-test/SKILL.md index fd07007f..1a3b1922 100644 --- a/.claude/skills/web-test/SKILL.md +++ b/.claude/skills/web-test/SKILL.md @@ -170,7 +170,7 @@ const form = await getFormState(); **confirmation** — if present, a Yes/No dialog is shown. Call `clickElement('Да')` or `clickElement('Нет')`. -**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data. +**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data. The same info bar carries `"Поиск..."` while a list is still searching — actions do not return while it is up, so a filtered list never hands you the previous rows. ### Reading data diff --git a/.claude/skills/web-test/scripts/engine/core/state.mjs b/.claude/skills/web-test/scripts/engine/core/state.mjs index 612530f9..b275cfcc 100644 --- a/.claude/skills/web-test/scripts/engine/core/state.mjs +++ b/.claude/skills/web-test/scripts/engine/core/state.mjs @@ -1,4 +1,4 @@ -// web-test core/state v1.17 — module-level state for the web-test engine. +// web-test core/state v1.18 — module-level state for the web-test engine. // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills // // Holds the single browser/page/recorder slot plus the multi-context registry, @@ -80,6 +80,11 @@ export const ACTION_WAIT = 2000; // fallback minimum wait export const MAX_WAIT = 10000; // max wait for stability export const POLL_INTERVAL = 200; // polling interval export const STABLE_CYCLES = 3; // consecutive stable cycles needed +// Ceiling for the case where 1C is VISIBLY still working (its "Поиск…" state window is up). +// Higher than MAX_WAIT on purpose: a search on a production-sized list legitimately runs for +// tens of seconds, and returning mid-search hands the caller the previous rows — a wrong +// result that looks like a right one. Bounded so a wedged operation still ends the wait. +export const BUSY_MAX_WAIT = 60000; // 1C browser extension ID (stable across versions, defined by key in manifest.json) export const EXT_ID = 'pbhelknnhilelbnhfpcjlcabhmfangik'; diff --git a/.claude/skills/web-test/scripts/engine/core/wait.mjs b/.claude/skills/web-test/scripts/engine/core/wait.mjs index 4d6590a3..851b5850 100644 --- a/.claude/skills/web-test/scripts/engine/core/wait.mjs +++ b/.claude/skills/web-test/scripts/engine/core/wait.mjs @@ -1,30 +1,49 @@ -// web-test core/wait v1.17 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor. +// web-test core/wait v1.18 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor. // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills -import { page, MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs'; +import { page, MAX_WAIT, BUSY_MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs'; import { detectFormScript } from '../../dom.mjs'; /** * Smart wait: poll until DOM is stable and no loading indicators are visible. - * Checks: form number change, loading indicators, DOM stability. + * Checks: form number change, loading indicators, busy state window, DOM stability. * @param {number|null} previousFormNum — form number before the action (null = don't check) */ export async function waitForStable(previousFormNum = null) { let stableCount = 0; let lastSnapshot = ''; const start = Date.now(); + let deadline = start + MAX_WAIT; - while (Date.now() - start < MAX_WAIT) { + while (Date.now() < deadline) { await page.waitForTimeout(POLL_INTERVAL); // Check for loading indicators const status = await page.evaluate(`(() => { const loading = document.querySelector('.loadingImage, .waitCurtain, .progressBar'); const isLoading = loading && loading.offsetWidth > 0; + // While a dynamic list is still searching, 1C floats a state window over the grid + // ("Поиск…") — and NOTHING else in the DOM says so: the old rows stay put, the element + // counters below don't move, so the page looks perfectly stable with stale data. + // Match busy markers by text, never "state window present": the same carrier also holds + // TERMINAL report messages ("Отчет не сформирован", "Не установлено значение параметра"), + // and waiting for those to disappear would hang until the timeout on every report. + const busy = [...document.querySelectorAll('.stateWindowSupportSurface')].some(el => + el.offsetWidth > 0 && /^\\s*(Поиск|Ожид|Searching|Please wait)/i.test(el.innerText || '')); const formCount = document.querySelectorAll('input.editInput[id], a.press[id]').length; - return { isLoading, formCount }; + return { isLoading, busy, formCount }; })()`); + // A visible busy indicator outranks DOM stability — it is the only evidence we get that the + // server is still working. Push the deadline while it lasts (bounded by BUSY_MAX_WAIT) instead + // of reporting "stable": returning mid-search is what handed a caller the previous rows and + // let the next click open the wrong document. + if (status.busy) { + deadline = Math.min(Date.now() + MAX_WAIT, start + BUSY_MAX_WAIT); + stableCount = 0; + continue; + } + if (status.isLoading) { stableCount = 0; continue;