DMM.com Advent Calendar 2018 6日目の記事です。
おはようございます、アドテクノロジー部の津久井(@kkkdev )と申します。
前職は MotecoBeautyというスマホアプリやアドフリくんという国産SSP等の開発に携わっており、
昨年12月にDMMに参画いたしました。
アドテクと自動テストによるQA(品質保証)の関連性
クロスブラウザ・モバイルOS....
ブラウザ / モバイル実機による検証・および自動化は、webサービスやモバイルアプリ開発者のみならず
アドネットワークやSSP等のアドテク界隈でも重要視されています。
参考: QA(品質保証)チームによる実機端末をつかった自動/手動テストの裏側(アドテクセンター通信)
自動化のミドルウェアはいくつか知られていますが、今回はその中から、古くから
ブラウザの自動テストに用いられているSeleniumと、ブラウザ操作の記録ツール
「Selenium IDE」にまつわる子ネタ(表題)を1点ピックアップして紹介いたします。
Selenium IDEの歴史
Selenium IDE自体は2004年(!)よりThoughtWorks社によって開発が始まった自動化フレームワークです。
おそらくエンジニアの方なら公私のどちらかで1度は触れてみたことがあるのではないでしょうか。
スタンドアロンのSeleniumが潰れた!
まず最初に、表題の機能は現行のSelenium IDEには搭載されておりません。
詳しい説明は [2018年時点] Selenium IDE についてまとめてみた にまとめられていますが、Firefox 55から
(旧)Selenium IDEが使えなくなりました。
現在全てを刷新した現行Selenium IDEがブラウザ拡張機能(Chrome,Firefox)で提供されていますが、旧版
に比べ、 さながらLight版程度の機能になっています。(動作を記録して、独自のファイルに保存するのみ)
潰れた結果、コードへの変換が出来なくなった!
前述の刷新でスタンドアロン版で実装されているSelenium IDE -> selenium-webdriverへのコード変換の
機能も消えてしまい、独自の.sideファイル(Selenium IDEのベースとなるSideeXに起因する)での保存のみとなりました。
Selenium IDEで出力した.sideを「Selianize」でコード変換
(本投稿は結局このnpmライブラリを紹介するだけの中身なのですが)
あまり(というかほとんど)言及されているのを見たことがありませんが、実はSelenium IDEのリポジトリには
Selianizeという selenium-webdriver 用のコード変換用のライブラリが同梱されています。
(単独のnpmライブラリとしても利用可)
今回はこのSelianizeで実際前述の.sideファイルをにselenium-webdriver用のコードに
変換&出力する手順を、以下に記載していきます。
手順
Googleで「まなびストレート」を検索&該当のwikiページから林原めぐみ氏のページに飛ぶ操作を記録します。
(中身自体に意味はありません)
※Selenium IDE自体の記述は最小限操作程度です
事前準備. ChromeにSelenium IDEの拡張
Selenium IDE(Chrome拡張)をインストールしておく
※Firefoxの場合は https://addons.mozilla.org/ja/firefox/addon/selenium-ide
1. Selenium IDEで操作を記録する
割愛 (参考 : https://qiita.com/gluelan2013/items/0513c5331b6a67086308)
※各Commandのリファレンス的なものは、ベースになっているSideeXのページが参考になるかも
操作の自動記録以外に 直接Selenium IDEを操作して手順を追加することも可能です。
sleep / run script の Command などは のこの手段でIDE上から実装を行えます。
1点補足すると、Command によってはSideeXのリファレンス(兼 公式)通り
Targetの引数に 値を入れることもあります。どうも直感的ではないですが......
[たとえば、pauseコマンドに設定するミリ秒はTargetに書く]
2. Selenium IDEから.sideファイル(操作手順を記録したファイル)をローカルに保存
記録した手順を、IDEの右上から「Save Project」でから.sideファイルに保存します。
3. 「Selianize」モジュールを読み込み、selenium-webdriverコードに変換するnode.jsの実装を行う
"use strict"
const selianize = require('selianize').default;
const fs = require('fs');
(async () => {
//.sideの読み込み & JSONをパース
const project = JSON.parse(
fs.readFileSync('sample.side')
);
const selianized = await selianize(project);
//変換後のプロジェクトを表示
console.log(selianized);
})()
上記の実行結果は以下です。
{ globalConfig: 'global.Key = require(\'selenium-webdriver\').Key;global.URL = require(\'url\').URL;global.BASE_URL = configuration.baseUrl || \'https://www.google.co.jp\';let vars = {};',
suites:
[ { name: 'Default Suite',
persistSession: false,
code: 'jest.setTimeout(300000);describe("Default Suite", () => {it("AAA", async () => {await tests["AAA"](driver, vars);await driver.getTitle().then(title => {expect(title).toBeDefined();});});});',
tests: undefined,
snapshot: undefined } ],
tests:
[ { id: '51404450-7b46-4f36-b443-b56386b206ee',
name: 'AAA',
code: 'tests["AAA"] = async (driver, vars, opts) => {await driver.get((new URL("/", BASE_URL)).href);await driver.manage().window().setSize(...(`1306x847`.split("x").map((s) => parseInt(s))));await driver.wait(until.elementLocated(By.name(`q`)), configuration.timeout);await driver.findElement(By.name(`q`)).then(element => {element.clear().then(() => {element.sendKeys(`まなびすと`);});});await driver.wait(until.elementLocated(By.css(`div.sbl1 > span`)), configuration.timeout);await driver.findElement(By.css(`div.sbl1 > span`)).then(element => {element.click();});await driver.wait(until.elementLocated(By.css(`h3.LC20lb`)), configuration.timeout);await driver.findElement(By.css(`h3.LC20lb`)).then(element => {element.click();});await driver.wait(until.elementLocated(By.css(`dd > dl > dd > a`)), configuration.timeout);await driver.findElement(By.css(`dd > dl > dd > a`)).then(element => {element.click();});}',
snapshot: undefined } ] }
各要素ごとに、selenium-webdriverのjsコードが文字列として返却されているのがわかります。
global.Key = require('selenium-webdriver').Key;
global.URL = require('url').URL;
global.BASE_URL = configuration.baseUrl || 'https://www.google.co.jp';
let vars = {};
jest.setTimeout(300000);
describe("Default Suite", () => {
it("AAA", async () => {
await tests["AAA"](driver, vars);
await driver.getTitle().then(title => {
expect(title).toBeDefined();
});
});
});
async (driver, vars, opts) => {
await driver.get((new URL("/", BASE_URL)).href);
await driver.manage().window().setSize(...(`1306x847`.split("x").map((s) => parseInt(s))));
await driver.wait(until.elementLocated(By.name(`q`)), configuration.timeout);
await driver.findElement(By.name(`q`)).then(element => {
element.clear().then(() => {
element.sendKeys(`まなびストレート`);
});
});
await driver.wait(until.elementLocated(By.css(`div.sbl1 > span`)), configuration.timeout);
await driver.findElement(By.css(`div.sbl1 > span`)).then(element => {
element.click();
});
await driver.wait(until.elementLocated(By.css(`h3.LC20lb`)), configuration.timeout);
await driver.findElement(By.css(`h3.LC20lb`)).then(element => {
element.click();
});
await driver.wait(until.elementLocated(By.css(`dd > dl > dd > a`)), configuration.timeout);
await driver.findElement(By.css(`dd > dl > dd > a`)).then(element => {
element.click();
});
}
globalConfig は 定型文かなー、と思いきや、開始地点のURLもここで定義されていますね....
4. あとはよしなに
上記のコードを保存なりevalなり
Selenium IDE自体の使い所(現行版)
コミュニケーションツールとして
Selenium IDE(現行版)には 例えばselenium-webdriverでよく使われるスクリーンショット等の
機能は無く、記録したものをそのまま使うには機能不足感は否めません。なので、
・非エンジニアのメンバーに記録してもらう
・操作を再現した.sideファイルを渡す事により、実現したい内容に対するメンバー間の齟齬を無くす
(長ったらしい仕様書を書いた上、それが間違っているという悲劇を防ぐ)
上記のようなコミュニケーションにおける補助的なツールになれば御の字かな....と思います。
javascriptに変換できるのはSelianizeだけ!(知る限り)
実はSelenium IDEと同様の操作記録ツールには 強化版とも言えるKatalon Recorderという
Chrome拡張が存在し、こちらは既にコード出力機能が実装済です。
ただnode.jsによるjavascriptコードの出力は未対応になっており (現在 C# / JAVA / Python / Ruby 対応)、
node.jsのselenium-webdriverによる実装を行いたい場合には Selenium IDE + Selianize を使うのが
ベストかな....と考えています。
その他
現行のSelenium IDE自体もまだまだこれから + Appiumなどのアプリ向け自動化フレームワークは
機械学習を取り入れ始め、なかなか面白い動きを見せているというところもあり、今後の
IDEアップデートに期待しつつ、自動化界隈のアクションをマクロな視点で注視したいところです。
最後に
アドテクノロジー部は今年4年に発足したばかりの組織ですが、松本新CTOの元(明日は松本さんの記事です!)、
2019年は本格的に活動の幅を広げ、飛躍と貢献の流れに繋げられればと思っております!(します)