CEF-based 應用程式的自動化測試 – WebDriver 篇

CEF 是基於 Chromium,談到 CEF-based application 的自動化測試,就不免讓人聯想到 ChromeDriver (它實作了 WebDriver/JSON Wire Protocol,可以用來操控 Google Chrome 及 Chromium)。

Note 在實務上我們通常無法單獨選用一種方法來實作自動化測試,因為 CEF-based application 類似手機上的 hybrid apps,都是用 WebView (也就是這裡的 CEF/Chromium) 將網頁內容內嵌在 native UI 中,在一個測試中要交互操作 native UI 跟 web UI 的情況 (切換 context) 並不少見。

沒錯,ChromeDriver 確實可以用來操控 CEF-based application 內嵌的網頁。下面就以 OS X 的 cefclientPython language binding 來做說明。

Tip cefclient 是 CEF 提供的 test app,可以取得原始碼後自行編譯,或是從這裡下載已經編譯好的版本。它用 CEF 包裝了一個非常陽春的瀏覽器,一打開就可以看到網頁。

cefclient 而言,做法上跟操控 Google Chrome/Chromium 沒什麼差別,只是要額外指定 CEF-based application 執行檔的位置而已 (預設會找安裝在標準位置的 Google Chrome):

>>> from selenium.webdriver import Chrome, ChromeOptions
>>> options = ChromeOptions()
>>> options.binary_location = '/tmp/cefclient/Release/cefclient.app/Contents/MacOS/cefclient' # 1
>>> driver = Chrome(chrome_options=options) # 2
>>> driver.get('http://www.wikipedia.org')
>>>
1 ChromeOption.binary_location 指定 CEF-based application 執行檔的位置 (假設 cefclient 安裝在 /tmp/cefclient),做法跟 ChromeDriver 要操控 Chromium 的做法一樣。
2 由於 cefclient 一開啟就會顯示網頁,所以 language binding 在這個步驟可以成功建立連線。

Important “一開啟就會顯示網頁" 這一點很重要,也就是一開始就要完成 CEF/Chromium 的初始化,並且在約定好的 port 啟用 remote debugging,否則 language binding 最後會因為連線逾時而失敗;這一點在下面會進一步做說明。

對於 ChromeDriver 來說,cefclient 只是另一個 Chromium-based browser。以同樣的方式檢查 Chrome(chrome_options=options) 帶出了哪些 child process,會發現背後的機制跟操控 Chrome/Chromium 時一樣 - 由 language binding 叫出 ChromeDriver Server,再由 Server 叫出 cefclient 並啟動它的 remote debugging。

../../chromedriver/chromedriver/architecture.png
Figure 1. ChromeDriver 的架構,背後運作的細節可以參考這裡

假設 Python interpreter 自己的 PID 是 96318:

$ ps -o pid,ppid,command | grep 96318
96327 96207 grep 96318
96318 87679 python
96320 96318 chromedriver --port=64409

$ ps -o pid,ppid,command | grep 96320
96334 96207 grep 96320
96320 96318 chromedriver --port=64409
96321 96320 /tmp/cefclient/Release/cefclient.app/Contents/MacOS/cefclient --disable-background-networking --disable-client-side-phishing-detection --disable-component-update --disable-default-apps --disable-hang-monitor --disable-prompt-on-repost --disable-sync --disable-web-resources --enable-logging --ignore-certificate-errors --load-extension=/var/folders/xw/6vc5xhp915g5plms5qx1jnfr0000gr/T/.org.chromium.Chromium.OxTGjG/internal --log-level=0 --metrics-recording-only --no-first-run --password-store=basic --remote-debugging-port=12266 --safebrowsing-disable-auto-update --safebrowsing-disable-download-protection --test-type=webdriver --use-mock-keychain --user-data-dir=/var/folders/xw/6vc5xhp915g5plms5qx1jnfr0000gr/T/.org.chromium.Chromium.2vAZPQ data:,

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

Python interpreter (PID:96318)
  `- ChromeDriver Server (PID:96320, chromedriver --port=64409)
     `- cefclient (PID:96321, cefclient --remote-debugging-port=12266)

分別在 command-line option 指定的 port 服務:

$ lsof -nP -iTCP -sTCP:LISTEN
COMMAND     PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
...
chromedri 96320 jeremy    7u  IPv4 0xe1ab0daedb9474a9      0t0  TCP 127.0.0.1:64409 (LISTEN)
cefclient 96321 jeremy   33u  IPv4 0xe1ab0daecdf214a9      0t0  TCP 127.0.0.1:12266 (LISTEN)

關鍵在於 cefclient 接收到 –remote-debugging-port 這個 option 時會啟用 remote debugging,所以 ChromeDriver Srever 才能跟 CEF/Chromium 溝通。

Tip

為什麼 cefclient 可以透過跟 Chromium 一樣的 command-line options 來影響 CEF/Chromium 的行為?那是因為 CEF 在初始化時也會收到 command-line options 並加以處理,如果不想處理的話,得明確將 CefSettings.command_line_args_disabled 設為 false 才行。

Many features in CEF3 and Chromium can be configured using command line arguments. These arguments take the form “–some-argument[=optional-param]" and are passed into CEF via CefExecuteProcess() and the CefMainArgs structure.

To disable processing of arguments from the command line set CefSettings.command_line_args_disabled to true before passing the CefSettings structure into CefInitialize().

General Usage
— CEF
int RunMain(int argc, char* argv[]) {
  CefMainArgs main_args(argc, argv);
  // ...

  CefSettings settings;

  // Populate the settings based on command line arguments.
  context->PopulateSettings(&settings);

  // Initialize CEF.
  context->Initialize(main_args, settings, app, NULL);

  // ...
}

不過,應用程式通常不會像 cefclient 這樣無條件接受所有 Chromium 的 command-line options,可以考慮在偵測到特定的 option 時才將 CefSettings.remote_debugging_port 設定成特定的 port (1024 – 65535):

CefSettings settings;
settings.remote_debugging_port = 9222;

現實 CEF-based applications 與 cefclient 的差異

上面提到 cefclient 具有 “一開啟就會顯示網頁" 的特性,所以只要想辦法讓程式 “一開始" 就在約定好的 port 啟用 CEF/Chromium 的 remote debugging 即可。但實際的狀況是,WebView/CEF 通常不會在一開始就出現

回顧一下在 CEF-based application 特性時所舉的例子:


test-automation/cef-based-flow.png

WebView/CEF 通常要經過一些 native UI 的操作之後 (例如完成登入) 才會出現,如果 language binding (透過 ChromeDriver Server) 在啟動 CEF-based application 後直接要與它內嵌的 CEF/Chromium 建立連線,肯定會失敗。

Important 解決的方式是不要由 ChromeDriver 開啟 CEF-based application,利用其他方法 (例如 accessibility) 將畫面帶到有 WebView/CEF 的畫面後,再中途跟已經開啟的 Chrome session 連接上。

同樣以 cefclient 做為例子,但這次 cefclient 不是由 ChromeDriver Server 執行起來。

>>> import subprocess
>>> process = subprocess.Popen(['/path/to/cefclient_executable', '--remote-debugging-port', '9222']) # 1
>>>
>>> # go through the login process              2
>>> from selenium.webdriver import Chrome, ChromeOptions
>>> options = ChromeOptions()
>>> options.debugger_address='127.0.0.1:9222' # 3
>>> driver = Chrome(chrome_options=options)
1 啟動 cefclient 時,約定在 port 9222 啟用 remote debugging。
2 利用其他測試工具 (例如 accessibility) 操作 native UI,以完成登入的動作。
3 告訴 driver 直接連線到已經開啟的 Chrome session。

關於 ChromeDriver 如何連接到已經開啟的 Chrome session 的細節,可以參考這裡

廣告

One thought on “CEF-based 應用程式的自動化測試 – WebDriver 篇

發表迴響

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