ラジオボタンを自動選択させる
stackOverFlowに投稿したネタです。
How to select radio from dynamic table row in Nightwatchjs
画面の中にリストがあるとして、動的に行の数が変化する場合において任意のラジオボタンを指定したい場合にどうすればいいか。
という内容です。
テスト対象画面
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>test trList</title>
</head>
<body>
<table width="100%" cellspacing="1" cellpadding="2" border="1">
<tbody>
<tr>
<td colspan="2">TITLE</td>
</tr>
<tr>
<td width="20%">check box</td>
<td>No.</td>
</tr>
<tr>
<td><input type="radio" name="checkradio"></td>
<td>0001</td>
</tr>
<tr>
<td><input type="radio" name="checkradio"></td>
<td>0002</td>
</tr>
<tr>
<td><input type="radio" name="checkradio">
</td>
<td>0003</td>
</tr>
<tr>
<td><input type="radio" name="checkradio">
</td>
<td>0004</td>
</tr>
<tr>
<td><input type="radio" name="checkradio">
</td>
<td>0005</td>
</tr>
</tbody>
</table>
</body>
</html>
画面は行が固定されているのですが、本当ならこの行が動的に増えて表示されるような感じです。
5件分表示とか6件分表示とかそんな感じ。
やりたいことは「行の数が定まらない中でも任意のラジオボタンを選択する」ことです。
つまり、行が増えたとしてもずっと「No.0003」のラジオボタンを選択するということになります。
最初に考えた方法(失敗)
const path = require('path');
var url = path.resolve(__dirname,"trList.html");
//check box targetNo
var targetNo = "0003";
var elementCount;
var parentPath = "/html/body/table/tbody/";
module.exports = {
'@disabled': false,
'tr dynamic selection' : function (client) {
client
.url(url)
.elements('xpath', parentPath +'tr',
function(result){
elementCount = Object.keys(result.value).length;
console.log("data is " + String(Number(elementCount)-2) + " rows");
for(var i=3; i<=elementCount; i++ ){
//rows xpath
var xpath = parentPath + "tr[" + i + "]/";
client
.useXpath()
.getText(xpath + "td[2]",
function(result){
console.log("current row is " + result.value);
console.log("current path is [" + xpath + "td[1]/input]");
if(targetNo === result.value){
console.log("target checkBOX! click");
client.useXpath().click(xpath + "td[1]/input");
}
});
}
})
.pause(3000)
.end();
}
};
結果
上手くいかず。
コンソール上は0003の時に認識できているようだが、選択されたボタンは0005の方だった。
[Tr List Test Before] Test Suite
================================
DevTools listening on ws://127.0.0.1:63912/devtools/browser/b9769524-06c9-47ef-9af7-a274dfce48c7
Running: tr dynamic selection
data is 5 rows
current row is 0001
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
current row is 0002
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
current row is 0003
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
target checkBOX! click
current row is 0004
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
current row is 0005
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
No assertions ran.
OK. 5 total assertions passed. (9.754s)
for文による変数の代入が先に行われ、その結果がnightwatchでclickさせた際に使われたようだ。
どうやらnightwatchの動作とjavascriptの動作は非同期のように実行されているらしい。
forの制御が先に動き、nightwatchはソース内すべての制御が実行された後、改めて実行される。
※公式のドキュメントの動作概要にそんな感じの事が確かに書いてあった気がする。
https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue
stackOverFlow投稿後(成功)
どうしても上手くいかないので、stackOverFlowに質問書いた。
解決策じゃないけど、
「You probably have to add a waitForElementPresent() and include your path. What’s happening now is likely that the code, being asynchronous, is advancing to it’s endpoint.」
→「おそらくwaitForElementPresent()を追加してパスを含める必要があります。今起こっていることは、非同期のコードがそのエンドポイントに向かって進んでいるということです。」(google直翻訳)
というコメントをもらったので、waitForElementPresent()を使ってみることにした。
test.js
const path = require('path');
var url = path.resolve(__dirname,"trList.html");
//check box targetNo
var targetNo = "0003";
var elementCount;
var parentPath = "/html/body/table/tbody/";
module.exports = {
'@disabled': false,
'tr dynamic selection' : function (client) {
client
.url(url)
.elements('xpath', parentPath +'tr',
function(result){
elementCount = Object.keys(result.value).length;
console.log("data is " + String(Number(elementCount)-2) + " rows");
for(var i=3; i<=elementCount; i++ ){
//rows xpath
var xpath = parentPath + "tr[" + i + "]/";
client
.useXpath()
.waitForElementPresent(xpath + "td[2]", function(result,xpath){
console.log(xpath.element.selector.slice(0,29));
result.value.map(function(element, err){
client.elementIdAttribute(element.ELEMENT, 'innerText', function(res){
console.log("current row is " + result.value);
console.log("current path is [" + xpath.element.selector.slice(0,29) + "td[1]/input]");
if(targetNo === res.value){
console.log("target checkBOX! click");
client.useXpath().click(xpath.element.selector.slice(0,29) + "td[1]/input");
}
})
});
});
}
})
.pause(3000)
.end();
}
};
結果
上手くいった。
waitForElementPresent()は使ったがそれが解決策としてあったわけではなく、そこでoptionとして設定できるfuncitonに引数を入れることで対応できた。
考え方は失敗した時と変わっていないが、
forで呼び出したnightwatchの命令に値をfunctionで入れ込み、さらにその先で呼び出した命令にfunctionで入れ込む・・・という方法をとった。
[Tr List Test After] Test Suite
===============================
DevTools listening on ws://127.0.0.1:50318/devtools/browser/ab3e16e8-901d-432e-9b78-84bec3176519
Running: tr dynamic selection
data is 5 rows
√ Element </html/body/table/tbody/tr[3]/td[2]> was present after 10 milliseconds.
/html/body/table/tbody/tr[3]/
current row is [object Object]
current path is [/html/body/table/tbody/tr[3]/td[1]/input]
√ Element </html/body/table/tbody/tr[4]/td[2]> was present after 10 milliseconds.
/html/body/table/tbody/tr[4]/
current row is [object Object]
current path is [/html/body/table/tbody/tr[4]/td[1]/input]
√ Element </html/body/table/tbody/tr[5]/td[2]> was present after 9 milliseconds.
/html/body/table/tbody/tr[5]/
current row is [object Object]
current path is [/html/body/table/tbody/tr[5]/td[1]/input]
target checkBOX! click
√ Element </html/body/table/tbody/tr[6]/td[2]> was present after 10 milliseconds.
/html/body/table/tbody/tr[6]/
current row is [object Object]
current path is [/html/body/table/tbody/tr[6]/td[1]/input]
√ Element </html/body/table/tbody/tr[7]/td[2]> was present after 10 milliseconds.
/html/body/table/tbody/tr[7]/
current row is [object Object]
current path is [/html/body/table/tbody/tr[7]/td[1]/input]
OK. 5 assertions passed. (3.234s)
行数を増やしても無事選択されました。
備考
ちなみに、今回stackOverFlowに初めて投稿した。結構緊張した。
適当な質問とか簡単な質問とかしたら「ちゃんと調べてきたのかよ?」って言われるらしいから。
投稿する時に気を付けることとかお作法は以下のサイトを参考にしました。
Stack Overflowの使い方
Stack Overflowで世界を相手に腕試し
なんで日本版にしなかったかって?世界を相手にしたいからだよ。(ドヤァ
コメントを書く