SeleniumとPlaywrightで起動済みのブラウザを操作する

# はじめに
ブラウザ自動テストのデファクトスタンダードである `Selenium`。
そして新進気鋭の `Playwright`。
これらを使用して組んだプログラムは、自分自身でブラウザを起動して自動操作を開始します。

しかし、**起動済みのブラウザ**を自動操作したい！というケースもありますよね？
はたして `Selenium` と `Playwright` を使って起動済みのブラウザを操作することができるのか？
※ブラウザは MicrosoftEdge と GoogleChrome で検証しています。

動かせた時の感動は省略。

:::note info
2023/05月 `Playwright` における実装方法を追記しました。
:::

# やったこと
## ブラウザの起動
今回の手法は、ブラウザを起動するところにポイントがあります。
コマンドライン引数で `-remote-debugging-port=9222` を指定してあげるのです！
 [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) が有効な状態でブラウザが起動します。
これによって、`Selenium` および `Playwright` がアタッチできるようになります。
※ MicrosoftEdge を起動したい場合は `msedge` 、
　 GoogleChrome を起動したい場合は `chrome` を指定してください。
```bat
msedge --remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChromium
```

`--user-data-dir`の指定も忘れずに。
操作対象のブラウザ以外にブラウザを起動している場合、
ユーザプロファイルがあるフォルダとして同一パスを指定することができないようです。
私の環境では以下のようなエラーになってしまいました。

Selenium の場合：
>OpenQA.Selenium.WebDriverException: The HTTP request to the remote WebDriver server for URL http://localhost:61065/session timed out after 60 seconds. ---> System.Net.WebException: 要求は中止されました: 操作はタイムアウトになりました。

Playwright の場合：
>connect ECONNREFUSED 127.0.0.1:9222
=========================== logs ===========================
<ws preparing> retrieving websocket url from http://localhost:9222
============================================================

参考：[SeleniumでChromeのユーザープロファイルを指定しつつ同時に自分もChromeを使う方法](https://qiita.com/Hidenatsu/items/e43ba04b4b5f710784e6)

## ソース (`Selenium` の場合)
`ChromeOptions.DebuggerAddress` にブラウザ起動時に指定したものと同じポート番号を指定します。
ブラウザ起動済みの状態で当プログラムを実行すると、見事に動き始めます。

```csharp
var options = new ChromeOptions
{
    DebuggerAddress = "127.0.0.1:9222"
};

using (var driver = new ChromeDriver(options))
{
    // 任意のブラウザ操作処理 ↓↓↓
    var wait = new WebDriverWait(driver, new TimeSpan(0, 0, 5));
    driver.Url = "https://www.google.com";
    var q = driver.FindElement(By.Name("q"));
    q.SendKeys("Chromium");
    q.Submit();

    wait.Until(ExpectedConditions.TitleIs("Chromium - Google 検索"));
    ((ITakesScreenshot)driver).GetScreenshot().SaveAsFile($"{DateTime.Now.ToString("yyyyMMddHHmmss")}.png");
    // 任意のブラウザ操作処理 ↑↑↑
}

```

## ソース (`Playwright` の場合)
`BrowserType.ConnectOverCDPAsync` にブラウザ起動時に指定したものと同じポート番号を指定します。
ブラウザ起動済みの状態で当プログラムを実行すると、見事に動き始めます。
```cs
using (var playwright = await Playwright.CreateAsync())
{
    var browser = await playwright.Chromium.ConnectOverCDPAsync("http://localhost:9222");
    var page = browser.Contexts[0].Pages[0];

    // 任意のブラウザ操作処理 ↓↓↓
    _ = await page.GotoAsync("https://playwright.dev/dotnet");
    _ = await page.ScreenshotAsync(new PageScreenshotOptions { Path = "screenshot.png" });
    // 任意のブラウザ操作処理 ↑↑↑

    await browser.DisposeAsync();
}
```

<!--
#おまけ
Google Chrome を起動する部分がポイントだったわけです。
ここで、手動ではなく**別プログラム経由で起動されたブラウザ**を自動操作したい場合を考えてみます。
うまいこと`-remote-debugging-port`を指定してあげる必要がありますよね？

やるとすればこんな感じか。。？

```csharp
using System.Diagnostics;

/// <summary>
/// chrome.exe という名前のアセンブリとしてビルドする
/// </summary>
namespace DummyChrome
{
    class Program
    {
        static void Main(string[] args)
        {
            var proc = new Process();
            proc.StartInfo.FileName = @"C:\Program Files (x86)\Google\Chrome\Application\_chrome.exe";
            proc.StartInfo.Arguments = @"-remote-debugging-port=9222 --user-data-dir=C:\Temp_ForChrome";
            proc.Start();
        }
    }
}
```
オリジナルの`chrome.exe`は`_chrome.exe`にリネームしてしまいます。
Google Chrome の通常利用に支障が出るのはご愛敬。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432322/37078729-5e62-43f1-0e9c-a83f44d86e4f.png)
-->

# おわりに
参考にしたページ：
[Selenium: Attach to an existing Chrome browser with C#](https://intellitect.com/selenium-chrome-csharp/)
[BrowserType | Playwright .NET ConnectOverCDPAsync](https://playwright.dev/dotnet/docs/api/class-browsertype#browser-type-connect-over-cdp)

<!--
参考になりそうでしなかったページ：
[Re-using existing browser session in Selenium using C#](https://tarunlalwani.com/post/reusing-existing-browser-session-selenium-csharp/)
`Reflection`をつかって`private`メンバーにアクセスしてるので黒魔術感が否めない。
そもそもやりたいことが違う雰囲気もある。
-->