※この記事は新入社員の問いにあることないことないことないこと話した備忘録です。
本日の困りごと
Seleniumを使ってのWebサイトやWebシステムの自動検証。
find_element_by_xpath
を使ってもこの要素取れないんです!
この直前の要素なら取れるのに…!
経緯
元々「このサイトは何故かSeleniumで検証できないんですよねー」と言われているWebサイトがあった。
えぇwまさかぁwww
作った人(もういない)の組み方が悪いんだってwww
と、メンバーに「組みなおしてみ?」と依頼する。
すると返ってきたのがこの言葉。
なるほど。丸投げしてたのでHTMLでも見てみようじゃないか。
今回の解決方法
みんな、HTMLにはclass属性やid属性を付けようぜ!
…とはいえ「検証の自動化」を行うために画面を修正するのはナンセンスである。
WebサイトWebサイトと言っているが、どうせ中身はWebシステム。phpだったりJSPだったりするものだろう。
今回はsample.html
から、記事2の本文を取得したい!
…という要件だったとして解説をしたいと思う。
<html>
<head><!-- 省略 --></head>
<body>
<div id='wrap'>
<div class='article'>
<article>
<h1>記事1のタイトル</h1>
<div>
<p>記事1の本文</p>
</div>
</article>
</div><!-- .article -->
<div class='article'>
<article>
<h1>記事2のタイトル</h1>
<div>
<p>記事2の本文</p>
</div>
</article>
</div><!-- .article -->
</div><!-- #wrap -->
</body>
</html>
「動かない」といわれた指定方法がこちら。
path = "/html/body/div/div[2]/article/div"
elmt = driver.find_element_by_xpath(path)
html
の中のbody
の中のdiv
(id=wrap)の中のdiv
(class=article)の2つ目のarticle
の中のdiv
を指定したいらしい。
ふむふむ。それって"/html/body/div[5]"なんじゃね?
path = "/html/body/div[5]"
elmt = driver.find_element_by_xpath(path)
xpathの指定では、画面の要素がどのようにネストしているかは関係ない。
**「上から何回目に登場したdivか?」**が判定の基準となる。
CSSの疑似クラス:nth-child()
に似ているだろうか。
2つ目の<div class='article'>
を非表示にしたくて
div#wrap div:nth-child(2) { display: none; }
と指定すると、1つ目のdiv#wrap
の中の「2つ目のdiv」を軒並み消してしまう。
sample.htmlの場合は記事1の本文と記事2が全て非表示となる。
(サンプルなのでclass指定しろよ!…とは言ってはいけない)
うーん。なんか例が悪いな。。
結論
なんせプログラマという生き物はインデントとかネストとかを気にして生きている人種なので勘違いしてしまうこともあるのだが、外部のプログラムにそのHTMLがどんな構成で書かれているかはわからない。
CSSだろうがPythonだろうがJavaだろうが、彼らは人の意思を酌んで動いてくれるものではないのだ。
今回の場合のように「記事が複数あるから同じようなブロックがある」を知っているのは、開発した人だけが知っている。
find_element_by_xpathで要素を指定する時はその要素は上から数えて何回目に出現してるか?を注意してみてほしい。