【PHP】Seleniumで要素が現れるまで待機する方法

SeleniumでWebブラウザの自動操作を行いながらスクレイピングを行う際、ページ遷移後に検索したい要素が現れていないのにFindElementしようとしてエラーが出ました。
一番簡単な解決方法はsleep処理で任意の秒数待機させるだと思うのですが、これだとスクレピングコードを実行するマシンの性能によって待機時間に過不足が発生する可能性が高いです。

# Pythonならできるらしい
しばらくネットの海を漂っていると、素晴らしい記事を見つけました。
[Seleniumを安定稼働させるために行うべき３つの設定](https://tanuhack.com/stable-selenium/)
PythonでSeleniumを利用する際は、指定した要素が表示されるまで待機する関数の他、待機系の関数が多数用意されているようです。
PHPにもこのような関数は用意されているのでしょうか?私は見つけることが出来なかったのですが、もしあれば教えていただきたいです。

# 要素が現れるまで待機する関数を作ってみた
ということで本題に入りますが、今回はPHPで要素が現れるまで待機する関数を作りました。作ったといっても要素がまだ描画されていない時にfindElementしようとして起きるエラーをtry-catchで処理して、現れるまで待機させるだけのゴリ押しコードです。
美しくないですが、一応私がやりたかったことは実現できたので掲載します。今回はPHPで書いていますが、シンプルなコードなので別の言語でも書き換えられると思います。(私はVisualBasicでも書きました。)

```php
//引数１：ドライバー
//引数２；検索する要素('id','name','xpath','css')など
//引数３:検索する要素の文字列
//引数４：最大待機時間(秒)
//返り値：検索した要素(見つからなかった場合はNULLを返す)
function wait_find_element($driver, $by, $string, $max)
{
    $flag = FALSE;
    $count = 0;
    while ($flag == FALSE) {
        try {
            switch ($by) {
                //ここのcaseは任意で増減させてね
                case 'id':
                    $element = $driver->findElement(WebDriverBy::id($string));
                    $flag = TRUE;
                    break;
                case 'name':
                    $element = $driver->findElement(WebDriverBy::name($string));
                    $flag = TRUE;
                    break;
                case 'xpath':
                    $element = $driver->findElement(WebDriverBy::xpath($string));
                    $flag = TRUE;
                    break;
                case 'css':
                    $element = $driver->findElement(WebDriverBy::cssSelector($string));
                    $flag = TRUE;
                    break;
                default:
                    echo 'error';
            }
        } catch (Exception $e) {
            sleep(1); //処理時間もあるので厳密には1秒以上になるが、その正確性はいらないと判断
            $count += 1;
            if ($count == $max) {
                echo $e->getMessage();
                $element = NULL;
                $flag = TRUE;
            }
        }
    }
    return $element;
}
```


```php5
//例えば、下記のような要素が見つかるまで最大10秒待機させる場合、
$element = $driver->findElement(WebDriverBy::id('sample'));
//次のように記述
$element = wait_find_element($driver, 'id', 'sample', 10);
```

# 実際使ってみる
今回は、簡単なサンプルとしてQiitaのHP(https://qiita.com/) の検索欄から「PHP」を検索し、検索結果のページ内で1番上の記事のタイトルを出力するプログラムを作りました。
## サンプルプログラム
```php
<?php
require_once(__DIR__ . '/vendor/autoload.php');

use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverKeys;
use Facebook\WebDriver\Chrome\ChromeDriver;

putenv("webdriver.chrome.driver=" . __DIR__ . "/chromedriver"); //windowsは.exe

$driver = ChromeDriver::start();

$driver->get('https://qiita.com/'); //QiitaのHPへ

// 「PHP」で検索
$element = $driver->findElement(WebDriverBy::name('q')); //入力欄
$element->sendKeys('PHP'); // 「PHP」と　入力
$driver->getKeyboard()->pressKey(WebDriverKeys::ENTER); //Enterキーを押す

//ここでwait処理 - ページ遷移後、検索したい要素(xpath)が現れるまで最大5秒待機
$element = wait_find_element($driver, 'xpath', '//*[@id="main"]/div/div[1]/div[2]/div[2]/h1/a', 5);
if (isset($element)) {
    echo $element->getText(); //見つかったら記事のテキストを出力
} else {
    echo '要素が見つかりません';
}
$driver->close();
```

## 実行結果
![スクリーンショット 2021-12-18 23.50.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2397215/0973d001-3286-8d55-8f0b-02e58e03796c.png)
![スクリーンショット 2021-12-18 23.50.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2397215/6cfab02d-8336-a8e6-1a3a-5c918ca8f751.png)


```
>> 【PHP】PHPでREPLを使うには？
```
上手くいきました！

ちなみに存在しない要素を故意に検索した結果はこちら

```
>> The driver server has died. 要素が見つかりません
```
最大待機時間待っても現れなかったため、きちんとエラー表示されていますね。

# まとめ
今回は、PHP+Seleniumで要素が現れるまで待機する方法を紹介しました。
大人しくPythonを使ってもいいのですが、私と同じようにPHP好きで困っている人の助けになれば幸いです。