diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..f8d496e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,26 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python +build: + os: ubuntu-20.04 + tools: + python: "3.10" + +# Optionally set the version of Python and requirements required to build your docs +python: + install: + - requirements: requirements.txt + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: source/conf.py + fail_on_warning: false + +# Optionally build your docs in additional formats such as PDF +formats: + - pdf diff --git a/README.rst b/README.rst index 1f25ce5..54d9a43 100644 --- a/README.rst +++ b/README.rst @@ -3,16 +3,16 @@ Selenium Python Bindings Documentation https://selenium-python.readthedocs.io -NOTE: THIS IS NOT AN OFFICIAL DOCUMENTATION +NOTE: THIS IS NOT OFFICIAL DOCUMENTATION -This is not an official documentation. If you would like to contribute to this +This is not official documentation. If you would like to contribute to this documentation, you can `fork this project in GitHub and send pull requests `_. You can also send your feedback -to my email: baiju.m.mail AT gmail DOT com. So far 50+ community members have -contributed to this project (See the closed pull requests). I encourage -contributors to add more sections and make it an awesome documentation! If you -know any translation of this document, please send a PR to update the below -list. +to my email: baiju.m.mail AT gmail DOT com. So far 60+ community members have +contributed to this project (See the 'Closed' pull requests). I encourage +contributors to add more sections to make this documentation even more awesome! +If you know any translations of this document, or would like to create new translations, +please send a PR to update the below list. **Translations:** diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index d571ab2..0000000 --- a/TODO.txt +++ /dev/null @@ -1,4 +0,0 @@ -- Cover basics of unittest -- XPATH (Appendix) -- Mouse over -- Complete: Test Design Considerations diff --git a/source/api.rst b/source/api.rst index 8e91d06..1118aed 100644 --- a/source/api.rst +++ b/source/api.rst @@ -23,16 +23,32 @@ Then, you can access the classes like this:: webdriver.Firefox webdriver.FirefoxProfile + webdriver.FirefoxOptions + webdriver.FirefoxService webdriver.Chrome webdriver.ChromeOptions + webdriver.ChromeService webdriver.Ie - webdriver.Opera - webdriver.PhantomJS + webdriver.IeOptions + webdriver.IeService + webdriver.Edge + webdriver.ChromiumEdge + webdriver.EdgeOptions + webdriver.EdgeService + webdriver.Safari + webdriver.SafariOptions + webdriver.SafariService + webdriver.WebKitGTK + webdriver.WebKitGTKOptions + webdriver.WebKitGTKService + webdriver.WPEWebKit + webdriver.WPEWebKitOptions + webdriver.WPEWebKitService webdriver.Remote webdriver.DesiredCapabilities webdriver.ActionChains - webdriver.TouchActions webdriver.Proxy + webdriver.Keys The special keys class (``Keys``) can be imported like this:: @@ -139,16 +155,6 @@ See the :ref:`selenium-remote-webdriver` section for example usages of desired c :member-order: groupwise :show-inheritance: -Touch Actions -~~~~~~~~~~~~~ - -.. automodule:: selenium.webdriver.common.touch_actions - :members: - :undoc-members: - :special-members: __init__ - :member-order: groupwise - :show-inheritance: - Proxy ~~~~~ @@ -353,46 +359,6 @@ Internet Explorer WebDriver :member-order: groupwise :show-inheritance: -Android WebDriver -~~~~~~~~~~~~~~~~~ - -.. automodule:: selenium.webdriver.android.webdriver - :members: - :undoc-members: - :special-members: __init__ - :member-order: groupwise - :show-inheritance: - -Opera WebDriver -~~~~~~~~~~~~~~~ - -.. automodule:: selenium.webdriver.opera.webdriver - :members: - :undoc-members: - :special-members: __init__ - :member-order: groupwise - :show-inheritance: - -PhantomJS WebDriver -~~~~~~~~~~~~~~~~~~~ - -.. automodule:: selenium.webdriver.phantomjs.webdriver - :members: - :undoc-members: - :special-members: __init__ - :member-order: groupwise - :show-inheritance: - -PhantomJS WebDriver Service -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: selenium.webdriver.phantomjs.service - :members: - :undoc-members: - :special-members: __init__ - :member-order: groupwise - :show-inheritance: - Safari WebDriver ~~~~~~~~~~~~~~~~ diff --git a/source/conf.py b/source/conf.py index 316e761..3af276f 100644 --- a/source/conf.py +++ b/source/conf.py @@ -41,7 +41,7 @@ # General information about the project. project = u'Selenium Python Bindings' -copyright = u'2011-2018, Baiju Muthukadan' +copyright = u'2011-2024, Baiju Muthukadan' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -257,7 +257,7 @@ # The format is a list of tuples containing the path and title. #epub_pre_files = [] -# HTML files shat should be inserted after the pages created by sphinx. +# HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] diff --git a/source/faq.rst b/source/faq.rst index 9de4370..529b1f6 100644 --- a/source/faq.rst +++ b/source/faq.rst @@ -9,10 +9,10 @@ How to use ChromeDriver ? ~~~~~~~~~~~~~~~~~~~~~~~~~ Download the latest `chromedriver from download page -`_. Unzip the +`_. Unzip the file:: - unzip chromedriver_linux32_x.x.x.x.zip + unzip chromedriver_linux64.zip You should see a ``chromedriver`` executable. Now you can create an instance of Chrome WebDriver like this:: diff --git a/source/getting-started.rst b/source/getting-started.rst index 3dab345..8c71e49 100644 --- a/source/getting-started.rst +++ b/source/getting-started.rst @@ -13,11 +13,12 @@ Python like this. from selenium import webdriver from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.by import By driver = webdriver.Firefox() driver.get("http://www.python.org") assert "Python" in driver.title - elem = driver.find_element_by_name("q") + elem = driver.find_element(By.NAME, "q") elem.clear() elem.send_keys("pycon") elem.send_keys(Keys.RETURN) @@ -36,12 +37,14 @@ Example Explained The `selenium.webdriver` module provides all the WebDriver implementations. Currently supported WebDriver implementations are Firefox, Chrome, IE and -Remote. The `Keys` class provide keys in the keyboard like RETURN, F1, ALT etc. +Remote. The `Keys` class provide keys in the keyboard like RETURN, F1, ALT etc. +The `By` class is used to locate elements within a document. :: from selenium import webdriver from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.by import By Next, the instance of Firefox WebDriver is created. @@ -57,20 +60,20 @@ completely loaded*:: driver.get("http://www.python.org") -The next line is an assertion to confirm that title has "Python" word in it:: +The next line is an assertion to confirm that title has the word "Python" in it:: assert "Python" in driver.title -WebDriver offers a number of ways to find elements using one of the -`find_element_by_*` methods. For example, the input text element can be located -by its `name` attribute using `find_element_by_name` method. A detailed -explanation of finding elements is available in the :ref:`locating-elements` +WebDriver offers a number of ways to find elements using the +`find_element` method. For example, the input text element can be located +by its `name` attribute using the `find_element` method and using By.NAME as its first parameter. +A detailed explanation of finding elements is available in the :ref:`locating-elements` chapter:: - elem = driver.find_element_by_name("q") + elem = driver.find_element(By.NAME, "q") Next, we are sending keys, this is similar to entering keys using your keyboard. -Special keys can be sent using `Keys` class imported from +Special keys can be sent using the `Keys` class imported from `selenium.webdriver.common.keys`. To be safe, we'll first clear any pre-populated text in the input field (e.g. "Search") so it doesn't affect our search results:: @@ -84,9 +87,9 @@ ensure that some results are found, make an assertion:: assert "No results found." not in driver.page_source -Finally, the browser window is closed. You can also call `quit` method instead -of `close`. The `quit` will exit entire browser whereas `close` will close one -tab, but if just one tab was open, by default most browser will exit entirely.:: +Finally, the browser window is closed. You can also call the `quit` method instead +of `close`. The `quit` method will exit the browser whereas `close` will close one +tab, but if just one tab was open, by default most browsers will exit entirely.:: driver.close() @@ -96,16 +99,17 @@ Using Selenium to write tests Selenium is mostly used for writing test cases. The `selenium` package itself doesn't provide a testing tool/framework. You can write test cases using -Python's unittest module. The other options for a tool/framework are `pytest `_ -and `nose `_. +Python's unittest module. Alternatively, you may consider `pytest +`_ for writing tests. In this chapter, we use `unittest` as the framework of choice. Here is the -modified example which uses unittest module. This is a test for `python.org` +modified example which uses the unittest module. This is a test for the `python.org` search functionality:: import unittest from selenium import webdriver from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.by import By class PythonOrgSearch(unittest.TestCase): @@ -116,10 +120,10 @@ search functionality:: driver = self.driver driver.get("http://www.python.org") self.assertIn("Python", driver.title) - elem = driver.find_element_by_name("q") + elem = driver.find_element(By.NAME, "q") elem.send_keys("pycon") elem.send_keys(Keys.RETURN) - assert "No results found." not in driver.page_source + self.assertNotIn("No results found.", driver.page_source) def tearDown(self): @@ -140,11 +144,11 @@ You can run the above test case from a shell like this:: The above result shows that the test has been successfully completed. -Note: To run the above test in IPython or Jupyter, you should pass a couple of arguments to the `main` function as shown below: -:: +Note: To run the above test in IPython or Jupyter, you should pass a couple of +arguments to the `main` function as shown below:: unittest.main(argv=['first-arg-is-ignored'], exit=False) - + Walkthrough of the example @@ -152,27 +156,28 @@ Walkthrough of the example Initially, all the basic modules required are imported. The `unittest `_ module is a built-in Python -based on Java's JUnit. This module provides the framework for organizing the +module based on Java's JUnit. This module provides the framework for organizing the test cases. The `selenium.webdriver` module provides all the WebDriver -implementations. Currently supported WebDriver implementations are Firefox, +implementations. Currently supported WebDriver implementations are: Firefox, Chrome, IE and Remote. The `Keys` class provides keys in the keyboard like -RETURN, F1, ALT etc. +RETURN, F1, ALT etc. The `By` class is used to locate elements within a document. :: import unittest from selenium import webdriver from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.by import By The test case class is inherited from `unittest.TestCase`. Inheriting from -`TestCase` class is the way to tell `unittest` module that this is a test case:: +the `TestCase` class is the way to tell `unittest` module that this is a test case:: class PythonOrgSearch(unittest.TestCase): -The `setUp` is part of initialization, this method will get called before every +The `setUp` method is part of initialization. This method will get called before every test function which you are going to write in this test case class. Here you -are creating the instance of Firefox WebDriver. +are creating an instance of a Firefox WebDriver. :: @@ -180,7 +185,7 @@ are creating the instance of Firefox WebDriver. self.driver = webdriver.Firefox() This is the test case method. The test case method should always start with -characters `test`. The first line inside this method create a local reference +characters `test`. The first line inside this method creates a local reference to the driver object created in `setUp` method. :: @@ -190,27 +195,27 @@ to the driver object created in `setUp` method. The `driver.get` method will navigate to a page given by the URL. WebDriver will wait until the page has fully loaded (that is, the "onload" event has -fired) before returning control to your test or script. *Be aware that if your +fired) before returning control to your test or script. *Be aware that if your page uses a lot of AJAX on load then WebDriver may not know when it has completely loaded*:: driver.get("http://www.python.org") -The next line is an assertion to confirm that title has "Python" word in it:: +The next line is an assertion to confirm that title has the word "Python" in it:: self.assertIn("Python", driver.title) -WebDriver offers a number of ways to find elements using one of the -`find_element_by_*` methods. For example, the input text element can be located -by its `name` attribute using `find_element_by_name` method. Detailed +WebDriver offers a number of ways to find elements using the +`find_element` method. For example, the input text element can be located +by its `name` attribute using the `find_element` method. Detailed explanation of finding elements is available in the :ref:`locating-elements` chapter:: - elem = driver.find_element_by_name("q") + elem = driver.find_element(By.NAME, "q") Next, we are sending keys, this is similar to entering keys using your keyboard. -Special keys can be send using `Keys` class imported from +Special keys can be sent using the `Keys` class imported from `selenium.webdriver.common.keys`:: elem.send_keys("pycon") @@ -219,13 +224,13 @@ Special keys can be send using `Keys` class imported from After submission of the page, you should get the result as per search if there is any. To ensure that some results are found, make an assertion:: - assert "No results found." not in driver.page_source + self.assertNotIn("No results found.", driver.page_source) The `tearDown` method will get called after every test method. This is a place to do all cleanup actions. In the current method, the browser window is closed. -You can also call `quit` method instead of `close`. The `quit` will exit the +You can also call the `quit` method instead of `close`. The `quit` method will exit the entire browser, whereas `close` will close a tab, but if it is the only tab -opened, by default most browser will exit entirely.:: +opened, by default most browsers will exit entirely.:: def tearDown(self): self.driver.close() @@ -240,7 +245,7 @@ Final lines are some boiler plate code to run the test suite:: Using Selenium with remote WebDriver ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To use the remote WebDriver, you should have Selenium server running. To run +To use the remote WebDriver, you should have the Selenium server running. To run the server, use this command:: java -jar selenium-server-standalone-2.x.x.jar @@ -249,29 +254,17 @@ While running the Selenium server, you could see a message looking like this:: 15:43:07.541 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub -The above line says that you can use this URL for connecting to remote +The above line says that you can use this URL for connecting to the remote WebDriver. Here are some examples:: from selenium import webdriver - from selenium.webdriver.common.desired_capabilities import DesiredCapabilities - - driver = webdriver.Remote( - command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities=DesiredCapabilities.CHROME) - - driver = webdriver.Remote( - command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities=DesiredCapabilities.OPERA) driver = webdriver.Remote( command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities=DesiredCapabilities.HTMLUNITWITHJS) - -The desired capabilities is a dictionary, so instead of using the default -dictionaries, you can specify the values explicitly:: + options=webdriver.ChromeOptions() + ) driver = webdriver.Remote( command_executor='http://127.0.0.1:4444/wd/hub', - desired_capabilities={'browserName': 'htmlunit', - 'version': '2', - 'javascriptEnabled': True}) + options=webdriver.FirefoxOptions() + ) diff --git a/source/index.rst b/source/index.rst index 7d51db0..8507a6e 100644 --- a/source/index.rst +++ b/source/index.rst @@ -11,7 +11,7 @@ Selenium with Python This is not an official documentation. If you would like to contribute to this documentation, you can `fork this project in GitHub and send pull requests `_. You can also send - your feedback to my email: baiju.m.mail AT gmail DOT com. So far 50+ + your feedback to my email: baiju.m.mail AT gmail DOT com. So far 60+ community members have contributed to this project (See the closed pull requests). I encourage contributors to add more sections and make it an awesome documentation! If you know any translation of this document, please diff --git a/source/installation.rst b/source/installation.rst index da5ecdd..343bff3 100644 --- a/source/installation.rst +++ b/source/installation.rst @@ -18,19 +18,18 @@ This documentation explains Selenium 2 WebDriver API. Selenium 1 / Selenium RC API is not covered here. -Downloading Python bindings for Selenium -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +Installing Python bindings for Selenium +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Use `pip `_ to install the selenium -package. Python 3.6 has pip available in the `standard library -`_. Using `pip`, you can +Use `pip `_ to install the selenium +package. Python 3 has pip available in the `standard library +`_. Using `pip`, you can install selenium like this:: pip install selenium -You may consider using `virtualenv `_ to create -isolated Python environments. Python 3 has `venv +You may consider using `virtualenv `_ to +create isolated Python environments. Python 3 has `venv `_ which is almost the same as virtualenv. @@ -38,6 +37,52 @@ You can also download Python bindings for Selenium from the `PyPI page for selenium package `_. and install manually. + +Instructions for Windows users +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Install Python 3 using the `MSI available in python.org download page + `_. + +2. Start a command prompt using the ``cmd.exe`` program and run the ``pip`` + command as given below to install `selenium`. + + :: + + C:\Python39\Scripts\pip.exe install selenium + +Now you can run your test scripts using Python. For example, if you have +created a Selenium based script and saved it inside +``C:\my_selenium_script.py``, you can run it like this:: + + C:\Python39\python.exe C:\my_selenium_script.py + + +Installing from Git sources +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To build Selenium Python from the source code, clone `the official repository +`_. It contains the source code for +all official Selenium flavors, like Python, Java, Ruby and others. The Python +code resides in the ``/py`` directory. To build, you will also need the `Bazel +`_ build system. + +.. note:: + + Currently, as Selenium gets near to the 4.0.0 release, it requires Bazel 3.2.0 + (`Install instructions + `_), even though 3.3.0 + is already available. + +To build a Wheel from the sources, run the following command from the repository +root:: + + bazel //py:selenium-wheel + +This command will prepare the source code with some preprocessed JS files needed +by some webdriver modules and build the ``.whl`` package inside the +``./bazel-bin/py/`` directory. Afterwards, you can use ``pip`` to install it. + Drivers ~~~~~~~ @@ -55,7 +100,7 @@ Other supported browsers will have their own drivers available. Links to some of the more popular browser drivers follow. +--------------+-----------------------------------------------------------------------+ -| **Chrome**: | https://sites.google.com/a/chromium.org/chromedriver/downloads | +| **Chrome**: | https://sites.google.com/chromium.org/driver/ | +--------------+-----------------------------------------------------------------------+ | **Edge**: | https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ | +--------------+-----------------------------------------------------------------------+ @@ -66,31 +111,28 @@ the more popular browser drivers follow. For more information about driver installation, please refer the `official documentation -`_. - -Detailed instructions for Windows users -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. Note:: +`_. - You should have an internet connection to perform this installation. - -1. Install Python 3.6 using the `MSI available in python.org download page - `_. - -2. Start a command prompt using the ``cmd.exe`` program and run the ``pip`` - command as given below to install `selenium`. +Starting from version ``4.6.0`` (November 4, 2022) +selenium comes with **Selenium Manager** packed in distribution. - :: - - C:\Python35\Scripts\pip.exe install selenium +**Selenium Manager** is a new tool that helps to get a working environment +to run **Selenium** out of the box: -Now you can run your test scripts using Python. For example, if you have -created a Selenium based script and saved it inside -``C:\my_selenium_script.py``, you can run it like this:: +* automatically discovers, downloads, and caches the ``drivers`` + required by Selenium when these ``drivers`` are unavailable; +* automatically discovers, downloads, and caches the ``browsers`` + driven with Selenium (Chrome, Firefox, and Edge) + when these ``browsers`` are not installed in the local system. - C:\Python35\python.exe C:\my_selenium_script.py +For example, to see the result of **Selenium Manager** work +just run any selenium script without previous driver setup +and explore `~/.cache/selenium`. +More about **Selenium Manager** you can read in the +`documentation `_ +and +`blog `_. Downloading Selenium server ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -130,29 +172,3 @@ command. Similarly, you can provide a relative or absolute path to Selenium server jar file. Then, the command will look something like this:: /path/to/java -jar /path/to/selenium-server-standalone-2.x.x.jar - - -Installing from Git sources -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To build Selenium Python from the source code, clone `the official repository -`_. It contains the source code for -all official Selenium flavors, like Python, Java, Ruby and others. The Python -code resides in the ``/py`` directory. To build, you will also need the `Bazel -`_ build system. - -.. note:: - - Currently, as Selenium gets near to the 4.0.0 release, it requires Bazel 3.2.0 - (`Install instructions - `_), even though 3.3.0 - is already available. - -To build a Wheel from the sources, run the following command from the repository -root:: - - bazel //py:selenium-wheel - -This command will prepare the source code with some preprocessed JS files needed -by some webdriver modules and build the ``.whl`` package inside the -``./bazel-bin/py/`` directory. Afterwards, you can use ``pip`` to install it. diff --git a/source/locating-elements.rst b/source/locating-elements.rst index 630f864..7bc499e 100644 --- a/source/locating-elements.rst +++ b/source/locating-elements.rst @@ -4,34 +4,13 @@ Locating Elements ----------------- There are various strategies to locate elements in a page. You can use the most -appropriate one for your case. Selenium provides the following methods to +appropriate one for your case. Selenium provides the following method to locate elements in a page: -- `find_element_by_id` -- `find_element_by_name` -- `find_element_by_xpath` -- `find_element_by_link_text` -- `find_element_by_partial_link_text` -- `find_element_by_tag_name` -- `find_element_by_class_name` -- `find_element_by_css_selector` - +- `find_element` **To find multiple elements (these methods will return a list):** -- `find_elements_by_name` -- `find_elements_by_xpath` -- `find_elements_by_link_text` -- `find_elements_by_partial_link_text` -- `find_elements_by_tag_name` -- `find_elements_by_class_name` -- `find_elements_by_css_selector` - - -Apart from the public methods given above, there are two private methods which -might be useful for locating page elements: - -- `find_element` - `find_elements` @@ -42,18 +21,32 @@ Example usage:: driver.find_element(By.XPATH, '//button[text()="Some text"]') driver.find_elements(By.XPATH, '//button') - +The attributes available for the `By` class are used to locate elements on a page. These are the attributes available for `By` class:: ID = "id" + NAME = "name" XPATH = "xpath" LINK_TEXT = "link text" PARTIAL_LINK_TEXT = "partial link text" - NAME = "name" TAG_NAME = "tag name" CLASS_NAME = "class name" CSS_SELECTOR = "css selector" +The 'By' class is used to specify which attribute is used to locate elements on a page. +These are the various ways the attributes are used to locate elements on a page:: + + find_element(By.ID, "id") + find_element(By.NAME, "name") + find_element(By.XPATH, "xpath") + find_element(By.LINK_TEXT, "link text") + find_element(By.PARTIAL_LINK_TEXT, "partial link text") + find_element(By.TAG_NAME, "tag name") + find_element(By.CLASS_NAME, "class name") + find_element(By.CSS_SELECTOR, "css selector") + +If you want to locate several elements with the same attribute replace find_element with find_elements. + Locating by Id ~~~~~~~~~~~~~~ @@ -73,11 +66,11 @@ For instance, consider this page source:: - + The form element can be located like this:: - login_form = driver.find_element_by_id('loginForm') + login_form = driver.find_element(By.ID, 'loginForm') Locating by Name @@ -99,16 +92,16 @@ For instance, consider this page source:: - + The username & password elements can be located like this:: - username = driver.find_element_by_name('username') - password = driver.find_element_by_name('password') + username = driver.find_element(By.NAME, 'username') + password = driver.find_element(By.NAME, 'password') This will give the "Login" button as it occurs before the "Clear" button:: - continue = driver.find_element_by_name('continue') + continue_button = driver.find_element(By.NAME, 'continue') Locating by XPath @@ -144,13 +137,13 @@ For instance, consider this page source:: - + The form elements can be located like this:: - login_form = driver.find_element_by_xpath("/html/body/form[1]") - login_form = driver.find_element_by_xpath("//form[1]") - login_form = driver.find_element_by_xpath("//form[@id='loginForm']") + login_form = driver.find_element(By.XPATH, "/html/body/form[1]") + login_form = driver.find_element(By.XPATH, "//form[1]") + login_form = driver.find_element(By.XPATH, "//form[@id='loginForm']") 1. Absolute path (would break if the HTML was changed only slightly) @@ -161,9 +154,9 @@ The form elements can be located like this:: The username element can be located like this:: - username = driver.find_element_by_xpath("//form[input/@name='username']") - username = driver.find_element_by_xpath("//form[@id='loginForm']/input[1]") - username = driver.find_element_by_xpath("//input[@name='username']") + username = driver.find_element(By.XPATH, "//form[input/@name='username']") + username = driver.find_element(By.XPATH, "//form[@id='loginForm']/input[1]") + username = driver.find_element(By.XPATH, "//input[@name='username']") 1. First form element with an input child element with `name` set to `username` @@ -174,8 +167,8 @@ The username element can be located like this:: The "Clear" button element can be located like this:: - clear_button = driver.find_element_by_xpath("//input[@name='continue'][@type='button']") - clear_button = driver.find_element_by_xpath("//form[@id='loginForm']/input[4]") + clear_button = driver.find_element(By.XPATH, "//input[@name='continue'][@type='button']") + clear_button = driver.find_element(By.XPATH, "//form[@id='loginForm']/input[4]") 1. Input with attribute `name` set to `continue` and attribute `type` set to @@ -220,12 +213,12 @@ For instance, consider this page source:: Continue Cancel - + The continue.html link can be located like this:: - continue_link = driver.find_element_by_link_text('Continue') - continue_link = driver.find_element_by_partial_link_text('Conti') + continue_link = driver.find_element(By.LINK_TEXT, 'Continue') + continue_link = driver.find_element(By.PARTIAL_LINK_TEXT, 'Conti') Locating Elements by Tag Name @@ -242,11 +235,11 @@ For instance, consider this page source::

Welcome

Site content goes here.

- + The heading (h1) element can be located like this:: - heading1 = driver.find_element_by_tag_name('h1') + heading1 = driver.find_element(By.TAG_NAME, 'h1') Locating Elements by Class Name @@ -263,11 +256,11 @@ For instance, consider this page source::

Site content goes here.

- + The "p" element can be located like this:: - content = driver.find_element_by_class_name('content') + content = driver.find_element(By.CLASS_NAME, 'content') Locating Elements by CSS Selectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -284,11 +277,11 @@ For instance, consider this page source::

Site content goes here.

- + The "p" element can be located like this:: - content = driver.find_element_by_css_selector('p.content') + content = driver.find_element(By.CSS_SELECTOR, 'p.content') `Sauce Labs has good documentation `_ on CSS diff --git a/source/navigating.rst b/source/navigating.rst index 2e1c511..9da1e0e 100644 --- a/source/navigating.rst +++ b/source/navigating.rst @@ -28,10 +28,10 @@ ways to find elements. For example, given an element defined as:: you could find it using any of:: - element = driver.find_element_by_id("passwd-id") - element = driver.find_element_by_name("passwd") - element = driver.find_element_by_xpath("//input[@id='passwd-id']") - element = driver.find_element_by_css_selector("input#passwd-id") + element = driver.find_element(By.ID, "passwd-id") + element = driver.find_element(By.NAME, "passwd") + element = driver.find_element(By.XPATH, "//input[@id='passwd-id']") + element = driver.find_element(By.CSS_SELECTOR, "input#passwd-id") You can also look for a link by its text, but be careful! The text must be an exact match! You should also be careful when using `XPATH in WebDriver`. If @@ -74,8 +74,8 @@ about the other elements? You can "toggle" the state of the drop down, and you can use "setSelected" to set something like an `OPTION` tag selected. Dealing with `SELECT` tags isn't too bad:: - element = driver.find_element_by_xpath("//select[@name='name']") - all_options = element.find_elements_by_tag_name("option") + element = driver.find_element(By.XPATH, "//select[@name='name']") + all_options = element.find_elements(By.TAG_NAME, "option") for option in all_options: print("Value is: %s" % option.get_attribute("value")) option.click() @@ -88,7 +88,7 @@ elements. WebDriver's support classes include one called a "Select", which provides useful methods for interacting with these:: from selenium.webdriver.support.ui import Select - select = Select(driver.find_element_by_name('name')) + select = Select(driver.find_element(By.NAME, 'name')) select.select_by_index(index) select.select_by_visible_text("text") select.select_by_value(value) @@ -96,7 +96,7 @@ provides useful methods for interacting with these:: WebDriver also provides features for deselecting all the selected options:: - select = Select(driver.find_element_by_id('id')) + select = Select(driver.find_element(By.ID, 'id')) select.deselect_all() This will deselect all OPTIONs from that particular SELECT on the page. @@ -104,9 +104,9 @@ This will deselect all OPTIONs from that particular SELECT on the page. Suppose in a test, we need the list of all default selected options, Select class provides a property method that returns a list:: - select = Select(driver.find_element_by_xpath("//select[@name='name']")) + select = Select(driver.find_element(By.XPATH, "//select[@name='name']")) all_selected_options = select.all_selected_options - + To get all available options:: options = select.options @@ -115,7 +115,7 @@ Once you've finished filling out the form, you probably want to submit it. One way to do this would be to find the "submit" button and click it:: # Assume the button has the ID "submit" :) - driver.find_element_by_id("submit").click() + driver.find_element(By.ID, "submit").click() Alternatively, WebDriver has the convenience method "submit" on every element. If you call this on an element within a form, WebDriver will walk up the DOM @@ -131,8 +131,8 @@ Drag and drop You can use drag and drop, either moving an element by a certain amount, or on to another element:: - element = driver.find_element_by_name("source") - target = driver.find_element_by_name("target") + element = driver.find_element(By.NAME, "source") + target = driver.find_element(By.NAME, "target") from selenium.webdriver import ActionChains action_chains = ActionChains(driver) @@ -144,9 +144,9 @@ Moving between windows and frames It's rare for a modern web application not to have any frames or to be constrained to a single window. WebDriver supports moving between named windows -using the "switch_to_window" method:: +using the "switch_to.window" method:: - driver.switch_to_window("windowName") + driver.switch_to.window("windowName") All calls to ``driver`` will now be interpreted as being directed to the particular window. But how do you know the window's name? Take a look at the @@ -154,20 +154,20 @@ javascript or link that opened it:: Click here to open a new window -Alternatively, you can pass a "window handle" to the "switch_to_window()" +Alternatively, you can pass a "window handle" to the "switch_to.window()" method. Knowing this, it's possible to iterate over every open window like so:: for handle in driver.window_handles: - driver.switch_to_window(handle) + driver.switch_to.window(handle) You can also swing from frame to frame (or into iframes):: - driver.switch_to_frame("frameName") + driver.switch_to.frame("frameName") It's possible to access subframes by separating the path with a dot, and you can specify the frame by its index too. That is:: - driver.switch_to_frame("frameName.0.child") + driver.switch_to.frame("frameName.0.child") would go to the frame named "child" of the first subframe of the frame called "frameName". **All frames are evaluated as if from *top*.** @@ -175,7 +175,7 @@ would go to the frame named "child" of the first subframe of the frame called Once we are done with working on frames, we will have to come back to the parent frame which can be done using:: - driver.switch_to_default_content() + driver.switch_to.default_content() Popup dialogs @@ -226,7 +226,7 @@ that the cookie will be valid for: driver.get("http://www.example.com") # Now set the cookie. This one's valid for the entire domain - cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’} + cookie = {'name' : 'foo', 'value' : 'bar'} driver.add_cookie(cookie) # And now output all the available cookies for the current URL diff --git a/source/page-objects.rst b/source/page-objects.rst index ec81c7a..e628ad7 100644 --- a/source/page-objects.rst +++ b/source/page-objects.rst @@ -3,13 +3,14 @@ Page Objects ------------ -This chapter is a tutorial introduction to page objects design pattern. A page -object represents an area in the web application user interface that your test -is interacting. +This chapter is a tutorial introduction to the Page Objects design pattern. A +page object represents an area where the test interacts within the web +application user interface. Benefits of using page object pattern: -* Creating reusable code that can be shared across multiple test cases +* Easy to read test cases +* Creating reusable code that can share across multiple test cases * Reducing the amount of duplicated code * If the user interface changes, the fix needs changes in only one place @@ -17,8 +18,9 @@ Benefits of using page object pattern: Test case ~~~~~~~~~ -Here is a test case which searches for a word in python.org website and ensure -some results are found. +Here is a test case that searches for a word on the `python.org` website and +ensures some results. The following section will introduce the `page` module +where the page objects will be defined. :: @@ -34,22 +36,21 @@ some results are found. self.driver.get("http://www.python.org") def test_search_in_python_org(self): - """ - Tests python.org search feature. Searches for the word "pycon" then verified that some results show up. - Note that it does not look for any particular text in search results page. This test verifies that - the results were not empty. - """ + """Tests python.org search feature. Searches for the word "pycon" then + verified that some results show up. Note that it does not look for + any particular text in search results page. This test verifies that + the results were not empty.""" #Load the main page. In this case the home page of Python.org. main_page = page.MainPage(self.driver) #Checks if the word "Python" is in title - assert main_page.is_title_matches(), "python.org title doesn't match." + self.assertTrue(main_page.is_title_matches(), "python.org title doesn't match.") #Sets the text of search textbox to "pycon" main_page.search_text_element = "pycon" main_page.click_go_button() search_results_page = page.SearchResultsPage(self.driver) #Verifies that the results page is not empty - assert search_results_page.is_results_found(), "No results found." + self.assertTrue(search_results_page.is_results_found(), "No results found.") def tearDown(self): self.driver.close() @@ -61,9 +62,9 @@ some results are found. Page object classes ~~~~~~~~~~~~~~~~~~~ -The page object pattern intends creating an object for each web page. By -following this technique a layer of separation between the test code and -technical implementation is created. +The page object pattern intends to create an object for each part of a web page. +This technique helps build a separation between the test code and the actual +code that interacts with the web page. The ``page.py`` will look like this:: @@ -78,7 +79,8 @@ The ``page.py`` will look like this:: class BasePage(object): - """Base class to initialize the base page that will be called from all pages""" + """Base class to initialize the base page that will be called from all + pages""" def __init__(self, driver): self.driver = driver @@ -92,10 +94,12 @@ The ``page.py`` will look like this:: def is_title_matches(self): """Verifies that the hardcoded text "Python" appears in page title""" + return "Python" in self.driver.title def click_go_button(self): """Triggers the search""" + element = self.driver.find_element(*MainPageLocators.GO_BUTTON) element.click() @@ -114,6 +118,7 @@ Page elements The ``element.py`` will look like this:: + from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait @@ -122,18 +127,20 @@ The ``element.py`` will look like this:: def __set__(self, obj, value): """Sets the text to the value supplied""" + driver = obj.driver WebDriverWait(driver, 100).until( - lambda driver: driver.find_element_by_name(self.locator)) - driver.find_element_by_name(self.locator).clear() - driver.find_element_by_name(self.locator).send_keys(value) + lambda driver: driver.find_element(By.NAME, self.locator)) + driver.find_element(By.NAME, self.locator).clear() + driver.find_element(By.NAME, self.locator).send_keys(value) def __get__(self, obj, owner): """Gets the text of the specified object""" + driver = obj.driver WebDriverWait(driver, 100).until( - lambda driver: driver.find_element_by_name(self.locator)) - element = driver.find_element_by_name(self.locator) + lambda driver: driver.find_element(By.NAME, self.locator)) + element = driver.find_element(By.NAME, self.locator) return element.get_attribute("value") @@ -141,8 +148,8 @@ Locators ~~~~~~~~ One of the practices is to separate the locator strings from the place where -they are being used. In this example, locators of the same page belong to same -class. +they are getting used. In this example, locators of the same page belong to the +same class. The ``locators.py`` will look like this:: @@ -150,8 +157,11 @@ The ``locators.py`` will look like this:: class MainPageLocators(object): """A class for main page locators. All main page locators should come here""" + GO_BUTTON = (By.ID, 'submit') class SearchResultsPageLocators(object): - """A class for search results locators. All search results locators should come here""" + """A class for search results locators. All search results locators should + come here""" + pass diff --git a/source/waits.rst b/source/waits.rst index fc00b1e..1e0c50f 100644 --- a/source/waits.rst +++ b/source/waits.rst @@ -31,7 +31,7 @@ is one way this can be accomplished. from selenium import webdriver from selenium.webdriver.common.by import By - from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Firefox()