💨

Apple Silicon PC(M1 Mac)でLaravel Duskを動かす

2022/11/20に公開約6,000字

背景

Laravel DuskのテストをLaravelを組み込んだdockerのコンテナでphp artisan serveして行っていたのですが、
他開発者(IntelのMac)なら成功するのに、自分のM1 Macでは何故かエラーになりました。なぜぇええええと悶絶していると、どうもApple Silicon PCではエラーになってしまう時がありそうでした。
以下を実行したときにエラーになり、もしやと気づきました(必要なパッケージの一部がarm用でまだ無く、Chorme Driverが適切に動いていない?)。

$ ./vendor/laravel/dusk/bin/chromedriver-linux
qemu-x86_64: Could not open '/lib64/ld-linux-x86-64.so.2': No such file or directory

https://github.com/laravel/dusk/issues/899

https://tech.gunosy.io/entry/docker_chromium_with_m1

https://blog.savanna.io/entry/2021/12/06/182102

https://stackoverflow.com/questions/71040681/qemu-x86-64-could-not-open-lib64-ld-linux-x86-64-so-2-no-such-file-or-direc

https://stackoverflow.com/questions/71165982/cannot-run-selenium-chromedriver-on-m1-mac

色々試してseleniumのDockerコンテナを作成して、やっとうまく行ったので、次苦戦しないようにメモを残しておこうと思います。

環境

PC

機種 : MacBook Pro 2021(M1 Max)
OS : Monterey(12.2.1)

Laravel Sail

PHP version : 8.1.9
Laravel version : 9.25.1

※実際に問題にひっかかったのはphp:8.1.9-apacheのDockerイメージですが、同じものを準備するのが面倒なのでSailでやってみます。

Laravel Sailを立ち上げ

curlコマンドを打ち込みます。

curl -s "https://laravel.build/example-app?with=mysql,redis" | bash

seleniumのDockerコンテナを作成する

docker-compose.ymlを以下のようにしてみます。seleniarm/standalone-chromiumでないと動かないようなのでこちらを利用します。

ローカルマシンに Apple Silicon チップが搭載されている場合、seleniumサービスにはseleniarm/standalone-chromiumイメージを使用する必要があります。

https://readouble.com/laravel/9.x/ja/sail.html

example-app/docker-compose.yml
+    selenium:
+        image: 'seleniarm/standalone-chromium'
+        ports:
+            - '4444:4444'
+            - '7900:7900'
+            - '5900:5900'
+        volumes:
+            - '/dev/shm:/dev/shm'
+        networks:
+	    - sail

ちなみによ〜くみると

selenium
seleniarm

と微妙にスペルが異なります。

何が違うんだってばよ!と思い調べてみると、seleniarmはseleniumからフォークしてarm系でも動くようにし,
M1 MacでnoVNCを使えるようにしたdockerイメージのようです(解釈間違ってたらご指摘ください)。

https://github.com/seleniumhq-community/docker-seleniarm

Dockerコンテナ起動

コンテナを起動します。

$ cd example-app
$ ./vendor/bin/sail down
$ ./vendor/bin/sail up -d

DuskTestCase.phpを修正する

seleniumコンテナでテストを行うようにDuskTestCase.phpを修正して向き先を変更します。.env.dusk.{environment}というenvファイルを作成することもできるので、変数にしてそちらに記述しても良いです。

seleniumとlaravel.testはdocker-compose.ymlに記述したサービス名です。

example-app/tests/DuskTestCase.php
        return RemoteWebDriver::create(
-            $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
+            $_ENV['DUSK_DRIVER_URL'] ?? 'http://selenium:4444/wd/hub',
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY, $options
            )
        );
example-app/tests/DuskTestCase.php
+    protected function baseUrl()
+    {
+        return 'http://laravel.test:80';
+    }

最終的には次のようになります。

example-app/tests/DuskTestCase.php
<?php

namespace Tests;

use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;

abstract class DuskTestCase extends BaseTestCase
{
    use CreatesApplication;

    /**
     * Prepare for Dusk test execution.
     *
     * @beforeClass
     * @return void
     */
    public static function prepare()
    {
        if (! static::runningInSail()) {
            static::startChromeDriver();
        }
    }

    /**
     * Create the RemoteWebDriver instance.
     *
     * @return \Facebook\WebDriver\Remote\RemoteWebDriver
     */
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments(collect([
            $this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
        ])->unless($this->hasHeadlessDisabled(), function ($items) {
            return $items->merge([
                '--disable-gpu',
                '--headless',
            ]);
        })->all());

        return RemoteWebDriver::create(
            $_ENV['DUSK_DRIVER_URL'] ?? 'http://selenium:4444/wd/hub',
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY, $options
            )
        );
    }

    /**
     * Determine whether the Dusk command has disabled headless mode.
     *
     * @return bool
     */
    protected function hasHeadlessDisabled()
    {
        return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
               isset($_ENV['DUSK_HEADLESS_DISABLED']);
    }

    /**
     * Determine if the browser window should start maximized.
     *
     * @return bool
     */
    protected function shouldStartMaximized()
    {
        return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
               isset($_ENV['DUSK_START_MAXIMIZED']);
    }

    protected function baseUrl()
    {
        return 'http://laravel.test:80';
    }
}

テストを実行する

Duskに必要なパッケージをインストールします。

$ ./vendor/bin/sail composer require --dev laravel/dusk
$ ./vendor/bin/sail php artisan dusk:install

テストを実行して、成功すれば完了です。

$ ./vendor/bin/sail php artisan dusk
PHPUnit 9.5.22 #StandWithUkraine

.                                                                   1 / 1 (100%)

Time: 00:00.976, Memory: 22.00 MB

VNCとnoVNCでテストの途中状態を確認する

--headlessの部分をコメントアウトしておきます。これをしておかないとchromiumのブラウザが表示されません。

example-app/tests/DuskTestCase.php
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments(collect([
            $this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
        ])->unless($this->hasHeadlessDisabled(), function ($items) {
            return $items->merge([
                '--disable-gpu',
+//                '--headless',
            ]);
        })->all());

docker-compose.ymlで指定した5900と7900ポートは以下の方法でVNC、noVNCを利用できます。

https://qiita.com/suruseas/items/91177806910fb756a9f9#実行結果

https://qiita.com/yutachaos/items/4a1da5d55a3bf0df889e#vncserver

seleniumの画面を表示した状態で./vendor/bin/sail php artisan duskを実行すると途中状態を確認できます。

Discussion

ログインするとコメントできます