mcpField
W
web-test

web-test

github.com/nikolay-shirokovテスト・品質74158 回インストール今日更新
リポジトリ

web-test について

web-testは、1C:EnterpriseのWebクライアントを操作するブラウザー自動化スキルです。Playwrightを使い、単発スクリプトと対話セッションの両方でフォーム状態や表データを扱えます。

主な機能

  1. 01Playwrightによるブラウザー操作
  2. 02セクション移動とコマンド起動
  3. 03フォーム、レポート、表の読み取り
  4. 04フィールド入力とリストの絞り込み
  5. 05単発実行と対話セッション

活用シーン

  1. 011C Webクライアントのテストシナリオ実行
  2. 02業務表やレポートデータの読み取り
  3. 03フォーム操作スクリプトの段階的なデバッグ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nikolay-shirokov/cc-1c-skills --skill web-test

インストール後、Claude Code で/web-testを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.gitignore
recording.md
regress.md
scripts/
browser.mjs
dom.mjs
scripts/cli/
exec-context.mjs
server.mjs
session.mjs
util.mjs
scripts/cli/commands/
exec.mjs
run.mjs
shot.mjs
start.mjs
status.mjs
stop.mjs
test.mjs
scripts/cli/test-runner/
assertions.mjs
context-pool.mjs
discover.mjs
reporters.mjs
severity.mjs
suite-root.mjs
scripts/dom/
_shared.mjs
edd.mjs
edit-state.mjs
errors-stack.mjs
errors.mjs
filter.mjs
form-state.mjs
forms.mjs

詳細

/web-test — 1C Web クライアントのブラウザー自動化

Playwright を使用して、1C:Enterprise Web クライアントでのユーザー操作(セクション間の移動、フォームへの入力、テーブルやレポートの読み取り、リストのフィルタリング)を自動化します。

クイックスタート

RUN="${CLAUDE_SKILL_DIR}/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

セットアップ(初回のみ)

cd "${CLAUDE_SKILL_DIR}/scripts" && npm install && npx playwright install chromium

Node.js 18 以降が必要です。npm install で Playwright を取得し、npx playwright install chromium でブラウザーをダウンロードします。

URL の解決

プロジェクトのルートから .v8-project.json を読み込みます。各データベースには id があり、webUrl は任意です。 URL は http://localhost:8081/<id> の形式で作成するか、webUrl が設定されている場合はそれを使用します。 データベースが公開されていない場合は、先に /web-publish を使用します。

実行モード

自律モード(シナリオ全体の実行に推奨)
node $RUN run <url> script.js   # exits when done, no session
node $RUN run <url> script.js --timeout-min=10   # bound the script (default 30 min)

制限時間の対象はスクリプトであり、1C の起動時間は含まれません。--timeout=<ms>、--timeout-min=<min>、または WEB_TEST_EXEC_TIMEOUT_MS を指定します。run の終了コードは、0 — 成功、1 — スクリプトエラー、2 — タイムアウトです。

対話モード(段階的な開発)
# 1. Start session in the background — `start` stays running as the server, so don't wait on
#    its stdout. Poll `status` instead: it exits 0 only once the session is loaded and live.
node $RUN start <url>            # run_in_background=true
until node $RUN status >/dev/null 2>&1; do sleep 2; done   # exit 0 = ready

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

# 2b. Execute without video recording (for debugging/testing)
cat script.js | node $RUN exec - --no-record

# 2c. Override exec HTTP timeout (default 30 min). Use for long scripts
#     such as multi-block recordings + addNarration.
cat script.js | node $RUN exec - --timeout-min=120
cat script.js | node $RUN exec - --timeout=7200000
WEB_TEST_EXEC_TIMEOUT_MS=7200000 node $RUN exec script.js

# 3. Screenshot
node $RUN shot result.png

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

start はバックグラウンドで HTTP サーバーを起動します。他のシェルから exec/shot/stop を使用します。

exec スクリプトの作成

browser.mjs のすべてのエクスポートはグローバルとして利用できるため、import は不要です。 console.log() の出力は JSON レスポンスに取り込まれます。 writeFileSync/readFileSync も利用できます。

API リファレンス

ナビゲーション
navigateSection(name) → { navigated, sections, commands }

トップレベルのセクションに移動します(あいまい検索)。そのセクション内のコマンド一覧を返します。

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

機能パネルからコマンドを開きます(あいまい検索)。開いたフォームの状態を返します。

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

メタデータパスで任意の 1C オブジェクトを開きます(Shift+F11 ダイアログ)。セクションやコマンドのナビゲーションを経由しません。

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

「ファイル」→「開く」から外部データプロセッサまたはレポート(EPF/ERF)を開きます。セキュリティ確認ダイアログは自動的に処理されます。

const form = await openFile('C:\\WS\\build\\МояОбработка.epf');
const form = await openFile('build/МояОбработка.epf'); // relative paths work too
switchTab(name) → form state

すでに開いているタブ/ウィンドウに切り替えます(あいまい検索)。

フォームの状態を読み取る
getFormState() → { form, formCount, openForms, title, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }

現在のフォーム構造を返します。画面上の状態を把握するための主要な手段です。

form — アクティブなフォーム番号。フォームが開いていない場合(デスクトップ)は null です。

formCount — 開いているフォームの数。ウィンドウがいくつ重なっているかを確認するために使用します。0 はデスクトップを意味します。

openForms — 開いているすべてのフォーム番号の配列(例:[0, 1])。1C の設定で開いているウィンドウのタブバーが非表示になっている場合でも機能します。

modal — アクティブなフォームが UI の操作をブロックするモーダルダイアログの場合は true です。モーダルがアクティブな場合にのみ含まれます。

openTabs — 開いているウィンドウのタブバーにある { name, active? } の配列。1C の設定でタブバーが有効になっている場合にのみ含まれます。これに依存しないでください。代わりに formCount/openForms を使用します。

title — アクティブなフォームのキャプション(「取引先」、「仕入先への注文 TD00-000052(05.07.2022)」など)。開いているウィンドウのタブバーに依存しない、フォーム独自のヘッダーから読み取ります。フォームにヘッダーがない場合は、アクティブなタブのキャプションを使用します。どちらも利用できない場合は null です。

fields — 各フィールドには、name、value、label?、actions?(選択、クリア、表示)、required?(未入力の必須フィールドの場合は true)、disabled?(コントロールが利用不可の場合)が含まれます。buttons[] にも disabled? が含まれます。

navigation — フォームのナビゲーションパネルのリンク(下位カタログを持つオブジェクトの場合):[{ name, active? }]。clickElement() でクリックできます。フォームにナビゲーションパネルがある場合にのみ含まれます(例:「メイン」、「メタデータオブジェクト」、「サブシステム」)。 groups — 折りたたみ式グループとポップアップグループ:[{ name, title, collapsed, behavior? }]。collapsed: true はグループの内容が非表示であることを意味します。展開するまでフォームの一部は表示されません(「Администрирование → Интернет-поддержка и сервисы」など、設定ページでよく見られます)。behavior: 'popup' はポップアップグループを示します(内容はフローティングパネルに表示されます)。通常の折りたたみ式グループでは指定されません。ツリーのノードと同じ語彙を使い、グループのタイトルを clickElement でクリックして展開/折りたたみ(またはポップアップを開く/閉じる)します。{ expand: true } は表示します(繰り返し実行しても結果は変わりません)。{ expand: false } は非表示にし、{ toggle: true } は切り替えます。展開後、次の getFormState() ではグループの内容を読み取れるようになり、フィールド/ハイパーリンク/テキストが表示されます。通常の(折りたたみ式でない)グループは一覧に含まれません。 グループのタイトルは安定したキーではありません。同じキャプションがフォーム内の複数のブロックで繰り返し使われることがあり、展開後にグループのタイトルが切り替わる場合もあります(「Показать детализацию」↔「Скрыть детализацию」)。その場合、以前のキャプションを使ったクリックは「not found」エラーになります。そのため、クリック結果には group(技術名。変更されず、clickElement で使用できます)と title(現在のキャプション)が含まれます。同じグループを再度クリックする予定がある場合は、group を使用してください。

const form = await getFormState();
// form.groups = [{ name: "ГруппаНовости", title: "Новости", collapsed: true }, ...]
const r = await clickElement('Новости', { expand: true });   // reveal the group's content
// r.clicked = { kind: 'formGroup', name: 'Новости', group: 'ГруппаНовости', title: 'Новости', toggled: true }
await clickElement(r.clicked.group, { expand: false });      // stable key — safe to reuse

tables — 表示されているすべてのグリッドの配列:[{ name, columns, rowCount, label? }]。label は画面に表示される視覚的なグループタイトル(例:「Входящие」)で、グリッドに表示タイトルがない場合は含まれません。実際のデータを取得するには readTable() を使用してください。

table — 最初のグリッドを指す、後方互換性のあるエイリアス:{ present, columns, rowCount }。

htmlFields — 画面上のHTMLドキュメントフィールド:[{ name, label? }]。getHtmlFrame() で開きます。非アクティブなページ上、折りたたまれたグループ内、またはフォームで非表示になっているフィールドは、表示されるまで一覧に含まれません。

reportSettings — DCSレポート用:技術的な名前ではなく、人が読める形式のフィルター設定です。

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

errorModal — 存在する場合、1Cでエラーダイアログが表示されています。メッセージを読み、次の操作を判断してください。

confirmation — 存在する場合、「はい/いいえ」ダイアログが表示されています。clickElement('Да') または clickElement('Нет') を呼び出してください。

errors.stateText — SpreadsheetDocument の状態メッセージの配列です(例:"Не установлено значение параметра \"X\""、"Отчет не сформирован..."、"Изменились настройки...")。レポート領域にデータの代わりに情報バーが表示されている場合に含まれます。同じ情報バーには、一覧の検索中に "Поиск..." と表示されます。このバーが表示されている間は操作が完了しないため、フィルター処理された一覧から以前の行が返されることはありません。

getHtmlFrame(name?) → Playwright FrameLocator

アクティブなフォームのHTMLドキュメントフィールド(HTMLまたはReactアプリ)を、フィールド名または htmlFields のタイトルで開きます。フォームにHTMLフィールドが1つだけ表示されている場合は、name を省略できます。フレーム内のページはアプリケーション独自のものです。通常のPlaywrightとして操作してください。

const app = await getHtmlFrame('Чат');
await app.getByTestId('chat-message').filter({ visible: true }).fill('text');  // hidden duplicates
await app.getByRole('button', { name: 'Отправить' }).click();

名前が不明な場合、複数のフィールドが表示されていて名前が指定されていない場合、またはフィールドがまだ表示されていない場合は、フィールド一覧とともにエラーが発生します。まず該当ページを開くか、グループを展開してください。1Cサーバーを呼び出すRaw Playwright操作では、1Cのエラーは自動検出されません。該当する操作の後に getFormState().errors を確認してください。フォーム上にモーダルウィンドウが開いた後は、フレームを使い続けないでください。クリックがモーダルに対して実行されます。

データの読み取り
readTable({ maxRows?, offset?, table? }) → { columns, rows, total, shown, offset, hasMore }

ページネーションを使用して、実際のグリッドデータを読み取ります。各行は { columnName: value } です。

オプションデフォルト説明
maxRows201 回の呼び出しで返す最大行数
offset0先頭からスキップする行数
table—tables[] のグリッド名(複数グリッドのフォーム用)

画像列。 アイコン(ステータス/ステージのマーク、「ЭДО」のマーク、添付ファイルのクリップ)が表示されるセルは、表示されている場合は 'pic:<N>'(N = アイコンのフレーム/状態)、存在しない場合は '' として読み取られます。そのため、存在する場合は真となり、アイコンはインデックスによって異なります。アイコンのみの列(ヘッダーテキストなし)も表示され、ツールチップまたは '(picture)' を名前として使用します。これらの値は読み取り専用です。行のフィルタリング/選択には 'pic:N' ではなくテキスト列を使用してください。

if (t.rows[0]['Присоединенные файлы']) { /* has an attached file */ }
t.rows[0]['ЭДО'] === 'pic:1';   // connected to 1С-ЭДО ('pic:0' = not)

グループ化されたヘッダー。 グループのキャプションの下で結合された列は、そのキャプション付きで報告されます。例:'Цена / План'、'Цена / Факт'。キャプションのみの列はデータ列ではありません。このような名前は clickElement({row, column}) と fillTableRow でも使用できます。短い名前('Факт')も解決され、その場合は左端の一致項目が選択されます。

特別な行フィールド:

  • _kind: 'group' — 階層グループ行
  • _kind: 'parent' — 階層内の親行
  • _tree: 'expanded'|'collapsed' — ツリーノードの状態
  • _level: N — ツリービューでのネストの深さ
  • _selected: true — 行が選択されています(ハイライト表示)。複数選択を確認するには、clickElement({ modifier: 'ctrl'|'shift' }) と組み合わせて使用します
  • hierarchical: true — リストにグループがあります(結果オブジェクト上)
  • viewMode: 'tree' — ツリービューが有効です(結果オブジェクト上)

行の状態 — オブジェクトリストでは、行の状態アイコンからデコードされます(リストに列を追加する必要はありません):

  • _deleted: true|false — 削除対象としてマークされています(カタログ、ドキュメント、タスク、ビジネスプロセス、勘定科目表/計算タイプ)
  • _posted: true|false — ドキュメント
  • _predefined: true|false — カタログ、勘定科目表/計算タイプ
  • _completed: true|false — タスク
  • _started、_finished — ビジネスプロセス
  • _rowPic: '<icon>:<N>' — 生のアイコン ID(診断用)
const t = await readTable();
const doc = t.rows.find(r => r['Номер'] === 'ТД00-000005');
if (doc._deleted === true) { /* marked for deletion */ }

状態フィールドがない場合は「不明」を意味し、false ではありません。 そのプロパティが該当しない(ドキュメントには _predefined がない)か、アイコンを識別できない可能性があります。そのため、if (!row._deleted) は安全ではありません。「不明」を「削除されていない」として扱ってしまいます。明示的に比較(=== true / === false)し、undefined は第 3 の結果として扱ってください。オブジェクトリスト以外の行(フォームの表形式セクション、値リスト)には状態フィールドがありません。_rowPic が存在するのにブール値がない場合は、その値を報告してください。そのアイコンにはデコードのサポートが必要です。

長いリストでは total は誤解を招く値です。 1С は動的リストとフォームの表形式セクションの両方を仮想化するため、DOM には表示中の行の範囲しか保持されません。total / shown が数えるのは、基になるコレクションのサイズではなく、現在読み込まれている行です。表示範囲外にデータがあるかどうかは、hasMore を使用して確認してください。

const t = await readTable();
// t.hasMore = { above: false, below: true }   ← form tabular section, scrollbar visible
// t.hasMore = { below: true }                 ← dynamic list (catalog/journal/register)
// t.hasMore = { below: false }                ← everything visible / end of list reached
  • hasMore.below — 常に存在します。true の場合、下にスクロール(PageDown / clickElement に scroll:true を指定)すると、さらに行が表示されます。
  • hasMore.above — 通常はこれも存在します。動的リストのページ切り替えボタン(#vertButtonScroll)または表形式セクションのスクロールバーから検出されます。どちらのウィジェットもないまれなグリッドでのみ省略されます。省略されている場合は不明として扱ってください。
const t = await readTable({ maxRows: 50 });
console.log('Columns:', t.columns);
console.log('Loaded:', t.shown, 'rows; more below:', t.hasMore.below);
// Pagination:
const page2 = await readTable({ maxRows: 50, offset: 50 });
readSpreadsheet() → { title?, headers?, data?, totals?, rows?, total }

「Сформировать」をクリックした後、レポート出力(SpreadsheetDocument)を読み取ります。

ヘッダー行が検出された場合は、構造化データを返します。

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

ヘッダーを検出できない場合は、{ rows: string[][], total } を返します。

getSections() → { activeSection, sections, commands }

画面遷移せずに、セクションパネルとコマンドを読み取ります。

getCommands() → string[]

現在のセクションのコマンドを取得します。

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

セクションと、開いているすべてのタブを取得します。

アクション

戻り値の形式に関する規約。 すべてのアクション関数は、アクション固有の追加情報(clicked、focused、selected、filled、notFilled、closed、opened、navigated、deleted、filtered、unfiltered)を含む**フラットなフォーム状態**(getFormState()と同じ形式)を返します。エラーは常にトップレベルの.errorsに格納されます(存在する場合)。exec-wrapperは.errors.modal/.errors.balloon`があると自動的に例外をスローします。

clickElement(text, { dblclick?, table?, expand?, modifier?, scroll?, file? }) → フォーム状態

ボタン、ハイパーリンク、タブ、ナビゲーションパネルのリンク、またはグリッド行をクリックします(あいまい一致)。

無効なコントロールは例外になります。 clickElement、fillFields、selectValueは、利用できないコントロールに対して見せかけの成功を報告する代わりに、"X" is disabledをスローします。まずgetFormState().buttons[].disabled/fields[].disabledを確認してください。

  • table — ボタン検索の範囲を特定のグリッドのコマンドパネルに限定します(tables[]にある名前を指定):

    await clickElement('Добавить', { table: 'Исходящие' }); // clicks "Добавить" near "Исходящие" grid
    
  • リストではシングルクリックで行を選択します。ダブルクリックで項目を開きます:

    await clickElement('0000-000227', { dblclick: true }); // opens document
    
  • メニューを開くとsubmenu[]を返します。項目名を指定してもう一度クリックしてください:

    const r = await clickElement('Ещё');
    // r.submenu = ['Расширенный поиск', 'Настройки', ...]
    await clickElement('Расширенный поиск');
    
  • ツリーノード:デフォルトのクリックは選択(行をハイライト表示)です。{ expand: true }を指定すると、展開/折りたたみを切り替えます:

    await clickElement('ИСУ ФХД');                      // select row
    await clickElement('ИСУ ФХД', { expand: true });    // expand/collapse
    
  • fileでファイルまたはフォルダーを選択できます。「Добавить из файла...」(ファイルから追加...)や「Загрузить」(アップロード)など、ファイル選択ダイアログを開くボタンをクリックし、表示されたダイアログで指定したパスを選択します。1Cの「Выбор файлов」ウィンドウ、または(Windowsの場合)1Cファイルシステム拡張機能のネイティブなファイル/フォルダー選択ダイアログが対象です。フォルダーを選択できるのはネイティブダイアログのみです。file: { path, via: 'html' | 'native' }を返します。パスが存在しない場合、またはtimeout(デフォルトは10秒)以内にダイアログが表示されない場合は例外になります:

    await clickElement('Добавить из файла...', { file: 'build/МоёРасширение.cfe' });
    await clickElement('Выбрать каталог', { file: 'C:/Exchange/out' });
    
  • フィールドにフォーカスを移す(最後の手段。tableを指定していない場合):textがクリック可能なコントロールに一致せず、フォームフィールドの名前/ラベルに一致する場合、値を変更せずに入力欄をクリックしてフォーカスします。focused: { field, id, ok }を返します(フィールドにフォーカスできなかった場合、ok: false)。フォーカスに依存するキー操作に使用できます:

    await clickElement('Контрагент');          // focus the reference field
    await getPage().keyboard.press('F4');      // open its selection form
    
  • modifier: 'ctrl'(選択に追加)またはmodifier: 'shift'(範囲を選択)を指定して、複数の行を選択します:

    await clickElement('Номенклатура 1');                          // select first row
    await clickElement('Номенклатура 2', { modifier: 'ctrl' });   // add to selection
    await clickElement('Номенклатура 5', { modifier: 'shift' });  // select range 2..5
    // Verify selection:
    const t = await readTable();
    t.rows.filter(r => r._selected);  // rows with _selected: true
    
  • (行、列)を指定してセルをクリック — 最初の引数に{ row, column }を指定します。ルーティング先は、フォーム上にスプレッドシートがある場合はスプレッドシートのドリルダウン、それ以外の場合はグリッドセルです。両方がある場合はtable: 'GridName'を指定して特定のグリッドを指定します。

    スプレッドシートレポートのドリルダウン:

    const report = await readSpreadsheet();
    // report.data[0] = { 'К1': 'Материалы строительные', 'К6': '150 000', ... }
    await clickElement({ row: 0, column: 'К6' }, { dblclick: true });                      // by index
    await clickElement({ row: { 'К1': 'Материалы' }, column: 'К6' }, { dblclick: true });  // by filter
    await clickElement({ row: 'totals', column: 'К6' }, { dblclick: true });               // totals row
    await clickElement('150 000', { dblclick: true });                                     // fallback: by text
    

    フォームのグリッドセル(カタログリスト、ジャーナル、テーブル部分)。表示範囲外の列は横方向に自動スクロールします(固定列に対応)。現在のDOMウィンドウ外にあるフィルター対象の行には、scroll: true | numberを指定します:

    await clickElement({ row: 0, column: 'Количество' }, { table: 'Товары', dblclick: true });
    await clickElement({ row: { 'Номенклатура': 'Бумага' }, column: 'Цена' }, { table: 'Товары' });
    await clickElement({ row: { 'Номер': '0000-000601' }, column: 'Сумма' },
                       { table: 'Реализации', scroll: true });  // PageDown loop, max 50
    

    注意点:

    • row: <number>は絶対位置ではなく、現在のDOMウィンドウ内のインデックスです。1Сでは長いリストが仮想化されます。row: 0は、前回のスクロール後に読み込まれた最上部の行です。長いリスト内の任意の行には、row: { col: val }とscroll: trueを使用してください。
    • scroll: trueは下方向にのみ(PageDownで)移動します。上に戻るには、まずgetPage().keyboardでHomeを押すか、filterListで対象を絞り込んでください。
    • フィルターに一致する行が複数ある場合は、最初の行が選択されます。区別するにはフィルターを絞り込んでください。
fillFields({ name: value }) → filled を含むフォーム状態

ラベルでフォームフィールドを入力します(あいまい一致)。フィールドの種類は自動検出されます。

値フィールドの種類方法
'Конфетпром'参照クリップボード貼り付け+入力補完
'5000'プレーンテキストクリップボード貼り付け
'true' / 'да'チェックボックス切り替え
'Оплата поставщику'ラジオボタンラベルのあいまい一致
'' / null任意(チェックボックス/ラジオボタンを除く)Shift+F4 でクリア

DCS レポートのフィルター:人が読めるラベル名を使用します。チェックボックスは自動的に有効になります。

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

filled: [{ field, ok: true, value, method }] を含むフォーム状態を返します(method:clear|toggle|radio|paste|dropdown|form|typeahead)。フィールドごとのエラーがある場合は詳細メッセージを付けて例外をスローします。問題のあるフィールドと利用可能な選択肢が一覧表示されます。呼び出しが戻った場合はすべてのフィールドが入力済みなので、項目ごとの確認は不要です。

selectValue(field, search, opts?) → selected を含むフォーム状態

ドロップダウンまたは選択フォームから、参照フィールドの値を選択します。カタログから正確に選択する必要がある参照フィールドでは、fillFields よりも信頼性が高くなります。フィールドをクリアするには、空の search('' または null)を渡します(Shift+F4)。

search — 単純検索用の文字列、またはフィールドごとの詳細検索用の { field: value } オブジェクトです。

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

// Per-field search (disambiguate by multiple columns):
await selectValue('Документ', { 'Номер': '0000-000601', 'Дата': '29.12.2016' }, { type: 'Реализация (акт' });

複合型フィールド(複数の型を受け入れるフィールド)では、type を指定して、最初に型を選択してから値を選択します。

await selectValue('Документ', '0000-000601', { type: 'Реализация (акт' });
// Clears field → opens type dialog → picks type via Ctrl+F → picks value from selection form
// result.selected = { field: 'Документ', search: '0000-000601', type: 'Реализация (акт', method: 'form' }

DCS ラベルにも対応しており、対応するチェックボックスが自動的に有効になります。

複数選択 — 配列を渡すと、複数の値を一度に選択できます。フィールドには、その値の集合だけが設定されます(置換)。selected: { field, values, notSelected? } を返します。values は実際に選択された値で、フィールドにない値は notSelected: [{ value, reason }] に含まれます(呼び出しは例外をスローしません)。要素には search と同じ形式(文字列、または { col: value })を使用します。

await selectValue('Наименование компании', ['Альфа ООО', 'Бета АО']);
// selected: { field: 'Наименование компании', values: ['Альфа ООО', 'Бета АО'] }
fillTableRow(fields, opts) → filled(および任意で notFilled)を含むフォーム状態

Tab キーで移動しながら、テーブル行のセルに値を入力します。値にはプレーンな文字列、複合型セル用の { value, type }、またはクリア用の ''/null(Shift+F4)を指定します。

filled: [{ field, ok, ...}] を含むフォーム状態を返します。成功時の項目は { field, ok: true, method, value }(method:direct|paste|dropdown|form|type-direct|skip|clear|toggle)、フィールドごとのエラー時は { field, ok: false, error, message } です。一致しなかったフィールドは notFilled: [...] に含まれます。

fillFields とは異なり、fillTableRow はフィールドごとのエラーで例外をスローしません。エラーは filled[] 内の ok: false の項目として示されるため、呼び出し元は必要に応じて個別に対処できます(例:行のほかのセルは入力したまま、1 つのセルだけ再試行)。r.filled.filter(f => !f.ok) で確認してください。エラーコード:composite_type/type_required/type_dialog_failed({value, type} で再試行);column_not_found(readTable で列名を確認);no_selection_form/no_selection_after_type(再試行するか、selectValue に切り替え);not_found/no_match/ambiguous(検索文字列を絞り込む);still_open(グループを選択しています。末端の行を選択してください)。1C のソフト検証エラー(balloon、modal)は exec-wrapper 経由で引き続き例外をスローします。

オプション説明
tab入力前にタブを切り替えます
add入力前に新しい行を追加します
row既存の行を編集します:0 始まりのインデックス、またはセルの値で行を特定するための { col: value } フィルター(1 つ以上の列)
scrollrow フィルター使用時、現在の DOM ウィンドウの範囲外も検索します(true = 最大 50 回の PageDown、数値 = 上限)
tabletables[] のグリッド名(複数グリッドのフォーム用)
// Add new row:
await fillTableRow(
  { 'Номенклатура': 'Бумага', 'Количество': '10', 'Цена': '100' },
  { tab: 'Товары', add: true }
);
// Edit existing row by index:
await fillTableRow(
  { 'Количество': '20' },
  { tab: 'Товары', row: 0 }
);
// Edit existing row located by cell values (одна или несколько колонок):
await fillTableRow({ 'Цена': '120' }, { table: 'Товары', row: { 'Номенклатура': 'Бумага' } });
await fillTableRow({ 'Сумма': '500' }, { row: { 'Номер': '0000-000601', 'Дата': '29.12.2016' }, scroll: true });
// Multi-grid form — add row to specific table:
await fillTableRow(
  { 'Объект': 'БДДС' },
  { table: 'Исходящие', add: true }
);
// Composite-type cell (e.g. SubConto accepting multiple types):
await fillTableRow(
  { 'СубконтоКт1': { value: 'Голованов', type: 'Физическое лицо' } },
  { tab: 'Проводки' }
);
  • Tab キーによる順次移動 — フィールドの順序は 1C のフォーム設定で決まります
  • あいまいなセル一致:「Количество」は「ТоварыКоличество」に一致します
  • 参照セルは入力補完ポップアップによって自動検出されます
deleteTableRow(row, { tab?, table? }) → フォーム状態

0 始まりのインデックスで行を削除します。table は複数グリッドのフォームで特定のグリッドを対象にします。

closeForm({ save? }) → closedを含むフォーム状態

Escapeキーで現在のフォームを閉じます。実際にフォームが閉じたかどうかを示すclosed: true/falseを含むフォーム状態を返します。

引数動作
{ save: false }確認時に「Нет」を自動クリックします
{ save: true }確認時に「Да」を自動クリックします
{}(省略時)ダイアログが表示された場合、confirmationフィールドを返します

closed — フォームが閉じた場合(フォーム番号が変わった場合)はtrue、開いたままの場合(例:Escapeが無視された場合)はfalseです。フォームが実際に閉じたことを確認するため、必ずチェックしてください。閉じた後は、残っているフォームの数を確認するためにformCountをチェックしてください。

clickElement('×')よりこちらを優先してください。タブ上の閉じるボタンは判別しにくいためです。

filterList(text, opts?) → フォーム状態

リストをフィルターします。シンプルモードではすべての列を検索し、詳細モードでは特定のフィールドを対象にします。

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

階層型カタログでも使用できます(ビューをフラット化します)。

unfilterList({ field? }) → フォーム状態

フィルターを解除します。引数なしの場合はすべてのフィルターを解除し、{ field }を指定した場合は特定のバッジを解除します。

ユーティリティ
screenshot() → PNG Buffer
wait(seconds) → フォーム状態
getPage() → Playwright Page(生のオブジェクト。高度なスクリプト作成用)
startRecording(path, opts?) / stopRecording() → MP4動画の録画(録画中に再起動する場合は{ force: true })
showCaption(text, opts?) / hideCaption() → ページ上のテキストオーバーレイ
showTitleSlide(text, opts?) / hideTitleSlide() → 全画面のタイトルカード(イントロ/アウトロ)
isRecording() → boolean
setHighlight(on) / isHighlightMode() → 動画用の自動ハイライトモード
highlight(text) / unhighlight() → 要素の手動ハイライト(エラー時に利用可能な要素を一覧表示)
addNarration(videoPath, opts?) → TTSのナレーションを追加したMP4
getCaptions() → 前回の録画の字幕タイムスタンプ

セットアップ(ffmpeg)、ハイライトモード、TTSナレーション、APIの詳細、使用例については、recording.mdを参照してください。 .v8-project.jsonにffmpegPathが設定されている場合は、startRecording({ ffmpegPath })に渡してください。 .v8-project.jsonにtts設定がある場合は、オブジェクト全体をオプションとしてaddNarration()に渡してください。

よくあるパターン

ドキュメントを作成して保存する
await navigateSection('Продажи');
await openCommand('Заказы клиентов');
await clickElement('Создать');
await fillFields({ 'Организация': 'Конфетпром', 'Контрагент': 'Альфа' });
await fillTableRow({ 'Номенклатура': 'Бумага', 'Количество': '10' }, { tab: 'Товары', add: true });
await clickElement('Провести и закрыть');
リストから項目を開く
await clickElement('КП00-000227', { dblclick: true });
// Always use { dblclick: true } — single click only selects the row
階層型リストを操作する
await filterList('Конфетпром');                               // flatten + search
await clickElement('Конфетпром ООО', { dblclick: true });     // open
await closeForm();
await unfilterList();                                          // restore hierarchy
レポートを生成して読み取る
// 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);
レポートのセルをドリルダウンする
// Generate report
await clickElement('Сформировать');
await wait(5);
const report = await readSpreadsheet();

// Double-click cell to open drill-down (uses coordinates from readSpreadsheet)
await clickElement({ row: 0, column: 'К6' }, { dblclick: true });
// Modal dialog "Выбор поля" opens
await clickElement('Регистратор');
await clickElement('Выбрать');
await wait(10);
const drilldown = await readSpreadsheet();
複数グリッドのフォームを操作する

フォームによっては、複数のグリッド(例:1つのフォームに「Входящие」と「Исходящие」のテーブル)が含まれます。tableを指定しない場合、「Добавить」などのボタンは最初に一致したものがクリックされ、readTableは最初のグリッドを読み取ります。必要なものとは異なる場合があります。

手順1 — getFormState()でテーブルを確認します。

const form = await getFormState();
// form.tables = [
//   { name: "ДеревоБизнесПроцессов", columns: ["Полный код", "Бизнес-процесс"], rowCount: 21 },
//   { name: "Входящие", label: "Входящие", columns: ["Объект", "Бизнес-процесс источник", ...], rowCount: 1 },
//   { name: "Исходящие", label: "Исходящие", columns: ["Объект", "Бизнес-процесс приемник", ...], rowCount: 1 }
// ]

手順2 — グリッド操作でtable名を使用します。

// Read specific table
const t = await readTable({ table: 'Исходящие' });

// Add row — fillTableRow with add:true already clicks the right "Добавить" button
await fillTableRow({ 'Объект': 'БДДС' }, { table: 'Исходящие', add: true });

// Or click buttons separately
await clickElement('Добавить', { table: 'Входящие' });

// Delete from specific table
await deleteTableRow(0, { table: 'Исходящие' });

テーブルの照合では、技術名(tables[].name)と表示ラベル(tables[].label)のどちらも使用できます。ラベルは画面に表示されるグループタイトルで、スクリーンショットを見ながら作業する際に便利です。名前による照合がラベルによる照合より優先されます。

キーボードショートカット
const page = await getPage();
await page.keyboard.press('F8');  // example: create new item in focused reference field
キーコンテキスト操作
F8参照フィールドにフォーカスがある新しいカタログ項目を作成
Shift+F4任意の入力フィールドにフォーカスがあるフィールド値をクリア(fillFields/selectValue/fillTableRowで''/nullを指定すると自動的に実行)
F4参照フィールドにフォーカスがある選択フォームを開く
Alt+Fリスト/テーブルフォーム詳細検索ダイアログを開く
フォームを閉じる — 使用する方法
目的方法
ドキュメントを登録して閉じるclickElement('Провести и закрыть')
カタログを保存して閉じるclickElement('Записать и закрыть')
保存せずに閉じるcloseForm({ save: false })
保存して閉じるcloseForm({ save: true })
閉じる(手動で確認)closeForm() — ダイアログが表示された場合はconfirmationを返します

Execのレスポンス形式

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

エラー時(スクリーンショットが自動的に撮影されます):

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

runがタイムアウトした場合:

{ "ok": false, "error": "Script timed out after 600000ms (--timeout)", "output": "...", "timedOut": true, "elapsed": 600.1 }

ループの回避

  • 操作ごとの試行は最大2回です。 同じ方法で2回続けて失敗した場合は、そこで中止してユーザーに報告してください
  • 見つからないものは、見つからないものとして扱います。 filterList の結果が0行、またはフィルタリング後の readTable が空の場合、その項目はこのデータベースに存在しない可能性が高いです。少しずつ条件を変えながら、同じ検索を5回繰り返さないでください
  • 同じ方法ではなく、別の方法を試してください。 セクションのナビゲーションで見つからなかった場合は、navigateLink を試してください。簡易検索で見つからなかった場合は、特定のフィールドを指定した詳細検索を試してください。ただし、同じ方法を繰り返さないでください
  • 部分的な結果も報告してください。 一覧は見つかったものの特定の項目が見つからなかった場合は、その旨を伝えてください。何も報告せずに検索を繰り返すのではなく、現在利用できる項目を示してください

重要な注意事項

  • ヘッドありモード — 1C ではブラウザーの表示が必要です。ヘッドレスモードは使用できません
  • 起動時間 — 初回接続時、1C の読み込みには30~60秒かかります(start に組み込まれています)
  • あいまい一致 — 名前検索はすべて、完全一致 > 前方一致 > 部分一致の順で行われます
  • クリップボードへの貼り付け — すべてのテキストフィールドには Ctrl+V で入力します(1C のイベントが適切に発生します)。各操作の前に OS のクリップボードが自動的に保存され、操作後に復元されるため、ローカルユーザーのクリップボードはテスト実行後も保持されます。--no-preserve-clipboard(任意のコマンド)、環境変数 WEB_TEST_PRESERVE_CLIPBOARD=0、または webtest.config.mjs の preserveClipboard: false を指定すると、この動作を無効にできます
  • bash でのキリル文字 — エスケープの問題を避けるには、cat <<'SCRIPT' | node $RUN exec - を使用してください
  • ノーブレークスペース — 1C では通常のスペースの代わりに \u00a0 が使われます。一致判定ではすべて内部で正規化されます
  • セクションパネルの表示 — navigateSection() はパネルの位置(側面、上部)を問わず動作しますが、「画像とテキスト」または「テキスト」の表示モードが必要です。アイコンのみのモードには対応していません。API はアイコンだけからセクション名を読み取れません

リグレッションテストスイート

ユーザーから、1C ソリューションを自動リグレッションテスト(アサーション、フック、タグ、再試行、Allure/JUnit レポート、複数ユーザーのプロセステストを含む複数ファイルのテストスイート)でカバーするよう求められた場合は、test モードに切り替えてください。作成時のガイドライン、調査フロー(メタデータ + exec による実操作の確認)、アプリケーションごとのフォルダ構成、そのまま貼り付けて使えるテンプレート、失敗時のトリアージについては regress.md を参照してください。単一スクリプトの自動化では、通常の run/exec を使用してください。test はプロジェクト全体を対象とする専門モードです。

コメント