LoginSignup
125

More than 5 years have passed since last update.

WebDriver+JavaScriptでWebアプリのE2Eテスト

Last updated at Posted at 2014-12-09

いろいろ調べたけれど、まだ決められないなー、というお話です。

Webアプリが正しく動作することを、Webブラウザーを操作して確認する E2E (End-to-End) テスト。テストの記述には様々なプログラミング言語が使えます。

Selenium WebDriver + JavaScript で E2E テストをするやり方が

An Introduction to WebDriver Using the JavaScript Bindings - Tuts+ Code Tutorial

で紹介されています。

この記事は、基本となる WebDriverJS 以外に、7つのクライアントAPIライブラリーを紹介しています。どれも github で公開されていたので、スター数を調べてみました (2014/12/07時点と2015/09/12時点)。また InternProtractor についても調べました。

Client API スター数 2014/12 スター数 2015/09 最新スター数 補足
WebDriverJS N/A N/A GitHub stars W3Cで標準化しているAPIで書く。JavaでSelenium動かしていた人向け。
WD.js 658 845 GitHub stars
WebDriver.io 661 1,132 GitHub stars WD.jsより短く書ける。
Testium 203 263 GitHub stars CoffeeScript で書く。
Leadfoot 63 107 GitHub stars Internで使われてる。WD.jsと似たAPI。
Nightwatch 2,386 3,221 GitHub stars CI、ブラウザーテストのクラウドサービス連携。
DalekJS 481 600 GitHub stars Selenium Server まで入った全部入り。Webサイトが派手w。
Webdriver-sync 36 71 GitHub stars Java APIに準拠。同期型。
Intern 未計測 3,006 GitHub stars 全部入り。単体テスト(コードカバレッジ)、CI、ブラウザーテストのクラウドサービス連携。
Protractor 未計測 4,355 GitHub stars AngularJS ためのE2Eテストフレームワークだが AngularJS 以外にも使える。

さらに、WebdriverJS, WD.js, WebdriverIO, Nightwatch, Dalek, Intern, Protractor の7つについて、実際にコードを書いてみました。

お題は「Googleで"webdriver"を検索した時のヒット数を標準出力に表示する」です。assert は使いません。

共通の事前準備

Mac を使います。Web ブラウザーは Chrome です。
Homebrew と Cask で node.js と java をインストールします。

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew install node
$ brew brew install brew-cask
$ brew cask install java

さらに webdriver-manager をインストールします。webdriver-manager update コマンドで selenium-server, chrome-driver をインストールします。

$ npm install -g webdriver-manager
$ webdriver-manager update

コードを実行する前に、Selenium Server を起動しておきます。ただし、Dalek の場合は不要です。

$ webdriver-manager start

WebDriverJS

APIはこちら。豊富です。
http://selenium.googlecode.com/git/docs/api/javascript/index.html

インストール

$ npm install selenium-webdriver

コード

webdriverjs.js:

var webdriver = require('selenium-webdriver');
var By = webdriver.By;

//WebブラウザーはChrome
var driver = new webdriver.Builder().
   withCapabilities(webdriver.Capabilities.chrome()).
   build();
var $ = driver.findElement.bind(driver);

//Googleを開く。
driver.get('http://www.google.com');

//検索ボックスにwebdriverと入力する。
$(By.name('q')).sendKeys('webdriver');

//検索ボタンを押す。
$(By.name('btnG')).click();

//ヒット数が表示されるまで待つ。
var timeoutMSec = 2000;
driver.wait(webdriver.until.elementLocated(By.id('resultStats')), timeoutMSec)
.then(function() { //waitした後はthenでつなぐ
    $(By.id('resultStats')).getText().then(function(text) {
        console.log(text);
    });
})
.then(function() {
    driver.quit();
});

実行

$ node webdriverjs.js 
約 615,000 件 (0.11 秒) 

WD.js

sendKeys() など WebDriverJS の名残があります。また、waitForElementByCss()など、APIが若干長めです。

インストール

$ npm install --save-dev wd

コード

wd.js:

var wd = require('wd');
var browser = wd.promiseChainRemote();
var timeoutMSec = 1000;
browser
    .init({
        browserName: 'chrome'
    })
    .get('http://www.google.com')
    .elementByName('q')
    .sendKeys('webdriver')
    .elementByName('btnG')
    .click()
    .waitForElementByCss('#resultStats', timeoutMSec)
    .text(function(err, text) {
        console.log(text);
    })
    .quit();

実行

$ node wd.js
約 615,000 件 (0.12 秒)

Webdriver.io

今回試した中では、API 名が短くて好きです。

インストール

$ npm install --save-dev webdriverio

コード

webdriverio.js:

var timeoutMSec = 1000;
var webdriverio = require('webdriverio')
    .remote({
        desiredCapabilities: {
            browserName: 'chrome'
        }
    })
    .init()
    .url('http://www.google.com')
    .setValue('[name="q"]', 'webdriver')
    .click('[name="btnG"]')
    .waitForExist('#resultStats', timeoutMSec)
    .getText('#resultStats', function(err, text) {
        console.log(text);
    })
    .end();

実行

$ node webdriverio.js 
約 615,000 件 (0.12 秒) 

Nightwatch

runner を使って実行するので、出力が綺麗です。
デフォルトのブラウザーが Firefox で、Chrome を使うためには設定ファイルが必要です。

インストール

$ npm install --save-dev nightwatch

コード

nightwatch.json:

{
  "test_settings" : {
    "default" : {
      "silent": true,
      "desiredCapabilities": {
        "browserName": "chrome"
      }
    }
  }
}

nightwatch.js:

module.exports = {
  "webdriverの検索ヒット数を表示する" : function (browser) {
    var timeoutMSec = 1000;
    browser
      .url("http://www.google.com")
      .setValue('[name="q"]', 'webdriver')
      .click('[name="btnG"]')
      .waitForElementPresent('#resultStats', timeoutMSec)
      .getText('#resultStats', function(res) {
          console.log(res.value);
      })
      .end();
  }
};

実行

$ ./node_modules/.bin/nightwatch -t nightwatch.js 

[Nightwatch] Test Suite
=======================

Running:  webdriverの検索ヒット数を表示する 

✔  Element <#resultStats> was present after 1038 milliseconds.
約 615,000 件 (0.14 秒) 

OK. 1 total assertions passed. (11.812s)

DalekJS

Selenium server を動かす必要はありません。

インストール

$npm install --save-dev dalek-cli dalekjs dalek-browser-chrome 

コード

waitForElementが動作しないので、waitForを使いました(github)。
標準出力への表示はexecute()とlog.message()を組み合わせて使います。

dalek.js:

module.exports = {
  'webdriverのヒット数を表示する': function (test) {
    var timeoutMSec = 10000;
    test
      .open("http://www.google.com")
      .setValue('[name="q"]', 'webdriver')
      .click('[name="btnG"]')
      //.waitForElement('#resultStats', timeoutMSec)
      .waitFor(function() {
        return Boolean(document.querySelector('#resultStats'));
      }, [], timeoutMSec)
      .execute(function() {
        var result = document.querySelector('#resultStats').innerText;
        this.data('result', result);
      })
      .log.message(function() {
        return test.data('result');
      })
      .done();
  }
};

実行

引数 -b chrome を与えて Chrome で動かします。
デフォルトではヘッドレスブラウザーの PhantomJS で動かします。

$ ./node_modules/.bin/dalek dalek.js -b chrome
Running tests
Running Browser: Google Chrome
OS: Mac OS X 10.10.1 x86_64
Browser Version: 39.0.2171.71

RUNNING TEST - "webdriverのヒット数を表示する"
▶ OPEN http://www.google.com
▶ SETVALUE [name="q"]
▶ CLICK [name="btnG"]
▶ WAITFOR 
▶ EXECUTE 
☁ [USER] MESSAGE: 約 615,000 件 (0.38 秒) 
✔ 0 Assertions run
✔ TEST - "webdriverのヒット数を表示する" SUCCEEDED

 0/0 assertions passed. Elapsed Time: 4.44 sec 

intern

全部入りです。単体テスト(コードカバレッジ)、CI、ブラウザーテストのクラウドサービス Sauce Labs, BrowserStack との連携など。

参考: Can you add Nightwatch.js to the compare section?

Nightwatch との比較

インストール

$ npm install --save-dev intern
$ mkdir tests
$ cp node_modules/intern/tests/example.intern.js tests/intern.js
$ ./node_modules/.bin/intern-client config=tests/intern
0/0 tests failed

コード

$ mkdir tests/functional

tests/functional/google.js:

define(function (require) {
    var registerSuite = require('intern!object');
    var assert = require('intern/chai!assert');

    registerSuite({
        name: 'intern',
        'greeting form': function () {
            return this.remote
                .get('http://www.google.com')
                .setFindTimeout(5000)
                .findByCssSelector('[name="q"]')
                    .type('webdriver')
                    .end()
                .findByCssSelector('[name="btnG"]')
                    .click()
                    .end()
                .setFindTimeout(5000)
                .findByCssSelector('#resultStats')
                .getVisibleText()
                .then(function (text) {
                    console.log(text);
                });
        }
    });
});

デフォルトでは BrowserStack を使う設定です。ローカルの webdriver を使うため NullTunnel を設定します。

tests/intern.js

define({
...
  environments: [
    { browserName: 'chrome', platform: 'MAC' }
  ],
...
  //tunnel: 'BrowserStackTunnel',
  tunnel: 'NullTunnel',
...
  //functionalSuites: [ /* 'myPackage/tests/functional' */ ],
  functionalSuites: [ 'tests/functional/google' ],
...
});

実行

$ ./node_modules/.bin/intern-runner config=tests/intern
Listening on 0.0.0.0:9000
Tunnel started
‣ Created session chrome on MAC (fd9b05d8-ad42-4b51-ac27-4ce898fc7a66)
約 9,590,000 件 (0.24 秒)  
✓ chrome on MAC - intern - greeting form (1.851s)
No unit test coverage for chrome on MAC
chrome on MAC: 0/1 tests failed


TOTAL: tested 1 platforms, 0/1 tests failed

Protractor

AngularJS ための E2E テストフレームワークですが、工夫をすれば AngularJS 以外の Web アプリでも使えます。assert には Jasmine が組み込まれています。

インストール

$ npm install --save-dev protractor

コード

AngularJS を待つ機能を無効化します(browser.ignoreSynchronization = true)。

AngularJS 以外の Web アプリでは element() が使えないため、シンタックスシュガー querySelector を定義して使っています。

getText() は Promise を返すので、then(callback) で結果を出力します。

protractor.js:

browser.ignoreSynchronization = true; // for non-angular web app

function querySelector(selector) { // for non-angular web app
    browser.wait(function () {
        return browser.isElementPresent(by.css(selector));
    }, 5000);
    return browser.findElement(by.css(selector));
}

describe('protractor example', function() {
    it('shows searched results count for webdriver', function() {
        browser.get('http://www.google.co.jp');
        querySelector('[name="q"]').sendKeys('webdriver');
        querySelector('[name="btnG"]').click();
        querySelector('#resultStats').getText().then(function (text) {
            console.log(text);
        });
    });
});

設定ファイルを作ります。

exports.config = {
    seleniumAddress: 'http://localhost:4444/wd/hub',
    specs: ['protractor.js']
};

実行

設定ファイルを渡して実行します。

$ protractor conf.js
Using the selenium server at http://localhost:4444/wd/hub
[launcher] Running 1 instances of WebDriver
約 323,000 件 (0.24 秒) 
.

Finished in 1.897 seconds
1 test, 0 assertions, 0 failures

[launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #1 passed

どれを選ぶか

調べれば調べるほど決められなくなりますね・・・。コードを builder pattern (fluent interface) で書けるものも多く、選択の決め手にはでしづらいです。

  • チームメンバーが好む assert が使えるか (assert を変更できるか) ?
  • PageObject パターンを適用してテストを構造化するか?
  • End-to-End (E2E) / Functional テストだけでなく、Unit テストも統合するか?カバレッジも計算するか?
  • SauseLabs, BrowserStack といったクラウドサービスを使うか?
  • CI をやるか?

といった観点も必要でした。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
125