- find_element_by_xxx()
- find_elements_by_xxx()
- find_element()
- find_elements()
などのメソッドが用意されています。
今回は、それらを試してみた時のメモを残します。
なお、途中からSeleniumのPage Objectsパターンが出てきます。
ただ、今回使うPage Objectsは公式ドキュメントよりは簡易的なPage Objectsになっています。
6. Page Objects — Selenium Python Bindings 2 documentation
目次
- 環境
- Seleniumの対象とするHTML
- find_element_by_xxx()
- find_elements_by_xxx()
- find_element()
- find_elements()
- テストの実行
- ソースコード
環境
Seleniumの対象とするHTML
以下のHTMLを用意しました。
なお、id属性が重複していますが、これは意図的にやっています。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ol> <li id="id1">Text1</li> <li id="id1">Text2</li> <li id="id1">Text3</li> </ol> <ul> <li class="post1">Post1-1</li> <li class="post1">Post1-2</li> <li class="post2">Post2</li> </ul> </body> </html>
あとは、上記のindex.htmlファイルがあるディレクトリにて、
$ python -m http.server
を実行し、 http://localhost:8000
でHTMLファイルが表示されるようにします。
21.22. http.server — HTTP サーバ — Python 3.6.3 ドキュメント
find_element_by_xxx()
HTMLから
などで要素があるかを検索し、WebElement(selenium.webdriver.remote.webelement.WebElement
)を返します。
WebElement - 7. WebDriver API — Selenium Python Bindings 2 documentation
また、複数の要素がHTMLにあった場合は、そのうち最初に見つかったものを返します。
class TestFindElement: def setup_method(self): self.chrome = Chrome() self.chrome.get('http://localhost:8000') def teardown_method(self): self.chrome.quit() def test_find_element_by_xxx(self): id1 = self.chrome.find_element_by_id('id1') assert id1.text == 'Text1' post1_1 = self.chrome.find_element_by_class_name('post1') assert post1_1.text == 'Post1-1' post1_2 = self.chrome.find_element_by_css_selector('.post1') assert post1_2.text == 'Post1-1' post1_3 = self.chrome.find_element_by_xpath('//li[@class="post1"]') assert post1_3.text == 'Post1-1'
なお、要素が見つからない場合は、例外 selenium.common.exceptions.NoSuchElementException
を送出します。
def test_find_elemnt_by_xxx_without_element(self): try: self.chrome.find_element_by_id('id2') except NoSuchElementException: # エラー内容をprintする print(f'\n{traceback.format_exc()}')
find_elements_by_xxx()
find_element_by_xxx()と同じく、HTMLから
などで要素があるかを検索します。
戻り値は、WebElementのリストとなります。
def test_find_elements_by_xxx(self): id1 = self.chrome.find_elements_by_id('id1') assert len(id1) == 3 assert id1[0].text == 'Text1' post1_1 = self.chrome.find_elements_by_class_name('post1') assert len(post1_1) == 2 assert post1_1[0].text == 'Post1-1' post1_2 = self.chrome.find_elements_by_css_selector('.post1') assert len(post1_2) == 2 assert post1_2[0].text == 'Post1-1' post1_3 = self.chrome.find_elements_by_xpath('//li[@class="post1"]') assert len(post1_3) == 2 assert post1_3[0].text == 'Post1-1'
なお、find_element_by_xxx()と異なり、要素が見つからない場合は、長さ0のリストを返します。
そのため、要素の有無をテストする場合、例外の送出がないこちらを使うのが良いかもしれません。
def test_find_elements_by_xxx_without_element(self): post3 = self.chrome.find_elements_by_class_name('post3') assert len(post3) == 0
find_element()
最初、find_element_by_xxx()があるならいらないのでは?と思っていました。
しかし、SeleniumでPageObjectsパターンを使う場合は、これを使うのが良さそうだと分かりました。公式ドキュメントにも記載されています。
6. Page Objects — Selenium Python Bindings 2 documentation
今回は、簡易的なPageObjectsを実装してみます。
まず、index.html向けのクラス IndexPageObject
を用意します。そのクラスには
を実装します。
class IndexPageObject: # Seleniumで探す要素をクラス定数として定義する ID1 = (By.ID, 'id1') CLASS1 = (By.CLASS_NAME, 'post1') CLASS2 = (By.CLASS_NAME, 'post2') CLASS3 = (By.CLASS_NAME, 'post3') # これは存在しない要素 CSS = (By.CSS_SELECTOR, '.post1') XPATH = (By.XPATH, '//li[@class="post1"]') def __init__(self, driver: WebDriver): # WebDriverをインスタンスに渡す self.driver = driver
次に、ページに対する操作をメソッドとして実装します。
ページの要素を探す場合は、find_element()
メソッドを使います*1。
find_element() - 7. WebDriver API — Selenium Python Bindings 2 documentation
また、find_element()メソッドの引数には、クラス定数として定義したタプルを展開して渡します。
これにより、もしHTML構造が変わっても、クラス定数のタプルの内容を変更するだけです。テストコードには影響しません。
def get_post_using_find_element(self): # IDで探す場合 id1 = self.driver.find_element(*IndexPageObject.ID1)
あとは、テストコードの方で
します。
def test_find_element(self): index_page = IndexPageObject(self.chrome) post = index_page.get_post_using_find_element() assert post == 'Post1-1'
もし、要素が存在しない場合は、find_element_by_xxx()と同じく例外を送出します。
def get_post_using_find_element_without_element(self): try: # find_element()の場合、要素が存在しないと例外が送出される return self.driver.find_element(*IndexPageObject.CLASS3) except NoSuchElementException: print(f'\n{traceback.format_exc()}') raise
テストコードはこんな感じです。
def test_find_element_without_element(self): with pytest.raises(NoSuchElementException): index_page = IndexPageObject(self.chrome) index_page.get_post_using_find_element_without_element()
find_elements()
こちらは、find_elements_by_xxx()
をPageObjectsで実装する場合に使います。
def get_post_count_using_find_elements(self): # XPATHで探す場合 xpath1 = self.driver.find_elements(*IndexPageObject.XPATH) return len(xpath1)
テストコードです。
def test_find_elements(self): index_page = IndexPageObject(self.chrome) count = index_page.get_post_count_using_find_elements() assert count == 2
要素が存在しない場合は、find_elements_by_xxx()
と同様長さ0のリストが返ってきます。
def get_post_count_using_find_elements_without_element(self): # find_elements()の場合は、要素が存在しなくても例外とならず、長さ0のリストが返る elements = self.driver.find_elements(*IndexPageObject.CLASS3) return len(elements)
テストコードです。
def test_find_elements_without_element(self): index_page = IndexPageObject(self.chrome) count = index_page.get_post_count_using_find_elements_without_element() assert count == 0
テストの実行
いずれのテストもパスしました。
$ python -m pytest . -v -s ==== test session starts ==== platform darwin -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- /path/to/bin/python cachedir: .cache rootdir: /path/to/python_selenium_sample/e.g._find_element, inifile: collected 8 items test_selenium_find_element.py::TestFindElement::test_find_element_by_xxx PASSED test_selenium_find_element.py::TestFindElement::test_find_elemnt_by_xxx_without_element Traceback (most recent call last): File "/path/to/python_selenium_sample/e.g._find_element/test_selenium_find_element.py", line 33, in test_find_elemnt_by_xxx_without_element self.chrome.find_element_by_id('id2') File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 341, in find_element_by_id return self.find_element(by=By.ID, value=id_) File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 843, in find_element 'value': value})['value'] File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 308, in execute self.error_handler.check_response(response) File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"id","selector":"id2"} (Session info: chrome=62.0.3202.75) (Driver info: chromedriver=2.33.506106 (8a06c39c4582fbfbab6966dbb1c38a9173bfb1a2),platform=Mac OS X 10.11.6 x86_64) PASSED test_selenium_find_element.py::TestFindElement::test_find_elements_by_xxx PASSED test_selenium_find_element.py::TestFindElement::test_find_elements_by_xxx_without_element PASSED test_selenium_find_element.py::TestFindElement::test_find_element PASSED test_selenium_find_element.py::TestFindElement::test_find_element_without_element Traceback (most recent call last): File "/path/to/python_selenium_sample/e.g._find_element/index_page_object.py", line 41, in get_post_using_find_element_without_element return self.driver.find_element(*IndexPageObject.CLASS3) File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 843, in find_element 'value': value})['value'] File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 308, in execute self.error_handler.check_response(response) File "/path/to/python_selenium_sample/env363/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"class name","selector":"post3"} (Session info: chrome=62.0.3202.75) (Driver info: chromedriver=2.33.506106 (8a06c39c4582fbfbab6966dbb1c38a9173bfb1a2),platform=Mac OS X 10.11.6 x86_64) PASSED test_selenium_find_element.py::TestFindElement::test_find_elements PASSED test_selenium_find_element.py::TestFindElement::test_find_elements_without_element PASSED ==== 8 passed in 20.19 seconds ====
ソースコード
GitHubに上げました。e.g._find_element
ディレクトリ以下が、今回のファイルです。
thinkAmi-sandbox/python_selenium-sample
なお、説明用にPageObjectsでassertしていますが、本来は不要です。
*1:公式ドキュメントには、「‘Private’ method used by the find_element_by* methods. Use the corresponding find_element_by* instead of this.」と書いてあるので、使ってよいか不安ですが...