時間のない人は1.、6.、7.を読んで下さい。
1.ログイン
前回に引き続き、HTMLソースをまだ見れないうちに自動操作を記述するケースです。
ページ中の1番目の入力欄(input type="text")は、XPathで"(//input[@type='text'])[1]"で手に入ります。2番目なら[2]です。
<form action="XXXXXXXX">
<table>
<tbody>
<tr><td>ユーザーID</td><td><input type="text" name="userid" /></td></tr>
<tr><td>パスワード</td><td><input type="password" name="password" /></td></tr>
<tr><td colspan="2"><input type="submit" value="ログイン" /></td></tr>
</tbody>
</table>
</form>
または
<form action="XXXXXXXX">
ユーザーID <input type="text" value="" /><br />
パスワード <input type="password" value="" /><br />
<input type="submit" value="ログイン" />
</form>
driver.FindElement(By.XPath("(//input[@type='text'])[1]")).SendKeys("abcde");
driver.FindElement(By.XPath("(//input[@type='password'])[1]")).SendKeys("fghij");
driver.FindElement(By.XPath("//input[@value='ログイン'] | //button[text()='ログイン']")).Click();
driver.findElement(By.xpath("(//input[@type='text'])[1]")).sendKeys("abcde");
driver.findElement(By.xpath("(//input[@type='password'])[1]")).sendKeys("fghij");
driver.findElement(By.xpath("//input[@value='ログイン'] | //button[text()='ログイン']")).click();
driver.find_element_by_xpath("(//input[@type='text'])[1]").send_keys("abcde")
driver.find_element_by_xpath("(//input[@type='password'])[1]").send_keys("fghij")
driver.find_element_by_xpath("//input[@value='ログイン'] | //button[text()='ログイン']").click()
2021/05/06追記://input[@type='text']を()で囲みました。()なしだと、兄弟要素が[1]、[2]、…となり、兄弟以外が[1]で複数マッチしてしまうのと、兄弟要素でない2番目が[2]でマッチしないためです。
2.テーブルなし登録画面(失敗)
下記のようなHTMLの場合に、「氏名」「住所」の「文字列の右」という指定をして、tableを含むかどうか分からない場合にも要素を取得できないかがんばったのですが、できませんでした。
//[contains(text(),'氏名')]
でformを見つけられ、
//[contains(text(),'氏名')]//input[@type='text']
で氏名の入力欄も見つけられますが、「住所」にそのまま置き換えてもだめで、
//[contains(text()[2],'住所')]
とする必要がある上に、これで手に入るのもformなので、結局inputにも[2]が必要になり、
//[contains(text()[2],'住所')]//input[@type='text'][2]
となります。
inputに[2]を指定するのであれば、
//*[contains(text()[2],'住所')]
自体が必要ありません。
また、tableを使用せずにform直書きと分かっていないとtext()に[2]を使えません。
<form action="XXXXXXXX">
氏名 <input type="text" name="name" value="" /><br />
住所 <input type="text" name="address" value="" /><br />
<input type="submit" value="登録" />
</form>
3.テーブルあり登録画面(途中経過)
上のHTMLだと見出しがずれるので使われないで、下記のようにtableが使われてtdでそれぞれ別になれば、
//[contains(text(),'氏名')]/..//input[@type='text']
でいけそうな気がしてきました。
上の記述だと、「氏名」の兄弟タグであれば左側のinputも取得してしまうので、さらに厳密にすると、
//[contains(text(),'氏名')]/following-sibling:://input[@type='text']
となります。
//[contains(text(),'氏名')]/following-sibling::[1]//input[@type='text']
で、次の兄弟タグのみになります。
//[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1]
で、次の兄弟タグに限らず、兄弟タグの中で最初のinput type="text"になります。
<form action="XXXXXXXX">
<table>
<tbody>
<tr><td>氏名</td><td><input type="text" name="name" /></td></tr>
<tr><td>住所</td><td><input type="text" name="address" /></td></tr>
<tr><td colspan="2"><input type="submit" value="登録" /></td></tr>
</tbody>
</table>
</form>
driver.find_element_by_xpath("//*[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1]").send_keys("佐藤太郎")
driver.find_element_by_xpath("//*[contains(text(),'住所')]/following-sibling::*//input[@type='text'][1]").send_keys("東京都千代田区")
driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click()
HTMLが見れるようになれば、素直にname属性で探せばよいですが。
4.テーブルなし登録画面(リベンジ)(途中経過)
下記のXPathで見つけられました。
inputが階層下にある場合は未対応です。
<form action="XXXXXXXX">
氏名 <input type="text" name="name" value="" /><br />
住所 <input type="text" name="address" value="" /><br />
<input type="submit" value="登録" />
</form>
driver.find_element_by_xpath("//text()[contains(.,'氏名')]/following-sibling::input[@type='text'][1]").send_keys("佐藤太郎")
driver.find_element_by_xpath("//text()[contains(.,'住所')]/following-sibling::input[@type='text'][1]").send_keys("東京都千代田区")
driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click()
同じXPathでテーブルなしとテーブルあり両方に対応したいですが、このXPathだとテーブルありのHTMLでは見つけられませんでした。
5.テーブルあり、テーブルなし両対応(途中経過)
単純に「|」で結合しました。
driver.find_element_by_xpath("//*[contains(text(),'氏名')]/following-sibling::*//input[@type='text'][1] | //text()[contains(.,'氏名')]/following-sibling::input[@type='text'][1]").send_keys("佐藤太郎")
driver.find_element_by_xpath("//*[contains(text(),'住所')]/following-sibling::*//input[@type='text'][1] | //text()[contains(.,'住所')]/following-sibling::input[@type='text'][1]").send_keys("東京都千代田区")
driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click()
6. テーブルあり、テーブルなし(階層なし)、テーブルなし(階層下)全対応(最終形)
following-sibling::*//
と無駄なことをせずに、
following::
でいけました。
//text()[contains(.,'住所')]
で「住所」を含むテキストノードを取得し、
/following::
を追加してそれ以降の兄弟ノードとそれらの子孫ノードを対象とし、
input[@type='text']
を追加してinput type="text"を複数取得し、
[1]
を追加してその複数のうちの1番目を取得しています。
containsのため、入力欄の見出し以外にもけっこうマッチするのが要注意です。メニュー項目や、注釈のテキストなどとマッチすることがあります。その場合は、単純に1.の方法を使うか、
(//text()[contains(.,'住所')])[2]/following::input[@type='text'][1]
などと2番目の「住所」を指定したりする方法があります。
<form action="XXXXXXXX">
<table>
<tbody>
<tr><td>氏名</td><td><input type="text" name="name" /></td></tr>
<tr><td>住所</td><td><input type="text" name="address" /></td></tr>
<tr><td colspan="2"><input type="submit" value="登録" /></td></tr>
</tbody>
</table>
</form>
または
<form action="XXXXXXXX">
氏名 <input type="text" name="name" value="" /><br />
住所 <input type="text" name="address" value="" /><br />
<input type="submit" value="登録" />
</form>
または
<form action="XXXXXXXX">
氏名 <div><input type="text" name="name" value="" /></div><br />
住所 <div><input type="text" name="address" value="" /></div><br />
<input type="submit" value="登録" />
</form>
driver.find_element_by_xpath("//text()[contains(.,'氏名')]/following::input[@type='text'][1]").send_keys("佐藤太郎")
driver.find_element_by_xpath("//text()[contains(.,'住所')]/following::input[@type='text'][1]").send_keys("東京都千代田区")
driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click()
7.XPathの確認方法
Google Chromeの開発者ツール(F12)のコンソールで$x("<XPathの内容>")を入力して確認できます。
結果のinputの左の三角を開けば、nameを確認できます。