AArch64(arm64)のLinuxでSeleniumを使う方法（C#版）

AArch64(arm64)のLinuxで(CUIでheadlessで)Seleniumを動かそうと思ったら
ひと手間必要だったのでまとめました。

:::note warn
この記事でのAArch64はすべてLinuxを表します。
厳密にはApple SiliconのmacOSもAArch64ですが
この記事の内容はmacOSには当てはまりません。
Linuxという表記を省略してAArch64と表記します。
（試してませんが、Apple SiliconのmacOSではこの記事に書いているひと手間がなくても簡単にSeleniumは動くはず）
:::

:::note warn
LinuxはRocky Linux 9を使用しているため
パッケージマネージャのコマンドはdnfで記載しています。
他のディストリビューションの場合はパッケージマネージャを読み替える、
そのパッケージマネージャでインストールできない場合は手動でインストールするなどしてください。
:::

# TL;DR
AArch64でSeleniumを動かすためには
* Chromeは無いのでChromiumをインストールする
* Selenium Managerでのドライバ自動インストールはできないので、
  手動でドライバをインストールしてパスを指定する

# 前提（Selenium Manager）
**通常（今回対象としているAArch64のLinux以外においては）**

詳細は[こちらの記事](https://devadjust.exblog.jp/29530356/)がわかりやすいのですが
以前はSeleniumを使うためには手動でブラウザごとのドライバをインストールする必要がありましたが、現在はSelenium Managerという仕組みで自動でドライバのダウンロードまで行ってくれます。

nugetから以下をインストールして
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/28ff9932-bfe4-8739-17c1-6c1736788f7d.png)
ブラウザがインストールされていれば、ドライバは意識せず以下のコードのみでSeleniumが動きます。
```csharp
using OpenQA.Selenium.Chrome;

ChromeDriverService service = ChromeDriverService.CreateDefaultService("");
ChromeOptions options = new();
options.AddArgument("--headless=new");
using ChromeDriver driver = new(service,options);
driver.Navigate().GoToUrl("https://domains.google.com/checkip");
driver.Quit();
```

`ChromeDriverService service = ChromeDriverService.CreateDefaultService("");`
のコードのところで自動的にブラウザに対応したドライバ（この例ではChromedriver）をダウンロードしてきてくれます。

# AArch64では
では、上記前提に従ってSeleniumを動かそうとしてみると
### Chromeは？
CUIで使っているLinuxには普通はブラウザ（Chrome）は入っていません。
インストールしようかと思って調べると分かるのですが、AArch64のChromeは配布されていません。

### 実行してみると
（Chromeをどうするのかは後でまとめて解説しますが）
ブラウザの問題が解決したとして、前述のソースコードを実行してみると
以下のようなエラーが出てしまいます。
```
Unhandled exception. OpenQA.Selenium.WebDriverException: Error starting process: /******/bin/Debug/net8.0/selenium-manager/linux/selenium-manager  --browser "chrome" --language-binding csharp --output json
 ---> System.ComponentModel.Win32Exception (8): An error occurred trying to start process '/******/bin/Debug/net8.0/selenium-manager/linux/selenium-manager' with working directory '/******'. Exec format error
   at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)
   at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
   at OpenQA.Selenium.SeleniumManager.RunCommand(String fileName, String arguments)
   --- End of inner exception stack trace ---
   at OpenQA.Selenium.SeleniumManager.RunCommand(String fileName, String arguments)
   at OpenQA.Selenium.SeleniumManager.BinaryPaths(String arguments)
   at OpenQA.Selenium.DriverFinder.BinaryPaths()
   at OpenQA.Selenium.DriverFinder.GetDriverPath()
   at OpenQA.Selenium.Chromium.ChromiumDriver.GenerateDriverServiceCommandExecutor(DriverService service, DriverOptions options, TimeSpan commandTimeout)
   at OpenQA.Selenium.Chromium.ChromiumDriver..ctor(ChromiumDriverService service, ChromiumOptions options, TimeSpan commandTimeout)
   at OpenQA.Selenium.Chrome.ChromeDriver..ctor(ChromeDriverService service, ChromeOptions options, TimeSpan commandTimeout)
   at OpenQA.Selenium.Chrome.ChromeDriver..ctor(ChromeDriverService service, ChromeOptions options)
   at Program.<Main>$(String[] args) in /******/Program.cs:line 6
```
このエラーの内容ですが、`selenium-manager/linux/selenium-manager`が`Exec format error`と言っています。
Selenium Managerが自動でドライバをダウンロードしてくると言いましたが
このSelenium Manager自体もバイナリであり、
ビルドした時点でまずはプログラムの出力先のselenium-managerというフォルダにダウンロードされます。
このフォルダにはWindows向け・macOS向け・Linux向けそれぞれのバイナリが入っているのですがLinux向けはx64のバイナリしか入っていません。
x64向けのバイナリなのでAArch64で実行されると`Exec format error`になっているというわけです。
試しにこのバイナリを単独で実行してもエラーになることが確認できます。
```
$ bin/Debug/net8.0/selenium-manager/linux/selenium-manager 
-bash: bin/Debug/net8.0/selenium-manager/linux/selenium-manager: cannot execute binary file: Exec format error
```
ちなみにSeleniumのサイトを見ると、現時点では今以上のアーキテクチャをサポートするつもりは無いようで、AArch64の場合の対応方法も記載されています。
https://www.selenium.dev/documentation/selenium_manager/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149348/5d39a07e-e025-e542-8313-f716665bf22f.png)

# どうすれば動くのか？
## ブラウザ（インストール）
ChromeはありませんがChromiumはあります。Chromiumはdnfからインストールできます。
```
$ sudo dnf install chromium
```
## ドライバ（インストール＆パス指定）
Selenium Managerで自動でドライバをインストールすることはできませんが
手動でインストールしてドライバのパスを指定することができます。
こちらもdnfでインストールできます。
```
$ sudo dnf install chromedriver
```
その上で、プログラム側ではSelenium Managerを使わせないように
インストール済みのドライバのパスを指定します。
パスを指定するとSelenium Managerでのドライバ自動インストールは動作しません。
```csharp
using OpenQA.Selenium.Chrome;

ChromeDriverService service = ChromeDriverService.CreateDefaultService("/usr/bin/chromedriver");
ChromeOptions options = new();
options.AddArgument("--headless=new");
using ChromeDriver driver = new(service,options);
driver.Navigate().GoToUrl("https://domains.google.com/checkip");
driver.Quit();
```
3行目の`ChromeDriverService service = ChromeDriverService.CreateDefaultService("/usr/bin/chromedriver");`の部分です。
変更する部分はここだけで、他は前出のプログラムと同じです。
dnfでドライバインストールした場合、通常は上記のパスになると思いますが
不明な場合は以下のコマンドで確認してください。
```
$ which chromedriver
/usr/bin/chromedriver
```
ちなみにブラウザのパスも`options.BinaryLocation`というプロパティで指定できますが、Chromiumの場合でも指定せずに動きました。