`_
13 |
14 | The only way to trigger Travis CI to run again for a pull request, is to submit
15 | another change to the pull branch.
16 |
17 | Running Tests
18 | -------------
19 |
20 | You will need `Tox `_ installed to run the tests
21 | against the supported Python versions.
22 |
23 | .. code-block:: bash
24 |
25 | $ pip install tox
26 | $ tox
27 |
--------------------------------------------------------------------------------
/docs/examples/nested_regions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Nested Regions Example
5 | Main Page
6 |
7 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/docs/examples/nested_regions.py:
--------------------------------------------------------------------------------
1 | from pypom import Region, Page
2 | from selenium.webdriver.common.by import By
3 |
4 |
5 | class MainPage(Page):
6 | @property
7 | def menu1(self):
8 | root = self.find_element(By.ID, "menu1")
9 | return Menu(self, root=root)
10 |
11 | @property
12 | def menu2(self):
13 | root = self.find_element(By.ID, "menu2")
14 | return Menu(self, root=root)
15 |
16 |
17 | class Menu(Region):
18 | @property
19 | def entries(self):
20 | return [
21 | Entry(self.page, item) for item in self.find_elements(*Entry.entry_locator)
22 | ]
23 |
24 |
25 | class Entry(Region):
26 | entry_locator = (By.CLASS_NAME, "entry")
27 |
28 | @property
29 | def name(self):
30 | return self.root.text
31 |
--------------------------------------------------------------------------------
/docs/examples/repeated_regions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Repeated Regions Example
5 |
6 |
7 | -
8 | Result 1
9 | detail
10 |
11 | -
12 | Result 2
13 | detail
14 |
15 | -
16 | Result 3
17 | detail
18 |
19 | -
20 | Result 4
21 | detail
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/docs/examples/repeated_regions.py:
--------------------------------------------------------------------------------
1 | from pypom import Page, Region
2 | from selenium.webdriver.common.by import By
3 |
4 |
5 | class Results(Page):
6 | _result_locator = (By.CLASS_NAME, "result")
7 |
8 | @property
9 | def results(self):
10 | return [
11 | self.Result(self, el) for el in self.find_elements(*self._result_locator)
12 | ]
13 |
14 | class Result(Region):
15 | _name_locator = (By.CLASS_NAME, "name")
16 | _detail_locator = (By.TAG_NAME, "a")
17 |
18 | @property
19 | def name(self):
20 | return self.find_element(*self._name_locator).text
21 |
22 | @property
23 | def detail_link(self):
24 | return self.find_element(*self._detail_locator).get_property("href")
25 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | PyPOM - Python Page Object Model
2 | ================================
3 |
4 | PyPOM, or Python Page Object Model, is a Python library that provides a base
5 | page object model for use with Selenium_ or Splinter_ functional tests.
6 |
7 | It is tested on Python 2.7 and 3.6.
8 |
9 | Contributions are welcome. Feel free to fork_ and contribute!
10 |
11 | .. _Selenium: http://seleniumhq.org/
12 | .. _Splinter: https://github.com/cobrateam/splinter
13 | .. _fork: https://github.com/mozilla/PyPOM
14 |
15 |
16 | .. toctree::
17 | :maxdepth: 2
18 |
19 | installing
20 | user_guide
21 | plugins
22 | api
23 | development
24 | news
25 |
--------------------------------------------------------------------------------
/docs/installing.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | Requirements
5 | ------------
6 |
7 | PyPOM requires Python >= 2.7.
8 |
9 | Install PyPOM
10 | -----------------------
11 |
12 | To install PyPOM using `pip `_:
13 |
14 | .. code-block:: bash
15 |
16 | $ pip install PyPOM
17 |
18 | If you want to use PyPOM with Splinter install the optional
19 | splinter support:
20 |
21 | .. code-block:: bash
22 |
23 | $ pip install PyPOM[splinter]
24 |
25 | To install from source:
26 |
27 | .. code-block:: bash
28 |
29 | $ python setup.py develop
30 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | echo. coverage to run coverage check of the documentation if enabled
41 | goto end
42 | )
43 |
44 | if "%1" == "clean" (
45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
46 | del /q /s %BUILDDIR%\*
47 | goto end
48 | )
49 |
50 |
51 | REM Check if sphinx-build is available and fallback to Python version if any
52 | %SPHINXBUILD% 1>NUL 2>NUL
53 | if errorlevel 9009 goto sphinx_python
54 | goto sphinx_ok
55 |
56 | :sphinx_python
57 |
58 | set SPHINXBUILD=python -m sphinx.__init__
59 | %SPHINXBUILD% 2> nul
60 | if errorlevel 9009 (
61 | echo.
62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
63 | echo.installed, then set the SPHINXBUILD environment variable to point
64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
65 | echo.may add the Sphinx directory to PATH.
66 | echo.
67 | echo.If you don't have Sphinx installed, grab it from
68 | echo.http://sphinx-doc.org/
69 | exit /b 1
70 | )
71 |
72 | :sphinx_ok
73 |
74 |
75 | if "%1" == "html" (
76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
77 | if errorlevel 1 exit /b 1
78 | echo.
79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
80 | goto end
81 | )
82 |
83 | if "%1" == "dirhtml" (
84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
85 | if errorlevel 1 exit /b 1
86 | echo.
87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
88 | goto end
89 | )
90 |
91 | if "%1" == "singlehtml" (
92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
93 | if errorlevel 1 exit /b 1
94 | echo.
95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
96 | goto end
97 | )
98 |
99 | if "%1" == "pickle" (
100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
101 | if errorlevel 1 exit /b 1
102 | echo.
103 | echo.Build finished; now you can process the pickle files.
104 | goto end
105 | )
106 |
107 | if "%1" == "json" (
108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
109 | if errorlevel 1 exit /b 1
110 | echo.
111 | echo.Build finished; now you can process the JSON files.
112 | goto end
113 | )
114 |
115 | if "%1" == "htmlhelp" (
116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
117 | if errorlevel 1 exit /b 1
118 | echo.
119 | echo.Build finished; now you can run HTML Help Workshop with the ^
120 | .hhp project file in %BUILDDIR%/htmlhelp.
121 | goto end
122 | )
123 |
124 | if "%1" == "qthelp" (
125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
129 | .qhcp project file in %BUILDDIR%/qthelp, like this:
130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyPOM.qhcp
131 | echo.To view the help file:
132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyPOM.ghc
133 | goto end
134 | )
135 |
136 | if "%1" == "devhelp" (
137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
138 | if errorlevel 1 exit /b 1
139 | echo.
140 | echo.Build finished.
141 | goto end
142 | )
143 |
144 | if "%1" == "epub" (
145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
146 | if errorlevel 1 exit /b 1
147 | echo.
148 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
149 | goto end
150 | )
151 |
152 | if "%1" == "latex" (
153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
154 | if errorlevel 1 exit /b 1
155 | echo.
156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
157 | goto end
158 | )
159 |
160 | if "%1" == "latexpdf" (
161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
162 | cd %BUILDDIR%/latex
163 | make all-pdf
164 | cd %~dp0
165 | echo.
166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdfja" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf-ja
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "text" (
181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
182 | if errorlevel 1 exit /b 1
183 | echo.
184 | echo.Build finished. The text files are in %BUILDDIR%/text.
185 | goto end
186 | )
187 |
188 | if "%1" == "man" (
189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
190 | if errorlevel 1 exit /b 1
191 | echo.
192 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
193 | goto end
194 | )
195 |
196 | if "%1" == "texinfo" (
197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
198 | if errorlevel 1 exit /b 1
199 | echo.
200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
201 | goto end
202 | )
203 |
204 | if "%1" == "gettext" (
205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
206 | if errorlevel 1 exit /b 1
207 | echo.
208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
209 | goto end
210 | )
211 |
212 | if "%1" == "changes" (
213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
214 | if errorlevel 1 exit /b 1
215 | echo.
216 | echo.The overview file is in %BUILDDIR%/changes.
217 | goto end
218 | )
219 |
220 | if "%1" == "linkcheck" (
221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
222 | if errorlevel 1 exit /b 1
223 | echo.
224 | echo.Link check complete; look for any errors in the above output ^
225 | or in %BUILDDIR%/linkcheck/output.txt.
226 | goto end
227 | )
228 |
229 | if "%1" == "doctest" (
230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
231 | if errorlevel 1 exit /b 1
232 | echo.
233 | echo.Testing of doctests in the sources finished, look at the ^
234 | results in %BUILDDIR%/doctest/output.txt.
235 | goto end
236 | )
237 |
238 | if "%1" == "coverage" (
239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
240 | if errorlevel 1 exit /b 1
241 | echo.
242 | echo.Testing of coverage in the sources finished, look at the ^
243 | results in %BUILDDIR%/coverage/python.txt.
244 | goto end
245 | )
246 |
247 | if "%1" == "xml" (
248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
249 | if errorlevel 1 exit /b 1
250 | echo.
251 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
252 | goto end
253 | )
254 |
255 | if "%1" == "pseudoxml" (
256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
257 | if errorlevel 1 exit /b 1
258 | echo.
259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
260 | goto end
261 | )
262 |
263 | :end
264 |
--------------------------------------------------------------------------------
/docs/news.rst:
--------------------------------------------------------------------------------
1 | Release Notes
2 | =============
3 |
4 | .. towncrier release notes start
5 |
6 | 2.2.0 (2018-10-29)
7 | ==================
8 |
9 | Deprecations and Removals
10 | -------------------------
11 |
12 | - Removed PhantomJS support from Splinter driver due to removal in Splinter v0.9.0. (#93)
13 |
14 |
15 | 2.1.0 (2018-08-13)
16 | ==================
17 |
18 | Bugfixes
19 | --------
20 |
21 | - Replace use of ``implprefix`` with ``HookimplMarker`` due to deprecation.
22 |
23 | Existing PyPOM plugins will need to be updated to import the `hookimpl` and use
24 | it to decorate hook implementations rather than rely on the prefix of the
25 | function names.
26 |
27 | Before::
28 |
29 | def pypom_after_wait_for_page_to_load(page):
30 | pass
31 |
32 | After::
33 |
34 | from pypom import hookimpl
35 |
36 | @hookimpl
37 | def pypom_after_wait_for_page_to_load(page):
38 | pass (#90)
39 |
40 |
41 | 2.0.0 (2018-04-17)
42 | ==================
43 |
44 | * Added support for plugins.
45 |
46 | * This introduces plugin hooks ``pypom_after_wait_for_page_to_load`` and
47 | ``pypom_after_wait_for_region_to_load``.
48 | * In order to take advantage of plugin support you must avoid implementing
49 | ``wait_for_page_to_load`` or ``wait_for_region_to_load`` in your page
50 | objects.
51 | * This was previously the only way to implement a custom wait for your pages
52 | and regions, but now means the calls to plugin hooks would be bypassed.
53 | * Custom waits can now be achieved by implementing a ``loaded`` property on
54 | the page or region, which returns ``True`` when the page or region has
55 | finished loading.
56 | * See the user guide for more details.
57 |
58 | * Any unused ``url_kwargs`` after formatting ``URL_TEMPLATE`` are added as URL
59 | query string parameters.
60 |
61 | 1.3.0 (2018-02-28)
62 | ==================
63 |
64 | * Added support for EventFiringWebDriver
65 |
66 | * Thanks to `@Greums `_ for the PR
67 |
68 | 1.2.0 (2017-06-20)
69 | ==================
70 |
71 | * Dropped support for Python 2.6
72 |
73 | 1.1.1 (2016-11-21)
74 | ==================
75 |
76 | * Fixed packaging of ``pypom.interfaces``
77 |
78 | 1.1.0 (2016-11-17)
79 | ==================
80 |
81 | * Added support for Splinter
82 |
83 | * Thanks to `@davidemoro `_ for the PR
84 |
85 | 1.0.0 (2016-05-24)
86 | ==================
87 |
88 | * Official release
89 |
--------------------------------------------------------------------------------
/docs/plugins.rst:
--------------------------------------------------------------------------------
1 | Plugins
2 | =======
3 |
4 | Plugin support was added in v2.0.
5 |
6 | Writing plugins
7 | ---------------
8 |
9 | PyPOM uses `pluggy`_ to enable support for plugins. In order to write a plugin
10 | you can create an installable Python package with a specific entry point. For
11 | example, the following (incomplete) ``setup.py`` will register a plugin named
12 | screenshot::
13 |
14 | from setuptools import setup
15 |
16 | setup(name='PyPOM-screenshot',
17 | description='plugin for PyPOM that takes a lot of screenshots',
18 | packages=['pypom_screenshot'],
19 | install_requires=['PyPOM'],
20 | entry_points={'pypom.plugin': ['screenshot = pypom_screenshot.plugin']})
21 |
22 | Then, in your package implement one or more of the plugin :ref:`hooks` provided
23 | by PyPOM. The following example will take a screenshot whenever a page or
24 | region has finished loading::
25 |
26 |
27 | from pypom import hookimpl
28 |
29 | @hookimpl
30 | def pypom_after_wait_for_page_to_load(page):
31 | page.selenium.get_screenshot_as_file(page.__class__.__name__ + '.png')
32 |
33 |
34 | @hookimpl
35 | def pypom_after_wait_for_region_to_load(region):
36 | region.root.screenshot(region.__class__.__name__ + '.png')
37 |
38 | .. _pluggy: https://pluggy.readthedocs.io/
39 |
--------------------------------------------------------------------------------
/docs/user_guide.rst:
--------------------------------------------------------------------------------
1 | User Guide
2 | ==========
3 |
4 | .. contents:: :depth: 3
5 |
6 | Upgrading to 2.0
7 | ----------------
8 |
9 | Plugin support was introduced in v2.0, and if you're upgrading from an earlier
10 | version you may need to make some changes to take advantage of any plugins.
11 | Before this version, to implement a custom wait for pages/regions to finish
12 | loading it was necessary to implement ``wait_for_page_to_load`` or
13 | ``wait_for_region_to_load``. If you haven't implemented either of these, you
14 | don't need to do anything to upgrade. If you have, then whilst your custom
15 | waits will still work, we now support plugins that can be triggered after a
16 | page/region load, and these calls are made from the base classes. By overriding
17 | the default behaviour, you may be missing out on triggering any plugin
18 | behaviours. Rather than having to remember to always call the same method from
19 | the parent, you can simply change your custom wait to a new ``loaded`` property
20 | that returns ``True`` when the page/region has loaded.
21 |
22 | So, if you have implemented your own
23 | :py:func:`~pypom.page.Page.wait_for_page_to_load` like this::
24 |
25 | def wait_for_page_to_load(self):
26 | self.wait.until(lambda s: self.seed_url in s.current_url)
27 |
28 | You will want to change it to use :py:attr:`~pypom.page.Page.loaded` like this::
29 |
30 | @property
31 | def loaded(self):
32 | return self.seed_url in self.selenium.current_url
33 |
34 | Similarly, if you have implemented your own
35 | :py:func:`~pypom.region.Region.wait_for_region_to_load` like this::
36 |
37 | def wait_for_region_to_load(self):
38 | self.wait.until(lambda s: self.root.is_displayed())
39 |
40 | You will want to change it to use :py:attr:`~pypom.region.Region.loaded` like
41 | this::
42 |
43 | @property
44 | def loaded(self):
45 | return self.root.is_displayed()
46 |
47 | Drivers
48 | -------
49 |
50 | PyPOM requires a driver object to be instantiated, and supports multiple driver
51 | types. The examples in this guide will assume that you have a driver instance.
52 |
53 | Selenium
54 | ~~~~~~~~
55 |
56 | To instantiate a Selenium_ driver you will need a
57 | :py:class:`~selenium.webdriver.remote.webdriver.WebDriver` object::
58 |
59 | from selenium.webdriver import Firefox
60 | driver = Firefox()
61 |
62 | Splinter
63 | ~~~~~~~~
64 |
65 | To instantiate a Splinter_ driver you will need a :py:class:`~splinter.Browser`
66 | object::
67 |
68 | from splinter import Browser
69 | driver = Browser()
70 |
71 | Pages
72 | -----
73 |
74 | Page objects are representations of web pages. They provide functions to allow
75 | simulating user actions, and providing properties that return state from the
76 | page. The :py:class:`~pypom.page.Page` class provided by PyPOM provides a
77 | simple implementation that can be sub-classed to apply to your project.
78 |
79 | The following very simple example instantiates a page object representing the
80 | landing page of the Mozilla website::
81 |
82 | from pypom import Page
83 |
84 | class Mozilla(Page):
85 | pass
86 |
87 | page = Mozilla(driver)
88 |
89 | If a page has a seed URL then you can call the :py:func:`~pypom.page.Page.open`
90 | function to open the page in the browser. There are a number of ways to specify
91 | a seed URL.
92 |
93 | Base URL
94 | ~~~~~~~~
95 |
96 | A base URL can be passed to a page object on instantiation. If no URL template
97 | is provided, then calling :py:func:`~pypom.page.Page.open` will open this base
98 | URL::
99 |
100 | from pypom import Page
101 |
102 | class Mozilla(Page):
103 | pass
104 |
105 | base_url = 'https://www.mozilla.org'
106 | page = Mozilla(driver, base_url).open()
107 |
108 | URL templates
109 | ~~~~~~~~~~~~~
110 |
111 | By setting a value for :py:attr:`~pypom.page.Page.URL_TEMPLATE`, pages can
112 | specify either an absolute URL or one that is relative to the base URL (when
113 | provided). In the following example, the URL https://www.mozilla.org/about/
114 | will be opened::
115 |
116 | from pypom import Page
117 |
118 | class Mozilla(Page):
119 | URL_TEMPLATE = '/about/'
120 |
121 | base_url = 'https://www.mozilla.org'
122 | page = Mozilla(driver, base_url).open()
123 |
124 | As this is a template, any additional keyword arguments passed when
125 | instantiating the page object will attempt to resolve any placeholders. In the
126 | following example, the URL https://www.mozilla.org/de/about/ will be opened::
127 |
128 | from pypom import Page
129 |
130 | class Mozilla(Page):
131 | URL_TEMPLATE = '/{locale}/about/'
132 |
133 | base_url = 'https://www.mozilla.org'
134 | page = Mozilla(driver, base_url, locale='de').open()
135 |
136 | URL parameters
137 | ~~~~~~~~~~~~~~
138 |
139 | Any keyword arguments provided that are not used as placeholders in the URL
140 | template are added as query string parameters. In the following example, the
141 | URL https://developer.mozilla.org/fr/search?q=bold&topic=css will be opened::
142 |
143 | from pypom import Page
144 |
145 | class Search(Page):
146 | URL_TEMPLATE = '/{locale}/search'
147 |
148 | base_url = 'https://developer.mozilla.org/'
149 | page = Search(driver, base_url, locale='fr', q='bold', topic='css').open()
150 |
151 | Waiting for pages to load
152 | ~~~~~~~~~~~~~~~~~~~~~~~~~
153 |
154 | Whenever a driver detects that a page is loading, it does its best to block
155 | until it's complete. Unfortunately, as the driver does not know your
156 | application, it's quite common for it to return earlier than a user would
157 | consider the page to be ready. For this reason, the
158 | :py:attr:`~pypom.page.Page.loaded` property can be overridden and customised
159 | for your project's needs by returning ``True`` when the page has loaded. This
160 | property is polled by :py:func:`~pypom.page.Page.wait_for_page_to_load`, which
161 | is called by :py:func:`~pypom.page.Page.open` after loading the seed URL, and
162 | can be called directly by functions that cause a page to load.
163 |
164 | The following example waits for the seed URL to be in the current URL. You can
165 | use this so long as the URL is not rewritten or redirected by your
166 | application::
167 |
168 | from pypom import Page
169 |
170 | class Mozilla(Page):
171 |
172 | @property
173 | def loaded(self):
174 | return self.seed_url in self.selenium.current_url
175 |
176 | Other things to wait for might include when elements are displayed or enabled,
177 | or when an element has a particular class. This will be very dependent on your
178 | application.
179 |
180 | Regions
181 | -------
182 |
183 | Region objects represent one or more elements of a web page that are repeated
184 | multiple times on a page, or shared between multiple web pages. They prevent
185 | duplication, and can improve the readability and maintainability of your page
186 | objects.
187 |
188 | Root elements
189 | ~~~~~~~~~~~~~
190 |
191 | It's important for page regions to have a root element. This is the element
192 | that any child elements will be located within. This means that page region
193 | locators do not need to be unique on the page, only unique within the context
194 | of the root element.
195 |
196 | If your page region contains a :py:attr:`~pypom.region.Region._root_locator`
197 | attribute, this will be used to locate the root element every time an instance
198 | of the region is created. This is recommended for most page regions as it
199 | avoids issues when the root element becomes stale.
200 |
201 | Alternatively, you can locate the root element yourself and pass it to the
202 | region on construction. This is useful when creating regions that are repeated
203 | on a single page.
204 |
205 | The root element can later be accessed via the
206 | :py:attr:`~pypom.region.Region.root` attribute on the region, which may be
207 | necessary if you need to interact with it.
208 |
209 | Repeating regions
210 | ~~~~~~~~~~~~~~~~~
211 |
212 | Page regions are useful when you have multiple items on a page that share the
213 | same characteristics, such as a list of search results. By creating a page
214 | region, you can interact with any of these items in a common way:
215 |
216 | The following example uses Selenium_ to locate all results on a page and return
217 | a list of ``Result`` regions. This can be used to determine the number of
218 | results, and each result can be accessed from this list for further state or
219 | interactions. Refer to `locating elements`_ for more information on how to
220 | write locators for your driver:
221 |
222 | .. literalinclude:: examples/repeated_regions.html
223 | :language: html
224 | :emphasize-lines: 6-23
225 |
226 | .. literalinclude:: examples/repeated_regions.py
227 | :language: python
228 | :emphasize-lines: 6-8
229 | :lines: 5-24
230 |
231 | Nested regions
232 | ~~~~~~~~~~~~~~
233 |
234 | Regions can be nested inside other regions (i.e. a menu region with multiple entry
235 | regions). In the following example a main page contains two menu regions that
236 | include multiple repeated entry regions:
237 |
238 | .. literalinclude:: examples/nested_regions.html
239 | :language: html
240 |
241 | As a region requires a page object to be passed you need
242 | to pass ``self.page`` when instantiating nested regions:
243 |
244 | .. literalinclude:: examples/nested_regions.py
245 | :language: python
246 | :emphasize-lines: 4-5,9-10,16-18
247 | :lines: 5-30
248 |
249 |
250 | Shared regions
251 | ~~~~~~~~~~~~~~
252 |
253 | Pages with common characteristics can use regions to avoid duplication.
254 | Examples of this include page headers, navigation menus, login forms, and
255 | footers. These regions can either be defined in a base page object that is
256 | inherited by the pages that contain the region, or they can exist in their own
257 | module:
258 |
259 | In the following example, any page objects that extend ``Base`` will inherit
260 | the ``header`` property, and be able to check if it's displayed. Refer to
261 | `locating elements`_ for more information on how to write locators for your
262 | driver::
263 |
264 | from pypom import Page, Region
265 | from selenium.webdriver.common.by import By
266 |
267 | class Base(Page):
268 |
269 | @property
270 | def header(self):
271 | return self.Header(self)
272 |
273 | class Header(Region):
274 | _root_locator = (By.ID, 'header')
275 |
276 | def is_displayed(self):
277 | return self.root.is_displayed()
278 |
279 | Waiting for regions to load
280 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
281 |
282 | The :py:attr:`~pypom.region.Region.loaded` property function can be
283 | overridden and customised for your project's needs by returning ``True`` when
284 | the region has loaded to ensure it's ready for interaction. This property is
285 | polled by :py:attr:`~pypom.region.Region.wait_for_region_to_load`, which is
286 | called whenever a region is instantiated, and can be called directly by
287 | functions that a region to reload.
288 |
289 | The following example waits for an element within a page region to be
290 | displayed::
291 |
292 | from pypom import Region
293 |
294 | class Header(Region):
295 |
296 | @property
297 | def loaded(self):
298 | return self.root.is_displayed()
299 |
300 | Other things to wait for might include when elements are displayed or enabled,
301 | or when an element has a particular class. This will be very dependent on your
302 | application.
303 |
304 | Locating elements
305 | -----------------
306 |
307 | Each driver has its own approach to locating elements. A suggested approach is
308 | to store your locators at the top of your page/region classes. Ideally these
309 | should be preceeded with a single underscore to indicate that they're primarily
310 | reserved for internal use. These attributes can be stored as a two item tuple
311 | containing both the strategy and locator, and can then be unpacked when passed
312 | to a method that requires the arguments to be separated.
313 |
314 | Selenium
315 | ~~~~~~~~
316 |
317 | The :py:class:`~selenium.webdriver.common.by.By` class covers the common
318 | locator strategies for Selenium_. The following example shows a locator being
319 | defined and used in a page object::
320 |
321 | from pypom import Page
322 | from selenium.webdriver.common.by import By
323 |
324 | class Mozilla(Page):
325 | _logo_locator = (By.ID, 'logo')
326 |
327 | @property
328 | def loaded(self):
329 | logo = self.find_element(*self._logo_locator)
330 | return logo.is_displayed()
331 |
332 | Splinter
333 | ~~~~~~~~
334 |
335 | The available locator strategies for Splinter_ are:
336 |
337 | * name
338 | * id
339 | * css
340 | * xpath
341 | * text
342 | * value
343 | * tag
344 |
345 | The following example shows a locator being defined and used in a page object::
346 |
347 | from pypom import Page
348 | from selenium.webdriver.common.by import By
349 |
350 | class Mozilla(Page):
351 | _logo_locator = ('id', 'logo')
352 |
353 | @property
354 | def loaded(self):
355 | logo = self.find_element(*self._logo_locator)
356 | return logo.is_displayed()
357 |
358 | Explicit waits
359 | --------------
360 |
361 | For convenience, a :py:class:`~selenium.webdriver.support.wait.WebDriverWait`
362 | object is instantiated with an optional timeout (with a default of 10 seconds)
363 | for every page. This allows your page objects to define an explicit wait
364 | whenever an interaction causes a reponse that a real user would wait for before
365 | continuing. For example, checking a box might make a button become enabled. If
366 | we didn't wait for the button to become enabled we may try clicking on it too
367 | early, and nothing would happen. Another example of where explicit waits are
368 | common is when `waiting for pages to load`_ or `waiting for regions to load`_.
369 |
370 | The following example uses Selenium_ to demonstrate a wait that is necessary
371 | after checking a box that causes a button to become enabled. Refer to
372 | `locating elements`_ for more information on how to write locators for your
373 | driver::
374 |
375 | from pypom import Page
376 | from selenium.webdriver.common.by import By
377 |
378 | class Mozilla(Page):
379 | _privacy_policy_locator = (By.ID, 'privacy')
380 | _sign_me_up_locator = (By.ID, 'sign_up')
381 |
382 | def accept_privacy_policy(self):
383 | self.find_element(*self._privacy_policy_locator).click()
384 | sign_me_up = self.find_element(*self._sign_me_up_locator)
385 | self.wait.until(lambda s: sign_me_up.is_enabled())
386 |
387 | You can either specify a timeout by passing the optional ``timeout`` keyword
388 | argument when instantiating a page object, or you can override the
389 | :py:func:`~pypom.page.Page.__init__` method if you want your timeout to be
390 | inherited by a base project page class.
391 |
392 | .. note::
393 |
394 | The default timeout of 10 seconds may be considered excessive, and you may
395 | wish to reduce it. Increasing the timeout is not recommended. If you have
396 | interactions that take longer than the default you may find that you have
397 | a performance issue that will considerably affect the user experience.
398 |
399 | .. _Selenium: http://docs.seleniumhq.org/
400 | .. _Splinter: https://github.com/cobrateam/splinter
401 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.towncrier]
2 | package = "pypom"
3 | package_dir = "src"
4 | filename = "docs/news.rst"
5 | title_format = "{version} ({project_date})"
6 |
--------------------------------------------------------------------------------
/requirements/pipenv.txt:
--------------------------------------------------------------------------------
1 | pipenv==2018.10.13
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
3 |
4 | [coverage:run]
5 | source = src/pypom
6 |
7 | [flake8]
8 | exclude = .eggs,.tox,docs
9 | ignore=E501
10 |
11 | [isort]
12 | default_section = THIRDPARTY
13 | known_first_party = pypom
14 | skip = .tox, build, docs
15 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | splinter_requires = ["splinter"]
4 |
5 | setup(
6 | name="PyPOM",
7 | use_scm_version=True,
8 | description="python page object model for selenium",
9 | long_description=open("README.rst").read(),
10 | author="Dave Hunt",
11 | author_email="dhunt@mozilla.com",
12 | url="https://github.com/mozilla/PyPOM",
13 | package_dir={"": "src"},
14 | packages=["pypom", "pypom.interfaces"],
15 | install_requires=["zope.interface", "zope.component", "pluggy", "selenium"],
16 | setup_requires=["setuptools_scm"],
17 | extras_require={"splinter": splinter_requires},
18 | license="Mozilla Public License 2.0 (MPL 2.0)",
19 | keywords="pypom page object model selenium",
20 | classifiers=[
21 | "Development Status :: 5 - Production/Stable",
22 | "Intended Audience :: Developers",
23 | "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
24 | "Operating System :: POSIX",
25 | "Operating System :: Microsoft :: Windows",
26 | "Operating System :: MacOS :: MacOS X",
27 | "Topic :: Software Development :: Quality Assurance",
28 | "Topic :: Software Development :: Testing",
29 | "Topic :: Utilities",
30 | "Programming Language :: Python",
31 | "Programming Language :: Python :: 2.7",
32 | "Programming Language :: Python :: 3.6",
33 | ],
34 | )
35 |
--------------------------------------------------------------------------------
/src/pypom/__init__.py:
--------------------------------------------------------------------------------
1 | from .page import Page # noqa
2 | from .region import Region # noqa
3 |
4 | import pluggy
5 | import selenium # noqa
6 |
7 | # register selenium support
8 | from .selenium_driver import register as registerSelenium
9 |
10 | registerSelenium()
11 |
12 | try:
13 | import splinter # noqa
14 | except ImportError: # pragma: no cover
15 | pass # pragma: no cover
16 | else:
17 | from .splinter_driver import register as registerSplinter
18 |
19 | registerSplinter()
20 |
21 | hookimpl = pluggy.HookimplMarker("pypom")
22 |
--------------------------------------------------------------------------------
/src/pypom/driver.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from zope import component
6 | from zope.interface import classImplements
7 |
8 | from .interfaces import IDriver
9 |
10 |
11 | def registerDriver(iface, driver, class_implements=[]):
12 | """ Register driver adapter used by page object"""
13 | for class_item in class_implements:
14 | classImplements(class_item, iface)
15 |
16 | component.provideAdapter(factory=driver, adapts=[iface], provides=IDriver)
17 |
--------------------------------------------------------------------------------
/src/pypom/exception.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 |
6 | class UsageError(Exception):
7 | """PyPOM usage error."""
8 |
9 | pass
10 |
--------------------------------------------------------------------------------
/src/pypom/hooks.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from pluggy import HookspecMarker
6 |
7 | hookspec = HookspecMarker("pypom")
8 |
9 |
10 | @hookspec
11 | def pypom_after_wait_for_page_to_load(page):
12 | """Called after waiting for the page to load"""
13 |
14 |
15 | @hookspec
16 | def pypom_after_wait_for_region_to_load(region):
17 | """Called after waiting for the region to load"""
18 |
--------------------------------------------------------------------------------
/src/pypom/interfaces/__init__.py:
--------------------------------------------------------------------------------
1 | from .driver import IDriver # noqa
2 |
--------------------------------------------------------------------------------
/src/pypom/interfaces/driver.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from zope.interface import Interface
6 |
7 |
8 | class ISplinter(Interface):
9 | """ Marker interface for Splinter"""
10 |
11 |
12 | class IDriver(Interface):
13 | """ Driver interface """
14 |
15 | def wait_factory(timeout):
16 | """Returns a WebDriverWait like property for a given timeout.
17 |
18 | :param timeout: Timeout used by WebDriverWait like calls
19 | :type timeout: int
20 | """
21 |
22 | def open(url):
23 | """Open the page.
24 | Navigates to :py:attr:`url`
25 | """
26 |
27 | def find_element(strategy, locator, root=None):
28 | """Finds an element on the page.
29 |
30 | :param strategy: Location strategy to use (type depends on the driver implementation)
31 | :param locator: Location of target element.
32 | :param root: (optional) root node.
33 | :type strategy: str
34 | :type locator: str
35 | :type root: web element object or None.
36 | :return: web element object
37 | :rtype: it depends on the driver implementation
38 | """
39 |
40 | def find_elements(strategy, locator, root=None):
41 | """Finds elements on the page.
42 |
43 | :param strategy: Location strategy to use (type depends on the driver implementation)
44 | :param locator: Location of target elements.
45 | :param root: (optional) root node.
46 | :type strategy: str
47 | :type locator: str
48 | :type root: web element object or None.
49 | :return: iterable of web element objects
50 | :rtype: iterable (if depends on the driver implementation)
51 | """
52 |
53 | def is_element_present(strategy, locator, root=None):
54 | """Checks whether an element is present.
55 |
56 | :param strategy: Location strategy to use (type depends on the driver implementation)
57 | :param locator: Location of target element.
58 | :param root: (optional) root node.
59 | :type strategy: str
60 | :type locator: str
61 | :type root: web element object or None.
62 | :return: ``True`` if element is present, else ``False``.
63 | :rtype: bool
64 | """
65 |
66 | def is_element_displayed(strategy, locator, root=None):
67 | """Checks whether an element is displayed.
68 |
69 | :param strategy: Location strategy to use (type depends on the driver implementation)
70 | :param locator: Location of target element.
71 | :param root: (optional) root node.
72 | :type strategy: str
73 | :type locator: str
74 | :type root: web element object or None.
75 | :return: ``True`` if element is displayed, else ``False``.
76 | :rtype: bool
77 | """
78 |
--------------------------------------------------------------------------------
/src/pypom/newsfragments/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 |
--------------------------------------------------------------------------------
/src/pypom/page.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import collections
6 | import sys
7 |
8 | from .exception import UsageError
9 | from .view import WebView
10 |
11 | if sys.version_info >= (3,):
12 | import urllib.parse as urlparse
13 | from urllib.parse import urlencode
14 | else:
15 | import urlparse
16 | from urllib import urlencode
17 |
18 |
19 | def iterable(arg):
20 | if isinstance(arg, collections.Iterable) and not isinstance(arg, str):
21 | return arg
22 | return [arg]
23 |
24 |
25 | class Page(WebView):
26 | """A page object.
27 |
28 | Used as a base class for your project's page objects.
29 |
30 | :param driver: A driver.
31 | :param base_url: (optional) Base URL.
32 | :param timeout: (optional) Timeout used for explicit waits. Defaults to ``10``.
33 | :param url_kwargs: (optional) Keyword arguments used when generating the :py:attr:`seed_url`.
34 | :type driver: :py:class:`~selenium.webdriver.remote.webdriver.WebDriver` or :py:class:`~splinter.browser.Browser`
35 | :type base_url: str
36 | :type timeout: int
37 |
38 | Usage (Selenium)::
39 |
40 | from pypom import Page
41 | from selenium.webdriver import Firefox
42 |
43 | class Mozilla(Page):
44 | URL_TEMPLATE = 'https://www.mozilla.org/{locale}'
45 |
46 | driver = Firefox()
47 | page = Mozilla(driver, locale='en-US')
48 | page.open()
49 |
50 | Usage (Splinter)::
51 |
52 | from pypom import Page
53 | from splinter import Browser
54 |
55 | class Mozilla(Page):
56 | URL_TEMPLATE = 'https://www.mozilla.org/{locale}'
57 |
58 | driver = Browser()
59 | page = Mozilla(driver, locale='en-US')
60 | page.open()
61 |
62 | """
63 |
64 | URL_TEMPLATE = None
65 | """Template string representing a URL that can be used to open the page.
66 |
67 | This string is formatted and can contain names of keyword arguments passed
68 | during construction of the page object. The template should either assume
69 | that its result will be appended to the value of :py:attr:`base_url`, or
70 | should yield an absolute URL.
71 |
72 | Examples::
73 |
74 | URL_TEMPLATE = 'https://www.mozilla.org/' # absolute URL
75 | URL_TEMPLATE = '/search' # relative to base URL
76 | URL_TEMPLATE = '/search?q={term}' # keyword argument expansion
77 |
78 | """
79 |
80 | def __init__(self, driver, base_url=None, timeout=10, **url_kwargs):
81 | super(Page, self).__init__(driver, timeout)
82 | self.base_url = base_url
83 | self.url_kwargs = url_kwargs
84 |
85 | @property
86 | def seed_url(self):
87 | """A URL that can be used to open the page.
88 |
89 | The URL is formatted from :py:attr:`URL_TEMPLATE`, which is then
90 | appended to :py:attr:`base_url` unless the template results in an
91 | absolute URL.
92 |
93 | :return: URL that can be used to open the page.
94 | :rtype: str
95 |
96 | """
97 | url = self.base_url
98 | if self.URL_TEMPLATE is not None:
99 | url = urlparse.urljoin(
100 | self.base_url, self.URL_TEMPLATE.format(**self.url_kwargs)
101 | )
102 |
103 | if not url:
104 | return None
105 |
106 | url_parts = list(urlparse.urlparse(url))
107 | query = urlparse.parse_qsl(url_parts[4])
108 |
109 | for k, v in self.url_kwargs.items():
110 | if v is None:
111 | continue
112 | if "{{{}}}".format(k) not in str(self.URL_TEMPLATE):
113 | for i in iterable(v):
114 | query.append((k, i))
115 |
116 | url_parts[4] = urlencode(query)
117 | return urlparse.urlunparse(url_parts)
118 |
119 | def open(self):
120 | """Open the page.
121 |
122 | Navigates to :py:attr:`seed_url` and calls :py:func:`wait_for_page_to_load`.
123 |
124 | :return: The current page object.
125 | :rtype: :py:class:`Page`
126 | :raises: UsageError
127 |
128 | """
129 | if self.seed_url:
130 | self.driver_adapter.open(self.seed_url)
131 | self.wait_for_page_to_load()
132 | return self
133 | raise UsageError("Set a base URL or URL_TEMPLATE to open this page.")
134 |
135 | def wait_for_page_to_load(self):
136 | """Wait for the page to load."""
137 | self.wait.until(lambda _: self.loaded)
138 | self.pm.hook.pypom_after_wait_for_page_to_load(page=self)
139 | return self
140 |
141 | @property
142 | def loaded(self):
143 | """Loaded state of the page.
144 |
145 | By default the driver will try to wait for any page loads to be
146 | complete, however it's not uncommon for it to return early. To address
147 | this you can override :py:attr:`loaded` to return ``True`` when the
148 | page has finished loading.
149 |
150 | :return: ``True`` if page is loaded, else ``False``.
151 | :rtype: bool
152 |
153 | Usage (Selenium)::
154 |
155 | from pypom import Page
156 | from selenium.webdriver.common.by import By
157 |
158 | class Mozilla(Page):
159 |
160 | @property
161 | def loaded(self):
162 | body = self.find_element(By.TAG_NAME, 'body')
163 | return 'loaded' in body.get_attribute('class')
164 |
165 | Usage (Splinter)::
166 |
167 | from pypom import Page
168 |
169 | class Mozilla(Page):
170 |
171 | def loaded(self):
172 | body = self.find_element('tag', 'body')
173 | return 'loaded' in body['class']
174 |
175 | Examples::
176 |
177 | # wait for the seed_url value to be in the current URL
178 | self.seed_url in self.selenium.current_url
179 |
180 | """
181 | return True
182 |
--------------------------------------------------------------------------------
/src/pypom/region.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from .view import WebView
6 |
7 |
8 | class Region(WebView):
9 | """A page region object.
10 |
11 | Used as a base class for your project's page region objects.
12 |
13 | :param page: Page object this region appears in.
14 | :param root: (optional) element that serves as the root for the region.
15 | :type page: :py:class:`~.page.Page`
16 | :type root: :py:class:`~selenium.webdriver.remote.webelement.WebElement` or :py:class:`~splinter.driver.webdriver.WebDriverElement`
17 |
18 | Usage (Selenium)::
19 |
20 | from pypom import Page, Region
21 | from selenium.webdriver import Firefox
22 | from selenium.webdriver.common.by import By
23 |
24 | class Mozilla(Page):
25 | URL_TEMPLATE = 'https://www.mozilla.org/'
26 |
27 | @property
28 | def newsletter(self):
29 | return Newsletter(self)
30 |
31 | class Newsletter(Region):
32 | _root_locator = (By.ID, 'newsletter-form')
33 | _submit_locator = (By.ID, 'footer_email_submit')
34 |
35 | def sign_up(self):
36 | self.find_element(*self._submit_locator).click()
37 |
38 | driver = Firefox()
39 | page = Mozilla(driver).open()
40 | page.newsletter.sign_up()
41 |
42 | Usage (Splinter)::
43 |
44 | from pypom import Page, Region
45 | from splinter import Browser
46 |
47 | class Mozilla(Page):
48 | URL_TEMPLATE = 'https://www.mozilla.org/'
49 |
50 | @property
51 | def newsletter(self):
52 | return Newsletter(self)
53 |
54 | class Newsletter(Region):
55 | _root_locator = ('id', 'newsletter-form')
56 | _submit_locator = ('id', 'footer_email_submit')
57 |
58 | def sign_up(self):
59 | self.find_element(*self._submit_locator).click()
60 |
61 | driver = Browser()
62 | page = Mozilla(driver).open()
63 | page.newsletter.sign_up()
64 |
65 | """
66 |
67 | _root_locator = None
68 |
69 | def __init__(self, page, root=None):
70 | super(Region, self).__init__(page.driver, page.timeout, pm=page.pm)
71 | self._root = root
72 | self.page = page
73 | self.wait_for_region_to_load()
74 |
75 | @property
76 | def root(self):
77 | """Root element for the page region.
78 |
79 | Page regions should define a root element either by passing this on
80 | instantiation or by defining a :py:attr:`_root_locator` attribute. To
81 | reduce the chances of hitting :py:class:`~selenium.common.exceptions.StaleElementReferenceException`
82 | or similar you should use :py:attr:`_root_locator`, as this is looked up every
83 | time the :py:attr:`root` property is accessed.
84 | """
85 | if self._root is None and self._root_locator is not None:
86 | return self.page.find_element(*self._root_locator)
87 | return self._root
88 |
89 | def wait_for_region_to_load(self):
90 | """Wait for the page region to load."""
91 | self.wait.until(lambda _: self.loaded)
92 | self.pm.hook.pypom_after_wait_for_region_to_load(region=self)
93 | return self
94 |
95 | def find_element(self, strategy, locator):
96 | """Finds an element on the page.
97 |
98 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
99 | :param locator: Location of target element.
100 | :type strategy: str
101 | :type locator: str
102 | :return: An element.
103 | :rytpe: :py:class:`~selenium.webdriver.remote.webelement.WebElement` or :py:class:`~splinter.driver.webdriver.WebDriverElement`
104 |
105 | """
106 | return self.driver_adapter.find_element(strategy, locator, root=self.root)
107 |
108 | def find_elements(self, strategy, locator):
109 | """Finds elements on the page.
110 |
111 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
112 | :param locator: Location of target elements.
113 | :type strategy: str
114 | :type locator: str
115 | :return: List of :py:class:`~selenium.webdriver.remote.webelement.WebElement` or :py:class:`~splinter.element_list.ElementList`
116 | :rtype: list
117 |
118 | """
119 | return self.driver_adapter.find_elements(strategy, locator, root=self.root)
120 |
121 | def is_element_present(self, strategy, locator):
122 | """Checks whether an element is present.
123 |
124 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
125 | :param locator: Location of target element.
126 | :type strategy: str
127 | :type locator: str
128 | :return: ``True`` if element is present, else ``False``.
129 | :rtype: bool
130 |
131 | """
132 | return self.driver_adapter.is_element_present(strategy, locator, root=self.root)
133 |
134 | def is_element_displayed(self, strategy, locator):
135 | """Checks whether an element is displayed.
136 |
137 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
138 | :param locator: Location of target element.
139 | :type strategy: str
140 | :type locator: str
141 | :return: ``True`` if element is displayed, else ``False``.
142 | :rtype: bool
143 |
144 | """
145 | return self.driver_adapter.is_element_displayed(
146 | strategy, locator, root=self.root
147 | )
148 |
149 | @property
150 | def loaded(self):
151 | """Loaded state of the page region.
152 |
153 | You may need to initialise your page region before it's ready for you
154 | to interact with it. If this is the case, you can override
155 | :py:attr:`loaded` to return ``True`` when the region has finished
156 | loading.
157 |
158 | :return: ``True`` if page is loaded, else ``False``.
159 | :rtype: bool
160 |
161 | Usage (Selenium)::
162 |
163 | from pypom import Page, Region
164 | from selenium.webdriver.common.by import By
165 |
166 | class Mozilla(Page):
167 | URL_TEMPLATE = 'https://www.mozilla.org/'
168 |
169 | @property
170 | def newsletter(self):
171 | return Newsletter(self)
172 |
173 | class Newsletter(Region):
174 | _root_locator = (By.ID, 'newsletter-form')
175 |
176 | @property
177 | def loaded(self):
178 | return 'loaded' in self.root.get_attribute('class')
179 |
180 | Usage (Splinter)::
181 |
182 | from pypom import Page, Region
183 |
184 | class Mozilla(Page):
185 | URL_TEMPLATE = 'https://www.mozilla.org/'
186 |
187 | @property
188 | def newsletter(self):
189 | return Newsletter(self)
190 |
191 | class Newsletter(Region):
192 | _root_locator = ('id', 'newsletter-form')
193 |
194 | @property
195 | def loaded(self):
196 | return 'loaded' in self.root['class']
197 |
198 | """
199 | return True
200 |
--------------------------------------------------------------------------------
/src/pypom/selenium_driver.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 |
6 | from selenium.common.exceptions import NoSuchElementException
7 | from selenium.webdriver import (
8 | Android,
9 | BlackBerry,
10 | Chrome,
11 | Edge,
12 | Firefox,
13 | Ie,
14 | Opera,
15 | PhantomJS,
16 | Remote,
17 | Safari,
18 | )
19 | from selenium.webdriver.support.events import EventFiringWebDriver
20 | from selenium.webdriver.support.ui import WebDriverWait
21 | from zope.interface import Interface, implementer
22 |
23 | from .driver import registerDriver
24 | from .interfaces import IDriver
25 |
26 |
27 | class ISelenium(Interface):
28 | """ Marker interface for Selenium"""
29 |
30 |
31 | @implementer(IDriver)
32 | class Selenium(object):
33 | def __init__(self, driver):
34 | self.driver = driver
35 |
36 | def wait_factory(self, timeout):
37 | """Returns a WebDriverWait like property for a given timeout.
38 |
39 | :param timeout: Timeout used by WebDriverWait calls
40 | :type timeout: int
41 | """
42 | return WebDriverWait(self.driver, timeout)
43 |
44 | def open(self, url):
45 | """Open the page.
46 | Navigates to :py:attr:`url`
47 | """
48 | self.driver.get(url)
49 |
50 | def find_element(self, strategy, locator, root=None):
51 | """Finds an element on the page.
52 |
53 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` for valid values.
54 | :param locator: Location of target element.
55 | :param root: (optional) root node.
56 | :type strategy: str
57 | :type locator: str
58 | :type root: str :py:class:`~selenium.webdriver.remote.webelement.WebElement` object or None.
59 | :return: :py:class:`~selenium.webdriver.remote.webelement.WebElement` object.
60 | :rtype: selenium.webdriver.remote.webelement.WebElement
61 |
62 | """
63 | if root is not None:
64 | return root.find_element(strategy, locator)
65 | return self.driver.find_element(strategy, locator)
66 |
67 | def find_elements(self, strategy, locator, root=None):
68 | """Finds elements on the page.
69 |
70 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` for valid values.
71 | :param locator: Location of target elements.
72 | :param root: (optional) root node.
73 | :type strategy: str
74 | :type locator: str
75 | :type root: str :py:class:`~selenium.webdriver.remote.webelement.WebElement` object or None.
76 | :return: List of :py:class:`~selenium.webdriver.remote.webelement.WebElement` objects.
77 | :rtype: list
78 |
79 | """
80 | if root is not None:
81 | return root.find_elements(strategy, locator)
82 | return self.driver.find_elements(strategy, locator)
83 |
84 | def is_element_present(self, strategy, locator, root=None):
85 | """Checks whether an element is present.
86 |
87 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` for valid values.
88 | :param locator: Location of target element.
89 | :param root: (optional) root node.
90 | :type strategy: str
91 | :type locator: str
92 | :type root: str :py:class:`~selenium.webdriver.remote.webelement.WebElement` object or None.
93 | :return: ``True`` if element is present, else ``False``.
94 | :rtype: bool
95 |
96 | """
97 | try:
98 | return self.find_element(strategy, locator, root=root)
99 | except NoSuchElementException:
100 | return False
101 |
102 | def is_element_displayed(self, strategy, locator, root=None):
103 | """Checks whether an element is displayed.
104 |
105 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` for valid values.
106 | :param locator: Location of target element.
107 | :param root: (optional) root node.
108 | :type strategy: str
109 | :type locator: str
110 | :type root: str :py:class:`~selenium.webdriver.remote.webelement.WebElement` object or None.
111 | :return: ``True`` if element is displayed, else ``False``.
112 | :rtype: bool
113 |
114 | """
115 | try:
116 | return self.find_element(strategy, locator, root=root).is_displayed()
117 | except NoSuchElementException:
118 | return False
119 |
120 |
121 | def register():
122 | """ Register the Selenium specific driver implementation.
123 |
124 | This register call is performed by the init module if
125 | selenium is available.
126 | """
127 | registerDriver(
128 | ISelenium,
129 | Selenium,
130 | class_implements=[
131 | Firefox,
132 | Chrome,
133 | Ie,
134 | Edge,
135 | Opera,
136 | Safari,
137 | BlackBerry,
138 | PhantomJS,
139 | Android,
140 | Remote,
141 | EventFiringWebDriver,
142 | ],
143 | )
144 |
--------------------------------------------------------------------------------
/src/pypom/splinter_driver.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 |
6 | from splinter.driver.webdriver.chrome import WebDriver as ChromeWebDriver
7 | from splinter.driver.webdriver.firefox import WebDriver as FirefoxWebDriver
8 | from splinter.driver.webdriver.remote import WebDriver as RemoteWebDriver
9 | from zope.interface import Interface, implementer
10 |
11 | from .driver import registerDriver
12 | from .exception import UsageError
13 | from .interfaces import IDriver
14 | from .selenium_driver import Selenium
15 |
16 | ALLOWED_STRATEGIES = ["name", "id", "css", "xpath", "text", "value", "tag"]
17 |
18 |
19 | class ISplinter(Interface):
20 | """ Marker interface for Splinter"""
21 |
22 |
23 | @implementer(IDriver)
24 | class Splinter(Selenium):
25 | def __init__(self, driver):
26 | self.driver = driver
27 |
28 | def open(self, url):
29 | """Open the page.
30 | Navigates to :py:attr:`url`
31 | """
32 | self.driver.visit(url)
33 |
34 | def find_element(self, strategy, locator, root=None):
35 | """Finds an element on the page.
36 |
37 | :param strategy: Location strategy to use. See pypom.splinter_driver.ALLOWED_STRATEGIES for valid values.
38 | :param locator: Location of target element.
39 | :type strategy: str
40 | :type locator: str
41 | :return: :py:class:`~splinter.driver.webdriver.WebDriverElement`.
42 | :rtype: splinter.driver.webdriver.WebDriverElement
43 |
44 | """
45 | elements = self.find_elements(strategy, locator, root=root)
46 | return elements and elements.first or None
47 |
48 | def find_elements(self, strategy, locator, root=None):
49 | """Finds elements on the page.
50 |
51 | :param strategy: Location strategy to use. See pypom.splinter_driver.ALLOWED_STRATEGIES for valid values.
52 | :param locator: Location of target elements.
53 | :type strategy: str
54 | :type locator: str
55 | :return: List of :py:class:`~splinter.driver.webdriver.WebDriverElement`
56 | :rtype: :py:class:`splinter.element_list.ElementList`
57 |
58 | """
59 | node = root or self.driver
60 |
61 | if strategy in ALLOWED_STRATEGIES:
62 | return getattr(node, "find_by_" + strategy)(locator)
63 | raise UsageError("Strategy not allowed")
64 |
65 | def is_element_present(self, strategy, locator, root=None):
66 | """Checks whether an element is present.
67 |
68 | :param strategy: Location strategy to use. See pypom.splinter_driver.ALLOWED_STRATEGIES for valid values.
69 | :param locator: Location of target element.
70 | :type strategy: str
71 | :type locator: str
72 | :return: ``True`` if element is present, else ``False``.
73 | :rtype: bool
74 |
75 | """
76 | return self.find_element(strategy, locator, root=root) and True or False
77 |
78 | def is_element_displayed(self, strategy, locator, root=None):
79 | """Checks whether an element is displayed.
80 |
81 | :param strategy: Location strategy to use. See pypom.splinter_driver.ALLOWED_STRATEGIES for valid values.
82 | :param locator: Location of target element.
83 | :type strategy: str
84 | :type locator: str
85 | :return: ``True`` if element is displayed, else ``False``.
86 | :rtype: bool
87 |
88 | """
89 |
90 | element = self.find_element(strategy, locator, root=root)
91 | return element and element.visible or False
92 |
93 |
94 | def register():
95 | """ Register the Selenium specific driver implementation.
96 |
97 | This register call is performed by the init module if
98 | selenium is available.
99 | """
100 | registerDriver(
101 | ISplinter,
102 | Splinter,
103 | class_implements=[
104 | FirefoxWebDriver,
105 | ChromeWebDriver,
106 | RemoteWebDriver,
107 | ],
108 | )
109 |
--------------------------------------------------------------------------------
/src/pypom/view.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from warnings import warn
6 |
7 | from pluggy import PluginManager
8 |
9 | from pypom import hooks
10 |
11 | from .interfaces import IDriver
12 |
13 |
14 | class WebView(object):
15 | def __init__(self, driver, timeout, pm=None):
16 | self.driver = driver
17 | self.driver_adapter = IDriver(driver)
18 | self.timeout = timeout
19 | self.pm = pm
20 | if self.pm is None:
21 | self.pm = PluginManager("pypom")
22 | self.pm.add_hookspecs(hooks)
23 | self.pm.load_setuptools_entrypoints("pypom.plugin")
24 | self.pm.check_pending()
25 | self.wait = self.driver_adapter.wait_factory(self.timeout)
26 |
27 | @property
28 | def selenium(self):
29 | """Backwards compatibility attribute"""
30 | warn("use driver instead", DeprecationWarning, stacklevel=2)
31 | return self.driver
32 |
33 | def find_element(self, strategy, locator):
34 | return self.driver_adapter.find_element(strategy, locator)
35 |
36 | def find_elements(self, strategy, locator):
37 | """Finds elements on the page.
38 |
39 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
40 | :param locator: Location of target elements.
41 | :type strategy: str
42 | :type locator: str
43 | :return: List of :py:class:`~selenium.webdriver.remote.webelement.WebElement` or :py:class:`~splinter.element_list.ElementList`
44 | :rtype: list
45 |
46 | """
47 | return self.driver_adapter.find_elements(strategy, locator)
48 |
49 | def is_element_present(self, strategy, locator):
50 | """Checks whether an element is present.
51 |
52 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
53 | :param locator: Location of target element.
54 | :type strategy: str
55 | :type locator: str
56 | :return: ``True`` if element is present, else ``False``.
57 | :rtype: bool
58 |
59 | """
60 | return self.driver_adapter.is_element_present(strategy, locator)
61 |
62 | def is_element_displayed(self, strategy, locator):
63 | """Checks whether an element is displayed.
64 |
65 | :param strategy: Location strategy to use. See :py:class:`~selenium.webdriver.common.by.By` or :py:attr:`~pypom.splinter_driver.ALLOWED_STRATEGIES`.
66 | :param locator: Location of target element.
67 | :type strategy: str
68 | :type locator: str
69 | :return: ``True`` if element is displayed, else ``False``.
70 | :rtype: bool
71 |
72 | """
73 | return self.driver_adapter.is_element_displayed(strategy, locator)
74 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/PyPOM/9cb84df9d27b428b4e7423d1bbe6502e92990154/tests/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import pytest
6 | from mock import Mock
7 | from zope.interface import alsoProvides
8 |
9 | from pypom.selenium_driver import ISelenium
10 | from pypom.splinter_driver import ALLOWED_STRATEGIES, ISplinter
11 |
12 |
13 | @pytest.fixture
14 | def base_url():
15 | return "https://www.mozilla.org/"
16 |
17 |
18 | @pytest.fixture
19 | def element(driver):
20 | element = Mock()
21 | driver.find_element.return_value = element
22 | return element
23 |
24 |
25 | @pytest.fixture
26 | def page(driver, base_url):
27 | from pypom import Page
28 |
29 | return Page(driver, base_url)
30 |
31 |
32 | @pytest.fixture(params=[ISelenium, ISplinter], ids=["selenium", "splinter"])
33 | def driver_interface(request):
34 | return request.param
35 |
36 |
37 | @pytest.fixture
38 | def driver(request, driver_interface):
39 | """ All drivers """
40 | mock = Mock()
41 | alsoProvides(mock, driver_interface)
42 | return mock
43 |
44 |
45 | @pytest.fixture(params=ALLOWED_STRATEGIES)
46 | def splinter_strategy(request):
47 | return request.param
48 |
--------------------------------------------------------------------------------
/tests/selenium_specific/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/PyPOM/9cb84df9d27b428b4e7423d1bbe6502e92990154/tests/selenium_specific/__init__.py
--------------------------------------------------------------------------------
/tests/selenium_specific/conftest.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import pytest
6 | from mock import Mock
7 | from zope.interface import alsoProvides
8 |
9 | from pypom.selenium_driver import ISelenium
10 |
11 |
12 | @pytest.fixture
13 | def element(selenium):
14 | element = Mock()
15 | selenium.find_element.return_value = element
16 | return element
17 |
18 |
19 | @pytest.fixture
20 | def page(selenium, base_url):
21 | from pypom import Page
22 |
23 | return Page(selenium, base_url)
24 |
25 |
26 | @pytest.fixture
27 | def selenium():
28 | """ Selenium driver """
29 | mock = Mock()
30 | alsoProvides(mock, ISelenium)
31 | return mock
32 |
--------------------------------------------------------------------------------
/tests/selenium_specific/test_page.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import random
6 |
7 |
8 | def test_find_element_selenium(page, selenium):
9 | locator = (str(random.random()), str(random.random()))
10 | page.find_element(*locator)
11 | selenium.find_element.assert_called_once_with(*locator)
12 |
13 |
14 | def test_find_elements_selenium(page, selenium):
15 | locator = (str(random.random()), str(random.random()))
16 | page.find_elements(*locator)
17 | selenium.find_elements.assert_called_once_with(*locator)
18 |
19 |
20 | def test_is_element_present_selenium(page, selenium):
21 | locator = (str(random.random()), str(random.random()))
22 | assert page.is_element_present(*locator)
23 | selenium.find_element.assert_called_once_with(*locator)
24 |
25 |
26 | def test_is_element_present_not_present_selenium(page, selenium):
27 | locator = (str(random.random()), str(random.random()))
28 | from selenium.common.exceptions import NoSuchElementException
29 |
30 | selenium.find_element.side_effect = NoSuchElementException()
31 | assert not page.is_element_present(*locator)
32 | selenium.find_element.assert_called_once_with(*locator)
33 |
34 |
35 | def test_is_element_displayed_selenium(page, selenium):
36 | locator = (str(random.random()), str(random.random()))
37 | assert page.is_element_displayed(*locator)
38 | selenium.find_element.assert_called_once_with(*locator)
39 |
40 |
41 | def test_is_element_displayed_not_present_selenium(page, selenium):
42 | locator = (str(random.random()), str(random.random()))
43 | from selenium.common.exceptions import NoSuchElementException
44 |
45 | selenium.find_element.side_effect = NoSuchElementException()
46 | assert not page.is_element_displayed(*locator)
47 | selenium.find_element.assert_called_once_with(*locator)
48 | selenium.find_element.is_displayed.assert_not_called()
49 |
50 |
51 | def test_is_element_displayed_not_displayed_selenium(page, selenium):
52 | locator = (str(random.random()), str(random.random()))
53 | element = selenium.find_element()
54 | element.is_displayed.return_value = False
55 | assert not page.is_element_displayed(*locator)
56 | selenium.find_element.assert_called_with(*locator)
57 | element.is_displayed.assert_called_once_with()
58 |
--------------------------------------------------------------------------------
/tests/selenium_specific/test_region.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import random
6 |
7 | import pytest
8 | from mock import Mock
9 |
10 | from pypom import Region
11 |
12 |
13 | class TestNoRoot:
14 | def test_find_element_selenium(self, page, selenium):
15 | locator = (str(random.random()), str(random.random()))
16 | Region(page).find_element(*locator)
17 | selenium.find_element.assert_called_once_with(*locator)
18 |
19 | def test_find_elements_selenium(self, page, selenium):
20 | locator = (str(random.random()), str(random.random()))
21 | Region(page).find_elements(*locator)
22 | selenium.find_elements.assert_called_once_with(*locator)
23 |
24 | def test_is_element_displayed_selenium(self, page, selenium):
25 | locator = (str(random.random()), str(random.random()))
26 | assert Region(page).is_element_displayed(*locator)
27 | selenium.find_element.assert_called_once_with(*locator)
28 |
29 | def test_is_element_displayed_not_present_selenium(self, page, selenium):
30 | locator = (str(random.random()), str(random.random()))
31 | from selenium.common.exceptions import NoSuchElementException
32 |
33 | selenium.find_element.side_effect = NoSuchElementException()
34 | assert not Region(page).is_element_displayed(*locator)
35 | selenium.find_element.assert_called_once_with(*locator)
36 | selenium.find_element.is_displayed.assert_not_called()
37 |
38 | def test_is_element_displayed_hidden_selenium(self, page, selenium):
39 | locator = (str(random.random()), str(random.random()))
40 | hidden_element = selenium.find_element()
41 | hidden_element.is_displayed.return_value = False
42 | assert not Region(page).is_element_displayed(*locator)
43 | selenium.find_element.assert_called_with(*locator)
44 | hidden_element.is_displayed.assert_called_once_with()
45 |
46 |
47 | class TestRootElement:
48 | def test_find_element_selenium(self, page, selenium):
49 | root_element = Mock()
50 | locator = (str(random.random()), str(random.random()))
51 | Region(page, root=root_element).find_element(*locator)
52 | root_element.find_element.assert_called_once_with(*locator)
53 | selenium.find_element.assert_not_called()
54 |
55 | def test_find_elements_selenium(self, page, selenium):
56 | root_element = Mock()
57 | locator = (str(random.random()), str(random.random()))
58 | Region(page, root=root_element).find_elements(*locator)
59 | root_element.find_elements.assert_called_once_with(*locator)
60 | selenium.find_elements.assert_not_called()
61 |
62 | def test_is_element_present_selenium(self, page, selenium):
63 | root_element = Mock()
64 | locator = (str(random.random()), str(random.random()))
65 | assert Region(page, root=root_element).is_element_present(*locator)
66 | root_element.find_element.assert_called_once_with(*locator)
67 | selenium.find_element.assert_not_called()
68 |
69 | def test_is_element_present_not_preset_selenium(self, page, selenium):
70 | root_element = Mock()
71 | locator = (str(random.random()), str(random.random()))
72 | from selenium.common.exceptions import NoSuchElementException
73 |
74 | root_element.find_element.side_effect = NoSuchElementException()
75 | assert not Region(page, root=root_element).is_element_present(*locator)
76 | root_element.find_element.assert_called_once_with(*locator)
77 | selenium.find_element.assert_not_called()
78 |
79 | def test_is_element_displayed_selenium(self, page, selenium):
80 | root_element = Mock()
81 | locator = (str(random.random()), str(random.random()))
82 | assert Region(page, root=root_element).is_element_displayed(*locator)
83 | root_element.find_element.assert_called_once_with(*locator)
84 | selenium.find_element.assert_not_called()
85 |
86 | def test_is_element_displayed_not_present_selenium(self, page, selenium):
87 | root_element = Mock()
88 | locator = (str(random.random()), str(random.random()))
89 | from selenium.common.exceptions import NoSuchElementException
90 |
91 | root_element.find_element.side_effect = NoSuchElementException()
92 | region = Region(page, root=root_element)
93 | assert not region.is_element_displayed(*locator)
94 | root_element.find_element.assert_called_once_with(*locator)
95 | root_element.find_element.is_displayed.assert_not_called()
96 |
97 | def test_is_element_displayed_hidden_selenium(self, page, selenium):
98 | root_element = Mock()
99 | locator = (str(random.random()), str(random.random()))
100 | hidden_element = root_element.find_element()
101 | hidden_element.is_displayed.return_value = False
102 | region = Region(page, root=root_element)
103 | assert not region.is_element_displayed(*locator)
104 | root_element.find_element.assert_called_with(*locator)
105 | hidden_element.is_displayed.assert_called_once_with()
106 |
107 |
108 | class TestRootLocator:
109 | @pytest.fixture
110 | def region(self, page):
111 | class MyRegion(Region):
112 | _root_locator = (str(random.random()), str(random.random()))
113 |
114 | return MyRegion(page)
115 |
116 | def test_root_selenium(self, element, region, selenium):
117 | assert element == region.root
118 | selenium.find_element.assert_called_once_with(*region._root_locator)
119 |
120 | def test_find_element_selenium(self, element, region, selenium):
121 | locator = (str(random.random()), str(random.random()))
122 | region.find_element(*locator)
123 | selenium.find_element.assert_called_once_with(*region._root_locator)
124 | element.find_element.assert_called_once_with(*locator)
125 |
126 | def test_find_elements_selenium(self, element, region, selenium):
127 | locator = (str(random.random()), str(random.random()))
128 | region.find_elements(*locator)
129 | selenium.find_element.assert_called_once_with(*region._root_locator)
130 | element.find_elements.assert_called_once_with(*locator)
131 |
132 | def test_is_element_present_selenium(self, element, region, selenium):
133 | locator = (str(random.random()), str(random.random()))
134 | assert region.is_element_present(*locator)
135 | selenium.find_element.assert_called_once_with(*region._root_locator)
136 | element.find_element.assert_called_once_with(*locator)
137 |
138 | def test_is_element_present_not_present_selenium(self, element, region, selenium):
139 | locator = (str(random.random()), str(random.random()))
140 | from selenium.common.exceptions import NoSuchElementException
141 |
142 | element.find_element.side_effect = NoSuchElementException()
143 | assert not region.is_element_present(*locator)
144 | selenium.find_element.assert_called_once_with(*region._root_locator)
145 | element.find_element.assert_called_once_with(*locator)
146 |
147 | def test_is_element_displayed_selenium(self, element, region, selenium):
148 | locator = (str(random.random()), str(random.random()))
149 | assert region.is_element_displayed(*locator)
150 | selenium.find_element.assert_called_once_with(*region._root_locator)
151 | element.find_element.assert_called_once_with(*locator)
152 |
153 | def test_is_element_displayed_not_present_selenium(self, element, region, selenium):
154 | locator = (str(random.random()), str(random.random()))
155 | from selenium.common.exceptions import NoSuchElementException
156 |
157 | element.find_element.side_effect = NoSuchElementException()
158 | assert not region.is_element_displayed(*locator)
159 | element.find_element.assert_called_once_with(*locator)
160 | element.find_element.is_displayed.assert_not_called()
161 |
162 | def test_is_element_displayed_hidden_selenium(self, element, region, selenium):
163 | locator = (str(random.random()), str(random.random()))
164 | hidden_element = element.find_element()
165 | hidden_element.is_displayed.return_value = False
166 | assert not region.is_element_displayed(*locator)
167 | element.find_element.assert_called_with(*locator)
168 | hidden_element.is_displayed.assert_called_once_with()
169 |
--------------------------------------------------------------------------------
/tests/splinter_specific/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla/PyPOM/9cb84df9d27b428b4e7423d1bbe6502e92990154/tests/splinter_specific/__init__.py
--------------------------------------------------------------------------------
/tests/splinter_specific/conftest.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import pytest
6 | from mock import Mock
7 | from zope.interface import alsoProvides
8 |
9 | from pypom.splinter_driver import ISplinter
10 |
11 |
12 | @pytest.fixture
13 | def element(splinter):
14 | element = Mock()
15 | splinter.find_element.return_value = element
16 | return element
17 |
18 |
19 | @pytest.fixture
20 | def page(splinter, base_url):
21 | from pypom import Page
22 |
23 | return Page(splinter, base_url)
24 |
25 |
26 | @pytest.fixture
27 | def splinter():
28 | """ Splinter driver """
29 | mock = Mock()
30 | alsoProvides(mock, ISplinter)
31 | return mock
32 |
--------------------------------------------------------------------------------
/tests/splinter_specific/test_page.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import random
6 |
7 |
8 | def test_find_element_splinter(page, splinter, splinter_strategy):
9 | locator = (splinter_strategy, str(random.random()))
10 | page.find_element(*locator)
11 | getattr(
12 | page.driver, "find_by_{0}".format(splinter_strategy)
13 | ).assert_called_once_with(locator[1])
14 |
15 |
16 | def test_find_elements_splinter(page, splinter, splinter_strategy):
17 | locator = (splinter_strategy, str(random.random()))
18 | page.find_elements(*locator)
19 | getattr(
20 | page.driver, "find_by_{0}".format(splinter_strategy)
21 | ).assert_called_once_with(locator[1])
22 |
23 |
24 | def test_is_element_present_splinter(page, splinter, splinter_strategy):
25 | locator = (splinter_strategy, str(random.random()))
26 | from splinter.element_list import ElementList
27 | from mock import Mock
28 |
29 | page.driver.configure_mock(
30 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([Mock()])}
31 | )
32 | assert page.is_element_present(*locator)
33 | getattr(
34 | page.driver, "find_by_{0}".format(splinter_strategy)
35 | ).assert_called_once_with(locator[1])
36 |
37 |
38 | def test_is_element_present_not_present_splinter(page, splinter, splinter_strategy):
39 | locator = (splinter_strategy, str(random.random()))
40 | from splinter.element_list import ElementList
41 |
42 | page.driver.configure_mock(
43 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([])}
44 | )
45 | assert not page.is_element_present(*locator)
46 | getattr(
47 | page.driver, "find_by_{0}".format(splinter_strategy)
48 | ).assert_called_once_with(locator[1])
49 |
50 |
51 | def test_is_element_displayed_splinter(page, splinter, splinter_strategy):
52 | locator = (splinter_strategy, str(random.random()))
53 |
54 | from mock import PropertyMock
55 |
56 | visible_mock = PropertyMock(return_value=True)
57 | page.driver.configure_mock(
58 | **{
59 | "find_by_{0}.return_value.first.visible".format(
60 | splinter_strategy
61 | ): visible_mock
62 | }
63 | )
64 | type(
65 | getattr(page.driver, "find_by_{0}".format(splinter_strategy)).return_value.first
66 | ).visible = visible_mock
67 | assert page.is_element_displayed(*locator)
68 | getattr(
69 | page.driver, "find_by_{0}".format(splinter_strategy)
70 | ).assert_called_once_with(locator[1])
71 | visible_mock.assert_called_with()
72 |
73 |
74 | def test_is_element_displayed_not_present_splinter(page, splinter, splinter_strategy):
75 | locator = (splinter_strategy, str(random.random()))
76 | from splinter.element_list import ElementList
77 |
78 | page.driver.configure_mock(
79 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([])}
80 | )
81 | assert not page.is_element_displayed(*locator)
82 | getattr(
83 | page.driver, "find_by_{0}".format(splinter_strategy)
84 | ).assert_called_once_with(locator[1])
85 |
86 |
87 | def test_is_element_displayed_not_displayed_splinter(page, splinter, splinter_strategy):
88 | locator = (splinter_strategy, str(random.random()))
89 |
90 | from mock import PropertyMock
91 |
92 | visible_mock = PropertyMock(return_value=False)
93 | page.driver.configure_mock(
94 | **{
95 | "find_by_{0}.return_value.first.visible".format(
96 | splinter_strategy
97 | ): visible_mock
98 | }
99 | )
100 | type(
101 | getattr(page.driver, "find_by_{0}".format(splinter_strategy)).return_value.first
102 | ).visible = visible_mock
103 | assert not page.is_element_displayed(*locator)
104 | getattr(
105 | page.driver, "find_by_{0}".format(splinter_strategy)
106 | ).assert_called_once_with(locator[1])
107 | visible_mock.assert_called_with()
108 |
--------------------------------------------------------------------------------
/tests/splinter_specific/test_region.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import random
6 |
7 | import pytest
8 | from mock import MagicMock, Mock, patch
9 |
10 | from pypom import Region
11 |
12 |
13 | class TestNoRootSplinter:
14 | def test_no_root_usage_error(self, page, splinter):
15 | locator = ("not_valid_strategy", str(random.random()))
16 | from pypom.exception import UsageError
17 |
18 | with pytest.raises(UsageError):
19 | Region(page).find_element(*locator)
20 |
21 | def test_find_element_splinter(self, page, splinter, splinter_strategy):
22 | locator = (splinter_strategy, str(random.random()))
23 | from splinter.element_list import ElementList
24 |
25 | page.driver.configure_mock(
26 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([])}
27 | )
28 | Region(page).find_element(*locator)
29 | getattr(
30 | page.driver, "find_by_{0}".format(splinter_strategy)
31 | ).assert_called_once_with(locator[1])
32 |
33 | def test_find_elements_splinter(self, page, splinter, splinter_strategy):
34 | locator = (splinter_strategy, str(random.random()))
35 | from splinter.element_list import ElementList
36 |
37 | page.driver.configure_mock(
38 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([])}
39 | )
40 | Region(page).find_elements(*locator)
41 | getattr(
42 | page.driver, "find_by_{0}".format(splinter_strategy)
43 | ).assert_called_once_with(locator[1])
44 |
45 | def test_is_element_displayed_splinter(self, page, splinter, splinter_strategy):
46 | locator = (splinter_strategy, str(random.random()))
47 |
48 | from mock import PropertyMock
49 |
50 | visible_mock = PropertyMock(return_value=True)
51 | page.driver.configure_mock(
52 | **{
53 | "find_by_{0}.return_value.first.visible".format(
54 | splinter_strategy
55 | ): visible_mock
56 | }
57 | )
58 | type(
59 | getattr(
60 | page.driver, "find_by_{0}".format(splinter_strategy)
61 | ).return_value.first
62 | ).visible = visible_mock
63 | assert Region(page).is_element_displayed(*locator)
64 |
65 | getattr(
66 | page.driver, "find_by_{0}".format(splinter_strategy)
67 | ).assert_called_once_with(locator[1])
68 | visible_mock.assert_called_with()
69 |
70 | def test_is_element_displayed_not_present_splinter(
71 | self, page, splinter, splinter_strategy
72 | ):
73 | locator = (str(random.random()), str(random.random()))
74 | from splinter.element_list import ElementList
75 |
76 | with patch(
77 | "pypom.splinter_driver.Splinter.find_element", new_callable=MagicMock()
78 | ) as mock_find_element:
79 | mock_find_element.return_value = ElementList([])
80 | assert not Region(page).is_element_displayed(*locator)
81 |
82 | def test_is_element_displayed_hidden_splinter(
83 | self, page, splinter, splinter_strategy
84 | ):
85 | locator = (splinter_strategy, str(random.random()))
86 | hidden_element = splinter.find_element()
87 | hidden_element.is_displayed.return_value = False
88 | region = Region(page)
89 | with patch(
90 | "pypom.splinter_driver.Splinter.find_element", new_callable=Mock()
91 | ) as mock_find_element:
92 | visible_mock = Mock().visible.return_value = False
93 | first_mock = Mock().first.return_value = visible_mock
94 | mock_find_element.return_value = first_mock
95 | assert not region.is_element_displayed(*locator)
96 |
97 |
98 | class TestRootElementSplinter:
99 | def test_no_root_usage_error(self, page, splinter):
100 | root_element = MagicMock()
101 | locator = ("not_valid_strategy", str(random.random()))
102 | from pypom.exception import UsageError
103 |
104 | with pytest.raises(UsageError):
105 | Region(page, root=root_element).find_element(*locator)
106 |
107 | def test_find_element_splinter(self, page, splinter, splinter_strategy):
108 | root_element = MagicMock()
109 | root_element.configure_mock(
110 | **{"find_by_{0}.return_value".format(splinter_strategy): Mock()}
111 | )
112 | locator = (splinter_strategy, str(random.random()))
113 | Region(page, root=root_element).find_element(*locator)
114 | getattr(
115 | root_element, "find_by_{0}".format(splinter_strategy)
116 | ).assert_called_once_with(locator[1])
117 |
118 | def test_find_elements_splinter(self, page, splinter, splinter_strategy):
119 | root_element = MagicMock()
120 | root_element.configure_mock(
121 | **{"find_by_{0}.return_value".format(splinter_strategy): Mock()}
122 | )
123 | locator = (splinter_strategy, str(random.random()))
124 | Region(page, root=root_element).find_elements(*locator)
125 | getattr(
126 | root_element, "find_by_{0}".format(splinter_strategy)
127 | ).assert_called_once_with(locator[1])
128 |
129 | def test_is_element_present_splinter(self, page, splinter, splinter_strategy):
130 | root_element = Mock()
131 | locator = (splinter_strategy, str(random.random()))
132 | from splinter.element_list import ElementList
133 |
134 | with patch(
135 | "pypom.splinter_driver.Splinter.find_element", new_callable=MagicMock()
136 | ) as mock_find_element:
137 | mock_find_element.return_value = ElementList([Mock()])
138 | assert Region(page, root=root_element).is_element_present(*locator)
139 | mock_find_element.assert_called_once_with(*locator, root=root_element)
140 |
141 | def test_is_element_present_not_preset_splinter(
142 | self, page, splinter, splinter_strategy
143 | ):
144 | root_element = MagicMock()
145 | from splinter.element_list import ElementList
146 |
147 | root_element.configure_mock(
148 | **{"find_by_{0}.return_value".format(splinter_strategy): ElementList([])}
149 | )
150 | locator = (splinter_strategy, str(random.random()))
151 | assert not Region(page, root=root_element).is_element_present(*locator)
152 |
153 | def test_is_element_displayed_splinter(self, page, splinter, splinter_strategy):
154 | root_element = MagicMock()
155 | root_element.configure_mock(
156 | **{"find_by_{0}.return_value.first.visible".format(splinter_strategy): True}
157 | )
158 | locator = (splinter_strategy, str(random.random()))
159 | region = Region(page, root=root_element)
160 | assert region.is_element_displayed(*locator)
161 |
162 | def test_is_element_displayed_not_present_splinter(
163 | self, page, splinter, splinter_strategy
164 | ):
165 | root_element = Mock()
166 | locator = (splinter_strategy, str(random.random()))
167 | region = Region(page, root=root_element)
168 | from splinter.element_list import ElementList
169 |
170 | with patch(
171 | "pypom.splinter_driver.Splinter.find_element", new_callable=MagicMock()
172 | ) as mock_find_element:
173 | mock_find_element.return_value = ElementList([])
174 | assert not region.is_element_displayed(*locator)
175 |
176 | def test_is_element_displayed_hidden_splinter(
177 | self, page, splinter, splinter_strategy
178 | ):
179 | root_element = MagicMock()
180 | root_element.configure_mock(
181 | **{
182 | "find_by_{0}.return_value.first.visible".format(
183 | splinter_strategy
184 | ): False
185 | }
186 | )
187 | locator = (splinter_strategy, str(random.random()))
188 | region = Region(page, root=root_element)
189 | assert not region.is_element_displayed(*locator)
190 |
191 |
192 | class TestRootLocatorSplinter:
193 | @pytest.fixture
194 | def region(self, page, splinter_strategy):
195 | class MyRegion(Region):
196 | _root_locator = (splinter_strategy, str(random.random()))
197 |
198 | return MyRegion(page)
199 |
200 | def test_root_splinter(self, region, splinter_strategy):
201 | region.root
202 | getattr(
203 | region.driver, "find_by_{0}".format(splinter_strategy)
204 | ).assert_called_once_with(region._root_locator[1])
205 |
206 | def test_find_element_splinter(self, region, splinter_strategy):
207 | locator = (splinter_strategy, str(random.random()))
208 | region.find_element(*locator)
209 |
210 | getattr(
211 | region.root, "find_by_{0}".format(splinter_strategy)
212 | ).assert_called_once_with(locator[1])
213 |
214 | def test_find_elements_splinter(self, region, splinter_strategy):
215 | locator = (splinter_strategy, str(random.random()))
216 | region.find_elements(*locator)
217 |
218 | getattr(
219 | region.root, "find_by_{0}".format(splinter_strategy)
220 | ).assert_called_once_with(locator[1])
221 |
222 | def test_is_element_present_splinter(self, region, splinter_strategy):
223 | assert region._root_locator[0] == splinter_strategy
224 | locator = (splinter_strategy, str(random.random()))
225 |
226 | assert region.is_element_present(*locator)
227 | getattr(
228 | region.root, "find_by_{0}".format(splinter_strategy)
229 | ).assert_called_once_with(locator[1])
230 |
231 | def test_is_element_present_not_present_splinter(self, region, splinter_strategy):
232 | from splinter.element_list import ElementList
233 |
234 | locator = (splinter_strategy, str(random.random()))
235 | with patch(
236 | "pypom.splinter_driver.Splinter.find_elements", new_callable=MagicMock()
237 | ) as mock_find_elements:
238 | mock_find_elements.return_value = ElementList([])
239 | assert not region.is_element_present(*locator)
240 |
241 | def test_is_element_displayed_splinter(self, region, splinter_strategy):
242 | locator = (splinter_strategy, str(random.random()))
243 | with patch(
244 | "pypom.splinter_driver.Splinter.find_element", new_callable=MagicMock()
245 | ) as mock_find_element:
246 | mock_find_element.return_value.first.visible = True
247 | assert region.is_element_displayed(*locator)
248 |
249 | def test_is_element_displayed_not_present_splinter(self, region, splinter_strategy):
250 | locator = (splinter_strategy, str(random.random()))
251 | from splinter.element_list import ElementList
252 |
253 | with patch(
254 | "pypom.splinter_driver.Splinter.find_element", new_callable=Mock()
255 | ) as mock_find_element:
256 | mock_find_element.return_value = ElementList([])
257 | assert not region.is_element_displayed(*locator)
258 |
259 | def test_is_element_displayed_hidden_splinter(self, region, splinter_strategy):
260 | locator = (splinter_strategy, str(random.random()))
261 | with patch(
262 | "pypom.splinter_driver.Splinter.find_element", new_callable=Mock()
263 | ) as mock_find_element:
264 | visible_mock = Mock().visible = False
265 | first_mock = Mock().first.return_value = visible_mock
266 | mock_find_element.return_value = first_mock
267 | assert not region.is_element_displayed(*locator)
268 |
--------------------------------------------------------------------------------
/tests/test_driver.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 |
6 | def test_register_driver():
7 | """ We are testing the registerDriver hook"""
8 | import pytest
9 | from zope.interface import implementer
10 | from zope.interface import Interface
11 | from pypom.driver import registerDriver
12 | from pypom.interfaces import IDriver
13 |
14 | class IFakeDriver(Interface):
15 | """ A fake marker interface"""
16 |
17 | @implementer(IFakeDriver)
18 | class FakeDriver:
19 | """ A fake driver """
20 |
21 | def __init__(self, driver):
22 | self.driver = driver
23 |
24 | fake_driver = FakeDriver(None)
25 |
26 | # no register driver, look up error
27 | with pytest.raises(TypeError):
28 | IDriver(fake_driver)
29 |
30 | registerDriver(IFakeDriver, FakeDriver)
31 |
32 | # driver implementation available after registerDriver
33 | adapted_driver = IDriver(fake_driver)
34 |
35 | # same instance of adapted_driver
36 | assert isinstance(adapted_driver, FakeDriver)
37 |
38 |
39 | def test_multiple_register_driver():
40 | """ We are testing the registerDriver hook,
41 | multiple registrations"""
42 | from zope.interface import implementer
43 | from zope.interface import Interface
44 | from pypom.driver import registerDriver
45 | from pypom.interfaces import IDriver
46 |
47 | class IFakeDriver(Interface):
48 | """ A fake marker interface"""
49 |
50 | @implementer(IFakeDriver)
51 | class FakeDriver:
52 | """ A fake driver """
53 |
54 | def __init__(self, driver):
55 | self.driver = driver
56 |
57 | class IFakeDriver2(Interface):
58 | """ Another fake marker interface"""
59 |
60 | @implementer(IFakeDriver2)
61 | class FakeDriver2:
62 | """ Another fake driver """
63 |
64 | def __init__(self, driver):
65 | self.driver = driver
66 |
67 | fake_driver = FakeDriver(None)
68 | fake_driver2 = FakeDriver2(None)
69 |
70 | registerDriver(IFakeDriver, FakeDriver)
71 | registerDriver(IFakeDriver2, IFakeDriver2)
72 |
73 | # driver implementation available after registerDriver
74 | adapted_driver = IDriver(fake_driver)
75 | adapted_driver2 = IDriver(fake_driver2)
76 |
77 | # same instance of adapted_driver
78 | assert isinstance(adapted_driver, FakeDriver)
79 | assert isinstance(adapted_driver2, FakeDriver2)
80 |
81 |
82 | def test_register_driver_class_implements():
83 | """ We are testing the registerDriver hook with
84 | class implements"""
85 | from zope.interface import Interface
86 | from pypom.driver import registerDriver
87 | from pypom.interfaces import IDriver
88 |
89 | class IFakeDriver(Interface):
90 | """ A fake marker interface"""
91 |
92 | class FakeDriver:
93 | """ A fake driver """
94 |
95 | def __init__(self, driver):
96 | self.driver = driver
97 |
98 | fake_driver = FakeDriver(None)
99 |
100 | registerDriver(IFakeDriver, FakeDriver, [FakeDriver])
101 |
102 | # driver implementation available after registerDriver
103 | adapted_driver = IDriver(fake_driver)
104 |
105 | # same instance of adapted_driver
106 | assert isinstance(adapted_driver, FakeDriver)
107 |
--------------------------------------------------------------------------------
/tests/test_page.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import random
6 |
7 | import pytest
8 |
9 | from pypom import Page
10 |
11 |
12 | def test_base_url(base_url, page):
13 | assert base_url == page.seed_url
14 |
15 |
16 | def test_seed_url_absolute(base_url, driver):
17 | url_template = "https://www.test.com/"
18 |
19 | class MyPage(Page):
20 | URL_TEMPLATE = url_template
21 |
22 | page = MyPage(driver, base_url)
23 | assert url_template == page.seed_url
24 |
25 |
26 | def test_seed_url_absolute_keywords_tokens(base_url, driver):
27 | value = str(random.random())
28 | absolute_url = "https://www.test.com/"
29 |
30 | class MyPage(Page):
31 | URL_TEMPLATE = absolute_url + "{key}"
32 |
33 | page = MyPage(driver, base_url, key=value)
34 | assert absolute_url + value == page.seed_url
35 |
36 |
37 | def test_seed_url_absolute_keywords_params(base_url, driver):
38 | value = str(random.random())
39 | absolute_url = "https://www.test.com/"
40 |
41 | class MyPage(Page):
42 | URL_TEMPLATE = absolute_url
43 |
44 | page = MyPage(driver, base_url, key=value)
45 | assert "{}?key={}".format(absolute_url, value) == page.seed_url
46 |
47 |
48 | def test_seed_url_absolute_keywords_params_none(base_url, driver):
49 | value = None
50 | absolute_url = "https://www.test.com/"
51 |
52 | class MyPage(Page):
53 | URL_TEMPLATE = absolute_url
54 |
55 | page = MyPage(driver, base_url, key=value)
56 | assert absolute_url == page.seed_url
57 |
58 |
59 | def test_seed_url_absolute_keywords_tokens_and_params(base_url, driver):
60 | values = (str(random.random()), str(random.random()))
61 | absolute_url = "https://www.test.com/"
62 |
63 | class MyPage(Page):
64 | URL_TEMPLATE = absolute_url + "?key1={key1}"
65 |
66 | page = MyPage(driver, base_url, key1=values[0], key2=values[1])
67 | assert "{}?key1={}&key2={}".format(absolute_url, *values) == page.seed_url
68 |
69 |
70 | def test_seed_url_empty(driver):
71 | page = Page(driver)
72 | assert page.seed_url is None
73 |
74 |
75 | def test_seed_url_keywords_tokens(base_url, driver):
76 | value = str(random.random())
77 |
78 | class MyPage(Page):
79 | URL_TEMPLATE = "{key}"
80 |
81 | page = MyPage(driver, base_url, key=value)
82 | assert base_url + value == page.seed_url
83 |
84 |
85 | def test_seed_url_keywords_params(base_url, driver):
86 | value = str(random.random())
87 | page = Page(driver, base_url, key=value)
88 | assert "{}?key={}".format(base_url, value) == page.seed_url
89 |
90 |
91 | def test_seed_url_keywords_params_space(base_url, driver):
92 | value = "a value"
93 | page = Page(driver, base_url, key=value)
94 | assert "{}?key={}".format(base_url, "a+value") == page.seed_url
95 |
96 |
97 | def test_seed_url_keywords_params_special(base_url, driver):
98 | value = "mozilla&co"
99 | page = Page(driver, base_url, key=value)
100 | assert "{}?key={}".format(base_url, "mozilla%26co") == page.seed_url
101 |
102 |
103 | def test_seed_url_keywords_multiple_params(base_url, driver):
104 | value = ("foo", "bar")
105 | page = Page(driver, base_url, key=value)
106 | seed_url = page.seed_url
107 | assert "key={}".format(value[0]) in seed_url
108 | assert "key={}".format(value[1]) in seed_url
109 | import re
110 |
111 | assert re.match(r"{}\?key=(foo|bar)&key=(foo|bar)".format(base_url), seed_url)
112 |
113 |
114 | def test_seed_url_keywords_multiple_params_special(base_url, driver):
115 | value = ("foo", "mozilla&co")
116 | page = Page(driver, base_url, key=value)
117 | seed_url = page.seed_url
118 | assert "key=foo" in seed_url
119 | assert "key=mozilla%26co" in seed_url
120 | import re
121 |
122 | assert re.match(
123 | r"{}\?key=(foo|mozilla%26co)&key=(foo|mozilla%26co)".format(base_url), seed_url
124 | )
125 |
126 |
127 | def test_seed_url_keywords_keywords_and_params(base_url, driver):
128 | values = (str(random.random()), str(random.random()))
129 |
130 | class MyPage(Page):
131 | URL_TEMPLATE = "?key1={key1}"
132 |
133 | page = MyPage(driver, base_url, key1=values[0], key2=values[1])
134 | assert "{}?key1={}&key2={}".format(base_url, *values) == page.seed_url
135 |
136 |
137 | def test_seed_url_prepend(base_url, driver):
138 | url_template = str(random.random())
139 |
140 | class MyPage(Page):
141 | URL_TEMPLATE = url_template
142 |
143 | page = MyPage(driver, base_url)
144 | assert base_url + url_template == page.seed_url
145 |
146 |
147 | def test_open(page, driver):
148 | assert isinstance(page.open(), Page)
149 |
150 |
151 | def test_open_seed_url_none(driver):
152 | from pypom.exception import UsageError
153 |
154 | page = Page(driver)
155 | with pytest.raises(UsageError):
156 | page.open()
157 |
158 |
159 | def test_open_timeout(base_url, driver):
160 | class MyPage(Page):
161 | def wait_for_page_to_load(self):
162 | self.wait.until(lambda s: False)
163 |
164 | page = MyPage(driver, base_url, timeout=0)
165 | from selenium.common.exceptions import TimeoutException
166 |
167 | with pytest.raises(TimeoutException):
168 | page.open()
169 |
170 |
171 | def test_open_timeout_loaded(base_url, driver):
172 | class MyPage(Page):
173 | @property
174 | def loaded(self):
175 | return False
176 |
177 | page = MyPage(driver, base_url, timeout=0)
178 | from selenium.common.exceptions import TimeoutException
179 |
180 | with pytest.raises(TimeoutException):
181 | page.open()
182 |
183 |
184 | def test_wait_for_page(page, driver):
185 | assert isinstance(page.wait_for_page_to_load(), Page)
186 |
187 |
188 | def test_wait_for_page_timeout(base_url, driver):
189 | class MyPage(Page):
190 | def wait_for_page_to_load(self):
191 | self.wait.until(lambda s: False)
192 |
193 | page = MyPage(driver, base_url, timeout=0)
194 | from selenium.common.exceptions import TimeoutException
195 |
196 | with pytest.raises(TimeoutException):
197 | page.wait_for_page_to_load()
198 |
199 |
200 | def test_wait_for_page_timeout_loaded(base_url, driver):
201 | class MyPage(Page):
202 | @property
203 | def loaded(self):
204 | return False
205 |
206 | page = MyPage(driver, base_url, timeout=0)
207 | from selenium.common.exceptions import TimeoutException
208 |
209 | with pytest.raises(TimeoutException):
210 | page.wait_for_page_to_load()
211 |
212 |
213 | def test_wait_for_page_empty_base_url(driver):
214 | assert isinstance(Page(driver).wait_for_page_to_load(), Page)
215 |
216 |
217 | def test_bwc_selenium(page, driver_interface):
218 | """ Backwards compatibility with old selenium attribute """
219 | driver = page.selenium
220 | assert driver == page.driver
221 |
222 |
223 | def test_loaded(page, driver):
224 | assert page.loaded is True
225 |
--------------------------------------------------------------------------------
/tests/test_plugin.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | from pypom import Region, hookimpl
6 |
7 |
8 | def test_after_wait_for_page_to_load(page):
9 | log = []
10 |
11 | class Plugin:
12 | @hookimpl
13 | def pypom_after_wait_for_page_to_load(self, page):
14 | log.append(1)
15 |
16 | @hookimpl
17 | def pypom_after_wait_for_region_to_load(self, region):
18 | log.append(2)
19 |
20 | page.pm.register(Plugin())
21 | page.open()
22 | Region(page)
23 | assert log == [1, 2]
24 |
--------------------------------------------------------------------------------
/tests/test_region.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | import pytest
6 | from mock import Mock
7 |
8 | from pypom import Region
9 |
10 |
11 | class TestWaitForRegion:
12 | def test_wait_for_region(self, page):
13 | assert isinstance(Region(page).wait_for_region_to_load(), Region)
14 |
15 | def test_wait_for_region_timeout(self, page):
16 | class MyRegion(Region):
17 | def wait_for_region_to_load(self):
18 | self.wait.until(lambda s: False)
19 |
20 | page.timeout = 0
21 | from selenium.common.exceptions import TimeoutException
22 |
23 | with pytest.raises(TimeoutException):
24 | MyRegion(page)
25 |
26 | def test_wait_for_region_timeout_loaded(self, page):
27 | class MyRegion(Region):
28 | @property
29 | def loaded(self):
30 | return False
31 |
32 | page.timeout = 0
33 | from selenium.common.exceptions import TimeoutException
34 |
35 | with pytest.raises(TimeoutException):
36 | MyRegion(page)
37 |
38 |
39 | class TestNoRoot:
40 | def test_root(self, page):
41 | assert Region(page).root is None
42 |
43 |
44 | class TestRootElement:
45 | def test_root(self, page, driver):
46 | element = Mock()
47 | assert Region(page, root=element).root == element
48 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{27,36,py,py3}, flake8, docs
3 |
4 | [testenv]
5 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
6 | deps = -rrequirements/pipenv.txt
7 | commands =
8 | pipenv install --dev --skip-lock
9 | pipenv run pytest --cov {posargs}
10 | - pipenv run coveralls
11 |
12 | [testenv:flake8]
13 | skip_install = true
14 | commands =
15 | pipenv install --dev
16 | pipenv run flake8 {posargs}
17 |
18 | [testenv:docs]
19 | changedir = docs
20 | commands =
21 | pipenv install --dev
22 | pipenv run sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
23 |
--------------------------------------------------------------------------------