WebElement 内部を find_element_by_xpath() で再検索した時のつまずき


python selenium の WebDriver でブラウザテストを作成しているときにWebElement 内部 の検索にfind_element_by_xpath を使った際につまずきましたのでまとめます。

つまずき1:要素内部についての find_element_by_xpath() 検索のはずなのに、別の要素の値が取れてしまう

browserTest.py
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.chrome.webdriver import WebDriver

def printUserEmails(driver:WebDriver)->None:
    UserTables = driver.find_elements_by_xpath('//table[start-with(@name, "user")]')
    if len(UserTables) == 0:
        print('Userが表示されていません。')
        return
    for UserTable in UserTables:
        emailElement = UserTable.find_element_by_xpath('//input[@type="text" and @name="email"]')
        print(emailElement.get_attribute('value'))

■状況
「一度 find_elements_by_xpath() でとった WebElement の List について for ループを回しているにも関わらず一番最初の要素の同一の値が取れてしまう」という現象につまずきました。

■原因:XPath の文字列の最初の「//」が「HTML内の全要素」という意味のため再検索していた

xpath「//」←「HTML内の全要素」から検索するの意味
(こちらの記事に詳細に記載して頂いておりましたので参考にさせて頂きました。
https://www.techscore.com/tech/XML/XPath/XPath3/xpath03.html/#xpath3-2

element.find_element_by_xpath('//tag名') #  → Rootから検索するので同じデータが取得されてしまう
element.find_element_by_xpath('tag名') # → 対象要素から検索できる

■対応:xpath の文字列 から最初の「//」を消す

browserTest.py

#ーー中略ーー
    for UserTable in UserTables:
        emailElement = UserTable.find_element_by_xpath('input[@type="text" and @name="email"]')
        print(emailElement.get_attribute('value'))


これでルートHTMLから検索されることはなくなりました。

つまずき2:要素からEmailが見つからなくなってしまった。

上記の対応で、「//」を消して、root からの検索をしないようにしたのですが、今まで取得できていた、検索がかからなくなり失敗するようになってしまいました。

■原因:「//」が特殊で、通常XPathは全パスを指定しないといけないため。

通常XPath はHTML からの全要素を指定して記述することで、DOMを解析し要素を特定するようで(「//」が特殊で全要素を読み込むという意味
User の table タグ直下に input が存在しないため、検索に失敗しまいました。

element.find_element_by_xpath('tag名') 
# → 対象要素直下に検索対象のtagがないと検索失敗となる。

■対応:子要素 の 内部 全検索は「.//検索条件」

element.find_element_by_xpath('.//tag名') 
# → 対象要素直下に検索対象のタグがあれば取得する

要素.find_element_by_xpath('.//検索条件') で記述するようにすることで、current node からの検索として、全検索できるようでした。
https://stackoverflow.com/questions/21578839/how-to-find-the-element-within-element-in-selenium

browserTest.py
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.chrome.webdriver import WebDriver

def printUserEmails(driver:WebDriver)->None:
    UserTables = driver.find_elements_by_xpath('//table[start-with(@name, "user")]')
    if len(UserTables) == 0:
        print('Userが表示されていません。')
        return
    for UserTable in UserTables:
        emailElement = UserTable.find_element_by_xpath('.//input[@type="text" and @name="email"]')
        print(emailElement.get_attribute('value'))

以上です。