待機戦略

ブラウザ自動化における最も一般的な課題は、特定の Selenium コマンドを意図したとおりに実行するために、Web アプリケーションが適切な状態になっていることを保証することでしょう。プロセスはしばしば競合状態に陥り、ブラウザが最初に正しい状態になる場合(意図したとおりに動作する)と、Selenium コードが最初に実行される場合(意図したとおりに動作しない)があります。これは、不安定なテストの主な原因の 1 つです。

すべてのナビゲーションコマンドは、ページ読み込み戦略(待機するデフォルト値は "complete")に基づいて特定の readyState 値になるのを待ってから、ドライバがコードに制御を返します。readyState は HTML で定義されたアセットの読み込みのみに関係しますが、読み込まれた JavaScript アセットは多くの場合、サイトの変更を引き起こし、操作する必要のある要素は、コードが次の Selenium コマンドを実行する準備ができたときにはまだページ上に存在しない可能性があります。

同様に、多くのシングルページアプリケーションでは、要素はクリックに基づいて動的にページに追加されたり、表示/非表示が切り替わったりします。Selenium が要素を操作するためには、要素がページ上に存在し、かつ表示されている必要があります。

例として、こちらのページをご覧ください: https://selenium.dokyumento.jp/selenium/web/dynamic.html 「Add a box!」ボタンをクリックすると、存在しない「div」要素が作成されます。「Reveal a new input」ボタンをクリックすると、非表示のテキストフィールド要素が表示されます。どちらの場合も、遷移には数秒かかります。Selenium コードがこれらのボタンのいずれかをクリックし、結果の要素を操作する場合、その要素の準備が整う前に操作しようとして失敗します。

多くの人が最初に行う解決策は、コードの実行を一定時間一時停止させる sleep ステートメントを追加することです。コードは正確にどれくらい待つ必要があるかを知ることができないため、sleep 時間が十分でないと失敗する可能性があります。あるいは、値を高く設定しすぎ、必要なすべての場所に sleep ステートメントを追加すると、セッションの時間が法外に長くなる可能性があります。

Selenium は、より優れた同期のための 2 つの異なるメカニズムを提供しています。

暗黙的な待機

Selenium には、暗黙的な待機と呼ばれる要素を自動的に待機する組み込みの方法があります。暗黙的な待機時間は、ブラウザオプションのタイムアウト機能、またはドライバメソッド(下記参照)のいずれかで設定できます。

これは、セッション全体のすべての要素位置特定呼び出しに適用されるグローバル設定です。デフォルト値は 0 で、これは要素が見つからない場合、すぐにエラーを返すことを意味します。暗黙的な待機時間が設定されている場合、ドライバは提供された時間の長さだけ待機してからエラーを返します。要素が見つかり次第、ドライバは要素参照を返し、コードは実行を継続するため、暗黙的な待機時間を長くしても、必ずしもセッションの時間が長くなるわけではないことに注意してください。

警告: 暗黙的な待機と明示的な待機を混在させないでください。そうすると、予測できない待機時間が発生する可能性があります。たとえば、暗黙的な待機を 10 秒、明示的な待機を 15 秒に設定すると、20 秒後にタイムアウトが発生する可能性があります。

暗黙的な待機を使用した例の解決策は次のようになります。

    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
    driver.implicitly_wait(2)
            driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);
    driver.manage.timeouts.implicit_wait = 2
        await driver.manage().setTimeouts({ implicit: 2000 });

明示的な待機

明示的な待機は、アプリケーションをポーリングして、ループを終了してコード内の次のコマンドに進む前に、特定の条件が true と評価されるのを待つためにコードに追加されるループです。指定されたタイムアウト値までに条件が満たされない場合、コードはタイムアウトエラーを出します。アプリケーションが目的の状態にならない理由はたくさんあるため、明示的な待機は、必要な場所ごとに待機する正確な条件を指定するのに最適です。もう 1 つの優れた機能は、デフォルトで、Selenium Wait クラスは指定された要素が存在するのを自動的に待機することです。

この例は、待機される条件をラムダとして示しています。Java はExpected Conditionsもサポートしています。

    Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(2));
    wait.until(d -> revealed.isDisplayed());

この例は、待機される条件をラムダとして示しています。Python はExpected Conditionsもサポートしています。

    wait = WebDriverWait(driver, timeout=2)
    wait.until(lambda d : revealed.is_displayed())
            WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
            wait.Until(d => revealed.Displayed);
    wait = Selenium::WebDriver::Wait.new
    wait.until { revealed.displayed? }

JavaScript もExpected Conditionsをサポートしています。

        await driver.wait(until.elementIsVisible(revealed), 2000);

カスタマイズ

Wait クラスは、条件の評価方法を変更するさまざまなパラメータでインスタンス化できます。

これには以下が含まれます。

  • コードが評価される頻度を変更する (ポーリング間隔)
  • 自動的に処理する必要がある例外を指定する
  • 合計タイムアウト時間を変更する
  • タイムアウトメッセージをカスタマイズする

たとえば、要素がインタラクションできないエラーがデフォルトで再試行される場合、実行されるコード内のメソッドにアクションを追加できます(成功した場合にコードが true を返すようにするだけで済みます)。

Java で待機をカスタマイズする最も簡単な方法は、FluentWait クラスを使用することです。

    Wait<WebDriver> wait =
        new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(2))
            .pollingEvery(Duration.ofMillis(300))
            .ignoring(ElementNotInteractableException.class);

    wait.until(
        d -> {
          revealed.sendKeys("Displayed");
          return true;
        });
    errors = [NoSuchElementException, ElementNotInteractableException]
    wait = WebDriverWait(driver, timeout=2, poll_frequency=.2, ignored_exceptions=errors)
    wait.until(lambda d : revealed.send_keys("Displayed") or True)
            WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2))
            {
                PollingInterval = TimeSpan.FromMilliseconds(300),
            };
            wait.IgnoreExceptionTypes(typeof(ElementNotInteractableException));

            wait.Until(d => {
                revealed.SendKeys("Displayed");
                return true;
            });
    errors = [Selenium::WebDriver::Error::NoSuchElementError,
              Selenium::WebDriver::Error::ElementNotInteractableError]
    wait = Selenium::WebDriver::Wait.new(timeout: 2,
                                         interval: 0.3,
                                         ignore: errors)

    wait.until { revealed.send_keys('Displayed') || true }