こんにちは、中島 (@nazomikan)です。
※この記事は2016年に書かれた記事でver2系を対象に書いています。 ver3,4-betaで色々変わってるのでその辺についてのキャッチアップはこちらの記事をご利用ください 最近のselenium-webdriverの話
本記事は株式会社ネクスト(Lifull) Advent Calendar 2016の1日目の投稿です。
ブラウザベースのテストツールのseleniumをコードから実行するselenium-webdriverのnode版について導入からAPI紹介までしていきます。
あと手元で作業するにあたってAPIの翻訳(ソース上にうまってるdocsの翻訳)をしてたのでそれも載せておきます(むしろそっちメイン)
- 導入
- webdriverのテスティングについて
- テストの書き方
- よくある使い方
- API翻訳
よく使って調べたくなるのはのは以下の三つだと思う。
driverがwebdriverのインスタンスでブラウザ操作する系
locatorがwebElementを探索する系
untilがwaitの条件
導入
導入はいろんな記事でまとめられてるので触りだけ
seleniumのstandaline serverとchromeドライバさえあればとりあえず起動できます。
※macの場合はxcode-select, winの場合はvisualstudioが入ってることが前提だった気がします。
selenium standalone serverの入手
chrome drvierの入手
seleniumサーバの起動
java -jar -Dwebdriver.chrome.driver=chromedriver ~/selenium/selenium-server-standalone-3.0.1.jar
今回はnodeからseleniumをさわるのでnpmからselenium-webdriver
をもってきます。
あとテストモジュールとしてmochaを使います
sudo npm -g install selenium-webdriver
npm install -g mocha
テストの書き方
これもいろんな記事でまとめられてるので触りだけ
doctime html
html(lang="ja")
head
body
form(action="/path/to/confirm")
input(name="user_name", type="text")
button#send_button(type="submit")
こんなhtmlに対してsubmit系のテスト
'use strict';
let assert = require('assert')
;
let webdriver = require('selenium-webdriver')
, test = require('selenium-webdriver/testing')
, By = webdriver.By
, until = webdriver.until
;
test.describe('なにかのフォームについて', function () {
let driver;
// テスト実行前にdriverの起動と作成
test.before(function() {
driver = new webdriver.Builder()
.usingServer('http://localhost:4444/wd/hub')
.withCapabilities(webdriver.Capabilities.chrome())
.build();
});
test.after(function () {
driver.quit()
});
test.it('confirmページに移動できること', function () {
driver.get('http://host.com/path/to/');
driver.findElement(By.name('user_name')).sendKeys('takuya nakajima');
driver.findElement(By.id('send_button')).click();
driver.wait(until.titleIs('confirm'), 1000);
driver.getCurrentUrl().then(function (url) {
assert.equal(url, '/path/to/confirm');
});
})
});
webdriverのテスティングについて
基本的に純正の(selenium-webdriver/testing)
を用いてテストします。
直接mochaを使わない方が楽です。
let test = require('selenium-webdriver/testing')
test.describe..
test.it....
これらのselenium-webdriverが提供するメソッドはmochaがglobalに放出するメソッドのラッパメソッドです。
Mocha's BDD interface:
以下のようなmochaのAPIは全部wrapされて同名で定義されています。
- after
- afterEach
- before
- beforeEach
- it
- it.only
- it.skip
- xit
selenium-webdriverを用いたテストは基本的にブラウザに命令して操作する特性上おおよそ全部非同期になります。
なのでselenium-webdriver/testingではデフォルトのit等を非同期テストに対応させるようにwrapしてくれます。
詳しくいうとdriverのapiは内部的にすべて非同期(promise)で実装されており、apiをコールすると内部的にブラウザへの命令がスケジューリングされます。
スケジュールされたタスクを逐次的に実行していき、すべて完了した場合にテストを終了させる(暗黙的にitのdoneをコールする)といった実装になってます。
selenium-webdriverを用いたテストをする場合はこれらの恩恵を受けた方が実装よっぽど楽なので基本的にmochaのデフォルトのtestingを使わずにこのラップされたtestingを利用します。
webdriverの独自のテスティング
mochaのtestingを基本的に全部wrapしてるのですが独自で持っている機能もあります。
- ignore
テストを条件付きでskipする機能です。
test.ignore(year > 2016).it('なんらかのてすと', function () {
...
});
上記のテストはyearが2016年以降の場合はスキップされます
よくある使い方
モバイルのUAでやりたい!
chromeOptionを使えばUAを切り替えることができます
driver = new webdriver.Builder()
.usingServer('http://localhost:4444/wd/hub')
.withCapabilities(webdriver.Capabilities.chrome())
.setChromeOptions(
(new chrome.Options())
.setMobileEmulation({deviceName: 'Apple iPhone 6'})
)
.build();
よく設定するUA
- Apple iPad
- Apple iPhone 5
- Apple iPhone 6
- Apple iPhone 6 Plus
- Google Nexus 10
- Google Nexus 4
- Google Nexus 5
- Google Nexus 7
windowがでかくて邪魔だからちっさくしたい
driver.manage().window().setSize(200, 400);
driverにはない自前の非同期なapiをdriverのスケジューラに登録したい!
webdriverのpromiseで包んであげればスケジューラに内部的に登録されます。
let promise = webdriver.promise;
driver.call(function () {
return new promise.Promise(function (resolve, reject) {
yourAPI('xxx', function () {
resolve('hello');
});
});
})
テスト実行するブラウザにエクステンション突っ込みたい!
statusコードの確認やjsエラーの確認とかそのままのselenium-webdriverではできないようなことも起動するブラウザに拡張を突っ込んで橋渡ししてあげればとれたりします。
そんなextensionの突っ込み方
driver = new webdriver.Builder()
.usingServer('http://localhost:4444/wd/hub')
.withCapabilities(webdriver.Capabilities.chrome())
.setChromeOptions(
(new chrome.Options())
.addExtensions('/path/to/your_extension1.crx')
.addExtensions('/path/to/your_extension2.crx')
)
.build();
クッキーを設定してテストしたい
ABテスト時の検証とかクッキーを指定しないとできないようなやつとかありますよね。
安心してください。 できますよ。
test.before(function() {
let expire = new Date();
expire.setTime(expire.getTime() + 1000 * 3600 * 24);
// cookieをセットするドメインのページに移動
driver.get('http://www.homes.co.jp/');
// 邪魔になりそうなcookieをあらかじめすべて削除
driver.manage()
.deleteAllCookies();
// テスト対象のcookieを設定
driver.manage()
.addCookie(
'your_cookie_name',
'your_cookie_value',
'/',
null,
false,
expire
);
});
明示的にdomreadyのタイミングを待ちたい
基本loadイベントを待ってくれるので苦労することはないけど明示的にdomReadyの完了を待ちたいケースもあるかもしれません。
executeScriptでreadyStateを確認するConditionを作っておけばその辺もできます。
let until = webdriver.until;
let untilDomReady = new until.Condition('to be ready', function (driver) {
return driver
.executeScript("return document.readyState;")
.then(function (state) {
return state === 'complete';
});
});
driver.wait(untilDomReady, 50000);
以下はAPI翻訳
driverのAPI
すべてのwebdriverコマンドは、そのコマンドの結果を表す{webdriver.promise.Promise}を返します。
promiseのcallback(たとえばthenとか)でスケジュールされたコマンド(driver apiの呼び出し)は現在のフレーム内の次のコマンドの前に実行されます。
たとえば以下のようなコマンド群があった場合最後のassertiは成立します
var message = [];
driver.call(message.push, message, 'a').then(function() {
driver.call(message.push, message, 'b');
});
driver.call(message.push, message, 'c');
driver.call(function() {
assert.equal(message.join(''), 'abc');
});
driver.controlFlow()
-
@return {!webdriver.promise.ControlFlow}
現在のインスタンスのcontrolFlowを返却する
driver.schedule
- @param {!webdriver.Command} コマンド
- @param {string} コマンドの説明文(デバッグ用)
- @return {!webdriver.promise.Promise.<T>}
与えられたコマンドを実行し、そのコマンドの完了を監視するPromiseを返却する
driver.schedule(new webdriver.Command(webdriver.CommandName.QUIT), 'WebDriver.quit()');
driver.setFileDetector(detector)
- @param {webdriver.FileDetector} ファイル検知モジュール
input[type="file"]に対してsendKeysを介してファイルを指定できるようにする
let driver = new webdriver.Builder()
.usingServer('http://localhost:4444/wd/hub')
.withCapabilities(webdriver.Capabilities.chrome())
.build();
driver.setFileDetector(new webdriver.FileDetector());
test.it('ファイルをアプロードできること', function() {
driver.get("http://sl-test.herokuapp.com/guinea_pig/file_upload");
driver.findElement(By.id("myfile")).sendKeys("/path/to/some_image.jpg");
driver.findElement(By.id("submit")).click();
...
});
driver.getSession()
- @return {!webdriver.promise.Promise.<!webdriver.Session>}
現在のクライアントのセッションを返すPromiseを返す。
driver.getSession().then(function (session) {
console.log(session);
// { id_: '1069bbb4-924c-4332-b4e4-4a326447e3ab',
// caps_:
// { caps_:
// { platform: 'MAC',
// acceptSslCerts: true,
// javascriptEnabled: true,
// browserName: 'chrome',
// chrome: [Object],
// rotatable: false,
// locationContextEnabled: true,
// mobileEmulationEnabled: true,
// 'webdriver.remote.sessionid': '1069bbb4-924c-4332-b4e4-4a326447e3ab',
// version: '52.0.2743.116',
// takesHeapSnapshot: true,
// cssSelectorsEnabled: true,
// databaseEnabled: false,
// handlesAlerts: true,
// browserConnectionEnabled: false,
// nativeEvents: true,
// webStorageEnabled: true,
// hasTouchScreen: true,
// applicationCacheEnabled: false,
// takesScreenshot: true } } }
});
driver.getCapabilities();
- @return {!webdriver.promise.Promise.}
現在のクライアントの機能情報(capability)を返すPromiseを返す
driver.getCapabilities().then(function (capability) {
console.log(capability);
// { caps_:
// { platform: 'MAC',
// acceptSslCerts: true,
// javascriptEnabled: true,
// browserName: 'chrome',
// chrome: { userDataDir: '/var/folders/mz/kqgfzn5x00d3101y1lkjx1lrly_xxv/T/.org.chromium.Chromium.fcl4uz' },
// rotatable: false,
// locationContextEnabled: true,
// mobileEmulationEnabled: true,
// 'webdriver.remote.sessionid': '9049213d-c78c-4087-8011-4ac07496d229',
// version: '52.0.2743.116',
// takesHeapSnapshot: true,
// cssSelectorsEnabled: true,
// databaseEnabled: false,
// handlesAlerts: true,
// browserConnectionEnabled: false,
// nativeEvents: true,
// webStorageEnabled: true,
// hasTouchScreen: true,
// applicationCacheEnabled: false,
// takesScreenshot: true } }
});
driver.quit()
- @return {!webdriver.promise.Promise.<void>} QUITコマンドの完了時に解決するPromise
QUITコマンドを実行する(現在のセッションを終了する)
driver.quit();
driver.actions()
- @return {!webdriver.ActionSequence} このインスタンスのaction sequence
マウスやキーボード操作などのアクションを行う順序を設定する(ActionSequence#performがcallされるまで実行はされない)
driver.actions().
mouseDown(element1). // element1 = webdriver.WebElement
mouseMove(element2). // element2 = webdriver.WebElement
mouseUp().
perform(); // 上記で定義された操作を実行する(返り値は完了を待つPromise)
driver.touchActions()
- @return {!webdriver.TouchSequence} このインスタンスのtouch sequence
タッチ操作系のアクションを行う順序の設定(TouchSequence#performがcallされるまで実行はされない)
driver.touchActions().
tap(element1). // element1 = webdriver.WebElement
doubleTap(element2). // element2 = webdriver.WebElement
perform(); // 上記で定義された操作を実行する(返り値は完了を待つPromise)
driver.executeScript(script, var_args)
- @param {!(string|Function)} script 実行する関数文字列(もしくは関数)
- @param {...*} var_args 実行するscript(関数)に与える引数
- @return {!webdriver.promise.Promise.<T>} scriptの結果が得られたときに解決されるPromise
与えれたscript(関数)を現在のブラウザのフレーム上で実行する
// 指定されたセレクタの要素位置までスクロールするjsをブラウザ上で実行させる
let fn = function (selector) {
var button = document.querySelector(selector)
, top = button.getBoundingClientRect().top
;
window.scrollTo(0, top);
return 'scrolled';
};
driver.executeScript(fn, "#some_id li").then(function (text) {
console.log(text); // scrolled
});
driver.executeAsyncScript(script, var_args)
- @param {!(string|Function)} script 実行する関数文字列(もしくは関数)
- @param {...*} var_args The arguments to pass to the script.
- @param {...*} var_args 実行するscript(関数)に与える引数
- @return {!webdriver.promise.Promise.<T>} scriptの結果が得られたときに解決されるPromise
与えれた非同期なscript(関数)を現在のブラウザのフレーム上で実行する
scriptの引数の最後{@code arguments[arguments.length - 1]}がcallbackとなり、callbackの引数がPromiseの値となる
let fn = function(filePath) {
var callback = arguments[arguments.length - 1];
var xhr = new XMLHttpRequest();
xhr.open("GET", filePath, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
callback(xhr.responseText);
}
}
xhr.send('');
};
driver.executeAsyncScript(fn, "/resource/data.json").then(function(json) {
console.log(json); // json text (/resource/data.json)
});
driver.call(fn, opt_scope, var_args)
- @param {function(...): (T|webdriver.promise.Promise.<T>)} 実行する関数を与える
- @param {Object=} 実行する関数のcontext (this)
- @param {...*} 実行する関数の引数(可変長)
- @return {!webdriver.promise.Promise.<T>} 関数の結果が得られた時に解決するPromise
同期処理
driver.call(function () {
return 'hoge';
}).then(function (text) {
console.log(text); // hoge
});
非同期処理(Promiseを返却するとコマンドとしてスケジューリングされる)
var webdriver = require('selenium-webdriver')
, promise = webdriver.promise
;
// ...
driver.call(function () {
return new promise.Promise(function (resolve, reject) {
setTimeout(function () {
resolve('hello');
}, 1000);
});
}).then(function (text) {
console.log(text); // hello
});
driver.wait(condition, opt_timeout, opt_message)
- @param {!(webdriver.promise.Promise<T>| webdriver.until.Condition| function(!webdriver.WebDriver): T)} condition 待機条件(promise,condition,conditionのように評価される関数)
- @param {number=} opt_timeout conditionがtrueになるまで、どのくらいまつかのタイムアウト値
- @param {string=} opt_message オプショナルのメッセージ(タイムアウト時に出力される)
- @return {!webdriver.promise.Promise<T>} conditionがtruthyな値を返した時にresolve, timeoutした時にrejectが呼ばれるpromise
conditionが成立するまで待機する
conditionがtruthyな値を返却するまでconditionは繰り返し評価されます
promiseを使った条件
var webdriver = require('selenium-webdriver')
, promise = webdriver.promise
;
// ...
test.it('xxx', function () {
let p = new promise.Promise(function (resolve, reject) {
setTimeout(function () {
resolve(true);
}, 5000);
})
driver.wait(p).then(function () {
console.log('hello')
});
});
用意されてるuntil.conditionを使った条件(alertがでるまで待つ)
var webdriver = require('selenium-webdriver')
, until = webdriver.until
;
// ...
test.it('xxx', function () {
driver.wait(until.alertIsPresent(), 10000);
});
until.conditionを使った条件
var webdriver = require('selenium-webdriver')
, until = webdriver.until
;
// ...
test.it('b', function () {
let expected = 'サイトのタイトル'
, condition
;
driver.wait(new until.Condition('title is equal to expected', function(driver) {
return driver.getTitle().then(function(title) {
return title === expected;
});
}), 5000);
});
driver.sleep(ms)
- @param {number} ms sleepする時間(単位はms)
- @return {!webdriver.promise.Promise.<void>} sleepが終わったらresolveするPromise
与えられた時間だけsleepする(本格運用時にほぼ用途はない)
driver.getWindowHandle()
- @return {!webdriver.promise.Promise.<string>} 現在のwindowハンドルを取得するPromise
現在のWindowのハンドルを取得する
driver.getAllWindowHandles()
- @return {!webdriver.promise.Promise.>} 全てのwindowのハンドルを配列で取得するPromise
現在利用可能な全てのWindowのハンドルを取得する
最新のwindowに切り替える
driver.getAllWindowHandles().then(function (allhandles) {
driver.switchTo().window(allhandles[allhandles.length - 1]);
});
driver.getPageSource()
- @return {!webdriver.promise.Promise.<string>} 現在のページのソースを取得するPromise
現在のページのソースを取得する。(エスケープや整形されてない生のソース)
googleのソースコードを標準出力に表示する
driver.get('http://www.google.com');
driver.getPageSource().then(function (source) {
console.log(source); // google source <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" item...
});
driver.close()
- @return {!webdriver.promise.Promise.<void>} 閉じるコマンドが完了したときにresolveするPromise
現在のWindowを閉じる
driver.get(url)
- @param {string} url 開きたいサイトの完全なURL
- @return {!webdriver.promise.Promise.<void>} ページの読み込みが完了したときにresolveするPromise
与えられたURLを開く
alias: driver.navigate.to(url)
driver.getCurrentUrl()
- @return {!webdriver.promise.Promise.<string>} 現在のURLを取得するPromise
現在のページのURLを取得する
driver.findElement(By.id('some_submit_button')).click();
driver.wait(until.titleContains('サイトのタイトル'), 10000);
driver.getCurrentUrl().then(function (currentUrl) {
assert.equal(currentUrl, 'http://google.com');
});
driver.getTitle()
- @return {!webdriver.promise.Promise.<string>} 現在のページのtitleを取得するPromise
現在のページのtitleを取得する
driver.getTitle(function (title) {
assert.equal(title, 'サイトタイトル');
});
driver.findElement(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator ロケータ
- @return {!webdriver.WebElement} WebElement: 検索された要素に対してコマンドを発行することのできるオブジェクト(もし要素が見つからなかった場合、スケジュールされてたコマンドは中断されます)
ページ内の要素を検索するコマンドを実行する
もし要素がなかった場合は{bot.ErrorCode.NO_SUCH_ELEMENT}が発生します。 他のコマンドとは異なり、このエラーを抑制することはできません。(つまり、このコマンドは要素がページ上に存在するかを検証するアサートを兼ねています
もし要素がページ上に存在するかをどうかを検証したい場合は代わりに{#isElementPresent}を使ってください
要素の検索条件は{webdriver.By}か、そのショートハンドの{webdriver.By.Hash}を使います
以下は2つのステートメントは等価です
var e1 = driver.findElement(By.id('foo'));
var e2 = driver.findElement({id:'foo'});
また、カスタムロケータを利用して要素を検索することもできます。 カスタムロケータはWebDriverのインスタンス(driver)を引数として受け取り、WebEelentのインスタンスか、将来WebElementを返却することを約束するPromiseオブジェクトを返す必要があります
以下はページ上の見えている(visible)アンカー要素を取得するカスタムロケータの例です
var link = driver.findElement(firstVisibleLink);
function firstVisibleLink(driver) {
var links = driver.findElements(By.tagName('a'));
return webdriver.promise.filter(links, function(link) {
return links.isDisplayed();
}).then(function(visibleLinks) {
return visibleLinks[0];
});
}
driver.isElementPresent(locatorOrElement)
- @param {!(webdriver.Locator|webdriver.By.Hash|Element| Function)} locatorOrElement ロケータ、もしくはElement
- @return {!webdriver.promise.Promise.} ページ上に要素が存在するかどうかの結果を返すPromise
要素がページ上に存在するかをテストします
driver.get('http://www.homes.co.jp');
driver.isElementPresent(By.id('#hogehoge')).then(function (bool) {
console.log(bool);
});
driver.findElements(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator ロケータ
- @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} WebElementの配列を返すPromise
ページ上から複数要素を返却する
var links = driver.findElements(By.tagName('a'));
driver.takeScreenshot()
- @return {!webdriver.promise.Promise.<string>} base64エンコードされたPNGファイルを返すPromise
スクリーンショットを撮る
ドライバは、以下のスクリーンショットを優先順に返そうとします
- ページ全体
- 現在のウィンドウ
- 現在のフレームの見えてる部分
- ブラウザを含むディスプレイ全体のスクリーンショット
driver.manage()
- @return {!webdriver.WebDriver.Options} このインスタンスのOptionインタフェース
このインスタンスのOptionインタフェースを返却する
// クッキーを追加する
driver.manage()
.addCookie(
'cookie_name',
'cookie_value',
'/',
null,
false,
(new Date()).setTime(expire.getTime() + 1000 * 3600 * 24);
);
driver.navigate()
- @return {!webdriver.WebDriver.Navigation} このインスタンスのNavigationインタフェース
このインスタンスのNavigationインタフェースを返却する
// ブラウザバック
driver.navigate().back()
driver.switchTo()
- @return {!webdriver.WebDriver.TargetLocator} このインスタンスのarget locatorインタフェース
このインスタンスのTargetLocatorインタフェースを返却する
// windowを切り替える
driver.switchTo().window(someWindowHandle);
navigationのAPI
ブラウザのヒストリ関連のインタフェース
driver.navigate().to(url)
- @param {string} url 遷移先のURL
- @return {!webdriver.promise.Promise.<void>} ページの読み込みが完了した時にresolveするPromise
与えられたURLを開く
driver.navigate().back()
- @return {!webdriver.promise.Promise.<void>} ブラウザバックが完了した時にresolveするPromise
ブラウザバックを行う
driver.navigate().forward()
- @return {!webdriver.promise.Promise.<void>} ブラウザフォワード(進む)が完了した時にresolveするPromise
ブラウザフォワード(進む)を行う
driver.navigate().refresh()
- @return {!webdriver.promise.Promise.<void>} リフレッシュが完了した時にresolveするPromise
現在のページをリフレッシュする
optionsのAPI
ドライバの状態やブラウザの管理を行うメソッドを提供します
driver.manage().addCookie(name, value, opt_path, opt_domain, opt_isSecure, opt_expiry)
- @param {string} name クッキーの名前
- @param {string} value クッキーの値
- @param {string=} opt_path クッキーのパス
- @param {string=} opt_domain クッキーのドメイン
- @param {boolean=} opt_isSecure セキュアなクッキーかどうか
- @param {(number|!Date)=} opt_expiry クッキーのexpires. (もしnumber型が与えられた場合、それは1970年1/1[UTC]からの加算ミリ秒として評価されます)
- @return {!webdriver.promise.Promise.} そのページにクッキーが追加された時にresolveされるPromise
クッキーを追加する
// クッキーを追加する
driver.manage()
.addCookie(
'cookie_name',
'cookie_value',
'/',
null,
false,
(new Date()).setTime(expire.getTime() + 1000 * 3600 * 24);
);
driver.manage().deleteAllCookies()
- @return {!webdriver.promise.Promise.} 全てのクッキーが消し終わったらresolveするPromise
現在のページ上で有効なクッキーをすべて削除する
driver.manage().deleteCookie(name)
与えれた名前のクッキーを削除する
このコマンドは与えられた名前のクッキーが現在のページから見えない場合は何もしません
driver.manage().getCookies()
- @return {!webdriver.promise.Promise.>} 現在のページで見れるクッキーを全て取得し終えたらresolveするPromise
- @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
現在のページで見れるクッキーを全て取得する
driver.manage().getCookie(name)
- @param {string} name 取得するクッキーの名前
- @return {!webdriver.promise.Promise.} cookieが取得できた時にresolveするPromise (該当するクッキーがあればその値を、なければ{@code null}が得られる)
- @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
与えられたクッキーを取得する(なければnullを返す)
driver.manage().logs()
- @return {!webdriver.WebDriver.Logs} driverのログを管理するインタフェース
driverのログを管理するインタフェースを取得する
driver.manage().timeouts()
- @return {!webdriver.WebDriver.Timeouts} driverのタイムアウトを管理するインタフェース
driverのタイムアウトを管理するインタフェースを取得する
driver.manage().window()
- @return {!webdriver.WebDriver.Window} 現在のwindowを管理するインタフェース
現在のwindowを管理するインタフェースを取得する
WindowのAPI
現在のwindowを管理するインタフェース
webdriver.WebDriver.Window.prototype.getPosition()
- @return {!webdriver.promise.Promise.<{x: number, y: number}>} windowの座標({x:number, y:number})を取得したらresolveするPromise
画面の左上隅を0,0とした時の現在のwindowの座標を返却する
webdriver.WebDriver.Window.prototype.setPosition(x, y)
- @param {number} x 画面の左上からみた水平方向の座標
- @param {number} y 画面の左上からみた垂直方向の座標
- @return {!webdriver.promise.Promise.<void>} 設定が完了したらresolveするPromise
現在のウィンドウの位置を変更します。
webdriver.WebDriver.Window.prototype.getSize()
- @return {!webdriver.promise.Promise.<{width: number, height: number}>} windowのサイズ({width:number, height:number})を取得したらresolveするPromise
windowの現在のサイズを取得する
webdriver.WebDriver.Window.prototype.setSize(width, height)
- @param {number} width windowの横幅
- @param {number} height windowの縦幅
- @return {!webdriver.promise.Promise.<void>} 設定が完了したらresolveするPromise
現在のwindowをリサイズする
webdriver.WebDriver.Window.prototype.maximize()
- @return {!webdriver.promise.Promise.<void>} 設定が完了したらresolveするPromise
現在のwindowを最大化する
logsのAPI
WebDriverのログレコードを管理するインタフェース
driver.manage().logs().get(type)
- @param {!webdriver.logging.Type} type 取得したいログの種類
- @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} 指定したタイプのログが取得できた時にresolveされるPromise
指定されたタイプの有効なログエントリを取得します
driver.get('http://googole.com');
driver.manage().logs().get(webdriver.logging.Type.CLIENT).then(function(entries) {
entries.forEach(function(entry) {
console.log('[%s] %s', entry.level.name, entry.message);
});
});
/**
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Done: [fetching logs for: client]
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Executing: [delete session: 78795e5b-5535-4600-bcf1-55a29d25dd19])
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Done: [delete session: 78795e5b-5535-4600-bcf1-55a29d25dd19]
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Executing: [new session: Capabilities [{browserName=chrome}]])
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Done: [new session: Capabilities [{browserName=chrome}]]
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Executing: [get: http://googole.com])
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Done: [get: http://googole.com]
[INFO] org.openqa.selenium.remote.server.DriverServlet org.openqa.selenium.remote.server.rest.ResultConfig.handle Executing: [fetching logs for: client])
*/
driver.manage().logs().getAvailableLogTypes()
- @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} 有効なログ種別のリストが取得できたらresolveするPromise
このドライバで使用可能なログの種類を取得します。
driver.manage().logs().getAvailableLogTypes().then(function (logTypeList) {
console.log(logTypeList);
})
// [ 'browser', 'driver', 'client', 'server' ]
TargetLocatorのAPI
ドライバのフォーカスを別のフレームまたはウィンドウに変更するためのインターフェイス。
driver.switchTo().activeElement()
- @return {!webdriver.WebElementPromise} フォーカスの当たっている要素
フォーカスの当たっている要素{@code document.activeElement}を取得する
要素がみつからなかった場合は document, あるいは {@code document.body}を取得する
driver.switchTo().defaultContent()
- @return {!webdriver.promise.Promise.<void>} ページのデフォルトのframeにコマンドの向き先を移動戻し終えたらresolveするPromise
ページの最初のframeに全てのコマンドの向き先を移動する
webdriver.switchTo().frame(nameOrIndex)
- @param {string|number} nameOrIndex frameのロケータ
- @return {!webdriver.promise.Promise.<void>} 指定したframeにコマンドの向き先を移し終えたらresolveするPromise
ページの別のフレームに全てのコマンドの向き先を移動する
もしも数字(n)が与えれた場合、このコマンドはn番目(0から数える)に切り替えます window.frames.
もし文字列が与えられた場合、コマンドはその文字列のnameかIDのframeに切り替えます
サブframeへの切替はnameかIDをドット(.)で区切ることで指定できます(ex, "main.child" と指定した場合は"main"がnameに指定されたframeの中の"child"がnameに指定されたframeに移ります)
もし、設定されたframeがみつからなかった場合、{bot.ErrorCode.NO_SUCH_FRAME} errorが返却されます
webdriver.WebDriver.TargetLocator.prototype.window(nameOrHandle)
- @param {string} nameOrHandle 切り替えたいwindowのnameかhandle
- @return {!webdriver.promise.Promise.} 指定されたwindowに切り替わったらresolveするPromise
別のwindowに全てのコマンドの向き先を切り替える
windowは{@code window.name}属性、あるいはhandle({webdriver.WebDriver#getWindowHandles}から返される値)によって指定されます
もし、指定されたwindowが存在しなければ、{bot.ErrorCode.NO_SUCH_WINDOW} errorを返却します
webdriver.WebDriver.TargetLocator.prototype.alert()
- @return {!webdriver.AlertPromise} 開いているalert
alertダイアログにフォーカスを移動する
このコマンドは、現在開いてるalertダイアログがない場合{bot.ErrorCode.NO_SUCH_ALERT} errorを返します
WebElementのAPI
DOM要素を表現します。 WebElementは{webdriver.WebDriver}インスタンスを利用して、document root, あるいは以下のように他のWebElementから要素を検索して見つける事が出来ます
driver.get('http://www.google.com');
var searchForm = driver.findElement(By.tagName('form'));
var searchBox = searchForm.findElement(By.name('q'));
searchBox.sendKeys('webdriver');
WebElementにはPromise APIと互換のあるPromiseが実装されています
このPromiseは内部のstateが完全にresolveされ、コマンドが要素に対して発行可能となったときresolveします
このPromiseは要素がページ上で見つからなかった時のエラーハンドリングに使えます:
driver.findElement(By.id('not-there')).then(function(element) {
alert('Found an element that was not expected to be there!');
}, function(error) {
alert('The element was not found, as expected');
});
webdriver.WebElement.equals(a, b)
- @param {!webdriver.WebElement} a WebElement
- @param {!webdriver.WebElement} b 比較するWebElement
- @return {!webdriver.promise.Promise.<boolean>} 2つのWebElementが同一かどうかの判定をし終えたらresolveするPromise
2つのWebElementが等価か判定する
webdriver.WebElement.prototype.findElement(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator 要素を探索するロケータ
- @return {!webdriver.WebElement} 見つかったWebElement. もし要素がなかった場合は無効となり、スケジュールされたコマンドは中止されます
この要素の子孫要素から指定された要素を探索します
要素が見つからない場合は、{bot.ErrorCode.NO_SUCH_ELEMENT}がドライバによって返されます
他のコマンドとは異なり、このエラーを抑制することができません
つまり、要素を検索するためのコマンドをスケジュールするということは、要素がページ上に存在するかどうかを確かめるアサートを兼ねています
要素がページ上に存在するかどうかをテストしたい時は、{#isElementPresent}を代わりに使用します
要素の検索条件は、{webdriver.By}のfactoryを使用して定義されたlocatorか、または、そのショートハンドの{webdriver.By.Hash}を利用することができます
たとえば、次の2つのステートメントは等価です
var e1 = element.findElement(By.id('foo'));
var e2 = element.findElement({id:'foo'});
また、custom locatorを作って要素を探索することもできます。 その場合、custom locatorの第一引数にwebdriverインスタンスが渡され、そのメソッドは{webdriver.WebElement}こととなります
たとえば、ページ内で最初に現れるvisibleなリンクを見つけるコードは以下のように書けます:
var link = element.findElement(firstVisibleLink);
function firstVisibleLink(element) {
var links = element.findElements(By.tagName('a'));
return webdriver.promise.filter(links, function(link) {
return links.isDisplayed();
}).then(function(visibleLinks) {
return visibleLinks[0];
});
}
webdriver.WebElement.prototype.isElementPresent(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator 要素を探索するロケータ
- @return {!webdriver.promise.Promise.<boolean>} ページ内での要素の有無判定が完了したらresolveするPromise
子孫要素に与えられた条件の要素があるかどうかを判定する
webdriver.WebElement.prototype.findElements(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator 要素を探索するロケータ
- @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} WebElementsの配列が取得できた時にresolveするPromise
与えられた条件にマッチする子孫要素を全て探索します
webdriver.WebElement.prototype.click()
- @return {!webdriver.promise.Promise.<void>} クリックコマンドが完了したときにresolveされるPromise
要素をクリックします
webdriver.WebElement.prototype.sendKeys(var_args)
- @param {...(string|!webdriver.promise.Promise<string>)} var_args タイプするキー (複数指定した場合それらを一つにあわせた入力を行う)
- @return {!webdriver.promise.Promise.<void>} タイプ後にresolveされるPromise
実行対象のDOMに対して入力するコマンドをスケジューリングします。 (たとえばinput(type="text")
とかにテキスト入力したりとかに使う
修飾キー(SHIFT, CONTROL, ALT, META)も用意されており、これらは以降の入力に対して影響を与える。 新しい修飾キーを発行した場合は以降の入力では以前の修飾キーの作用は上書きされ新しい修飾キーのみがその入力に対して影響を与える。
たとえば
driver.findElement(By.id('name')).sendKeys("text was", webdriver.Key.SHIFT, "abc", webdriver.Key.NULL, "def");
とした場合は text wasABCdef
となる
.sendKeys(webdriver.Key.SHIFT, 'abc')
は .sendKeys(webdriver.key.chord(webdriver.Key.SHIFT, 'abc'))
のショートハンドです
タイプ文字の最後にきた時は自動的に修飾キーが解放されます。
入力がファイルの場合(input(type="file")
)はパスを指定することでファイルの入力ができます
以下のコードを実行するとファイル選択ダイアログが開き、与えられたパスのファイルを自動的に選択します
var form = driver.findElement(By.css('form'));
var element = form.findElement(By.css('input[type=file]'));
element.sendKeys('/path/to/file.txt');
form.submit();
アップロードをうまく機能させるためには入力するパスはこのスクリプトを実行してるローカルマシンではなく実行されるブラウザのあるマシン上のパスである必要があります(remote実行との場合は注意してください)
リモートのseleniumサーバに対して実行する場合、webdriver.FileDetector
を利用してブラウザにファイルをアップロードする前に、ファイルをリモートマシンに透過コピーすることもできます。
webdriver.WebElement.prototype.getTagName()
- @return {!webdriver.promise.Promise.<string>} 要素のタグ名が取得できたらresolveするPromise
要素のtag(node)の名前を取得する
webdriver.WebElement.prototype.getCssValue(cssStyleProperty)
- @param {string} cssStyleProperty cssプロパティの名前
- @return {!webdriver.promise.Promise.<string>} cssの設定値が取得できた時にresolveするPromise
対象のDOMに対して適用されてるスタイルを問い合わせるコマンドをスケジュールします。
要素がその親から指定されたスタイルを継承する場合、その親のスタイルも照会されます。
カラーコードは可能であれば16進表記で返されます(rgbじゃなくて#00ff00とかになります)
※返される値はブラウザが解釈するので適切なアサーションを作成するのは難しいかもしれないです
webdriver.WebElement.prototype.getAttribute(attributeName)
@param {string} attributeName 属性名
@return {!webdriver.promise.Promise.<?string>} 属性が取得できた時にresolveするPromise
対象の要素から指定された属性の値を問い合わせるコマンドをスケジュールします。
以下の属性はboolとみなされて、trueもしくはnullを返します。
async, autofocus, autoplay, checked, compact, complete, controls, declare,
defaultchecked, defaultselected, defer, disabled, draggable, ended,
formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
selected, spellcheck, truespeed, willvalidate
webdriver.WebElement.prototype.getText()
- @return {!webdriver.promise.Promise.<string>} (visibleな)テキストが取得できた時にresolveするPromise
cssによってhiddenとかで隠されてないようなテキストを取得します。
この結果は子要素の結果も含みます。
※先頭・末尾のホワイトスペースはトリミングされた結果を返します
webdriver.WebElement.prototype.getSize()
- @return {!webdriver.promise.Promise.<{width: number, height: number}>} 要素のサイズが取得できた時にresolveするPromise
サイズは {@code {width:number, height:number}}
なオブジェクト表現で返される
対象のDOMの要素サイズを取得する(単位はpixel)
webdriver.WebElement.prototype.getLocation()
- @return {!webdriver.promise.Promise.<{x: number, y: number}>} 要素の座標が取得できた時にresolveするPromise
要素のページ内での座標を取得するコマンドをスケジュールします。
座標は {@code {x:number, y:number}}
なオブジェクト
webdriver.WebElement.prototype.isEnabled()
- @return {!webdriver.promise.Promise.<boolean>} 要素がenabledかどうかの判定が得られた時にresolveするPromise
対象のDOMが(disabled属性によって)無効になっていないかどうかを判定するコマンドをスケジュールします
webdriver.WebElement.prototype.isSelected()
- @return {!webdriver.promise.Promise.<boolean>} 要素が現在選択されているかどうかの判定が得られた時にresolveするPromise
要素がselectedかどうかを判定するコマンドをスケジュールします
webdriver.WebElement.prototype.submit()
- @return {!webdriver.promise.Promise.<void>} formをsubmitしたらresolveするPromise
この要素がform要素に含まれてるか、あるいはform要素自身の場合、そのformをsubmitします.
要素がformに含まれていない場合はこのコマンドは何もしません
webdriver.WebElement.prototype.clear()
- @return {!webdriver.promise.Promise.<void>} 要素のvalueをクリアできたらresolveするPromise
要素の{@code value}をクリアします. 対象の要素がinput要素やtextarea要素でない場合、このコマンドは効果がありません
webdriver.WebElement.prototype.isDisplayed()
- @return {!webdriver.promise.Promise.<boolean>} 要素がvisibleかどうかの判定が得られた時にresolveするPromise
要素が表示されてるかどうかの判定をします
webdriver.WebElement.prototype.getOuterHtml()
- @return {!webdriver.promise.Promise.<string>} 要素の外側のHTMLを取得したらresolveするPromise
この要素の外側のHTMLを取得する
webdriver.WebElement.prototype.getInnerHtml()
- @return {!webdriver.promise.Promise.<string>} 要素の内側のHTMLを取得したらresolveするPromise
要素の内側のHTMLを取得する
UntilのAPI
until.Condition(message, fn)
- @param {string} message 条件名 右のように出力される "Waiting [...]" ...が条件名
- @param {function(!webdriver.WebDriver): OUT} fn 条件(lazyloopで都度検証される)
driver.waitなどで利用する条件を生成するコンストラクタ
例えば内部で実装されているアラートがでるまで待機する条件は以下のように実装されている
exports.alertIsPresent = function alertIsPresent() {
return new Condition('for alert to be present', function(driver) {
return driver.switchTo().alert().catch(function(e) {
if (!(e instanceof error.NoSuchAlertError)) {
throw e;
}
});
});
};
until.ableToSwitchToFrame(frame)
- @param {!(number|webdriver.WebElement| webdriver.Locator|webdriver.By.Hash| function(!webdriver.WebDriver): !webdriver.WebElement)} frame frameのid.
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
frame(window)が与えられたframe idのものに切り替わるまで待機する条件を生成する
frameは以下の3つの方法で指定できる
- 現在選択されているフレームwindow.framesの数値インデックス
- 現在のページ上のframe,もしくはiframe要素(webdriver.webElement)
- 2を探索するためのLocator
until.alertIsPresent()
- @return {!until.Condition.<!webdriver.Alert>} 条件(Conditionのインスタンス).
alertが開くまで待機するする条件を生成する
until.titleIs(title)
現在のページのタイトルと与えられた値の等価チェックが終わるまでwaitするconditionを作る
until.titleContains(substr)
- @param {string} substr ページタイトルに含まれてることを期待する文字列
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
現在のページのタイトルが指定された部分文字列を含む状態になるのを待つ条件を生成する
until.titleMatches(regex)
- @param {!RegExp} regex ページタイトルにマッチすることを期待する正規表現
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
現在のページタイトルが指定された正規表現にマッチするまで待つ条件を生成する
until.elementLocated(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator 要素を探索するロケータ
- @return {!until.Condition.<!webdriver.WebElement>} 条件(Conditionのインスタンス).
与えられたロケータで要素が見つかるまで待つ条件を生成する
until.elementsLocated(locator)
- @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator 要素群を探索するロケータ
- @return {!until.Condition.<!Array.<!webdriver.WebElement>>} 条件(Conditionのインスタンス).
与えられたロケータで少なくとも一つの要素が見つかるまで待つ条件を生成する
until.stalenessOf(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
与えられた要素が古くなる(削除されたり、新しいページがロードされたりする)まで待つ条件を生成する
until.elementIsVisible(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
与えられた要素がvisibleになるまで待つ条件を生成する
until.elementIsNotVisible(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
与えられた要素がvisibleじゃなくなるまで待つ条件を生成する(DOM上にはまだ存在している状態)
until.elementIsEnabled(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
与えられた要素がenabledになるまで待つ条件を生成する
until.elementIsDisabled(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素がdisabledになるまで待つ条件を生成する
until.elementIsSelected(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素がselectedになるまで待つ条件を生成する
until.elementIsNotSelected(element)
- @param {!webdriver.WebElement} element 対象の要素
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素の選択が解除されるまで待つ条件を生成する
until.elementTextIs(element, text)
- @param {!webdriver.WebElement} element 対象の要素
- @param {string} text 期待する文字列
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素の中のテキストが期待値のテキストと等しくなるまで待つ情景を生成する
※要素のテキストはwebdriver.WebDriver#getText
で得られるもの
until.elementTextContains(element, substr)
- @param {!webdriver.WebElement} element 対象の要素
- @param {string} substr 含まれるのを期待する文字列.
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素の中のテキストが期待値のテキストを含むようになるまで待つ情景を生成する
※要素のテキストはwebdriver.WebDriver#getText
で得られるもの
until.elementTextMatches(element, regex)
- @param {!webdriver.WebElement} element 対象の要素
- @param {!RegExp} regex マッチするのを期待する正規表現
- @return {!until.Condition.<boolean>} 条件(Conditionのインスタンス).
対象の要素の中のテキストが期待値の正規表現にマッチするまで待つ情景を生成する
※要素のテキストはwebdriver.WebDriver#getText
で得られるもの
LocatorのAPI
要素を探索するAPIです。
webdriver.By.Hash
主要なロケータのショートハンド表現です.
例えば以下の2つのステートメントは等価です
For example the following two statements are equivalent:
var e1 = driver.findElement(webdriver.By.id('foo'));
var e2 = driver.findElement({id: 'foo'});
@typedef {(
{className: string}|
{css: string}|
{id: string}|
{js: string}|
{linkText: string}|
{name: string}|
{partialLinkText: string}|
{tagName: string}|
{xpath: string})}
webdriver.By.className(className)
与えられたクラス名の要素を探索する. 返されるロケータはCSSセレクタ「.clazz」を持つ要素の検索に相当します。
webdriver.By.css(selector)
CSSセレクタを用いて要素を探索します. CSSセレクタをサポートしてないブラウザの時は、WebDriverは{bot.Error.State.INVALID_SELECTOR invalid selector} errorを返すことがあります.
しかしながら、CSSセレクタAPIをエミュレートするかもしれません.
webdriver.By.id(id)
IDを用いて要素を探索します
webdriver.By.linkText(text)
与えられたリンク文字列({webdriver.WebElement#getText visible text})を用いて要素を探索します
/*<a href="http://google.com">google</a>*/
driver.findElement(By.linkText("google")).click();
webdriver.By.js(script, var_args)
- @param {!(string|Function)} script 実行するスクリプト
- @param {...*} var_args スクリプトに与える引数
- @return {function(!webdriver.WebDriver): !webdriver.promise.Promise} JavaScriptベースの新しいlocator関数
javascriptをブラウザ側で実行(webdriver.executeScript
)して得られた要素をそのまま取得する
driver.findElement(By.js(function () {
var ele1 = document.getElementById('#ele1')
, ele2 = document.getElementById('#ele2')
, position
;
position = ele1.compareDocumentPosition(ele2);
return (val === 4) ele1 : ele2;
})).click();
webdriver.By.name(name)
与えられた{@code name}属性をもつ要素を探索する
webdriver.By.partialLinkText(text)
与えられた文字列がwebdriver.WebElement#getText
に含まれるリンク要素を探索する
webdriver.By.tagName(tag name)
指定されたタグ名を持つ要素を探索する
タグ名は getElementsByTagNameで利用するものと同じです。
webdriver.By.xpath(xpath)
与えられたXPathに一致する要素を探索します。
webdriver.Locator.checkLocator(value)
- @param {*} value 検証対象のロケータ
- @return {!(webdriver.Locator|Function)} 有効なロケータ
- @throws {TypeError} もしロケータがinvalidであれば例外をthrowする
与えられたロケータがページ上の要素を検索するために使用する有効なロケータであることを検証します。