Selenium IDE(2018/12/06現行版)からselenium-webdriverのjsコードに変換する

[DMM.com Advent Calendar 2018](https://qiita.com/advent-calendar/2018/dmm) 6日目の記事です。

おはようございます、アドテクノロジー部の津久井(@kkkdev )と申します。
前職は [MotecoBeauty](https://moteco-web.jp/)というスマホアプリや[アドフリくん](https://adfurikun.jp/adfurikun/)という国産SSP等の開発に携わっており、
昨年12月にDMMに参画いたしました。


# アドテクと自動テストによるQA（品質保証）の関連性


クロスブラウザ・モバイルOS....

ブラウザ / モバイル実機による検証・および自動化は、webサービスやモバイルアプリ開発者のみならず
アドネットワークやSSP等のアドテク界隈でも重要視されています。



[参考: QA（品質保証）チームによる実機端末をつかった自動／手動テストの裏側（アドテクセンター通信）](https://supership.jp/magazine/technology/836/)


自動化のミドルウェアはいくつか知られていますが、今回はその中から、古くから
ブラウザの自動テストに用いられているSeleniumと、ブラウザ操作の記録ツール
「Selenium IDE」にまつわる子ネタ(表題)を1点ピックアップして紹介いたします。


# Selenium IDEの歴史


[Selenium IDE](https://www.seleniumhq.org/projects/ide/)自体は2004年(!)よりThoughtWorks社によって開発が始まった自動化フレームワークです。
おそらくエンジニアの方なら公私のどちらかで1度は触れてみたことがあるのではないでしょうか。


### スタンドアロンのSeleniumが潰れた!

まず最初に、表題の機能は現行のSelenium IDEには搭載されておりません。

詳しい説明は [[2018年時点] Selenium IDE についてまとめてみた](https://qiita.com/gluelan2013/items/0513c5331b6a67086308) にまとめられていますが、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](https://github.com/SeleniumHQ/selenium-ide/tree/master/packages/selianize)という [selenium-webdriver](https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver) 用のコード変換用のライブラリが同梱されています。
(単独の[npmライブラリ](https://www.npmjs.com/package/selianize)としても利用可)

今回はこのSelianizeで実際前述の.sideファイルをにselenium-webdriver用のコードに
変換&出力する手順を、以下に記載していきます。


## 手順

Googleで「まなびストレート」を検索&該当のwikiページから林原めぐみ氏のページに飛ぶ操作を記録します。
(中身自体に意味はありません)

*※Selenium IDE自体の記述は最小限操作程度です*



#### 事前準備. ChromeにSelenium IDEの拡張

[Selenium IDE(Chrome拡張)](https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd)をインストールしておく


※Firefoxの場合は https://addons.mozilla.org/ja/firefox/addon/selenium-ide



#### 1. Selenium IDEで操作を記録する ####


割愛 (参考 : https://qiita.com/gluelan2013/items/0513c5331b6a67086308)


*※各Commandのリファレンス的なものは、ベースになっている[SideeXのページ](http://sideex.org/)が参考になるかも*

操作の自動記録以外に 直接Selenium IDEを操作して手順を追加することも可能です。
sleep / run script の Command などは のこの手段でIDE上から実装を行えます。
1点補足すると、Command によっては[SideeXのリファレンス(兼 公式)](http://sideex.org)通り
Targetの引数に 値を入れることもあります。どうも直感的ではないですが......

[たとえば、pauseコマンドに設定するミリ秒はTargetに書く]
<img width="644" alt="スクリーンショット 2018-12-03 16.35.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/157228/ee7aa972-2026-346a-054d-fdf962313f6c.png">




#### 2. Selenium IDEから.sideファイル(操作手順を記録したファイル)をローカルに保存 ####

記録した手順を、IDEの右上から「Save Project」でから.sideファイルに保存します。

#### 3. 「Selianize」モジュールを読み込み、selenium-webdriverコードに変換するnode.jsの実装を行う ####
 
```javascript:.sideを読み、WebDriverIOコードに変換する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);
})()
```

上記の実行結果は以下です。

```shell
{ 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コードが文字列として返却されているのがわかります。

```javascript:globalConfig　(初期設定)
global.Key = require('selenium-webdriver').Key;
global.URL = require('url').URL;
global.BASE_URL = configuration.baseUrl || 'https://www.google.co.jp';
let vars = {};
```

```javascript:suites[].code　(jestのテストコードの雛形)
jest.setTimeout(300000);
describe("Default Suite", () => {
    it("AAA", async () => {
        await tests["AAA"](driver, vars);
        await driver.getTitle().then(title => {
            expect(title).toBeDefined();
        });
    });
});
```

```javascript:tests[].code　(実際の動作のコード)
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]( https://www.katalon.com/resources-center/blog/katalon-automation-recorder/ )という
Chrome拡張が存在し、こちらは既にコード出力機能が実装済です。

ただnode.jsによるjavascriptコードの出力は未対応になっており (現在 C# / JAVA / Python / Ruby 対応)、
node.jsのselenium-webdriverによる実装を行いたい場合には Selenium IDE + Selianize を使うのが
ベストかな....と考えています。

# その他


現行のSelenium IDE自体もまだまだこれから + Appiumなどのアプリ向け自動化フレームワークは
[機械学習を取り入れ始め、なかなか面白い動きを見せている]( http://www.selenium.jp/translation/appiumpro39appiumtesutozidonghuahenochuqiduanjienoai )というところもあり、今後の
IDEアップデートに期待しつつ、自動化界隈のアクションをマクロな視点で注視したいところです。



# 最後に


アドテクノロジー部は今年4年に発足したばかりの組織ですが、松本新CTOの元(明日は松本さんの記事です!)、

2019年は本格的に活動の幅を広げ、飛躍と貢献の流れに繋げられればと思っております!(します)