agentleFS
anymasoftanymasoft/1c-ai/.claude/skills/web-test/SKILL.md

web-test

Skill0 starsChanged 4 months agoMITView on GitHub

Этот скилл MUST быть вызван когда пользователь просит проверить или протестировать 1С через браузер в интерактивном режиме (быстрые ручные проверки). SHOULD также вызывать для одноразовой верификации поведения веб-интерфейса 1С. Do NOT использовать для создания файлов автотестов — используй playwright-test; для сложных сценариев — используй 1c-web-session.

  • Installs packages

3 other projects use a version of this, 1 of them edited.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

12 rules. About 0 are common, 6 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read .v8-project.json from project root.

    line 48
  2. Very specific:

    Use /web-publish first if the database is not published.

    line 50
  3. Very specific:

    All browser.mjs exports are globals — no import needed.

    line 83
  4. Very specific:

    Read actual grid data with pagination.

    line 140
  5. Distinctive:

    Read report output (SpreadsheetDocument) after clicking "Сформировать".

    line 164
  6. Distinctive:

    Read section panel and commands without navigating.

    line 179
  7. Distinctive:

    DCS report filters: use human-readable label names.

    line 214
  8. Distinctive:

    Delete row by 0-based index.

    line 256
  9. Very specific:

    If .v8-project.json has ffmpegPath, pass it to startRecording({ ffmpegPath }).

    line 296
  10. Distinctive:

    Max 2 attempts per operation: If an action fails twice with the same approach — stop and report to the user

    line 371
  11. Very specific:

    Not found = not found: If filterList returns 0 rows or readTable is empty after filtering — the item likely doesn't exist in this database.

    line 372
  12. Distinctive:

    Report partial results: If you found the list but not the specific item — say so.

    line 374

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/web-test — Browser automation for 1C web client

Automates user interactions with 1C:Enterprise web client via Playwright — navigating sections, filling forms, reading tables and reports, filtering lists.

Quick start

RUN=".claude/skills/web-test/scripts/run.mjs"

# One-shot: opens browser → runs script → closes browser → exits
node $RUN run http://localhost:8081/bpdemo test-scenario.js

# Or pipe inline:
cat <<'SCRIPT' | node $RUN run http://localhost:8081/bpdemo -
await navigateSection('Продажи');
await openCommand('Заказы клиентов');
await clickElement('Создать');
await fillFields({ 'Клиент': 'Альфа' });
await clickElement('Провести и закрыть');
SCRIPT

Setup (first time)

cd .claude/skills/web-test/scripts && npm install

Requires Node.js 18+. npm install downloads Playwright and Chromium.

URL resolution

Read .v8-project.json from project root. Each database has id and optional webUrl. Construct URL as http://localhost:8081/<id> or use webUrl if set. Use /web-publish first if the database is not published.

Execution modes

Autonomous mode (preferred for complete scenarios)

node $RUN run <url> script.js   # exits when done, no session

Interactive mode (step-by-step development)

# 1. Start session (run_in_background=true, prints JSON when ready)
node $RUN start <url>

# 2. Execute scripts against running session
cat <<'SCRIPT' | node $RUN exec -
const form = await getFormState();
console.log(JSON.stringify(form, null, 2));
SCRIPT

# 3. Screenshot
node $RUN shot result.png

# 4. Stop (logout + close)
node $RUN stop

start runs an HTTP server in background. Use exec/shot/stop from other shells.

Writing exec scripts

All browser.mjs exports are globals — no import needed. console.log() output is captured in the JSON response. writeFileSync / readFileSync also available.

API reference

Navigation

navigateSection(name) → { navigated, sections, commands }

Go to a top-level section (fuzzy match). Returns list of commands in that section.

await navigateSection('Продажи');
// { navigated: 'Продажи', sections: [...], commands: ['Заказы клиентов', ...] }

openCommand(name) → form state

Open a command from the function panel (fuzzy). Returns form state of the opened form.

const form = await openCommand('Заказы клиентов');

navigateLink(url) → form state

Open any 1C object by metadata path (Shift+F11 dialog). Bypasses section/command navigation.

await navigateLink('Документ.ЗаказКлиента');
await navigateLink('РегистрНакопления.ЗаказыКлиентов');
await navigateLink('Справочник.Контрагенты');

switchTab(name) → form state

Switch to an already-open tab/window (fuzzy match).

Reading form state

getFormState() → { fields, buttons, tabs, table, filters, reportSettings? }

Returns current form structure. This is the primary way to understand what's on screen.

fields — each field has: name, value, label?, actions? (select, clear, open), required? (true for unfilled mandatory fields)

table — summary only: { name, columns, rowCount }. Use readTable() for actual data.

reportSettings — for DCS reports: human-readable filter settings instead of raw technical names:

const form = await getFormState();
// form.reportSettings = [
//   { name: "Склад", enabled: true, value: "Склад бытовой техники", actions: ["select"] },
//   { name: "Номенклатура", enabled: false, value: "" }
// ]

errorModal — if present, 1C showed an error dialog. Read the message and decide how to proceed.

confirmation — if present, a Yes/No dialog is shown. Call clickElement('Да') or clickElement('Нет').

Reading data

readTable({ maxRows?, offset? }) → { columns, rows, total, shown, offset }

Read actual grid data with pagination. Each row is { columnName: value }.

OptionDefaultDescription
maxRows20Max rows to return per call
offset0Skip first N rows

Special row fields:

  • _kind: 'group' — hierarchical group row
  • _kind: 'parent' — parent row in hierarchy
  • _tree: 'expanded'|'collapsed' — tree node state
  • _level: N — nesting depth in tree view
  • hierarchical: true — list has groups (on result object)
  • viewMode: 'tree' — tree view active (on result object)
const t = await readTable({ maxRows: 50 });
console.log('Columns:', t.columns);
console.log('Rows:', t.rows.length, 'of', t.total);
// Pagination:
const page2 = await readTable({ maxRows: 50, offset: 50 });

readSpreadsheet() → { title?, headers?, data?, totals?, rows?, total }

Read report output (SpreadsheetDocument) after clicking "Сформировать".

Returns structured data when header row is detected:

await clickElement('Сформировать');
await wait(5);
const report = await readSpreadsheet();
// { title: "Остатки товаров", headers: ["Номенклатура", "Склад", "Количество"],
//   data: [{ "Номенклатура": "Бумага", "Склад": "Основной", "Количество": "150" }, ...],
//   totals: { "Количество": "1250" }, total: 42 }

Falls back to { rows: string[][], total } when headers can't be detected.

getSections() → { activeSection, sections, commands }

Read section panel and commands without navigating.

getCommands() → string[]

Commands of the current section.

getPageState() → { activeSection, activeTab, sections, tabs }

Sections + all open tabs.

Actions

clickElement(text, { dblclick? }) → form state

Click button, hyperlink, tab, or grid row (fuzzy match).

  • Single click selects a row in a list. Double-click opens the item:
    await clickElement('0000-000227', { dblclick: true }); // opens document
  • Returns submenu[] when a menu opens — click again with item name:
    const r = await clickElement('Ещё');
    // r.submenu = ['Расширенный поиск', 'Настройки', ...]
    await clickElement('Расширенный поиск');
  • Handles tree nodes: clicking a tree icon expands/collapses.

fillFields({ name: value }) → { filled, form }

Fill form fields by label (fuzzy match). Auto-detects field type.

ValueField typeMethod
'Конфетпром'ReferenceClipboard paste + typeahead
'5000'Plain textClipboard paste
'true' / 'да'CheckboxToggle
'Оплата поставщику'RadioFuzzy label match

DCS report filters: use human-readable label names. Checkbox is auto-enabled:

await fillFields({
  'Склад': 'Склад бытовой техники',   // auto-enables "Склад" checkbox + fills value
  'Номенклатура': 'Вентилятор'          // same: enables checkbox + fills
});

Returns { filled: [{ field, ok, value, method }], form: {...} }. Method is one of: 'toggle' | 'radio' | 'paste' | 'dropdown' | 'form' | 'typeahead'

selectValue(field, search) → 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.

await selectValue('Организация', 'Конфетпром');
// result.selected = { field: 'Организация', search: 'Конфетпром', method: 'dropdown'|'form' }

Also supports DCS labels — auto-enables the paired checkbox.

fillTableRow(fields, opts) → form state

Fill table row cells via Tab navigation.

// Add new row:
await fillTableRow(
  { 'Номенклатура': 'Бумага', 'Количество': '10', 'Цена': '100' },
  { tab: 'Товары', add: true }
);
// Edit existing row:
await fillTableRow(
  { 'Количество': '20' },
  { tab: 'Товары', row: 0 }
);
  • Tab-based sequential navigation — field order set by 1C form config
  • Fuzzy cell match: "Количество" matches "ТоварыКоличество"
  • Reference cells auto-detected by autocomplete popup

deleteTableRow(row, { tab? }) → form state

Delete row by 0-based index.

closeForm({ save? }) → form state

Close the current form via Escape.

ArgumentBehavior
{ save: false }Auto-clicks "Нет" on confirmation
{ save: true }Auto-clicks "Да" on confirmation
{} (omitted)Returns confirmation field if dialog appears

Preferred over clickElement('×') — close buttons on tabs are ambiguous.

filterList(text, opts?) → form state

Filter list. Simple mode searches all columns, advanced mode targets a specific field.

await filterList('КП00-000018');                          // simple — all columns
await filterList('Мишка', { field: 'Наименование' });     // advanced — specific column
await filterList('Мишка', { field: 'Наименование', exact: true }); // exact match

Works on hierarchical catalogs too (flattens the view).

unfilterList({ field? }) → form state

Clear filters. Without arguments clears all, with { field } clears specific badge.

Utility

screenshot() → PNG Buffer

wait(seconds) → form state

getPage() → Playwright Page (raw, for advanced scripting)

startRecording(path, opts?) / stopRecording() → MP4 video recording

showCaption(text, opts?) / hideCaption() → text overlay on page

showTitleSlide(text, opts?) / hideTitleSlide() → full-screen title card (intro/outro)

isRecording() → boolean

setHighlight(on) / isHighlightMode() → auto-highlight mode for video

highlight(text) / unhighlight() → manual element highlighting

See recording.md for setup (ffmpeg), highlight mode, API details, and examples. If .v8-project.json has ffmpegPath, pass it to startRecording({ ffmpegPath }).

Common patterns

Create and save a document

await navigateSection('Продажи');
await openCommand('Заказы клиентов');
await clickElement('Создать');
await fillFields({ 'Организация': 'Конфетпром', 'Контрагент': 'Альфа' });
await fillTableRow({ 'Номенклатура': 'Бумага', 'Количество': '10' }, { tab: 'Товары', add: true });
await clickElement('Провести и закрыть');

Open item from list

await clickElement('КП00-000227', { dblclick: true });
// Always use { dblclick: true } — single click only selects the row

Work with hierarchical lists

await filterList('Конфетпром');                               // flatten + search
await clickElement('Конфетпром ООО', { dblclick: true });     // open
await closeForm();
await unfilterList();                                          // restore hierarchy

Generate and read a report

// Fill report filters using readable labels
await fillFields({ 'Склад': 'Основной склад' });
await clickElement('Сформировать');
await wait(5);
const report = await readSpreadsheet();
console.log('Title:', report.title);
console.log('Data rows:', report.data?.length);

Keyboard shortcuts (via page.keyboard.press)

KeyContextAction
F8Reference field focusedCreate new catalog item
Shift+F4Reference field focusedClear field value
F4Reference field focusedOpen selection form
Alt+FList/table formOpen advanced search dialog

Closing forms — which method to use

GoalMethod
Post & close documentclickElement('Провести и закрыть')
Save & close catalogclickElement('Записать и закрыть')
Close without savingcloseForm({ save: false })
Close and savecloseForm({ save: true })
Close (manual confirm)closeForm() — returns confirmation if dialog appears

Exec response format

{ "ok": true, "output": "...console.log output...", "elapsed": 3.2 }

On error (auto-screenshot taken):

{ "ok": false, "error": "Element not found", "output": "...", "screenshot": "error-shot.png", "elapsed": 1.5 }

Avoiding loops

  • Max 2 attempts per operation. If an action fails twice with the same approach — stop and report to the user
  • Not found = not found. If filterList returns 0 rows or readTable is empty after filtering — the item likely doesn't exist in this database. Don't retry the same search 5 times with slight variations
  • Try a different approach, not the same one. Couldn't find via section navigation? Try navigateLink. Couldn't find via simple search? Try advanced search with a specific field. But don't repeat the same method
  • Report partial results. If you found the list but not the specific item — say so. Show what IS available instead of silently retrying

Important notes

  • Headed mode — 1C requires visible browser, no headless
  • Startup time — 1C loads 30-60s on initial connect (built into start)
  • Fuzzy matching — all name lookups: exact > startsWith > includes
  • Clipboard paste — all text fields filled via Ctrl+V (triggers 1C events properly)
  • Cyrillic in bash — use cat <<'SCRIPT' | node $RUN exec - to avoid escaping issues
  • Non-breaking spaces — 1C uses \u00a0 instead of regular spaces. All matching is normalized internally

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.