深入瞭解 ChromeDriver

首先回顧一下 ChromeDriver 的架構:

chromedriver/architecture.png

以 Python 為例,只要安裝好 ChromeDriver 後,透過下面的程式就可以將 Google Chrome 打開,接著用標準的 WebDriver API 操控:

>>> from selenium.webdriver import Chrome
>>> driver = Chrome()
>>> driver.get('http://www.wikipedia.org')

但這中間是發生了什麼事?ChromeDriver Server 跟 Chrome/Chromium 是怎麼被啟動的?它們之間又是怎麼溝通的?如果手動將 ChromeDriver Server 或 Chrome/Chromium 事先執行起來,它們之間要如何串接起來?

先從最簡單的狀況看起。(下面以 Python interpreter + Google Chrome + Mac OS X 做說明)

>>> import os
>>> os.getpid()                # 1
76077
>>>
>>> from selenium.webdriver import Chrome
>>> driver = Chrome()          # 2
>>> driver.service.service_url # 3
'http://localhost:58316'
>>> driver.quit()              # 4
1 取得 Python interpreter 自己的 PID,做為找尋 process 間父子關係的起點。
2 取得 driver 的當下,language binding 會先將 PATH 裡的 ChromeDriver executable 執行起來 (也就是 ChromeDriver Server),然後 server 又會將安裝在標準位置的 Google Chrome 執行起來,分別在不同的 port 服務。

透過 PID 跟 PPID,可以找出 driver = Chrome() 帶出了哪些 child process:

$ ps -o pid,ppid,command | grep 76077
76077 75686 python
76079 76077 chromedriver --port=58316
76374 76093 grep 76077

$ ps -o pid,ppid,command | grep 76079
76079 76077 chromedriver --port=58316
76081 76079 /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
--disable-background-networking --disable-client-side-phishing-detection
...
--remote-debugging-port=12632
76378 76093 grep 76079

其間的關係如下:(PID 後面跟著的是簡化過的 command line)

Python interpreter (PID:76077)
  `- ChromeDriver Server (PID:76079, chromedriver --port=58316)
     `- Google Chrome (PID:76081, Google Chrome --remote-debugging-port=12632)
$ lsof -nP -iTCP -sTCP:LISTEN
COMMAND     PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
...
chromedri 76079 jeremy    7u  IPv4 0xe1ab0daeda7da4a9      0t0  TCP 127.0.0.1:58316 (LISTEN)
Google    76081 jeremy   61u  IPv4 0xe1ab0daece0794a9      0t0  TCP 127.0.0.1:12632 (LISTEN)
3 取得 ChromeDriver Server 服務的位置,跟上面推導出來的 CHROME_DRIVER_PORT 一致。
4 關閉 driver 時,也會將自動帶出來的 ChromeDriver Server 跟 Google Chrome 一併關閉。
Tip

架構圖中一直沒有揭露一個細節 – ChromeDriver Server 在啟動 Google Chrome/Chromium 時,會自動安裝一個 Chrome Automation Extension:(可以透過 chrome://extensions/ 檢查)

under-the-hood/chrome-automation-extension.png

那是因為 –load-extension 要求安裝特定資料夾下的 extension。觀察資料夾的內容可以發現:(根據 ChromeDriver v2.14)

  • manifest.json – 從裡頭對於這個 extension 的描述看來 (Exposes extension APIs for automating Chrome),顯然跟 ChromeDriver 能夠操控 Google Chrome/Chromium 有關。

  • backgroup.js – 宣告了 captureScreenshot()getWindowInfo()updateWindow()launchApp(),可見這個 extension 跟操控視窗、網頁快照等功能有關。

官方文件也提到,若沒有安裝這個 extension (例如自行開啟 Google Chrome/Chromium 的狀況),"某些操作" 會失敗而丟出 “Operation not supported when using remote debugging" 的錯誤。

事先將 ChromeDriver Server 執行起來

上面每一次測試都要重新開關 ChromeDriver Server 的做法明顯沒有效率,如果事先將 server 執行起來,language binding 要怎麼跟它連接呢?

Tip

The ChromeDriver class starts the ChromeDriver server process at creation and terminates it when quit is called. This can waste a significant amount of time for large test suites where a ChromeDriver instance is created per test.

Getting started
— ChromeDriver

如果測試的數量不多,重複使用 ChromeDriver Server 所省下的時間可能不明顯,不過 “重複開關 server" 這個行為本身就不太科學。如果測試的善後工作 (teardown) 沒有做好,長時間下來可能殘留幾個 server 掛在背景,這是比較另人擔憂的。

$ chromedriver
Starting ChromeDriver 2.14.313457 (3d645c400edf2e2c500566c9aa096063e707c9cf) on port 9515 1
Only local connections are allowed.
1 ChromeDriver Server 預設會在 9515 port 服務 (可以用 –port=<PORT> 自訂)。

簡單來說,就是要在 driver 指定 server 的位置。以 Python 為例:

>>> from selenium.webdriver import Remote, ChromeOptions # 1
>>> options = ChromeOptions()
>>> driver = Remote(command_executor='http://127.0.0.1:9515', desired_capabilities=options.to_capabilities()) # 2
1 要改用 Remote WebDriver 才能指定 server 的位置,另外 desired capabilities 不能為 None,可以用 ChromeOptions.to_capabilities() 轉出。
2 透過 command_executor 指定 service URL;格式為 http://127.0.0.1:CHROME_DRIVER_PORT (結尾不用加 /wd/hub)。

Google Chrome/Chromium 已經執行起來

延續上面的例子,如果 Google Chrome/Chromium 已經執行起來,language binding 要如何連接已經開啟的 Chrome session,而非重新開一個?

基本上 ChromeDriver 支援這種用法,關鍵就在於 Chrome/Chrome 上的 remote debugging 要啟用,而 language binding 這一端則要透過 debuggerAddress capability 來自訂 debugger server 的位置。

Note 內嵌 CEF (Chromium Embedded Framework) 的桌面應用程式狀況也很類似,通常要經過一些 UI 操作之後 (例如完成登入) 才會帶出內嵌 CEF 的視窗,這時候如果有啟用 remote debugging 的話,language binding 就可以中途跟已經開啟的 Chrome session 連接上,進而操作 CEF 呈現的網頁內容。

由於這種應用情境比較特別,也是用 ChromeDriver 操控 CEF-based application 的基礎,因此獨立出另一份文件做說明。

結論

chromedriver/architecture.png

由於 language binding → ChromeDriver Server → Google Chrome/Chromium 之間都是走網路連線,加上 service URL (ChromeDriver Server) 與 debugger address (Chrome/Chromium) 都可以自訂,在配置上可以變換出 4 種組合:

under-the-hood/combinations.png

基本上,建議重複使用 ChromeDriver Server 以提昇測試的效率,至於要不要開新的 Chrome session,則視不同的應用而定。

廣告

2 thoughts on “深入瞭解 ChromeDriver

  1. 科科你好,我很喜歡你文章所用到到的 code syntax highlighting 跟註譯的方法。請問可以分享一下用了什麼wordpress插件嗎?我希望在學習新程序語言也可以用這樣的筆記方式!感謝你!

發表迴響

Please log in using one of these methods to post your comment:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s