├── .gitignore ├── LICENSE.txt ├── MANIFEST ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── build │ ├── .DS_Store │ ├── doctrees │ │ ├── environment.pickle │ │ ├── generators.doctree │ │ ├── index.doctree │ │ ├── po │ │ │ ├── remotecontrol.doctree │ │ │ └── webdriver.doctree │ │ ├── providers.doctree │ │ └── testcase │ │ │ ├── base.doctree │ │ │ ├── remotecontrol.doctree │ │ │ └── webdriver.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _sources │ │ ├── generators.txt │ │ ├── index.txt │ │ ├── po │ │ │ ├── remotecontrol.txt │ │ │ └── webdriver.txt │ │ ├── providers.txt │ │ └── testcase │ │ │ ├── base.txt │ │ │ ├── remotecontrol.txt │ │ │ └── webdriver.txt │ │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── default.css │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ │ ├── generators.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── po │ │ ├── remotecontrol.html │ │ └── webdriver.html │ │ ├── providers.html │ │ ├── py-modindex.html │ │ ├── search.html │ │ ├── searchindex.js │ │ └── testcase │ │ ├── base.html │ │ ├── remotecontrol.html │ │ └── webdriver.html ├── make.bat └── source │ ├── conf.py │ ├── generators.rst │ ├── index.rst │ ├── po │ ├── remotecontrol.rst │ └── webdriver.rst │ ├── providers.rst │ └── testcase │ ├── base.rst │ ├── remotecontrol.rst │ └── webdriver.rst ├── saunter ├── ConfigWrapper.py ├── SaunterWebDriver.py ├── __init__.py ├── _defaults │ ├── conf │ │ ├── browsers │ │ │ └── browser.yaml.default │ │ ├── sauce labs.yaml.default │ │ ├── saunter.ini.default │ │ ├── saunter.yaml.default │ │ └── selenium.yaml.default │ ├── conftest.py │ ├── pytest.ini │ └── tailored │ │ ├── page.py │ │ └── webdriver.py ├── browser.py ├── exceptions.py ├── generators │ ├── __init__.py │ └── string_data.py ├── main.py ├── matchers.py ├── po │ ├── __init__.py │ └── webdriver │ │ ├── __init__.py │ │ ├── attribute.py │ │ ├── checkbox.py │ │ ├── element.py │ │ ├── jquery │ │ ├── __init__.py │ │ └── datepicker.py │ │ ├── multi_select.py │ │ ├── number.py │ │ ├── page.py │ │ ├── radio.py │ │ ├── select.py │ │ ├── text.py │ │ └── unicode.py ├── providers │ ├── __init__.py │ ├── couch_provider.py │ ├── csv_provider.py │ ├── django_provider.py │ ├── mysql_provider.py │ └── sqlite3_provider.py ├── saucelabs.py ├── testcase │ ├── __init__.py │ ├── base.py │ └── webdriver.py └── web_element.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | conf/*.ini 2 | *.pyc 3 | logs/*.xml 4 | logs/*.vlc 5 | logs/*.flv 6 | logs/*.log 7 | build/* 8 | dist/* 9 | saunter.egg-info/* 10 | examples/**/logs/*.xml 11 | examples/**/logs/*.log 12 | examples/**/logs/*.flv 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 16 | 17 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 18 | 19 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 22 | 23 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 24 | 25 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 26 | 27 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 28 | 29 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 30 | 31 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 32 | 33 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 34 | 35 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 36 | 37 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 38 | 39 | You must cause any modified files to carry prominent notices stating that You changed the files; and 40 | 41 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 42 | 43 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | saunter/ConfigWrapper.py 5 | saunter/SaunterWebDriver.py 6 | saunter/__init__.py 7 | saunter/browser.py 8 | saunter/exceptions.py 9 | saunter/main.py 10 | saunter/matchers.py 11 | saunter/saucelabs.py 12 | saunter/web_element.py 13 | saunter/_defaults/conftest.py 14 | saunter/_defaults/pytest.ini 15 | saunter/_defaults/conf/sauce labs.yaml.default 16 | saunter/_defaults/conf/saunter.ini.default 17 | saunter/_defaults/conf/saunter.yaml.default 18 | saunter/_defaults/conf/selenium.yaml.default 19 | saunter/_defaults/conf/browsers/browser.yaml.default 20 | saunter/_defaults/tailored/page.py 21 | saunter/_defaults/tailored/webdriver.py 22 | saunter/generators/__init__.py 23 | saunter/generators/string_data.py 24 | saunter/po/__init__.py 25 | saunter/po/webdriver/__init__.py 26 | saunter/po/webdriver/attribute.py 27 | saunter/po/webdriver/checkbox.py 28 | saunter/po/webdriver/element.py 29 | saunter/po/webdriver/multi_select.py 30 | saunter/po/webdriver/number.py 31 | saunter/po/webdriver/page.py 32 | saunter/po/webdriver/select.py 33 | saunter/po/webdriver/text.py 34 | saunter/po/webdriver/unicode.py 35 | saunter/providers/__init__.py 36 | saunter/providers/couch_provider.py 37 | saunter/providers/csv_provider.py 38 | saunter/providers/django_provider.py 39 | saunter/providers/mysql_provider.py 40 | saunter/providers/sqlite3_provider.py 41 | saunter/testcase/__init__.py 42 | saunter/testcase/base.py 43 | saunter/testcase/webdriver.py 44 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include saunter/_defaults * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Saunter is an opinionated automation framework for use with the Selenium RC and WebDriver libraries. It is designed to remove a lot of the overhead and cruft that hinders teams when they first start out with automation. For documentation around Saunter see http://element34.ca/products/saunter. 2 | 3 | Note: The configuration structure changed with 2.0.0. Either pin your install to the old version of click above link for a brief description of the change. In a nutshell, .ini has been replaced with .yaml. 4 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | # export DOCGENERATION := true 4 | 5 | # You can set these variables from the command line. 6 | SPHINXOPTS = 7 | SPHINXBUILD = sphinx-build 8 | PAPER = 9 | BUILDDIR = build 10 | 11 | # Internal variables. 12 | PAPEROPT_a4 = -D latex_paper_size=a4 13 | PAPEROPT_letter = -D latex_paper_size=letter 14 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 15 | 16 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 17 | 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " devhelp to make HTML files and a Devhelp project" 28 | @echo " epub to make an epub" 29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 31 | @echo " text to make text files" 32 | @echo " man to make manual pages" 33 | @echo " changes to make an overview of all changed/added/deprecated items" 34 | @echo " linkcheck to check all external links for integrity" 35 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 36 | 37 | clean: 38 | -rm -rf $(BUILDDIR)/* 39 | 40 | html: 41 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 42 | @echo 43 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 44 | 45 | dirhtml: 46 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 47 | @echo 48 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 49 | 50 | singlehtml: 51 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 52 | @echo 53 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 54 | 55 | pickle: 56 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 57 | @echo 58 | @echo "Build finished; now you can process the pickle files." 59 | 60 | json: 61 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 62 | @echo 63 | @echo "Build finished; now you can process the JSON files." 64 | 65 | htmlhelp: 66 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 67 | @echo 68 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 69 | ".hhp project file in $(BUILDDIR)/htmlhelp." 70 | 71 | qthelp: 72 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 73 | @echo 74 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 75 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 76 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PySaunter.qhcp" 77 | @echo "To view the help file:" 78 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PySaunter.qhc" 79 | 80 | devhelp: 81 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 82 | @echo 83 | @echo "Build finished." 84 | @echo "To view the help file:" 85 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PySaunter" 86 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PySaunter" 87 | @echo "# devhelp" 88 | 89 | epub: 90 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 91 | @echo 92 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 93 | 94 | latex: 95 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 96 | @echo 97 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 98 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 99 | "(use \`make latexpdf' here to do that automatically)." 100 | 101 | latexpdf: 102 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 103 | @echo "Running LaTeX files through pdflatex..." 104 | make -C $(BUILDDIR)/latex all-pdf 105 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 106 | 107 | text: 108 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 109 | @echo 110 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 111 | 112 | man: 113 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 114 | @echo 115 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 116 | 117 | changes: 118 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 119 | @echo 120 | @echo "The overview file is in $(BUILDDIR)/changes." 121 | 122 | linkcheck: 123 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 124 | @echo 125 | @echo "Link check complete; look for any errors in the above output " \ 126 | "or in $(BUILDDIR)/linkcheck/output.txt." 127 | 128 | doctest: 129 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 130 | @echo "Testing of doctests in the sources finished, look at the " \ 131 | "results in $(BUILDDIR)/doctest/output.txt." 132 | -------------------------------------------------------------------------------- /docs/build/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/.DS_Store -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/generators.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/generators.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/po/remotecontrol.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/po/remotecontrol.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/po/webdriver.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/po/webdriver.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/providers.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/providers.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/testcase/base.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/testcase/base.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/testcase/remotecontrol.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/testcase/remotecontrol.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/testcase/webdriver.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/doctrees/testcase/webdriver.doctree -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 85ce1e53995e316852273b5ac383771b 4 | tags: fbb0d17656682115ca4d033fb2f83ba1 5 | -------------------------------------------------------------------------------- /docs/build/html/_sources/generators.txt: -------------------------------------------------------------------------------- 1 | saunter.generators 2 | ================== 3 | 4 | Generators are useful for creating data on the fly. 5 | 6 | .. automodule:: saunter.generators.string_data 7 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. Py.Saunter documentation master file, created by 2 | sphinx-quickstart on Thu Sep 8 12:12:06 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Py.Saunter 7 | ========== 8 | 9 | This is the class documentation for Py.Saunter. For the larger documentation set in context, see the `Py.Saunter page `_ at Element 34. 10 | 11 | Remote Control: 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | testcase/remotecontrol 17 | po/remotecontrol 18 | 19 | WebDriver: 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | testcase/webdriver 25 | po/webdriver 26 | 27 | Common: 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | testcase/base 33 | generators 34 | providers 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | 43 | -------------------------------------------------------------------------------- /docs/build/html/_sources/po/remotecontrol.txt: -------------------------------------------------------------------------------- 1 | Pages 2 | ===== 3 | 4 | .. automodule:: saunter.po.remotecontrol.page 5 | :members: Page 6 | 7 | Elements 8 | ======== 9 | 10 | .. automodule:: saunter.po.remotecontrol.element 11 | :members: Element 12 | 13 | .. automodule:: saunter.po.remotecontrol.checkbox 14 | :members: Checkbox 15 | 16 | .. automodule:: saunter.po.remotecontrol.select 17 | :members: Select 18 | 19 | .. automodule:: saunter.po.remotecontrol.text 20 | :members: Text -------------------------------------------------------------------------------- /docs/build/html/_sources/po/webdriver.txt: -------------------------------------------------------------------------------- 1 | Pages 2 | ===== 3 | 4 | .. automodule:: saunter.po.webdriver.page 5 | :members: Page 6 | 7 | Elements 8 | ======== 9 | 10 | .. automodule:: saunter.po.webdriver.element 11 | :members: Element 12 | 13 | .. automodule:: saunter.po.webdriver.select 14 | :members: Select 15 | 16 | .. automodule:: saunter.po.webdriver.text 17 | :members: Text -------------------------------------------------------------------------------- /docs/build/html/_sources/providers.txt: -------------------------------------------------------------------------------- 1 | saunter.providers 2 | ================= 3 | 4 | Providers can insert data into scripts from canned resources, or act as conduits into the backend for verification purposes. 5 | 6 | .. automodule:: saunter.providers.csv_provider 7 | :members: CSVProvider 8 | 9 | .. automodule:: saunter.providers.django_provider 10 | :members: DjangoProvider 11 | 12 | .. automodule:: saunter.providers.sqlite3_provider 13 | :members: DBProvider -------------------------------------------------------------------------------- /docs/build/html/_sources/testcase/base.txt: -------------------------------------------------------------------------------- 1 | saunter.testcase.base 2 | ===================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.base 7 | :members: BaseTestCase -------------------------------------------------------------------------------- /docs/build/html/_sources/testcase/remotecontrol.txt: -------------------------------------------------------------------------------- 1 | saunter.testcase.remotecontrol 2 | ============================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.remotecontrol 7 | :members: SaunterTestCase -------------------------------------------------------------------------------- /docs/build/html/_sources/testcase/webdriver.txt: -------------------------------------------------------------------------------- 1 | saunter.testcase.webdriver 2 | ========================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.webdriver 7 | :members: SaunterTestCase -------------------------------------------------------------------------------- /docs/build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/comment.png -------------------------------------------------------------------------------- /docs/build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | } 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * small function to check if an array contains 66 | * a given item. 67 | */ 68 | jQuery.contains = function(arr, item) { 69 | for (var i = 0; i < arr.length; i++) { 70 | if (arr[i] == item) 71 | return true; 72 | } 73 | return false; 74 | }; 75 | 76 | /** 77 | * highlight a given string on a jquery object by wrapping it in 78 | * span elements with the given class name. 79 | */ 80 | jQuery.fn.highlightText = function(text, className) { 81 | function highlight(node) { 82 | if (node.nodeType == 3) { 83 | var val = node.nodeValue; 84 | var pos = val.toLowerCase().indexOf(text); 85 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 86 | var span = document.createElement("span"); 87 | span.className = className; 88 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 89 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 90 | document.createTextNode(val.substr(pos + text.length)), 91 | node.nextSibling)); 92 | node.nodeValue = val.substr(0, pos); 93 | } 94 | } 95 | else if (!jQuery(node).is("button, select, textarea")) { 96 | jQuery.each(node.childNodes, function() { 97 | highlight(this); 98 | }); 99 | } 100 | } 101 | return this.each(function() { 102 | highlight(this); 103 | }); 104 | }; 105 | 106 | /** 107 | * Small JavaScript module for the documentation. 108 | */ 109 | var Documentation = { 110 | 111 | init : function() { 112 | this.fixFirefoxAnchorBug(); 113 | this.highlightSearchWords(); 114 | this.initIndexTable(); 115 | }, 116 | 117 | /** 118 | * i18n support 119 | */ 120 | TRANSLATIONS : {}, 121 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 122 | LOCALE : 'unknown', 123 | 124 | // gettext and ngettext don't access this so that the functions 125 | // can safely bound to a different name (_ = Documentation.gettext) 126 | gettext : function(string) { 127 | var translated = Documentation.TRANSLATIONS[string]; 128 | if (typeof translated == 'undefined') 129 | return string; 130 | return (typeof translated == 'string') ? translated : translated[0]; 131 | }, 132 | 133 | ngettext : function(singular, plural, n) { 134 | var translated = Documentation.TRANSLATIONS[singular]; 135 | if (typeof translated == 'undefined') 136 | return (n == 1) ? singular : plural; 137 | return translated[Documentation.PLURALEXPR(n)]; 138 | }, 139 | 140 | addTranslations : function(catalog) { 141 | for (var key in catalog.messages) 142 | this.TRANSLATIONS[key] = catalog.messages[key]; 143 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 144 | this.LOCALE = catalog.locale; 145 | }, 146 | 147 | /** 148 | * add context elements like header anchor links 149 | */ 150 | addContextElements : function() { 151 | $('div[id] > :header:first').each(function() { 152 | $('\u00B6'). 153 | attr('href', '#' + this.id). 154 | attr('title', _('Permalink to this headline')). 155 | appendTo(this); 156 | }); 157 | $('dt[id]').each(function() { 158 | $('\u00B6'). 159 | attr('href', '#' + this.id). 160 | attr('title', _('Permalink to this definition')). 161 | appendTo(this); 162 | }); 163 | }, 164 | 165 | /** 166 | * workaround a firefox stupidity 167 | */ 168 | fixFirefoxAnchorBug : function() { 169 | if (document.location.hash && $.browser.mozilla) 170 | window.setTimeout(function() { 171 | document.location.href += ''; 172 | }, 10); 173 | }, 174 | 175 | /** 176 | * highlight the search words provided in the url in the text 177 | */ 178 | highlightSearchWords : function() { 179 | var params = $.getQueryParameters(); 180 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 181 | if (terms.length) { 182 | var body = $('div.body'); 183 | window.setTimeout(function() { 184 | $.each(terms, function() { 185 | body.highlightText(this.toLowerCase(), 'highlighted'); 186 | }); 187 | }, 10); 188 | $('') 190 | .appendTo($('#searchbox')); 191 | } 192 | }, 193 | 194 | /** 195 | * init the domain index toggle buttons 196 | */ 197 | initIndexTable : function() { 198 | var togglers = $('img.toggler').click(function() { 199 | var src = $(this).attr('src'); 200 | var idnum = $(this).attr('id').substr(7); 201 | $('tr.cg-' + idnum).toggle(); 202 | if (src.substr(-9) == 'minus.png') 203 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 204 | else 205 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 206 | }).css('display', ''); 207 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 208 | togglers.click(); 209 | } 210 | }, 211 | 212 | /** 213 | * helper function to hide the search marks again 214 | */ 215 | hideSearchWords : function() { 216 | $('#searchbox .highlight-link').fadeOut(300); 217 | $('span.highlighted').removeClass('highlighted'); 218 | }, 219 | 220 | /** 221 | * make the url absolute 222 | */ 223 | makeURL : function(relativeURL) { 224 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 225 | }, 226 | 227 | /** 228 | * get the current relative url 229 | */ 230 | getCurrentURL : function() { 231 | var path = document.location.pathname; 232 | var parts = path.split(/\//); 233 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 234 | if (this == '..') 235 | parts.pop(); 236 | }); 237 | var url = parts.join('/'); 238 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 239 | } 240 | }; 241 | 242 | // quick alias for translations 243 | _ = Documentation.gettext; 244 | 245 | $(document).ready(function() { 246 | Documentation.init(); 247 | }); 248 | -------------------------------------------------------------------------------- /docs/build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/down.png -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #303030 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | // global elements used by the functions. 26 | // the 'sidebarbutton' element is defined as global after its 27 | // creation, in the add_sidebar_button function 28 | var bodywrapper = $('.bodywrapper'); 29 | var sidebar = $('.sphinxsidebar'); 30 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 31 | 32 | // for some reason, the document has no sidebar; do not run into errors 33 | if (!sidebar.length) return; 34 | 35 | // original margin-left of the bodywrapper and width of the sidebar 36 | // with the sidebar expanded 37 | var bw_margin_expanded = bodywrapper.css('margin-left'); 38 | var ssb_width_expanded = sidebar.width(); 39 | 40 | // margin-left of the bodywrapper and width of the sidebar 41 | // with the sidebar collapsed 42 | var bw_margin_collapsed = '.8em'; 43 | var ssb_width_collapsed = '.8em'; 44 | 45 | // colors used by the current theme 46 | var dark_color = $('.related').css('background-color'); 47 | var light_color = $('.document').css('background-color'); 48 | 49 | function sidebar_is_collapsed() { 50 | return sidebarwrapper.is(':not(:visible)'); 51 | } 52 | 53 | function toggle_sidebar() { 54 | if (sidebar_is_collapsed()) 55 | expand_sidebar(); 56 | else 57 | collapse_sidebar(); 58 | } 59 | 60 | function collapse_sidebar() { 61 | sidebarwrapper.hide(); 62 | sidebar.css('width', ssb_width_collapsed); 63 | bodywrapper.css('margin-left', bw_margin_collapsed); 64 | sidebarbutton.css({ 65 | 'margin-left': '0', 66 | 'height': bodywrapper.height() 67 | }); 68 | sidebarbutton.find('span').text('»'); 69 | sidebarbutton.attr('title', _('Expand sidebar')); 70 | document.cookie = 'sidebar=collapsed'; 71 | } 72 | 73 | function expand_sidebar() { 74 | bodywrapper.css('margin-left', bw_margin_expanded); 75 | sidebar.css('width', ssb_width_expanded); 76 | sidebarwrapper.show(); 77 | sidebarbutton.css({ 78 | 'margin-left': ssb_width_expanded-12, 79 | 'height': bodywrapper.height() 80 | }); 81 | sidebarbutton.find('span').text('«'); 82 | sidebarbutton.attr('title', _('Collapse sidebar')); 83 | document.cookie = 'sidebar=expanded'; 84 | } 85 | 86 | function add_sidebar_button() { 87 | sidebarwrapper.css({ 88 | 'float': 'left', 89 | 'margin-right': '0', 90 | 'width': ssb_width_expanded - 28 91 | }); 92 | // create the button 93 | sidebar.append( 94 | '
«
' 95 | ); 96 | var sidebarbutton = $('#sidebarbutton'); 97 | light_color = sidebarbutton.css('background-color'); 98 | // find the height of the viewport to center the '<<' in the page 99 | var viewport_height; 100 | if (window.innerHeight) 101 | viewport_height = window.innerHeight; 102 | else 103 | viewport_height = $(window).height(); 104 | sidebarbutton.find('span').css({ 105 | 'display': 'block', 106 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 107 | }); 108 | 109 | sidebarbutton.click(toggle_sidebar); 110 | sidebarbutton.attr('title', _('Collapse sidebar')); 111 | sidebarbutton.css({ 112 | 'color': '#FFFFFF', 113 | 'border-left': '1px solid ' + dark_color, 114 | 'font-size': '1.2em', 115 | 'cursor': 'pointer', 116 | 'height': bodywrapper.height(), 117 | 'padding-top': '1px', 118 | 'margin-left': ssb_width_expanded - 12 119 | }); 120 | 121 | sidebarbutton.hover( 122 | function () { 123 | $(this).css('background-color', dark_color); 124 | }, 125 | function () { 126 | $(this).css('background-color', light_color); 127 | } 128 | ); 129 | } 130 | 131 | function set_position_from_cookie() { 132 | if (!document.cookie) 133 | return; 134 | var items = document.cookie.split(';'); 135 | for(var k=0; k=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)}); 17 | return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length); 18 | var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false; 19 | if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== 20 | 0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&& 21 | a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g, 22 | " ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments); 23 | o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); 24 | -------------------------------------------------------------------------------- /docs/build/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/build/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/_static/up.png -------------------------------------------------------------------------------- /docs/build/html/generators.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | saunter.generators — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |

saunter.generators

59 |

Generators are useful for creating data on the fly.

60 |
61 |

string_data

62 |
63 |
64 | saunter.generators.string_data.random_string(random_length=None)
65 |

A generator for creating random string data of letters plus ‘ ‘ (whitespace)

66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
Params random_length:
 how many characters of random string data. if not provided, will be between 1 - 30
Returns:String
77 |
78 | 79 |
80 |
81 | 82 | 83 |
84 |
85 |
86 |
87 |
88 |

Table Of Contents

89 | 95 | 96 |

Previous topic

97 |

saunter.testcase.base

99 |

Next topic

100 |

saunter.providers

102 |

This Page

103 | 107 | 119 | 120 |
121 |
122 |
123 |
124 | 142 | 146 | 147 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Py.Saunter — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 47 | 48 |
49 |
50 |
51 |
52 | 53 |
54 |

Py.Saunter

55 |

This is the class documentation for Py.Saunter. For the larger documentation set in context, see the Py.Saunter page at Element 34.

56 |

Remote Control:

57 |
58 | 75 |
76 |

WebDriver:

77 |
78 | 94 |
95 |

Common:

96 |
97 | 110 |
111 |
112 |
113 |

Indices and tables

114 | 119 |
120 | 121 | 122 |
123 |
124 |
125 |
126 |
127 |

Table Of Contents

128 | 134 | 135 |

Next topic

136 |

saunter.testcase.remotecontrol

138 |

This Page

139 | 143 | 155 | 156 |
157 |
158 |
159 |
160 | 175 | 179 | 180 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Python Module Index — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 46 | 47 |
48 |
49 |
50 |
51 | 52 | 53 |

Python Module Index

54 | 55 |
56 | s 57 |
58 | 59 | 60 | 61 | 63 | 64 | 66 | 69 | 70 | 71 | 74 | 75 | 76 | 79 | 80 | 81 | 84 | 85 | 86 | 89 | 90 | 91 | 94 | 95 | 96 | 99 | 100 | 101 | 104 | 105 | 106 | 109 | 110 | 111 | 114 | 115 | 116 | 119 | 120 | 121 | 124 | 125 | 126 | 129 | 130 | 131 | 134 | 135 | 136 | 139 | 140 | 141 | 144 | 145 | 146 | 149 |
 
62 | s
67 | saunter 68 |
    72 | saunter.generators.string_data 73 |
    77 | saunter.po.remotecontrol.checkbox 78 |
    82 | saunter.po.remotecontrol.element 83 |
    87 | saunter.po.remotecontrol.page 88 |
    92 | saunter.po.remotecontrol.select 93 |
    97 | saunter.po.remotecontrol.text 98 |
    102 | saunter.po.webdriver.element 103 |
    107 | saunter.po.webdriver.page 108 |
    112 | saunter.po.webdriver.select 113 |
    117 | saunter.po.webdriver.text 118 |
    122 | saunter.providers.csv_provider 123 |
    127 | saunter.providers.django_provider 128 |
    132 | saunter.providers.sqlite3_provider 133 |
    137 | saunter.testcase.base 138 |
    142 | saunter.testcase.remotecontrol 143 |
    147 | saunter.testcase.webdriver 148 |
150 | 151 | 152 |
153 |
154 |
155 |
156 |
157 | 169 | 170 |
171 |
172 |
173 |
174 | 186 | 190 | 191 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Search — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 104 | 105 | -------------------------------------------------------------------------------- /docs/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({objects:{"saunter.po.remotecontrol.page":{Page:[1,3,1,""]},"saunter.testcase":{webdriver:[4,0,1,""],base:[6,0,1,""],remotecontrol:[3,0,1,""]},"saunter.po.remotecontrol.select":{Select:[1,1,1,""]},"saunter.po.remotecontrol.checkbox":{Checkbox:[1,1,1,""]},"saunter.providers.csv_provider":{CSVProvider:[2,3,1,""]},"saunter.testcase.remotecontrol":{SaunterTestCase:[3,3,1,""]},"saunter.po.webdriver.select":{Select:[7,1,1,""]},"saunter.po.webdriver.page.Page":{wait_for_hidden:[7,2,1,""],wait_for_available:[7,2,1,""],wait_for_visible:[7,2,1,""],wait_for_text:[7,2,1,""],wait_for_element_not_present:[7,2,1,""],is_element_available:[7,2,1,""],wait_for_value:[7,2,1,""]},"saunter.po.webdriver":{text:[7,0,1,""],page:[7,0,1,""],select:[7,0,1,""],element:[7,0,1,""]},"saunter.testcase.webdriver":{SaunterTestCase:[4,3,1,""]},"saunter.po.remotecontrol.text":{Text:[1,1,1,""]},"saunter.providers.sqlite3_provider":{DBProvider:[2,3,1,""]},"saunter.po.webdriver.page":{Page:[7,3,1,""]},"saunter.providers":{csv_provider:[2,0,1,""],sqlite3_provider:[2,0,1,""],django_provider:[2,0,1,""]},"saunter.po.webdriver.element":{Element:[7,3,1,""]},"saunter.po.remotecontrol":{text:[1,0,1,""],checkbox:[1,0,1,""],page:[1,0,1,""],select:[1,0,1,""],element:[1,0,1,""]},"saunter.generators":{string_data:[5,0,1,""]},"saunter.providers.django_provider.DjangoProvider":{get_random_user:[2,2,1,""]},"saunter.po.remotecontrol.element":{Element:[1,3,1,""]},"saunter.providers.django_provider":{DjangoProvider:[2,3,1,""]},"saunter.po.remotecontrol.page.Page":{wait_for_hidden:[1,2,1,""],wait_for_element_present:[1,2,1,""],wait_for_visible:[1,2,1,""],wait_for_text:[1,2,1,""],wait_for_element_not_present:[1,2,1,""],is_element_available:[1,2,1,""],wait_for_value:[1,2,1,""]},"saunter.providers.csv_provider.CSVProvider":{randomRow:[2,2,1,""]},"saunter.testcase.webdriver.SaunterTestCase":{setup_method:[4,2,1,""],teardown_method:[4,2,1,""]},"saunter.testcase.remotecontrol.SaunterTestCase":{tearDown:[3,2,1,""],setUp:[3,2,1,""]},"saunter.providers.sqlite3_provider.DBProvider":{get_random_user:[2,2,1,""]},"saunter.generators.string_data":{random_string:[5,4,1,""]},"saunter.po.webdriver.text":{Text:[7,1,1,""]}},terms:{all:[3,4],checkbox:[0,1],csv_provid:[0,2],text:[0,1,7],random:[5,2],remov:[1,7],through:[3,4],file:2,sauntertestcas:[0,3,4],row:2,onli:[1,7],locat:[1,7,2],field:[1,7],configur:[3,4],selenium:3,param:[5,2],aren:[1,7],creat:5,hidden:[1,7],"return":[5,2],string:5,get:2,string_data:[0,5],randomrow:2,like:[3,4,6],insert:2,document:0,name:[3,4,2],whitespac:5,list:2,wait_for_element_not_pres:[1,7],server:[3,4],sauc:[3,4],remot:0,either:[3,2],verif:2,wait_for_valu:[1,7],soft:[3,4],page:[0,1,7],wait_for_hidden:[1,7],set:[0,1,7],deal:[1,7],some:[1,7],methodnam:[3,4],random_length:5,see:0,video:[3,4],connect:3,testcas:[0,3,4,6],resourc:2,index:0,statu:[3,4],parent:[3,4],databas:2,subclass:[3,4,6],access:2,setup_method:4,between:5,method:[3,1,4,7],common:0,plu:5,run:[3,4],wait_for_avail:7,power:2,gener:[0,5],django_provid:[0,2],job:[3,4],base:[0,1,6,7],dictionari:2,sinc:[1,7],valu:[1,7],wait:[1,7],search:0,remotecontrol:[0,3,1],most:[1,7],larger:0,synchron:[1,7],is_element_avail:[1,7],thing:[1,7],context:0,act:2,action:[1,7],mani:5,disabl:[1,7],directli:[3,4,6],modul:0,wait_for_text:[1,7],visibl:[1,7],your:[3,4,6],select:[0,1,7],backend:2,from:[1,2,3,4,6,7],ondemand:[3,4],script:[3,4,6,2],teardown:[3,4],top:[1,7],support:2,custom:[3,4],sqlite3_provid:[0,2],trigger:[1,7],until:[1,7],conduit:2,csvprovid:2,assert:[3,4],provid:[0,5,2],particular:[1,7],happili:[1,7],is_element_pres:[1,7],present:[1,7],fly:5,runtest:[3,4],none:5,look:[1,7],"default":[3,4],setup:[3,4],tree:[1,7],displai:[1,7],charact:5,project:[3,4,6],directori:2,can:2,djangoprovid:2,purpos:2,fixtur:[3,4],control:0,want:[3,4,6],helper:[1,7],share:[3,4],indic:0,wait_for_vis:[1,7],download:[3,4],tag:[3,4],ini:3,conf:3,tabl:0,element:[0,1,7],make:[1,7],webdriv:[0,4,7],how:5,teardown_method:4,sqlite3:2,instead:[3,1,4,7,6],you:[3,4,6],csv:2,updat:[3,4],sure:[1,7],get_random_us:2,"class":[0,1,2,3,4,6,7],oracl:2,ness:[1,7],lab:[3,4],driven:2,dbprovid:2,rais:[1,7],user:2,letter:5,random_str:5,data:[5,2],saunter:[0,1,2,3,4,5,6,7],wait_for_element_pres:1,also:[3,1,4,7],inherit:[3,4,6],django:2,elementvisiblitytimeout:[1,7],log:[3,4],thi:[0,1,3,4,6,7],time:[1,7],model:2,usual:[3,4]},objtypes:{"0":"py:module","1":"py:attribute","2":"py:method","3":"py:class","4":"py:function"},titles:["Py.Saunter","Pages","saunter.providers","saunter.testcase.remotecontrol","saunter.testcase.webdriver","saunter.generators","saunter.testcase.base","Pages"],objnames:{"0":["py","module","Python module"],"1":["py","attribute","Python attribute"],"2":["py","method","Python method"],"3":["py","class","Python class"],"4":["py","function","Python function"]},filenames:["index","po/remotecontrol","providers","testcase/remotecontrol","testcase/webdriver","generators","testcase/base","po/webdriver"]}) -------------------------------------------------------------------------------- /docs/build/html/testcase/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | saunter.testcase.base — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |

saunter.testcase.base

59 |

You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead.

60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 |
68 |

Previous topic

69 |

Pages

71 |

Next topic

72 |

saunter.generators

74 |

This Page

75 | 79 | 91 | 92 |
93 |
94 |
95 |
96 | 114 | 118 | 119 | -------------------------------------------------------------------------------- /docs/build/html/testcase/remotecontrol.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | saunter.testcase.remotecontrol — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |

saunter.testcase.remotecontrol

59 |

You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead.

60 |
61 |

SaunterTestCase

62 |
63 |
64 | class saunter.testcase.remotecontrol.SaunterTestCase(methodName='runTest')
65 |

Parent class of all script classes used for custom asserts (usually ‘soft’ asserts) and shared fixture setup 66 | and teardown

67 |
68 |
69 | setUp()
70 |

Default setup method for all scripts. Connects either to the RC server configured in conf/selenium.ini 71 | or to Sauce Labs OnDemand

72 |
73 | 74 |
75 |
76 | tearDown()
77 |

Default teardown method for all scripts. If run through Sauce Labs OnDemand, the job name, status and tags 78 | are updated. Also the video and server log are downloaded if so configured.

79 |
80 | 81 |
82 | 83 |
84 |
85 | 86 | 87 |
88 |
89 |
90 |
91 |
92 |

Table Of Contents

93 | 99 | 100 |

Previous topic

101 |

Py.Saunter

103 |

Next topic

104 |

Pages

106 |

This Page

107 | 111 | 123 | 124 |
125 |
126 |
127 |
128 | 146 | 150 | 151 | -------------------------------------------------------------------------------- /docs/build/html/testcase/webdriver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | saunter.testcase.webdriver — Py.Saunter 0.47 documentation 12 | 13 | 14 | 15 | 16 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 51 | 52 |
53 |
54 |
55 |
56 | 57 |
58 |

saunter.testcase.webdriver

59 |

You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead.

60 |
61 |

SaunterTestCase

62 |
63 |
64 | class saunter.testcase.webdriver.SaunterTestCase(methodName='runTest')
65 |

Parent class of all script classes used for custom asserts (usually ‘soft’ asserts) and shared fixture setup 66 | and teardown

67 |
68 |
69 | setup_method(method)
70 |

Parent class of all script classes used for custom asserts (usually ‘soft’ asserts) and shared fixture setup 71 | and teardown

72 |
73 | 74 |
75 |
76 | teardown_method(method)
77 |

Default teardown method for all scripts. If run through Sauce Labs OnDemand, the job name, status and tags 78 | are updated. Also the video and server log are downloaded if so configured.

79 |
80 | 81 |
82 | 83 |
84 |
85 | 86 | 87 |
88 |
89 |
90 |
91 |
92 |

Table Of Contents

93 | 99 | 100 |

Previous topic

101 |

Pages

103 |

Next topic

104 |

Pages

106 |

This Page

107 | 111 | 123 | 124 |
125 |
126 |
127 |
128 | 146 | 150 | 151 | -------------------------------------------------------------------------------- /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% source 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PySaunter.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PySaunter.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Py.Saunter documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Sep 8 12:12:06 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import os 15 | import sys 16 | 17 | os.environ['DOCGENERATION'] = "true" 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('../../')) 23 | 24 | # -- General configuration ----------------------------------------------------- 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be extensions 30 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 31 | extensions = ['sphinx.ext.autodoc'] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ['_templates'] 35 | 36 | # The suffix of source filenames. 37 | source_suffix = '.rst' 38 | 39 | # The encoding of source files. 40 | #source_encoding = 'utf-8-sig' 41 | 42 | # The master toctree document. 43 | master_doc = 'index' 44 | 45 | # General information about the project. 46 | project = u'Py.Saunter' 47 | copyright = u'2011, Element 34' 48 | 49 | # The version info for the project you're documenting, acts as replacement for 50 | # |version| and |release|, also used in various other places throughout the 51 | # built documents. 52 | # 53 | # The short X.Y version. 54 | version = '0.47' 55 | # The full version, including alpha/beta/rc tags. 56 | release = '0.47' 57 | 58 | # The language for content autogenerated by Sphinx. Refer to documentation 59 | # for a list of supported languages. 60 | #language = None 61 | 62 | # There are two options for replacing |today|: either, you set today to some 63 | # non-false value, then it is used: 64 | #today = '' 65 | # Else, today_fmt is used as the format for a strftime call. 66 | #today_fmt = '%B %d, %Y' 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | exclude_patterns = [] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all documents. 73 | #default_role = None 74 | 75 | # If true, '()' will be appended to :func: etc. cross-reference text. 76 | #add_function_parentheses = True 77 | 78 | # If true, the current module name will be prepended to all description 79 | # unit titles (such as .. function::). 80 | #add_module_names = True 81 | 82 | # If true, sectionauthor and moduleauthor directives will be shown in the 83 | # output. They are ignored by default. 84 | #show_authors = False 85 | 86 | # The name of the Pygments (syntax highlighting) style to use. 87 | pygments_style = 'sphinx' 88 | 89 | # A list of ignored prefixes for module index sorting. 90 | #modindex_common_prefix = [] 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'PySaunterdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | # The paper size ('letter' or 'a4'). 176 | #latex_paper_size = 'letter' 177 | 178 | # The font size ('10pt', '11pt' or '12pt'). 179 | #latex_font_size = '10pt' 180 | 181 | # Grouping the document tree into LaTeX files. List of tuples 182 | # (source start file, target name, title, author, documentclass [howto/manual]). 183 | latex_documents = [('index', 'PySaunter.tex', u'Py.Saunter Documentation', u'Element 34', 'manual')] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'pysaunter', u'Py.Saunter Documentation', 215 | [u'Element 34'], 1) 216 | ] 217 | -------------------------------------------------------------------------------- /docs/source/generators.rst: -------------------------------------------------------------------------------- 1 | saunter.generators 2 | ================== 3 | 4 | Generators are useful for creating data on the fly. 5 | 6 | .. automodule:: saunter.generators.string_data 7 | :members: -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Py.Saunter documentation master file, created by 2 | sphinx-quickstart on Thu Sep 8 12:12:06 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Py.Saunter 7 | ========== 8 | 9 | This is the class documentation for Py.Saunter. For the larger documentation set in context, see the `Py.Saunter page `_ at Element 34. 10 | 11 | Remote Control: 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | testcase/remotecontrol 17 | po/remotecontrol 18 | 19 | WebDriver: 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | 24 | testcase/webdriver 25 | po/webdriver 26 | 27 | Common: 28 | 29 | .. toctree:: 30 | :maxdepth: 2 31 | 32 | testcase/base 33 | generators 34 | providers 35 | 36 | Indices and tables 37 | ================== 38 | 39 | * :ref:`genindex` 40 | * :ref:`modindex` 41 | * :ref:`search` 42 | 43 | -------------------------------------------------------------------------------- /docs/source/po/remotecontrol.rst: -------------------------------------------------------------------------------- 1 | Pages 2 | ===== 3 | 4 | .. automodule:: saunter.po.remotecontrol.page 5 | :members: Page 6 | 7 | Elements 8 | ======== 9 | 10 | .. automodule:: saunter.po.remotecontrol.element 11 | :members: Element 12 | 13 | .. automodule:: saunter.po.remotecontrol.checkbox 14 | :members: Checkbox 15 | 16 | .. automodule:: saunter.po.remotecontrol.select 17 | :members: Select 18 | 19 | .. automodule:: saunter.po.remotecontrol.text 20 | :members: Text -------------------------------------------------------------------------------- /docs/source/po/webdriver.rst: -------------------------------------------------------------------------------- 1 | Pages 2 | ===== 3 | 4 | .. automodule:: saunter.po.webdriver.page 5 | :members: Page 6 | 7 | Elements 8 | ======== 9 | 10 | .. automodule:: saunter.po.webdriver.element 11 | :members: Element 12 | 13 | .. automodule:: saunter.po.webdriver.select 14 | :members: Select 15 | 16 | .. automodule:: saunter.po.webdriver.text 17 | :members: Text -------------------------------------------------------------------------------- /docs/source/providers.rst: -------------------------------------------------------------------------------- 1 | saunter.providers 2 | ================= 3 | 4 | Providers can insert data into scripts from canned resources, or act as conduits into the backend for verification purposes. 5 | 6 | .. automodule:: saunter.providers.csv_provider 7 | :members: CSVProvider 8 | 9 | .. automodule:: saunter.providers.django_provider 10 | :members: DjangoProvider 11 | 12 | .. automodule:: saunter.providers.sqlite3_provider 13 | :members: DBProvider -------------------------------------------------------------------------------- /docs/source/testcase/base.rst: -------------------------------------------------------------------------------- 1 | saunter.testcase.base 2 | ===================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.base 7 | :members: BaseTestCase -------------------------------------------------------------------------------- /docs/source/testcase/remotecontrol.rst: -------------------------------------------------------------------------------- 1 | saunter.testcase.remotecontrol 2 | ============================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.remotecontrol 7 | :members: SaunterTestCase -------------------------------------------------------------------------------- /docs/source/testcase/webdriver.rst: -------------------------------------------------------------------------------- 1 | saunter.testcase.webdriver 2 | ========================== 3 | 4 | You likely do not want your scripts to inherit from this class directly. Instead, subclass it in your project and use that instead. 5 | 6 | .. automodule:: saunter.testcase.webdriver 7 | :members: SaunterTestCase -------------------------------------------------------------------------------- /saunter/ConfigWrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ============= 16 | ConfigWrapper 17 | ============= 18 | """ 19 | import os 20 | import os.path 21 | import sys 22 | import yaml 23 | 24 | 25 | class ConfigWrapper(object): 26 | """ 27 | Singleton reference to the config information 28 | """ 29 | # singleton 30 | _instance = None 31 | 32 | def __new__(cls, *args, **kwargs): 33 | if not cls._instance: 34 | cls._instance = super(ConfigWrapper, cls).__new__(cls, *args, **kwargs) 35 | cls._instance._data = {} 36 | return cls._instance 37 | 38 | def __str__(self): 39 | return yaml.dump(self._data, default_flow_style=False) 40 | 41 | def __getitem__(self, key): 42 | return self._data[key] 43 | 44 | def __setitem__(self, key, value): 45 | self._data[key] = value 46 | 47 | def __contains__(self, item): 48 | if item in self._data: 49 | return True 50 | return False 51 | 52 | def configure(self, config="saunter.yaml"): 53 | if not os.path.exists(os.path.join("conf", config)): 54 | print("Could not find %s; are you sure you remembered to create one?" % os.path.join("conf", config)) 55 | sys.exit(1) 56 | 57 | # this should exist since configure() is only called in main.py 58 | config_dir = os.path.join(self._data["saunter"]["base"], "conf") 59 | for root, dirs, files in os.walk(config_dir): 60 | for f in files: 61 | if f.endswith(".yaml"): 62 | file_path = os.path.join(root, f) 63 | relative_path = file_path[len(config_dir) + 1:] 64 | head, tail = os.path.split(relative_path) 65 | section_name = f[:-5] 66 | o = open(file_path, "r") 67 | if head: 68 | if head not in self._data.keys(): 69 | self._data[head] = {} 70 | if section_name in self._data[head]: 71 | self._data[head][section_name] = dict(self._data[head][section_name].items() + yaml.load(o).items()) 72 | else: 73 | self._data[head][section_name] = yaml.load(o) 74 | else: 75 | if section_name in self._data: 76 | self._data[section_name] = dict(self._data[section_name].items() + yaml.load(o).items()) 77 | else: 78 | self._data[section_name] = yaml.load(o) 79 | o.close() 80 | -------------------------------------------------------------------------------- /saunter/SaunterWebDriver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import saunter.exceptions 16 | from selenium import webdriver 17 | from selenium.common.exceptions import NoSuchElementException 18 | from saunter.web_element import WebElement 19 | 20 | 21 | class SaunterWebDriver(webdriver.Remote): 22 | def __init__(self, **kwargs): 23 | super(SaunterWebDriver, self).__init__(**kwargs) 24 | 25 | def find_element_by_locator(self, locator): 26 | locator_type = locator[:locator.find("=")] 27 | if locator_type == "": 28 | raise saunter.exceptions.InvalidLocatorString(locator) 29 | locator_value = locator[locator.find("=") + 1:] 30 | if locator_type == 'class': 31 | return WebElement(self.find_element_by_class_name(locator_value)) 32 | elif locator_type == 'css': 33 | return WebElement(self.find_element_by_css_selector(locator_value)) 34 | elif locator_type == 'id': 35 | return WebElement(self.find_element_by_id(locator_value)) 36 | elif locator_type == 'link': 37 | return WebElement(self.find_element_by_link_text(locator_value)) 38 | elif locator_type == 'name': 39 | return WebElement(self.find_element_by_name(locator_value)) 40 | elif locator_type == 'plink': 41 | return WebElement(self.find_element_by_partial_link_text(locator_value)) 42 | elif locator_type == 'tag': 43 | return WebElement(self.find_element_by_tag_name(locator_value)) 44 | elif locator_type == 'xpath': 45 | return WebElement(self.find_element_by_xpath(locator_value)) 46 | else: 47 | raise saunter.exceptions.InvalidLocatorString(locator) 48 | 49 | def find_elements_by_locator(self, locator): 50 | locator_type = locator[:locator.find("=")] 51 | if locator_type == "": 52 | raise saunter.exceptions.InvalidLocatorString(locator) 53 | locator_value = locator[locator.find("=") + 1:] 54 | if locator_type == 'class': 55 | elements = self.find_elements_by_class_name(locator_value) 56 | elif locator_type == 'css': 57 | elements = self.find_elements_by_css_selector(locator_value) 58 | elif locator_type == 'id': 59 | elements = self.find_elements_by_id(locator_value) 60 | elif locator_type == 'link': 61 | elements = self.find_elements_by_link_text(locator_value) 62 | elif locator_type == 'name': 63 | elements = self.find_elements_by_name(locator_value) 64 | elif locator_type == 'plink': 65 | elements = self.find_elements_by_partial_link_text(locator_value) 66 | elif locator_type == 'tag': 67 | elements = self.find_elements_by_tag_name(locator_value) 68 | elif locator_type == 'xpath': 69 | elements = self.find_elements_by_xpath(locator_value) 70 | else: 71 | raise saunter.exceptions.InvalidLocatorString(locator) 72 | 73 | return [WebElement(e) for e in elements] 74 | 75 | # @deprecated 76 | @classmethod 77 | def click(cls, locator): 78 | driver = se_wrapper().connection 79 | 80 | e = cls.find_element_by_locator(locator) 81 | e.click() 82 | 83 | def is_element_present(self, locator): 84 | try: 85 | self.find_element_by_locator(locator) 86 | return True 87 | except NoSuchElementException: 88 | return False 89 | 90 | def is_visible(self, locator): 91 | return self.find_element_by_locator(locator).is_displayed() 92 | -------------------------------------------------------------------------------- /saunter/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | __version__ = '2.0.0' 16 | __all__ = ['generators', 'po', 'providers', 'testcase'] 17 | -------------------------------------------------------------------------------- /saunter/_defaults/conf/browsers/browser.yaml.default: -------------------------------------------------------------------------------- 1 | %YAML 1.3 2 | --- 3 | type: firefox 4 | 5 | # profiles 6 | profiles: 7 | darwin: 8 | profile: 9 | 10 | # sauce labs 11 | sauce labs: 12 | ondemand: false 13 | os: 14 | version: 15 | artifacts: 16 | video: false 17 | log: false 18 | images: false 19 | selenium-version: 20 | sauce_connect: true 21 | 22 | # filtering of nodes with grid 23 | grid filters: 24 | platform: mac 25 | version: 23.0.1 -------------------------------------------------------------------------------- /saunter/_defaults/conf/sauce labs.yaml.default: -------------------------------------------------------------------------------- 1 | %YAML 1.3 2 | --- 3 | username: 4 | key: 5 | -------------------------------------------------------------------------------- /saunter/_defaults/conf/saunter.ini.default: -------------------------------------------------------------------------------- 1 | # This config file is divided into sections based largely on the logical grouping of the 2 | # options contained. 3 | 4 | [Saunter] 5 | # With this true, Saunter will take a screenshot after most interactions with the 6 | # application such as click, submit, type, etc. 7 | take_screenshots: false 8 | 9 | # If you are using Jenkins with the JUnit Attachments plugin, set this to true to 10 | # attach screenshots to the test results 11 | jenkins: false 12 | 13 | [Selenium] 14 | # Where is the server that Saunter will connect to 15 | server_host: localhost 16 | server_port: 4444 17 | 18 | # The browser for this run. If running a mix of both RC and WebDriver, use the RC format 19 | # of *browser. If just using WebDriver the * can be skipped 20 | browser: *firefox 21 | 22 | # The first URL that is going to be opened in the browser 23 | base_url: https://change_to_your_url 24 | 25 | # To drive the chrome browser with WebDriver you need the external chromedriver. And Saunter needs 26 | # to know where to find it. 27 | chromedriver_path: /path/to/chromedriver 28 | 29 | # The timeout that the server will use when trying to do things like page loads. In seconds. 30 | timeout: 30 31 | 32 | [SauceLabs] 33 | # Should the job be run in the Sauce Labs OnDemand service 34 | ondemand: false 35 | 36 | # There are a number of artifacts available after the run. If set to true they are put 37 | # in the logs directory 38 | get_video: true 39 | get_log: true 40 | 41 | # Your Sauce Labs username and API key 42 | username: your_username 43 | key: your-key 44 | 45 | # These can likely just be left alone, but in case the Sauce entry point changes they are 46 | # here 47 | server_host: ondemand.saucelabs.com 48 | server_port: 80 49 | 50 | # Which OS/Browser/Version do you want to run in OnDemand. Much like with the browser key 51 | # in the [Selenium] section, you can use the RC format if you want. Saunter will convert 52 | # it to a WebDriver capability 53 | os: Windows 2003 54 | browser: *firefox 55 | browser_version: 3.6. 56 | 57 | # if you want to specific a version of Se to use in the Sauce cloud 58 | #selenium-version: 2.0.32 59 | 60 | # Extra information added to a SauceLabs job - optional 61 | #[SauceLabs CustomData] 62 | #orange: peel 63 | 64 | [Django] 65 | # Saunter can interact with Django models if it knows where they are 66 | installation: /path/to/django/install 67 | app: app_name 68 | 69 | [Grid] 70 | use_grid: false 71 | type: selenium 72 | platform: linux 73 | browser_version: 13.0.1 74 | 75 | # Site-specific options should go in their own section. 76 | #[YourCompany] 77 | #some_option: some_value 78 | -------------------------------------------------------------------------------- /saunter/_defaults/conf/saunter.yaml.default: -------------------------------------------------------------------------------- 1 | %YAML 1.3 2 | --- 3 | default_browser: rabbit 4 | 5 | ci_type: jenkins 6 | 7 | screenshots: 8 | on_failure: true 9 | on_error: true 10 | on_finish: false -------------------------------------------------------------------------------- /saunter/_defaults/conf/selenium.yaml.default: -------------------------------------------------------------------------------- 1 | %YAML 1.3 2 | --- 3 | timeout: 30 4 | 5 | executor: 6 | host: localhost 7 | port: 4444 8 | is grid: false 9 | 10 | # proxy 11 | proxy: 12 | url: 13 | type: -------------------------------------------------------------------------------- /saunter/_defaults/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import os.path 17 | import py 18 | import sys 19 | import random 20 | import saunter.saucelabs 21 | import saunter.ConfigWrapper 22 | 23 | def pytest_configure(config): 24 | cwd = os.getcwd() 25 | sys.path.append(os.path.join(cwd, "modules")) 26 | saunter_config = saunter.ConfigWrapper.ConfigWrapper() 27 | saunter_config["saunter"] = {} 28 | saunter_config["saunter"]["base"] = cwd 29 | saunter_config["saunter"]["log_dir"] = os.environ["SAUNTER_LOG_DIR"] 30 | saunter_config.configure() 31 | 32 | # create the proxy instances we'll need; one per instance 33 | saunter_config['saunter']['proxies'] = [] 34 | if saunter_config['selenium']['proxy']['url'] != '' and saunter_config['selenium']['proxy']['type'].lower() == 'browsermob': 35 | from browsermobproxy import Client 36 | for x in range(0, int(os.environ["SAUNTER_PARALLEL"])): 37 | saunter_config['saunter']['proxies'].append(Client(saunter_config['selenium']['proxy']['url'])) 38 | 39 | def pytest_unconfigure(config): 40 | saunter_config = saunter.ConfigWrapper.ConfigWrapper() 41 | for proxy in saunter_config['saunter']['proxies']: 42 | proxy.close() 43 | 44 | # Comment or remove to disable auto screenshotting on error 45 | def pytest_runtest_call(item, __multicall__): 46 | try: 47 | __multicall__.execute() 48 | except Exception as e: 49 | if hasattr(item.parent.obj, 'driver') or hasattr(item.parent.obj, 'selenium'): 50 | item.parent.obj.take_named_screenshot('exception') 51 | raise 52 | 53 | def pytest_runtest_makereport(__multicall__, item, call): 54 | if call.when == "call": 55 | try: 56 | if len(item.parent.obj.verificationErrors) != 0: 57 | if call.excinfo: 58 | bits = call.excinfo.exconly().split(':') 59 | raise AssertionError({bits[0]: call.excinfo.exconly()[len(bits[0]) + 2:], "Verification Failures": item.parent.obj.verificationErrors}) 60 | else: 61 | raise AssertionError(item.parent.obj.verificationErrors) 62 | except AssertionError: 63 | call.excinfo = py.code.ExceptionInfo() 64 | 65 | report = __multicall__.execute() 66 | 67 | item.outcome = report.outcome 68 | 69 | if call.when == "call": 70 | if hasattr(item.parent.obj, 'config') and item.parent.obj.config["browsers"][item.parent.obj.config["saunter"]["default_browser"]]["sauce labs"]["ondemand"]: 71 | s = saunter.saucelabs.SauceLabs(item.parent.obj.config["sauce labs"]["username"], item.parent.obj.config["sauce labs"]["key"]) 72 | s.update_job_from_item(item) 73 | 74 | return report 75 | 76 | def pytest_runtest_teardown(__multicall__, item): 77 | __multicall__.execute() 78 | 79 | if hasattr(item.parent.obj, 'config') and item.parent.obj.config["browsers"][item.parent.obj.config["saunter"]["default_browser"]]["sauce labs"]["ondemand"]: 80 | s = saunter.saucelabs.SauceLabs(item.parent.obj.config["sauce labs"]["username"], item.parent.obj.config["sauce labs"]["key"]) 81 | s.update_job_from_item(item) 82 | 83 | def pytest_collection_modifyitems(items): 84 | random.shuffle(items) -------------------------------------------------------------------------------- /saunter/_defaults/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files=*.py 3 | python_classes=Check 4 | python_functions=test -------------------------------------------------------------------------------- /saunter/_defaults/tailored/page.py: -------------------------------------------------------------------------------- 1 | from saunter.po.webdriver.page import Page as SaunterPage 2 | 3 | from saunter.po import timeout_seconds 4 | from selenium.webdriver.support.ui import WebDriverWait 5 | from selenium.common.exceptions import TimeoutException 6 | from selenium.common.exceptions import NoSuchElementException 7 | import time 8 | 9 | 10 | class Page(SaunterPage): 11 | pass 12 | -------------------------------------------------------------------------------- /saunter/_defaults/tailored/webdriver.py: -------------------------------------------------------------------------------- 1 | """ 2 | ============= 3 | WebDriver 4 | ============= 5 | """ 6 | from saunter.SaunterWebDriver import SaunterWebDriver 7 | 8 | 9 | class WebDriver(SaunterWebDriver): 10 | """ 11 | Modifications to the core WebDriver API are done in this class 12 | """ 13 | def __init__(self, **kwargs): 14 | super(WebDriver, self).__init__(**kwargs) 15 | -------------------------------------------------------------------------------- /saunter/browser.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from tailored.webdriver import WebDriver as TailoredWebDriver 3 | from saunter.exceptions import ProfileNotFound 4 | from selenium.webdriver import FirefoxProfile 5 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 6 | from saunter.matchers import Matchers 7 | 8 | capabilities_map = { 9 | "firefox": DesiredCapabilities.FIREFOX, 10 | "internet explorer": DesiredCapabilities.INTERNETEXPLORER, 11 | "internetexplorer": DesiredCapabilities.INTERNETEXPLORER, 12 | "iexplore": DesiredCapabilities.INTERNETEXPLORER, 13 | "ie": DesiredCapabilities.INTERNETEXPLORER, 14 | "chrome": DesiredCapabilities.CHROME, 15 | "opera": DesiredCapabilities.OPERA, 16 | "chrome": DesiredCapabilities.CHROME, 17 | "htmlunitjs": DesiredCapabilities.HTMLUNITWITHJS, 18 | "htmlunit": DesiredCapabilities.HTMLUNIT, 19 | "iphone": DesiredCapabilities.IPHONE, 20 | "ipad": DesiredCapabilities.IPAD, 21 | "android": DesiredCapabilities.ANDROID, 22 | "phantomjs": DesiredCapabilities.PHANTOMJS, 23 | } 24 | 25 | os_map = { 26 | "XP": "XP", 27 | "Windows 2003": "XP", 28 | "VISTA": "VISTA", 29 | "Windows 2008": "VISTA", 30 | "Linux": "LINUX", 31 | "LINUX": "LINUX", 32 | "MAC": "MAC" 33 | } 34 | 35 | class Browser(TailoredWebDriver): 36 | def __init__(self, browser_config, all_config): 37 | self.browser_config = browser_config 38 | self.config = all_config 39 | 40 | profile = None 41 | if browser_config["type"] == 'firefox': 42 | if browser_config["profiles"][sys.platform]: 43 | profile_path = os.path.join(self.config["saunter"]["base"], 'support', 'profiles', browser_config["profiles"][sys.platform]) 44 | elif browser_config["profiles"]["profile"]: 45 | profile_path = os.path.join(self.config["saunter"]["base"], 'support', 'profiles', browser_config["profiles"]["profile"]) 46 | else: 47 | profile_path = None 48 | 49 | if profile_path: 50 | if os.path.isdir(profile_path): 51 | profile = FirefoxProfile(profile_path) 52 | else: 53 | raise ProfileNotFound("Profile not found at %s" % profile_path) 54 | 55 | if browser_config["sauce labs"]["ondemand"]: 56 | desired_capabilities = { 57 | "platform": browser_config["sauce labs"]["os"], 58 | "browserName": browser_config["type"], 59 | "version": browser_config["sauce labs"]["version"], 60 | } 61 | if desired_capabilities["platform"] in os_map: 62 | desired_capabilities["platform"] = os_map[desired_capabilities["platform"]] 63 | 64 | if 'selenium version' in browser_config['sauce labs'] and \ 65 | len(browser_config['sauce labs']['selenium version']) > 0: 66 | desired_capabilities['selenium-version'] = browser['sauce labs']['selenium version'] 67 | 68 | if "disable" in self.config["sauce labs"] and self.config["sauce labs"]["disable"] is not None: 69 | if "record video" in self.config["sauce labs"]["disable"]: 70 | if self.config["sauce labs"]["disable"]["record video"] == True: 71 | desired_capabilities['record-video'] = False 72 | if "upload video on pass" in self.config["sauce labs"]["disable"]: 73 | if self.config["sauce labs"]["disable"]["upload video on pass"] == True: 74 | desired_capabilities['video-upload-on-pass'] = False 75 | if "step screenshots" in self.config["sauce labs"]["disable"]: 76 | if self.config["sauce labs"]["disable"]["step screenshots"] == True: 77 | desired_capabilities['record-screenshots'] = False 78 | if "sauce advisor" in self.config["sauce labs"]["disable"]: 79 | if self.config["sauce labs"]["disable"]["sauce advisor"] == True: 80 | desired_capabilities['sauce-advisor'] = False 81 | 82 | if "enable" in self.config["sauce labs"] and self.config["sauce labs"]["enable"] is not None: 83 | if "source capture" in self.config["sauce labs"]["enable"]: 84 | if self.config["sauce labs"]["enable"]["source capture"] == True: 85 | desired_capabilities['source capture'] = True 86 | if "error screenshots" in self.config["sauce labs"]["enable"]: 87 | if self.config["sauce labs"]["enable"]["error screenshots"] == True: 88 | desired_capabilities['webdriver.remote.quietExceptions'] = True 89 | 90 | command_executor = "http://%s:%s@ondemand.saucelabs.com:80/wd/hub" % (self.config["sauce labs"]["username"], self.config["sauce labs"]["key"]) 91 | else: 92 | desired_capabilities = capabilities_map[browser_config["type"]] 93 | 94 | if 'type' in self.config['selenium']['proxy'] and \ 95 | self.config['selenium']['proxy']['type'] is not None and \ 96 | self.config['selenium']['proxy']['type'].lower() == "browsermob": 97 | 98 | self.proxy = self.config['saunter']['proxies'].pop() 99 | self.proxy.add_to_webdriver_capabilities(desired_capabilities) 100 | 101 | if "is grid" in self.config["selenium"] and self.config["selenium"]["executor"]["is grid"]: 102 | if browser_config["grid filters"]["platform"]: 103 | desired_capabilities["platform"] = browser_config["grid filters"]["platform"].upper() 104 | if browser_config["grid filters"]["version"]: 105 | desired_capabilities["platform"] = str(browser_config["grid filters"]["version"]) 106 | 107 | command_executor = "http://%s:%s/wd/hub" % (self.config["selenium"]["executor"]["host"], self.config["selenium"]["executor"]["port"]) 108 | 109 | # print(desired_capabilities) 110 | self.driver = TailoredWebDriver(desired_capabilities=desired_capabilities, command_executor=command_executor, browser_profile=profile) 111 | -------------------------------------------------------------------------------- /saunter/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ============== 16 | SyncExceptions 17 | ============== 18 | """ 19 | 20 | 21 | class SaunterExceptions(Exception): 22 | """Base class for exceptions in this module.""" 23 | pass 24 | 25 | 26 | class ElementNotFound(SaunterExceptions): 27 | def _get_message(self): 28 | return self._message 29 | 30 | def _set_message(self, message): 31 | self._message = message 32 | message = property(_get_message, _set_message) 33 | 34 | 35 | class ElementVisiblityTimeout(SaunterExceptions): 36 | def _get_message(self): 37 | return self._message 38 | 39 | def _set_message(self, message): 40 | self._message = message 41 | message = property(_get_message, _set_message) 42 | 43 | 44 | class ElementTextTimeout(SaunterExceptions): 45 | def _get_message(self): 46 | return self._message 47 | 48 | def _set_message(self, message): 49 | self._message = message 50 | message = property(_get_message, _set_message) 51 | 52 | 53 | class InvalidLocatorString(SaunterExceptions): 54 | def _get_message(self): 55 | return self._message 56 | 57 | def _set_message(self, message): 58 | self._message = message 59 | message = property(_get_message, _set_message) 60 | 61 | 62 | class WindowNotFound(SaunterExceptions): 63 | def _get_message(self): 64 | return self._message 65 | 66 | def _set_message(self, message): 67 | self._message = message 68 | message = property(_get_message, _set_message) 69 | 70 | 71 | class ProfileNotFound(SaunterExceptions): 72 | def _get_message(self): 73 | return self._message 74 | 75 | def _set_message(self, message): 76 | self._message = message 77 | message = property(_get_message, _set_message) 78 | 79 | 80 | class ProviderException(SaunterExceptions): 81 | def _get_message(self): 82 | return self._message 83 | 84 | def _set_message(self, message): 85 | self._message = message 86 | message = property(_get_message, _set_message) 87 | -------------------------------------------------------------------------------- /saunter/generators/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /saunter/generators/string_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | =========== 16 | string_data 17 | =========== 18 | """ 19 | import random 20 | import string 21 | 22 | 23 | def random_string(random_length=None): 24 | """ 25 | A generator for creating random string data of letters plus ' ' (whitespace) 26 | 27 | :params random_length: how many characters of random string data. if not provided, will be between 1 - 30 28 | :returns: String 29 | """ 30 | choices = string.letters + ' ' 31 | text = [] 32 | if not random_length: 33 | random_length = random.randint(1, 30) 34 | for x in range(random_length): 35 | text.append(random.choice(choices)) 36 | return "".join(text) 37 | -------------------------------------------------------------------------------- /saunter/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import datetime 16 | import pytest 17 | import marks 18 | import argparse 19 | import os 20 | import os.path 21 | import re 22 | import shutil 23 | import sys 24 | import time 25 | import tempfile 26 | import types 27 | import saunter 28 | import yaml 29 | saunter_installed_at = os.path.dirname(saunter.__file__) 30 | cwd = os.getcwd() 31 | 32 | 33 | def new(): 34 | # conf 35 | if not os.path.isdir(os.path.join(cwd, "conf")): 36 | os.mkdir(os.path.join(cwd, "conf")) 37 | if not os.path.isfile(os.path.join(cwd, "conf", "saunter.yaml.default")): 38 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "conf", "saunter.yaml.default"), 39 | os.path.join(cwd, "conf")) 40 | if not os.path.isfile(os.path.join(cwd, "conf", "selenium.yaml.default")): 41 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "conf", "selenium.yaml.default"), 42 | os.path.join(cwd, "conf")) 43 | if not os.path.isfile(os.path.join(cwd, "conf", "sauce labs.yaml.default")): 44 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "conf", "sauce labs.yaml.default"), 45 | os.path.join(cwd, "conf")) 46 | 47 | # browsers 48 | if not os.path.isdir(os.path.join(cwd, "conf", "browers")): 49 | os.mkdir(os.path.join(cwd, "conf", "browsers")) 50 | if not os.path.isfile(os.path.join(cwd, "conf", "browsers", "browser.yaml.default")): 51 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "conf", "browsers", "browser.yaml.default"), 52 | os.path.join(cwd, "conf", "browsers")) 53 | 54 | # log 55 | if not os.path.isdir(os.path.join(cwd, "logs")): 56 | os.mkdir(os.path.join(cwd, "logs")) 57 | 58 | # modules 59 | if not os.path.isdir(os.path.join(cwd, "modules")): 60 | os.mkdir(os.path.join(cwd, "modules")) 61 | 62 | # modules/pages 63 | if not os.path.isdir(os.path.join(cwd, "modules", "pages")): 64 | os.mkdir(os.path.join(cwd, "modules", "pages")) 65 | 66 | # pages is a package 67 | if not os.path.isfile(os.path.join(cwd, "modules", "pages", "__init__.py")): 68 | f = open(os.path.join(cwd, "modules", "pages", "__init__.py"), "w") 69 | f.close() 70 | 71 | # modules/providers 72 | if not os.path.isdir(os.path.join(cwd, "modules", "providers")): 73 | os.mkdir(os.path.join(cwd, "modules", "providers")) 74 | 75 | # providers is a package 76 | if not os.path.isfile(os.path.join(cwd, "modules", "providers", "__init__.py")): 77 | f = open(os.path.join(cwd, "modules", "providers", "__init__.py"), "w") 78 | f.close() 79 | 80 | # modules/pages 81 | if not os.path.isdir(os.path.join(cwd, "modules", "tailored")): 82 | os.mkdir(os.path.join(cwd, "modules", "tailored")) 83 | 84 | # pages is a package 85 | if not os.path.isfile(os.path.join(cwd, "modules", "tailored", "__init__.py")): 86 | f = open(os.path.join(cwd, "modules", "tailored", "__init__.py"), "w") 87 | f.close() 88 | 89 | if not os.path.isfile(os.path.join(cwd, "modules", "tailored", "webdriver.py")): 90 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "tailored", "webdriver.py"), os.path.join(cwd, "modules", "tailored")) 91 | 92 | if not os.path.isfile(os.path.join(cwd, "modules", "tailored", "page.py")): 93 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "tailored", "page.py"), os.path.join(cwd, "modules", "tailored")) 94 | 95 | # scripts 96 | if not os.path.isdir(os.path.join(cwd, "scripts")): 97 | os.mkdir(os.path.join(cwd, "scripts")) 98 | 99 | # support 100 | if not os.path.isdir(os.path.join(cwd, "support")): 101 | os.mkdir(os.path.join(cwd, "support")) 102 | 103 | # support/csv 104 | if not os.path.isdir(os.path.join(cwd, "support", "csv")): 105 | os.mkdir(os.path.join(cwd, "support", "csv")) 106 | 107 | # support/db 108 | if not os.path.isdir(os.path.join(cwd, "support", "db")): 109 | os.mkdir(os.path.join(cwd, "support", "db")) 110 | 111 | # support/db 112 | if not os.path.isdir(os.path.join(cwd, "support", "files")): 113 | os.mkdir(os.path.join(cwd, "support", "files")) 114 | 115 | # misc. 116 | if not os.path.isfile(os.path.join(cwd, "conftest.py")): 117 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "conftest.py"), cwd) 118 | if not os.path.isfile(os.path.join(cwd, "pytest.ini")): 119 | shutil.copy(os.path.join(saunter_installed_at, "_defaults", "pytest.ini"), cwd) 120 | 121 | sys.exit() 122 | 123 | p = argparse.ArgumentParser() 124 | p.add_argument('--new', action='store_true', default=False, help="creates a new Saunter environment") 125 | p.add_argument('-v', action='store_true', default=None, help="increase verbosity") 126 | p.add_argument('-s', action='store_true', default=None, help="don't capture output") 127 | p.add_argument('--tb', action='store', default="native", help='traceback print mode (long/short/line/native/no)') 128 | p.add_argument('-p', action='append', default=[], help="early-load given plugin (multi-allowed)") 129 | p.add_argument('-m', action='append', default=[], help="filter based on marks") 130 | p.add_argument('-n', action='store', default=None, help="number of processes to fork") 131 | p.add_argument('--traceconfig', action='store_true', default=None, help="trace considerations of conftest.py files") 132 | p.add_argument('--pdb', action='store_true', default=None, help="start the interactive Python debugger on errors") 133 | p.add_argument('--maxfail', action='store', default=None, help="exit after first num failures or errors.") 134 | p.add_argument('--collectonly', action='store_true', default=None, help="only collect tests, don't execute them") 135 | p.add_argument('--durations', action='store', default=None, help='show N slowest setup/test durations (N=0 for all)') 136 | p.add_argument('--debug', action='store', default=None, help="store internal tracing debug information in 'pytestdebug.log'.") 137 | p.add_argument('--version', action='version', version='Saunter %s' % saunter.__version__) 138 | 139 | results = p.parse_args() 140 | 141 | # argument handling; what a mess 142 | if results.new: 143 | new() 144 | 145 | arguments = [] 146 | 147 | # argparse will take all the -m arguments and compile them into a list 148 | if len(results.m) == 1: 149 | arguments.append("-m") 150 | arguments.append(results.m[0]) 151 | elif len(results.m) > 1: 152 | for markers in results.m: 153 | arguments.append("-m") 154 | arguments.append(markers) 155 | else: 156 | arguments.append("-m") 157 | arguments.append("shallow") 158 | 159 | # this are either true or false 160 | for noneable in ['v', 's']: 161 | if noneable in results.__dict__ and results.__dict__[noneable] is not None: 162 | arguments.append("-%s" % noneable) 163 | 164 | for noneable in ['traceconfig', 'pdb', 'collectonly', "debug"]: 165 | if noneable in results.__dict__ and results.__dict__[noneable] is not None: 166 | arguments.append("--%s" % noneable) 167 | 168 | for has_value in ['maxfail', 'durations']: 169 | if has_value in results.__dict__ and results.__dict__[has_value] is not None: 170 | arguments.append("--%s=%s" % (has_value, results.__dict__[has_value][0])) 171 | # arguments.append(results.__dict__[has_value][0]) 172 | 173 | if 'n' in results.__dict__ and results.__dict__['n'] is not None: 174 | arguments.append("--dist=load") 175 | arguments.append("--tx=%s*popen" % results.__dict__['n']) 176 | os.environ["SAUNTER_PARALLEL"] = str(results.__dict__['n']) 177 | else: 178 | os.environ["SAUNTER_PARALLEL"] = "1" 179 | 180 | # plugin control 181 | if len(results.p) == 1: 182 | arguments.append("-p") 183 | arguments.append(results.p[0]) 184 | else: 185 | for p in results.p: 186 | arguments.append("-p") 187 | arguments.append(p) 188 | 189 | # import saunter.ConfigWrapper 190 | # config = saunter.ConfigWrapper.ConfigWrapper() 191 | # config["saunter"] = {} 192 | # config["saunter"]["base"] = cwd 193 | # config.configure() 194 | 195 | # logging 196 | timestamp = time.strftime("%Y-%m-%d-%H-%M-%S") 197 | saunter_log_dir = os.path.join(cwd, 'logs', timestamp) 198 | 199 | os.environ["SAUNTER_LOG_DIR"] = saunter_log_dir 200 | 201 | os.makedirs(saunter_log_dir) 202 | 203 | log_name = os.path.join(saunter_log_dir, "%s.xml" % timestamp) 204 | arguments.append('--junitxml=%s' % log_name) 205 | 206 | # config["saunter"]["log_dir"] = log_dir 207 | 208 | arguments.append('--tb=%s' % results.__dict__["tb"]) 209 | 210 | # run 211 | arguments.append("scripts") 212 | 213 | run_status = pytest.main(args=arguments, plugins=[marks.MarksDecorator()]) 214 | 215 | shutil.copy(log_name, os.path.join(cwd, 'logs', 'latest.xml')) 216 | 217 | sys.exit(run_status) 218 | -------------------------------------------------------------------------------- /saunter/po/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | ====== 17 | Common 18 | ====== 19 | """ 20 | 21 | import saunter.ConfigWrapper 22 | 23 | cf = saunter.ConfigWrapper.ConfigWrapper() 24 | cf.configure() 25 | # print(cf.__dict__) 26 | #: timeout value in s as an integer 27 | if "timeout" in cf["selenium"]: 28 | timeout_seconds = cf["selenium"]["timeout"] 29 | else: 30 | timeout_seconds = 30 31 | 32 | #: timout value in ms as an integer 33 | timeout_microseconds = timeout_seconds * 1000 34 | #: timout value in ms as a string 35 | string_timeout = str(timeout_microseconds) 36 | -------------------------------------------------------------------------------- /saunter/po/webdriver/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /saunter/po/webdriver/attribute.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ========= 16 | Attribute 17 | ========= 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | from saunter.exceptions import ElementNotFound 21 | 22 | 23 | class Attribute(Element): 24 | """ 25 | Base element class for Text fields 26 | """ 27 | def __init__(self, element, attribute): 28 | self.locator = element 29 | self.attribute = attribute 30 | 31 | def __set__(self, obj, val): 32 | pass 33 | 34 | def __get__(self, obj, cls=None): 35 | try: 36 | e = obj.driver.find_element_by_locator(self.locator) 37 | return e.get_attribute(self.attribute) 38 | except AttributeError as e: 39 | if str(e) == "'SeleniumWrapper' object has no attribute 'connection'": 40 | pass 41 | else: 42 | raise e 43 | except ElementNotFound as e: 44 | msg = "Element %s was not found. It is used in the %s page object in the %s module." % (self.locator, obj.__class__.__name__, self.__module__) 45 | raise ElementNotFound(msg) 46 | -------------------------------------------------------------------------------- /saunter/po/webdriver/checkbox.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ======== 16 | Checkbox 17 | ======== 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | 21 | 22 | class CheckBox(Element): 23 | def __set__(self, obj, val): 24 | e = obj.driver.find_element_by_locator(self.locator) 25 | current = e.is_selected() 26 | if current != val: 27 | e.click() 28 | 29 | def __get__(self, obj, cls=None): 30 | e = obj.driver.find_element_by_locator(self.locator) 31 | return e.is_selected() 32 | -------------------------------------------------------------------------------- /saunter/po/webdriver/element.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ======= 16 | Element 17 | ======= 18 | """ 19 | 20 | 21 | class Element(object): 22 | """ 23 | Top of the PO element tree 24 | """ 25 | pass 26 | -------------------------------------------------------------------------------- /saunter/po/webdriver/jquery/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Element-34/py.saunter/bdc8480b1453e082872c80d3382d42565b8ed9c0/saunter/po/webdriver/jquery/__init__.py -------------------------------------------------------------------------------- /saunter/po/webdriver/jquery/datepicker.py: -------------------------------------------------------------------------------- 1 | from saunter.po.webdriver.element import Element 2 | 3 | class NoSuchDayException(Exception): 4 | """ Custom exception for trying to select an invalid day of the month 5 | 6 | Rather than try and be clever around which days are valid in a month, we trust that 7 | the widget is populating things correctly and just blow up if you request the 4th day 8 | of the month for instance. 9 | """ 10 | pass 11 | 12 | class DatePicker(Element): 13 | """ Helper class for dealing with JQueryUI Datepicker controls/widgets/whatever """ 14 | 15 | def __set__(self, obj, val): 16 | """ Navigate through the datepicker control and select the date 17 | 18 | Using named arguments rather than try and dictate a specific date format. If 19 | someone wants to format things a specific way they can wrap this class and parse 20 | their input before passing it along here 21 | """ 22 | _how_many_clicks = 0 23 | 24 | # this is the input field that the datepicker control is attached to (in this 25 | # standalone example it doesn't make sense, but in the larger framework it does...) 26 | click_to_open = obj.driver.find_element_by_locator(self.locator) 27 | click_to_open.click() 28 | 29 | # everything is relative to this though 30 | datepicker = obj.driver.find_element_by_id('ui-datepicker-div') 31 | 32 | # is current year? 33 | year_element = datepicker.find_element_by_css_selector('.ui-datepicker-year') 34 | current_year = year_element.text 35 | year_difference = int(current_year) - val['year'] 36 | _how_many_clicks += year_difference * 12 37 | 38 | # is current month? 39 | month_element = datepicker.find_element_by_css_selector('.ui-datepicker-month') 40 | current_month = month_element.text 41 | months = ['January', 'February', 'March', 'April', 'May', 'June', 42 | 'July', 'August', 'September', 'October', 'November', 'December'] 43 | current_month_number = months.index(current_month) + 1 44 | month_difference = current_month_number - val['month'] 45 | _how_many_clicks += month_difference 46 | 47 | # navigate to correct month 48 | if _how_many_clicks != 0: 49 | if _how_many_clicks < 0: 50 | _how_many_clicks = abs(_how_many_clicks) 51 | month_mover_locator = '.ui-datepicker-next' 52 | else: 53 | month_mover_locator = '.ui-datepicker-prev' 54 | 55 | for x in range(0, _how_many_clicks): 56 | month_mover = datepicker.find_element_by_css_selector(month_mover_locator) 57 | month_mover.click() 58 | 59 | has_date = datepicker.find_elements_by_xpath('//table//a[text()="%s"]' % val['day']) 60 | if len(has_date) == 1: 61 | has_date[0].click() 62 | else: 63 | raise NoSuchDayException() 64 | 65 | def __get__(self, obj, cls=None): 66 | locator_type = locator[:locator.find("=")] 67 | if locator_type not in ['css', 'id']: 68 | raise saunter.exceptions.InvalidLocatorString(locator) 69 | 70 | locator_value = locator[locator.find("=") + 1:] 71 | if locator_type == 'css': 72 | _locator = locator_value 73 | elif locator_type == 'id': 74 | _locator = '#%s' % locator_value 75 | 76 | current_date = obj.driver.execute_script('return $(arguments[0]).datepicker("getDate");', _locator) 77 | return current_date 78 | -------------------------------------------------------------------------------- /saunter/po/webdriver/multi_select.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ====== 16 | Select 17 | ====== 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | from saunter.web_element import WebElement 21 | from saunter.SeleniumWrapper import SeleniumWrapper as wrapper 22 | from saunter.exceptions import ElementNotFound 23 | from saunter.SaunterWebDriver import SaunterWebDriver 24 | from selenium.webdriver.support.select import Select as WebDriverSelect 25 | 26 | 27 | class MultiSelect(Element, WebDriverSelect): 28 | def __get__(self, obj, cls=None): 29 | s = WebDriverSelect(obj.driver.find_element_by_locator(self.locator)) 30 | a = s.all_selected_options 31 | if len(a) == 0: 32 | return None 33 | return [x.text for x in a] 34 | 35 | def __getitem__(self, key): 36 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 37 | a = s.all_selected_options 38 | if len(a) == 0: 39 | return None 40 | selections = [x.text for x in a] 41 | return selections[key] 42 | 43 | def __delitem__(self, key): 44 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 45 | method = key[:key.find("=")] 46 | value = key[key.find("=") + 1:] 47 | if method == "value": 48 | s.deselect_by_value(value) 49 | elif method == "index": 50 | s.deselect_by_index(value) 51 | elif method == "text": 52 | s.deselect_by_visible_text(value) 53 | else: 54 | raise saunter.exceptions.InvalidLocatorString("%s is an invalid locator" % item) 55 | 56 | def __len__(self): 57 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 58 | return len(s.all_selected_options) 59 | 60 | def append(self, item): 61 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 62 | method = item[:item.find("=")] 63 | value = item[item.find("=") + 1:] 64 | if method == "value": 65 | s.select_by_value(value) 66 | elif method == "index": 67 | s.select_by_index(value) 68 | elif method == "text": 69 | s.select_by_visible_text(value) 70 | else: 71 | raise saunter.exceptions.InvalidLocatorString("%s is an invalid locator" % item) 72 | -------------------------------------------------------------------------------- /saunter/po/webdriver/number.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ====== 16 | Number 17 | ====== 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | from saunter.SeleniumWrapper import SeleniumWrapper as wrapper 21 | from saunter.exceptions import ElementNotFound 22 | from saunter.SaunterWebDriver import SaunterWebDriver 23 | 24 | 25 | class Number(Element): 26 | """ 27 | Base element class for Number fields 28 | """ 29 | def __set__(self, obj, val): 30 | e = SaunterWebDriver.find_element_by_locator(self.locator) 31 | e.send_keys(val) 32 | 33 | def __get__(self, obj, cls=None): 34 | try: 35 | e = SaunterWebDriver.find_element_by_locator(self.locator) 36 | return int(e.text) 37 | except AttributeError as e: 38 | if str(e) == "'SeleniumWrapper' object has no attribute 'connection'": 39 | pass 40 | else: 41 | raise e 42 | except ElementNotFound as e: 43 | msg = "Element %s was not found. It is used in the %s page object in the %s module." % (self.locator, obj.__class__.__name__, self.__module__) 44 | raise ElementNotFound(msg) 45 | -------------------------------------------------------------------------------- /saunter/po/webdriver/page.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ==== 16 | Page 17 | ==== 18 | """ 19 | from saunter.po import timeout_seconds 20 | from saunter.exceptions import ElementVisiblityTimeout, ElementTextTimeout 21 | import time 22 | from selenium.common.exceptions import StaleElementReferenceException 23 | import saunter.ConfigWrapper 24 | from selenium.webdriver.support.wait import WebDriverWait 25 | 26 | 27 | class Page(object): 28 | """ 29 | Top of the PO page tree 30 | """ 31 | 32 | def __init__(self, driver): 33 | self.driver = driver 34 | if not hasattr(self, 'config'): 35 | self.cf = saunter.ConfigWrapper.ConfigWrapper() 36 | self.config = self.cf 37 | 38 | if not "short_wait" in self.cf["selenium"]: 39 | setattr(self, 'short_wait', WebDriverWait(self.driver, self.cf['selenium']['timeout'] / 2)) 40 | setattr(self, 'wait', WebDriverWait(self.driver, self.cf['selenium']['timeout'])) 41 | setattr(self, 'long_wait', WebDriverWait(self.driver, self.cf['selenium']['timeout'] * 2)) 42 | 43 | def is_element_available(self, locator): 44 | """ 45 | Synchronization method for making sure the element we're looking for is not only on the page, 46 | but also visible -- since Se will happily deal with things that aren't visible. 47 | 48 | Use this instead of is_element_present most of the time. 49 | """ 50 | if self.driver.is_element_present(locator): 51 | if self.driver.is_visible(locator): 52 | return True 53 | else: 54 | return False 55 | else: 56 | return False 57 | 58 | def wait_for_available(self, locator): 59 | """ 60 | Synchronization to deal with elements that are present, and are visible 61 | 62 | :raises: ElementVisiblityTimeout 63 | """ 64 | for i in range(timeout_seconds): 65 | try: 66 | if self.is_element_available(locator): 67 | break 68 | except: 69 | pass 70 | time.sleep(1) 71 | else: 72 | raise ElementVisiblityTimeout("%s availability timed out" % locator) 73 | return True 74 | 75 | def wait_for_visible(self, locator): 76 | """ 77 | Synchronization to deal with elements that are present, but are disabled until some action 78 | triggers their visibility. 79 | 80 | :raises: ElementVisiblityTimeout 81 | """ 82 | for i in range(timeout_seconds): 83 | try: 84 | if self.driver.is_visible(locator): 85 | break 86 | except: 87 | pass 88 | time.sleep(1) 89 | else: 90 | raise ElementVisiblityTimeout("%s visibility timed out" % locator) 91 | return True 92 | 93 | def wait_for_hidden(self, locator): 94 | """ 95 | Synchronization to deal with elements that are present, but are visibility until some action 96 | triggers their hidden-ness. 97 | 98 | :raises: ElementVisiblityTimeout= 99 | """ 100 | for i in range(timeout_seconds): 101 | if self.driver.is_visible(locator): 102 | time.sleep(1) 103 | else: 104 | break 105 | else: 106 | raise ElementVisiblityTimeout("%s visibility timed out" % locator) 107 | return True 108 | 109 | def wait_for_text(self, locator, text): 110 | """ 111 | Synchronization on some text being displayed in a particular element. 112 | 113 | :raises: ElementVisiblityTimeout 114 | """ 115 | for i in range(timeout_seconds): 116 | try: 117 | e = self.driver.find_element_by_locator(locator) 118 | if e.text == text: 119 | break 120 | except: 121 | pass 122 | time.sleep(1) 123 | else: 124 | raise ElementTextTimeout("%s value timed out" % locator) 125 | return True 126 | 127 | def wait_for_value(self, locator, text): 128 | """ 129 | Synchronization on some value being set in a particular element. 130 | 131 | :raises: ElementVisiblityTimeout 132 | 133 | """ 134 | for i in range(timeout_seconds): 135 | try: 136 | e = self.driver.find_element_by_locator(locator) 137 | if e.value == text: 138 | break 139 | except: 140 | pass 141 | time.sleep(1) 142 | else: 143 | raise ElementTextTimeout("%s value timed out" % locator) 144 | return True 145 | 146 | def wait_for_value_changed(self, locator, text): 147 | e = self.driver.find_element_by_locator(locator) 148 | for i in range(timeout_seconds): 149 | try: 150 | if len(e.text.strip()) != 0 and e.text != text: 151 | return True 152 | except StaleElementReferenceException, e: 153 | e = self.driver.find_element_by_locator(locator) 154 | finally: 155 | time.sleep(1) 156 | else: 157 | raise saunter.exceptions.ElementVisiblityTimeout("%s visibility timed out" % locator) 158 | 159 | def wait_for_element_not_present(self, locator): 160 | """ 161 | Synchronization helper to wait until some element is removed from the page 162 | 163 | :raises: ElementVisiblityTimeout 164 | """ 165 | for i in range(timeout_seconds): 166 | if self.driver.is_element_present(locator): 167 | time.sleep(1) 168 | else: 169 | break 170 | else: 171 | raise ElementVisiblityTimeout("%s presence timed out" % locator) 172 | return True 173 | -------------------------------------------------------------------------------- /saunter/po/webdriver/radio.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ===== 16 | Radio 17 | ===== 18 | """ 19 | from saunter.po.webdriver.checkbox import CheckBox 20 | 21 | 22 | class Radio(Checkbox): 23 | pass -------------------------------------------------------------------------------- /saunter/po/webdriver/select.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ====== 16 | Select 17 | ====== 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | from saunter.web_element import WebElement 21 | from saunter.exceptions import ElementNotFound 22 | from saunter.SaunterWebDriver import SaunterWebDriver 23 | from selenium.webdriver.support.select import Select as WebDriverSelect 24 | import saunter.exceptions 25 | 26 | 27 | class Select(Element, WebDriverSelect): 28 | """ 29 | Base element class for Select fields 30 | """ 31 | def __set__(self, obj, val): 32 | s = WebDriverSelect(obj.driver.find_element_by_locator(self.locator)) 33 | method = val[:val.find("=")] 34 | value = val[val.find("=") + 1:] 35 | if method == "value": 36 | s.select_by_value(value) 37 | elif method == "index": 38 | s.select_by_index(value) 39 | elif method == "text": 40 | s.select_by_visible_text(value) 41 | else: 42 | raise saunter.exceptions.InvalidLocatorString(val) 43 | 44 | def __get__(self, obj, cls=None): 45 | try: 46 | s = WebDriverSelect(obj.driver.find_element_by_locator(self.locator)) 47 | e = s.first_selected_option 48 | return str(e.text) 49 | except AttributeError as e: 50 | if str(e) == "'SeleniumWrapper' object has no attribute 'connection'": 51 | pass 52 | else: 53 | raise e 54 | 55 | 56 | class Select2(Element, WebDriverSelect): 57 | def __init__(self, driver, locator): 58 | self.driver = driver 59 | self.locator = locator 60 | 61 | @property 62 | def selected(self): 63 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 64 | e = s.first_selected_option 65 | return str(e.text) 66 | 67 | @selected.setter 68 | def selected(self, val): 69 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 70 | method = val[:val.find("=")] 71 | value = val[val.find("=") + 1:] 72 | if method == "value": 73 | s.select_by_value(value) 74 | elif method == "index": 75 | s.select_by_index(value) 76 | elif method == "text": 77 | s.select_by_visible_text(value) 78 | else: 79 | raise saunter.exceptions.InvalidLocatorString(val) 80 | 81 | @property 82 | def options(self): 83 | s = WebDriverSelect(self.driver.find_element_by_locator(self.locator)) 84 | options = s.options 85 | text = [option.text.strip() for option in options] 86 | return text 87 | -------------------------------------------------------------------------------- /saunter/po/webdriver/text.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ==== 16 | Text 17 | ==== 18 | """ 19 | from saunter.po.webdriver.unicode import Unicode 20 | 21 | class Text(Unicode): 22 | def __get__(self, obj, cls=None): 23 | return str(super(Text, self).__get__(obj, cls)) 24 | -------------------------------------------------------------------------------- /saunter/po/webdriver/unicode.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ======= 16 | Unicode 17 | ======= 18 | """ 19 | from saunter.po.webdriver.element import Element 20 | from saunter.exceptions import ElementNotFound 21 | from saunter.SaunterWebDriver import SaunterWebDriver 22 | 23 | 24 | class Unicode(Element): 25 | """ 26 | Base element class for Unicode fields 27 | """ 28 | def __set__(self, obj, val): 29 | e = obj.driver.find_element_by_locator(self.locator) 30 | e.send_keys(val) 31 | 32 | def __get__(self, obj, cls=None): 33 | e = obj.driver.find_element_by_locator(self.locator) 34 | if e.tag_name in ["input", "textarea"]: 35 | return e.get_attribute("value") 36 | return e.text 37 | -------------------------------------------------------------------------------- /saunter/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /saunter/providers/couch_provider.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import couchdb.client 3 | import random 4 | import socket 5 | import saunter.ConfigWrapper 6 | from saunter.exceptions import ProviderException 7 | 8 | 9 | # as will this 10 | class CouchProvider(object): 11 | def __init__(self, database): 12 | cf = saunter.ConfigWrapper.ConfigWrapper() 13 | 14 | # read url from the config 15 | if "couchdb" in cf and "url" in cf["couch"]: 16 | url = cf["saunter"]["couchdb"]["url"] 17 | else: 18 | url = None 19 | 20 | # make sure we can connect to the server 21 | server = couchdb.client.Server(url=url) 22 | try: 23 | server.version() 24 | except socket.error: 25 | raise ProviderException('couch server not found at %s' % url) 26 | 27 | # check that the database is in the server 28 | if database not in server: 29 | raise ProviderException('database "%s" does not exist' % database) 30 | self.database = server[database] 31 | 32 | # this will live per-project in $saunter_root/lib/providers 33 | # class MySubclassedCouchProvider(CouchProvider): 34 | # def __init__(self, database): 35 | # super(MySubclassedCouchProvider, self).__init__(database) 36 | 37 | # def random_user(self): 38 | # map_fun = '''function(doc) { 39 | # emit(doc.username, null); 40 | # }''' 41 | # all_users = [user.id for user in self.database.query(map_fun=map_fun)] 42 | # random_user_id = random.choice(all_users) 43 | # return self.database.get(random_user_id) 44 | 45 | # def random_admin_user(self): 46 | # map_fun = '''function(doc) { 47 | # if (doc.role == 'admin') 48 | # emit(doc.username, null); 49 | # }''' 50 | # all_users = [user.id for user in self.database.query(map_fun=map_fun)] 51 | # random_user_id = random.choice(all_users) 52 | # return self.database.get(random_user_id) 53 | 54 | # for when I figure out how to test this stuff 55 | 56 | # if __name__ == '__main__': 57 | # # in a saunter context, this is stored in the config 58 | # config = ConfigParser.ConfigParser() 59 | # config.add_section('couchdb') 60 | # config.set('couchdb', 'url', 'http://localhost:5984/') 61 | 62 | # c = MySubclassedCouchProvider('people') 63 | 64 | # # records from couchdb are 'just' dictionaries 65 | # random_user = c.random_user() 66 | # print(random_user['username']) 67 | 68 | # random_admin_user = c.random_admin_user() 69 | # print(random_admin_user['username']) 70 | -------------------------------------------------------------------------------- /saunter/providers/csv_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ============ 16 | csv_provider 17 | ============ 18 | """ 19 | import csv 20 | import os.path 21 | import random 22 | 23 | import saunter.ConfigWrapper 24 | 25 | 26 | class CSVProvider(object): 27 | """ 28 | Provides data for either data driven scripting or as oracles from a csv file 29 | 30 | :params c: name of csv file located in support/csv directory 31 | """ 32 | def __init__(self, c): 33 | cf = saunter.ConfigWrapper.ConfigWrapper() 34 | f = os.path.join(cf["saunter"]["base"], 'support', 'csv', c) 35 | self.data = csv.DictReader(open(f, 'rU')) 36 | 37 | def randomRow(self): 38 | """ 39 | Gets a random row from the provider 40 | 41 | :returns: List 42 | """ 43 | l = [] 44 | for row in self.data: 45 | l.append(row) 46 | return random.choice(l) 47 | -------------------------------------------------------------------------------- /saunter/providers/django_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | =============== 16 | django_provider 17 | =============== 18 | """ 19 | import os 20 | import sys 21 | import saunter.ConfigWrapper 22 | 23 | 24 | class DjangoProvider(object): 25 | """ 26 | Uses Django's Models to access the database 27 | """ 28 | def __init__(self): 29 | cf = saunter.ConfigWrapper.ConfigWrapper() 30 | django_where = cf["django"]["installation"] 31 | if django_where not in sys.path: 32 | sys.path.append(django_where) 33 | 34 | django_name = cf.get("Django", "app") 35 | if not 'DJANGO_SETTINGS_MODULE' in os.environ: 36 | os.environ['DJANGO_SETTINGS_MODULE'] = "%s.settings" % django_name 37 | 38 | def get_random_user(self): 39 | """ 40 | Gets a random user from the provider 41 | 42 | :returns: Dictionary 43 | """ 44 | from provider.models import User 45 | u = User.objects.order_by('?')[0] 46 | return {"username": u.username, "password": u.password, "fullname": u.fullname} 47 | -------------------------------------------------------------------------------- /saunter/providers/mysql_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ================ 16 | mysql_provider 17 | ================ 18 | """ 19 | import mysql.connector 20 | 21 | import saunter.ConfigWrapper 22 | 23 | 24 | class DBProvider(object): 25 | """ 26 | MySQL powered provider 27 | 28 | :params db: name of db file located in support/db directory 29 | """ 30 | def __init__(self, db): 31 | try: 32 | cf = saunter.ConfigWrapper.ConfigWrapper() 33 | except: 34 | print('ooooo') 35 | 36 | self.mysql = mysql.connector.connect( 37 | user=cf["saunter"]["mysql"]["user"], 38 | password=cf["saunter"]["mysql"]["password"], 39 | host=cf["saunter"]["mysql"]["host"], 40 | database=cf["saunter"]["mysql"]["database"] 41 | ) 42 | 43 | def __del__(self): 44 | self.mysql.close() 45 | 46 | # this would sit in the client side implementation 47 | 48 | # def get_random_user(self): 49 | # """ 50 | # Gets a random user from the provider 51 | 52 | # :returns: Dictionary 53 | # """ 54 | # c = self.db.cursor() 55 | # c.execute('''SELECT username, password, fullname FROM users 56 | # WHERE rowid >= (abs(random()) % (SELECT max(rowid) FROM users)) 57 | # LIMIT 1''') 58 | # r = c.fetchone() 59 | # return {"username": r[0], "password": r[1], "fullname": r[2]} 60 | -------------------------------------------------------------------------------- /saunter/providers/sqlite3_provider.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | ================ 16 | sqlite3_provider 17 | ================ 18 | """ 19 | import os.path 20 | import sqlite3 21 | 22 | import saunter.ConfigWrapper 23 | 24 | 25 | class DBProvider(object): 26 | """ 27 | SQLite3 powered provider 28 | 29 | :params db: name of db file located in support/db directory 30 | """ 31 | def __init__(self, db): 32 | try: 33 | cf = saunter.ConfigWrapper.ConfigWrapper() 34 | except: 35 | print('ooooo') 36 | self.db = sqlite3.connect(os.path.join(cf["saunter"]["base"], 'support', 'db', db)) 37 | 38 | def __del__(self): 39 | self.db.close() 40 | 41 | def get_random_user(self): 42 | """ 43 | Gets a random user from the provider 44 | 45 | :returns: Dictionary 46 | """ 47 | c = self.db.cursor() 48 | c.execute('''SELECT username, password, fullname FROM users 49 | WHERE rowid >= (abs(random()) % (SELECT max(rowid) FROM users)) 50 | LIMIT 1''') 51 | r = c.fetchone() 52 | return {"username": r[0], "password": r[1], "fullname": r[2]} 53 | -------------------------------------------------------------------------------- /saunter/saucelabs.py: -------------------------------------------------------------------------------- 1 | from _pytest.mark import MarkInfo 2 | import json 3 | import requests 4 | import time 5 | import os.path 6 | 7 | 8 | class SauceLabs(object): 9 | def __init__(self, username, key): 10 | self.username = username 11 | self.key = key 12 | 13 | def update_job_from_item(self, item): 14 | # session couldn't be established for some reason 15 | if not hasattr(item.parent._obj.driver, "session_id"): 16 | return 17 | 18 | self.log_dir = item.parent._obj.config["saunter"]["log_dir"] 19 | 20 | j = {} 21 | 22 | # name 23 | j["name"] = item.name 24 | 25 | # result 26 | if item.outcome.lower() == 'passed': 27 | # print("pass") 28 | j["passed"] = True 29 | else: 30 | # print("fail") 31 | j["passed"] = False 32 | 33 | # tags 34 | j["tags"] = [] 35 | j["custom-data"] = {} 36 | for keyword in item.keywords: 37 | if isinstance(item.keywords[keyword], MarkInfo): 38 | # per item custom data 39 | if keyword == "saucelabs_customdata": 40 | for key, value in item.keywords[keyword].kwargs.iteritems(): 41 | j["custom-data"][key] = value 42 | # tags 43 | else: 44 | j["tags"].append(keyword) 45 | 46 | # browser custom data 47 | if "custom data" in item.parent._obj.config["browsers"][item.parent._obj.config["saunter"]["default_browser"]]["sauce labs"]: 48 | for option in item.parent._obj.config["browsers"][item.parent._obj.config["saunter"]["default_browser"]]["sauce labs"]["custom data"]: 49 | j["custom-data"][option] = item.parent._obj.config["browsers"][item.parent._obj.config["saunter"]["default_browser"]]["sauce labs"]["custom data"][option] 50 | 51 | # global custom data 52 | if "custom data" in item.parent._obj.config["sauce labs"]: 53 | for option in item.parent._obj.config["sauce labs"]["custom data"]: 54 | j["custom-data"][option] = item.parent._obj.config["sauce labs"]["custom data"][option] 55 | 56 | # build 57 | if 'build' in item.parent._obj.config['sauce labs'] and \ 58 | item.parent._obj.config['sauce labs']['build']: 59 | j["build"] = item.parent._obj.config['sauce labs']['build'] 60 | # print(json.dumps(j)) 61 | 62 | # update 63 | which_url = "https://saucelabs.com/rest/v1/%s/jobs/%s" % (self.username, item.parent._obj.driver.session_id) 64 | r = requests.put(which_url, 65 | data=json.dumps(j), 66 | headers={"Content-Type": "application/json"}, 67 | auth=(self.username, self.key)) 68 | r.raise_for_status() 69 | 70 | # if item.parent._obj.config.getboolean("SauceLabs", "get_video"): 71 | # self._fetch_sauce_artifact("video.flv") 72 | 73 | # if item.parent._obj.config.getboolean("SauceLabs", "get_log"): 74 | # self._fetch_sauce_artifact("selenium-server.log") 75 | 76 | def _fetch_sauce_artifact(self, which): 77 | sauce_session = self.sauce_session 78 | which_url = "https://saucelabs.com/rest/%s/jobs/%s/results/%s" % (self.username, self.sauce_session, which) 79 | code = 404 80 | timeout = 0 81 | while code in [401, 404]: 82 | r = requests.get(which_url, auth=(self.username, self.key)) 83 | try: 84 | code = r.status_code 85 | r.raise_for_status() 86 | except requests.exceptions.HTTPError, e: 87 | time.sleep(4) 88 | 89 | artifact = open(os.path.join(self.log_dir, which), "wb") 90 | artifact.write(r.content) 91 | artifact.close() 92 | 93 | def update_name(self, session_id, name): 94 | j = {"name": name} 95 | 96 | which_url = "https://saucelabs.com/rest/v1/%s/jobs/%s" % (self.username, session_id) 97 | r = requests.put(which_url, 98 | data=json.dumps(j), 99 | headers={"Content-Type": "application/json"}, 100 | auth=(self.username, self.key)) 101 | r.raise_for_status() 102 | -------------------------------------------------------------------------------- /saunter/testcase/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /saunter/testcase/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import time 16 | import urllib2 17 | import os 18 | import os.path 19 | import saunter.matchers as matchers 20 | 21 | 22 | class BaseTestCase(object): 23 | def assertEqual(self, first, second, msg=None): 24 | self.matchers.assert_equal(first, second, msg) 25 | 26 | def assertNotEqual(self, first, second, msg=None): 27 | self.matchers.assert_not_equal(first, second, msg) 28 | 29 | def assertTrue(self, expr, msg=None): 30 | self.matchers.assert_true(expr, msg) 31 | 32 | def assertFalse(self, expr, msg=None): 33 | self.matchers.assert_false(expr, msg) 34 | 35 | def assertIs(self, first, second, msg=None): 36 | self.matchers.assert_is(first, second, msg) 37 | 38 | def assertIsNot(self, first, second, msg=None): 39 | self.matchers.assert_is_not(first, second, msg) 40 | 41 | def assertIsNone(self, expr, msg=None): 42 | self.matchers.assert_is_none(expr, msg) 43 | 44 | def assertIsNotNone(self, expr, msg=None): 45 | self.matchers.assert_is_not_none(expr, msg) 46 | 47 | def assertIn(self, first, second, msg=None): 48 | self.matchers.assert_in(first, second, msg) 49 | 50 | def assertNotIn(self, first, second, msg=None): 51 | self.matchers.assert_not_in(first, second, msg) 52 | 53 | def assertIsInstance(self, obj, cls, msg=None): 54 | self.matchers.assert_is_instance(obj, cls, msg) 55 | 56 | def assertIsNotInstance(self, obj, cls, msg=None): 57 | self.matchers.assert_is_not_instance(obj, cls, msg) 58 | 59 | def _screenshot_prep_dirs(self): 60 | class_dir = os.path.join(os.path.join(self.config['saunter']['log_dir'], self.__class__.__name__)) 61 | if not os.path.exists(class_dir): 62 | os.makedirs(class_dir) 63 | 64 | method_dir = os.path.join(class_dir, self.current_method_name) 65 | if not os.path.exists(method_dir): 66 | os.makedirs(method_dir) 67 | 68 | return method_dir 69 | -------------------------------------------------------------------------------- /saunter/testcase/webdriver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 Element 34 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | """ 15 | =============== 16 | SaunterTestCase 17 | =============== 18 | """ 19 | import json 20 | import logging 21 | import os 22 | import os.path 23 | import requests 24 | import sys 25 | 26 | import saunter.ConfigWrapper 27 | 28 | 29 | import saunter.browser 30 | from selenium.webdriver.common.by import By 31 | from selenium.common.exceptions import WebDriverException 32 | from selenium.common.exceptions import TimeoutException 33 | from saunter.exceptions import ProfileNotFound 34 | from selenium.webdriver.remote.webelement import WebElement 35 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 36 | from saunter.testcase.base import BaseTestCase 37 | from selenium.webdriver import FirefoxProfile 38 | import py.test 39 | from _pytest.mark import MarkInfo 40 | import saunter.saucelabs 41 | 42 | from saunter.matchers import Matchers 43 | 44 | class SaunterTestCase(BaseTestCase): 45 | """ 46 | Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup 47 | and teardown 48 | """ 49 | def setup_method(self, method): 50 | """ 51 | Parent class of all script classes used for custom asserts (usually 'soft' asserts) and shared fixture setup 52 | and teardown 53 | """ 54 | self.cf = self.config = saunter.ConfigWrapper.ConfigWrapper() 55 | 56 | self.current_method_name = method.__name__ 57 | 58 | default_browser = self.cf["browsers"][self.cf["saunter"]["default_browser"]] 59 | self.browser = saunter.browser.Browser(default_browser, self.cf) 60 | self.driver = self.browser.driver 61 | 62 | if hasattr(self.browser, 'proxy'): 63 | self.proxy = self.browser.proxy 64 | 65 | if "sauce labs" in self.cf["browsers"][self.cf["saunter"]["default_browser"]] and \ 66 | self.cf["browsers"][self.cf["saunter"]["default_browser"]]["sauce labs"]["ondemand"] and \ 67 | hasattr(self.driver, "session_id"): 68 | s = saunter.saucelabs.SauceLabs(self.cf["sauce labs"]["username"], self.cf["sauce labs"]["key"]) 69 | s.update_name(self.driver.session_id, self.current_method_name) 70 | 71 | self.verificationErrors = [] 72 | self.matchers = Matchers(self.driver, self.verificationErrors) 73 | 74 | self._screenshot_number = 1 75 | 76 | def teardown_method(self, method): 77 | """ 78 | Default teardown method for all scripts. If run through Sauce Labs OnDemand, the job name, status and tags 79 | are updated. Also the video and server log are downloaded if so configured. 80 | """ 81 | if hasattr(self, "config"): 82 | if "sauce labs" in self.cf["browsers"][self.cf["saunter"]["default_browser"]] and \ 83 | not self.cf["browsers"][self.cf["saunter"]["default_browser"]]["sauce labs"]["ondemand"] \ 84 | and self.cf["saunter"]["screenshots"]["on_finish"]: 85 | self.take_named_screenshot("final") 86 | 87 | if hasattr(self, "driver"): 88 | self.driver.quit() 89 | 90 | if hasattr(self.browser, 'proxy'): 91 | self.config['saunter']['proxies'].append(self.proxy) 92 | 93 | def take_numbered_screenshot(self): 94 | if self.config.has_option("Saunter", "take_screenshots"): 95 | if self.cf.getboolean("Saunter", "take_screenshots"): 96 | method_dir = self._screenshot_prep_dirs() 97 | 98 | self.driver.get_screenshot_as_file(os.path.join(method_dir, str(self._screenshot_number).zfill(3) + ".png")) 99 | self._screenshot_number = self._screenshot_number + 1 100 | 101 | if self.config.has_option("Saunter", "jenkins"): 102 | if self.cf.getboolean("Saunter", "jenkins"): 103 | sys.stdout.write(os.linesep + "[[ATTACHMENT|%s]]" % image_path + os.linesep) 104 | 105 | def take_named_screenshot(self, name): 106 | method_dir = self._screenshot_prep_dirs() 107 | 108 | image_path = os.path.join(method_dir, str(name) + ".png") 109 | self.driver.get_screenshot_as_file(image_path) 110 | 111 | if "ci_type" in self.cf and self.cf["ci_type"].lower() == "jenkins": 112 | sys.stdout.write(os.linesep + "[[ATTACHMENT|%s]]" % image_path + os.linesep) 113 | -------------------------------------------------------------------------------- /saunter/web_element.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.remote.webelement import WebElement 2 | import saunter.exceptions 3 | 4 | 5 | class WebElement(WebElement): 6 | def __init__(self, element): 7 | self.__dict__.update(element.__dict__) 8 | 9 | def find_element_by_locator(self, locator): 10 | locator_type = locator[:locator.find("=")] 11 | if locator_type == "": 12 | raise saunter.exceptions.InvalidLocatorString(locator) 13 | locator_value = locator[locator.find("=") + 1:] 14 | if locator_type == 'class': 15 | return WebElement(self.find_element_by_class_name(locator_value)) 16 | elif locator_type == 'css': 17 | return WebElement(self.find_element_by_css_selector(locator_value)) 18 | elif locator_type == 'id': 19 | return WebElement(self.find_element_by_id(locator_value)) 20 | elif locator_type == 'link': 21 | return WebElement(self.find_element_by_link_text(locator_value)) 22 | elif locator_type == 'name': 23 | return WebElement(self.find_element_by_name(locator_value)) 24 | elif locator_type == 'plink': 25 | return WebElement(self.find_element_by_partial_link_text(locator_value)) 26 | elif locator_type == 'tag': 27 | return WebElement(self.find_element_by_tag_name(locator_value)) 28 | elif locator_type == 'xpath': 29 | return WebElement(self.find_element_by_xpath(locator_value)) 30 | else: 31 | raise saunter.exceptions.InvalidLocatorString(locator) 32 | 33 | def find_elements_by_locator(self, locator): 34 | locator_type = locator[:locator.find("=")] 35 | if locator_type == "": 36 | raise saunter.exceptions.InvalidLocatorString(locator) 37 | locator_value = locator[locator.find("=") + 1:] 38 | if locator_type == 'class': 39 | elements = self.find_elements_by_class_name(locator_value) 40 | elif locator_type == 'css': 41 | elements = self.find_elements_by_css_selector(locator_value) 42 | elif locator_type == 'id': 43 | elements = self.find_elements_by_id(locator_value) 44 | elif locator_type == 'link': 45 | elements = self.find_elements_by_link_text(locator_value) 46 | elif locator_type == 'name': 47 | elements = self.find_elements_by_name(locator_value) 48 | elif locator_type == 'plink': 49 | elements = self.find_elements_by_partial_link_text(locator_value) 50 | elif locator_type == 'tag': 51 | elements = self.find_elements_by_tag_name(locator_value) 52 | elif locator_type == 'xpath': 53 | elements = self.find_elements_by_xpath(locator_value) 54 | else: 55 | raise saunter.exceptions.InvalidLocatorString(locator) 56 | 57 | return [WebElement(e) for e in elements] 58 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs/source 3 | build-dir = docs/build 4 | all_files = 1 5 | 6 | [upload_sphinx] 7 | upload-dir = docs/build/html 8 | 9 | [pep8] 10 | ignore = E501 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="saunter", 5 | packages=['saunter', 6 | 'saunter.generators', 7 | 'saunter.po', 8 | 'saunter.po.webdriver', 9 | 'saunter.po.webdriver.jquery', 10 | 'saunter.providers', 11 | 'saunter.testcase'], 12 | package_data={"saunter": ["_defaults/conftest.py", 13 | "_defaults/pytest.ini", 14 | "_defaults/conf/sauce labs.yaml.default", 15 | "_defaults/conf/saunter.yaml.default", 16 | "_defaults/conf/selenium.yaml.default", 17 | "_defaults/conf/browsers/browser.yaml.default", 18 | "_defaults/tailored/page.py", 19 | "_defaults/tailored/webdriver.py"]}, 20 | version="2.0.0", 21 | author="adam goucher", 22 | author_email="adam@element34.ca", 23 | install_requires=['pytest>=2.7.0', 24 | 'pytest-marks>=0.3', 25 | 'pytest-xdist', 26 | 'requests', 27 | 'selenium>=2.46.0', 28 | 'browsermob-proxy>=0.6.0', 29 | 'harpy>=0.2.0', 30 | 'pyyaml'], 31 | license="LICENSE.txt", 32 | description="An opinionated WebDriver-based framework", 33 | long_description="An opinionated WebDriver-based framework", 34 | url='https://github.com/Element-34/py.saunter', 35 | classifiers=[ 36 | 'Development Status :: 5 - Production/Stable', 37 | 'Intended Audience :: Developers', 38 | 'License :: OSI Approved :: Apache Software License', 39 | 'Operating System :: POSIX', 40 | 'Operating System :: Microsoft :: Windows', 41 | 'Operating System :: MacOS :: MacOS X', 42 | 'Topic :: Software Development :: Testing', 43 | 'Topic :: Software Development :: Quality Assurance', 44 | 'Programming Language :: Python' 45 | ], 46 | entry_points={ 47 | "console_scripts": [ 48 | "saunter = saunter.main", 49 | ], 50 | } 51 | ) 52 | --------------------------------------------------------------------------------