1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

バックアップ復元はPASSでも金額は間違っていた:Playwrightで「何を保証したか」を分ける

1
Last updated at Posted at 2026-10-15

JSONを出力し、新しいブラウザ環境で読み込み、リロードしても元のレコードが残る。ここまで通っても、元の金額の計算が正しいとは限りません。

公開OSSのNestSave Savings Plannerを変更せずローカルで検査したところ、次の二つが同時に成立しました。

  • 目標・入出金履歴・支出・予算・設定のバックアップ往復はPASS。
  • 新規目標の「Already Saved」に5,000を入れると、目標残高は10,000になる。履歴は5,000の1件だけ。

この記事では、この結果を一つの「テスト成功」にまとめず、保存の検査と仕様の検査に分けた方法を説明します。

AI支援で運用しているaichanceの実験です。合成データだけを使い、対象アプリの98個の追跡ファイルは変更していません。作者による確認や公開サービスの不具合を主張するものではありません。

この検証に使ったOSSはApp Crash Labです。自分のアプリで試すなら、バックアップ用の導入プロンプトを普段のコーディングAIへ渡し、保存と実JSONの復元を検査する設定を作れます。Node22とnpmが必要です。テストランナー自体はLLM・APIキーを使わず、結果を単独Playwrightテストとして残せます。

同じ入力操作で、5,000と0を比べる

対象はNestSaveのこのコミットです。macOS、Node22.16.0、Playwright1.64.0のChromiumで、別々の新しいブラウザコンテキストを使いました。サービスワーカーと外部へのリクエストは止めています。

通常の画面から購入目標を作り、目標額75,000、初期貯金5,000を入力します。日程・積立・収支の設定を進めて確定すると、目標画面は次の状態になりました。

ローカルの合成データ。目標残高10,000に対し、初回入金の履歴は5,000の1件

初期貯金の入力 作成後の目標残高 入金履歴 判定
5,000 10,000 1件、合計5,000 期待する5,000と不一致
0 0 0件 一致

画面だけでなく、既存のIndexedDBを読み取り専用トランザクションで確認しました。目標レコードも10,000、入金レコードの合計は5,000です。表示の整形だけの差ではありませんでした。

コードでは、目標作成時にcurrentAmountへ初期貯金を入れ、作成後に同額の初回入金を追加しています。入金の保存処理は現在残高に金額を加算するため、二重計上になっていると考えられます。これは固定した版についての原因の推定です。

バックアップの比較がPASSになる理由

別の検査では、同じ種類の目標に加え、追加入金、支出、月次予算、設定を通常の画面で作りました。App Crash Lab0.9で実際のJSONをダウンロードし、その2,112バイトを変更せず、新しいブラウザコンテキストに渡して復元します。

検査するのは次の五つです。

  1. 目標の完全なレコード。
  2. 入出金履歴の完全なレコード。
  3. 支出の完全なレコード。
  4. 予算の完全なレコード。
  5. 設定の値全体。

ID、金額、メモ、日付などを含め、出力ファイル、復元直後、移動後、リロード後で比較しました。動的IDのある四つのストアは、同じIDを持つ完全なレコードの集合として比較します。外側の配列の並び順だけを比較対象から外し、IDや入れ子の配列順序は保持します。

この比較はPASSでした。しかし「計算結果が元から間違っていて、それが正確に保存された」場合もPASSになります。保存前と保存後の一致だけでは、初期貯金5,000という入力と計算結果の関係は保証できません。

バックアップ用の設定は、観測した10,000、追加入金後の12,000を保持するかを調べています。その値を正しい計算結果として認定したわけではありません。

また、新しいコンテキストは完全な未初期化状態ではありません。通常の設定画面で初期の収支設定を保存し、その値と、四つの業務ストアが空であることを、設定ファイルに宣言してランナーで確認してから読み込みます。復元元の目標や履歴を事前に注入してはいません。空欄の任意項目がIndexedDBにundefinedとして保存される経路は、このJSON検査ではINCONCLUSIVEです。

修正後に緑になるテストを残す

算術の再現テストでは、観測した10,000を期待値にしません。入力5,000なら、保存された目標残高も画面の金額も5,000であることを期待します。

expect(actual.transactionSum).toBe(initial);
expect(actual.amount).toBe(initial);

initialを5,000と0にした二つのケースを実行します。固定した版では前者が失敗し、後者が通ります。固定した版での終了コード1は、5,000の期待値に一致しないためで、起動エラーではありません。修正後は二つとも通る設計です。完全な操作手順と保存値の読み取りは単独で動くPlaywrightテストにあります。

再実行用のセットには、固定したアプリの起動手順、算術テスト、バックアップ往復のJSON設定、検査の範囲をまとめました。算術テストはPlaywrightだけで実行でき、App Crash Labのインストールは不要です。バックアップ設定から生成したテストも、Playwrightだけの別フォルダで2件とも通ることを確認しました。

ブラウザ内のJSONバックアップを持つ自分のアプリで試すなら、バックアップ用の導入プロンプトから、普段使うコーディングAIに設定作成と実行を頼めます。テストランナー自体はLLMもAPIキーも使いません。自分のアプリで使ったPASS/FAILの要約や導入で止まった箇所は、実データを除いてGitHubへ寄せてもらえると改善に使えます。

検査済みなのは選択した五つの領域です。エクスポートに含まれない連続利用日数・バッジ、作成していないマイルストーン、暗号化バックアップ、Android版、電源断は対象外です。通常の再読込レポートには保存値やトレースが含まれるため、実データへ適用したものをそのまま公開しないでください。

追記:値の一致と、画面で使えることを分ける(2026-10-16)

その後、変更していない別のローカルアプリでも、検査対象を分ける必要がありました。

固定したアプリ 実測したこと 残した期待値
VoxelDraftの取得時スナップショット JSON復元でブロックは3個のまま。座標と色も同じだが、2個の所属レイヤーが変化 元の所属レイヤーを維持する
Repwise 4032141 完了した運動記録のJSON往復はPASS。一方、復元前からHistoryに表示されない 完了した記録のタイトルが1件表示される

VoxelDraftの再実行用セットは、1レイヤーにだけブロックを置いた対照がPASS、2レイヤーへ配置した検査がFAILでした。圧縮されたファイル表現を扱うため、この往復検査は汎用JSON設定とは別のPlaywrightテストです。レイヤーを平坦化することが意図した仕様かは未確認です。

Repwiseの二つの設定と起動手順では、通常のゲスト画面で1セット12.5 kg×8回を完了し、保存レコードのisCompleted: trueとtotalVolumeKg: 100を確認しました。App Crash Lab 0.9は選択した6ストアの完全なレコードを、実ファイル・新しい環境への復元・再読み込みで比較してPASS。一方、Historyの表示件数は期待1に対して0なので、表示を含む検査は開始時点でINCONCLUSIVEです。既に表示されない状態から、再読み込みで壊れたとは判定しません。初期の種目・設定ストアは、この汎用設定の比較対象外です。

元の値の意味、出力ファイルによる保全、画面での表示を別々に観測すると、直すべき箇所を絞れます。失敗した期待値はそのまま残し、将来の更新で通るかを同じ手順で確かめます。いずれも合成データによるこちらのローカル実測で、作者の実行報告や修正確認ではありません。

追記:キーを値とは別に渡すIndexedDBの検査(0.10)

同じJSON保存でも、IndexedDBのオブジェクトストアがkeyPath: nullで、値とは別にキーを渡すアプリがあります。App Crash Lab 0.10ではkeyMode: "out-of-line"を明示し、外側の主キーとレコード自身のidが同じ型・値であることを確認して、完全なレコードを比較できます。IDのない値や一致しない主キーを、比較しやすい値へ変換して通す仕組みではありません。

変更していないMy Ideals a65fb102では、通常の画面から合成プロフィールを二つ作り、実際の「Export All Profiles」で出た1,965バイトのJSONを空の環境へ復元しました。両プロフィールのID、名前、テンプレート参照、各アイテムの収集状態、mode、version、lastModifiedを含む完全なレコードと主キーが、再読込後まで一致し、CLIはPASS・終了コード0でした。生成した単独Playwrightテストも1件成功しています。

固定版の設定・起動手順・比較範囲から同じ検査を実行できます。選択中のプロフィールと設定はバックアップ対象外なので、この結果では保証しません。こちらの合成データによるローカル実測で、作者の実行報告や実サービスの検査ではありません。

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?