ブラウザ自動操作API入門: WebDriver APIとChrome DevTools Protocol(CDP)
ウェブブラウザを自動操作する際には、WebDriverやChrome DevTools Protocol (CDP) などのAPIが広く利用されています。
これらのAPIを基盤に構築された様々なブラウザ自動操作フレームワークが、テスト自動化の分野で重要な役割を果たしています。
例えば、SeleniumやPlaywrightといったフレームワークを利用して、テストの自動化に取り組まれている方もいらっしゃると思います。
私もテスト自動化フレームワークの便利さを享受する一方で、フレームワークを介さずにブラウザを自動操作する方法についての興味がわいてきました。
そこで、この記事ではWebDriverやCDPが提供するAPIを直接利用してブラウザを操作する方法を基礎から探求してみることにしました。
これにより、私たちが普段利用しているフレームワークの背後にある原理を理解し、より深い知見を得ることを目指しています。
想定する読者
- SeleniumやPlaywrightなどを業務で使用しており、ブラウザがどのように操作されているかに興味を持っている方。
- WebDriver APIとChrome DevTools Protocolの相違点を理解したい方。
1.WebDriver APIとは
WebDriverは、Webブラウザを外部から操作するためのインターフェースであり、Webアプリケーション等のテスト自動化を可能にします。
Simon Stewart[1]が中心となって開発されたWebDriverがSeleniumプロジェクトに統合され、2018年の6月5日より仕様がW3Cの公式な標準として承認されました。 ここで定められた仕様に基づき、現在では各ブラウザベンダーがWebDriverを開発しています(個人のアイデアが標準化され、最終的には大きな影響を及ぼす過程は、筆者にとって非常に魅力的です)。
WebDriver APIは、SeleniumやAppium、WebDriverIOなどのフレームワークで使用されます。このAPIはHTTPリクエストを介してブラウザと通信し、操作を行います。以下の図がそのプロセスを示しています。
一般的に、WebDriver APIを使用すると、テストで「Flaky」(不安定)な挙動が生じやすいとされています。
これは、HTTPリクエストによる操作の性質に起因します。
ブラウザ操作は非同期で行われるため、リクエスト送信時とブラウザの状態変化時のタイミングのズレが生じることがあります。
また、ブラウザの内部状態に直接アクセスせず、HTTPリクエストを通じて指示を送るため、ページの完全なロード状態やJavaScriptの実行状況などを正確に把握するのが難しいです。
2.Chrome DevTools Protocol(CDP)とは
Chrome DevToolsは、もともとChromiumベースのブラウザ用に、デバッグやパフォーマンス測定を目的として開発されました。
このツールはHTML、CSS、JavaScriptの編集や最適化、ネットワーク活動の監視、ブラウザのパフォーマンス分析など多岐にわたる機能を備えています。
Chrome DevToolsの歴史に関する詳細は、こちらのブログ記事を参照してください(ただし、記事内の画像リンクが切れている点に注意が必要です)。
元々はブラウザ内でのみ使用可能だったChrome DevToolsの機能が、Chrome DevTools Protocol(CDP)により、ブラウザ外からのアクセスも可能になりました。
これにより、ブラウザの動作をプログラムによって制御することができるようになり、自動テストの用途にも利用可能となりました。
ただし、CDPは元々テスト用に設計されていなかったため、その機能はブラウザのバージョンによって大きく異なる可能性があります。
CDPは、主にPuppeteerやPlaywright[2]などのツールで利用されています[3]。
これらのツールはWebSocketを介してブラウザと通信し、リアルタイムの双方向通信を可能にします。
これにより、HTTPリクエストに比べてより迅速なブラウザとのインタラクションが可能となっています。
さらに、CDPはもともとブラウザの内部機能にアクセスする目的で開発されたため、WebDriver APIよりも詳細な操作が実行可能です。
しかし、CDPはブラウザごとに固有のプロトコルを持っているため、WebDriver APIのように標準的で汎用的なものではありません。
本記事では自動操作APIの入門として、ブラウザ内部への詳細なアクセス方法には触れていません。
3.WebDriverとCDP APIを使用したブラウザ操作の実践
今回は、Google Chromeを対象に、WebDriverとChrome DevTools Protocol (CDP) のAPIを直接呼び出し、以下の操作を行います。
⓪Chromeの起動
①Googleのトップページへのアクセス
②検索窓に「Test Automation」と入力
③検索ボタンのクリック
④スクリーンショットの取得
⑤Chromeの終了
HTTP通信だけでなくWebSocketにも対応しているため、今回は両方のAPIを簡単に呼び出すことができるPostmanを使用します [4]
3.1.WebDriver APIを用いたブラウザ操作
今回の主な目的はWebDriver APIの検証ですが、参考としてWebDriver APIを使用したフレームワークでの同様の操作の例を、Seleniumのソースコードを用いて示します。
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class GoogleSearchTest {
@Test
public void testGoogleSearch() throws IOException {
// WebDriverの設定
System.setProperty("webdriver.chrome.driver", "path/to/your/chromedriver");
WebDriver driver = new ChromeDriver();
try {
// ①Googleのトップページへのアクセス
driver.get("https://www.google.com");
// ②検索窓に「Test Automation」と入力
WebElement searchBox = driver.findElement(By.name("q"));
searchBox.sendKeys("Test Automation");
// ③検索ボタンのクリック
WebElement searchButton = driver.findElement(By.name("btnK"));
searchButton.click();
// 検索結果が表示されるまで待機
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("search")));
// ④スクリーンショットの取得
File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(scrFile, new File("screenshot.png"));
} finally {
// ⑤Chromeの終了
driver.quit();
}
}
}
メソッドが豊富で、簡潔なソースコードで実装可能です。
次に、WebDriver APIを直接利用して同様の操作を行ってみましょう。
⓪Chromeの起動
WebDriver APIを使用する際は、まず各ブラウザに対応するWebDriverを用意する必要があります。
今回はChromeを操作するため、以下のリンクから自分のOS及びChromeのバージョンに合ったChromeDriverをダウンロード[5]し、解凍後にChromeDriverを起動します。
./chromedriver
Starting ChromeDriver 119.0.6045.105 (38c72552c5e15ba9b3117c0967a0fd105072d7c6-refs/branch-heads/6045@{#1103}) on port 9515
Only local connections are allowed.
Please see https://chromedriver.chromium.org/security-considerations for suggestions on keeping ChromeDriver safe.
ChromeDriver was started successfully.
9515ポートでの起動を確認後、WebDriver APIを叩いてChromeを起動します。
起動用のAPIの詳細はこちらを参照してください。
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。
※HTTPを選択してください(以下同様です)
POST http://localhost:9515/session
{
"capabilities": {
"browserName": "chrome"
}
}
真っ白な画面が表示されましたが、Chromeが無事に起動されたことが確認できます!
レスポンスとして返されるsessionIdは、立ち上げたChromeを操作する上で重要ですので、メモしておきましょう。
"sessionId": "60c4ecbe174db2e35a37ac8f1a058f14"
①Googleのトップページへのアクセス
次に、URLを指定してGoogleのトップページに遷移します。
遷移用のAPIの詳細はこちらを参照してください。
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。{{SESSION}}部分は先ほど取得したsessionIdを入力してください(Postmanの変数を活用する方法についてはこちらを参照してください)。
POST http://localhost:9515/session/{{SESSION}}/url
{
"url": "https://www.google.co.jp/"
}
Googleのトップページに無事遷移しました!
②検索窓に「Test Automation」と入力
次に、検索窓に「Test Automation」と入力します。
WebDriver APIを直接使用する場合は、以下の2段階が必要です。
- 操作対象の要素の値を取得
- 取得した値を基に操作を実行
まずは対象要素の値の取得を行います。
要素の値取得用APIの詳細はこちらを参照してください。
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。
POST http://localhost:9515/session/{{SESSION}}/element
{
"using": "css selector",
"value": "[name=\"q\"]"
}
以下のように指定した要素が取得できました。
この要素に対して「Test Automation」と入力する操作を行います。
{
"value": {
"element-6066-11e4-a52e-4f735466cecf": "9B3950F7587477160BB546EC942FF8F4_element_5"
}
}
要素に対する値入力用のAPIの詳細はこちらを参照ください。
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。
{{SEARCH_INPUT_ELEMENT}}の部分は先ほど取得した要素のvalueを入力してください。
POST http://localhost:9515/session/{{SESSION}}/element/{{SEARCH_INPUT_ELEMENT}}/value
{
"text": "Test Automation"
}
「Test Automation」という文字が検索窓に入力されました!
③検索ボタンのクリック
検索窓への入力が完了したので、次は検索ボタンをクリックします。
これも②の時と同様に、まずは操作対象の要素を取得し、その後実際の操作を行います。
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。
POST http://localhost:9515/session/{{SESSION}}/element
{
"using": "css selector",
"value": "[class=gNO89b]"
}
{
"value": {
"element-6066-11e4-a52e-4f735466cecf": "9B3950F7587477160BB546EC942FF8F4_element_12"
}
}
指定した要素が取得できたので、この要素をクリックして検索を実行します。
要素のクリック用APIの詳細はこちら
PostmanでURLとBodyを以下のように設定し、「Send」ボタンを押します。
POST http://localhost:9515/session/{{SESSION}}/element/{{SEARCH_CLICK_ELEMENT}}/click
{}
検索結果が表示されました!
④スクリーンショットの取得
検索結果に関してスクリーンショットを取得します。WebDriver APIの仕様により、ブラウザに表示されている部分のみを撮影します。
スクリーンショット取得用APIの詳細はこちらを参照してください。
PostmanでURLを以下のように設定し、「Send」ボタンを押します。
GET http://localhost:9515/session/{{SESSION}}/screenshot
取得されたスクリーンショットはBase64形式で返されます。
これをテキストファイル(例:screenshot.txt)に保存し、次のコマンドで画像にデコードします。
certutil -f -decode screenshot.txt screenshot.png
base64 -D -i screenshot.txt -o screenshot.png
以下の画像のように、正しくスクリーンショットが撮影されていることを確認できました!
⑤Chromeの終了
最後にChromeを終了します。
ブラウザ終了のAPIの詳細はこちらを参照ください。
PostmanでURLを以下のように設定し、「Send」ボタンを押します。
DELETE http://localhost:9515/session/{{SESSION}}
無事にChromeが終了しました!
Seleniumのソースではわずか数行のことですが、かなりの数のAPIを叩く必要がありここまで操作するのに中々骨が折れました…。
3.2.Chrome DevTools Protocol (CDP) を用いたブラウザの操作
次に、CDPを直接活用して同様の操作を試みます。
このプロセスを理解するために、CDPを利用するフレームワークの一例として、Playwrightでの操作の記述方法を紹介します。
const { chromium } = require('playwright');
(async () => {
// ⓪Chromeの起動
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// ①Googleのトップページへのアクセス
await page.goto('https://www.google.com');
// ②検索窓に「Test Automation」と入力
await page.fill('input[name=q]', 'Test Automation');
// ③検索ボタンのクリック
await page.click('input[class=gNO89b]');
// 検索結果が表示されるまで待機
await page.waitForSelector('div[id=search]');
// ④スクリーンショットの取得
await page.screenshot({ path: 'screenshot.png' });
// ⑤Chromeの終了
await browser.close();
})();
こちらも豊富なメソッドを活用して、シンプルなソースコードで実装が可能でした。
実装時にWebSocketなどの詳細を意識する必要がない点も、非常に便利な特徴です。
⓪Chromeの起動
Chrome DevTools Protocol(CDP)を使用してブラウザを操作するためには、まずremote-debugging-portを指定してChromeをリモートデバッグモードで起動する必要があります。
以下はWindowsとMacでの起動コマンドです(Chromeのインストールパスに応じて適宜変更してください)。
なお、既にChromeが開いている場合は、リモートデバッグモードで起動できないため、事前にすべてのChromeを閉じてください。
C:\Program Files\Google\Chrome\Application\chrome.exe --remote-debugging-port=9222
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
Chromeがリモートデバッグモードで起動したら、Postmanを使用して新しいタブを生成します。
以下のURLを設定し、「Send」ボタンをクリックします(HTTPを選択)。
POST http://localhost:9222/json/new
新しいタブが開かれ、レスポンスとしてwebSocketDebuggerUrlが返されます。
このURLはWebSocket通信時に必要ですので、メモしておくと良いでしょう。
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3"
Postmanで取得したwebSocketDebuggerUrlを設定し、「Connect」ボタンを押します(WebSocketを選択)。
以下のように「Connected to」のメッセージが表示されれば成功です。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
①Googleのトップページへのアクセス
次に、URLを指定して、Googleのトップ画面に遷移していきます。
遷移用のAPIの詳細はこちらを参照してください。
ConnectしたURLに対して以下のメッセージを設定し、「Send」ボタンを押します。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
{
"id": 1,
"method": "Page.navigate",
"params": {
"url": "https://www.google.com"
}
}
無事にGoogleのトップページに遷移したことが確認できます。
②検索窓に「Test Automation」と入力
検索窓に「Test Automation」と入力する操作を実現するために、CDPを用いてJavaScriptを直接実行します。
PuppeteerやPlaywrightと異なり、ここでは要素の操作前後の状態チェックやイベント確認は行わず、JavaScriptの直接実行に焦点を当てます。
Postmanで以前に確立したWebSocket接続を使い、以下のメッセージを設定して「Send」ボタンを押します。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
{
"id": 2,
"method": "Runtime.evaluate",
"params": {
"expression": "document.querySelector('[name=\"q\"]').value = 'Test Automation';"
}
}
WebDriver APIとは異なり、同じ接続を保持しつつ、メッセージ内容を変更することで異なる操作を実行できます。
結果として、「Test Automation」という文字が検索窓に正しく入力されました!
③検索ボタンのクリック
次に、検索ボタンをクリックする操作もJavaScriptを用いて実行します。
Postmanで同じURLに対し、以下のメッセージを設定して「Send」ボタンを押します。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
{
"id": 3,
"method": "Runtime.evaluate",
"params": {
"expression": "document.querySelector('input[name=\"btnK\"]').click();"
}
}
この操作により、検索結果が正常に表示されました!
④スクリーンショットの取得
ここでは、表示されているブラウザ画面のスクリーンショットを取得します。
CDPは画面外の領域も含めたフルスクリーンショットの取得が可能ですが、今回は表示部分のみを対象にします。
スクリーンショット取得用のAPIの詳細はこちらを参照してください。
Postmanで確立したWebSocket接続を使用し、以下のメッセージを設定して「Send」ボタンを押します。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
{
"id": 4,
"method": "Page.captureScreenshot",
"params": {
"format": "png"
}
}
結果はBase64形式で返されます。この値をテキストファイル(例: screenshot.txt)に保存し、以下のコマンドで画像にデコードします。
certutil -f -decode screenshot.txt screenshot.png
base64 -D -i screenshot.txt -o screenshot.png
以下の画像のように、スクリーンショットが正確に撮影されています!
⑤Chromeの終了
最後にChromeを終了します。
ブラウザ終了のAPIの詳細はこちらを参照ください。
Postmanで確立したWebSocket接続を用いて、以下のメッセージを設定し、「Send」ボタンを押します。
ws://localhost:9222/devtools/page/B6C9CCB16F01267FFE17634D653B43D3
{
"id": 5,
"method": "Browser.close"
}
Chromeが無事終了し、WebSocket接続が切断されたことが確認できます。
WebSocketを使用して持続的なコネクションを確立し、CDPを通じて異なるメッセージを送ることで、ブラウザの操作が自動化できることが示されました。
Playwrightのようなフレームワークでは数行のコードで実現可能な操作が、CDPを直接用いた場合にはより多くの手順と労力を要することが明らかになりました。
具体的には、リモートデバッギングモードの設定、WebSocketコネクションの確立、そして各APIへのリクエスト送信などが必要です。
4.まとめ
この入門編では、基本的な操作のみを紹介しましたが、WebDriver APIとChrome DevTools Protocol (CDP) を使用してブラウザ操作を実際に体験することで、日常的に利用しているフレームワークが背後でどのようなAPIを利用しているかの具体的な理解を深めることができました。
また、これらのフレームワークがどれだけ多くの処理をカバーし、テスト実装の効率を高めてくれているかを実感しました。
今回紹介した内容以外にも、多くの興味深いAPIが存在します。
詳細については、W3CのWebDriver仕様やChrome DevTools Protocolの仕様をご覧ください。
特にCDPは、まだ多くが実験段階にありますが、実際に触れてその動作を確認することをお勧めします。
さらに、WebDriver APIとCDPの長所を組み合わせた新しいAPI、WebDriver BiDi(バイダイと呼ばれます)の策定も進行中です。
2023年5月時点でのWebDriver BiDiに関する概況はこちらの記事で確認できます。
このトピックについても、別の記事で詳細をご紹介する予定です。
テスト自動化技術の進展は日々新しい発見があり、その追跡は常にワクワクするものです。
それでは、皆様にとって充実したテストの時間を。Happy Testing!
仲間募集
Accenture Japan QE&Aでは、テスト自動化を通じてお客様の変革を支援しています。
同じビジョンを共有する仲間を募集しています!
-
Simon Stewartが初期のWebDriverについて語る動画は、現在もYouTubeのGoogle公式チャンネルで視聴可能です。 ↩︎
-
Chromiumベースのブラウザ以外にも、PuppeteerはFirefox、PlaywrightはFirefoxとSafariに対応しています。これらはCDP以外のプロトコルも活用しているようです。異なるブラウザやプロトコルをどのように統合してフレームワークを構築しているのかに関する詳細な分析は、今後の調査を経てブログで紹介する予定です。 ↩︎
-
実はSelenium4やWebDriverIOも、CDPを用いた操作を一部フレームワーク内に統合しています。このような進展についても、詳細な分析を行い、後日ブログ記事で取り上げる予定です。 ↩︎
Discussion