mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-15 02:14:57 +03:00
feat(web-test): exact-match при выборе типа в pickFromTypeDialog
Диалог выбора типа матчил по подстроке и падал «multiple types match», даже когда точное совпадение присутствовало в выдаче (напр. поиск «Контрагент» давал «Банковская карта контрагента», «Договор с контрагентом», …, «Контрагент» — и движок ругался, хотя точная строка была видна). pickFromTypeDialog теперь предпочитает точное совпадение (resolveExact: единственный матч, либо единственная строка, равная искомому имени после нормализации регистра/ё) — кликает именно её и жмёт OK. Применяется и в scan-пути (мелкие списки), и после Ctrl+F (большие виртуальные списки). Добавлен ограниченный скролл-скан (PageDown ×3) на случай, когда точная строка чуть ниже первого окна. Ошибка неоднозначности остаётся, только если единственного точного совпадения действительно нет. Стенд: в СписокТипов добавлен подстрочный дубль «Дата документа» рядом с «Дата» для детерминированной проверки exact-match. Тест 16-tree-form покрывает scan-путь (выбирается точное «Дата»). Проверено: регресс web-test 22/22, живой E2E на типовой Консоли запросов (ссылочный тип через Ctrl+F + примитив без регресса). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// web-test forms/select-value v1.21 — Reference & composite-type value selection: selectValue, fillReferenceField, selection/type-dialog pickers.
|
||||
// web-test forms/select-value v1.22 — Reference & composite-type value selection: selectValue, fillReferenceField, selection/type-dialog pickers.
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import {
|
||||
@@ -279,33 +279,44 @@ export async function pickFromTypeDialog(formNum, typeName) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: Scan visible rows (fast path — no Ctrl+F needed for small lists)
|
||||
const scan = await readVisibleRows();
|
||||
|
||||
if (scan.matches.length === 1) {
|
||||
// Single match — click to select, then OK
|
||||
await page.mouse.click(scan.matches[0].x, scan.matches[0].y);
|
||||
// Exact-match preference: substring search can surface several types that merely CONTAIN the
|
||||
// requested name (e.g. "Контрагент" → "Банковская карта контрагента", "Договор с контрагентом",
|
||||
// …, "Контрагент"). Prefer the row equal to the requested name; only the absence of a single
|
||||
// exact match among multiple substring hits is a genuine ambiguity.
|
||||
function resolveExact(matches) {
|
||||
if (!matches || matches.length === 0) return null;
|
||||
if (matches.length === 1) return matches[0];
|
||||
const exact = matches.filter(m => normYo((m.text || '').toLowerCase()) === typeNorm);
|
||||
return exact.length === 1 ? exact[0] : null;
|
||||
}
|
||||
async function selectRowAndOk(row) {
|
||||
await page.mouse.click(row.x, row.y);
|
||||
await page.waitForTimeout(200);
|
||||
await page.click(`#form${formNum}_OK`, { force: true });
|
||||
await page.waitForTimeout(ACTION_WAIT);
|
||||
return;
|
||||
}
|
||||
// Focus the grid via evaluate (does NOT punch through the modal overlay like page.click).
|
||||
async function focusGrid() {
|
||||
await page.evaluate(`(() => {
|
||||
const grid = document.getElementById('form${formNum}_ValueList');
|
||||
if (!grid) return;
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (body) body.focus(); else grid.focus();
|
||||
})()`);
|
||||
}
|
||||
|
||||
// Step 1: Scan visible rows (fast path — no Ctrl+F needed for small lists)
|
||||
const scan = await readVisibleRows();
|
||||
const scanPick = resolveExact(scan.matches);
|
||||
if (scanPick) { await selectRowAndOk(scanPick); return; }
|
||||
if (scan.matches.length > 1) {
|
||||
await dismissTypeDialog();
|
||||
await waitForStable();
|
||||
throw new Error(`selectValue: multiple types match "${typeName}": ${scan.matches.map(m => '"' + m.text + '"').join(', ')}. Specify a more precise type name`);
|
||||
}
|
||||
|
||||
// Step 2: Not found in visible rows — use Ctrl+F (virtual grid may have more items)
|
||||
|
||||
// Focus the grid via evaluate (does NOT punch through modal like page.click)
|
||||
await page.evaluate(`(() => {
|
||||
const grid = document.getElementById('form${formNum}_ValueList');
|
||||
if (!grid) return;
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (body) body.focus(); else grid.focus();
|
||||
})()`);
|
||||
// Step 2: Not in visible rows — Ctrl+F jumps near the match in the large virtual list.
|
||||
await focusGrid();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Ctrl+F to open "Найти" dialog
|
||||
@@ -326,29 +337,40 @@ export async function pickFromTypeDialog(formNum, typeName) {
|
||||
throw new Error('selectValue: Ctrl+F did not open "Найти" dialog in type selection');
|
||||
}
|
||||
|
||||
// Click "Найти" — search is client-side (no server round-trip), 500ms is enough
|
||||
// Click "Найти" — search is client-side (no server round-trip)
|
||||
await page.click(`#form${findFormNum}_Find`, { force: true });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Re-read visible rows after search scrolled to match
|
||||
const afterSearch = await readVisibleRows();
|
||||
// "Найти" positions at the first match; the exact row is at or just below it. Read, and if the
|
||||
// exact match is not yet in view, PageDown a few times (bounded) — virtualised grid, scrollTop
|
||||
// stays 0 but the visible window changes. Poll each window for matches to settle.
|
||||
let resolved = null, lastMatches = [], sawMatches = false;
|
||||
for (let pageStep = 0; pageStep <= 3; pageStep++) {
|
||||
if (pageStep > 0) { await focusGrid(); await page.keyboard.press('PageDown'); }
|
||||
let v = null;
|
||||
for (let w = 0; w < 5; w++) {
|
||||
await page.waitForTimeout(200);
|
||||
v = await readVisibleRows();
|
||||
if (v.matches.length) break;
|
||||
}
|
||||
if (v && v.matches.length) {
|
||||
sawMatches = true;
|
||||
lastMatches = v.matches;
|
||||
resolved = resolveExact(v.matches);
|
||||
if (resolved) break;
|
||||
// matches present but no single exact in this window — scroll to look just below
|
||||
} else if (sawMatches) {
|
||||
break; // scrolled past the matches without finding an exact one
|
||||
}
|
||||
}
|
||||
if (resolved) { await selectRowAndOk(resolved); return; }
|
||||
|
||||
if (afterSearch.matches.length === 0) {
|
||||
await dismissTypeDialog();
|
||||
await waitForStable();
|
||||
await dismissTypeDialog();
|
||||
await waitForStable();
|
||||
if (!sawMatches) {
|
||||
throw new Error(`selectValue: type "${typeName}" not found in type selection dialog` +
|
||||
`. Visible: ${(scan.visible || []).join(', ')}`);
|
||||
}
|
||||
|
||||
if (afterSearch.matches.length > 1) {
|
||||
await dismissTypeDialog();
|
||||
await waitForStable();
|
||||
throw new Error(`selectValue: multiple types match "${typeName}": ${afterSearch.matches.map(m => '"' + m.text + '"').join(', ')}. Specify a more precise type name`);
|
||||
}
|
||||
|
||||
// Click OK on type dialog via page.click({force:true}) — bypasses "Найти" modal
|
||||
await page.click(`#form${formNum}_OK`, { force: true });
|
||||
await page.waitForTimeout(ACTION_WAIT);
|
||||
throw new Error(`selectValue: multiple types match "${typeName}": ${lastMatches.map(m => '"' + m.text + '"').join(', ')}. Specify a more precise type name`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -857,6 +857,9 @@ export const steps = [
|
||||
\tСписокТипов.Добавить("Число");
|
||||
\tСписокТипов.Добавить("Дата");
|
||||
\tСписокТипов.Добавить("Булево");
|
||||
\t// Подстрочный дубль «Дата» — для проверки exact-match в pickFromTypeDialog:
|
||||
\t// поиск «Дата» даёт 2 совпадения, движок должен выбрать точное «Дата», не «Дата документа».
|
||||
\tСписокТипов.Добавить("Дата документа");
|
||||
КонецПроцедуры
|
||||
|
||||
&НаСервере
|
||||
|
||||
@@ -95,6 +95,20 @@ export default async function({ navigateLink, clickElement, closeForm, readTable
|
||||
assert.equal(tovar01['Тип значения'], 'Число', 'ТипЗначения = Число');
|
||||
});
|
||||
|
||||
await step('choice-exact: при подстрочной неоднозначности выбирается точное совпадение', async () => {
|
||||
// СписокТипов содержит «Дата» и «Дата документа». Поиск «Дата» даёт 2 подстрочных
|
||||
// совпадения — pickFromTypeDialog должен предпочесть ТОЧНОЕ «Дата», а не ругаться
|
||||
// на неоднозначность и не выбрать «Дата документа» (Проблема 2 из bug-report).
|
||||
const r = await fillTableRow({ ТипЗначения: 'Дата' }, { row: 1 });
|
||||
const cell = r.filled?.find(f => f.field === 'ТипЗначения');
|
||||
assert.ok(cell, 'поле ТипЗначения в результате');
|
||||
assert.equal(cell.ok, true, 'ok=true (exact-match разрешил неоднозначность)');
|
||||
assert.equal(cell.method, 'choice', 'method=choice');
|
||||
const t = await readTable('Дерево');
|
||||
const tovar01 = t.rows.find(row => row['Номенклатура'] === 'Товар 01');
|
||||
assert.equal(tovar01['Тип значения'], 'Дата', 'выбрано точное «Дата», не «Дата документа»');
|
||||
});
|
||||
|
||||
await step('choice-cell-negative: несуществующий тип → ok:false/not_found (форма не закрывается)', async () => {
|
||||
// not_found гасит только диалог выбора типа (умный dismiss), исходная форма остаётся —
|
||||
// следующие шаги (picture) это подтверждают.
|
||||
|
||||
Reference in New Issue
Block a user