アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

WebdriverIOを使ってみました

最近、「WebdriverIO がいい」という話を耳にしたので使ってみました。
軽く実案件の自動テストにも組み込んでみたのですが、かなり使いやすいと感じたので簡単に使い方をご紹介します。

WebdriverIOとは



UI操作を自動化するためのJSライブラリです。(執筆現在の最新バージョンはv4.2.8)
ブラウザ操作を同期的に記述でき、APIもシンプルに記述できるよう配慮されているため、
複雑になりがちなUI操作のテストコードをかなりシンプルに記述することができます。

構成



今回は以下の環境・バージョンで試しました。

OS : Mac OS El Capitan 10.11.6(15G31)
NodeJS : v6.4.0
Chrome バージョン 52.0.2743.116 (64-bit)

webdriverio : 4.2.8
selenium-standalone : 5.6.1
selenium standalone serverをjavascriptから立てるためのライブラリです。

今回はローカルにselenium standalone serverを立ててでテストを実行する想定で書いていますが、
リモートマシンやSelenium Gridだけでなく、
テスティングクラウドサービス(Sause Labs/Browserstack/Testingbot)等との連携も可能です。

インストール



(npm initは完了しているものとして)、ひとまずWebdriverIOをインストールしましょう。



$ npm install webdriverio --save-dev


また、selenium standalone serverを立ち上げる必要があるので、selenium-standaloneをインストールしておきます。




$ npm install selenium-standalone --save-dev


設定ファイル作成



次に、wdioを実行するための設定ファイルを作成します。
npm initやkarma initのように、対話的に設定ファイルを作成できるwdioのconfigコマンドが用意されているので利用しましょう。



$ ./node_modules/.bin/wdio config


質問内容は以下のとおりです。
※ ライブラリやadapterをインストールするか?という質問は省略しています


  1. Seleniumの動作環境(On my local machine)

  2. 使用するテストフレームワーク(jasmine)

  3. Specファイルの配置場所(./test/specs/*.js)

  4. 使用するレポーターの種類(dots)

  5. 使用するサービス(selenium-standalone)

  6. logレベル(error)

  7. エラー発生時のスクリーンショットの配置場所(./errorShots)

  8. テストサーバーのbase url(localhost:8000)



テストの実行



先ほど指定した場所にテストを作成・配置し、以下のコマンドでwdio testrunnerを実行します。
wdio.conf.jsのserviceにselenium-standaloneを登録しておくとテスト実行時に自動でselenium standalone serverが立ち上がります



$ ./node_modules/.bin/wdio


サンプルコード



典型的な操作をいくつかピックアップしてご紹介していきます。

※ browserオブジェクトについて
wdio test runnerを使用した場合、webdriverのインスタンスにbrowserというグローバル変数でアクセスできるようになります。(参考


ブラウザにアクセスしてタイトルを取得する





describe('WDIOのテスト', () => {
    it('画面の表示', () => {
        browser.url('/index.html');
        expect(browser.getTitle()).toBe('Hello WebdriverIO');
    });  
});


ユーザーIDとパスワードを入力してログイン





describe('Login Page', () => {
    beforeEach(() => {
        browser.url('/index.html');
    });

    it('should translate to todo list when submit correct userid  & password', () => {
        let title = browser.setValue('#userid', 'test@example.com')
            .setValue('#password', 'password')
            .click('#login')
            .getTitle();
        expect(title).toBe('Login Succeeded');
    });
});



キャプチャの取得




describe('Login Page', () => {
    beforeEach(() => {
        browser.url('/login.php');
    });
    it('should have right title', () => {
        browser.saveScreenshot('./images/login.png');
    });
});


また、こちらのページを参考にPage Objectを作成しておくと、より読みやすく変更に強いテストが作成できそうです。


wdio test runnerを使用しない場合



他のテストランナーと組み合わせる場合は非同期なAPIとなります。
以下のサンプルのようにco + generatorで同期的に書くこともできますが、いちいちyieldで止める必要があるので少々不便になります。

例:jasmine + jasmine-co + webdriverioを組み合わせて使用する場合のサンプルです。



require('jasmine-co').install();

const webdriverio = require('webdriverio');
const browser = webdriverio.remote({
    desiredCapabilities: { browserName: 'chrome' },
    baseUrl: 'http://127.0.0.1:8000'
});

describe('Login Page', () => {
    beforeAll(function* () {
        yield browser.init();
    });
    beforeEach(function* () {
        yield browser.url('/login.php');
    });
    it('should have right title', function* () {
        let title = yield browser.getTitle();
        expect(title).toBe('Login Page');
    });
});


あとがき



簡単な機能しか紹介していませんが、いかがでしたでしょうか。

次期Mac OS Xにて SafariWebDriver Supportが発表され、各ブラウザのWebDriverが一通り揃いつつあります。
また、今回紹介したWebdriverIOに限らずクライアント側のツールもこなれてきました。

UIテスト自動化を試してみるにはいい時期になってきたのではないでしょうか。
その時には是非WebdriverIOを試してみてください。

Selenium WebDriverでマウス操作 & 処理待機

はじめに



Selenium WebDriverを使えばブラウザを自動操作できます。WebDriverは単純なクリックから複雑なマウス操作や非同期処理の確認などもできます。最近のWebサイトやWebシステムではAjaxが多用されていたり、ドラッグ&ドロップを使用していたりと、結構複雑です。そんな場合のSelenium WebDriverの簡単な使い方をご紹介します。

Selenium WebDriverとは?」「どうやって使うのか?」と思った方は、簡単・便利、ブラウザの自動操作!~Selenium WebDriver~をご一読下さい。


複数操作



マウスで複数の操作を一連の動きとして実行する場合、どのようにするのか、簡単にやってみます。アシアル・ホームページのトップ画面上部に、下の画像の部分があります。


この部分(スライダー)をマウスでドラッグすると、その下の構築事例や会社情報が切り替わります。このドラッグをSelenium WebDriverで実行してみます。以下のコードを実行すると、スライダーが右に移動し、パネル部分が変化します。


package jp.co.asial.test;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;

public class AsialHpSliderTest {

    public static void main(String[] args) {
        WebDriver driver = new FirefoxDriver();
        driver.get("http://www.asial.co.jp");
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        
        // 興味のある技術分野の選択
        Action action = new Actions(driver)
            .clickAndHold(driver.findElement(By.cssSelector(".ui-slider-handle")))
            .moveByOffset(100, 0)
            .release()
            .build();
        action.perform();
    }
}

実際にマウスでスライダーを移動させる場合、マウス・ダウン、ドラッグ、リリースの3つの処理を実行します。このようにマウス操作を同時に複数行う場合、SeleniumではActionsクラスを使います。このクラスは、マウスの基本操作を実行するメソッドを多数保持しています。上記の通りBuilderパターンを使ってインスタンスを作成し、peform()メソッドで一連の処理を実行します。

単純なドラッグ&ドロップのみであれば、以下のようにも記述できます。dragAndDropBy()メソッドはclickAndHold()、moveByOffset()、release()を組み合わせたメソッドです。


Action action = new Actions(driver)
    .dragAndDropBy(driver.findElement(By.cssSelector(".ui-slider-handle")), 100, 0)
    .build();
action.perform();



アニメーション終了確認



先のコードではブラウザを開いたまま処理を終えました。本当はアニメーション終了とともにブラウザを閉じたいのですが、素直には実行できません。例えば、上記コードの最後にdriver.quit()を入れると、アニメーション前にブラウザを閉じてしまいます。ドラッグ&ドロップを終えるとすぐに次の処理を実行してしまうからです。

意図通りに動かしたい場合、処理を中断(待機)し、適切なタイミングで再開する必要があります。こういう場合、WebDriverWaitクラスを用いて待機処理を実装します。

上記パネルの更新は次のようになっています。

  1. パネル要素を画面外へ移動する
  2. 新しい要素を追加する
  3. 古い要素を削除する
  4. 新しいパネルを画面内へ移動する

このパネル更新を確認するためには、①パネルが動き出すまで待ち、②新しい要素が画面内の定位置に来るまで待つ、ことが必要です。これは以下のように実装できます。


package jp.co.asial.test;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

public class AsialHpSliderTest {

    public static void main(String[] args) {
        WebDriver driver = new FirefoxDriver();
        driver.get("http://www.asial.co.jp");
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        
        // スライダーを移動
        Action action = new Actions(driver)
            .dragAndDropBy(driver.findElement(By.cssSelector(".ui-slider-handle")), 100, 0)
            .build();
        action.perform();

        // 画面が動き出すまで待つ: 10秒間、50ms間隔で確認
        new WebDriverWait(driver, 10, 50).until(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                final WebElement element = driver.findElement(By.cssSelector(".topCardList > article:first-child"));
                return !element.getCssValue("top").equals("0px");
            }
        });

        // 最初の要素の位置が確定するまで待つ: 10秒間、100ms間隔で確認
        new WebDriverWait(driver, 10, 100).until(new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                final WebElement element = driver.findElement(By.cssSelector(".topCardList > article:nth-last-child(21)"));
                return element.getCssValue("top").equals("0px");
            }
        });

        driver.quit();
    }
}

WebDriverWaitコンストラクタの第二、第三引数にはそれぞれ最大待ち時間[秒]と確認間隔時間[ミリ秒]を渡します。そして、WebDriverWaitオブジェクトのuntil()メソッドへExpectedConditionオブジェクトを渡すことで処理を待機します。この仕組みはAjaxなどの非同期処理を確認する際には多用します。ちなみに、アシアル・サイトのパネル操作でもAjax通信を行っています。

実際のところ、上記のコードはたまに失敗してしまいます。2番目の待機で、最初の要素を上手く特定できないことがあるためです。Webサイトの自動テスト等を考える場合、HTMLの構造とJavaScriptの使い方も考える必要がありそうです。適切にIDを割り当てる、Ajaxが終了したら何らかの属性を変化させるなど、ちょっとした処理を入れておくと自動操作もかなり楽になります。もちろん、複雑にならないようなバランス感覚も重要です。


おわりに



マウス操作や処理待機の扱いになれれば、Selenium WebDriverを使ったブラウザ自動操作もかなり楽になります。その他にもダブルクリックや、キーボード入力なども扱えます。かなり万能に操作やテストを実行できますので、ぜひ試してみて下さい。