この記事はSelenium/Appium Advent Calendar 2014の12日目の記事です。誰も書かなさそうなので小ネタを引っ張りだしてきました。
chromedriverのアーキテクチャ
chromedriverはJSON Wire Protocolを受けるSelenium ServerとChromeの間の通信をRemote debugging protocolで行っています。これは、Chromeに標準で内蔵されているDeveloper Toolsでも利用しているものです。
chromedriverを使ったプログラムを途中で止めて、Developer Toolsでデバッグしてみて、そのDeveloper Toolsを閉じずに、プログラムを再開したら、Seleniumのセッションが使えずに死ぬといった経験はわかる人にはわかるでしょう。これは、両者が同じAPIを利用していて、かつそのAPIのセッションが同時には使えないことに起因します。
同じAPIを利用しているということは、Developer Toolsでできそうなことってたいていchromedriverでできそうですよね?ということで、Developer Toolsの以下の機能について試してみました
- mobile emulation
- console logの取得
- Performance Logの取得
mobile emulation
モバイルWeb開発では大活躍のmobile emulation機能です。Developer Toolsでは携帯電話っぽいアイコンを押すだけで表示がスマホのものになります。この機能はchromedriver2.11から利用可能となっています。
これをchromedriverから利用するのは、capabilityを適切に設定してやるだけです。
以下にrubyの例を書きます。
require "selenium/webdriver"
mobile_emulation = { "deviceName" => "Google Nexus 5" }
chrome_options = { "chromeOptions" => { "mobileEmulation" => mobile_emulation } }
caps = Selenium::WebDriver::Remote::Capabilities.chrome(chrome_options)
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
driver.get "https://twitter.com"
puts driver.current_url #=> "https://mobile.twitter.com/"
その時のスクリーンショットはこんな感じになります。twitterのモバイル版の画面が表示されていることや、ブラウザのウインドウサイズとは別にGoogle Nexus 5相当の画面サイズでレンダリングされていることがわかると思います。
今回 capabilitiesとしてdeviceNameを直指定しましたが、これは、deviceMetrics(width, height, pixelRatio)およびuserAgentを指定するようなやり方もあります。
mobile_emulation = {
"deviceMetrics" => { "width" => 360, "height" => 640, "pixelRatio" => 1.0 }
"userAgent" => "xxxx",
}
touchイベントも起こせます。以下のように、single_tapすることができます。ただし、double_tapやlong_pressは、chromedriverが未対応なのでできないようです。
driver.extend Selenium::WebDriver::DriverExtensions::HasTouchScreen
driver.get "https://s3-ap-northeast-1.amazonaws.com/codegrid/touch-click-pointer/demo/01-trytouch/index.html"
area = driver.find_element(:css, "#hitarea")
driver.touch.single_tap(area).perform
※ codegrid さんのサンプルサイトを使っています
console logの取得
以下の例では、browserのログ(要はconsoleに出るやつ)を設定して、javascriptのエラーを検知するようにしています。chromedriverはlogginPrefsに対応しておりますが、多分デフォルトはOFFになっているので、設定しないと何も取得できません。
この例では、内藤薬局でjavascriptのエラーが出ていることのログが取得できます。
log_prefs = { "browser" => "ALL" }
caps = Selenium::WebDriver::Remote::Capabilities.chrome("loggingPrefs" => log_prefs)
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
driver.get "http://hm.aitai.ne.jp/~naitonet/"
p driver.manage.logs.get(:browser).first
#=> #<Selenium::WebDriver::LogEntry:0x007ff6da15f968
@level="SEVERE",
@message="http://hm.aitai.ne.jp/~naitonet/ 998:18 Uncaught TypeError: Cannot set property 'left' of undefined",
@timestamp=1418304232408>
また、同じような感じで画像のリンク切れも検出できます。
driver.get "http://homepage3.nifty.com/TOKU/"
p driver.manage.logs.get(:browser).first
#=> #<Selenium::WebDriver::LogEntry:0x007ff6db85de08
@level="SEVERE",
@message=
"http://www.noe.jx-group.co.jp/csr/click/images/click_bn01.gif 0:0 Failed to load resource: the server responded with a status of 404 (Not Found)",
@timestamp=1418304578471>
これをうまく使えば、javascriptのミスや画像のリンク切れみたいなものの検出がより効率的になります。
Performance Logの取得
同様にPerformance Logも取得できます。
log_prefs = { "performance" => "ALL" }
caps = Selenium::WebDriver::Remote::Capabilities.chrome("loggingPrefs" => log_prefs)
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
driver.get "http://azcji.cocolog-nifty.com/blog/cat1459704/index.html"
driver.manage.logs.get(:performance) #=> たくさん出る!
要はChromeのNetworkとかTimelineに出てくるような情報が取得できます。これを使ってブラウザのパフォーマンス測定の自動化をしようとするとそれだけでヒトネタになると思うのでここの詳細はまた別の機会にしたいと思います。
まとめ
こういうエントリー書こうと重いサイトとか画像リンク切れのサイトとか変なjsのサイトとか探そうとすると案外難易度高かったりするのですね。。
明日のSelenium/Appium Advent Calendarはまだ立候補者がいないので、誰か是非ともよろしくおねがいします!