• #Chrome拡張機能
  • #クラウド会計
  • #仕訳管理
  • #Playwright
  • #UI改善
開発tax-assistantメモ

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つのセクションで構成した。

  1. 仕訳帳エクスポート - 仕訳帳データをスプレッドシートに書き出し
  2. 仕訳一括削除 - 期間指定で仕訳を一括削除
  3. 対象外明細エクスポート - 対象外に設定された明細の一覧を書き出し
  4. 対象外一括復帰 - 対象外明細を未登録状態に戻す

3階層のインデントでセクションを視覚的に分離し、各セクションにタイトルを付けた。タブ→セクション→操作ボタンの階層が目で追えるようになった。

口座共通ルールとページネーション

sub_account_id="0" を指定すると口座共通の自動仕訳ルールが返ることを確認した。APIドキュメントには記載がなく、Chrome DevToolsでリクエストを観察して突き止めた。

50件超の明細がある場合のページネーションも検証。APIはページ番号ベースで、pageパラメータを1ずつ増やして空配列が返るまで取得する方式で実装した。


API調査で踏んだ罠

journalizing_suggestionsstatus=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. 仕訳登録: 未登録明細から仕訳を作成
  2. 仕訳削除: 作成した仕訳を一括削除 → 明細が未登録に戻ることを確認
  3. 対象外設定: 明細を対象外に設定
  4. 対象外復帰: 対象外明細を未登録状態に復帰 → 再度仕訳登録できることを確認

複合ルール(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_suggestionsstatus=ignored未入力のみ返り、パラメータ無視APIドキュメントを鵜呑みにせず実際のレスポンスを見る
2trans_list取得fetchでHTML取得tbodyが空(Ajax読み込み)SSRかSPAかでスクレイピング方法が変わる
3trans_list取得iframe + MutationObserver成功。Ajax完了後のDOMを取得できたfetchがダメならiframeに切り替える判断を早くする
4confirm置換18箇所を一括でカスタムモーダルにEscapeキーでresolveされないバグ発生ネイティブAPIの暗黙の挙動を再実装し忘れがち
5Playwright APIfetchヘッダーにCookie手動設定CSRF検証で弾かれたHttpOnly + CSRFの組み合わせは page.evaluate() 一択
6口座共通ルールsub_account_id="0" で取得共通ルールが返ってきたドキュメントにない仕様はDevToolsで観察して見つける

学び

  • journalizing_suggestions APIは未入力明細専用だった。APIの名前やパラメータだけ見て推測せず、レスポンスを実際に確認してから実装に入るべきだった。30分の回り道になった
  • fetchで取れないHTMLはiframe + MutationObserverで取る。Ajax依存ページのスクレイピングはfetchを試す前にページのNetwork通信を見て判断した方が速い
  • confirm() → カスタムモーダルの置換では、Escapeキー・タブキー・オーバーレイクリックなどネイティブダイアログの暗黙の挙動を全て洗い出してから実装に入るべきだった
  • PlaywrightでHttpOnlyセッションが必要なAPIを叩くなら、page.evaluate() 内でfetchを実行する。Node.js側からリクエストを組み立てようとするとCookie/CSRFの壁に当たる
  • Chrome DevTools MCP経由の実機テストは、コードを書いて即座にブラウザで確認できるのでフィードバックループが速い