mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-15 10:24:57 +03:00
feat(web-test): expose formCount, openForms, modal in getFormState; closed in closeForm
When the open-windows tab bar is hidden in 1C settings, the model had no way to know how many forms are open or whether a form is modal. Now getFormState returns openForms/formCount/modal derived from DOM form elements (independent of tab bar), and closeForm compares form number before/after Escape to return closed: true/false. Tested on ncc (tab bar hidden) and bpdemo (tab bar visible). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -121,9 +121,19 @@ Switch to an already-open tab/window (fuzzy match).
|
||||
|
||||
### Reading form state
|
||||
|
||||
#### `getFormState()` → `{ fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
|
||||
#### `getFormState()` → `{ form, formCount, openForms, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
|
||||
Returns current form structure. This is the primary way to understand what's on screen.
|
||||
|
||||
**form** — active form number, or `null` when no form is open (desktop).
|
||||
|
||||
**formCount** — number of open forms. Use this to know how many windows are stacked. `0` means desktop.
|
||||
|
||||
**openForms** — array of all open form numbers (e.g. `[0, 1]`). Works even when the open-windows tab bar is hidden in 1C settings.
|
||||
|
||||
**modal** — `true` when the active form is a modal dialog blocking the UI. Only present when modal is active.
|
||||
|
||||
**openTabs** — array of `{ name, active? }` from the open-windows tab bar. Only present when the tab bar is enabled in 1C settings. Do NOT rely on this — use `formCount`/`openForms` instead.
|
||||
|
||||
**fields** — each field has: `name`, `value`, `label?`, `actions?` (select, clear, open), `required?` (true for unfilled mandatory fields)
|
||||
|
||||
**navigation** — form navigation panel links (for objects with subordinate catalogs): `[{ name, active? }]`. Clickable via `clickElement()`. Only present when the form has a navigation panel (e.g. "Основное", "Объекты метаданных", "Подсистемы").
|
||||
@@ -303,8 +313,8 @@ await fillTableRow(
|
||||
#### `deleteTableRow(row, { tab?, table? })` → form state
|
||||
Delete row by 0-based index. `table` targets a specific grid on multi-grid forms.
|
||||
|
||||
#### `closeForm({ save? })` → form state
|
||||
Close the current form via Escape.
|
||||
#### `closeForm({ save? })` → form state with `closed`
|
||||
Close the current form via Escape. Returns form state with `closed: true/false` indicating whether the form actually closed.
|
||||
|
||||
| Argument | Behavior |
|
||||
|----------|----------|
|
||||
@@ -312,6 +322,8 @@ Close the current form via Escape.
|
||||
| `{ save: true }` | Auto-clicks "Да" on confirmation |
|
||||
| `{}` (omitted) | Returns `confirmation` field if dialog appears |
|
||||
|
||||
**`closed`** — `true` if the form was closed (form number changed), `false` if it stayed open (e.g. Escape was ignored). Always check this to confirm the form actually closed. After closing, check `formCount` to see how many forms remain.
|
||||
|
||||
Preferred over `clickElement('×')` — close buttons on tabs are ambiguous.
|
||||
|
||||
#### `filterList(text, opts?)` → form state
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test browser v1.4 — Playwright browser management for 1C web client
|
||||
// web-test browser v1.5 — Playwright browser management for 1C web client
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* Playwright browser management for 1C web client.
|
||||
@@ -1893,8 +1893,9 @@ export async function clickElement(text, { dblclick, table, toggle, expand } = {
|
||||
export async function closeForm({ save } = {}) {
|
||||
ensureConnected();
|
||||
await dismissPendingErrors();
|
||||
const beforeForm = await page.evaluate(detectFormScript());
|
||||
await page.keyboard.press('Escape');
|
||||
await waitForStable();
|
||||
await waitForStable(beforeForm);
|
||||
const state = await getFormState();
|
||||
const err = await checkForErrors();
|
||||
if (err?.confirmation) {
|
||||
@@ -1906,15 +1907,19 @@ export async function closeForm({ save } = {}) {
|
||||
const txt = (await b.textContent()).trim();
|
||||
if (txt === label) {
|
||||
await b.click({ force: true });
|
||||
await waitForStable();
|
||||
await waitForStable(beforeForm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return await getFormState();
|
||||
const afterState = await getFormState();
|
||||
afterState.closed = afterState.form !== beforeForm;
|
||||
return afterState;
|
||||
}
|
||||
state.confirmation = err.confirmation;
|
||||
state.hint = 'Confirmation dialog shown. Click "Да" to confirm or "Нет" to cancel';
|
||||
return state;
|
||||
}
|
||||
state.closed = state.form !== beforeForm;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test dom v1.1 — DOM selectors and semantic mapping for 1C web client
|
||||
// web-test dom v1.2 — DOM selectors and semantic mapping for 1C web client
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
/**
|
||||
* DOM selectors and semantic mapping for 1C:Enterprise web client.
|
||||
@@ -32,6 +32,21 @@ const DETECT_FORM_FN = `function detectForm() {
|
||||
return candidates.reduce((best, n) => counts[n] > counts[best] ? n : best);
|
||||
}`;
|
||||
|
||||
/** Detect all open forms + modal state. Returns { activeForm, allForms, formCount, modal }.
|
||||
* Works even when the open-windows tab bar is hidden. */
|
||||
const DETECT_FORMS_FN = `function detectForms() {
|
||||
const counts = {};
|
||||
document.querySelectorAll('input.editInput[id], a.press[id]').forEach(el => {
|
||||
if (el.offsetWidth === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) counts[m[1]] = (counts[m[1]] || 0) + 1;
|
||||
});
|
||||
const nums = Object.keys(counts).map(Number);
|
||||
const modal = document.getElementById('modalSurface');
|
||||
const isModal = !!(modal && modal.offsetWidth > 0);
|
||||
return { allForms: nums.sort((a, b) => a - b), formCount: nums.length, modal: isModal };
|
||||
}`;
|
||||
|
||||
/** Read form state given prefix p. Returns { fields, buttons, tabs, texts, hyperlinks, table, iframes }. */
|
||||
const READ_FORM_FN = `function readForm(p) {
|
||||
const result = {};
|
||||
@@ -586,12 +601,14 @@ export function readTableScript(formNum, { maxRows = 20, offset = 0, gridSelecto
|
||||
export function getFormStateScript() {
|
||||
return `(() => {
|
||||
${DETECT_FORM_FN}
|
||||
${DETECT_FORMS_FN}
|
||||
${READ_FORM_FN}
|
||||
const formNum = detectForm();
|
||||
if (formNum === null) return { form: null, message: 'No form detected' };
|
||||
const meta = detectForms();
|
||||
if (formNum === null) return { form: null, formCount: 0, message: 'No form detected' };
|
||||
const p = 'form' + formNum + '_';
|
||||
const formData = readForm(p);
|
||||
// Open tabs bar
|
||||
// Open tabs bar (present only when tab panel is enabled in 1C settings)
|
||||
const openTabs = [];
|
||||
document.querySelectorAll('[id^="openedCell_cmd_"]').forEach(el => {
|
||||
const text = el.innerText?.trim();
|
||||
@@ -601,7 +618,10 @@ export function getFormStateScript() {
|
||||
openTabs.push(entry);
|
||||
});
|
||||
const activeTab = openTabs.find(t => t.active)?.name || null;
|
||||
return { form: formNum, activeTab, ...formData };
|
||||
const result = { form: formNum, activeTab, openForms: meta.allForms, formCount: meta.formCount, ...formData };
|
||||
if (meta.modal) result.modal = true;
|
||||
if (openTabs.length) result.openTabs = openTabs;
|
||||
return result;
|
||||
})()`;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ await closeForm({ save: false });
|
||||
|
||||
| Функция | Описание | Возвращает |
|
||||
|---------|----------|------------|
|
||||
| `getFormState()` | Структура формы: поля, кнопки, таблица, фильтры | `{ fields, buttons, tabs, table, filters, reportSettings? }` |
|
||||
| `getFormState()` | Структура формы: поля, кнопки, таблица, фильтры, состояние окон | `{ form, formCount, openForms, fields, buttons, tabs, table, filters, reportSettings? }` |
|
||||
| `readTable({ maxRows?, offset? })` | Данные таблицы с пагинацией | `{ columns, rows: [{col: val}], total }` |
|
||||
| `readSpreadsheet()` | Результат отчёта | `{ title?, headers?, data?, totals?, total }` |
|
||||
| `getSections()` | Разделы и команды | `{ activeSection, sections, commands }` |
|
||||
@@ -215,6 +215,11 @@ await closeForm({ save: false });
|
||||
|
||||
Основной способ «увидеть» что на экране:
|
||||
|
||||
- **form** — номер активной формы, `null` когда ничего не открыто (десктоп)
|
||||
- **formCount** — количество открытых форм. `0` = десктоп. Работает даже если панель открытых окон скрыта
|
||||
- **openForms** — `[0, 1, 2]` — номера всех открытых форм в DOM
|
||||
- **modal** — `true` когда активная форма — модальный диалог, блокирующий интерфейс
|
||||
- **openTabs** — `[{ name, active? }]` из панели открытых окон (только когда панель включена в настройках 1С)
|
||||
- **fields** — `[{ name, value, label?, actions?, required? }]`. `actions` = select/clear/open. `required: true` = незаполненное обязательное поле
|
||||
- **table** — `{ name, columns, rowCount }` (метаданные; для данных — `readTable()`)
|
||||
- **reportSettings** — DCS-фильтры в читаемом виде: `[{ name: "Склад", enabled: true, value: "..." }]`
|
||||
@@ -239,7 +244,7 @@ await closeForm({ save: false });
|
||||
| `selectValue(field, search, opts?)` | Выбрать из справочника. search: текст или `{поле: значение}`. `{ type }` для составного типа | form state с `selected` |
|
||||
| `fillTableRow(fields, {tab?, add?, row?})` | Заполнить строку. Значение: строка или `{ value, type }` для составного типа | form state |
|
||||
| `deleteTableRow(row, {tab?})` | Удалить строку по индексу | form state |
|
||||
| `closeForm({save?})` | Закрыть форму. `save: false` = "Нет", `save: true` = "Да" | form state |
|
||||
| `closeForm({save?})` | Закрыть форму. `save: false` = "Нет", `save: true` = "Да". Возвращает `closed: true/false` | form state с `closed` |
|
||||
| `filterList(text, {field?, exact?})` | Фильтр списка. Без field = все колонки, с field = расширенный поиск | form state |
|
||||
| `unfilterList({field?})` | Снять фильтры (все или конкретный) | form state |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user