SeleniumとPlaywrightでブラウザのズーム拡大率を操作するうまいやり方

# はじめに
`Selenium` と `Playwright` を用いたWebアプリケーションの自動テストにおいて、
「ブラウザの拡大率を変更した際に画面崩れを起こしてしまわないか？」
という観点でテストしたいことがありますよね！

そんなわけで、ブラウザ拡大率を操作するうまいやり方が無いかどうか調べました。

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

# うまくいった方法
`C#` にはキー操作をアプリケーションに送信する [SendKeys](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.sendkeys?view=windowsdesktop-6.0) クラスが用意されています。
これを使用して、ブラウザの拡大率を変更する際のショートカットキーをブラウザに送信することで拡大率を変更します。
拡大したいときは `"^{ADD}"`、縮小したい場合は `"^{SUBTRACT}"` を指定します。

## Seleniumの場合
```cs
using OpenQA.Selenium.Chrome;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace SeleniumApp
{
    internal class Program
    {
        private static readonly string _url = $"https://www.google.com";
        private static readonly string _dir = "screenshots";

        private static void Main(string[] args)
        {
            if (!Directory.Exists(_dir)) { _ = Directory.CreateDirectory(_dir); }

            using (var driver = new ChromeDriver())
            {
                driver.Url = _url;
                driver.Manage().Window.Size = new Size(1024, 1024);

                // Ctrl + プラス
                var keys = "^{ADD}";
                SendKeys.SendWait(keys);
                driver.GetScreenshot().SaveAsFile(Path.Combine(_dir, @"110%.png"));
                SendKeys.SendWait(keys);
                driver.GetScreenshot().SaveAsFile(Path.Combine(_dir, @"125%.png"));
                SendKeys.SendWait(keys);
                driver.GetScreenshot().SaveAsFile(Path.Combine(_dir, @"150%.png"));
                SendKeys.SendWait(keys);
                driver.GetScreenshot().SaveAsFile(Path.Combine(_dir, @"175%.png"));
                SendKeys.SendWait(keys);
                driver.GetScreenshot().SaveAsFile(Path.Combine(_dir, @"200%.png"));
            }
        }
    }
}
```

## Playwrightの場合
```cs
using Microsoft.Playwright;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PlaywrightApp
{
    internal class Program
    {
        private static readonly string _url = $"https://www.google.com";
        private static readonly string _dir = "screenshots";

        private static async Task Main(string[] args)
        {
            if (!Directory.Exists(_dir)) { _ = Directory.CreateDirectory(_dir); }

            using (var playwright = await Playwright.CreateAsync())
            {
                var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
                {
                    Channel = "msedge",
                    Headless = false,
                });

                var page = await browser.NewPageAsync();
                await page.SetViewportSizeAsync(1024, 1024);
                _ = await page.GotoAsync(_url);

                // Ctrl + プラス
                var keys = "^{ADD}";
                SendKeys.SendWait(keys);
                await page.ScreenshotAsync(new PageScreenshotOptions { Path = Path.Combine(_dir, @"110%.png") });
                SendKeys.SendWait(keys);
                await page.ScreenshotAsync(new PageScreenshotOptions { Path = Path.Combine(_dir, @"125%.png") });
                SendKeys.SendWait(keys);
                await page.ScreenshotAsync(new PageScreenshotOptions { Path = Path.Combine(_dir, @"150%.png") });
                SendKeys.SendWait(keys);
                await page.ScreenshotAsync(new PageScreenshotOptions { Path = Path.Combine(_dir, @"175%.png") });
                SendKeys.SendWait(keys);
                await page.ScreenshotAsync(new PageScreenshotOptions { Path = Path.Combine(_dir, @"200%.png") });

                await browser.DisposeAsync();
            }
        }
    }
}
```

## 解説
調査開始当初、`Selenium` にいい感じのAPIが実装されていることを期待しました。
しかし、後述する「うまくいかなかった方法」にある通り色々苦戦したため、
`.NET` で提供されている`SendKey`クラスを採用しました。
※`C#` 以外の言語でも同等の処理が用意されているはず。
* Q. `SendKey`は安定しないんじゃないの？
A. はい、安定しません。対象ブラウザがアクティブになっていないと`SendKey`は失敗します。そのため、採用を避けたいところではありますが、`SendKey`するタイミングはブラウザ起動直後のみなので、テスト全体の安定感は担保できると考えています。また、他にうまいやり方があればよかったのですが、私は見つけることができませんでした。

* Q. 細かな拡大率検証ができないんじゃないの？（101%、102%、103% …）
A. はい、100%、110%、125%、150% … 500% といった具合に、拡大率変更のショートカットで変更可能な範囲に限られます。ですが、Webアプリケーションのユーザもその範囲でのみ拡大率変更を行うことがほとんどだと考えられます。1%単位の拡大率検証は本当に必要かどうか、考え直してみてください。

* Q. Headless モードでブラウザ拡大率検証はできない？
A. はい、できません。Headless モードで拡大率検証は必要でしょうか？考え直してみてください。

* Q. IE でもできますか？
A. いいえ、できません。IE での検証は必要でしょうか？考え直してみてください。

# うまくいかなかった方法
いろいろ調査しましたが、うまくいかなかった方法を紹介します。
ひと工夫でよりうまいやり方に化けるかもしれません。

## 1. キーエミュレーション
`Control` キーを押下しながら `+`/`-` キーを押下することでブラウザ拡大率の操作する方法を試みました。
ですが、以下の通りうまくいきませんでした。

### 1.1. SeleniumのActions
`Selenium` の `OpenQA.Selenium.Interactions.Actions` でキー操作を再現することができます。
※詳しくは[こちら](https://www.selenium.dev/ja/documentation/webdriver/actions_api/keyboard/)

ですが、`Ctrl` + `+` を送信してもブラウザ拡大率を変更することができませんでした。
どうやら、HTMLドキュメントに対して有効なキー操作はできるようですが、
ブラウザ機能に割り当てられたショートカットを送っても動作させることはできないようです。
```cs
var actions = new Actions(driver)
    .KeyDown(Keys.Control)
    .SendKeys(Keys.Add)
    .Build();

// 期待する動作をしてくれない
actions.Perform();
```
他にも試してみたけど期待する動作をしてくれなかったショートカット：
* `Ctrl` + `0` （ブラウザの拡大率を 100% にするショートカット）
* `Alt` + `D` （ブラウザの検索バーにフォーカスするショートカット）

### 1.2. PlaywrightのKeyboard
`Playwright` の `Microsoft.Playwright.IKeyboard` でキー操作を再現することができます。
※詳しくは[こちら](https://playwright.dev/dotnet/docs/api/class-keyboard)

ですが、結果は `Selenium` と同様でうんともすんとも行きませんでした。
※ソースコードは省略

以下の Issue にある通り、未対応のようです：
[[Feature] Support browser zoom #2497](https://github.com/microsoft/playwright/issues/2497)

## 2. マウスエミュレーション
`Control` キーを押下しながらマウスホイールすることでブラウザ拡大率の操作する方法を試みました。
ですが、以下の通りうまくいきませんでした。

### 2.1. SeleniumのActions
以下のソースで検証しましたが、期待する動作をしてくれませんでした。
```cs
var actions = new Actions(driver)
      .KeyDown(Keys.Control)
      .ScrollByAmount(0, 1000)
      .Build();

// 期待する動作をしてくれない
actions.Perform();
```

### 2.2. PlaywrightのMouse
`Playwright` の `Microsoft.Playwright.IMouse` でマウス操作を再現することができます。
※詳しくは[こちら](https://playwright.dev/dotnet/docs/api/class-mouse)

ですが、結果は `Selenium` と同様でうんともすんとも行きませんでした。
※ソースコードは省略

以下の Issue にある通り、未対応のようです：
[[Feature] Simulate touchpad pinch-to-zoom #12263](https://github.com/microsoft/playwright/issues/12263)

## 3. javascriptによる拡大率変更
`Selenium` 及び `Playwright` によって任意の `javascript` を実行できます。
以下のスクリプトを実行して拡大率を変更しようという試みです：
```js
document.body.style.zoom = '110%';
```
ですが、ブラウザ機能による拡大率変更時とは異なる結果が得られたので不採用としました。
※`iframe` がうまく拡大されてくれません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432322/5126f2d0-b164-b1de-78b6-3d62a954eb7b.png)

`javascript` をあれこれこねくりまわすと実現できるのかもしれませんが、
あんまり汎用的なものを作れるイメージが無かったので試行停止しました。

## 4. ブラウザの起動オプション
`Selenium` 及び `Playwright` ではブラウザの起動オプションを指定することができます。
そこで、拡大率を指定するものが無いかどうか調べたのですが、それらしいものは見つけられませんでした。
参考までに、拡大率やブラウザサイズに関わるオプションを載せておきます：
* `--start-maximized`
起動時にウィンドウを最大化します。
* `--start-fullscreen`
起動時にウインドウを全画面表示します。F11キーでの全画面表示と同じ表示となります。
* `--force-device-scale-factor={倍率}`
起動時にブラウザそのものの倍率を変更します。
OSの解像度を無視してブラウザのスケールを指定できるオプションのようです。
試しに倍率`2`で指定してみるとこんな感じになります：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432322/2cdb7a5d-d752-ef7f-c71e-77fa21a4eb70.png)

# おわりに
もっとうまい方法があれば教えて下さい。