├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.rst ├── LICENSE ├── Makefile ├── README.rst ├── appveyor.yml ├── cookiecutter.json ├── docs ├── Makefile ├── _static │ ├── browserstack.svg │ └── logo.png ├── conf.py ├── index.rst ├── make.bat ├── prompts.rst ├── readme.rst └── troubleshooting.rst ├── hooks ├── post_gen_project.py └── pre_gen_project.py ├── pytest.ini ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests └── test_bake_project.py ├── tox.ini └── {{cookiecutter.project_slug}} ├── .dockerignore ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── CHANGES.rst ├── Dockerfile ├── Jenkinsfile ├── README.rst ├── capabilities ├── browsers │ ├── chrome │ │ ├── CHROME.json │ │ └── CHROME59.json │ ├── edge │ │ └── EDGE.json │ ├── firefox │ │ ├── FIREFOX.json │ │ └── FIREFOX53.json │ └── ie │ │ └── IE.json ├── debug.json ├── os │ └── WIN10.json ├── project.json └── resolutions │ └── 1280x1024.json ├── ci.py ├── credentials └── credentials_template.yml ├── pytest.ini ├── requirements.txt ├── setup.py ├── testrail.cfg ├── tox.ini └── {{cookiecutter.project_slug}} ├── __init__.py ├── config.py ├── features └── functional │ ├── login.feature │ └── logout.feature ├── pages ├── __init__.py └── base.py └── tests ├── __init__.py ├── conftest.py ├── functional ├── __init__.py ├── conftest.py ├── test_basic.py ├── test_login.py └── test_logout.py └── play └── test_play.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [Makefile] 18 | indent_style = tab 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Date you used Cookiecutter PyPackage: 2 | * Cookiecutter version used, if any: 3 | * Python version, if any: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | ``` 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | 51 | # PyInstaller 52 | # Usually these files are written by a python script from a template 53 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 54 | *.manifest 55 | *.spec 56 | 57 | # Installer logs 58 | pip-log.txt 59 | pip-delete-this-directory.txt 60 | 61 | # Unit test / coverage reports 62 | htmlcov/ 63 | .tox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *,cover 70 | .hypothesis/ 71 | 72 | # Translations 73 | *.mo 74 | *.pot 75 | 76 | # Django stuff: 77 | *.log 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # IntelliJ Idea family of suites 83 | .idea 84 | *.iml 85 | ## File-based project format: 86 | *.ipr 87 | *.iws 88 | ## mpeltonen/sbt-idea plugin 89 | .idea_modules/ 90 | 91 | # PyBuilder 92 | target/ 93 | 94 | # Cookiecutter 95 | output/ 96 | python_boilerplate/ 97 | python/ 98 | 99 | # Vim 100 | *.swp 101 | 102 | # junit 103 | junit*.xml 104 | 105 | # report 106 | report.html 107 | 108 | # pytest 109 | screenshots 110 | .pytest_cache 111 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | sudo: required 4 | python: 3.6 5 | services: 6 | - docker 7 | env: 8 | global: 9 | secure: L9iOapvDfgpEjdu+BHLPb7ilNbHmCkvUpLLdun2dJz+TMDRE2sS1g1x5L4AXuxY8ikQrZzTZgwtFJCrBtcO/I5ql18DCq7Cl6opK/4yAYsjYxiLmES+K538fbgo/DhtX0IijNljkKOoRtfCzG8S15oCkpPwVnCecumG+0QJT2VyaqZmMcuix38BT/HdyetmRHDptpzl9dYCqot7lEfYG4eomWO0LtHy594FBnfDM92oKxWyp+u1aAvGR2w88CYQoqgdelV+SRpPEJ8ebviz9wIKecz355msCcl86cfXMAdqHH0f73NfYjEMi+Vsy9lecydWEwZI90QhhCaxqriY1rzssLN6Qis60z0FTDZK+fmU8SxLzPQojpCvjw7nKTN6chSTRbjKPm+Rb0cWXK9/vCiCtJ6E76kwGJVj07Mxr0EynO/pWM8E18/6mnOr1cri1anzHWTR7SalZCY/QdgkiWRr3Gx566w6HPLsK2dbPFvapWU88H1FJYXnkq1NOf+BxodEEPqXzcby8GfCJSsD8qJqJ3BfkDkYC2kQpC3+I2hbjptXx3joMvFSSVC07ndRqzQRpne8mW6WNYVqTvc18KwcQB+fi+oCPhLMKsZ6yCprUFH76err+idD+WSYN5TGxRD7HqTNW6NwLHP2aubMmvGJd3AKlFp6FMwEHRJCfoCE= 10 | install: 11 | - travis_retry pip install -U tox 12 | - TOX_ENV=${TRAVIS_PYTHON_VERSION/[0-9].[0-9]/py${TRAVIS_PYTHON_VERSION/.}} 13 | script: 14 | - travis_wait 25 tox -e $TOX_ENV 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/davidemoro/cookiecutter-qa/issues 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | and "help wanted" is open to whoever wants to implement a fix for it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "enhancement" 34 | and "help wanted" is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | Cookiecutter QA could always use more documentation, whether as part of the 40 | official docs, in docstrings, or even on the web in blog posts, articles, and such. 41 | 42 | Submit Feedback 43 | ~~~~~~~~~~~~~~~ 44 | 45 | The best way to send feedback is to file an issue at https://github.com/davidemoro/cookiecutter-qa/issues. 46 | 47 | If you are proposing a new feature: 48 | 49 | * Explain in detail how it would work. 50 | * Keep the scope as narrow as possible, to make it easier to implement. 51 | * Remember that this is a volunteer-driven project, and that contributions 52 | are welcome :) 53 | 54 | Get Started! 55 | ------------ 56 | 57 | Ready to contribute? Here's how to set up `cookiecutter-qa` for local development. Please note this documentation assumes 58 | you already have `virtualenv` and `Git` installed and ready to go. 59 | 60 | 1. Fork the `cookiecutter-qa` repo on GitHub. 61 | 2. Clone your fork locally:: 62 | 63 | .. code-block:: bash 64 | 65 | $ cd path_for_the_repo 66 | $ git clone git@github.com:YOUR_NAME/cookiecutter-qa.git 67 | 68 | 3. Assuming you have virtualenv installed (If you have Python3.5 this should already be there), you can create a new environment for your local development by typing:: 69 | 70 | .. code-block:: bash 71 | 72 | $ virtualenv cookiecutter-qa-env 73 | $ source cookiecutter-qa-env/bin/activate 74 | 75 | This should change the shell to look something like 76 | (cookiecutter-qa-env) $ 77 | 78 | 4. Create a branch for local development:: 79 | 80 | .. code-block:: bash 81 | 82 | $ git checkout -b name-of-your-bugfix-or-feature 83 | 84 | Now you can make your changes locally. 85 | 86 | 5. When you're done making changes, check that your changes pass flake8. Since, this package contains mostly templates the flake should 87 | be run for tests directory:: 88 | 89 | .. code-block:: bash 90 | 91 | $ flake8 ./tests 92 | 93 | 6. The next step would be to run the test cases. `cookiecutter-qa` uses py.test, you can run PyTest. Before you run pytest you should ensure all dependancies are installed:: 94 | 95 | .. code-block:: bash 96 | 97 | $ pip install -rrequirements_dev.txt 98 | $ py.test ./tests 99 | 100 | If you get any errors while installing cryptography package (something like #include ). 101 | Please update your pip version and try again:: 102 | 103 | # Update pip 104 | $ pip install -U pip 105 | 106 | 7. Before raising a pull request you should also run tox. This will run the tests across different versions of Python:: 107 | 108 | .. code-block:: bash 109 | 110 | $ tox 111 | 112 | .. note:: 113 | If you are missing flake8, pytest and/or tox, just pip install them into your virtualenv. 114 | 115 | 8. If your contribution is a bug fix or new feature, you may want to add a test to the existing test suite. See section Add a New Test below for details. 116 | 117 | 9. Commit your changes and push your branch to GitHub:: 118 | 119 | .. code-block:: bash 120 | 121 | $ git add . 122 | $ git commit -m "Your detailed description of your changes." 123 | $ git push origin name-of-your-bugfix-or-feature 124 | 125 | 10. Submit a pull request through the GitHub website. 126 | 127 | Pull Request Guidelines 128 | ----------------------- 129 | 130 | Before you submit a pull request, check that it meets these guidelines: 131 | 132 | 1. The pull request should include tests. 133 | 134 | 2. If the pull request adds functionality, the docs should be updated. Put 135 | your new functionality into a function with a docstring, and add the 136 | feature to the list in README.rst. 137 | 138 | 3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check 139 | https://travis-ci.org/davidemoro/cookiecutter-qa/pull_requests 140 | and make sure that the tests pass for all supported Python versions. 141 | 142 | Add a New Test 143 | --------------- 144 | When fixing a bug or adding features, it's good practice to add a test to demonstrate your fix or new feature behaves as expected. These tests should focus on one tiny bit of functionality and prove changes are correct. 145 | 146 | To write and run your new test, follow these steps: 147 | 148 | 1. Add the new test to `tests/test_bake_project.py`. Focus your test on the specific bug or a small part of the new feature. 149 | 150 | 2. If you have already made changes to the code, stash your changes and confirm all your changes were stashed:: 151 | 152 | $ git stash 153 | $ git stash list 154 | 155 | 3. Run your test and confirm that your test fails. If your test does not fail, rewrite the test until it fails on the original code:: 156 | 157 | $ py.test ./tests 158 | 159 | 4. (Optional) Run the tests with tox to ensure that the code changes work with different Python versions:: 160 | 161 | $ tox 162 | 163 | 5. Proceed work on your bug fix or new feature or restore your changes. To restore your stashed changes and confirm their restoration:: 164 | 165 | $ git stash pop 166 | $ git stash list 167 | 168 | 6. Rerun your test and confirm that your test passes. If it passes, congratulations! 169 | 170 | .. cookiecutter: https://github.com/davidemoro/cookiecutter-qa 171 | .. virtualenv: https://virtualenv.pypa.io/en/stable/installation 172 | .. git: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git 173 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2018 Tierra spa 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BAKE_OPTIONS=--no-input 2 | 3 | help: 4 | @echo "bake generate project using defaults" 5 | @echo "watch generate project using defaults and watch for changes" 6 | @echo "replay replay last cookiecutter run and watch for changes" 7 | 8 | bake: 9 | cookiecutter $(BAKE_OPTIONS) . --overwrite-if-exists 10 | 11 | watch: bake 12 | watchmedo shell-command -p '*.*' -c 'make bake -e BAKE_OPTIONS=$(BAKE_OPTIONS)' -W -R -D \{{cookiecutter.project_slug}}/ 13 | 14 | replay: BAKE_OPTIONS=--replay 15 | replay: watch 16 | ; 17 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | cookiecutter QA 3 | =============== 4 | 5 | .. image:: https://travis-ci.org/davidemoro/cookiecutter-qa.svg?branch=master 6 | :target: https://travis-ci.org/davidemoro/cookiecutter-qa 7 | 8 | .. image:: https://readthedocs.org/projects/cookiecutter-qa/badge/?version=latest 9 | :target: http://cookiecutter-qa.readthedocs.io 10 | 11 | Coookiecutter QA let you create QA projects based on the Cookiecutter_ scaffolding project. 12 | 13 | Usage 14 | ===== 15 | 16 | Install Cookiecutter_, use ``cookiecutter-qa`` as project template 17 | and bake your new QA package providing some information:: 18 | 19 | $ pip install cookiecutter 20 | $ cookiecutter https://github.com/davidemoro/cookiecutter-qa 21 | email [davide.moro@gmail.com]: 22 | project_name [Project QA]: 23 | project_slug [project_qa]: 24 | project_short_description [Project QA contains all the boilerplate you need to create a QA package]: 25 | version [0.0.1]: 26 | base_url [https://github.com/davidemoro/cookiecutter-qa]: 27 | selenium_grid_url [http://USERNAME:ACCESS_KEY@hub.browserstack.com:80/wd/hub]: YOUR_SELENIUM_GRID_URL_HERE 28 | pytest_play [y]: 29 | testrail [y]: 30 | $ cd project_qa 31 | 32 | As result cookiecutter will create for you a new package with a hello world test pytest_, Splinter_, pytest-play_, 33 | BDD and page objects ready. 34 | 35 | **Important note**: be aware that the `selenium_grid_url` will be saved in ``project_name/pytest.ini``! 36 | 37 | And now you can launch your helo world project test (linux):: 38 | 39 | $ docker run --rm -it -v $(pwd):/src davidemoro/pytest-play 40 | 41 | or you are using Windows substitute the `$(pwd)` command with your project full path. 42 | 43 | You can also use custom options provided by pytest and other installed third party plugins 44 | or request for custom browser capabilities. For example:: 45 | 46 | $ docker run --rm -it -v $(pwd):/src davidemoro/pytest-play \ 47 | --variables capabilities/os/WIN10.json 48 | --variables capabilities/browsers/chrome/CHROME.json 49 | --variables capabilities/resolutions/1280x1024.json 50 | -x 51 | --pdb 52 | 53 | Local browsers testing is supported too (covered in next sections). 54 | 55 | Setup 56 | ===== 57 | 58 | Prerequisites: 59 | 60 | * python >= 3.6, needed for the scaffolding tool 61 | 62 | * cookiecutter scaffolding tool for generating your project (``pip install cookiecutter`` command) 63 | 64 | * docker, suggested method for executing your tests using the well 65 | tested https://github.com/davidemoro/pytest-play-docker container. 66 | Using docker is the easiest and faster solution for running your tests 67 | 68 | * a selenium grid url or ``geckodriver``/``chromedriver`` installed 69 | 70 | Browsers setup and selenium_grid_url 71 | ------------------------------------ 72 | 73 | If you are not yet comfortable with `geckodriver`/`chromedriver` installations, `PATH` environment variable 74 | update I suggest to start creating a BrowserStack_ free account and you will be able to 75 | run your tests against a real remote browser without having to install locally all the needed 76 | prerequisites. 77 | 78 | Once logged in on BrowserStack_ visit ``Account > Settings``, copy the Automate's username and access key 79 | and generate a new cookiecutter project providing the remote selenium grid url following the format:: 80 | 81 | http://USERNAME:ACCESS_KEY@hub.browserstack.com:80/wd/hub 82 | 83 | You can use any Selenium grid provider (SauceLabs_, BrowserStack_, TestingBot_) or using your own local 84 | grid with Zalenium_. 85 | 86 | Otherwise local browsers testing is supported too thanks to the option ``splinter-webdriver``, read the 87 | following section. 88 | 89 | Run tests with local browsers 90 | ============================= 91 | 92 | You can launch tests based on local browsers instead of relying to a 93 | remote (SauceLabs_, BrowserStack_, TestingBot_) or local grid (using Zalenium_) 94 | using the ``--splinter-webdriver firefox`` option for example. 95 | 96 | See https://github.com/pytest-dev/pytest-splinter#command-line-options 97 | 98 | Supported browser options: 99 | 100 | * firefox 101 | 102 | * remote (you need to provide a value for the ``--splinter-remote-url`` option) 103 | 104 | * chrome 105 | 106 | Using local browsers it's up to you the configuration of geckodriver, chromedriver, 107 | executable path settings, using the latest drivers 108 | (eg: https://github.com/mozilla/geckodriver/releases) and updated browser versions. 109 | 110 | pytest-play ready! 111 | ================== 112 | 113 | ``cookiecutter-qa`` supports also pytest-play_ including by default all the third 114 | party external plugins like play_selenium_, play_requests_ and their brothers 115 | (see `Third party pytest-play plugins`_) so you can execute plain **yaml** files. 116 | 117 | Credits 118 | ======= 119 | 120 | * heavily based on `cookiecutter-pypackage`_: `@audreyr`_'s ultimate Python package project 121 | template. 122 | 123 | Twitter 124 | ======= 125 | 126 | cookiecutter-qa tweets happens here: 127 | 128 | * `@davidemoro`_ 129 | 130 | 131 | Based on 132 | ======== 133 | 134 | .. image:: https://raw.github.com/audreyr/cookiecutter/3ac078356adf5a1a72042dfe72ebfa4a9cd5ef38/logo/cookiecutter_medium.png 135 | 136 | Sponsored by 137 | ============ 138 | 139 | .. image:: http://cookiecutter-qa.readthedocs.io/en/latest/_static/browserstack.svg 140 | :target: https://www.browserstack.com 141 | :alt: Browserstack 142 | 143 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 144 | .. _BrowserStack: https://www.browserstack.com 145 | .. _`@audreyr`: https://github.com/audreyr 146 | .. _`cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 147 | .. _`@davidemoro`: https://twitter.com/davidemoro 148 | .. _`Splinter`: https://splinter.readthedocs.io/en/latest/ 149 | .. _`pytest`: https://docs.pytest.org/en/latest/ 150 | .. _`pytest-play`: http://pytest-play.readthedocs.io/en/latest/ 151 | .. _`play_selenium`: https://github.com/davidemoro/play_selenium 152 | .. _`play_requests`: https://github.com/davidemoro/play_request 153 | .. _`Third party pytest-play plugins`: https://github.com/pytest-dev/pytest-play#third-party-pytest-play-plugins 154 | .. _`Zalenium`: https://github.com/zalando/zalenium 155 | .. _`SauceLabs`: https://saucelabs.com/ 156 | .. _`TestingBot`: https://testingbot.com/ 157 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # What Python version is installed where: 2 | # http://www.appveyor.com/docs/installed-software#python 3 | 4 | environment: 5 | matrix: 6 | - PYTHON: "C:\\Python27" 7 | TOX_ENV: "py27" 8 | 9 | - PYTHON: "C:\\Python27-x64" 10 | TOX_ENV: "py27" 11 | 12 | - PYTHON: "C:\\Python33" 13 | TOX_ENV: "py33" 14 | 15 | - PYTHON: "C:\\Python33-x64" 16 | TOX_ENV: "py33" 17 | 18 | - PYTHON: "C:\\Python34" 19 | TOX_ENV: "py34" 20 | 21 | - PYTHON: "C:\\Python34-x64" 22 | TOX_ENV: "py34" 23 | 24 | - PYTHON: "C:\\Python35" 25 | TOX_ENV: "py35" 26 | 27 | - PYTHON: "C:\\Python35-x64" 28 | TOX_ENV: "py35" 29 | 30 | init: 31 | - set PATH=%PYTHON%;%PYTHON%\Scripts;C:\MinGW\msys\1.0\bin;%PATH% 32 | - "git config --system http.sslcainfo \"C:\\Program Files\\Git\\mingw64\\ssl\\certs\\ca-bundle.crt\"" 33 | - "%PYTHON%/python -V" 34 | - "%PYTHON%/python -c \"import struct;print(8 * struct.calcsize(\'P\'))\"" 35 | 36 | install: 37 | - "%PYTHON%/Scripts/easy_install -U pip" 38 | - "%PYTHON%/Scripts/pip install tox" 39 | - "%PYTHON%/Scripts/pip install wheel" 40 | 41 | build: false # Not a C# project, build stuff at the test step instead. 42 | 43 | test_script: 44 | - "%PYTHON%/Scripts/tox -e %TOX_ENV%" 45 | 46 | after_test: 47 | - "%PYTHON%/python setup.py --command-packages wheel bdist_wheel" 48 | - ps: "ls dist" 49 | 50 | artifacts: 51 | - path: dist\* 52 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": "davide.moro@gmail.com", 3 | "project_name": "Project QA", 4 | "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}", 5 | "project_short_description": "Project QA contains all the boilerplate you need to create a QA package", 6 | "version": "0.0.1", 7 | "base_url": "https://github.com/davidemoro/cookiecutter-qa", 8 | "selenium_grid_url": "http://USERNAME:ACCESS_KEY@hub.browserstack.com:80/wd/hub", 9 | "pytest_play": "y", 10 | "testrail": "y" 11 | } 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/cookiecutter-qa.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/cookiecutter-qa.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/cookiecutter-qa" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/cookiecutter-qa" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidemoro/cookiecutter-qa/a711b435c55ffdbc959c34f858022a4ecdd61d0e/docs/_static/logo.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # cookiecutter-qa documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Dec 13 09:13:01 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.todo', 37 | 'sphinx.ext.coverage', 38 | 'sphinx.ext.viewcode', 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # The suffix(es) of source filenames. 45 | # You can specify multiple suffix as a list of string: 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The encoding of source files. 50 | #source_encoding = 'utf-8-sig' 51 | 52 | # The master toctree document. 53 | master_doc = 'index' 54 | 55 | # General information about the project. 56 | project = 'cookiecutter-qa' 57 | copyright = '2019, Davide Moro' 58 | author = 'Davide Moro' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = '0.0.1' 66 | # The full version, including alpha/beta/rc tags. 67 | release = '0.0.1' 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | # 72 | # This is also used if you do content translation via gettext catalogs. 73 | # Usually you set "language" from the command line for these cases. 74 | language = None 75 | 76 | # There are two options for replacing |today|: either, you set today to some 77 | # non-false value, then it is used: 78 | #today = '' 79 | # Else, today_fmt is used as the format for a strftime call. 80 | #today_fmt = '%B %d, %Y' 81 | 82 | # List of patterns, relative to source directory, that match files and 83 | # directories to ignore when looking for source files. 84 | exclude_patterns = ['_build'] 85 | 86 | # The reST default role (used for this markup: `text`) to use for all 87 | # documents. 88 | #default_role = None 89 | 90 | # If true, '()' will be appended to :func: etc. cross-reference text. 91 | #add_function_parentheses = True 92 | 93 | # If true, the current module name will be prepended to all description 94 | # unit titles (such as .. function::). 95 | #add_module_names = True 96 | 97 | # If true, sectionauthor and moduleauthor directives will be shown in the 98 | # output. They are ignored by default. 99 | #show_authors = False 100 | 101 | # The name of the Pygments (syntax highlighting) style to use. 102 | pygments_style = 'sphinx' 103 | 104 | # A list of ignored prefixes for module index sorting. 105 | #modindex_common_prefix = [] 106 | 107 | # If true, keep warnings as "system message" paragraphs in the built documents. 108 | #keep_warnings = False 109 | 110 | # If true, `todo` and `todoList` produce output, else they produce nothing. 111 | todo_include_todos = True 112 | 113 | 114 | # -- Options for HTML output ---------------------------------------------- 115 | 116 | # The theme to use for HTML and HTML Help pages. See the documentation for 117 | # a list of builtin themes. 118 | html_theme = 'sphinx_rtd_theme' 119 | 120 | # Theme options are theme-specific and customize the look and feel of a theme 121 | # further. For a list of options available for each theme, see the 122 | # documentation. 123 | #html_theme_options = {} 124 | 125 | # Add any paths that contain custom themes here, relative to this directory. 126 | #html_theme_path = [] 127 | 128 | # The name for this set of Sphinx documents. If None, it defaults to 129 | # " v documentation". 130 | #html_title = None 131 | 132 | # A shorter title for the navigation bar. Default is the same as html_title. 133 | #html_short_title = None 134 | 135 | # The name of an image file (relative to this directory) to place at the top 136 | # of the sidebar. 137 | html_logo = './_static/logo.png' 138 | 139 | # The name of an image file (within the static path) to use as favicon of the 140 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 141 | # pixels large. 142 | #html_favicon = None 143 | 144 | # Add any paths that contain custom static files (such as style sheets) here, 145 | # relative to this directory. They are copied after the builtin static files, 146 | # so a file named "default.css" will overwrite the builtin "default.css". 147 | html_static_path = ['_static'] 148 | 149 | # Add any extra paths that contain custom files (such as robots.txt or 150 | # .htaccess) here, relative to this directory. These files are copied 151 | # directly to the root of the documentation. 152 | #html_extra_path = [] 153 | 154 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 155 | # using the given strftime format. 156 | #html_last_updated_fmt = '%b %d, %Y' 157 | 158 | # If true, SmartyPants will be used to convert quotes and dashes to 159 | # typographically correct entities. 160 | #html_use_smartypants = True 161 | 162 | # Custom sidebar templates, maps document names to template names. 163 | #html_sidebars = {} 164 | 165 | # Additional templates that should be rendered to pages, maps page names to 166 | # template names. 167 | #html_additional_pages = {} 168 | 169 | # If false, no module index is generated. 170 | #html_domain_indices = True 171 | 172 | # If false, no index is generated. 173 | #html_use_index = True 174 | 175 | # If true, the index is split into individual pages for each letter. 176 | #html_split_index = False 177 | 178 | # If true, links to the reST sources are added to the pages. 179 | #html_show_sourcelink = True 180 | 181 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 182 | #html_show_sphinx = True 183 | 184 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 185 | #html_show_copyright = True 186 | 187 | # If true, an OpenSearch description file will be output, and all pages will 188 | # contain a tag referring to it. The value of this option must be the 189 | # base URL from which the finished HTML is served. 190 | #html_use_opensearch = '' 191 | 192 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 193 | #html_file_suffix = None 194 | 195 | # Language to be used for generating the HTML full-text search index. 196 | # Sphinx supports the following languages: 197 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 198 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 199 | #html_search_language = 'en' 200 | 201 | # A dictionary with options for the search language support, empty by default. 202 | # Now only 'ja' uses this config value 203 | #html_search_options = {'type': 'default'} 204 | 205 | # The name of a javascript file (relative to the configuration directory) that 206 | # implements a search results scorer. If empty, the default will be used. 207 | #html_search_scorer = 'scorer.js' 208 | 209 | # Output file base name for HTML help builder. 210 | htmlhelp_basename = 'cookiecutter-qadoc' 211 | 212 | # -- Options for LaTeX output --------------------------------------------- 213 | 214 | latex_elements = { 215 | # The paper size ('letterpaper' or 'a4paper'). 216 | #'papersize': 'letterpaper', 217 | 218 | # The font size ('10pt', '11pt' or '12pt'). 219 | #'pointsize': '10pt', 220 | 221 | # Additional stuff for the LaTeX preamble. 222 | #'preamble': '', 223 | 224 | # Latex figure (float) alignment 225 | #'figure_align': 'htbp', 226 | } 227 | 228 | # Grouping the document tree into LaTeX files. List of tuples 229 | # (source start file, target name, title, 230 | # author, documentclass [howto, manual, or own class]). 231 | latex_documents = [ 232 | (master_doc, 'cookiecutter-qa.tex', 'cookiecutter-qa Documentation', 233 | 'Audrey Roy Greenfeld', 'manual'), 234 | ] 235 | 236 | # The name of an image file (relative to this directory) to place at the top of 237 | # the title page. 238 | latex_logo = './_static/logo.png' 239 | 240 | # For "manual" documents, if this is true, then toplevel headings are parts, 241 | # not chapters. 242 | #latex_use_parts = False 243 | 244 | # If true, show page references after internal links. 245 | #latex_show_pagerefs = False 246 | 247 | # If true, show URL addresses after external links. 248 | #latex_show_urls = False 249 | 250 | # Documents to append as an appendix to all manuals. 251 | #latex_appendices = [] 252 | 253 | # If false, no module index is generated. 254 | #latex_domain_indices = True 255 | 256 | 257 | # -- Options for manual page output --------------------------------------- 258 | 259 | # One entry per manual page. List of tuples 260 | # (source start file, name, description, authors, manual section). 261 | man_pages = [ 262 | (master_doc, 'cookiecutter-qa', 'cookiecutter-qa Documentation', 263 | [author], 1) 264 | ] 265 | 266 | # If true, show URL addresses after external links. 267 | #man_show_urls = False 268 | 269 | 270 | # -- Options for Texinfo output ------------------------------------------- 271 | 272 | # Grouping the document tree into Texinfo files. List of tuples 273 | # (source start file, target name, title, author, 274 | # dir menu entry, description, category) 275 | texinfo_documents = [ 276 | (master_doc, 'cookiecutter-qa', 'cookiecutter-qa Documentation', 277 | author, 'cookiecutter-qa', 'One line description of project.', 278 | 'Miscellaneous'), 279 | ] 280 | 281 | # Documents to append as an appendix to all manuals. 282 | #texinfo_appendices = [] 283 | 284 | # If false, no module index is generated. 285 | #texinfo_domain_indices = True 286 | 287 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 288 | #texinfo_show_urls = 'footnote' 289 | 290 | # If true, do not generate a @detailmenu in the "Top" node's menu. 291 | #texinfo_no_detailmenu = False 292 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. cookiecutter-qa documentation master file, created by 2 | sphinx-quickstart on Sun Dec 13 09:13:01 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to cookiecutter-qa's documentation! 7 | ================================================== 8 | 9 | Getting Started 10 | --------------- 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | readme 16 | 17 | Basics 18 | ------ 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | prompts 24 | 25 | Advanced Features 26 | ----------------- 27 | 28 | .. toctree:: 29 | :maxdepth: 2 30 | 31 | 32 | Indices and tables 33 | ================== 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\cookiecutter-qa.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\cookiecutter-qa.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/prompts.rst: -------------------------------------------------------------------------------- 1 | Prompts 2 | ======= 3 | 4 | When you create a package, you are prompted to enter these values. 5 | 6 | Templated Values 7 | ---------------- 8 | 9 | The following appear in various parts of your generated project. 10 | 11 | email 12 | Your email address. 13 | 14 | project_name 15 | The name of your new Python package project. This is used in documentation, so spaces and any characters are fine here. 16 | 17 | project_slug 18 | The namespace of your Python package. This should be Python import-friendly. Typically, it is the slugified version of project_name. 19 | 20 | project_short_description 21 | A 1-sentence description of what your Python package does. 22 | 23 | version 24 | The starting version number of the package. 25 | 26 | base_url 27 | Your base url for your Splinter/Selenium tests 28 | 29 | selenium_grid_url 30 | Your remote selenium grid url 31 | 32 | testrail 33 | Upload test execution results to the Testrail (https://github.com/dubner/pytest-testrail) test management tool. If you don't have Testrail say ``n`` here 34 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. _readme: 2 | 3 | .. include:: ../README.rst 4 | -------------------------------------------------------------------------------- /docs/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | .. _troubleshooting: 2 | 3 | =============== 4 | Troubleshooting 5 | =============== 6 | 7 | .. note:: Can you help improve this file? `Edit this file`_ 8 | and submit a pull request with your improvements! 9 | 10 | .. _`Edit this file`: https://github.com/davidemoro/cookiecutter-qa/blob/master/docs/troubleshooting.rst 11 | 12 | 13 | Windows Issues 14 | -------------- 15 | 16 | * Some people have reported issues using git bash; try using the Command Terminal instead. 17 | 18 | * Virtual environments can sometimes be tricky on Windows. If you have Python 3.5 or above installed (recommended), this should get you a virtualenv named ``myenv`` created inside the current folder: 19 | 20 | .. code-block:: powershell 21 | 22 | > c:\Python35\python -m venv myenv 23 | 24 | Or: 25 | 26 | .. code-block:: powershell 27 | 28 | > c:\Python35\python c:\Python35\Tools\Scripts\pyvenv.py myenv 29 | 30 | * Some people have reported that they have to re-activate their virtualenv whenever they change directory, so you should remember the path to the virtualenv in case you need it. 31 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import shutil 4 | 5 | PROJECT_DIRECTORY = os.path.realpath(os.path.curdir) 6 | 7 | 8 | def remove_file(*filepath): 9 | os.remove(os.path.join(PROJECT_DIRECTORY, *filepath)) 10 | 11 | 12 | def remove_folder(*folderpath): 13 | shutil.rmtree(os.path.join(PROJECT_DIRECTORY, *folderpath)) 14 | 15 | 16 | if __name__ == '__main__': 17 | if '{{ cookiecutter.testrail }}' == 'n': 18 | remove_file('testrail.cfg') 19 | 20 | if '{{ cookiecutter.pytest_play }}' == 'n': 21 | remove_folder('{{cookiecutter.project_slug}}', 'tests', 'play') 22 | -------------------------------------------------------------------------------- /hooks/pre_gen_project.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | 4 | 5 | MODULE_REGEX = r'^[_a-zA-Z][_a-zA-Z0-9]+$' 6 | 7 | module_name = '{{ cookiecutter.project_slug}}' 8 | 9 | if not re.match(MODULE_REGEX, module_name): 10 | print('ERROR: The project slug (%s) is not a valid Python module name. ' 11 | 'Please do not use a - and use _ instead' % module_name) 12 | 13 | # Exit to cancel project 14 | sys.exit(1) 15 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests/ 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.12 2 | argh==0.26.2 3 | arrow==0.13.0 4 | Babel==2.6.0 5 | binaryornot==0.4.4 6 | certifi==2018.11.29 7 | chardet==3.0.4 8 | click==7.0 9 | cookiecutter==1.6.0 10 | docutils==0.14 11 | future==0.17.1 12 | idna==2.8 13 | imagesize==1.1.0 14 | Jinja2==2.10 15 | jinja2-time==0.2.0 16 | MarkupSafe==1.1.0 17 | pathtools==0.1.2 18 | pluggy==0.8.1 19 | poyo==0.4.2 20 | py==1.7.0 21 | Pygments==2.3.1 22 | pytest==4.2.0 23 | pytest-cookies==0.3.0 24 | python-dateutil==2.8.0 25 | pytz==2018.9 26 | PyYAML==4.2b4 27 | requests==2.21.0 28 | six==1.12.0 29 | snowballstemmer==1.2.1 30 | Sphinx==1.8.4 31 | sphinx-rtd-theme==0.4.2 32 | sphinxcontrib-websupport==1.1.0 33 | tox==3.7.0 34 | urllib3==1.24.1 35 | virtualenv==16.3.0 36 | watchdog==0.9.0 # required by watchmedo 37 | whichcraft==0.5.2 38 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.1 3 | commit = True 4 | tag = True 5 | 6 | [metadata] 7 | description-file = README.rst 8 | 9 | [bumpversion:file:setup.py] 10 | search = version='{current_version}' 11 | replace = version='{new_version}' 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # !/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | 6 | tests_require = [ 7 | 'click', 8 | 'cookiecutter>=1.4.0', 9 | 'pytest', 10 | 'pytest-cookies', 11 | 'tox', 12 | ] 13 | 14 | docs_require = [ 15 | 'Sphinx', 16 | 'sphinx_rtd_theme', 17 | ] 18 | 19 | setup( 20 | name='cookiecutter-qa', 21 | packages=[], 22 | version='0.1.0', 23 | description='Cookiecutter template for a QA package', 24 | author='Davide Moro', 25 | license='Apache Software License 2.0', 26 | author_email='davide.moro@gmail.com', 27 | url='https://github.com/davidemoro/cookiecutter-qa', 28 | keywords=['cookiecutter', 'template', 'package', ], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Environment :: Console', 32 | 'Intended Audience :: Developers', 33 | 'Natural Language :: English', 34 | 'License :: OSI Approved :: BSD License', 35 | 'Programming Language :: Python', 36 | 'Programming Language :: Python :: 2', 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.4', 40 | 'Programming Language :: Python :: 3.5', 41 | 'Programming Language :: Python :: Implementation :: CPython', 42 | 'Programming Language :: Python :: Implementation :: PyPy', 43 | 'Topic :: Software Development', 44 | ], 45 | extras_require={ 46 | 'docs': docs_require, 47 | 'tests': tests_require, 48 | }, 49 | ) 50 | -------------------------------------------------------------------------------- /tests/test_bake_project.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from contextlib import contextmanager 3 | import shlex 4 | import os 5 | import subprocess 6 | from cookiecutter.utils import rmtree 7 | 8 | 9 | BROWSERSTACK_USERNAME = os.getenv('BROWSERSTACK_USERNAME', "davidemoro2") 10 | BROWSERSTACK_ACCESS_KEY = os.getenv('BROWSERSTACK_ACCESS_KEY', '') 11 | REMOTE_URL = os.getenv('REMOTE_URL', '') 12 | 13 | selenium_grid_url = REMOTE_URL or 'http://{0}:{1}@hub.browserstack.com:80/wd/hub'.format( 14 | BROWSERSTACK_USERNAME, 15 | BROWSERSTACK_ACCESS_KEY, 16 | ) 17 | 18 | 19 | @pytest.fixture 20 | def default_extra_context(): 21 | return { 22 | 'selenium_grid_url': selenium_grid_url, 23 | } 24 | 25 | 26 | @contextmanager 27 | def inside_dir(dirpath): 28 | """ 29 | Execute code from inside the given directory 30 | :param dirpath: String, path of the directory the command is being run. 31 | """ 32 | old_path = os.getcwd() 33 | try: 34 | os.chdir(dirpath) 35 | yield 36 | finally: 37 | os.chdir(old_path) 38 | 39 | 40 | @contextmanager 41 | def bake_in_temp_dir(cookies, *args, **kwargs): 42 | """ 43 | Delete the temporal directory that is created when executing the tests 44 | :param cookies: pytest_cookies.Cookies, cookie to be baked and its 45 | temporalfiles will be removed 46 | """ 47 | result = cookies.bake(*args, **kwargs) 48 | try: 49 | yield result 50 | finally: 51 | rmtree(str(result.project)) 52 | 53 | 54 | def run_inside_dir(command, dirpath): 55 | """ 56 | Run a command from inside a given directory, returning the exit status 57 | :param command: Command that will be executed 58 | :param dirpath: String, path of the directory the command is being run. 59 | """ 60 | with inside_dir(dirpath): 61 | if os.getenv('TRAVIS'): 62 | with open(os.devnull, 'w') as devnull: 63 | return subprocess.check_call(shlex.split(command), 64 | stdout=devnull, 65 | stderr=devnull) 66 | else: 67 | return subprocess.check_call(shlex.split(command)) 68 | 69 | 70 | def check_output_inside_dir(command, dirpath): 71 | "Run a command from inside a given directory, returning the command output" 72 | with inside_dir(dirpath): 73 | return subprocess.check_output(shlex.split(command)) 74 | 75 | 76 | def project_info(result): 77 | """Get toplevel dir, project_slug, and project dir from baked cookies""" 78 | project_path = str(result.project) 79 | project_slug = os.path.split(project_path)[-1] 80 | project_dir = os.path.join(project_path, project_slug) 81 | return project_path, project_slug, project_dir 82 | 83 | 84 | def test_bake_with_defaults(cookies): 85 | with bake_in_temp_dir(cookies) as result: 86 | assert result.project.isdir() 87 | assert result.exit_code == 0 88 | assert result.exception is None 89 | 90 | found_toplevel_files = [f.basename for f in result.project.listdir()] 91 | assert 'setup.py' in found_toplevel_files 92 | assert 'pytest.ini' in found_toplevel_files 93 | assert 'tox.ini' in found_toplevel_files 94 | assert 'credentials' in found_toplevel_files 95 | assert 'testrail.cfg' in found_toplevel_files 96 | 97 | credential_files = [ 98 | subitem.basename for subitem in 99 | [item for item in result.project.visit( 100 | 'credentials')][0].listdir()] 101 | assert 'credentials_template.yml' in credential_files 102 | 103 | found_secondlevel_files = [ 104 | subitem.basename for subitem in 105 | [item for item in result.project.visit('project_qa')][0].listdir()] 106 | assert 'tests' in found_secondlevel_files 107 | assert '__init__.py' in found_secondlevel_files 108 | assert 'config.py' in found_secondlevel_files 109 | assert 'pages' in found_secondlevel_files 110 | assert 'features' in found_secondlevel_files 111 | 112 | setup_py_path = [f.strpath for f in result.project.listdir() 113 | if f.basename == 'setup.py'][0] 114 | with open(setup_py_path) as setup_py_file: 115 | assert 'pytest-testrail' in setup_py_file.read() 116 | 117 | 118 | def test_bake_with_testrail(cookies): 119 | """ testrail support """ 120 | with bake_in_temp_dir(cookies) as result: 121 | assert result.project.isdir() 122 | assert result.exit_code == 0 123 | assert result.exception is None 124 | 125 | setup_py_path = [f.strpath for f in result.project.listdir() 126 | if f.basename == 'setup.py'][0] 127 | with open(setup_py_path) as setup_py_file: 128 | assert 'pytest-testrail' in setup_py_file.read() 129 | 130 | 131 | def test_bake_without_testrail(cookies): 132 | """ testrail support """ 133 | with bake_in_temp_dir(cookies, extra_context={'testrail': "n"}) as result: 134 | assert result.project.isdir() 135 | assert result.exit_code == 0 136 | assert result.exception is None 137 | 138 | found_toplevel_files = [f.basename for f in result.project.listdir()] 139 | assert 'testrail.cfg' not in found_toplevel_files 140 | 141 | setup_py_path = [f.strpath for f in result.project.listdir() 142 | if f.basename == 'setup.py'][0] 143 | with open(setup_py_path) as setup_py_file: 144 | assert 'pytest-testrail' not in setup_py_file.read() 145 | 146 | 147 | def test_bake_and_run_tests(cookies, default_extra_context): 148 | extra_context = default_extra_context.copy() 149 | with bake_in_temp_dir( 150 | cookies, 151 | extra_context=extra_context) as result: 152 | assert result.project.isdir() 153 | project_path = str(result.project) 154 | run_inside_dir( 155 | 'docker run --rm -i -v {0}:/src davidemoro/pytest-play ' 156 | '--splinter-remote-url={1}'.format( 157 | project_path, 158 | default_extra_context['selenium_grid_url']), 159 | str(result.project)) == 0 160 | print("test_bake_and_run_tests path", project_path) 161 | 162 | 163 | def test_bake_with_no_testrail_and_run_tests(cookies, default_extra_context): 164 | """Ensure that an without testrail doesn't break things""" 165 | extra_context = default_extra_context.copy() 166 | extra_context['testrail'] = "n" 167 | with bake_in_temp_dir(cookies, extra_context=extra_context) as result: 168 | assert result.project.isdir() 169 | project_path = str(result.project) 170 | run_inside_dir( 171 | 'docker run --rm -i -v {0}:/src davidemoro/pytest-play ' 172 | '--splinter-remote-url={1}'.format( 173 | project_path, 174 | default_extra_context['selenium_grid_url']), 175 | str(result.project)) == 0 176 | 177 | 178 | def test_bake_with_no_play_and_run_tests(cookies, default_extra_context): 179 | """Ensure that an without testrail doesn't break things""" 180 | extra_context = default_extra_context.copy() 181 | extra_context['pytest_play'] = "n" 182 | with bake_in_temp_dir(cookies, extra_context=extra_context) as result: 183 | assert result.project.isdir() 184 | project_path = str(result.project) 185 | run_inside_dir( 186 | 'docker run --rm -i -v {0}:/src davidemoro/pytest-play ' 187 | '--splinter-remote-url={1}'.format( 188 | project_path, 189 | default_extra_context['selenium_grid_url']), 190 | str(result.project)) == 0 191 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, docs 3 | skipsdist = true 4 | 5 | [testenv:docs] 6 | basepython=python 7 | changedir=docs 8 | deps=sphinx 9 | commands= 10 | sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 11 | 12 | [testenv] 13 | passenv = BROWSERSTACK_ACCESS_KEY REMOTE_URL 14 | whitelist_externals = bash 15 | deps = 16 | -rrequirements_dev.txt 17 | commands = 18 | py.test {posargs} 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.dockerignore: -------------------------------------------------------------------------------- 1 | .* 2 | Dockerfile 3 | python 4 | docs 5 | **/*.egg-info 6 | **/*.pyc 7 | **/*.pyo 8 | **/*.swp 9 | **/*.log 10 | **/*.xml 11 | **/__pycache__ 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [Makefile] 18 | indent_style = tab 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * {{ cookiecutter.project_name }} version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # OSX useful to ignore 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # C extensions 31 | *.so 32 | 33 | # Distribution / packaging 34 | .Python 35 | env/ 36 | build/ 37 | develop-eggs/ 38 | dist/ 39 | downloads/ 40 | eggs/ 41 | .eggs/ 42 | lib/ 43 | lib64/ 44 | parts/ 45 | sdist/ 46 | var/ 47 | *.egg-info/ 48 | .installed.cfg 49 | *.egg 50 | 51 | # PyInstaller 52 | # Usually these files are written by a python script from a template 53 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 54 | *.manifest 55 | *.spec 56 | 57 | # Installer logs 58 | pip-log.txt 59 | pip-delete-this-directory.txt 60 | 61 | # Unit test / coverage reports 62 | htmlcov/ 63 | .tox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *,cover 70 | .hypothesis/ 71 | 72 | # Translations 73 | *.mo 74 | *.pot 75 | 76 | # Django stuff: 77 | *.log 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # IntelliJ Idea family of suites 83 | .idea 84 | *.iml 85 | ## File-based project format: 86 | *.ipr 87 | *.iws 88 | ## mpeltonen/sbt-idea plugin 89 | .idea_modules/ 90 | 91 | # PyBuilder 92 | target/ 93 | 94 | # Cookiecutter 95 | output/ 96 | python_boilerplate/ 97 | python/ 98 | junit-*.xml 99 | {{cookiecutter.project_slug}}/screenshots/ 100 | report.html 101 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.0.1dev (unreleased) 5 | --------------------- 6 | 7 | - Initial release 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-alpine 2 | 3 | COPY . /src 4 | WORKDIR /src 5 | 6 | 7 | RUN pip3 install --upgrade pip \ 8 | tox && \ 9 | apk update && \ 10 | apk add --no-cache git && \ 11 | apk add --no-cache build-base && \ 12 | apk add --no-cache postgresql-dev && \ 13 | apk add --no-cache mariadb-connector-c-dev 14 | 15 | ENTRYPOINT [ "tox"] 16 | CMD ["-epy36", "--", "-vvv", "--splinter-webdriver=remote", "--variables=credentials/credentials_template.yml", "--splinter-remote-url={{cookiecutter.selenium_grid_url}}", "--variables=capabilities/os/WIN10.json", "--variables=capabilities/browsers/chrome/CHROME59.json", "--variables=capabilities/resolutions/1280x1024.json"] 17 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | import groovy.json.JsonSlurper 3 | 4 | properties([parameters([ \ 5 | choice(choices: "DEV\nALPHA\nBETA\n", description: 'Environment where to run this job.', name: 'ENVIRONMENT'), \ 6 | choice(choices: "browsers/chrome/CHROME59.json\nbrowsers/chrome/CHROME.json\nbrowsers/edge/EDGE.json\nbrowsers/firefox/FIREFOX53.json\nbrowsers/firefox/FIREFOX.json\nbrowsers/ie/IE.json", description: 'Choose the browser', name: 'BROWSER'), \ 7 | choice(choices: "os/WIN10.json", description: 'Choose the OS', name: 'OS'), \ 8 | choice(choices: "resolutions/1280x1024.json", description: 'Choose the resolution', name: 'RESOLUTION'), \ 9 | string(defaultValue: '', description: '', name: 'KEYWORDS'), \ 10 | string(defaultValue: '', description: '', name: 'MARKERS'), \ 11 | booleanParam(defaultValue: false, description: 'Report test execution on Testrail', name: 'TESTRAIL_ENABLE'), \ 12 | booleanParam(defaultValue: false, description: 'Enable debug (it might slow down test executions)', name: 'DEBUG'), \ 13 | booleanParam(defaultValue: false, description: 'Block on first failure', name: 'BLOCK_FIRST_FAILURE'), \ 14 | string(defaultValue: '', description: 'Number of parallel executions. Leave empty if want a sequential test execution or set the desired number of parallelism (eg: 2)', name: 'PARALLEL_SESSIONS'), \ 15 | string(defaultValue: '', description: 'You can specify an alternative selenium grid url', name: 'SELENIUM_GRID_URL'), \ 16 | string(defaultValue: '', description: 'Number of test repetitions (for evaluating test robustness or random bugs until it fails)', name: 'COUNT'), \ 17 | booleanParam(defaultValue: false, description: 'Random tests execution', name: 'RANDOM_ENABLE'), \ 18 | string(defaultValue: '', description: 'Random seed', name: 'RANDOM_SEED'){%- if cookiecutter.pytest_play == 'n' %} \{%- else %}, \ 19 | text(defaultValue: '', description: 'Play and execute', name: 'PLAY') \ 20 | {%- endif %}]), pipelineTriggers([])]) 21 | 22 | 23 | // this requires a Jenkins docker executor tox-py36 24 | node('pytest-play') { 25 | // notifyMessage("start"); 26 | 27 | stage('Checkout') { 28 | checkout scm 29 | } 30 | 31 | try { 32 | stage('Run Test'){ 33 | env.ENVIRONMENT = params.ENVIRONMENT 34 | env.MARKERS = params.MARKERS 35 | env.KEYWORDS = params.KEYWORDS 36 | env.OS = params.OS 37 | env.BROWSER = params.BROWSER 38 | env.PARALLEL_SESSIONS = params.PARALLEL_SESSIONS 39 | env.RESOLUTION = params.RESOLUTION 40 | env.DEBUG = params.DEBUG 41 | env.TESTRAIL_ENABLE = params.TESTRAIL_ENABLE 42 | env.BLOCK_FIRST_FAILURE = params.BLOCK_FIRST_FAILURE 43 | env.SELENIUM_GRID_URL = params.SELENIUM_GRID_URL 44 | env.COUNT = params.COUNT 45 | env.RANDOM_ENABLE = params.RANDOM_ENABLE 46 | env.RANDOM_SEED = params.RANDOM_SEED 47 | {%- if cookiecutter.pytest_play == 'y' %}env.PLAY = params.PLAY{%- endif %} 48 | 49 | sh "./ci.py" 50 | } 51 | 52 | // notifyMessage("success") 53 | } 54 | catch (exception) { 55 | // notifyMessage("error") 56 | throw exception 57 | } 58 | } 59 | 60 | /* 61 | def notifyMessage(String status) { 62 | def colorSet = ["start": "warning", "success": "good", "error": "danger"] 63 | def message = "${status} - Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})" 64 | 65 | slackSend(color: "${colorSet[status]}", 66 | channel: '#deployments', 67 | message: "${message}", 68 | teamDomain: 'team-domain', 69 | token: 'TOKEN') 70 | } 71 | */ 72 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | {{cookiecutter.project_slug}} 3 | ========= 4 | 5 | py.test 6 | ======= 7 | 8 | This test automation project is based on ``py.test``. 9 | ``py.test`` is a Python test framework that provides a clever dependency injection 10 | mechanism for fixture management with strong focus on reusability and modularity. 11 | 12 | Know more: 13 | 14 | * http://pytest.org 15 | * https://pytest.org/latest/fixture.html 16 | * https://pytest.org/latest/example/markers.html 17 | * https://pytest.org/latest/example/simple.html 18 | 19 | Prerequisites 20 | ============= 21 | 22 | Docker. 23 | 24 | 25 | How to use it 26 | ============= 27 | 28 | Docker 29 | ------ 30 | 31 | :: 32 | 33 | $ docker run --rm -it -v $(pwd):/src davidemoro/pytest-play 34 | 35 | For further info see: 36 | 37 | * https://github.com/davidemoro/cookiecutter-qa/ 38 | 39 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/chrome/CHROME.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "Chrome" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/chrome/CHROME59.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "Chrome", 4 | "browser_version": "59.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/edge/EDGE.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "Edge" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/firefox/FIREFOX.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "Firefox" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/firefox/FIREFOX53.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "Firefox", 4 | "browser_version": "53.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/browsers/ie/IE.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browser": "INTERNETEXPLORER" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "browserstack.debug": true, 4 | "browserstack.networkLogs": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/os/WIN10.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "os": "Windows", 4 | "os_version": "10" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "project": "{{cookiecutter.project_slug}}" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/capabilities/resolutions/1280x1024.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": { 3 | "resolution": "1280x1024" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/ci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import os 5 | import sys 6 | import subprocess 7 | try: 8 | from shlex import quote 9 | except ImportError: 10 | from pipes import quote 11 | 12 | 13 | def quoted_args(args): 14 | for arg in args: 15 | if re.match('^[.:/\-\w]+$', arg): 16 | yield arg 17 | else: 18 | yield quote(arg) 19 | 20 | 21 | if __name__ == "__main__": 22 | environment = os.getenv('ENVIRONMENT') 23 | markers = os.getenv('MARKERS') 24 | keywords = os.getenv('KEYWORDS') 25 | os_version = os.getenv('OS') 26 | browser = os.getenv('BROWSER') 27 | parallel_sessions = os.getenv('PARALLEL_SESSIONS') 28 | count = os.getenv('COUNT') 29 | junit_output = 'results/{0}.xml'.format(os.getenv('BUILD_ID')) 30 | grid_url = os.getenv('SELENIUM_GRID_URL', '') 31 | fallback_grid_url = '{{cookiecutter.selenium_grid_url}}' 32 | selenium_grid_url = grid_url or fallback_grid_url 33 | play = os.getenv('PLAY', None) 34 | random_enable = os.getenv('RANDOM_ENABLE') 35 | random_seed = os.getenv('RANDOM_SEED') 36 | 37 | os_file = 'capabilities/{0}'.format(os_version) 38 | browser_file = 'capabilities/{0}'.format(browser) 39 | resolution_file = 'capabilities/{0}'.format(os.getenv('RESOLUTION')) 40 | credentials_file = 'credentials/credentials-{0}.yml'.format(environment) 41 | 42 | pytest_cmd = [ 43 | "pytest", 44 | "-vvv", 45 | "--variables", 46 | "capabilities/project.json", 47 | "--variables", 48 | os_file, 49 | "--variables", 50 | browser_file, 51 | "--variables", 52 | resolution_file, 53 | "--splinter-webdriver", 54 | "remote", 55 | "--splinter-remote-url", 56 | selenium_grid_url, 57 | "--variables", 58 | credentials_file, 59 | "--junitxml", 60 | junit_output, 61 | ] 62 | 63 | if play: 64 | with open( 65 | os.path.join('{{cookiecutter.project_slug}}', 66 | 'tests', 67 | 'play', 68 | 'play.yml'), 69 | 'w') as file_obj: 70 | file_obj.write(play) 71 | 72 | if markers: 73 | pytest_cmd.extend([ 74 | "-m", 75 | markers, 76 | ]) 77 | 78 | if keywords: 79 | pytest_cmd.extend([ 80 | "-k", 81 | keywords 82 | ]) 83 | 84 | if os.getenv('DEBUG') == 'true': 85 | pytest_cmd.extend([ 86 | "--variables", 87 | "capabilities/debug.json" 88 | ]) 89 | 90 | if random_enable == 'true': 91 | if random_seed: 92 | # we use a random seed if specified 93 | pytest_cmd.extend([ 94 | "--randomly-seed", 95 | random_seed 96 | ]) 97 | else: 98 | # no random, disable pytest-randomly 99 | pytest_cmd.extend([ 100 | "-p", 101 | "no:randomly" 102 | ]) 103 | 104 | if os.getenv('TESTRAIL_ENABLE') == 'true': 105 | testrail_file = 'testrail.cfg' 106 | assert os.path.isfile(testrail_file) 107 | 108 | tr_name = '[{0}][{1}][{2}][{3}][{4}]'.format( 109 | environment, os_version, browser, markers, keywords) 110 | 111 | pytest_cmd.extend([ 112 | "--testrail", 113 | "--tr-config", 114 | testrail_file, 115 | "--tr-testrun-name", 116 | tr_name, 117 | ]) 118 | 119 | if os.getenv('BLOCK_FIRST_FAILURE') == 'true': 120 | pytest_cmd.append('-x') 121 | 122 | if parallel_sessions: 123 | pytest_cmd.extend([ 124 | "-n", 125 | parallel_sessions, 126 | ]) 127 | 128 | if count and count > 0: 129 | pytest_cmd.extend([ 130 | "--count", 131 | count, 132 | ]) 133 | 134 | pytest_cmd.extend(sys.argv[1:]) 135 | 136 | print(" ".join(quoted_args(pytest_cmd))) 137 | subprocess.check_call(pytest_cmd) 138 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/credentials/credentials_template.yml: -------------------------------------------------------------------------------- 1 | skins: 2 | skin1: 3 | base_url: {{cookiecutter.base_url}} 4 | credentials: 5 | Administrator: 6 | username: admin 7 | password: adminpwd 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -s --strict --log-cli-level=INFO --variables credentials/credentials_template.yml --splinter-webdriver remote --splinter-remote-url {{ cookiecutter.selenium_grid_url }} {{cookiecutter.project_slug}} 3 | log_format = %(asctime)s %(levelname)s %(message)s 4 | log_date_format = %Y-%m-%d %H:%M:%S 5 | python_files = test*py 6 | pep8ignore = E501 E122 E123 E125 E128 E711 E402 7 | markers = 8 | user_id: mark test to be run as the given user 9 | UI: UI related tests 10 | login: login related tests 11 | logout: logout related tests 12 | bdd_features_base_dir = {{cookiecutter.project_slug}}/features/ 13 | filterwarnings = 14 | ignore::pytest.RemovedInPytest4Warning 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements.txt: -------------------------------------------------------------------------------- 1 | -r https://github.com/davidemoro/pytest-play-docker/blob/master/requirements.txt 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = '{{ cookiecutter.version }}dev' 4 | 5 | 6 | install_requires = [ 7 | 'pytest-pypom-navigation', 8 | 'colander', 9 | 'pytest-variables[yaml]', 10 | 'pytest-bdd', 11 | 'pytest-splinter', 12 | 'pypom_form', 13 | {%- if cookiecutter.testrail == 'y' %} 14 | 'pytest-testrail', 15 | {%- endif %} 16 | ] 17 | 18 | {%- if cookiecutter.pytest_play == 'y' %} 19 | play_require = [ 20 | 'pytest-play', 21 | 'play_selenium', 22 | 'play_requests', 23 | 'play_sql', 24 | 'play_cassandra', 25 | 'play_dynamodb', 26 | 'play_websocket', 27 | 'play_mqtt', 28 | ] 29 | {%- endif %} 30 | 31 | tests_require = [ 32 | 'pycodestyle', 33 | 'pytest-cov', 34 | 'tox', 35 | 'mock', 36 | 'pytest-html', 37 | 'pytest-repeat', 38 | 'pytest-randomly', 39 | ] 40 | 41 | {%- set license_classifiers = { 42 | 'MIT license': 'License :: OSI Approved :: MIT License', 43 | 'BSD license': 'License :: OSI Approved :: BSD License', 44 | 'ISC license': 'License :: OSI Approved :: ISC License (ISCL)', 45 | 'Apache Software License 2.0': 'License :: OSI Approved :: Apache Software License', 46 | 'GNU General Public License v3': 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)' 47 | } %} 48 | 49 | setup(name='{{cookiecutter.project_slug}}', 50 | version=version, 51 | description="{{ cookiecutter.project_short_description }}", 52 | long_description=open("README.rst").read() + "\n" + 53 | open("CHANGES.rst").read(), 54 | # Get more strings from 55 | # http://pypi.python.org/pypi?:action=list_classifiers 56 | classifiers=[ 57 | "Programming Language :: Python", 58 | "Intended Audience :: Developers", 59 | "Framework :: Pytest", 60 | "Topic :: Software Development :: Testing", 61 | "Programming Language :: Python :: 2.7", 62 | "Programming Language :: Python :: 3.5", 63 | "Programming Language :: Python :: 3.6", 64 | "Programming Language :: Python :: Implementation :: PyPy", 65 | ], 66 | keywords='{{ cookiecutter.project_slug }}', 67 | author_email='{{ cookiecutter.email }}', 68 | packages=find_packages(exclude=['ez_setup']), 69 | include_package_data=True, 70 | zip_safe=False, 71 | install_requires=install_requires, 72 | entry_points=""" 73 | # -*- Entry points: -*- 74 | """, 75 | extras_require={ 76 | 'tests': tests_require, 77 | {%- if cookiecutter.pytest_play == 'y' %} 78 | 'play': play_require, 79 | {%- endif %} 80 | }, 81 | ) 82 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/testrail.cfg: -------------------------------------------------------------------------------- 1 | [API] 2 | url = https://YOURPROJECT.testrail.net/ 3 | email = {{cookiecutter.email}} 4 | password = PASSWORD 5 | 6 | [TESTRUN] 7 | assignedto_id = ID 8 | project_id = ID 9 | suite_id = ID 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36 3 | skip_missing_interpreters = true 4 | 5 | [testenv] 6 | passenv = * 7 | deps= 8 | pip 9 | wheel 10 | -rrequirements.txt 11 | -e. 12 | commands=make test 13 | 14 | [testenv:firefox] 15 | commands={[testenv]commands} --splinter-webdriver=firefox 16 | 17 | [testenv:chrome] 18 | commands={[testenv]commands} --splinter-webdriver=chrome 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidemoro/cookiecutter-qa/a711b435c55ffdbc959c34f858022a4ecdd61d0e/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Page mappings config 3 | -------------------- 4 | Here you can define all your page mappings with path and optionally a 5 | class_page if you want your specific page object implementation. 6 | By default the :py:class:`{{cookiecutter.project_slug}}.pages.BasePage` will be instanciated. 7 | 8 | For example you could configure your own page object implementations: 9 | 10 | .. code-block:: python 11 | 12 | PAGE_MAPPINGS = { 13 | 'HomePage': {'path': '/'}, 14 | 'HelloPage': {'path': '/hello'}, 15 | 'AnotherPage': {'path': '/anotherpage', 'class_page': AnotherPage}, 16 | 'AnotherPage': { 17 | 'path': '/myfleet/cnh/app/view/io_profile.html', 18 | 'page_class': {'skin1': ListingPage, 19 | 'skin2': Listing2Page, 20 | 'fallback': ListingPage}, 21 | 'links': {'edit': 'DigitalInputEditPage'}}, 22 | } 23 | 24 | 25 | This is only an example, you can also decide to add extra optional info such 26 | as ``links`` that tells you where the current page leads you to another 27 | page or whatever you want. 28 | """ 29 | 30 | # Here we define the fallback page object classes if you don't specify 31 | # in PAGE_MAPPINGS any class 32 | DEFAULT_PAGES = { 33 | 'skin1': '{{cookiecutter.project_slug}}.pages.BasePage', 34 | } 35 | 36 | 37 | PAGE_MAPPINGS = { 38 | 'HomePage': {'path': '/'}, 39 | 'LoginPage': {'path': '/'}, 40 | } 41 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/features/functional/login.feature: -------------------------------------------------------------------------------- 1 | Feature: Login 2 | A user login try to login to the application 3 | 4 | @UI @login 5 | Scenario: Successful login 6 | Given I am logged in as Administrator 7 | Then I am logged in 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/features/functional/logout.feature: -------------------------------------------------------------------------------- 1 | Feature: Logout 2 | A logged user try to logout from the application 3 | 4 | @UI @logout 5 | Scenario: Successful logout 6 | Given I am logged in as Administrator 7 | When I logout from the application 8 | Then I land on the LoginPage page 9 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/pages/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import BasePage # noqa 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/pages/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base page implementation 3 | ======================== 4 | 5 | This is where the base page implementation lives. 6 | """ 7 | 8 | from pypom_navigation.pages import BasePage as BaseFormPage 9 | 10 | 11 | class BasePage(BaseFormPage): 12 | """ 13 | This is the base page to be used in {{cookiecutter.project_slug}}. 14 | 15 | Once you clone {{cookiecutter.project_slug}} for your own qa project, you should: 16 | 17 | * implement the login, is_loggedin and logout methods of this class 18 | * add your own page object classes depending on your business 19 | logics inheriting from this base class 20 | """ 21 | 22 | def login(self, username, password): 23 | """ 24 | This is the login method of the base page object. 25 | 26 | It's up to you implement this method once you cloned 27 | {{cookiecutter.project_slug}}. 28 | 29 | :return: BasePage instance 30 | :rtype: object 31 | """ 32 | 33 | def is_loggedin(self): 34 | """ 35 | This is the is_loggedin method of the base page object. 36 | 37 | It's up to you implement this method once you cloned 38 | {{cookiecutter.project_slug}}. 39 | 40 | :return: True if you are logged in or False 41 | :rtype: bool 42 | """ 43 | return True 44 | 45 | def username(self): 46 | """ 47 | This is the username method of the base page object. 48 | 49 | It's up to you implement this method once you cloned 50 | {{cookiecutter.project_slug}}. 51 | 52 | :return: the username or None 53 | :rtype: string or None 54 | """ 55 | return 'admin' 56 | 57 | def logout(self): 58 | """ 59 | This is the logout method of the base page object. 60 | 61 | It's up to you implement this method once you cloned 62 | {{cookiecutter.project_slug}}. 63 | 64 | :return: BasePage instance 65 | :rtype: object 66 | """ 67 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidemoro/cookiecutter-qa/a711b435c55ffdbc959c34f858022a4ecdd61d0e/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fixture Diagrams 3 | ---------------- 4 | 5 | The following diagram shows the interactions between the `pytest fixtures`_ 6 | created in the ``{{cookiecutter.project_slug}}`` package: 7 | 8 | .. graphviz:: 9 | 10 | digraph { 11 | credentials_mapping; 12 | base_url; 13 | page_mappings, 14 | default_page_class; 15 | base_page; 16 | page_instance; 17 | navigation; 18 | navigation_class; 19 | variables [color="grey"]; 20 | browser [color="grey"]; 21 | variables -> {credentials_mapping base_url} [color="grey"]; 22 | page_mappings -> {default_page_class base_page navigation} 23 | base_url -> {base_page navigation}; 24 | browser -> {base_page} [color="grey"]; 25 | default_page_class -> {base_page navigation}; 26 | base_page -> {page_instance}; 27 | navigation_class -> {navigation}; 28 | page_instance -> {navigation}; 29 | credentials_mapping -> {navigation}; 30 | } 31 | 32 | 33 | .. _pytest fixtures: http://doc.pytest.org/en/latest/fixture.html 34 | """ 35 | 36 | import os 37 | import platform 38 | 39 | import pytest 40 | 41 | import {{cookiecutter.project_slug}} 42 | from {{cookiecutter.project_slug}}.config import DEFAULT_PAGES 43 | {%- if cookiecutter.pytest_play == 'y' %} 44 | 45 | 46 | @pytest.fixture(autouse=True) 47 | def bdd_vars(bdd_vars, variables, skin, data_base_path): 48 | """ Inject bdd_vars so they becomes available in play 49 | fixture """ 50 | bdd_vars['data_base_path'] = data_base_path 51 | return bdd_vars 52 | 53 | 54 | @pytest.fixture 55 | def data_base_path(): 56 | """ where pytest-play json files live """ 57 | here = os.path.abspath(os.path.dirname(__file__)) 58 | data_path = os.path.join(here, 'data') 59 | if 'WIN' in platform.system().upper(): 60 | data_path = data_path.replace(os.sep, os.sep*2) 61 | return data_path 62 | {%- endif %} 63 | 64 | 65 | @pytest.fixture(scope="session") 66 | def default_pages(): 67 | """ A mapping with the default page object class for each skin 68 | 69 | It's up to you override this fixture with your settings. 70 | 71 | For example:: 72 | 73 | DEFAULT_PAGES = { 74 | 'skin1': 'mypackage.pages.BasePage', 75 | } 76 | """ 77 | return DEFAULT_PAGES 78 | 79 | 80 | @pytest.fixture(scope='session') 81 | def base_url(skin, variables): 82 | """ Returns the base_url associated to the skin. 83 | """ 84 | return variables['skins'][skin]['base_url'] 85 | 86 | 87 | @pytest.fixture(scope="session") 88 | def page_mappings(): 89 | """ 90 | Returns the page mappings dictionary with all known page with: 91 | 92 | * paths 93 | * optional page object class (otherwise the default implementation 94 | will be used as fallback provided by :py:func:`default_page_class`) 95 | 96 | See :py:mod:`{{cookiecutter.project_slug}}.config` for further details. 97 | 98 | :return: dictionary with all known pages 99 | :rtype: dict` 100 | """ 101 | return {{cookiecutter.project_slug}}.config.PAGE_MAPPINGS 102 | 103 | 104 | @pytest.fixture(scope="session") 105 | def splinter_screenshot_dir(): 106 | """Feature files base directory.""" 107 | return os.path.join(os.path.dirname({{cookiecutter.project_slug}}.__file__), 'screenshots') 108 | 109 | 110 | @pytest.fixture(scope='session') 111 | def splinter_driver_kwargs(splinter_webdriver, variables, metadata): 112 | """Webdriver kwargs.""" 113 | capabilities = {} 114 | if splinter_webdriver == 'remote': 115 | build = metadata.get('BUILD_TAG', None) 116 | if build: 117 | capabilities['build'] = build 118 | capabilities.update(variables.get('capabilities', {})) 119 | return capabilities 120 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidemoro/cookiecutter-qa/a711b435c55ffdbc959c34f858022a4ecdd61d0e/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/conftest.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Login feature tests.""" 3 | 4 | from pytest_bdd import ( 5 | given, 6 | when, 7 | then, 8 | ) 9 | from pytest_bdd.parsers import ( 10 | parse, 11 | ) 12 | 13 | 14 | @given(parse('I am logged in as {user_id}')) 15 | def username(user_id, navigation): 16 | """Login and returns username for the given user_id """ 17 | username, password = navigation.get_credentials(user_id) 18 | navigation.visit_page('LoginPage') 19 | navigation.page.login(username, password) 20 | return username 21 | 22 | 23 | # we need to change step description due to a nasty limitation of pytest-bdd. 24 | # See https://github.com/pytest-dev/pytest-bdd/issues/199 25 | 26 | @given(parse( 27 | '[outline] I am on the page')) 28 | @given(parse( 29 | 'I am on the {page_id} page')) 30 | def loggedin_page_given_outline(page_id, navigation): 31 | """Logged in fixture""" 32 | navigation.visit_page(page_id) 33 | 34 | 35 | def _check_page_url(page, page_id): 36 | """ Check page url matches used by several BDD steps. 37 | Page is not a dictionary but the page object. 38 | """ 39 | assert page.navigation.get_page_url(page_id) in page.current_url 40 | 41 | 42 | @when(parse( 43 | 'I visit the {page_id} page')) 44 | def visit_page(navigation, page_id): 45 | """Visit the page.""" 46 | navigation.visit_page(page_id) 47 | 48 | 49 | @when(parse( 50 | 'I logout from the application')) 51 | def check_logout_when(navigation): 52 | """Check the user logout.""" 53 | page = navigation.page 54 | page.logout() 55 | 56 | 57 | @then(parse( 58 | 'the page contains text ')) 59 | def page_text_check(navigation, text): 60 | page = navigation.page 61 | assert page.has_text(text) 62 | 63 | 64 | @then(parse( 65 | 'I land on the {page_id} page')) 66 | @then(parse( 67 | 'I land on the page')) 68 | def check_page_url_no_follow(navigation, 69 | page_id): 70 | """ Check page url matches """ 71 | page = navigation.page 72 | _check_page_url(page, page_id) 73 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/test_basic.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_basic(browser, base_url): 4 | browser.visit(base_url) 5 | assert '{{cookiecutter.base_url}}' in browser.url 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/test_login.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Login feature tests.""" 3 | 4 | from functools import partial 5 | from pytest_bdd import ( 6 | scenario as bdd_scenario, 7 | then, 8 | ) 9 | 10 | scenario = partial(bdd_scenario, "functional/login.feature") 11 | 12 | 13 | @scenario("Successful login") 14 | def test_successfull_login(): 15 | """Login.""" 16 | 17 | 18 | @then('I am logged in') 19 | def check_loggedin_then(navigation, username): 20 | """Assert user is logged in. Implement here your 21 | project related login logics. 22 | """ 23 | page = navigation.page 24 | assert page.is_loggedin() 25 | assert page.username() == username 26 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/functional/test_logout.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Logout feature tests.""" 3 | 4 | from functools import partial 5 | import pytest_bdd 6 | 7 | scenario = partial(pytest_bdd.scenario, "functional/logout.feature") 8 | 9 | 10 | @scenario("Successful logout") 11 | def test_successfull_logout(): 12 | """Logout.""" 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/tests/play/test_play.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - provider: selenium 3 | type: get 4 | url: "$base_url" 5 | - provider: selenium 6 | type: assertElementVisible 7 | locator: 8 | type: css 9 | value: body 10 | --------------------------------------------------------------------------------