Reactの弊害。Seleniumを阻むステート管理を無理やり突破

# なんの話か
Reactで作られたWebページ（申込フォーム）のテスト自動化のために、Seleniumを導入しようとしたのですが、Reactのステート管理の壁に阻まれて、思ったように値が入らないーーーとなりましたが、なんとか解消したという話です。

ついでに、需要があるかわかりませんが、Seleniumテストケースを生成するExcelツールにまとめてみました。詳細は[後述](#selenium-test-case-generator)

# はじめに
Seleniumを選定した理由は以前使ったことがあり、なんとかなりそうだと思ったからです。SeleniumはSeleniumでも、ブラウザエクステンションのSelenium IDEです。
> Selenium IDE
![SeleniumIDE.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/2c83eb64-0857-5ca1-2c93-fc7f5b86af22.png)

作ったのは、これに読み込ませるテストケースを生成するツールです。

# 経緯
少し前にとあるサイトでSeleniumで自動テストをできないかということになり、いろいろやってみました。すんなり行くんじゃないかと思いきや、画面は変わっても実際にinput項目に値が反映されてない状態に。

確認したところ、Reactステート管理されているから外から値は設定できないよ、とDev Leaderに一蹴されました。

React経験がなく、実はかなりビックリしました。ただ「はいそうですか」とはならず試行錯誤してみてたら、うまく値が設定できる方法があったので、おなじ境遇の方がいらっしゃれば参考になるかと思い投稿します。本来はどうすべきかを考えずにやっちゃってるので、詳しい方がいらっしゃればご指摘いただければ幸いです。

※当時は時間かけたくなかったのでReactの勉強は置いておいちゃいました。ただ、どうやれば目的を達成できるかしぼってリバースエンジニアリングだけしてました(とりあえず動けば目的達成)。

# Seleniumのコマンドは`run script`
Selenium IDEでは`Command` `Targeg` `Value`をきちんと設定しないといけません。Recボタンを押してから画面をポチポチやってもテストケースを作れますが、まともなものができたことが無い気がします。

なので、私は面倒でもかならず`id` `class` `name`などを調べて設定するようにしています。

人それぞれかと思いますが、Selenium IDEでは`run script`コマンドをつかって、JavaScriptで値を詰めたほうがいいと思ってます。大抵の自動テストツールには任意のスクリプトを実行できるオプションがあるので、JavaScriptでできるようにしておけば、別のツールへの以降が楽だ、と考えてるからです。

## フォームに値をいれるJavaScript
最終的に以下のようなJavaScriptでなんとか解決しました。環境やWeb側の実装方法によってはうまく行かないかもしれませんが、全体的なアイディアは流用できるのではないかと。

前述していますが、Seleniumのコマンドは`run script`を選択してます。このコマンドは`Target`項目に記載したJavaScriptを実行してくれます。

### ClickとCheck
クリックとチェックボックスは単純です。要素探してクリックするだけです。
```javascript:ClickとCheck
if(document.querySelector("#selector#")) {
  document.querySelector("#selector#").click();
}
```
※**#selector#** を実際のセレクタに置き換えてください

DOMとしては以下のような感じのものに対してです
> `<input  name="r-u-cat-or-not" type="checkbox" value="yes-i-am">`
![IMG-1f44284f2c51f8066dfbac6da9bc5d00-V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/a6fa259d-f377-680e-f7a0-65a166ffdc41.jpeg)

### input
Reactで作られたWebフォームで最初に苦戦したのがinputでした。
DOMとしてはこういうやつです。
> `<input type="text" name="name" />`
![IMG-180dfb57383d38faf69a5643a4d36000-V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/8316cffd-398b-291c-1f03-4b6324ab54f8.jpeg)

```javascript:input
if (document.querySelector("#selector#")) {
    (Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set).call(document.querySelector("#selector#"), "#target#");
    document.querySelector("#selector#").dispatchEvent(new Event('input', {
        bubbles: true
    }));
}
```
※**#selector#** を実際のセレクタに、**#target#** を設定する値に置き換えてください

元々は単純に`document.querySelector("#selector#").value = "#target#"; `で行けるとおもっていたのですが、Reactの場合は、ただ値を設定するだけでは無理でした。
やったことは、
* getOwnPropertyDescriptorでInput要素の完全なプロパティを取得
* セッターを取得して、対象項目に値を詰める（Call）
* dispatchEventでinputイベントを発火させてバブリング
これで何とか行けました。inputイベントじゃなくて、changeイベントでもいいかもです。

### radio
ClickやCheckと同じようにできます。ただクリックする要素を見つけるのに探索をするくらいの違いです。
> ![IMG-c2465d1a67341791067aae39c7641017-V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/accd5b0f-2e5e-82a6-fde3-2d9d786163ce.jpeg)

```javascript:radio
if (document.querySelectorAll("#selector#")) {
    document.querySelectorAll("#selector#").forEach(v => {
        if (v.parentNode.textContent.trim() === "#target#") v.click()
    })
}
```
※**#selector#** を実際のセレクタに、**#target#** を設定する値に置き換えてください

### select
inputをベースにできます。
> ![IMG-13fc26748f3ec1f403507f311e1aa5f9-V.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/9016cf36-4e53-be98-dc4f-b569e9325e8f.jpeg)
```javascript:select
if (document.querySelector("#selector#")) {
    document.querySelector("#selector#").focus();
    document.querySelector("#selector#").value = "#target#";
    document.querySelector("#selector#").dispatchEvent(new Event("change", {
        bubbles: true
    }));
}
```
※**#selector#** を実際のセレクタに、**#target#** を設定する値に置き換えてください

なぜか、最初にfocusしないとうまくいきませんでした。

### おまけ　CaptureScreenshot
```javascript:CaptureScreenshot
var CaptureScreenshot = async (filename) => {
    await import("https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js");
    html2canvas(document.querySelector("body"), {
        onrendered: (canvas) => {
            var a = document.createElement('a');
            a.href = canvas.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream");
            a.download = filename;
            a.click();
        }
    });
};
var dateFormat = (date) => {
    var y = date.getFullYear(),
        m = date.getMonth() + 1,
        d = date.getDate(),
        h = date.getHours(),
        mi = date.getMinutes(),
        ss = date.getSeconds();
    return y + (('0' + m).slice(-2)) + (('0' + d).slice(-2)) + (('0' + h).slice(-2)) + (('0' + mi).slice(-2)) + (('0' + ss).slice(-2))
};
CaptureScreenshot(`#target#-${dateFormat(new Date())}.jpg`);
```
※**#target#** を設定する値に置き換えてください

結構前のSelenium IDEにはスクリーンショットを取るコマンドがあったのですが、最近のはなくなったみたいなので。
もしかすると、SPA等の作りによっては、Wrapper要素の高さをまずauto等に設定してやらないとページ全体のキャプチャが取れないかもです。

# 効率化のために作ったもの
今回の動機であり元凶であるinputタグをなんとかできたことで、一気に他のタグにも応用できるようになりました。各項目にどうやって値を設定すればいいかがわかっので、効率化するためにツール化してみました。

# Selenium Test Case Generator

**Github:**
https://github.com/taukuma/excel-vba-selenium-test-case-generator

**ダウンロード:**
https://github.com/taukuma/excel-vba-selenium-test-case-generator/raw/main/SeleniumTestCaseGenerator.xlsm
![Selenium Test Case Generator 01 - main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/3614672e-0491-d978-928b-8f056a749779.png)
実際にテストをしたWebフォームのみで動作確認してます。ご利用の際は適宜修正いただく必要があると思いますので、参考程度にしていただければ。

## 使い方
Excelのツールです。事前設定がいろいろと必要ですが、一度定義さえしちゃえば、コードを書かずに、テストケース(Test Suite)を生成できます。実際にこのツールでテストやデータ投入を自動化できたので、おおきな省力化ができましたし、高価なサービスやツールに頼らなくても、ある程度はできることが証明できたと思ってます。

### 基本操作方法
SeleniumTestCaseGenerator.xlsmは開くと以下のようになってます。
![Selenium Test Case Generator 01 - main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/3614672e-0491-d978-928b-8f056a749779.png)
※`Test Case Generation`がメインのシートになります。

#### テストケースの生成
Test Case Generationシートの`Generate Test Suite`をクリックすると定義したテストパターンのTest Suiteを生成します。
![Selenium Test Case Generator 01 - main 02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/97385f9d-f58d-dffa-670e-eb67e89fcada.png)
生成されたTest Suiteは以下のようになってます。
<details><summary><font color="#5af"><b>ソースコードを表示</b></font></summary><div>

```json:Selenium Test Suite
{
    "id": "c2b08328-fece-dcbf-d8d4-626b0b2594d6",
    "name": "Selenium_TestSuit_20220623175143.side",
    "version": "2.0",
    "url": "https://wagahai-wa-neko-de-aru.jp",
    "tests": [{
            "id": "65699b60-e654-7fd6-4a5a-72904db8bb8b",
            "name": "TestCase01",
            "commands": [{
                    "id": "6956f3a7-721f-5624-919b-9faccc12de4",
                    "comment": "",
                    "command": "open",
                    "target": "https://wagahai-wa-neko-de-aru.jp",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "25514d73-46e8-3d72-1e94-79fa7b8ee925",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=r-u-cat-or-not]\")) {document.querySelector(\"input[name=r-u-cat-or-not]\").click()}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "617bcae6-359b-4f96-e28c-9e1fb848a676",
                    "comment": "",
                    "command": "run script",
                    "target": "var CaptureScreenshot = async (filename) => {await import(\"https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js\");document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"auto\"; html2canvas(document.querySelector(\"body\"), {onrendered: (canvas) => {var a = document.createElement('a');a.href = canvas.toDataURL(\"image/jpeg\").replace(\"image/jpeg\", \"image/octet-stream\");a.download = filename;a.click();document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"\";}});};  var dateFormat = (date) => {var y = date.getFullYear(), m=date.getMonth()+1,d=date.getDate(),h=date.getHours(),mi=date.getMinutes(),ss=date.getSeconds();return y+(('0'+m).slice(-2))+(('0'+d).slice(-2))+(('0'+h).slice(-2))+(('0'+mi).slice(-2))+(('0'+ss).slice(-2))};CaptureScreenshot(`TestCase01_STEP01-${dateFormat(new Date())}.jpg`);",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "dd82e5cb-74be-ceb7-69fa-b81d9650c87e",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"a.next\")) {document.querySelector(\"a.next\").click()}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "8f2871c0-c731-149d-caba-2941847b0ce",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name1]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name1]\"), \"珍野\"); document.querySelector(\"input[name=name1]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "b929db59-17fc-8043-10a9-ee74dd8cee34",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name2]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name2]\"), \"苦沙弥\"); document.querySelector(\"input[name=name2]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "51f75012-49a0-d4a4-9a28-7c2b85747f4f",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name1Kana]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name1Kana]\"), \"ちんの\"); document.querySelector(\"input[name=name1Kana]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "bb38e13b-278d-e03f-42b4-4d5bae4e2393",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name2Kana]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name2Kana]\"), \"くしゃみ\"); document.querySelector(\"input[name=name2Kana]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "2218b356-39c-188a-f76b-4df42d7ea694",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthYear]\")) {document.querySelector(\"select[name=birthYear]\").focus();document.querySelector(\"select[name=birthYear]\").value = \"1867\"; document.querySelector(\"select[name=birthYear]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "defd8f9c-3b82-d3c7-8b3b-bc4fd94485",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthMonth]\")) {document.querySelector(\"select[name=birthMonth]\").focus();document.querySelector(\"select[name=birthMonth]\").value = \"2\"; document.querySelector(\"select[name=birthMonth]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "ac4df41e-5995-4887-fd5d-91f87e934f64",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthDay]\")) {document.querySelector(\"select[name=birthDay]\").focus();document.querySelector(\"select[name=birthDay]\").value = \"9\"; document.querySelector(\"select[name=birthDay]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "857adb96-1621-dbcc-b9c7-1d9f7daea71a",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelectorAll(\"input[name=gender]\")) {document.querySelectorAll(\"input[name=gender]\").forEach(v => {if(v.parentNode.textContent.trim() === \"男\") v.click()})}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "4220b9d4-d156-3144-e68d-6fadec4c4c56",
                    "comment": "",
                    "command": "run script",
                    "target": "var CaptureScreenshot = async (filename) => {await import(\"https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js\");document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"auto\"; html2canvas(document.querySelector(\"body\"), {onrendered: (canvas) => {var a = document.createElement('a');a.href = canvas.toDataURL(\"image/jpeg\").replace(\"image/jpeg\", \"image/octet-stream\");a.download = filename;a.click();document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"\";}});};  var dateFormat = (date) => {var y = date.getFullYear(), m=date.getMonth()+1,d=date.getDate(),h=date.getHours(),mi=date.getMinutes(),ss=date.getSeconds();return y+(('0'+m).slice(-2))+(('0'+d).slice(-2))+(('0'+h).slice(-2))+(('0'+mi).slice(-2))+(('0'+ss).slice(-2))};CaptureScreenshot(`TestCase01_STEP1-${dateFormat(new Date())}.jpg`);",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "95a3e136-adb8-47cf-c555-13a88cfa7572",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\".complete\")) {document.querySelector(\".complete\").click()}",
                    "targets": [],
                    "value": "0"
                }
            ]
        },
        {
            "id": "0d21430d-b68d-8b49-b7d7-1e541b0f6e35",
            "name": "TestCase2",
            "commands": [{
                    "id": "a42ef73-cfd0-7940-1bf7-4862bb96b1de",
                    "comment": "",
                    "command": "open",
                    "target": "https://wagahai-wa-neko-de-aru.jp",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "2ee6b430-b5d-bb36-10df-606a4e5aa644",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=r-u-cat-or-not]\")) {document.querySelector(\"input[name=r-u-cat-or-not]\").click()}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "89f695e-e5ef-afdb-27fa-114d778e32bb",
                    "comment": "",
                    "command": "run script",
                    "target": "var CaptureScreenshot = async (filename) => {await import(\"https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js\");document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"auto\"; html2canvas(document.querySelector(\"body\"), {onrendered: (canvas) => {var a = document.createElement('a');a.href = canvas.toDataURL(\"image/jpeg\").replace(\"image/jpeg\", \"image/octet-stream\");a.download = filename;a.click();document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"\";}});};  var dateFormat = (date) => {var y = date.getFullYear(), m=date.getMonth()+1,d=date.getDate(),h=date.getHours(),mi=date.getMinutes(),ss=date.getSeconds();return y+(('0'+m).slice(-2))+(('0'+d).slice(-2))+(('0'+h).slice(-2))+(('0'+mi).slice(-2))+(('0'+ss).slice(-2))};CaptureScreenshot(`TestCase2_STEP0-${dateFormat(new Date())}.jpg`);",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "8ba9c573-d674-97c-ee61-3d8e11ed637",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"a.next\")) {document.querySelector(\"a.next\").click()}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "85db7130-84c7-13c8-37b0-6b066d0d061",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name1]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name1]\"), \"名前は\"); document.querySelector(\"input[name=name1]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "507ab946-690-cbb3-b6b1-257179a82a93",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name2]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name2]\"), \"まだない\"); document.querySelector(\"input[name=name2]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "cdd44183-1db1-5151-f450-a4756c1f1317",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name1Kana]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name1Kana]\"), \"なまえは\"); document.querySelector(\"input[name=name1Kana]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "6f663b16-c155-cc4-ee5a-3764cac295d",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\"input[name=name2Kana]\")) {(Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, \"value\").set).call(document.querySelector(\"input[name=name2Kana]\"), \"まだない\"); document.querySelector(\"input[name=name2Kana]\").dispatchEvent(new Event('input', { bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "ee15d819-2477-cc34-4bc4-96b316217e7f",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthYear]\")) {document.querySelector(\"select[name=birthYear]\").focus();document.querySelector(\"select[name=birthYear]\").value = \"1900\"; document.querySelector(\"select[name=birthYear]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "1a8133a-ff21-edd8-894f-856343ad942",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthMonth]\")) {document.querySelector(\"select[name=birthMonth]\").focus();document.querySelector(\"select[name=birthMonth]\").value = \"1\"; document.querySelector(\"select[name=birthMonth]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "3731f1e9-ebc5-9d7-366e-e12e6b35502",
                    "comment": "",
                    "command": "run script",
                    "target": "if (document.querySelector(\"select[name=birthDay]\")) {document.querySelector(\"select[name=birthDay]\").focus();document.querySelector(\"select[name=birthDay]\").value = \"1\"; document.querySelector(\"select[name=birthDay]\").dispatchEvent(new Event(\"change\", {bubbles: true}));}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "2f64abd4-1314-90c5-3438-5949fbe222c5",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelectorAll(\"input[name=gender]\")) {document.querySelectorAll(\"input[name=gender]\").forEach(v => {if(v.parentNode.textContent.trim() === \"男\") v.click()})}",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "367ca7fa-8d41-8ec-5ad7-b0ff984fddde",
                    "comment": "",
                    "command": "run script",
                    "target": "var CaptureScreenshot = async (filename) => {await import(\"https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js\");document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"auto\"; html2canvas(document.querySelector(\"body\"), {onrendered: (canvas) => {var a = document.createElement('a');a.href = canvas.toDataURL(\"image/jpeg\").replace(\"image/jpeg\", \"image/octet-stream\");a.download = filename;a.click();document.querySelector(\"body, #virtual-body, div.App__slides, section.Branch__root\").style.height = \"\";}});};  var dateFormat = (date) => {var y = date.getFullYear(), m=date.getMonth()+1,d=date.getDate(),h=date.getHours(),mi=date.getMinutes(),ss=date.getSeconds();return y+(('0'+m).slice(-2))+(('0'+d).slice(-2))+(('0'+h).slice(-2))+(('0'+mi).slice(-2))+(('0'+ss).slice(-2))};CaptureScreenshot(`TestCase2_STEP1-${dateFormat(new Date())}.jpg`);",
                    "targets": [],
                    "value": "0"
                },
                {
                    "id": "b98d4da9-75a6-7a7c-c1f7-858f71c2dd2",
                    "comment": "",
                    "command": "run script",
                    "target": "if(document.querySelector(\".complete\")) {document.querySelector(\".complete\").click()}",
                    "targets": [],
                    "value": "0"
                }
            ]
        }
    ]
}
```
</div></details>
あとはSelenium IDEに読み込ませて、実行するだけです。

#### フォーム要素の定義
まず、事前準備としては、テストしようとしているWebページにどんな要素があるかを調べます。基本的な対象は入力項目・選択項目・クリック項目です。調べながら`target master`シートを埋めていきます。
![Selenium Test Case Generator 03 - form master.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/72f6a8aa-af60-8549-7dfe-edf9e5aa405b.png)
|項目|意味|
|---|---|
|Target Selection|入力項目の項目名です。テストパターン定義の際に参照する値になります|
|Selector|DOMのセレクタです。Webページ全体で一意になるように設定します。idやclass、nameを定義する感じです。|

#### テスト項目の定義
Seleniumシートでテストパターンを定義します。
![Selenium Test Case Generator 02 - test pattern 01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/afd160a2-f26f-ef16-cb28-30ea9645f486.png)
上述のフォーム要素の定義とあわせて、どの項目をどのページで設定するのかを定義します。このとき、`入力項目`欄にはtarget masterシートで設定したTarget Selectionから選択して入力します。コマンドは後述する`command master`で定義してあるコマンドを選びます(click、check、input、radio、select等)。

#### テストパターンの定義
各ページの入力項目の定義が終わったら、今度はテストパターンごとに、どの項目になにを入れるかを設定します。
![Selenium Test Case Generator 02 - test pattern 02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/03237de7-a910-1cb7-223a-a0bb35f4ad94.png)
この部分がこのツールで一番こだわったところです。一度入力項目やコマンドを定義しちゃえば、簡単にテストパターンを作っていけるようになります。

テストパターンは列を増やしていけば、何パターンでも定義できます。

#### 使えるコマンドを増やす
command masterシートにSelenium IDEの`Command` `Target` `Value`を書いていけば、どんなコマンドでもテストパターンに含めることができます。
![Selenium Test Case Generator 04 - command master.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2187642/3336660a-9f78-e729-a37c-7e695845ee8b.png)
特に`run script`コマンドは強力なのでこれだけ知っていれば、大抵のことはなんとかできると思ってます。

# おわりに
React作られた全てのWebページに使えるとは思っていませんが、同じ境遇の方にとって、少しでも参考になれば幸いです。

:::note warn
決してReactを嫌っているわけではありません！
良さは(何となくですが)わかってるつもりです...
:::