
Playwright CLI 0.1.23の--isolated-contextで、1つのセッションに2人分のログイン状態を持たせる
どうも!オペ部の西村祐二です!
管理者が承認した内容が一般ユーザーの画面にどう出るか、チャットで送ったメッセージが相手側にどう届くか。Webアプリの動作確認では、2人のユーザーでログインした画面を並べて見たい場面があります。
Playwright CLI(playwright-cli)では、これまでは -s=<name> でセッション(ブラウザ)を2つ起動し、コマンドごとに操作先を付け替える必要がありました。2026年11月1日(日本時間)に公開された v0.1.23 では、tab-new に --isolated-context が加わり、1つのセッションの中で cookie と storage を分けたタブを開けるようになりました。
2人分のログイン状態を1つのセッションに持たせる手順と、その状態がどこまで共有され、いつ消えるのかをテスト用アプリで確かめた結果を紹介します。あわせて、cookie-list や state-save がどのタブに作用するか、セッションを閉じた後に state-load で引き継げるか、route のモックや端末のエミュレーションがタブごとに分かれるかも確かめました。最後に、-s でセッションを分ける方法との使い分けを整理します。
先に結論:最短の使い方
# 1人目はいつも通りデフォルトのタブでログインする
playwright-cli open https://example.com/login
# 2人目は名前を付けた別のコンテキストのタブで開く
playwright-cli tab-new https://example.com/login --isolated-context=user-b
# タブを切り替えて、それぞれの画面を確認する
playwright-cli tab-list
playwright-cli tab-select 0
今回確かめた挙動は次の通りです。
| 確かめたこと | 結果 |
|---|---|
| 名前を付けたタブとデフォルトのタブ | cookie と localStorage が分かれる |
| 同じ名前を付けた2つ目のタブ | 1つ目のログイン状態をそのまま使う |
| 名前を付けたタブをすべて閉じた後 | セッションが続いていれば、同じ名前で開き直してもログイン状態が残っている |
| セッションを閉じた後 | 名前を付けたコンテキストは消える。0.1.23 では、--profile でプロファイルを保存していても残らなかった |
state-save したファイルを次のセッションで state-load |
同じ名前のタブで、前回のログイン状態から始められた |
cookie-list や state-save |
現在のタブのコンテキストだけが対象になる |
route と open --device |
名前を付けたタブにも同じように適用される |
1つのフローで2人分の画面を見比べるなら、ブラウザを1つ起動したまま tab-select で切り替えられる --isolated-context が手軽です。ユーザーごとに、プロファイルを保存し続けたい場合や、route のモック・端末のエミュレーションを変えたい場合は、これまで通り -s でセッションを分けるほうが合っています。
--isolated-context とは
Playwright のブラウザは、ブラウザコンテキストという単位で cookie や localStorage を分けて持ちます。playwright-cli のセッションは、これまでデフォルトのコンテキスト1つだけを使っていました。
v0.1.23 では、tab-new [url] --isolated-context=<name> で名前付きのコンテキストにタブを作れるようになりました。この機能を追加した microsoft/playwright#43087 の説明では、同じ名前のタブは cookie と storage を共有し、違う名前のタブやデフォルトのタブとは分かれるとされています。この PR では、Playwright MCP のタブを操作する browser_tabs ツールにも、新しいタブを開くときの isolatedContext 引数が追加されています。MCP 側は今回試していないため、MCP 経由で使う場合の挙動はこの記事の結果からは判断できません。
playwright-cli は AI エージェントにブラウザを操作させる用途を想定した CLI です。Sessions のページでは、環境変数 PLAYWRIGHT_CLI_SESSION でエージェントの操作先を1つのセッションに固定する方法が紹介されています。セッションを1つに固定したままでも、その中に2人目のユーザーを用意できるようになったのが今回の変更です。
試してみる
環境
- @playwright/cli 0.1.23(同梱の playwright-core は 1.65.0-alpha-2026-10-30)
- Google Chrome 156.0.8078.80 / 157.0.8103.38(
playwright-cliがデフォルトで使うchrome。検証の途中で自動更新された) - Node.js v24.22.1
- macOS 26.7.2(Apple Silicon)
普段使いの環境を変えないよう、テスト用のディレクトリに pnpm add @playwright/cli@0.1.23 で入れ、node_modules/.bin/playwright-cli を呼び出して試しました。なお、リリースノートでは npm install -g @playwright/cli@0.1.23 でアップデートする方法が案内されています。
テスト用アプリは、Node.js の標準モジュールだけで書いた http://127.0.0.1:8765/ のサーバーです。/login?user=<名前> にアクセスすると、有効期限を指定しない cookie sid=<名前>(HttpOnly)を発行してトップページへ戻します。トップページには、ログイン中のユーザー名と、ページのスクリプトが localStorage の lastUser に書いた名前が表示されます。
tab-new --help には、次のオプションが追加されていました。
Options:
--isolated-context name of an isolated browser context to create the tab in. tabs in the same isolated context share cookies and storage.
1. 2人分のログイン状態を1つのセッションで持つ
後で -s で分ける方法と比べるため、ここからは名前付きのセッション iso で試しました。「先に結論」のように -s を付けないデフォルトのセッションでも、同じ手順で2人分のタブを開けることを確かめています。
まず、デフォルトのタブで alice としてログインし、--isolated-context=user-b を付けたタブを開きます。
playwright-cli -s=iso open http://127.0.0.1:8765/
playwright-cli -s=iso goto 'http://127.0.0.1:8765/login?user=alice'
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
新しいタブは alice の cookie を持たず、「Not signed in」の状態で開きました。tab-new の結果には、どのコンテキストのタブかが表示されます。
- 0: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 1: (current) [Test App - Not signed in](http://127.0.0.1:8765/) [isolatedContext: user-b]
...
### Page
- Page URL: http://127.0.0.1:8765/
- Page Title: Test App - Not signed in
- Isolated context: user-b
このタブで bob としてログインします。
playwright-cli -s=iso goto 'http://127.0.0.1:8765/login?user=bob'
bob としてログインした後、次のコマンドを順に実行した結果が下の画像です。
playwright-cli -s=iso tab-list
playwright-cli -s=iso cookie-list
playwright-cli -s=iso tab-select 0 >/dev/null && playwright-cli -s=iso cookie-list

cookie-list が返すのは、現在のタブのコンテキストにある cookie だけです。タブ1では sid=bob、tab-select 0 で切り替えた後は sid=alice でした。
それぞれのタブでスクリーンショットを撮ると、同じURLでも別のユーザーとして表示されていました。localStorage の lastUser も、タブ0では alice、タブ1では bob です。画像上部の見出しは、どのタブか分かるように追記したものです。

2. 同じ名前のタブはログイン状態を共有する
同じ user-b を付けてもう1つタブを開くと、ログイン操作なしで bob として表示されました。
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
- 0: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 1: [Test App - Signed in as bob](http://127.0.0.1:8765/) [isolatedContext: user-b]
- 2: (current) [Test App - Signed in as bob](http://127.0.0.1:8765/) [isolatedContext: user-b]
このタブで cookie-list と localstorage-list を実行すると、sid=bob と lastUser=bob が返りました。
続けて、default という名前と、まだ使っていない名前(user-c)でもタブを開きました。
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=default
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-c
- 0: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 1: [Test App - Signed in as bob](http://127.0.0.1:8765/) [isolatedContext: user-b]
- 2: [Test App - Signed in as bob](http://127.0.0.1:8765/) [isolatedContext: user-b]
- 3: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 4: (current) [Test App - Not signed in](http://127.0.0.1:8765/) [isolatedContext: user-c]
--isolated-context=defaultを付けたタブ3はデフォルトのコンテキストに入り、alice として表示されました。browser_tabsツールに PR で追加されたisolatedContext引数の説明にも、省略した場合とdefaultを指定した場合はタブをデフォルトのコンテキストに作ると書かれていますuser-cを付けたタブ4は、誰もログインしていない状態で開きました。名前を増やせば、別のログイン状態として開けます(3人目のログインまでは試していません)
3. トレースはコンテキストごとに分かれる
tracing-start を実行すると、コンテキストごとに別のファイルが作られました。
$ playwright-cli -s=iso tracing-start
### Result
Trace recording started
- [Action log](.playwright-cli/traces/trace-1793751668716.trace)
- [Network log](.playwright-cli/traces/trace-1793751668716.network)
- [Action log (isolatedContext: user-b)](.playwright-cli/traces/trace-1793751668716-user-b.trace)
- [Network log (isolatedContext: user-b)](.playwright-cli/traces/trace-1793751668716-user-b.network)
- [Action log (isolatedContext: user-c)](.playwright-cli/traces/trace-1793751668716-user-c.trace)
- [Network log (isolatedContext: user-c)](.playwright-cli/traces/trace-1793751668716-user-c.network)
- [Resources](.playwright-cli/traces/resources)
ファイル名には -user-b や -user-c のようにコンテキストの名前が付き、tracing-stop でもコンテキストごとのトレースが保存されました。PR の説明では、トレースと動画の録画はすべてのコンテキストにまたがるとされています。今回確かめたのはトレースのファイルが分かれるところまでです。Trace Viewer で中身を開いていないため、各ファイルにそのコンテキストの操作だけが入っているかは確かめていません。動画の録画も試していません。
4. タブを閉じてもセッション中は残り、セッションを閉じると消える
user-b のタブ2つを tab-close で両方とも閉じました。
playwright-cli -s=iso tab-close 2
playwright-cli -s=iso tab-close 1
- 0: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 1: [Test App - Signed in as alice](http://127.0.0.1:8765/)
- 2: (current) [Test App - Not signed in](http://127.0.0.1:8765/) [isolatedContext: user-c]
この時点の tab-list には user-b が出てきません。0.1.23 の tab-list はタブの一覧なので、タブのないコンテキストは表示されませんでした。ここでもう一度 --isolated-context=user-b でタブを開きます。
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
- 3: (current) [Test App - Signed in as bob](http://127.0.0.1:8765/) [isolatedContext: user-b]
ログインし直さなくても bob のままでした。名前付きのコンテキストはタブと一緒には消えず、セッションが続いている間は残ります。
一方、セッションを閉じてから開き直し、同じ名前でタブを作ると「Not signed in」に戻りました。
playwright-cli -s=iso close
playwright-cli -s=iso open http://127.0.0.1:8765/
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
- Page Title: Test App - Not signed in
- Isolated context: user-b
プロファイルをディスクに保存する open --profile=<dir> でも試しました。名前付きのタブはエラーにならずに作れました。テスト用アプリの sid は有効期限を指定しない cookie で、ブラウザを閉じると消えるため、そのままでは閉じた後に残るかを比べられません。そこで、cookie-set で有効期限付きの cookie をデフォルトのタブと user-b のタブの両方にセットしてから、セッションを開き直しました。
playwright-cli -s=iso open --profile=./profile-persist http://127.0.0.1:8765/
playwright-cli -s=iso cookie-set sid alice --domain=127.0.0.1 --path=/ --expires=$(( $(date +%s) + 3600 ))
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
playwright-cli -s=iso cookie-set sid bob --domain=127.0.0.1 --path=/ --expires=$(( $(date +%s) + 3600 ))
playwright-cli -s=iso close
# 同じプロファイルで開き直す
playwright-cli -s=iso open --profile=./profile-persist http://127.0.0.1:8765/
playwright-cli -s=iso tab-new http://127.0.0.1:8765/ --isolated-context=user-b
開き直した後、デフォルトのタブは alice のログイン状態で復元され、user-b のタブは「Not signed in」でした。PR の実装を見ると、名前付きのコンテキストは、--profile のディレクトリを使うデフォルトのコンテキストとは別に、browser.newContext() で作られます。そのため、プロファイルのディレクトリには書き込まれません。
5. ログイン状態をファイルに書き出して引き継ぐ
名前付きのコンテキストはセッションを閉じると消えるので、ログイン状態をファイルに書き出す state-save も試しました。state-save と state-load も、現在のタブのコンテキストが対象でした。
同じセッションの別の名前のタブへ移す
4節で user-b のタブを開き直した直後(セッションを閉じる前)の状態で試しました。タブ0とタブ1が alice(デフォルト)、タブ2が user-c(未ログイン)、タブ3が user-b(bob)です。
# user-b のタブ(タブ3)を選んだ状態で保存する
playwright-cli -s=iso state-save user-b-state.json
# まだ誰もログインしていない user-c のタブを選んで読み込む
playwright-cli -s=iso tab-select 2
playwright-cli -s=iso state-load user-b-state.json
playwright-cli -s=iso reload
保存したファイルには、bob の cookie と http://127.0.0.1:8765 の localStorage(lastUser=bob)だけが入っていました。alice の cookie は含まれていません。
{
"cookies": [
{
"name": "sid",
"value": "bob",
"domain": "127.0.0.1",
"path": "/",
"expires": -1,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
}
],
"origins": [
{
"origin": "http://127.0.0.1:8765",
"localStorage": [
{ "name": "lastUser", "value": "bob" }
]
}
]
}
state-load の後に reload すると、user-c のタブは bob として表示され、デフォルトのタブは alice のままでした。
セッションを閉じた後に読み込む
次に、セッションを閉じた後でも、書き出したファイルで bob に戻れるかを試しました。1節と同じ手順で bob としてログインした user-b のタブで state-save し、セッションを閉じてから次のコマンドを実行しています。
playwright-cli -s=iso open http://127.0.0.1:8765/
# URL を付けずに名前付きのタブを開き、ページを読み込む前に state-load する
playwright-cli -s=iso tab-new --isolated-context=user-b
playwright-cli -s=iso state-load user-b-state.json
playwright-cli -s=iso goto http://127.0.0.1:8765/
- Page Title: Test App - Signed in as bob
- Isolated context: user-b
開き直したセッションの user-b のタブは、ログイン操作なしで bob として表示されました。このタブで cookie-list は sid=bob、localstorage-list は lastUser=bob を返しました。デフォルトのタブは「Not signed in」のままです。テスト用アプリの sid は有効期限を指定しない cookie(ファイル上は "expires": -1)ですが、state-load で読み込めば新しいセッションでも使えました。
tab-new に URL を付けなかったのは、ページを読み込む前にログイン状態を入れておくためです。URL を付けて開いたタブでは、前の手順のように state-load の後に reload して表示を更新しました。
このファイルを読み込むと、別のタブでもそのユーザーとしてログインした状態になります。実際のサービスで使う場合は、.gitignore に入れてリポジトリにコミットしないようにし、使い終わったら削除するのが良さそうです。
6. route と端末のエミュレーションは名前付きのタブにも適用される
PR の説明では、route によるリクエストの差し替え、許可・拒否する origin の設定、init script がすべてのコンテキストに適用されるとされています。このうち route を試しました。あわせて、PR には書かれていない open --device による端末のエミュレーションも確かめています。
5節の後半で開き直したセッション(タブ0がデフォルト、タブ1が bob の user-b)で、タブ0を選んで route を追加しました。テスト用アプリに /hello というページはないので、route が適用されていなければトップページが返ります。
playwright-cli -s=iso tab-select 0
playwright-cli -s=iso route '**/hello' --body='mocked by route' --content-type=text/plain
playwright-cli -s=iso goto http://127.0.0.1:8765/hello
playwright-cli -s=iso eval "document.body.innerText"
続けて、user-b のタブと、新しく開いた user-c のタブでも同じURLを開きました。最後に、user-b のタブで unroute を実行しています。
playwright-cli -s=iso tab-select 1
playwright-cli -s=iso goto http://127.0.0.1:8765/hello
playwright-cli -s=iso eval "document.body.innerText"
playwright-cli -s=iso tab-new http://127.0.0.1:8765/hello --isolated-context=user-c
playwright-cli -s=iso eval "document.body.innerText"
# user-b のタブに戻って route を外す
playwright-cli -s=iso tab-select 1
playwright-cli -s=iso unroute '**/hello'
| タブ | route の後 |
user-b のタブで unroute した後 |
|---|---|---|
| デフォルトのタブ | "mocked by route" |
トップページ(Not signed in) |
user-b のタブ |
"mocked by route" |
トップページ(Signed in as bob) |
route の後に開いた user-c のタブ |
"mocked by route" |
トップページ(Not signed in) |
デフォルトのタブで追加した route も、user-b のタブで実行した unroute も、すべてのタブに反映されました。route の結果に表示される Playwright のコードは page.context().route(...) ですが、実際には現在のタブのコンテキストだけでなく、すべてのコンテキストに適用されていました。
端末のエミュレーションについては、--device="iPhone 15" を付けたセッションで、デフォルトのタブと user-b のタブの値を比べました。
playwright-cli -s=dev open http://127.0.0.1:8765/ --device="iPhone 15"
playwright-cli -s=dev eval '`screen=${screen.width}x${screen.height} dpr=${devicePixelRatio} touch=${navigator.maxTouchPoints} mobile=${/iPhone/.test(navigator.userAgent)}`'
playwright-cli -s=dev tab-new http://127.0.0.1:8765/ --isolated-context=user-b
# 同じ eval をもう一度実行する
| タブ | eval の結果 |
|---|---|
| デフォルトのタブ | screen=393x852 dpr=3 touch=1 mobile=true |
user-b のタブ |
screen=393x852 dpr=3 touch=1 mobile=true |
(参考)--device なしのセッションの user-b のタブ |
screen=1280x720 dpr=1 touch=0 mobile=false |
名前付きのタブも、open で指定した iPhone 15 のエミュレーションのまま開きました。0.1.23 に同梱されている playwright-core の実装では、名前付きのコンテキストも、セッションのコンテキスト設定(contextOptions)を渡した browser.newContext() で作られます。0.1.23 の tab-new には、端末を指定するオプションはありません。
設定ファイルで指定する init script や許可・拒否する origin が、すべてのコンテキストに適用されるかは試していません。これらを設定ファイルで使っている場合は、2人目のタブにも適用されるかどうか、実際の挙動を確かめておく必要がありそうです。
-s で分ける方法との違い
2026年11月7日時点で、公式の Sessions のページには、管理者と一般ユーザーを2つのセッションに分ける例(Workflow: isolated testing)が載っています。
# Admin session
playwright-cli -s=admin open https://app.example.com --persistent
playwright-cli -s=admin state-load admin-auth.json
playwright-cli -s=admin goto /admin/settings
# User session
playwright-cli -s=user open https://app.example.com --persistent
playwright-cli -s=user state-load user-auth.json
playwright-cli -s=user goto /dashboard
# Monitor both
playwright-cli show
この例のうち、state-load で2人分のログイン状態を読み込んで画面を並べる部分は、5節の後半の手順(tab-new --isolated-context=<name> → state-load → goto)を使えば1つのセッションにまとめられます。ただし、--persistent や --profile でプロファイルに残るのはデフォルトのコンテキストだけで、名前付きのコンテキストは残りません(4節では --profile で確認)。
2つの方法を比べるため、-s=iso-a と -s=iso-b の2つのセッションで、それぞれ alice と bob としてログインしました。playwright-cli list には2つのブラウザが並び、pid も別でした。1つのセッションに名前付きのコンテキストを足した場合、list に出るブラウザは iso の1つだけでした。
次の図は自作した概念図で、公式の構成図ではありません。左は -s でセッションごとにブラウザを起動する方法、右は1つのセッションのブラウザの中に名前付きのコンテキストを足す方法です。右では、同じ名前のタブがコンテキストを共有します。

-s=<name> で分ける |
--isolated-context=<name> で分ける |
|
|---|---|---|
| ブラウザ | セッションごとに起動する | 1つのブラウザの中にコンテキストを足す |
| ユーザーの切り替え | コマンドごとに -s を付け替える |
tab-select でタブを選ぶ |
| ログイン状態の保存 | --persistent / --profile でプロファイルごと保存できる |
セッションを閉じると消える。次のセッションへ引き継ぐなら state-save と state-load を使う |
| トレース | セッションごとに記録する | 1回の記録でコンテキストごとのファイルになる |
route のモック |
セッションごとに設定する | すべてのコンテキストに適用される |
| 端末やブラウザの種類 | セッションごとに open --device や --browser で選べる |
open で選んだものをすべてのコンテキストで使う |
ユーザーごとに、プロファイルを保存し続けたい場合や、モック・端末のエミュレーションを変えたい場合は、公式の例のように -s でセッションを分けるほうが合っています。
検証結果の考察
PR の実装では、名前付きのコンテキストはタブを閉じても一緒には閉じず、セッションを終えるときの処理(dispose())でまとめて閉じます。tab-list にはタブのないコンテキストが表示されないため、同じ名前を使い回すと、前のログイン状態から始まります。ログインしていない状態から試し直したい場合は、まだ使っていない名前を付けるか、セッションを開き直すことになります。名前付きのタブを選んで cookie-clear などで消す方法は試していません。
PR の説明には "cookie, storage and network tools act on the current tab's context" とあり、cookie-list、localstorage-list、state-save、state-load が現在のタブのコンテキストに作用した結果と一致していました。同じ箇条の中で、route は "Init scripts, origin filters and routes apply to every context" と、すべてのコンテキストに適用される側に挙げられています。6節で route と unroute がすべてのタブに作用したのも、この説明の通りでした。
今回試した範囲では、名前付きのコンテキストで分かれたのは cookie と localStorage(ログイン状態)で、route のモックと open --device の端末のエミュレーションはセッションで共通でした。
試してみた感想
2人目のユーザーを用意するのに、ブラウザをもう1つ起動する必要がありません。今回はコマンドを手で実行しただけですが、AI エージェントに任せる場合も -s の付け替えを指示しなくてよく、tab-list の [isolatedContext: ...] から誰のタブかを読み取ってもらえそうです。
想定と違ったのは、タブを全部閉じても名前付きのコンテキストが残っていた点です。tab-list を見てもタブのないコンテキストは分からないので、気づきにくいと感じました。コンテキストの寿命は Tabs のページにも tab-new --help にも書かれていないので、今回のように一度試して確かめておくのが良さそうです。
まとめ
- Playwright CLI 0.1.23 では、
tab-new [url] --isolated-context=<name>で cookie と storage を分けたタブを1つのセッションの中に作れる - 同じ名前のタブはログイン状態を共有し、デフォルトのタブや違う名前のタブとは分かれる
- 名前付きのコンテキストはタブを全部閉じてもセッション中は残り、セッションを閉じると消える。0.1.23 では
--profileで保存していても残らなかった cookie-listやstate-saveは現在のタブのコンテキストが対象になる。state-saveで書き出したログイン状態は、state-loadで同じセッションの別の名前のコンテキストに読み込めた- 開き直したセッションでも、同じ名前のタブに
state-loadすれば前回のログイン状態から始められた routeのモックとopen --deviceの端末のエミュレーションは、名前付きのタブにも適用される- 1つのフローで2人分の画面を見比べるなら
--isolated-context、ユーザーごとにプロファイルを保存し続けたい、またはモックや端末を変えたいなら-sが合う
誰かの参考になれば幸いです。
参考リンク:







