Playwrightとzxでブラウザ操作とCLIを連携する

画面上の値を取得してローカルPCの環境変数に設定しつつCLIでコマンドを実行する、ということをしていたので手順を残しておきます。

## 使用したライブラリなど

| 名称 | リンク | 補足 |
| --- | --- | --- |
| Playwright | https://playwright.dev/ | E2Eテストを行うためのライブラリとして有名ですね。もちろんブラウザ操作の自動化だけでも有用です。 |
| bun | https://bun.sh/ | JavaScriptのランタイム...というだけではなくall-in-oneのツールとのこと。TypeScriptを自分でトランスパイルしなくても実行してくれるのが嬉しい。肉まんかわいい。 |
| zx | https://github.com/google/zx | JavaScriptでシェルをかけるツールです。バックドアが仕込まれていたのはxzです。 |
| dprint | https://dprint.dev/ | Rust製のコードフォーマッターです。[KAKEHASHIさんのテックブログ](https://kakehashi-dev.hatenablog.com/entry/2023/11/27/103000)で知りました。 |

はじめzxを使う予定はありませんでした。ブラウザ操作が必須だったのでPlaywrightを選択し、bun shellのアナウンスを耳にしていたのでランタイムにbunを選びました。しかし結局bunのみで実現ができそうになかった[^1]のでzxを使うことにしました[^2]。

[^1]: bun shellで使用できるコマンドはIssueにまとまっています。[Implement commands in Bun shell](https://github.com/oven-sh/bun/issues/9716)
[^2]: 追っていないのですが、Denoにも[dax](https://github.com/dsherret/dax)というのがあるようですね

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Introducing the Bun Shell<br><br>Cross-platform shell language &amp; interpreter for shell scripting with JavaScript <a href="https://t.co/Pis37UJumg">pic.twitter.com/Pis37UJumg</a></p>&mdash; Bun (@bunjavascript) <a href="https://twitter.com/bunjavascript/status/1748587391433253044?ref_src=twsrc%5Etfw">January 20, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://bun.sh/docs/runtime/shell

zxを利用すると、例えばjavascriptからaws cliを利用する、とかもできます。これまでだったら何のために？と感じていましたが、ブラウザ操作が前提にある場合は非常に便利と感じました[^3]。

[^3]: シェルスクリプトを~~書けない~~書きたくないのです

dprintはおまけです。Biomeでも良いですが、formatを自動かつサクッとやりたいだけなのでdprintを使いました。

なお筆者のPCはApple SiliconのMacBookです。


**--- 以下手順 ---**

## セットアップ

```sh
bun init
```
## playwright

```sh
bun create playwright
```
playwrightの設定はデフォルトのままです。E2Eテストをしたいわけではないので`tests`, `test-examples`フォルダは削除します。そしてコードを生成するために`package.json`でscriptを定義します。
```json
{
  "scripts": {
    "record": "npx playwright codegen 対象のサイト",
  }
}
```
この状態で`bun record`を実行すればブラウザが立ち上がり、操作が記録されます。こうして作成されたコードを元にスクリプトを作成します。ここでは`index.ts`内に記述します。

注意点として、playwrightは基本的に新規でブラウザを起動するため、認証のセッションは維持できません[^4]。
しかしCookieによるセッション認証はコード内でCookieを設定することで突破できます。あるいはクライアント証明書が必要な場合は、別の工夫が必要です。

[^4]: 改めて調べたら[SeleniumとPlaywrightで起動済みのブラウザを操作する](https://qiita.com/yk109/items/6cab9564d1844d272a8c) という方法がありましたね

#### Cookieを設定する例
  
```typescript
const context = await browser.newContext();
await context.addCookies([
  {
    name: cookie_name,
    value: cookie_value,
    domain: your_domain,
    path: path,
    expires: expire_period,
  },
]);
```

（MacかつChromeの場合）クライアント認証を自動で選択する（`AutoSelectCertificateForUrls`を設定する）ためには、以下のコマンドをCLIで実行します。

```sh
defaults write com.google.Chrome AutoSelectCertificateForUrls -array-add -string '{"pattern":"https://example.com","filter":{"ISSUER":{"CN":"Example CA"}}}'
```

参考：[Mac OS XのChromeでAutoSelectCertificateForUrlsを設定する](https://qiita.com/koba_ninkigumi/items/1dbafd495ae960ab66cf)

## zx
TypeScriptファイル内でシェルを実行するためにzxを用意します
```sh
bun add zx
```
シェルの実行は以下のように行います。
```typescript
await $`echo "export ENV=${env}"`;
await $`echo "export MY_ENV=${my_env}" >> ~/.zshrc`;
```

なお、zxは新しい子プロセスでシェルコマンドを実行するため、スクリプトを実行しているシェルセッションには影響を与えません。したがって上記のように`echo "export ENV=${env}"`をzxで実行しても親のセッションでは利用できません。利用できるようにするためには、`.zshrc`などに書き込んだ上で、`source`するか新しいセッションを開始する必要があります。

## dprint
おまけですが、一応。
```sh
bun add dprint --dev
npx dprint init # 適当に必要なものを選択
```
```json
{
  "scripts": {
    "fmt": "npx dprint fmt",
  }
}
```
formatを実行。
```sh
bun fmt
```

## 感想
TypeScriptでブラウザ操作もCLI操作もできると捗る！

## 参考まとめ

### zx
https://google.github.io/zx/

https://zenn.dev/overflow_offers/articles/20220606-zx-introduction

https://www.webdelog.info/entry/2021/11/06/215036

### bun shell
https://bun.sh/docs/runtime/shell

https://bun.sh/blog/the-bun-shell

https://github.com/oven-sh/bun/issues/9716

https://azukiazusa.dev/blog/bun-cross-platform-shell-script/

### クライアント証明書
https://qiita.com/koba_ninkigumi/items/1dbafd495ae960ab66cf

https://chromeenterprise.google/policies/?policy=AutoSelectCertificateForUrls:title