テストシナリオのデータ化

WebDriver APIをラップするクラスを利用すると、1操作が1行で表現されるようになりましたが、テスト過程で使用するJavaScript変数も不要になっています。
そのため、プログラムの操作を表すデータに置き換えることも可能となります。

このサンプルは、データに応じた処理を実行するクラスにより、データ化したテストシナリオを実行しています。
テストシナリオをデータ化するメリットは、テストシナリオの作成が容易になるだけではなく、テストシナリオの数が増えた時でも管理が容易になるメリットもあります。

const {Runner} = require("./runner.js");
const { Capabilities, Key } = require("selenium-webdriver");

(async function doTest() {
scenario = [
    // 「トップメニュー」のクリックによるalert表示
    {action:"log", data:"「トップメニュー」の表示を待ちクリック"},
    {action:"click",
        target:"</html/body/div[1]/header/div/h1",timeout:3000},
    {action:"log", data:"Alertを待機"},
    {action:"waitAlert", timeout:1000},
    {action:"log", data:"Alertを閉じる"},
    {action:"dismissAlert"},
    {action:"log", data:"appData変数の値を取得"},
    {action:"exec", script:'return appData;',
        validate:(appData)=>{
        if(appData!=="hello")
            throw new Error(
                "appData is not true. appData=" + appData);
        }
    },
    // アコーディオンメニューの選択
    {action:"log", data:"「問合せボタン」をクリック"},
    {action:"click", target:"</html/body/ul/li[4]/div"},
    {action:"log", data:"「ユーザ登録」の表示を待つ"},
    {action:"waitVisible", target:"#userreg", timeout:3000},
    {action:"log", data:"「ユーザ登録」の表示OK"},
    // スクリーンキャプチャによるメニュー表示テスト
    {action:"log",
        data:"スクリーンキャプチャのために「ユーザ登録」の安定を待つ"},
    {action:"delay", timeout:700},
    {action:"log", data:"スクリーンキャプチャによるメニュー表示テスト"},
    {action:"screenShot",
        compare:"a4cc7834d3f1df879ee9361eb73943735a05792b6a1ed72671692e83ad074bd8",
        saveOnError:"images/snapshot.png"},
    // ユーザ登録ページへ
    {action:"log", data:"「ユーザ登録」をクリック"},
    {action:"click", target:"#userreg"},
    {action:"log", data:"ページ遷移を待つ"},
    {action:"waitURL",
        url:/^http.*\/demo\/reg\.html$/, timeout:1000},
    {action:"log", data:"マウス座標をリセット"},
    {action:"resetMousePosition"},
    // ユーザ情報の入力
    {action:"log", data:"「お名前」入力欄の表示をクリック"},
    {action:"click", target:"#name1"},
    {action:"delay", timeout:100},
    {action:"log", data:"「お名前」の入力"},
    {action:"sendKeys",
        key:["[HanjaMode On]yamada",Key.SPACE,Key.RETURN," "]},
    {action:"sendKeys",
        key:["tarou",Key.SPACE,Key.RETURN,"[HanjaMode Off]"]},
    {action:"sendKeys", key:Key.TAB},
    {action:"log", data:"「メールアドレス」の入力"},
    {action:"sendKeys",
        key:"Tarou.Yamada@nexaweb.com[Select All][Copy]"},
    {action:"sendKeys", key:Key.TAB},
    {action:"log", data:"「メールアドレス(確認用)」の入力"},
    {action:"sendKeys", key:"[Paste]"},
    {action:"sendKeys", key:Key.TAB},
    {action:"log", data:"「電話番号」の入力"},
    {action:"sendKeys", key:"080-4321-8765"},
    {action:"log", data:"「男性」を選択"},
    {action:"click",target:
        "</html/body/div[2]/form/div/div[5]/div/label[1]/input"},
    {action:"log", data:"「職業」をクリック"},
    {action:"click", target:"#job"},
    {action:"log", data:"「職業」のセレクタ表示を待つ"},
    {action:"delay", timeout:500},
    {action:"log", data:"「Webデザイナ」を選択"},
    {action:"click", target:"#job", offset:{x:250,y:-250}},
    {action:"log", data:"「デザインについて」を選択"},
    {action:"click",target:
        "</html/body/div[2]/form/div/div[7]/div[1]/label/input"},
    {action:"log", data:"「マーケティングについて」を選択"},
    {action:"click",target:
        "</html/body/div[2]/form/div/div[7]/div[3]/label/input"},
    // 送信
    {action:"log", data:"「送信する」をクリック"},
    {action:"click", target:"</html/body/div[2]/form/div/button"},
    {action:"log", data:"ページ遷移を待つ"},
    {action:"waitURL",
        url:/^http.*\/demo\/result\.html\?.*$/, timeout:1000},
    {action:"log", data:"ページ遷移完了"},
];

console.log("capabilitiesの設定");
const capabilities = new Capabilities()
    .set("scriptFilter",["\\.html"])
    .setBrowserName("safari+G.O");
console.log("Runner生成");
let runner = new Runner("http://192.168.1.147:7000/wd/hub", capabilities);
console.log("Runner実行");
await runner.run("http://192.168.1.147:8080/demo/index.html", scenario);
console.log("Runner終了");
})();

Runnerクラスの実装例

const {Commander} = require("./commander.js");
const {Builder} = require("selenium-webdriver");

exports.Runner = class Runner {
    constructor(driverUrl, capabilities) {
    this.driverUrl = driverUrl;
    this.capabilities = capabilities;
}
async executeScript(code, validate) {
    let r = await this.cmd.executeScript(code);
    if(validate)
    validate(r);
}
async run(url, scenario) {
    console.log("Test Start");
    const startTime = new Date();
    const driver = new Builder()
        .usingServer(this.driverUrl)
        .withCapabilities(this.capabilities)
        .build();
    const cmd = new Commander(driver);
    let action = null;
    try {
        await cmd.get(url);
        for(action of scenario) {
            if(action.action === "log")
                console.log(action.data);
            else if(action.action === "delay")
                await cmd.delay(action.timeout);
            else if(action.action === "resetMousePosition")
                await cmd.resetMousePosition();
            else if(action.action === "waitURL")
                await cmd.waitURL(action.url,action.timeout);
            else if(action.action === "waitVisible")
                await cmd.waitVisible(
                    action.target,action.timeout);
            else if(action.action === "waitAlert")
                await cmd.waitAlert(action.timeout);
            else if(action.action === "dismissAlert")
                await cmd.dismissAlert();
            else if(action.action === "exec")
                await cmd.executeScript(
                    action.script, action.validate);
            else if(action.action === "screenShot")
                await cmd.takeScreenshot(action);
            else if(action.action === "click")
                await cmd.mouseClick(
                    action.target, action.offset, action.timeout);
            else if(action.action === "move")
                await cmd.mouseMove(
                    action.target, action.offset, action.timeout);
            else if(action.action === "sendKeys")
                await cmd.sendKeys(action.target, action.key);
            else
                throw new Error(
                    "unknown action '"+action.action+"'");
        }
    }
    catch(e) {
        console.error("ERROR:"
            +(action?JSON.stringify(action):"")
            +"\n"+e.name+":"+e.message);
        try {
            await cmd.takeScreenshot({save:"images/error.png"});
        }
        catch(e) {
            console.error(e);
        }
        throw new Error("テスト失敗");
    }
    finally {
        if(driver)
            await driver.quit();
            const endTime = new Date();
            const elapsMs =endTime.getTime()-startTime.getTime();
            const elapsS = Math.floor(elapsMs / 1000);
            const hour = Math.floor(elapsS / 3600);
            const min = Math.floor(elapsS / 60) % 60;
            const sec = elapsS % 60;
            const ms = elapsMs % 1000;
            const tv = hour+"h "+min+"m "+sec+"s "+ms+"ms";
            console.log("Test finished");
            console.log("Time:" + tv);
        }
    }
};

※ このサンプルの関連ファイルはSeleniumSamples.zipのsec8_dataizedAPIに収められています。