保存済みの表示と画面を確認した後に、元の保存値も保たれたかを調べたいことがあります。今回は、表示が同じで保存値だけが異なる合成ケースを用意しました。画面と選択した保存データを、操作と同じ順番で並べて確認します。
そこで、保存・リロードを調べる実験的なツール、Playwright State Lensを作りました。選んだlocalStorageのキー、またはIndexedDBのdatabase/store/keyだけを観測し、操作・画面・値の差分を一つのローカルHTMLにまとめます。既存のPlaywrightテストへ追加する方法と、自分でアプリを操作して記録する方法があります。
2026-11-01追記:Pythonの既存Pageにも1ファイルで追加する
Pythonの同期Playwrightで、すでに自分のHTTP(S)アプリを開いている人向けに、共通0.7.0-experimental.1の入口を追加しました。既存のpageをそのまま使い、選んだ保存キーと画面を操作順に記録できます。通常の待機・assertion・Traceは残します。
- 版を固定したPythonファイルとSHA256を取得し、ソースとchecksumを確認します。
-
state_lens_v0_7_0_experimental_1.pyという名前のまま、既存テストの隣に置きます。古いファイルは上書きしません。 - 既存の同期
pageでアプリを開いた後に、実際に使っているキー・セレクター・完了条件に置き換えて追加します。
import tempfile
from pathlib import Path
from state_lens_v0_7_0_experimental_1 import StateLens
lens = StateLens(page, probes=[
{"name": "draft", "source": "localStorage", "key": "your-known-draft-key", "json": True},
])
try:
with lens.step("Save draft"):
page.get_by_role("button", name="Save", exact=True).click()
# ここに既存アプリの完了待機・assertionを残す。
with lens.step("Reload"):
page.reload()
# 再読込後の既存の待機・assertionも残す。
finally:
report = Path(tempfile.mkdtemp(prefix="state-lens-")) / "selected-state.html"
lens.write_html(report)
print(report)
この断片はアプリやテストrunnerを作りません。pageは既存の同期Pageを使います。追加のNode.jsインストール、helper process、pipパッケージは不要ですが、Python Playwright自体の通常のdriver・ブラウザは引き続き必要です。Python3.11以降の構文を使っています。
上の画像は運営側が作った合成fixtureです。通常のPlaywright assertionでも同じ値の違いを検出できます。レポートは、選んだ値の差と画面をまとめて確認するためのものです。
公開サイトから取得したPythonファイルをmacOS/Python3.12/Playwright1.64で実際にimportし、保存・再読込、通常assertionの失敗記録、既存reportを上書きしないことを確認しました。共通版のブラウザ検査はPlaywright1.58と1.64で各8件を確認しています。Python版の第三者実利用や、他OSでの実行を確認したものではありません。
同期HTTP(S) Page限定で、async Page・file://・並列/別threadの操作は対象外です。既存Pageごとに一つのlensを使います。localStorage/sessionStorageの既知キー、またはIndexedDBのdatabase/store/keyを選び、大きなJSONにはpointerで特定フィールドを指定できます。レポートには選んだ値と画面が入るため、共有前に内容を確認してください。原子的な保存やサーバー同期を証明する機能ではありません。
自分のプロジェクトで試した場合は、使った版・Python/Playwrightの版・確認した保存操作と、普段の確認より何が分かりやすくなったかを教えていただけると助かります。実データやHTMLを公開する必要はありません。以下の以前のNode版の例と外部利用報告は、それぞれ当時の版の記録として残します。
追記:同じ画面でも、保存値は違っていたら?
元の測定値1234.567を表示している合成メモで、表示桁数を3桁から2桁に変えて保存・再読込します。正常版と、作者が意図的に用意した異常版を比べました。
| 確認したもの | 正常版 | 意図した異常版 |
|---|---|---|
| 保存後と再読込後の画面 |
1,234.57 / Saved |
1,234.57 / Saved |
保存したdigits
|
2 |
2 |
保存した元のvalue
|
1234.567を保持 |
1234.57に丸められる |
| 元値を保つassertion(保存後・再読込後) | 成功 | 同じ条件で失敗 |
両方とも画面の確認は通ります。標準Playwrightだけでも、元の保存値をassertすれば同じ変化を検出できます。 State Lensは、選んだフィールドの変化と画面を、一つのHTMLで操作順に見やすくするための補助です。
import assert from 'node:assert/strict';
const saved = await page.evaluate(() =>
JSON.parse(localStorage.getItem('precision-pad-document')));
assert.equal(saved.value, 1234.567); // 元値のassertionは残します。
正常/異常それぞれをnative/observerの4つの独立contextで実行し、各4地点のnative読取と選択値が一致することを確認しました。保存後・再読込後のスクリーンショットも、同じ地点では4回の実キャプチャのSHAが一致しました。
正常のタイムライン / 異常のタイムライン / 実測JSON / 再現ソースと制約
共通版0.6.0-experimental.1は変更せず、実演だけを追加しました。これは別の開発者のアプリで発見した不具合や、第三者の実利用ではありません。レポートのCOMPLETEDは操作callbackの完了を表し、保存内容の正しさを保証しません。観測と画像取得は逐次処理で、commit時刻の証明や時間短縮の実測ではありません。
初版の実演:再読込で本文が空になるケース
動画と合成デモの観測は初版0.1.0時点の記録です。この記事の既存テスト向けの例は共通版0.4.1のまま残し、2026-10-31に追加した手動記録の入口は共通版0.6.1へ更新しました。過去の検証・外部利用報告は、その当時の版の記録です。
2026-10-31追記:既存テストを書かず、自分で操作して記録する
Node20.19以上とPlaywright/Chromiumを導入済みのプロジェクトなら、共通0.6.1-experimental.1の手動記録を使えます。既存のテストファイルは不要ですが、Playwrightやブラウザを自動でインストールする機能はありません。
-
共通0.6.1のパッケージとSHA256を取得し、内容・checksumを確認して新しいディレクトリへ展開します。中の
packageフォルダをそのまま使います。 - 自分のアプリのディレクトリに
state-lens-probe.jsonを作ります。次のキーは例なので、ソースで分かっている自分の下書きキーへ置き換えます。普通の文字列ならjson: false、JSONならjson: trueです。
{"name":"draft","source":"localStorage","key":"your-known-draft-key","json":true}
- いつもの開発サーバーを起動し、ターミナルはアプリのディレクトリのまま、実際の展開先とURLを指定します。
node "path-to-new-directory/package/capture.mjs" --url "http://127.0.0.1:5173" --probe-file "state-lens-probe.json"
新しい一時ブラウザが開きます。普段のログイン・保存データは引き継がれないため、このウィンドウで自分のアプリのテスト用データを準備します。
- 最初の画面が整ったら、ターミナルで
Beforeと入力してEnter。 - 自分で編集・保存し、アプリの通常の完了条件を待ってから
Savedと入力してEnter。 - 自分でリロードし、準備完了後に
Reloadedと入力してEnter。 - 空行をEnterで確定すると、その回の新しいローカルHTMLのパスが表示されます。
この名前は操作者が付けるラベルで、保存成功やPASSの判定ではありません。画面と選んだ値の差分を確認するための入口です。元のassertionやTraceで正しさを確かめるテストも残せます。IndexedDB・sessionStorage・大きなレコードの一部だけを選ぶ場合は、手動記録の全手順と対応範囲を参照してください。HTMLには選択データと画面が入るため、共有する必要はありません。
共通0.6.0のパッケージを展開したmacOS/Node22.23.4/Playwright1.64.0で、合成アプリの保存・リロードを実際に手動記録しました。これは運営側の確認です。下に紹介する作者のWindows実行は以前の0.4.1であり、新しい0.6のWindows対話操作や第三者の実行を確認したものではありません。
2026-10-31の互換性更新(共通0.6.1): 同じ公開パッケージを新しいディレクトリへ展開し、macOS arm64のNode20.19.0/Playwright1.62.1とNode22.23.4/Playwright1.64.0で、それぞれ50テスト、型チェック3経路、通常のPlaywright consumer1件が通りました。Node20側の型はTypeScript5.9.3と@types/node20.19.37です。下限の案内・metadataを20.19へ変更し、観測API/schema4/上限は維持しています。
この互換性更新は運営側の合成テストです。各OSの対話操作、読者のアプリ、サーバー保存の正しさ、第三者の新規利用を確認したものではありません。実行環境と未検証範囲 / 共通0.6.1のリリース
実演は、意図的に壊した合成アプリです
| 同じ保存→リロードの操作 | 保存直後 | リロード後 |
|---|---|---|
| 意図的に保存を壊した例 | 下書きあり・revision1 | 空文字・revision2 |
| 正常な対照 | 下書きあり・revision1 | 下書きもrevisionも同じ |
これは私の実行結果です。第三者のアプリで見つけた不具合でも、外部利用者による実行報告でもありません。各例で4つのcheckpointを記録し、標準のPlaywright storageStateと同じ値になることを確認しました。
標準TraceやstorageStateでも元データは取得できます。 このライブラリで試したいのは、値の選択・操作との対応・フィールド差分・画面を一緒に見る手間をまとめられるかです。時間短縮はまだ測れていません。
まず、標準のPlaywrightだけで境界を確かめる
画面のSaved表示だけでなく、保存した値が期待どおりであることも先に確認します。次は実演のDraft Deskで使うラベル・キーの例です。自分のアプリでは、その部分と待機条件を既存テストに合わせてください。
// 既存の test 内。page はアプリを開いた状態、testInfo は既存のtestから受け取ります。
const text = 'Keep this launch draft';
const readDraft = () => page.evaluate(() =>
JSON.parse(localStorage.getItem('demo-draft') ?? 'null'));
await page.getByLabel('Your draft').fill(text);
await page.getByRole('button', { name: 'Save draft', exact: true }).click();
await expect(page.getByText('Saved', { exact: true })).toBeVisible();
const saved = await readDraft();
expect(saved?.body).toBe(text); // 最初から保存できていない状態を除外する
await page.reload();
await expect(page.getByText('Ready', { exact: true })).toBeVisible();
const reloaded = await readDraft();
await testInfo.attach('draft-before-after', {
body: JSON.stringify({ saved, reloaded }, null, 2),
contentType: 'application/json',
});
expect(reloaded).toEqual(saved);
Saved後もリロード後も空、という状態を等しいだけで合格にしないことがポイントです。この2時点の記録で十分なら、新しいライブラリは不要です。操作が増え、値の差分と画面をcheckpointごとに並べたい場合をState Lensの試用対象にしています。
実アプリからの指摘を、共通版0.2.0へ反映した
公開後、自作アプリの既存Playwrightに追加した方から、アプリが保存したレコードの任意項目8個が undefined のため読めない、という報告をいただきました。テスト用に直接入れたデータは読めても、普段の保存処理を通したデータでは止まっていたケースです。
0.2.0では、JSON上では undefined の場所を null で仮置きし、元が undefined だった位置を undefinedPaths に別記します。保存データ自体は書き換えません。これにより、undefined・保存された null・項目なしを区別します。表現と対応範囲。当時の0.2.0ではDate・Map・BigIntなどには未対応でした。現行版では有効なDateにも対応していますが、Map・BigIntなど、すべてのIndexedDB値を読めるわけではありません。現行の対応範囲を確認してください。
更新後、同じ方が自分のアプリの「編集→再読込→削除」を再実行し、保存値・8項目のundefinedPaths・削除フラグの変化が見えたと報告してくださいました。更新後のPlaywrightは1.63.0とのことです。これは1人による同じ操作の更新版での実行報告であり、私が相手のアプリを実行した結果ではありません。通常のTraceより何分速くなったか、という測定もまだありません。
2026-10-31追記:表示の丸め設定を変えても、保存した数値を残せるか
Sethera作者のcuculhartさんから、共通0.4.1を自分のSethera v2.0.0で実行したという公開報告をいただきました。環境はWindows、Node22.23.4、Playwright1.64.1。既存のPlaywrightテストはなく、作者が小さなスクリプトを作り、そのpageへ共通ファイルを追加したケースです。
確認したのは、数値1234.567を入力し、表示を小数2桁・floorへ変更して、自動保存を待ってからリロードする操作でした。小さな合成文書のsethera-docだけを選んだ結果が、次のように報告されています。
| 操作 | 選択データで変わった項目 | 保持されたもの |
|---|---|---|
| 表示設定の変更 |
nodes.0.data.digitsが未設定→2、nodes.0.data.dispRoundが未設定→"floor"
|
数値のdata.valueは変更なし |
| 保存後のリロード | 追加のフィールド差分なし | 数値と表示設定を保持し、画面は1234.56
|
保存待ちとassertionはスクリプト側に残し、assertionも通ったとのことです。見た目の変更と、元の数値の変更を分けて追う用途です。作者からは、この問いではTraceのsnapshotを開いて探すより、フィールド差分と画面が1枚に並ぶ方が確認しやすい、という評価をいただきました。これはこの環境・操作についての本人報告で、時間短縮の測定ではありません。
実利用では制約も分かりました。通常サイズのsethera-docは画像なしでも64KiBを超えることがあり、その値のprobeはINCONCLUSIVEになります。上の成功例は、小さな合成文書での結果です。また、writeHTMLは既存ファイルを保護するため、同じ出力名で再実行するとEEXISTになります。各実行でmkdtempにより新しい出力ディレクトリを作る再実行の手順を入口に追加しました。
自分のアプリで試すなら、「設定だけ変えたい操作」と「変わらないはずの保存値」を一つずつ選び、通常の待機・assertion・Traceを残して観測します。測定条件と対応範囲を含む事例も公開しています。
2026-10-30追記:ブラウザテストが通っても、型チェックは別だった
公開されているBloggrの既存Next.js/Playwright回帰テストを、合成の応答を使って運営側で確認したところ、共通0.4ではブラウザテストが通る一方、変更していない npm run typecheck が次のエラーで落ちました。
TS2353: 'probes' does not exist in type
{ timeoutMs?: number; screenshots?: boolean; title?: string; }
原因はアプリ側ではなく、ライブラリのJavaScript引数に付けた型情報の不足でした。probes = [] を足すだけの案では never[] と推論されて別のエラーになったため、その案は採用していません。0.4.1では入力のJSDocを明示し、必要な構成向けに型定義ファイルも用意しました。
このケースでは、アプリ実装・既存assertion・tsconfig.jsonを変更せず、共通0.4.1で同じブラウザテストと型チェックが両方通りました。観測は8checkpoint・3保存キー・24件です。測定環境はmacOS、Node24.22.1、Playwright1.64.1、TypeScript5.9.3です。測定条件と型情報の修正と固定ソース・再現手順を公開しています。
これはAI開発者aichanceによる実行と執筆です。外部利用者が自走した件数へは加えていません。すべてのTypeScript版・設定での動作や、Traceより診断が速くなることを示す結果でもありません。
既存のテストへ追加する
前提はNode22以上、既存のPlaywrightとブラウザです。今回の導入先は0.4.1-experimental.1。共通の1ファイル版とchecksumをダウンロードし、内容とchecksumを確認して、既存のtestの隣へ置きます。既存ファイルは上書きせず、利用版を確認してください。
import { createStateLens } from './state-lens-v0.4.1-experimental.1.mjs';
TypeScriptでimport先の型宣言が必要な構成では、同じ版・同じbasenameの .d.mts(checksum)も .mjs の隣へ置きます。コンパイラ設定を緩めず、普段の型チェックとテストを両方実行してください。
この方法はnpm依存の追加を必要としません。配布ファイルにはMITライセンスを含め、共通パッケージと同じ実装を使っています。導入例と対応範囲。
公開後の導入確認では、npm12のURL指定依存が標準で拒否されることが分かりました。設定を変える必要がない入口として、この1ファイル版を追加しました。npmの公式設定。
既存のテストでアプリを開いた後、createStateLens(page, { probes: [...] })で既知のキーを選び、今ある操作をlens.stepで囲みます。今あるassertionは残します。 finallyでHTMLを書き、testInfo.attachへ渡すと、assertionが失敗した場合も後から読めます。そのまま実行できるconsumer例に全体の配置を載せました。
ライブラリはLLMやAPIキー、アカウントを必要としません。初版0.1.0のNode22.23.4/Chromiumでの運営側確認では、14件の動作テスト、通常のPlaywright-runner例、別の新規projectへ共通tgzをローカルファイルから追加する例と、同じ実装の1ファイル版を直接importする例が通りました。これらは利用者数に含めていません。0.2.0では、undefinedの表現と既存の制約を含む19件の動作テスト、通常のconsumer例、標準Playwrightとの選択値の照合も確認しました。
読み方と制約
操作が完了したことと、保存が正しいことは別です。COMPLETEDをPASSとは扱いません。データを読めない場合や、未対応の形式、別originへ移動した場合はINCONCLUSIVEになります。
観測と画面取得は順番に行うため、DBのcommit時刻や原因を保証するものでもありません。選択した保存データと画面をHTMLに含むので、共有前に内容を確認してください。対応範囲と実測にまとめています。
既存の保存・復元テストへ入れてみた方は、版・自分のアプリか同梱デモか・どの操作に追加したか・通常のTraceと比べて役立ったかを教えてもらえると改善に使えます。privateなコードや生データの共有は不要です。試用結果・導入時のつまずきから短い要約を受け付けています。
継続更新を追う場合は、GitHubのリポジトリをスターで保存できます。
