mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
feat(web-test): clear fields via empty value — Shift+F4 in fillFields, selectValue, fillTableRow
Pass '' or null as value to clear any field (except checkbox/radio) via native 1C Shift+F4. Returns method: 'clear'. Handles tree grids (close selection form first) and flat grids (dblclick to enter edit mode). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -240,6 +240,7 @@ Fill form fields by label (fuzzy match). Auto-detects field type.
|
||||
| `'5000'` | Plain text | Clipboard paste |
|
||||
| `'true'` / `'да'` | Checkbox | Toggle |
|
||||
| `'Оплата поставщику'` | Radio | Fuzzy label match |
|
||||
| `''` / `null` | Any (except checkbox/radio) | Clear via Shift+F4 |
|
||||
|
||||
**DCS report filters**: use human-readable label names. Checkbox is auto-enabled:
|
||||
```js
|
||||
@@ -250,10 +251,10 @@ await fillFields({
|
||||
```
|
||||
|
||||
Returns `{ filled: [{ field, ok, value, method }], form: {...} }`.
|
||||
Method is one of: `'toggle'` | `'radio'` | `'paste'` | `'dropdown'` | `'form'` | `'typeahead'`
|
||||
Method is one of: `'clear'` | `'toggle'` | `'radio'` | `'paste'` | `'dropdown'` | `'form'` | `'typeahead'`
|
||||
|
||||
#### `selectValue(field, search, opts?)` → form state with `selected`
|
||||
Select a value from reference field via dropdown or selection form. More reliable than `fillFields` for reference fields that need exact selection from a catalog.
|
||||
Select a value from reference field via dropdown or selection form. More reliable than `fillFields` for reference fields that need exact selection from a catalog. Pass empty `search` (`''` or `null`) to clear the field (Shift+F4).
|
||||
|
||||
`search` — string for simple search, or `{ field: value }` object for per-field advanced search:
|
||||
```js
|
||||
@@ -274,7 +275,7 @@ await selectValue('Документ', '0000-000601', { type: 'Реализаци
|
||||
Also supports DCS labels — auto-enables the paired checkbox.
|
||||
|
||||
#### `fillTableRow(fields, opts)` → form state
|
||||
Fill table row cells via Tab navigation. Value is a plain string or `{ value, type }` for composite-type cells.
|
||||
Fill table row cells via Tab navigation. Value is a plain string, `{ value, type }` for composite-type cells, or `''`/`null` to clear (Shift+F4).
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
@@ -435,7 +436,7 @@ Table matching accepts both technical name (`tables[].name`) and visual label (`
|
||||
| Key | Context | Action |
|
||||
|-----|---------|--------|
|
||||
| `F8` | Reference field focused | Create new catalog item |
|
||||
| `Shift+F4` | Reference field focused | Clear field value |
|
||||
| `Shift+F4` | Any input field focused | Clear field value (auto via `''`/`null` in fillFields/selectValue/fillTableRow) |
|
||||
| `F4` | Reference field focused | Open selection form |
|
||||
| `Alt+F` | List/table form | Open advanced search dialog |
|
||||
|
||||
|
||||
@@ -1834,6 +1834,19 @@ export async function fillFields(fields) {
|
||||
await waitForStable();
|
||||
}
|
||||
const selector = `[id="${r.inputId}"]`;
|
||||
// Clear field via Shift+F4 if value is empty (not applicable to checkbox/radio)
|
||||
const rawValue = fields[r.field];
|
||||
const isEmpty = rawValue === '' || rawValue === null || rawValue === undefined;
|
||||
if (isEmpty && !r.isCheckbox && !r.isRadio) {
|
||||
await page.click(selector);
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('Shift+F4');
|
||||
await page.waitForTimeout(300);
|
||||
await page.keyboard.press('Tab');
|
||||
await waitForStable();
|
||||
results.push({ field: r.field, ok: true, value: '', method: 'clear' });
|
||||
continue;
|
||||
}
|
||||
if (r.isCheckbox) {
|
||||
// Checkbox: compare desired with current, toggle if mismatch
|
||||
const desired = String(fields[r.field]).toLowerCase();
|
||||
@@ -2314,6 +2327,27 @@ export async function selectValue(fieldName, searchText, { type } = {}) {
|
||||
if (highlightMode) try { await highlight(fieldName); await page.waitForTimeout(500); await unhighlight(); } catch {}
|
||||
try {
|
||||
|
||||
// === CLEAR FIELD if searchText is empty/null ===
|
||||
if (!searchText && searchText !== 0) {
|
||||
const inputId = await page.evaluate(`(() => {
|
||||
const p = 'form${formNum}_';
|
||||
const name = ${JSON.stringify(btn.fieldName)};
|
||||
const el = document.querySelector('[id="' + p + name + '"], [id="' + p + name + '_i0"]');
|
||||
return el ? el.id : null;
|
||||
})()`);
|
||||
if (inputId) {
|
||||
await page.click(`[id="${inputId}"]`);
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('Shift+F4');
|
||||
await page.waitForTimeout(300);
|
||||
await page.keyboard.press('Tab');
|
||||
await waitForStable();
|
||||
}
|
||||
if (highlightMode) try { await unhighlight(); } catch {}
|
||||
const formData = await getFormState();
|
||||
return { ...formData, selected: { field: fieldName, search: null, method: 'clear' } };
|
||||
}
|
||||
|
||||
// === COMPOSITE TYPE HANDLING ===
|
||||
// When `type` is specified, clear the field first to reset cached type,
|
||||
// then open type selection dialog, pick the type, then pick the value.
|
||||
@@ -2781,7 +2815,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
|
||||
// Skip if cell already contains the desired value (single-field optimization)
|
||||
const firstKey0 = Object.keys(fields)[0];
|
||||
const firstVal0 = typeof fields[firstKey0] === 'object' ? fields[firstKey0].value : String(fields[firstKey0]);
|
||||
const rawFirstVal = fields[firstKey0];
|
||||
const firstVal0 = rawFirstVal === null || rawFirstVal === undefined || rawFirstVal === ''
|
||||
? '' : (typeof rawFirstVal === 'object' ? rawFirstVal.value : String(rawFirstVal));
|
||||
let firstFieldSkipped = false;
|
||||
if (cellCoords.currentText && firstVal0 &&
|
||||
cellCoords.currentText.toLowerCase().includes(firstVal0.toLowerCase())) {
|
||||
@@ -2795,6 +2831,57 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
// Then escalate: dblclick → F4 if needed.
|
||||
await page.mouse.click(cellCoords.x, cellCoords.y);
|
||||
|
||||
// Clear cell via Shift+F4 if value is empty
|
||||
if (firstVal0 === '') {
|
||||
await page.waitForTimeout(500);
|
||||
// Check if click opened a selection form — close it first
|
||||
let openedForm = await page.evaluate(`(() => {
|
||||
const forms = {};
|
||||
document.querySelectorAll('[id]').forEach(el => {
|
||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) forms[m[1]] = true;
|
||||
});
|
||||
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
||||
return nums.length > 0 ? Math.max(...nums) : null;
|
||||
})()`);
|
||||
if (openedForm !== null) {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(500);
|
||||
} else {
|
||||
// No form opened — need to enter edit mode first (dblclick), then close any form that opens
|
||||
await page.mouse.dblclick(cellCoords.x, cellCoords.y);
|
||||
await page.waitForTimeout(500);
|
||||
openedForm = await page.evaluate(`(() => {
|
||||
const forms = {};
|
||||
document.querySelectorAll('[id]').forEach(el => {
|
||||
if (el.offsetWidth === 0 && el.offsetHeight === 0) return;
|
||||
const m = el.id.match(/^form(\\d+)_/);
|
||||
if (m) forms[m[1]] = true;
|
||||
});
|
||||
const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum});
|
||||
return nums.length > 0 ? Math.max(...nums) : null;
|
||||
})()`);
|
||||
if (openedForm !== null) {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
await page.keyboard.press('Shift+F4');
|
||||
await page.waitForTimeout(300);
|
||||
const results = [{ field: firstKey0, ok: true, method: 'clear', value: '' }];
|
||||
// If more fields remain, process them on the same row
|
||||
const remaining = { ...fields };
|
||||
delete remaining[firstKey0];
|
||||
if (Object.keys(remaining).length > 0) {
|
||||
const more = await fillTableRow(remaining, { row, table });
|
||||
if (Array.isArray(more)) results.push(...more);
|
||||
else if (more?.filled) results.push(...more.filled);
|
||||
}
|
||||
const formData = await getFormState();
|
||||
return { filled: results, form: formData };
|
||||
}
|
||||
|
||||
// Check if clicked cell is a checkbox (toggle-on-click, no edit mode)
|
||||
const checkboxInfo = await page.evaluate(`(() => {
|
||||
const el = document.elementFromPoint(${cellCoords.x}, ${cellCoords.y});
|
||||
@@ -3154,8 +3241,14 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
// 4. Prepare pending fields for fuzzy matching
|
||||
const pending = new Map();
|
||||
for (const [key, val] of Object.entries(fields)) {
|
||||
if (val && typeof val === 'object' && 'value' in val) {
|
||||
pending.set(key, { value: String(val.value), type: val.type || null, filled: false });
|
||||
if (val === null || val === undefined || val === '') {
|
||||
pending.set(key, { value: '', type: null, filled: false });
|
||||
} else if (val && typeof val === 'object' && 'value' in val) {
|
||||
const innerVal = val.value;
|
||||
pending.set(key, {
|
||||
value: innerVal === null || innerVal === undefined || innerVal === '' ? '' : String(innerVal),
|
||||
type: val.type || null, filled: false
|
||||
});
|
||||
} else {
|
||||
pending.set(key, { value: String(val), type: null, filled: false });
|
||||
}
|
||||
@@ -3268,6 +3361,18 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
const info = pending.get(matchedKey);
|
||||
const text = info.value;
|
||||
|
||||
// Clear cell if value is empty (Shift+F4 = native 1C clear)
|
||||
if (text === '') {
|
||||
await page.keyboard.press('Shift+F4');
|
||||
await page.waitForTimeout(300);
|
||||
info.filled = true;
|
||||
results.push({ field: matchedKey, cell: cell.fullName, ok: true, method: 'clear', value: '' });
|
||||
if ([...pending.values()].every(p => p.filled)) break;
|
||||
await page.keyboard.press('Tab');
|
||||
await page.waitForTimeout(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If user specified a type, always clear and use type selection flow
|
||||
if (info.type) {
|
||||
await page.keyboard.press('Shift+F4'); // Clear cell to reset any inherited type
|
||||
|
||||
@@ -243,9 +243,9 @@ await closeForm({ save: false });
|
||||
| Функция | Описание | Возвращает |
|
||||
|---------|----------|------------|
|
||||
| `clickElement(text, {dblclick?})` | Клик по кнопке/ссылке/строке. `{dblclick: true}` для открытия из списка | form state или `{ submenu }` |
|
||||
| `fillFields({name: value})` | Заполнить поля (текст, чекбокс, радио, ссылки, DCS-фильтры) | `{ filled: [{field, ok, method}], form }` |
|
||||
| `selectValue(field, search, opts?)` | Выбрать из справочника. search: текст или `{поле: значение}`. `{ type }` для составного типа | form state с `selected` |
|
||||
| `fillTableRow(fields, {tab?, add?, row?})` | Заполнить строку. Значение: строка или `{ value, type }` для составного типа | form state |
|
||||
| `fillFields({name: value})` | Заполнить поля (текст, чекбокс, радио, ссылки, DCS-фильтры). Пустое значение (`''`/`null`) = очистка | `{ filled: [{field, ok, method}], form }` |
|
||||
| `selectValue(field, search, opts?)` | Выбрать из справочника. search: текст, `{поле: значение}` или `''`/`null` для очистки. `{ type }` для составного типа | form state с `selected` |
|
||||
| `fillTableRow(fields, {tab?, add?, row?})` | Заполнить строку. Значение: строка, `{ value, type }` для составного типа, `''`/`null` для очистки | form state |
|
||||
| `deleteTableRow(row, {tab?})` | Удалить строку по индексу | form state |
|
||||
| `closeForm({save?})` | Закрыть форму. `save: false` = "Нет", `save: true` = "Да". Возвращает `closed: true/false` | form state с `closed` |
|
||||
| `filterList(text, {field?, exact?})` | Фильтр списка. Без field = все колонки, с field = расширенный поиск | form state |
|
||||
@@ -260,6 +260,7 @@ await closeForm({ save: false });
|
||||
| `'true'` / `'да'` | Чекбокс | toggle |
|
||||
| `'Оплата поставщику'` | Радио | fuzzy match по меткам |
|
||||
| `'Склад бытовой техники'` (DCS) | Фильтр отчёта | авто-включение чекбокса + заполнение |
|
||||
| `''` / `null` | Любое (кроме чекбокс/радио) | очистка через Shift+F4 |
|
||||
|
||||
### Утилиты
|
||||
|
||||
@@ -291,7 +292,7 @@ await closeForm({ save: false });
|
||||
| Клавиша | Контекст | Действие |
|
||||
|---------|----------|----------|
|
||||
| `F8` | Ссылочное поле | Создать новый элемент |
|
||||
| `Shift+F4` | Ссылочное поле | Очистить значение |
|
||||
| `Shift+F4` | Любое поле | Очистить значение (автоматизировано: `fillFields({ поле: '' })`) |
|
||||
| `F4` | Ссылочное поле | Форма выбора |
|
||||
| `Alt+F` | Список/таблица | Расширенный поиск |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user