【Node.js】Seleniumでテスト自動化ツール用ライブラリを作った
はじまり






Seleniumについて
Seleniumとは、テスト自動化ツールであり、主にJava、Ruby、Python、Javascriptで使用できます。
今回は、Javascript(Node.js)上で動くテスト自動化ツールを作っていきました。利用したOSはWindows 10で、ブラウザはChromeです。
Seleniumを使える環境を作る
まずは環境構築です。最初にNode.jsをインストールします。こんなPowershellの画像が出てきたらインストール完了です。

次に、npm installします。
npm install selenium-webdriver --savenpm install mocha --saveそしたら、最後にchromedriverを入手します。僕はこのページから入手しました。
そして、入手したchromedriverを実行したいフォルダの中に置いたら、Seleniumを実行する環境は完成です!
最小構成:
<execution_directory>│ chromedriver.exe│ index.js│ package.jsonこのフォルダの状態で以下のコマンドを実行すると、テストが開始されます。
npx mocha index.js --timeout 0package.jsonの中身
package.jsonはそのフォルダ内に適用されるコンフィグの意味合いを持つファイルです。
そして、Package.jsonを編集すると、実行するスクリプトを短く出来ます。例えば以下のように設定するとこのように入力するとテストを開始できます。
package.json:
{ "scripts": { "psl": "npx mocha kouhochi-ichiran.js --timeout 0" }}シェル:
npm run pslツールを作るためのライブラリ
今回、テスト自動化ツールを作るためのライブラリを作りました。
ディレクトリ構成としては下記のようなイメージです。
./├── lib/│ ├── lib-methods.js│ ├── lib-variables.js│ └── getXpathByElement.js├── screen-01.js├── screen-02.js└── package.json細かいソースは公開できませんが、役割としては、以下のように分担させました。
lib-variables.js📖
このファイルにはクラスだけが入っています。そして、それらのクラスは、変数として使用してもらうための属性値を持っています。例えば、個々の画面の各々の要素のXPathが入っていたりします。
lib-methods.js🦾
関数とクラスが入っているファイルです。テストをするためによく使う処理をまとめました。
OperatorやInspectorは、基本的にとある属性(もしくは要素)からどの属性(もしくは要素)を取得したりクリックするかが記述されたメソッドを持っているクラスになります。こんなイメージです。

getXpathByElement.js🧗
一般的にDOMから要素のXPathを取得する方法は、ブラウザの開発者ツールからとされています。 しかし、Seleniumで要素にアクセスするためにいちいちDOMを右クリックしてXPathを取得するのも面倒だと思います。僕の場合は、300回右クリックしてXPathをメモするのは嫌だったので、一気にXPathを取得する方法を探しました。
今回の方法では、2つファイルを作り実現しました。作るファイルは下記の責務で分かれています:
- ブラウザで実行させるスクリプトが入ったファイル
- 普通にseleniumを実行するファイル
Chromeで実行し、対象のCSSクラス名はcheckboxでした。
まず、ブラウザ側のファイルです。こちらのQiita記事を参考にしました。
最後の方は文法としては変ですが、配列のインデックスをスクリプトに載せるために"scriptSeparator"と記述しています。
function getXpathByElement (element) { var NODE_TYPE_ELEMENT_NODE = 1;
if (element instanceof Array) { element = element[0]; }
if (element.nodeType != NODE_TYPE_ELEMENT_NODE) { throw new ErrorException('nodes other than the element node was passed. node_type:'+ element.nodeType +' node_name:'+ element.nodeName); }
var stacker = []; var node_name = ''; var node_count = 0; var node_point = null;
do { node_name = element.nodeName.toLowerCase(); if (element.parentNode.children.length > 1) { node_count = 0; node_point = null; for (i = 0;i < element.parentNode.children.length;i++) { if (element.nodeName == element.parentNode.children[i].nodeName) { node_count++; if (element == element.parentNode.children[i]) { node_point = node_count; } if (node_point != null && node_count > 1) { node_name += '['+ node_point +']'; break; } } } } stacker.unshift(node_name); } while ((element = element.parentNode) != null && element.nodeName != '#document');
return '/' + stacker.join('/').toLowerCase();}
return getXpathByElement(document.getElementsByClassName("checkbox")["scriptSeparator"]);そして、seleniumを実行するファイルです。
const { Builder, By } = require('selenium-webdriver');const fs = require("fs");const _ = require("lodash");
const getTextLines = (fileName) => { const separator = '\n'; let text = fs.readFileSync(`./${fileName}`, 'utf8'); let lines = text.toString().split(separator); return lines;}
const getXpathsByClassName = async(driver, className) => { let elements = []; let xpath = ''; let xpaths = []; const scriptFileName = 'lib/getXpathByElement.js'; let script = await getTextLines(scriptFileName).join('\t'); let scripts = await script.toString().split('\"scriptSeparator\"'); elements = await driver.findElements(By.className(className)); for (let i = 0; i < elements.length; i++){ xpath = await driver.executeScript(`${scripts[0]}${i}${scripts[1]}`); xpaths.push(_.cloneDeep(xpath)); } return new Promise( resolve => resolve(xpaths) );};
let driver;describe("テスト", () => { before(() => { driver = new Builder().forBrowser("chrome").build(); });
after(() => { return driver.quit(); });
it{(`URLを開いたり、対象の要素を画面上に表示する。`), async () => {
// URLを開いたり、対象の要素を画面上に表示する処理
}};
it(`XPath取得`, async () => { xpathArray = await getXpathsByClassName(driver, 'checkbox'); });});上記の他にも試したんですけど、上手く行きませんでした。
driver.findElements(By.className(className))で取得した要素をexecuteScriptに渡す。: ブラウザ側で認識している要素とどうやら違うようで、返り値がすべてnullになったので、失敗。- ブラウザ側でXPathの配列を作る。: メモリ不足エラーでブラウザが止まり、失敗。
Functions 🔧
- sleep: レンダリングが終わるであろう時間だけ処理を待ちます。
- assertEqual_and_log: アサーションを行って、同時にログも出力します。
- outputLog: ログを出力します。
- getStrRepeatedToMark: 引数の文字列を繰り返して出力します。主にログの部分を見やすくするために使います。
Classes 👨👨👧👦
使用例
例えば、Office365の画面を操作する場合:
const { Builder, By, until } = require('selenium-webdriver');const util = require('util');const assert = require("assert");const _ = require("lodash");const path = require('path');const fs = require("fs");const fsp = require('fs/promises');
// 一般ツールconst sleep = waitTime => new Promise( resolve => setTimeout(resolve, waitTime) );const assertEqual_and_log = (case_no, expected, actual) => { assert.equal(expected, actual); console.log(`${case_no},${expected},${actual}`);};const outputLog = (funcName, remark) => { console.log(`${funcName}: ${remark}`);};const getStrRepeatedToMark = (repeatStr, repeatNumberToMark=15) => { return repeatStr.repeat(repeatNumberToMark);};const getTextLines = (fileName) => { const separator = '\n'; let text = fs.readFileSync(`./${fileName}`, 'utf8'); let lines = text.toString().split(separator); return lines;}
// Selenium関連ツールclass Operator{ openAppViaO365 = async(driver, url, username, password, waitTimeToDisplay=40000) => { const funcName = 'openAppViaO365';
await driver.get(url); await driver.manage().window().maximize(); await outputLog(funcName, getStrRepeatedToMark('a')); await sleep(10000);
await driver.findElement(By.id("i0116")).sendKeys(username); await driver.findElement(By.id("idSIButton9")).click(); await outputLog(funcName, getStrRepeatedToMark('b')); await sleep(10000);
await driver.findElement(By.id("i0118")).sendKeys(password); await driver.findElement(By.id("idSIButton9")).click(); await outputLog(funcName, getStrRepeatedToMark('c')); await sleep(10000);
await driver.findElement(By.id("idBtn_Back")).click(); await outputLog(funcName, getStrRepeatedToMark('d')); await sleep(waitTimeToDisplay); }; openAppDirect = async(driver, url, waitTimeToDisplay=40000) => { const funcName = 'openAppDirect';
await driver.get(url); await outputLog(funcName, getStrRepeatedToMark('a')); await sleep(waitTimeToDisplay); }; openAppDev = async(driver, url, username, password, waitTimeToDisplay=40000) => { const funcName = 'openAppDev';
await driver.get(url); await driver.manage().window().maximize(); await outputLog(funcName, getStrRepeatedToMark('a')); await sleep(10000);
await driver.findElement(By.xpath('//*[@id="Input_UsernameVal"]')).sendKeys(username); await outputLog(funcName, getStrRepeatedToMark('b')); await driver.findElement(By.xpath('//*[@id="Input_PasswordVal"]')).sendKeys(password); await outputLog(funcName, getStrRepeatedToMark('c')); await driver.findElement(By.xpath('//*[@id="b6-Button"]/button')).click();
await sleep(waitTimeToDisplay); await outputLog(funcName, getStrRepeatedToMark('d')); } clickElementsByElementArray = async(driver, elementArray) => { const funcName = 'clickElementsByElementArray'; for (let i = 0; i < elementArray.length; i++){ // scroll in the window. let element = await driver.findElement(By.id(idArray[i])); await outputLog(funcName, `${i}: ${getStrRepeatedToMark('a')}`); await driver.executeScript("arguments[0].scrollIntoView()", elementArray[i]); await outputLog(funcName, `${i}: ${getStrRepeatedToMark('b')}`); await sleep(1000);
// click element. await elementArray[i].click(); await sleep(1000); await outputLog(funcName, `${i}: ${getStrRepeatedToMark('c')}`); } }; clickElementsByXpathArray = async(driver, xpathArray) => { const funcName = 'clickElementsByXpathArray'; for (let i = 0; i < xpathArray.length; i++){ // scroll in the window. let element = await driver.findElement(By.xpath(xpathArray[i])); await outputLog(funcName, `${i}: ${xpathArray[i]}: ${getStrRepeatedToMark('a')}`); await driver.executeScript("arguments[0].scrollIntoView()", element); await sleep(500); await outputLog(funcName, `${i}: ${xpathArray[i]}: ${getStrRepeatedToMark('b')}`);
// click element. await element.click(); await sleep(1000); await outputLog(funcName, `${i}: ${xpathArray[i]}: ${getStrRepeatedToMark('c')}`); } }; clickElementsByIdArray = async(driver, idArray, sleepMilisecond=1000) => { const funcName = 'clickElementsByIdArray'; for (let i = 0; i < idArray.length; i++){ // scroll in the window. let element = await driver.findElement(By.id(idArray[i])); await outputLog(funcName, `${i}: ${idArray[i]}: ${getStrRepeatedToMark('a')}`); await driver.executeScript("arguments[0].scrollIntoView()", element); await outputLog(funcName, `${i}: ${idArray[i]}: ${getStrRepeatedToMark('b')}`); await sleep(sleepMilisecond);
// click element. await element.click(); await sleep(sleepMilisecond); await outputLog(funcName, `${i}: ${idArray[i]}: ${getStrRepeatedToMark('c')}`); } }; clickElementByXpath = async(driver, xpath) => { const funcName = 'clickElementByXpath'; // scroll in the window. let element = await driver.findElement(By.xpath(xpath)); await driver.executeScript("arguments[0].scrollIntoView()", element); await outputLog(funcName, `${element}: ${getStrRepeatedToMark('a')}`);
// click element. await element.click(); await sleep(1000); await outputLog(funcName, `${element}: ${getStrRepeatedToMark('b')}`); }; scrollDisplayToTargetXpath = async(driver, xpath) => { const funcName = 'scrollDisplayToTargetXpath'; // scroll in the window. let element = await driver.findElement(By.xpath(xpath)); await driver.executeScript("arguments[0].scrollIntoView()", element); await sleep(1000); await outputLog(funcName, `${element}: ${getStrRepeatedToMark('a')}`); }};おしまい


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!