Chrome拡張 仕訳管理タブの新設と仕訳削除・対象外復帰機能
朝から会計ソフトA連携Chrome拡張の仕訳管理タブを一から組み上げ、夕方にはPlaywright PoCまで走らせていた。journalizing_suggestions APIが status=ignored を無視する仕様に30分取られ、trans_listのHTMLをfetchしたらtbodyが空で手が止まり、confirm()を18箇所潰してEscapeキーバグを踏み抜いた。最終的にChrome DevTools MCP経由で全テスト項目がパスし、未登録明細211件のスプレッドシート書き出しまで確認が通った。
仕訳管理タブの設計と実装
4セクション構成
新設した仕訳管理タブは4つのセクションで構成した。
- 仕訳帳エクスポート - 仕訳帳データをスプレッドシートに書き出し
- 仕訳一括削除 - 期間指定で仕訳を一括削除
- 対象外明細エクスポート - 対象外に設定された明細の一覧を書き出し
- 対象外一括復帰 - 対象外明細を未登録状態に戻す
3階層のインデントでセクションを視覚的に分離し、各セクションにタイトルを付けた。タブ→セクション→操作ボタンの階層が目で追えるようになった。
口座共通ルールとページネーション
sub_account_id="0" を指定すると口座共通の自動仕訳ルールが返ることを確認した。APIドキュメントには記載がなく、Chrome DevToolsでリクエストを観察して突き止めた。
50件超の明細がある場合のページネーションも検証。APIはページ番号ベースで、pageパラメータを1ずつ増やして空配列が返るまで取得する方式で実装した。
API調査で踏んだ罠
journalizing_suggestions の status=ignored は無視される
対象外に設定した明細を一覧取得しようとして、journalizing_suggestions APIに status=ignored パラメータを渡した。レスポンスが返ってきたが、中身を見ると未入力の明細しか含まれていなかった。
Chrome DevToolsのNetworkタブでリクエストを再確認し、パラメータを変えて何度か試した。結論として、このAPIは未入力(status=default)の明細しか返さず、statusパラメータ自体を無視している。会計ソフトAのフロントエンドも、対象外明細の取得にはこのAPIを使っていなかった。
trans_list のfetchが空 → iframe方式に転換
対象外明細のIDを取得するために trans_list ページをfetchで取得した。HTMLは返ってくるが、<tbody> が空だった。ページのソースを見るとテーブルのデータ部分はAjaxで後から読み込まれる構造だった。
fetchでは静的HTMLしか取得できないので、iframe方式に切り替えた。非表示のiframeに trans_list を読み込み、Ajax完了を待ってからiframe内のDOMを走査して明細IDを取得する。読み込み完了の検知は MutationObserver でtbodyに子要素が追加されるのを監視した。
// iframeのtbodyに行が追加されるのを待つ
const observer = new MutationObserver((mutations, obs) => {
const rows = iframe.contentDocument.querySelectorAll('tbody tr');
if (rows.length > 0) { obs.disconnect(); resolve(rows); }
});
フルサイクル検証
仕訳登録→削除→対象外設定→復帰のフルサイクルをChrome DevTools MCP経由で検証した。
- 仕訳登録: 未登録明細から仕訳を作成
- 仕訳削除: 作成した仕訳を一括削除 → 明細が未登録に戻ることを確認
- 対象外設定: 明細を対象外に設定
- 対象外復帰: 対象外明細を未登録状態に復帰 → 再度仕訳登録できることを確認
複合ルール(1つの明細から複数の仕訳行が生成されるルール)の個別削除も検証し、関連する仕訳行が連動して削除されることを確認した。
confirm() 18箇所のカスタムモーダル置換
Chrome拡張のUIでは confirm() を使っていたが、ブラウザのネイティブダイアログは「このページのスクリプトがダイアログを表示しています」の警告が出る場合があり、UXが悪い。18箇所すべてをカスタムモーダルに置き換えた。
Escapeキーバグ
置換後のテストで、Escapeキーを押すとモーダルが閉じるがPromiseがresolveもrejectもされない状態になる問題が見つかった。confirm() はEscapeで false を返すが、カスタムモーダルではEscapeのイベントハンドリングが抜けていた。
keydown イベントリスナーでEscapeキーをキャッチし、キャンセルボタンと同じ処理(resolve(false))を実行するように修正した。モーダルの表示時にリスナーを登録し、閉じるときに解除する。
Playwright 会計ソフトA API連携PoC
HttpOnlyセッションCookie の壁
Playwrightから会計ソフトAのAPIを直接叩こうとして、セッションCookieが HttpOnly 属性を持っていることに気づいた。HttpOnly のCookieはJavaScriptの document.cookie からは読めず、page.context().cookies() でPlaywrightから取得してfetchのヘッダーに付けようとしたが、API側のCSRF検証で弾かれた。
最終的に page.evaluate() 内でfetchを実行する方式に落ち着いた。page.evaluate() はブラウザコンテキストで実行されるため、Cookieは自動的に付与され、CSRFトークンもDOMから取得できる。
// page.evaluate() 内ならCookieもCSRFトークンも使える
const data = await page.evaluate(async () => {
const token = document.querySelector('meta[name="csrf-token"]').content;
const res = await fetch('/api/...', {
headers: { 'X-CSRF-Token': token }
});
return res.json();
});
未登録明細211件のエクスポート
Chrome DevTools MCP経由で未登録明細のエクスポートを実行し、211件が新規スプレッドシートに書き出された。APIのページネーション、スプレッドシートの書式設定、行数の整合性をすべて確認した。
試行錯誤テーブル
| # | テーマ | 試したこと | 結果 | 気づき |
|---|---|---|---|---|
| 1 | 対象外明細取得 | journalizing_suggestions に status=ignored | 未入力のみ返り、パラメータ無視 | APIドキュメントを鵜呑みにせず実際のレスポンスを見る |
| 2 | trans_list取得 | fetchでHTML取得 | tbodyが空(Ajax読み込み) | SSRかSPAかでスクレイピング方法が変わる |
| 3 | trans_list取得 | iframe + MutationObserver | 成功。Ajax完了後のDOMを取得できた | fetchがダメならiframeに切り替える判断を早くする |
| 4 | confirm置換 | 18箇所を一括でカスタムモーダルに | Escapeキーでresolveされないバグ発生 | ネイティブAPIの暗黙の挙動を再実装し忘れがち |
| 5 | Playwright API | fetchヘッダーにCookie手動設定 | CSRF検証で弾かれた | HttpOnly + CSRFの組み合わせは page.evaluate() 一択 |
| 6 | 口座共通ルール | sub_account_id="0" で取得 | 共通ルールが返ってきた | ドキュメントにない仕様はDevToolsで観察して見つける |
学び
journalizing_suggestionsAPIは未入力明細専用だった。APIの名前やパラメータだけ見て推測せず、レスポンスを実際に確認してから実装に入るべきだった。30分の回り道になった- fetchで取れないHTMLはiframe + MutationObserverで取る。Ajax依存ページのスクレイピングはfetchを試す前にページのNetwork通信を見て判断した方が速い
confirm()→ カスタムモーダルの置換では、Escapeキー・タブキー・オーバーレイクリックなどネイティブダイアログの暗黙の挙動を全て洗い出してから実装に入るべきだった- PlaywrightでHttpOnlyセッションが必要なAPIを叩くなら、
page.evaluate()内でfetchを実行する。Node.js側からリクエストを組み立てようとするとCookie/CSRFの壁に当たる - Chrome DevTools MCP経由の実機テストは、コードを書いて即座にブラウザで確認できるのでフィードバックループが速い