├── .env.example ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── components └── cookies_modal.py ├── config.py ├── conftest.py ├── docker-compose.yml ├── docs ├── app.js ├── data │ ├── attachments │ │ ├── 1ff1771b8f3f9c2d.png │ │ ├── 26df83d53f3fd1b5.txt │ │ ├── 2c1a394f90965df0.png │ │ ├── 37acdc13563560ff.png │ │ ├── 66046cfbfb0c166a.txt │ │ ├── 83092f481be98b0e.txt │ │ ├── 85dc6c4107a318ce.txt │ │ ├── 8cc046037c123ce5.txt │ │ ├── a27610a147fbfc81.png │ │ └── b07a9dbc18ac810.png │ ├── behaviors.csv │ ├── behaviors.json │ ├── categories.csv │ ├── categories.json │ ├── packages.json │ ├── suites.csv │ ├── suites.json │ ├── test-cases │ │ ├── 27c7177e1c0b2bec.json │ │ ├── 4016c4e2667a7019.json │ │ ├── 50e101896b909c0f.json │ │ ├── 75cc35bef1b0498.json │ │ └── dfff93f27ad3dbb6.json │ └── timeline.json ├── export │ ├── influxDbData.txt │ ├── mail.html │ └── prometheusData.txt ├── favicon.ico ├── history │ ├── categories-trend.json │ ├── duration-trend.json │ ├── history-trend.json │ ├── history.json │ └── retry-trend.json ├── index.html ├── plugins │ ├── behaviors │ │ └── index.js │ ├── packages │ │ └── index.js │ └── screen-diff │ │ ├── index.js │ │ └── styles.css ├── styles.css └── widgets │ ├── behaviors.json │ ├── categories-trend.json │ ├── categories.json │ ├── duration-trend.json │ ├── duration.json │ ├── environment.json │ ├── executors.json │ ├── history-trend.json │ ├── launch.json │ ├── retry-trend.json │ ├── severity.json │ ├── status-chart.json │ ├── suites.json │ └── summary.json ├── models └── customer.py ├── page_factory ├── button.py ├── component.py ├── editor.py ├── input.py ├── table_data.py ├── table_row.py ├── text.py ├── textarea.py └── title.py ├── pages ├── base_page.py └── try_sql_page.py ├── pytest.ini ├── requirements.txt ├── tests ├── conftest.py └── test_customers_sql.py └── utils ├── constants ├── features.py ├── routes.py └── suites.py ├── fakers ├── numbers.py └── strings.py ├── logger.py ├── types └── webdriver │ └── driver │ ├── element.py │ ├── element_should.py │ ├── element_wait.py │ ├── elements.py │ ├── elements_should.py │ ├── page.py │ └── page_wait.py └── webdriver ├── driver ├── element.py ├── element_should.py ├── element_wait.py ├── elements.py ├── elements_should.py ├── page.py ├── page_wait.py └── utils.py └── factory ├── browser.py ├── builders.py ├── capabilities.py ├── factory.py └── options.py /.env.example: -------------------------------------------------------------------------------- 1 | REMOTE_URL="http://selenium-hub:4444/wd/hub" 2 | BASE_URL="https://www.w3schools.com" 3 | SCREENSHOTS_ON=true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | .idea 10 | allure-results 11 | allure-report 12 | screenshots 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # poetry 103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 107 | #poetry.lock 108 | 109 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 110 | __pypackages__/ 111 | 112 | # Celery stuff 113 | celerybeat-schedule 114 | celerybeat.pid 115 | 116 | # SageMath parsed files 117 | *.sage.py 118 | 119 | # Environments 120 | .env 121 | .venv 122 | env/ 123 | venv/ 124 | ENV/ 125 | env.bak/ 126 | venv.bak/ 127 | 128 | # Spyder project settings 129 | .spyderproject 130 | .spyproject 131 | 132 | # Rope project settings 133 | .ropeproject 134 | 135 | # mkdocs documentation 136 | /site 137 | 138 | # mypy 139 | .mypy_cache/ 140 | .dmypy.json 141 | dmypy.json 142 | 143 | # Pyre type checker 144 | .pyre/ 145 | 146 | # pytype static type analyzer 147 | .pytype/ 148 | 149 | # Cython debug symbols 150 | cython_debug/ 151 | 152 | # PyCharm 153 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 154 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 155 | # and can be added to the global gitignore or merged into this file. For a more nuclear 156 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 157 | #.idea/ 158 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.confirmSync": false, 3 | "editor.formatOnPaste": true, 4 | "editor.formatOnSave": true, 5 | "python.formatting.provider": "autopep8", 6 | "explorer.confirmDelete": false, 7 | "explorer.confirmDragAndDrop": false, 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "[python]": { 10 | "editor.defaultFormatter": "ms-python.python" 11 | }, 12 | "cSpell.words": [], 13 | "window.zoomLevel": 0.5, 14 | "python.envFile": "${workspaceFolder}/.venv", 15 | "python.linting.enabled": true, 16 | "python.analysis.typeCheckingMode": "off" 17 | } 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /code 3 | USER root 4 | COPY requirements.txt /code/ 5 | RUN pip install -r requirements.txt 6 | COPY . /code/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selenium python 2 | 3 | [Demo allure report](https://nikita-filonov.github.io/selenium_python/) 4 | 5 | ## Install project 6 | 7 | ``` 8 | git clone https://github.com/Nikita-Filonov/selenium_python.git 9 | cd selenium_python 10 | ``` 11 | 12 | ## Setup env 13 | 14 | Make sure to create `.env` file locally. You can copy all content from `.env.example` 15 | 16 | ``` 17 | REMOTE_URL="http://selenium-hub:4444/wd/hub" 18 | BASE_URL="https://www.w3schools.com" 19 | SCREENSHOTS_ON=true 20 | ``` 21 | 22 | ## Running locally 23 | 24 | If you want to run auto tests locally, make sure to install `python 3.10` 25 | 26 | ``` 27 | pip install virtualenv 28 | virtualenv .venv 29 | source .venv/bin/activate 30 | 31 | pip install -r requirements.txt 32 | 33 | python -m pytest -m ui --alluredir=./allure-results 34 | ``` 35 | 36 | For local run `.env` file should looks like 37 | 38 | ``` 39 | REMOTE_URL="http://localhost:4444/wd/hub" # if you want to send auto tests to selenium hub, make sure docker-compose running 40 | # REMOTE_URL= # if you want to run auto tests on you machine 41 | BASE_URL="https://www.w3schools.com" 42 | SCREENSHOTS_ON=true 43 | ``` 44 | 45 | ## Running in docker 46 | 47 | Assume you have `docker`, `docker-compose` installed 48 | 49 | ``` 50 | docker-compose up 51 | ``` 52 | 53 | As artifact of the auto tests run in the docker container 54 | you will get `allure-results` folder 55 | 56 | ## Create report 57 | 58 | Run report server locally 59 | 60 | ``` 61 | allure serve 62 | ``` 63 | 64 | Or generate report 65 | 66 | ``` 67 | allure generate 68 | ``` 69 | 70 | This command will create folder `allure-report`. Open `allure-report` folder and open `index.html` file 71 | -------------------------------------------------------------------------------- /components/cookies_modal.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from selenium.common.exceptions import TimeoutException 3 | 4 | from page_factory.button import Button 5 | from utils.logger import logger 6 | from utils.webdriver.driver.page import Page 7 | 8 | 9 | class CookiesModal: 10 | def __init__(self, page: Page) -> None: 11 | self.accept_button = Button( 12 | page, 13 | locator='//div[@id="accept-choices"]', 14 | name='Accept all & visit the site' 15 | ) 16 | self.customize_button = Button( 17 | page, 18 | locator='//div[@id="sn-b-custom"]', 19 | name='Customise choices' 20 | ) 21 | 22 | @allure.step('Accepting all cookies') 23 | def accept_cookies(self): 24 | try: 25 | if self.accept_button.is_displayed() and self.customize_button.is_displayed(): 26 | self.accept_button.click() 27 | except TimeoutException: 28 | logger.error('Cookies modal did not appear') 29 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from pydantic import BaseModel, BaseSettings, Field 4 | 5 | from utils.webdriver.factory.browser import Browser 6 | 7 | 8 | class DriverConfig(BaseSettings): 9 | browser: Browser = Field(default=Browser.CHROME, env="BROWSER") 10 | remote_url: str = Field(default="", env="REMOTE_URL") 11 | wait_time: int = 10 12 | page_load_wait_time: int = 0 13 | options: list[str] = [ 14 | "ignore-certificate-errors", 15 | "--no-sandbox", 16 | "disable-infobars", 17 | '--headless', 18 | '--disable-extensions', 19 | '--disable-gpu' 20 | ] 21 | capabilities: dict[str, str] = {} 22 | experimental_options: list[dict] | None = None 23 | seleniumwire_options: dict = {} 24 | extension_paths: list[str] | None = None 25 | webdriver_kwargs: dict | None = None 26 | version: str | None 27 | local_path: str = "" 28 | 29 | class Config: 30 | env_file = '.env' 31 | env_file_encoding = 'utf-8' 32 | 33 | 34 | class LoggingConfig(BaseSettings): 35 | log_level: str = "INFO" 36 | screenshots_on: bool = Field(default=True, env="SCREENSHOTS_ON") 37 | screenshots_dir: str = Field( 38 | default='./screenshots', env="SCREENSHOTS_DIR" 39 | ) 40 | 41 | class Config: 42 | env_file = '.env' 43 | env_file_encoding = 'utf-8' 44 | 45 | 46 | class ViewportConfig(BaseModel): 47 | maximize: bool = True 48 | width: int = 1440 49 | height: int = 900 50 | orientation: str = "portrait" 51 | 52 | 53 | class UIConfig(BaseSettings): 54 | base_url: str = Field(env="BASE_URL") 55 | driver: DriverConfig = DriverConfig() 56 | logging: LoggingConfig = LoggingConfig() 57 | viewport: ViewportConfig = ViewportConfig() 58 | custom: dict = {} 59 | 60 | class Config: 61 | env_file = '.env' 62 | env_file_encoding = 'utf-8' 63 | 64 | 65 | @lru_cache() 66 | def get_ui_config() -> UIConfig: 67 | return UIConfig() 68 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from config import UIConfig 4 | from utils.webdriver.driver.page import Page 5 | from utils.webdriver.driver.utils import attach_screenshot 6 | 7 | 8 | @pytest.fixture(scope='session') 9 | def ui_config() -> UIConfig: 10 | return UIConfig() 11 | 12 | 13 | @pytest.fixture(scope='function') 14 | def page(request: pytest.FixtureRequest, ui_config: UIConfig) -> Page: 15 | page_client = Page(ui_config) 16 | page_client.wait_until_stable() 17 | yield page_client 18 | 19 | if ui_config.logging.screenshots_on: 20 | attach_screenshot(page_client, request.node.name) 21 | 22 | page_client.quit() 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | tests: 4 | build: . 5 | command: python -m pytest -m ui --alluredir=./allure-results 6 | volumes: 7 | - .:/code 8 | env_file: 9 | - .env 10 | networks: 11 | - selenium 12 | depends_on: 13 | - chrome 14 | - selenium-hub 15 | chrome: 16 | image: selenium/node-chrome:latest 17 | shm_size: 2gb 18 | networks: 19 | - selenium 20 | depends_on: 21 | - selenium-hub 22 | environment: 23 | - SE_EVENT_BUS_HOST=selenium-hub 24 | - SE_EVENT_BUS_PUBLISH_PORT=4442 25 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 26 | ports: 27 | - "7900:7900" 28 | 29 | selenium-hub: 30 | image: selenium/hub:latest 31 | container_name: selenium-hub 32 | networks: 33 | - selenium 34 | ports: 35 | - "4442:4442" 36 | - "4443:4443" 37 | - "4444:4444" 38 | expose: 39 | - "4444" 40 | 41 | networks: 42 | selenium: 43 | name: selenium 44 | -------------------------------------------------------------------------------- /docs/data/attachments/1ff1771b8f3f9c2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/attachments/1ff1771b8f3f9c2d.png -------------------------------------------------------------------------------- /docs/data/attachments/26df83d53f3fd1b5.txt: -------------------------------------------------------------------------------- 1 | INFO  Logger:page.py:70 Page.wait_for_alive() - Page wait until driver stable 2 | INFO  Logger:page.py:138 Page.maximize_window() - Maximize browser window 3 | INFO  Logger:page.py:58 Page.visit() - Visit URL: `https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all` 4 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 5 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 6 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 7 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 8 | INFO  Logger:element.py:35 Element.click() - Click this element 9 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 10 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 11 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 12 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 13 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 14 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 15 | INFO  Logger:element.py:35 Element.click() - Click this element 16 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[2]` 17 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[2]` 18 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[3]` 19 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[3]` 20 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[4]` 21 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[4]` 22 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[5]` 23 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[5]` 24 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[6]` 25 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[6]` 26 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[7]` 27 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="tKEGXG2rvIaH"]/../td[7]` 28 | INFO  Logger:page.py:132 Page.screenshot() - Save screenshot to: `screenshots/test_create_customer[create_customer0].png` 29 | INFO  Logger:page.py:125 Page.quit() - Quit page and close all windows from the browser session -------------------------------------------------------------------------------- /docs/data/attachments/2c1a394f90965df0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/attachments/2c1a394f90965df0.png -------------------------------------------------------------------------------- /docs/data/attachments/37acdc13563560ff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/attachments/37acdc13563560ff.png -------------------------------------------------------------------------------- /docs/data/attachments/66046cfbfb0c166a.txt: -------------------------------------------------------------------------------- 1 | INFO  Logger:page.py:70 Page.wait_for_alive() - Page wait until driver stable 2 | INFO  Logger:page.py:138 Page.maximize_window() - Maximize browser window 3 | INFO  Logger:page.py:58 Page.visit() - Visit URL: `https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all` 4 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 5 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 6 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 7 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 8 | INFO  Logger:element.py:35 Element.click() - Click this element 9 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 10 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 11 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 12 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 13 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 14 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 15 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 16 | INFO  Logger:element.py:35 Element.click() - Click this element 17 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 18 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 19 | INFO  Logger:page.py:132 Page.screenshot() - Save screenshot to: `screenshots/test_delete_customer[1].png` 20 | INFO  Logger:page.py:125 Page.quit() - Quit page and close all windows from the browser session -------------------------------------------------------------------------------- /docs/data/attachments/83092f481be98b0e.txt: -------------------------------------------------------------------------------- 1 | INFO  Logger:page.py:70 Page.wait_for_alive() - Page wait until driver stable 2 | INFO  Logger:page.py:138 Page.maximize_window() - Maximize browser window 3 | INFO  Logger:page.py:58 Page.visit() - Visit URL: `https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all` 4 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 5 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 6 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 7 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 8 | INFO  Logger:element.py:35 Element.click() - Click this element 9 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="Via Ludovico il Moro 22"]/../td[4]` 10 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="Via Ludovico il Moro 22"]/../td[4]` 11 | INFO  Logger:page.py:132 Page.screenshot() - Save screenshot to: `screenshots/test_select_all_customers[Via Ludovico il Moro 22-Via Ludovico il Moro 22].png` 12 | INFO  Logger:page.py:125 Page.quit() - Quit page and close all windows from the browser session -------------------------------------------------------------------------------- /docs/data/attachments/85dc6c4107a318ce.txt: -------------------------------------------------------------------------------- 1 | INFO  Logger:page.py:70 Page.wait_for_alive() - Page wait until driver stable 2 | INFO  Logger:page.py:138 Page.maximize_window() - Maximize browser window 3 | INFO  Logger:page.py:58 Page.visit() - Visit URL: `https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all` 4 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 5 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 6 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 7 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 8 | INFO  Logger:element.py:35 Element.click() - Click this element 9 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 10 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 11 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div` 12 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 13 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 14 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 15 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 16 | INFO  Logger:element.py:35 Element.click() - Click this element 17 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[1]` 18 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[1]` 19 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[2]` 20 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[2]` 21 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[3]` 22 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[3]` 23 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[4]` 24 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[4]` 25 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[5]` 26 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[5]` 27 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[6]` 28 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[6]` 29 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[7]` 30 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//tr/td[text()="1"]/../td[7]` 31 | INFO  Logger:page.py:132 Page.screenshot() - Save screenshot to: `screenshots/test_update_customer[update_customer0-1].png` 32 | INFO  Logger:page.py:125 Page.quit() - Quit page and close all windows from the browser session -------------------------------------------------------------------------------- /docs/data/attachments/8cc046037c123ce5.txt: -------------------------------------------------------------------------------- 1 | INFO  Logger:page.py:70 Page.wait_for_alive() - Page wait until driver stable 2 | INFO  Logger:page.py:138 Page.maximize_window() - Maximize browser window 3 | INFO  Logger:page.py:58 Page.visit() - Visit URL: `https://www.w3schools.com/sql/trysql.asp?filename=trysql_select_all` 4 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]` 5 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue('');` into the Browser 6 | INFO  Logger:page.py:144 Page.execute_script() - Execute javascript `window.editor.setValue(arguments[0]);` into the Browser 7 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//button[text()="Run SQL »"]` 8 | INFO  Logger:element.py:35 Element.click() - Click this element 9 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div//div` 10 | INFO  Logger:page.py:79 Page.get_xpath() - Get the element with xpath: `//div[@id="divResultSQL"]//div//div` 11 | INFO  Logger:page.py:99 Page.find_xpath() - Get the elements with xpath: `//div[@id="divResultSQL"]//tbody//tr` 12 | INFO  Logger:elements_should.py:47 Elements.should().not_be_empty() 13 | INFO  Logger:elements_should.py:26 Elements.should().have_length(): 7 14 | INFO  Logger:page.py:132 Page.screenshot() - Save screenshot to: `screenshots/test_filter_customers[6].png` 15 | INFO  Logger:page.py:125 Page.quit() - Quit page and close all windows from the browser session -------------------------------------------------------------------------------- /docs/data/attachments/a27610a147fbfc81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/attachments/a27610a147fbfc81.png -------------------------------------------------------------------------------- /docs/data/attachments/b07a9dbc18ac810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/attachments/b07a9dbc18ac810.png -------------------------------------------------------------------------------- /docs/data/behaviors.csv: -------------------------------------------------------------------------------- 1 | "Epic","Feature","Story","FAILED","BROKEN","PASSED","SKIPPED","UNKNOWN" 2 | "","Customers","","0","0","5","0","0" 3 | -------------------------------------------------------------------------------- /docs/data/behaviors.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "b1a8273437954620fa374b796ffaacdd", 3 | "name" : "behaviors", 4 | "children" : [ { 5 | "name" : "Customers", 6 | "children" : [ { 7 | "name" : "Select all customers", 8 | "uid" : "50e101896b909c0f", 9 | "parentUid" : "9817bdee84068b56fffc2b215a9a538d", 10 | "status" : "passed", 11 | "time" : { 12 | "start" : 1676194845034, 13 | "stop" : 1676194848602, 14 | "duration" : 3568 15 | }, 16 | "flaky" : false, 17 | "newFailed" : false, 18 | "parameters" : [ "'Via Ludovico il Moro 22'", "'Via Ludovico il Moro 22'" ] 19 | }, { 20 | "name" : "Filter customers", 21 | "uid" : "4016c4e2667a7019", 22 | "parentUid" : "9817bdee84068b56fffc2b215a9a538d", 23 | "status" : "passed", 24 | "time" : { 25 | "start" : 1676194849047, 26 | "stop" : 1676194850946, 27 | "duration" : 1899 28 | }, 29 | "flaky" : false, 30 | "newFailed" : false, 31 | "parameters" : [ "6" ] 32 | }, { 33 | "name" : "Create customer", 34 | "uid" : "dfff93f27ad3dbb6", 35 | "parentUid" : "9817bdee84068b56fffc2b215a9a538d", 36 | "status" : "passed", 37 | "time" : { 38 | "start" : 1676194851420, 39 | "stop" : 1676194856088, 40 | "duration" : 4668 41 | }, 42 | "flaky" : false, 43 | "newFailed" : false, 44 | "parameters" : [ "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" ] 45 | }, { 46 | "name" : "Update customer", 47 | "uid" : "27c7177e1c0b2bec", 48 | "parentUid" : "9817bdee84068b56fffc2b215a9a538d", 49 | "status" : "passed", 50 | "time" : { 51 | "start" : 1676194856526, 52 | "stop" : 1676194860261, 53 | "duration" : 3735 54 | }, 55 | "flaky" : false, 56 | "newFailed" : false, 57 | "parameters" : [ "1", "CreateCustomer(customer_name='cKQflX7kixwt', contact_name='4NpuUqp6MZOSWRq', address='bYOP1MTWCDy', city='i8rKllqcsxCUw', postal_code='461', country='et3QbrRx6DlAQE')" ] 58 | }, { 59 | "name" : "Delete customer", 60 | "uid" : "75cc35bef1b0498", 61 | "parentUid" : "9817bdee84068b56fffc2b215a9a538d", 62 | "status" : "passed", 63 | "time" : { 64 | "start" : 1676194860801, 65 | "stop" : 1676194863253, 66 | "duration" : 2452 67 | }, 68 | "flaky" : false, 69 | "newFailed" : false, 70 | "parameters" : [ "1" ] 71 | } ], 72 | "uid" : "9817bdee84068b56fffc2b215a9a538d" 73 | } ] 74 | } -------------------------------------------------------------------------------- /docs/data/categories.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nikita-Filonov/selenium_python/d2699f3dba4b1da1777a36cfe7f3b9c5c51ee047/docs/data/categories.csv -------------------------------------------------------------------------------- /docs/data/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "4b4757e66a1912dae1a509f688f20b0f", 3 | "name" : "categories", 4 | "children" : [ ] 5 | } -------------------------------------------------------------------------------- /docs/data/packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "83edc06c07f9ae9e47eb6dd1b683e4e2", 3 | "name" : "packages", 4 | "children" : [ { 5 | "name" : "tests.test_customers_sql", 6 | "children" : [ { 7 | "name" : "Select all customers", 8 | "uid" : "50e101896b909c0f", 9 | "parentUid" : "563deb89585c6e3b01f036e7f62d3b4b", 10 | "status" : "passed", 11 | "time" : { 12 | "start" : 1676194845034, 13 | "stop" : 1676194848602, 14 | "duration" : 3568 15 | }, 16 | "flaky" : false, 17 | "newFailed" : false, 18 | "parameters" : [ "'Via Ludovico il Moro 22'", "'Via Ludovico il Moro 22'" ] 19 | }, { 20 | "name" : "Filter customers", 21 | "uid" : "4016c4e2667a7019", 22 | "parentUid" : "563deb89585c6e3b01f036e7f62d3b4b", 23 | "status" : "passed", 24 | "time" : { 25 | "start" : 1676194849047, 26 | "stop" : 1676194850946, 27 | "duration" : 1899 28 | }, 29 | "flaky" : false, 30 | "newFailed" : false, 31 | "parameters" : [ "6" ] 32 | }, { 33 | "name" : "Create customer", 34 | "uid" : "dfff93f27ad3dbb6", 35 | "parentUid" : "563deb89585c6e3b01f036e7f62d3b4b", 36 | "status" : "passed", 37 | "time" : { 38 | "start" : 1676194851420, 39 | "stop" : 1676194856088, 40 | "duration" : 4668 41 | }, 42 | "flaky" : false, 43 | "newFailed" : false, 44 | "parameters" : [ "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" ] 45 | }, { 46 | "name" : "Update customer", 47 | "uid" : "27c7177e1c0b2bec", 48 | "parentUid" : "563deb89585c6e3b01f036e7f62d3b4b", 49 | "status" : "passed", 50 | "time" : { 51 | "start" : 1676194856526, 52 | "stop" : 1676194860261, 53 | "duration" : 3735 54 | }, 55 | "flaky" : false, 56 | "newFailed" : false, 57 | "parameters" : [ "1", "CreateCustomer(customer_name='cKQflX7kixwt', contact_name='4NpuUqp6MZOSWRq', address='bYOP1MTWCDy', city='i8rKllqcsxCUw', postal_code='461', country='et3QbrRx6DlAQE')" ] 58 | }, { 59 | "name" : "Delete customer", 60 | "uid" : "75cc35bef1b0498", 61 | "parentUid" : "563deb89585c6e3b01f036e7f62d3b4b", 62 | "status" : "passed", 63 | "time" : { 64 | "start" : 1676194860801, 65 | "stop" : 1676194863253, 66 | "duration" : 2452 67 | }, 68 | "flaky" : false, 69 | "newFailed" : false, 70 | "parameters" : [ "1" ] 71 | } ], 72 | "uid" : "tests.test_customers_sql" 73 | } ] 74 | } -------------------------------------------------------------------------------- /docs/data/suites.csv: -------------------------------------------------------------------------------- 1 | "Status","Start Time","Stop Time","Duration in ms","Parent Suite","Suite","Sub Suite","Test Class","Test Method","Name","Description" 2 | "passed","Sun Feb 12 12:40:51 MSK 2023","Sun Feb 12 12:40:56 MSK 2023","4668","tests","Smoke","TestCustomersSQL","","","Create customer","" 3 | "passed","Sun Feb 12 12:40:45 MSK 2023","Sun Feb 12 12:40:48 MSK 2023","3568","tests","Smoke","TestCustomersSQL","","","Select all customers","" 4 | "passed","Sun Feb 12 12:41:00 MSK 2023","Sun Feb 12 12:41:03 MSK 2023","2452","tests","Smoke","TestCustomersSQL","","","Delete customer","" 5 | "passed","Sun Feb 12 12:40:49 MSK 2023","Sun Feb 12 12:40:50 MSK 2023","1899","tests","Smoke","TestCustomersSQL","","","Filter customers","" 6 | "passed","Sun Feb 12 12:40:56 MSK 2023","Sun Feb 12 12:41:00 MSK 2023","3735","tests","Smoke","TestCustomersSQL","","","Update customer","" 7 | -------------------------------------------------------------------------------- /docs/data/suites.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "98d3104e051c652961429bf95fa0b5d6", 3 | "name" : "suites", 4 | "children" : [ { 5 | "name" : "tests", 6 | "children" : [ { 7 | "name" : "Smoke", 8 | "children" : [ { 9 | "name" : "TestCustomersSQL", 10 | "children" : [ { 11 | "name" : "Select all customers", 12 | "uid" : "50e101896b909c0f", 13 | "parentUid" : "9cbcab5bb21a617957602a0aaba23088", 14 | "status" : "passed", 15 | "time" : { 16 | "start" : 1676194845034, 17 | "stop" : 1676194848602, 18 | "duration" : 3568 19 | }, 20 | "flaky" : false, 21 | "newFailed" : false, 22 | "parameters" : [ "'Via Ludovico il Moro 22'", "'Via Ludovico il Moro 22'" ] 23 | }, { 24 | "name" : "Filter customers", 25 | "uid" : "4016c4e2667a7019", 26 | "parentUid" : "9cbcab5bb21a617957602a0aaba23088", 27 | "status" : "passed", 28 | "time" : { 29 | "start" : 1676194849047, 30 | "stop" : 1676194850946, 31 | "duration" : 1899 32 | }, 33 | "flaky" : false, 34 | "newFailed" : false, 35 | "parameters" : [ "6" ] 36 | }, { 37 | "name" : "Create customer", 38 | "uid" : "dfff93f27ad3dbb6", 39 | "parentUid" : "9cbcab5bb21a617957602a0aaba23088", 40 | "status" : "passed", 41 | "time" : { 42 | "start" : 1676194851420, 43 | "stop" : 1676194856088, 44 | "duration" : 4668 45 | }, 46 | "flaky" : false, 47 | "newFailed" : false, 48 | "parameters" : [ "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" ] 49 | }, { 50 | "name" : "Update customer", 51 | "uid" : "27c7177e1c0b2bec", 52 | "parentUid" : "9cbcab5bb21a617957602a0aaba23088", 53 | "status" : "passed", 54 | "time" : { 55 | "start" : 1676194856526, 56 | "stop" : 1676194860261, 57 | "duration" : 3735 58 | }, 59 | "flaky" : false, 60 | "newFailed" : false, 61 | "parameters" : [ "1", "CreateCustomer(customer_name='cKQflX7kixwt', contact_name='4NpuUqp6MZOSWRq', address='bYOP1MTWCDy', city='i8rKllqcsxCUw', postal_code='461', country='et3QbrRx6DlAQE')" ] 62 | }, { 63 | "name" : "Delete customer", 64 | "uid" : "75cc35bef1b0498", 65 | "parentUid" : "9cbcab5bb21a617957602a0aaba23088", 66 | "status" : "passed", 67 | "time" : { 68 | "start" : 1676194860801, 69 | "stop" : 1676194863253, 70 | "duration" : 2452 71 | }, 72 | "flaky" : false, 73 | "newFailed" : false, 74 | "parameters" : [ "1" ] 75 | } ], 76 | "uid" : "9cbcab5bb21a617957602a0aaba23088" 77 | } ], 78 | "uid" : "0e3ff3077e7899fd0eef656ff7bb9bb8" 79 | } ], 80 | "uid" : "e387fa4bb326b54ea8c19c2822aba374" 81 | } ] 82 | } -------------------------------------------------------------------------------- /docs/data/test-cases/4016c4e2667a7019.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "4016c4e2667a7019", 3 | "name" : "Filter customers", 4 | "fullName" : "tests.test_customers_sql.TestCustomersSQL#test_filter_customers", 5 | "historyId" : "bd54304e84c2b2f8797da1928314c689", 6 | "time" : { 7 | "start" : 1676194849047, 8 | "stop" : 1676194850946, 9 | "duration" : 1899 10 | }, 11 | "status" : "passed", 12 | "flaky" : false, 13 | "newFailed" : false, 14 | "beforeStages" : [ { 15 | "name" : "page", 16 | "time" : { 17 | "start" : 1676194848785, 18 | "stop" : 1676194849047, 19 | "duration" : 262 20 | }, 21 | "status" : "passed", 22 | "steps" : [ ], 23 | "attachments" : [ ], 24 | "parameters" : [ ], 25 | "stepsCount" : 0, 26 | "hasContent" : false, 27 | "attachmentsCount" : 0, 28 | "shouldDisplayMessage" : false 29 | }, { 30 | "name" : "ui_config", 31 | "time" : { 32 | "start" : 1676194844744, 33 | "stop" : 1676194844745, 34 | "duration" : 1 35 | }, 36 | "status" : "passed", 37 | "steps" : [ ], 38 | "attachments" : [ ], 39 | "parameters" : [ ], 40 | "stepsCount" : 0, 41 | "hasContent" : false, 42 | "attachmentsCount" : 0, 43 | "shouldDisplayMessage" : false 44 | }, { 45 | "name" : "try_sql_page", 46 | "time" : { 47 | "start" : 1676194849047, 48 | "stop" : 1676194849047, 49 | "duration" : 0 50 | }, 51 | "status" : "passed", 52 | "steps" : [ ], 53 | "attachments" : [ ], 54 | "parameters" : [ ], 55 | "stepsCount" : 0, 56 | "hasContent" : false, 57 | "attachmentsCount" : 0, 58 | "shouldDisplayMessage" : false 59 | } ], 60 | "testStage" : { 61 | "status" : "passed", 62 | "steps" : [ { 63 | "name" : "Opening the url \"/sql/trysql.asp?filename=trysql_select_all\"", 64 | "time" : { 65 | "start" : 1676194849047, 66 | "stop" : 1676194849860, 67 | "duration" : 813 68 | }, 69 | "status" : "passed", 70 | "steps" : [ ], 71 | "attachments" : [ ], 72 | "parameters" : [ ], 73 | "stepsCount" : 0, 74 | "hasContent" : false, 75 | "attachmentsCount" : 0, 76 | "shouldDisplayMessage" : false 77 | }, { 78 | "name" : "Fill SQL editor with value \"'SELECT * FROM Customers WHERE City=\"London\";'\"", 79 | "time" : { 80 | "start" : 1676194849860, 81 | "stop" : 1676194850021, 82 | "duration" : 161 83 | }, 84 | "status" : "passed", 85 | "steps" : [ { 86 | "name" : "Checking that editor \"SQL editor\" is visible", 87 | "time" : { 88 | "start" : 1676194849860, 89 | "stop" : 1676194849894, 90 | "duration" : 34 91 | }, 92 | "status" : "passed", 93 | "steps" : [ { 94 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 95 | "time" : { 96 | "start" : 1676194849860, 97 | "stop" : 1676194849880, 98 | "duration" : 20 99 | }, 100 | "status" : "passed", 101 | "steps" : [ ], 102 | "attachments" : [ ], 103 | "parameters" : [ ], 104 | "stepsCount" : 0, 105 | "hasContent" : false, 106 | "attachmentsCount" : 0, 107 | "shouldDisplayMessage" : false 108 | } ], 109 | "attachments" : [ ], 110 | "parameters" : [ ], 111 | "stepsCount" : 1, 112 | "hasContent" : true, 113 | "attachmentsCount" : 0, 114 | "shouldDisplayMessage" : false 115 | }, { 116 | "name" : "Clearing editor with name \"SQL editor\"", 117 | "time" : { 118 | "start" : 1676194849894, 119 | "stop" : 1676194849962, 120 | "duration" : 68 121 | }, 122 | "status" : "passed", 123 | "steps" : [ ], 124 | "attachments" : [ ], 125 | "parameters" : [ ], 126 | "stepsCount" : 0, 127 | "hasContent" : false, 128 | "attachmentsCount" : 0, 129 | "shouldDisplayMessage" : false 130 | }, { 131 | "name" : "Typing value \"SELECT * FROM Customers WHERE City=\"London\";\" to editor with name \"SQL editor\"", 132 | "time" : { 133 | "start" : 1676194849962, 134 | "stop" : 1676194850021, 135 | "duration" : 59 136 | }, 137 | "status" : "passed", 138 | "steps" : [ ], 139 | "attachments" : [ ], 140 | "parameters" : [ ], 141 | "stepsCount" : 0, 142 | "hasContent" : false, 143 | "attachmentsCount" : 0, 144 | "shouldDisplayMessage" : false 145 | } ], 146 | "attachments" : [ ], 147 | "parameters" : [ { 148 | "name" : "sql", 149 | "value" : "'SELECT * FROM Customers WHERE City=\"London\";'" 150 | } ], 151 | "stepsCount" : 4, 152 | "hasContent" : true, 153 | "attachmentsCount" : 0, 154 | "shouldDisplayMessage" : false 155 | }, { 156 | "name" : "Running SQL script", 157 | "time" : { 158 | "start" : 1676194850021, 159 | "stop" : 1676194850181, 160 | "duration" : 160 161 | }, 162 | "status" : "passed", 163 | "steps" : [ { 164 | "name" : "Clicking button with name \"Run SQL »\"", 165 | "time" : { 166 | "start" : 1676194850021, 167 | "stop" : 1676194850181, 168 | "duration" : 160 169 | }, 170 | "status" : "passed", 171 | "steps" : [ { 172 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 173 | "time" : { 174 | "start" : 1676194850021, 175 | "stop" : 1676194850104, 176 | "duration" : 83 177 | }, 178 | "status" : "passed", 179 | "steps" : [ ], 180 | "attachments" : [ ], 181 | "parameters" : [ ], 182 | "stepsCount" : 0, 183 | "hasContent" : false, 184 | "attachmentsCount" : 0, 185 | "shouldDisplayMessage" : false 186 | } ], 187 | "attachments" : [ ], 188 | "parameters" : [ ], 189 | "stepsCount" : 1, 190 | "hasContent" : true, 191 | "attachmentsCount" : 0, 192 | "shouldDisplayMessage" : false 193 | } ], 194 | "attachments" : [ ], 195 | "parameters" : [ { 196 | "name" : "empty_result", 197 | "value" : "False" 198 | }, { 199 | "name" : "rows_affected", 200 | "value" : "None" 201 | } ], 202 | "stepsCount" : 2, 203 | "hasContent" : true, 204 | "attachmentsCount" : 0, 205 | "shouldDisplayMessage" : false 206 | }, { 207 | "name" : "Checking that number of records in result equals to \"6\"", 208 | "time" : { 209 | "start" : 1676194850181, 210 | "stop" : 1676194850946, 211 | "duration" : 765 212 | }, 213 | "status" : "passed", 214 | "steps" : [ { 215 | "name" : "Checking that title \"Number of records\" is visible", 216 | "time" : { 217 | "start" : 1676194850181, 218 | "stop" : 1676194850819, 219 | "duration" : 638 220 | }, 221 | "status" : "passed", 222 | "steps" : [ { 223 | "name" : "Getting title with name \"Number of records\" and locator \"//div[@id=\"divResultSQL\"]//div//div\"", 224 | "time" : { 225 | "start" : 1676194850181, 226 | "stop" : 1676194850805, 227 | "duration" : 624 228 | }, 229 | "status" : "passed", 230 | "steps" : [ ], 231 | "attachments" : [ ], 232 | "parameters" : [ ], 233 | "stepsCount" : 0, 234 | "hasContent" : false, 235 | "attachmentsCount" : 0, 236 | "shouldDisplayMessage" : false 237 | } ], 238 | "attachments" : [ ], 239 | "parameters" : [ ], 240 | "stepsCount" : 1, 241 | "hasContent" : true, 242 | "attachmentsCount" : 0, 243 | "shouldDisplayMessage" : false 244 | }, { 245 | "name" : "Checking that title \"Number of records\" has text \"Number of Records: 6\"", 246 | "time" : { 247 | "start" : 1676194850819, 248 | "stop" : 1676194850883, 249 | "duration" : 64 250 | }, 251 | "status" : "passed", 252 | "steps" : [ { 253 | "name" : "Getting title with name \"Number of records\" and locator \"//div[@id=\"divResultSQL\"]//div//div\"", 254 | "time" : { 255 | "start" : 1676194850819, 256 | "stop" : 1676194850870, 257 | "duration" : 51 258 | }, 259 | "status" : "passed", 260 | "steps" : [ ], 261 | "attachments" : [ ], 262 | "parameters" : [ ], 263 | "stepsCount" : 0, 264 | "hasContent" : false, 265 | "attachmentsCount" : 0, 266 | "shouldDisplayMessage" : false 267 | } ], 268 | "attachments" : [ ], 269 | "parameters" : [ ], 270 | "stepsCount" : 1, 271 | "hasContent" : true, 272 | "attachmentsCount" : 0, 273 | "shouldDisplayMessage" : false 274 | }, { 275 | "name" : "Checking that table row with name \"Result table row\" has length 7", 276 | "time" : { 277 | "start" : 1676194850883, 278 | "stop" : 1676194850946, 279 | "duration" : 63 280 | }, 281 | "status" : "passed", 282 | "steps" : [ { 283 | "name" : "Getting table rows with name \"Result table row\" and locator \"//div[@id=\"divResultSQL\"]//tbody//tr\"", 284 | "time" : { 285 | "start" : 1676194850883, 286 | "stop" : 1676194850945, 287 | "duration" : 62 288 | }, 289 | "status" : "passed", 290 | "steps" : [ ], 291 | "attachments" : [ ], 292 | "parameters" : [ ], 293 | "stepsCount" : 0, 294 | "hasContent" : false, 295 | "attachmentsCount" : 0, 296 | "shouldDisplayMessage" : false 297 | } ], 298 | "attachments" : [ ], 299 | "parameters" : [ ], 300 | "stepsCount" : 1, 301 | "hasContent" : true, 302 | "attachmentsCount" : 0, 303 | "shouldDisplayMessage" : false 304 | } ], 305 | "attachments" : [ ], 306 | "parameters" : [ { 307 | "name" : "number_of_records", 308 | "value" : "6" 309 | } ], 310 | "stepsCount" : 6, 311 | "hasContent" : true, 312 | "attachmentsCount" : 0, 313 | "shouldDisplayMessage" : false 314 | } ], 315 | "attachments" : [ { 316 | "uid" : "8cc046037c123ce5", 317 | "name" : "log", 318 | "source" : "8cc046037c123ce5.txt", 319 | "type" : "text/plain", 320 | "size" : 1659 321 | } ], 322 | "parameters" : [ ], 323 | "stepsCount" : 16, 324 | "hasContent" : true, 325 | "attachmentsCount" : 1, 326 | "shouldDisplayMessage" : false 327 | }, 328 | "afterStages" : [ { 329 | "name" : "page::0", 330 | "time" : { 331 | "start" : 1676194850948, 332 | "stop" : 1676194851113, 333 | "duration" : 165 334 | }, 335 | "status" : "passed", 336 | "steps" : [ ], 337 | "attachments" : [ { 338 | "uid" : "a27610a147fbfc81", 339 | "name" : "Page", 340 | "source" : "a27610a147fbfc81.png", 341 | "type" : "image/png", 342 | "size" : 101821 343 | } ], 344 | "parameters" : [ ], 345 | "stepsCount" : 0, 346 | "hasContent" : true, 347 | "attachmentsCount" : 1, 348 | "shouldDisplayMessage" : false 349 | } ], 350 | "labels" : [ { 351 | "name" : "suite", 352 | "value" : "Smoke" 353 | }, { 354 | "name" : "severity", 355 | "value" : "critical" 356 | }, { 357 | "name" : "feature", 358 | "value" : "Customers" 359 | }, { 360 | "name" : "tag", 361 | "value" : "customers_sql" 362 | }, { 363 | "name" : "tag", 364 | "value" : "ui" 365 | }, { 366 | "name" : "parentSuite", 367 | "value" : "tests" 368 | }, { 369 | "name" : "subSuite", 370 | "value" : "TestCustomersSQL" 371 | }, { 372 | "name" : "host", 373 | "value" : "LAPTOP-VDKHCJMI" 374 | }, { 375 | "name" : "thread", 376 | "value" : "1712-MainThread" 377 | }, { 378 | "name" : "framework", 379 | "value" : "pytest" 380 | }, { 381 | "name" : "language", 382 | "value" : "cpython3" 383 | }, { 384 | "name" : "package", 385 | "value" : "tests.test_customers_sql" 386 | }, { 387 | "name" : "resultFormat", 388 | "value" : "allure2" 389 | } ], 390 | "parameters" : [ { 391 | "name" : "number_of_records", 392 | "value" : "6" 393 | } ], 394 | "links" : [ ], 395 | "hidden" : false, 396 | "retry" : false, 397 | "extra" : { 398 | "severity" : "critical", 399 | "retries" : [ ], 400 | "categories" : [ ], 401 | "tags" : [ "ui", "customers_sql" ] 402 | }, 403 | "source" : "4016c4e2667a7019.json", 404 | "parameterValues" : [ "6" ] 405 | } -------------------------------------------------------------------------------- /docs/data/test-cases/50e101896b909c0f.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "50e101896b909c0f", 3 | "name" : "Select all customers", 4 | "fullName" : "tests.test_customers_sql.TestCustomersSQL#test_select_all_customers", 5 | "historyId" : "088e0ec3d7b20c5ba461617dbc04052f", 6 | "time" : { 7 | "start" : 1676194845034, 8 | "stop" : 1676194848602, 9 | "duration" : 3568 10 | }, 11 | "status" : "passed", 12 | "flaky" : false, 13 | "newFailed" : false, 14 | "beforeStages" : [ { 15 | "name" : "try_sql_page", 16 | "time" : { 17 | "start" : 1676194845031, 18 | "stop" : 1676194845031, 19 | "duration" : 0 20 | }, 21 | "status" : "passed", 22 | "steps" : [ ], 23 | "attachments" : [ ], 24 | "parameters" : [ ], 25 | "stepsCount" : 0, 26 | "hasContent" : false, 27 | "attachmentsCount" : 0, 28 | "shouldDisplayMessage" : false 29 | }, { 30 | "name" : "ui_config", 31 | "time" : { 32 | "start" : 1676194844744, 33 | "stop" : 1676194844745, 34 | "duration" : 1 35 | }, 36 | "status" : "passed", 37 | "steps" : [ ], 38 | "attachments" : [ ], 39 | "parameters" : [ ], 40 | "stepsCount" : 0, 41 | "hasContent" : false, 42 | "attachmentsCount" : 0, 43 | "shouldDisplayMessage" : false 44 | }, { 45 | "name" : "page", 46 | "time" : { 47 | "start" : 1676194844745, 48 | "stop" : 1676194845031, 49 | "duration" : 286 50 | }, 51 | "status" : "passed", 52 | "steps" : [ ], 53 | "attachments" : [ ], 54 | "parameters" : [ ], 55 | "stepsCount" : 0, 56 | "hasContent" : false, 57 | "attachmentsCount" : 0, 58 | "shouldDisplayMessage" : false 59 | } ], 60 | "testStage" : { 61 | "status" : "passed", 62 | "steps" : [ { 63 | "name" : "Opening the url \"/sql/trysql.asp?filename=trysql_select_all\"", 64 | "time" : { 65 | "start" : 1676194845034, 66 | "stop" : 1676194847585, 67 | "duration" : 2551 68 | }, 69 | "status" : "passed", 70 | "steps" : [ ], 71 | "attachments" : [ ], 72 | "parameters" : [ ], 73 | "stepsCount" : 0, 74 | "hasContent" : false, 75 | "attachmentsCount" : 0, 76 | "shouldDisplayMessage" : false 77 | }, { 78 | "name" : "Fill SQL editor with value \"'SELECT * FROM Customers;'\"", 79 | "time" : { 80 | "start" : 1676194847585, 81 | "stop" : 1676194847789, 82 | "duration" : 204 83 | }, 84 | "status" : "passed", 85 | "steps" : [ { 86 | "name" : "Checking that editor \"SQL editor\" is visible", 87 | "time" : { 88 | "start" : 1676194847585, 89 | "stop" : 1676194847664, 90 | "duration" : 79 91 | }, 92 | "status" : "passed", 93 | "steps" : [ { 94 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 95 | "time" : { 96 | "start" : 1676194847585, 97 | "stop" : 1676194847599, 98 | "duration" : 14 99 | }, 100 | "status" : "passed", 101 | "steps" : [ ], 102 | "attachments" : [ ], 103 | "parameters" : [ ], 104 | "stepsCount" : 0, 105 | "hasContent" : false, 106 | "attachmentsCount" : 0, 107 | "shouldDisplayMessage" : false 108 | } ], 109 | "attachments" : [ ], 110 | "parameters" : [ ], 111 | "stepsCount" : 1, 112 | "hasContent" : true, 113 | "attachmentsCount" : 0, 114 | "shouldDisplayMessage" : false 115 | }, { 116 | "name" : "Clearing editor with name \"SQL editor\"", 117 | "time" : { 118 | "start" : 1676194847664, 119 | "stop" : 1676194847721, 120 | "duration" : 57 121 | }, 122 | "status" : "passed", 123 | "steps" : [ ], 124 | "attachments" : [ ], 125 | "parameters" : [ ], 126 | "stepsCount" : 0, 127 | "hasContent" : false, 128 | "attachmentsCount" : 0, 129 | "shouldDisplayMessage" : false 130 | }, { 131 | "name" : "Typing value \"SELECT * FROM Customers;\" to editor with name \"SQL editor\"", 132 | "time" : { 133 | "start" : 1676194847721, 134 | "stop" : 1676194847789, 135 | "duration" : 68 136 | }, 137 | "status" : "passed", 138 | "steps" : [ ], 139 | "attachments" : [ ], 140 | "parameters" : [ ], 141 | "stepsCount" : 0, 142 | "hasContent" : false, 143 | "attachmentsCount" : 0, 144 | "shouldDisplayMessage" : false 145 | } ], 146 | "attachments" : [ ], 147 | "parameters" : [ { 148 | "name" : "sql", 149 | "value" : "'SELECT * FROM Customers;'" 150 | } ], 151 | "stepsCount" : 4, 152 | "hasContent" : true, 153 | "attachmentsCount" : 0, 154 | "shouldDisplayMessage" : false 155 | }, { 156 | "name" : "Running SQL script", 157 | "time" : { 158 | "start" : 1676194847789, 159 | "stop" : 1676194847941, 160 | "duration" : 152 161 | }, 162 | "status" : "passed", 163 | "steps" : [ { 164 | "name" : "Clicking button with name \"Run SQL »\"", 165 | "time" : { 166 | "start" : 1676194847789, 167 | "stop" : 1676194847941, 168 | "duration" : 152 169 | }, 170 | "status" : "passed", 171 | "steps" : [ { 172 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 173 | "time" : { 174 | "start" : 1676194847789, 175 | "stop" : 1676194847853, 176 | "duration" : 64 177 | }, 178 | "status" : "passed", 179 | "steps" : [ ], 180 | "attachments" : [ ], 181 | "parameters" : [ ], 182 | "stepsCount" : 0, 183 | "hasContent" : false, 184 | "attachmentsCount" : 0, 185 | "shouldDisplayMessage" : false 186 | } ], 187 | "attachments" : [ ], 188 | "parameters" : [ ], 189 | "stepsCount" : 1, 190 | "hasContent" : true, 191 | "attachmentsCount" : 0, 192 | "shouldDisplayMessage" : false 193 | } ], 194 | "attachments" : [ ], 195 | "parameters" : [ { 196 | "name" : "empty_result", 197 | "value" : "False" 198 | }, { 199 | "name" : "rows_affected", 200 | "value" : "None" 201 | } ], 202 | "stepsCount" : 2, 203 | "hasContent" : true, 204 | "attachmentsCount" : 0, 205 | "shouldDisplayMessage" : false 206 | }, { 207 | "name" : "Checking that result table data has text \"'Via Ludovico il Moro 22'\" in column \"\"", 208 | "time" : { 209 | "start" : 1676194847941, 210 | "stop" : 1676194848602, 211 | "duration" : 661 212 | }, 213 | "status" : "passed", 214 | "steps" : [ { 215 | "name" : "Checking that table data \"Result table data\" is visible", 216 | "time" : { 217 | "start" : 1676194847941, 218 | "stop" : 1676194848531, 219 | "duration" : 590 220 | }, 221 | "status" : "passed", 222 | "steps" : [ { 223 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"Via Ludovico il Moro 22\"]/../td[4]\"", 224 | "time" : { 225 | "start" : 1676194847941, 226 | "stop" : 1676194848515, 227 | "duration" : 574 228 | }, 229 | "status" : "passed", 230 | "steps" : [ ], 231 | "attachments" : [ ], 232 | "parameters" : [ ], 233 | "stepsCount" : 0, 234 | "hasContent" : false, 235 | "attachmentsCount" : 0, 236 | "shouldDisplayMessage" : false 237 | } ], 238 | "attachments" : [ ], 239 | "parameters" : [ ], 240 | "stepsCount" : 1, 241 | "hasContent" : true, 242 | "attachmentsCount" : 0, 243 | "shouldDisplayMessage" : false 244 | }, { 245 | "name" : "Checking that table data \"Result table data\" has text \"Via Ludovico il Moro 22\"", 246 | "time" : { 247 | "start" : 1676194848531, 248 | "stop" : 1676194848602, 249 | "duration" : 71 250 | }, 251 | "status" : "passed", 252 | "steps" : [ { 253 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"Via Ludovico il Moro 22\"]/../td[4]\"", 254 | "time" : { 255 | "start" : 1676194848531, 256 | "stop" : 1676194848591, 257 | "duration" : 60 258 | }, 259 | "status" : "passed", 260 | "steps" : [ ], 261 | "attachments" : [ ], 262 | "parameters" : [ ], 263 | "stepsCount" : 0, 264 | "hasContent" : false, 265 | "attachmentsCount" : 0, 266 | "shouldDisplayMessage" : false 267 | } ], 268 | "attachments" : [ ], 269 | "parameters" : [ ], 270 | "stepsCount" : 1, 271 | "hasContent" : true, 272 | "attachmentsCount" : 0, 273 | "shouldDisplayMessage" : false 274 | } ], 275 | "attachments" : [ ], 276 | "parameters" : [ { 277 | "name" : "expected_text", 278 | "value" : "'Via Ludovico il Moro 22'" 279 | }, { 280 | "name" : "reference_text", 281 | "value" : "'Via Ludovico il Moro 22'" 282 | }, { 283 | "name" : "column", 284 | "value" : "" 285 | } ], 286 | "stepsCount" : 4, 287 | "hasContent" : true, 288 | "attachmentsCount" : 0, 289 | "shouldDisplayMessage" : false 290 | } ], 291 | "attachments" : [ { 292 | "uid" : "83092f481be98b0e", 293 | "name" : "log", 294 | "source" : "83092f481be98b0e.txt", 295 | "type" : "text/plain", 296 | "size" : 1453 297 | } ], 298 | "parameters" : [ ], 299 | "stepsCount" : 14, 300 | "hasContent" : true, 301 | "attachmentsCount" : 1, 302 | "shouldDisplayMessage" : false 303 | }, 304 | "afterStages" : [ { 305 | "name" : "page::0", 306 | "time" : { 307 | "start" : 1676194848611, 308 | "stop" : 1676194848781, 309 | "duration" : 170 310 | }, 311 | "status" : "passed", 312 | "steps" : [ ], 313 | "attachments" : [ { 314 | "uid" : "37acdc13563560ff", 315 | "name" : "Page", 316 | "source" : "37acdc13563560ff.png", 317 | "type" : "image/png", 318 | "size" : 99495 319 | } ], 320 | "parameters" : [ ], 321 | "stepsCount" : 0, 322 | "hasContent" : true, 323 | "attachmentsCount" : 1, 324 | "shouldDisplayMessage" : false 325 | } ], 326 | "labels" : [ { 327 | "name" : "suite", 328 | "value" : "Smoke" 329 | }, { 330 | "name" : "severity", 331 | "value" : "critical" 332 | }, { 333 | "name" : "feature", 334 | "value" : "Customers" 335 | }, { 336 | "name" : "tag", 337 | "value" : "customers_sql" 338 | }, { 339 | "name" : "tag", 340 | "value" : "ui" 341 | }, { 342 | "name" : "parentSuite", 343 | "value" : "tests" 344 | }, { 345 | "name" : "subSuite", 346 | "value" : "TestCustomersSQL" 347 | }, { 348 | "name" : "host", 349 | "value" : "LAPTOP-VDKHCJMI" 350 | }, { 351 | "name" : "thread", 352 | "value" : "1712-MainThread" 353 | }, { 354 | "name" : "framework", 355 | "value" : "pytest" 356 | }, { 357 | "name" : "language", 358 | "value" : "cpython3" 359 | }, { 360 | "name" : "package", 361 | "value" : "tests.test_customers_sql" 362 | }, { 363 | "name" : "resultFormat", 364 | "value" : "allure2" 365 | } ], 366 | "parameters" : [ { 367 | "name" : "reference_text", 368 | "value" : "'Via Ludovico il Moro 22'" 369 | }, { 370 | "name" : "text", 371 | "value" : "'Via Ludovico il Moro 22'" 372 | } ], 373 | "links" : [ ], 374 | "hidden" : false, 375 | "retry" : false, 376 | "extra" : { 377 | "severity" : "critical", 378 | "retries" : [ ], 379 | "categories" : [ ], 380 | "tags" : [ "ui", "customers_sql" ] 381 | }, 382 | "source" : "50e101896b909c0f.json", 383 | "parameterValues" : [ "'Via Ludovico il Moro 22'", "'Via Ludovico il Moro 22'" ] 384 | } -------------------------------------------------------------------------------- /docs/data/test-cases/75cc35bef1b0498.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "75cc35bef1b0498", 3 | "name" : "Delete customer", 4 | "fullName" : "tests.test_customers_sql.TestCustomersSQL#test_delete_customer", 5 | "historyId" : "3a722f825f913a3567a3ee74e65a269a", 6 | "time" : { 7 | "start" : 1676194860801, 8 | "stop" : 1676194863253, 9 | "duration" : 2452 10 | }, 11 | "status" : "passed", 12 | "flaky" : false, 13 | "newFailed" : false, 14 | "beforeStages" : [ { 15 | "name" : "page", 16 | "time" : { 17 | "start" : 1676194860496, 18 | "stop" : 1676194860801, 19 | "duration" : 305 20 | }, 21 | "status" : "passed", 22 | "steps" : [ ], 23 | "attachments" : [ ], 24 | "parameters" : [ ], 25 | "stepsCount" : 0, 26 | "hasContent" : false, 27 | "attachmentsCount" : 0, 28 | "shouldDisplayMessage" : false 29 | }, { 30 | "name" : "ui_config", 31 | "time" : { 32 | "start" : 1676194844744, 33 | "stop" : 1676194844745, 34 | "duration" : 1 35 | }, 36 | "status" : "passed", 37 | "steps" : [ ], 38 | "attachments" : [ ], 39 | "parameters" : [ ], 40 | "stepsCount" : 0, 41 | "hasContent" : false, 42 | "attachmentsCount" : 0, 43 | "shouldDisplayMessage" : false 44 | }, { 45 | "name" : "try_sql_page", 46 | "time" : { 47 | "start" : 1676194860801, 48 | "stop" : 1676194860801, 49 | "duration" : 0 50 | }, 51 | "status" : "passed", 52 | "steps" : [ ], 53 | "attachments" : [ ], 54 | "parameters" : [ ], 55 | "stepsCount" : 0, 56 | "hasContent" : false, 57 | "attachmentsCount" : 0, 58 | "shouldDisplayMessage" : false 59 | } ], 60 | "testStage" : { 61 | "status" : "passed", 62 | "steps" : [ { 63 | "name" : "Opening the url \"/sql/trysql.asp?filename=trysql_select_all\"", 64 | "time" : { 65 | "start" : 1676194860801, 66 | "stop" : 1676194861561, 67 | "duration" : 760 68 | }, 69 | "status" : "passed", 70 | "steps" : [ ], 71 | "attachments" : [ ], 72 | "parameters" : [ ], 73 | "stepsCount" : 0, 74 | "hasContent" : false, 75 | "attachmentsCount" : 0, 76 | "shouldDisplayMessage" : false 77 | }, { 78 | "name" : "Fill SQL editor with value \"'DELETE FROM Customers WHERE CustomerID=\"1\";'\"", 79 | "time" : { 80 | "start" : 1676194861561, 81 | "stop" : 1676194861721, 82 | "duration" : 160 83 | }, 84 | "status" : "passed", 85 | "steps" : [ { 86 | "name" : "Checking that editor \"SQL editor\" is visible", 87 | "time" : { 88 | "start" : 1676194861561, 89 | "stop" : 1676194861592, 90 | "duration" : 31 91 | }, 92 | "status" : "passed", 93 | "steps" : [ { 94 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 95 | "time" : { 96 | "start" : 1676194861561, 97 | "stop" : 1676194861577, 98 | "duration" : 16 99 | }, 100 | "status" : "passed", 101 | "steps" : [ ], 102 | "attachments" : [ ], 103 | "parameters" : [ ], 104 | "stepsCount" : 0, 105 | "hasContent" : false, 106 | "attachmentsCount" : 0, 107 | "shouldDisplayMessage" : false 108 | } ], 109 | "attachments" : [ ], 110 | "parameters" : [ ], 111 | "stepsCount" : 1, 112 | "hasContent" : true, 113 | "attachmentsCount" : 0, 114 | "shouldDisplayMessage" : false 115 | }, { 116 | "name" : "Clearing editor with name \"SQL editor\"", 117 | "time" : { 118 | "start" : 1676194861592, 119 | "stop" : 1676194861663, 120 | "duration" : 71 121 | }, 122 | "status" : "passed", 123 | "steps" : [ ], 124 | "attachments" : [ ], 125 | "parameters" : [ ], 126 | "stepsCount" : 0, 127 | "hasContent" : false, 128 | "attachmentsCount" : 0, 129 | "shouldDisplayMessage" : false 130 | }, { 131 | "name" : "Typing value \"DELETE FROM Customers WHERE CustomerID=\"1\";\" to editor with name \"SQL editor\"", 132 | "time" : { 133 | "start" : 1676194861663, 134 | "stop" : 1676194861721, 135 | "duration" : 58 136 | }, 137 | "status" : "passed", 138 | "steps" : [ ], 139 | "attachments" : [ ], 140 | "parameters" : [ ], 141 | "stepsCount" : 0, 142 | "hasContent" : false, 143 | "attachmentsCount" : 0, 144 | "shouldDisplayMessage" : false 145 | } ], 146 | "attachments" : [ ], 147 | "parameters" : [ { 148 | "name" : "sql", 149 | "value" : "'DELETE FROM Customers WHERE CustomerID=\"1\";'" 150 | } ], 151 | "stepsCount" : 4, 152 | "hasContent" : true, 153 | "attachmentsCount" : 0, 154 | "shouldDisplayMessage" : false 155 | }, { 156 | "name" : "Running SQL script", 157 | "time" : { 158 | "start" : 1676194861721, 159 | "stop" : 1676194862681, 160 | "duration" : 960 161 | }, 162 | "status" : "passed", 163 | "steps" : [ { 164 | "name" : "Clicking button with name \"Run SQL »\"", 165 | "time" : { 166 | "start" : 1676194861721, 167 | "stop" : 1676194861859, 168 | "duration" : 138 169 | }, 170 | "status" : "passed", 171 | "steps" : [ { 172 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 173 | "time" : { 174 | "start" : 1676194861721, 175 | "stop" : 1676194861783, 176 | "duration" : 62 177 | }, 178 | "status" : "passed", 179 | "steps" : [ ], 180 | "attachments" : [ ], 181 | "parameters" : [ ], 182 | "stepsCount" : 0, 183 | "hasContent" : false, 184 | "attachmentsCount" : 0, 185 | "shouldDisplayMessage" : false 186 | } ], 187 | "attachments" : [ ], 188 | "parameters" : [ ], 189 | "stepsCount" : 1, 190 | "hasContent" : true, 191 | "attachmentsCount" : 0, 192 | "shouldDisplayMessage" : false 193 | }, { 194 | "name" : "Checking that text \"Query result\" is visible", 195 | "time" : { 196 | "start" : 1676194861859, 197 | "stop" : 1676194861971, 198 | "duration" : 112 199 | }, 200 | "status" : "passed", 201 | "steps" : [ { 202 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 203 | "time" : { 204 | "start" : 1676194861859, 205 | "stop" : 1676194861952, 206 | "duration" : 93 207 | }, 208 | "status" : "passed", 209 | "steps" : [ ], 210 | "attachments" : [ ], 211 | "parameters" : [ ], 212 | "stepsCount" : 0, 213 | "hasContent" : false, 214 | "attachmentsCount" : 0, 215 | "shouldDisplayMessage" : false 216 | } ], 217 | "attachments" : [ ], 218 | "parameters" : [ ], 219 | "stepsCount" : 1, 220 | "hasContent" : true, 221 | "attachmentsCount" : 0, 222 | "shouldDisplayMessage" : false 223 | }, { 224 | "name" : "Checking that text \"Query result\" has text \"You have made changes to the database. Rows affected: 1\"", 225 | "time" : { 226 | "start" : 1676194861971, 227 | "stop" : 1676194862681, 228 | "duration" : 710 229 | }, 230 | "status" : "passed", 231 | "steps" : [ { 232 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 233 | "time" : { 234 | "start" : 1676194861971, 235 | "stop" : 1676194862069, 236 | "duration" : 98 237 | }, 238 | "status" : "passed", 239 | "steps" : [ ], 240 | "attachments" : [ ], 241 | "parameters" : [ ], 242 | "stepsCount" : 0, 243 | "hasContent" : false, 244 | "attachmentsCount" : 0, 245 | "shouldDisplayMessage" : false 246 | }, { 247 | "name" : "Checking that text \"Query result\" has text \"You have made changes to the database. Rows affected: 1\"", 248 | "time" : { 249 | "start" : 1676194862614, 250 | "stop" : 1676194862681, 251 | "duration" : 67 252 | }, 253 | "status" : "passed", 254 | "steps" : [ { 255 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 256 | "time" : { 257 | "start" : 1676194862614, 258 | "stop" : 1676194862665, 259 | "duration" : 51 260 | }, 261 | "status" : "passed", 262 | "steps" : [ ], 263 | "attachments" : [ ], 264 | "parameters" : [ ], 265 | "stepsCount" : 0, 266 | "hasContent" : false, 267 | "attachmentsCount" : 0, 268 | "shouldDisplayMessage" : false 269 | } ], 270 | "attachments" : [ ], 271 | "parameters" : [ ], 272 | "stepsCount" : 1, 273 | "hasContent" : true, 274 | "attachmentsCount" : 0, 275 | "shouldDisplayMessage" : false 276 | } ], 277 | "attachments" : [ ], 278 | "parameters" : [ ], 279 | "stepsCount" : 3, 280 | "hasContent" : true, 281 | "attachmentsCount" : 0, 282 | "shouldDisplayMessage" : false 283 | } ], 284 | "attachments" : [ ], 285 | "parameters" : [ { 286 | "name" : "empty_result", 287 | "value" : "False" 288 | }, { 289 | "name" : "rows_affected", 290 | "value" : "1" 291 | } ], 292 | "stepsCount" : 8, 293 | "hasContent" : true, 294 | "attachmentsCount" : 0, 295 | "shouldDisplayMessage" : false 296 | }, { 297 | "name" : "Fill SQL editor with value \"'SELECT * FROM Customers WHERE CustomerID=\"1\";'\"", 298 | "time" : { 299 | "start" : 1676194862681, 300 | "stop" : 1676194862912, 301 | "duration" : 231 302 | }, 303 | "status" : "passed", 304 | "steps" : [ { 305 | "name" : "Checking that editor \"SQL editor\" is visible", 306 | "time" : { 307 | "start" : 1676194862681, 308 | "stop" : 1676194862803, 309 | "duration" : 122 310 | }, 311 | "status" : "passed", 312 | "steps" : [ { 313 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 314 | "time" : { 315 | "start" : 1676194862681, 316 | "stop" : 1676194862740, 317 | "duration" : 59 318 | }, 319 | "status" : "passed", 320 | "steps" : [ ], 321 | "attachments" : [ ], 322 | "parameters" : [ ], 323 | "stepsCount" : 0, 324 | "hasContent" : false, 325 | "attachmentsCount" : 0, 326 | "shouldDisplayMessage" : false 327 | } ], 328 | "attachments" : [ ], 329 | "parameters" : [ ], 330 | "stepsCount" : 1, 331 | "hasContent" : true, 332 | "attachmentsCount" : 0, 333 | "shouldDisplayMessage" : false 334 | }, { 335 | "name" : "Clearing editor with name \"SQL editor\"", 336 | "time" : { 337 | "start" : 1676194862803, 338 | "stop" : 1676194862857, 339 | "duration" : 54 340 | }, 341 | "status" : "passed", 342 | "steps" : [ ], 343 | "attachments" : [ ], 344 | "parameters" : [ ], 345 | "stepsCount" : 0, 346 | "hasContent" : false, 347 | "attachmentsCount" : 0, 348 | "shouldDisplayMessage" : false 349 | }, { 350 | "name" : "Typing value \"SELECT * FROM Customers WHERE CustomerID=\"1\";\" to editor with name \"SQL editor\"", 351 | "time" : { 352 | "start" : 1676194862857, 353 | "stop" : 1676194862912, 354 | "duration" : 55 355 | }, 356 | "status" : "passed", 357 | "steps" : [ ], 358 | "attachments" : [ ], 359 | "parameters" : [ ], 360 | "stepsCount" : 0, 361 | "hasContent" : false, 362 | "attachmentsCount" : 0, 363 | "shouldDisplayMessage" : false 364 | } ], 365 | "attachments" : [ ], 366 | "parameters" : [ { 367 | "name" : "sql", 368 | "value" : "'SELECT * FROM Customers WHERE CustomerID=\"1\";'" 369 | } ], 370 | "stepsCount" : 4, 371 | "hasContent" : true, 372 | "attachmentsCount" : 0, 373 | "shouldDisplayMessage" : false 374 | }, { 375 | "name" : "Running SQL script", 376 | "time" : { 377 | "start" : 1676194862912, 378 | "stop" : 1676194863253, 379 | "duration" : 341 380 | }, 381 | "status" : "passed", 382 | "steps" : [ { 383 | "name" : "Clicking button with name \"Run SQL »\"", 384 | "time" : { 385 | "start" : 1676194862912, 386 | "stop" : 1676194863054, 387 | "duration" : 142 388 | }, 389 | "status" : "passed", 390 | "steps" : [ { 391 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 392 | "time" : { 393 | "start" : 1676194862912, 394 | "stop" : 1676194862969, 395 | "duration" : 57 396 | }, 397 | "status" : "passed", 398 | "steps" : [ ], 399 | "attachments" : [ ], 400 | "parameters" : [ ], 401 | "stepsCount" : 0, 402 | "hasContent" : false, 403 | "attachmentsCount" : 0, 404 | "shouldDisplayMessage" : false 405 | } ], 406 | "attachments" : [ ], 407 | "parameters" : [ ], 408 | "stepsCount" : 1, 409 | "hasContent" : true, 410 | "attachmentsCount" : 0, 411 | "shouldDisplayMessage" : false 412 | }, { 413 | "name" : "Checking that text \"Query result\" is visible", 414 | "time" : { 415 | "start" : 1676194863054, 416 | "stop" : 1676194863165, 417 | "duration" : 111 418 | }, 419 | "status" : "passed", 420 | "steps" : [ { 421 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 422 | "time" : { 423 | "start" : 1676194863054, 424 | "stop" : 1676194863118, 425 | "duration" : 64 426 | }, 427 | "status" : "passed", 428 | "steps" : [ ], 429 | "attachments" : [ ], 430 | "parameters" : [ ], 431 | "stepsCount" : 0, 432 | "hasContent" : false, 433 | "attachmentsCount" : 0, 434 | "shouldDisplayMessage" : false 435 | } ], 436 | "attachments" : [ ], 437 | "parameters" : [ ], 438 | "stepsCount" : 1, 439 | "hasContent" : true, 440 | "attachmentsCount" : 0, 441 | "shouldDisplayMessage" : false 442 | }, { 443 | "name" : "Checking that text \"Query result\" has text \"No result.\"", 444 | "time" : { 445 | "start" : 1676194863171, 446 | "stop" : 1676194863253, 447 | "duration" : 82 448 | }, 449 | "status" : "passed", 450 | "steps" : [ { 451 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 452 | "time" : { 453 | "start" : 1676194863171, 454 | "stop" : 1676194863238, 455 | "duration" : 67 456 | }, 457 | "status" : "passed", 458 | "steps" : [ ], 459 | "attachments" : [ ], 460 | "parameters" : [ ], 461 | "stepsCount" : 0, 462 | "hasContent" : false, 463 | "attachmentsCount" : 0, 464 | "shouldDisplayMessage" : false 465 | } ], 466 | "attachments" : [ ], 467 | "parameters" : [ ], 468 | "stepsCount" : 1, 469 | "hasContent" : true, 470 | "attachmentsCount" : 0, 471 | "shouldDisplayMessage" : false 472 | } ], 473 | "attachments" : [ ], 474 | "parameters" : [ { 475 | "name" : "empty_result", 476 | "value" : "True" 477 | }, { 478 | "name" : "rows_affected", 479 | "value" : "None" 480 | } ], 481 | "stepsCount" : 6, 482 | "hasContent" : true, 483 | "attachmentsCount" : 0, 484 | "shouldDisplayMessage" : false 485 | } ], 486 | "attachments" : [ { 487 | "uid" : "66046cfbfb0c166a", 488 | "name" : "log", 489 | "source" : "66046cfbfb0c166a.txt", 490 | "type" : "text/plain", 491 | "size" : 2306 492 | } ], 493 | "parameters" : [ ], 494 | "stepsCount" : 27, 495 | "hasContent" : true, 496 | "attachmentsCount" : 1, 497 | "shouldDisplayMessage" : false 498 | }, 499 | "afterStages" : [ { 500 | "name" : "page::0", 501 | "time" : { 502 | "start" : 1676194863255, 503 | "stop" : 1676194863430, 504 | "duration" : 175 505 | }, 506 | "status" : "passed", 507 | "steps" : [ ], 508 | "attachments" : [ { 509 | "uid" : "b07a9dbc18ac810", 510 | "name" : "Page", 511 | "source" : "b07a9dbc18ac810.png", 512 | "type" : "image/png", 513 | "size" : 88853 514 | } ], 515 | "parameters" : [ ], 516 | "stepsCount" : 0, 517 | "hasContent" : true, 518 | "attachmentsCount" : 1, 519 | "shouldDisplayMessage" : false 520 | } ], 521 | "labels" : [ { 522 | "name" : "suite", 523 | "value" : "Smoke" 524 | }, { 525 | "name" : "severity", 526 | "value" : "critical" 527 | }, { 528 | "name" : "feature", 529 | "value" : "Customers" 530 | }, { 531 | "name" : "tag", 532 | "value" : "customers_sql" 533 | }, { 534 | "name" : "tag", 535 | "value" : "ui" 536 | }, { 537 | "name" : "parentSuite", 538 | "value" : "tests" 539 | }, { 540 | "name" : "subSuite", 541 | "value" : "TestCustomersSQL" 542 | }, { 543 | "name" : "host", 544 | "value" : "LAPTOP-VDKHCJMI" 545 | }, { 546 | "name" : "thread", 547 | "value" : "1712-MainThread" 548 | }, { 549 | "name" : "framework", 550 | "value" : "pytest" 551 | }, { 552 | "name" : "language", 553 | "value" : "cpython3" 554 | }, { 555 | "name" : "package", 556 | "value" : "tests.test_customers_sql" 557 | }, { 558 | "name" : "resultFormat", 559 | "value" : "allure2" 560 | } ], 561 | "parameters" : [ { 562 | "name" : "customer_id", 563 | "value" : "1" 564 | } ], 565 | "links" : [ ], 566 | "hidden" : false, 567 | "retry" : false, 568 | "extra" : { 569 | "severity" : "critical", 570 | "retries" : [ ], 571 | "categories" : [ ], 572 | "tags" : [ "ui", "customers_sql" ] 573 | }, 574 | "source" : "75cc35bef1b0498.json", 575 | "parameterValues" : [ "1" ] 576 | } -------------------------------------------------------------------------------- /docs/data/test-cases/dfff93f27ad3dbb6.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "dfff93f27ad3dbb6", 3 | "name" : "Create customer", 4 | "fullName" : "tests.test_customers_sql.TestCustomersSQL#test_create_customer", 5 | "historyId" : "eefc3149fd1eb267c387c9b9181e4dbd", 6 | "time" : { 7 | "start" : 1676194851420, 8 | "stop" : 1676194856088, 9 | "duration" : 4668 10 | }, 11 | "status" : "passed", 12 | "flaky" : false, 13 | "newFailed" : false, 14 | "beforeStages" : [ { 15 | "name" : "ui_config", 16 | "time" : { 17 | "start" : 1676194844744, 18 | "stop" : 1676194844745, 19 | "duration" : 1 20 | }, 21 | "status" : "passed", 22 | "steps" : [ ], 23 | "attachments" : [ ], 24 | "parameters" : [ ], 25 | "stepsCount" : 0, 26 | "hasContent" : false, 27 | "attachmentsCount" : 0, 28 | "shouldDisplayMessage" : false 29 | }, { 30 | "name" : "try_sql_page", 31 | "time" : { 32 | "start" : 1676194851419, 33 | "stop" : 1676194851419, 34 | "duration" : 0 35 | }, 36 | "status" : "passed", 37 | "steps" : [ ], 38 | "attachments" : [ ], 39 | "parameters" : [ ], 40 | "stepsCount" : 0, 41 | "hasContent" : false, 42 | "attachmentsCount" : 0, 43 | "shouldDisplayMessage" : false 44 | }, { 45 | "name" : "page", 46 | "time" : { 47 | "start" : 1676194851119, 48 | "stop" : 1676194851419, 49 | "duration" : 300 50 | }, 51 | "status" : "passed", 52 | "steps" : [ ], 53 | "attachments" : [ ], 54 | "parameters" : [ ], 55 | "stepsCount" : 0, 56 | "hasContent" : false, 57 | "attachmentsCount" : 0, 58 | "shouldDisplayMessage" : false 59 | } ], 60 | "testStage" : { 61 | "status" : "passed", 62 | "steps" : [ { 63 | "name" : "Opening the url \"/sql/trysql.asp?filename=trysql_select_all\"", 64 | "time" : { 65 | "start" : 1676194851420, 66 | "stop" : 1676194853973, 67 | "duration" : 2553 68 | }, 69 | "status" : "passed", 70 | "steps" : [ ], 71 | "attachments" : [ ], 72 | "parameters" : [ ], 73 | "stepsCount" : 0, 74 | "hasContent" : false, 75 | "attachmentsCount" : 0, 76 | "shouldDisplayMessage" : false 77 | }, { 78 | "name" : "Fill SQL editor with value \"'INSERT INTO Customers ('CustomerName', 'ContactName', 'Address', 'City', 'PostalCode', 'Country') VALUES ('tKEGXG2rvIaH', 'bsDUYD5g6', 'Wd8Xmkiwl8', 'aY911Xx7Z2p5a', '497', '5hv60jUzO');'\"", 79 | "time" : { 80 | "start" : 1676194853974, 81 | "stop" : 1676194854122, 82 | "duration" : 148 83 | }, 84 | "status" : "passed", 85 | "steps" : [ { 86 | "name" : "Checking that editor \"SQL editor\" is visible", 87 | "time" : { 88 | "start" : 1676194853974, 89 | "stop" : 1676194854000, 90 | "duration" : 26 91 | }, 92 | "status" : "passed", 93 | "steps" : [ { 94 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 95 | "time" : { 96 | "start" : 1676194853974, 97 | "stop" : 1676194853986, 98 | "duration" : 12 99 | }, 100 | "status" : "passed", 101 | "steps" : [ ], 102 | "attachments" : [ ], 103 | "parameters" : [ ], 104 | "stepsCount" : 0, 105 | "hasContent" : false, 106 | "attachmentsCount" : 0, 107 | "shouldDisplayMessage" : false 108 | } ], 109 | "attachments" : [ ], 110 | "parameters" : [ ], 111 | "stepsCount" : 1, 112 | "hasContent" : true, 113 | "attachmentsCount" : 0, 114 | "shouldDisplayMessage" : false 115 | }, { 116 | "name" : "Clearing editor with name \"SQL editor\"", 117 | "time" : { 118 | "start" : 1676194854000, 119 | "stop" : 1676194854061, 120 | "duration" : 61 121 | }, 122 | "status" : "passed", 123 | "steps" : [ ], 124 | "attachments" : [ ], 125 | "parameters" : [ ], 126 | "stepsCount" : 0, 127 | "hasContent" : false, 128 | "attachmentsCount" : 0, 129 | "shouldDisplayMessage" : false 130 | }, { 131 | "name" : "Typing value \"INSERT INTO Customers ('CustomerName', 'ContactName', 'Address', 'City', 'PostalCode', 'Country') VALUES ('tKEGXG2rvIaH', 'bsDUYD5g6', 'Wd8Xmkiwl8', 'aY911Xx7Z2p5a', '497', '5hv60jUzO');\" to editor with name \"SQL editor\"", 132 | "time" : { 133 | "start" : 1676194854061, 134 | "stop" : 1676194854122, 135 | "duration" : 61 136 | }, 137 | "status" : "passed", 138 | "steps" : [ ], 139 | "attachments" : [ ], 140 | "parameters" : [ ], 141 | "stepsCount" : 0, 142 | "hasContent" : false, 143 | "attachmentsCount" : 0, 144 | "shouldDisplayMessage" : false 145 | } ], 146 | "attachments" : [ ], 147 | "parameters" : [ { 148 | "name" : "sql", 149 | "value" : "'INSERT INTO Customers ('CustomerName', 'ContactName', 'Address', 'City', 'PostalCode', 'Country') VALUES ('tKEGXG2rvIaH', 'bsDUYD5g6', 'Wd8Xmkiwl8', 'aY911Xx7Z2p5a', '497', '5hv60jUzO');'" 150 | } ], 151 | "stepsCount" : 4, 152 | "hasContent" : true, 153 | "attachmentsCount" : 0, 154 | "shouldDisplayMessage" : false 155 | }, { 156 | "name" : "Running SQL script", 157 | "time" : { 158 | "start" : 1676194854122, 159 | "stop" : 1676194854443, 160 | "duration" : 321 161 | }, 162 | "status" : "passed", 163 | "steps" : [ { 164 | "name" : "Clicking button with name \"Run SQL »\"", 165 | "time" : { 166 | "start" : 1676194854122, 167 | "stop" : 1676194854260, 168 | "duration" : 138 169 | }, 170 | "status" : "passed", 171 | "steps" : [ { 172 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 173 | "time" : { 174 | "start" : 1676194854122, 175 | "stop" : 1676194854182, 176 | "duration" : 60 177 | }, 178 | "status" : "passed", 179 | "steps" : [ ], 180 | "attachments" : [ ], 181 | "parameters" : [ ], 182 | "stepsCount" : 0, 183 | "hasContent" : false, 184 | "attachmentsCount" : 0, 185 | "shouldDisplayMessage" : false 186 | } ], 187 | "attachments" : [ ], 188 | "parameters" : [ ], 189 | "stepsCount" : 1, 190 | "hasContent" : true, 191 | "attachmentsCount" : 0, 192 | "shouldDisplayMessage" : false 193 | }, { 194 | "name" : "Checking that text \"Query result\" is visible", 195 | "time" : { 196 | "start" : 1676194854260, 197 | "stop" : 1676194854362, 198 | "duration" : 102 199 | }, 200 | "status" : "passed", 201 | "steps" : [ { 202 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 203 | "time" : { 204 | "start" : 1676194854260, 205 | "stop" : 1676194854330, 206 | "duration" : 70 207 | }, 208 | "status" : "passed", 209 | "steps" : [ ], 210 | "attachments" : [ ], 211 | "parameters" : [ ], 212 | "stepsCount" : 0, 213 | "hasContent" : false, 214 | "attachmentsCount" : 0, 215 | "shouldDisplayMessage" : false 216 | } ], 217 | "attachments" : [ ], 218 | "parameters" : [ ], 219 | "stepsCount" : 1, 220 | "hasContent" : true, 221 | "attachmentsCount" : 0, 222 | "shouldDisplayMessage" : false 223 | }, { 224 | "name" : "Checking that text \"Query result\" has text \"You have made changes to the database. Rows affected: 1\"", 225 | "time" : { 226 | "start" : 1676194854362, 227 | "stop" : 1676194854443, 228 | "duration" : 81 229 | }, 230 | "status" : "passed", 231 | "steps" : [ { 232 | "name" : "Getting text with name \"Query result\" and locator \"//div[@id=\"divResultSQL\"]//div\"", 233 | "time" : { 234 | "start" : 1676194854362, 235 | "stop" : 1676194854426, 236 | "duration" : 64 237 | }, 238 | "status" : "passed", 239 | "steps" : [ ], 240 | "attachments" : [ ], 241 | "parameters" : [ ], 242 | "stepsCount" : 0, 243 | "hasContent" : false, 244 | "attachmentsCount" : 0, 245 | "shouldDisplayMessage" : false 246 | } ], 247 | "attachments" : [ ], 248 | "parameters" : [ ], 249 | "stepsCount" : 1, 250 | "hasContent" : true, 251 | "attachmentsCount" : 0, 252 | "shouldDisplayMessage" : false 253 | } ], 254 | "attachments" : [ ], 255 | "parameters" : [ { 256 | "name" : "empty_result", 257 | "value" : "False" 258 | }, { 259 | "name" : "rows_affected", 260 | "value" : "1" 261 | } ], 262 | "stepsCount" : 6, 263 | "hasContent" : true, 264 | "attachmentsCount" : 0, 265 | "shouldDisplayMessage" : false 266 | }, { 267 | "name" : "Fill SQL editor with value \"'SELECT * FROM Customers WHERE CustomerName=\"tKEGXG2rvIaH\";'\"", 268 | "time" : { 269 | "start" : 1676194854443, 270 | "stop" : 1676194854689, 271 | "duration" : 246 272 | }, 273 | "status" : "passed", 274 | "steps" : [ { 275 | "name" : "Checking that editor \"SQL editor\" is visible", 276 | "time" : { 277 | "start" : 1676194854443, 278 | "stop" : 1676194854560, 279 | "duration" : 117 280 | }, 281 | "status" : "passed", 282 | "steps" : [ { 283 | "name" : "Getting editor with name \"SQL editor\" and locator \"//div[@class=\"CodeMirror cm-s-default CodeMirror-wrap\"]\"", 284 | "time" : { 285 | "start" : 1676194854443, 286 | "stop" : 1676194854499, 287 | "duration" : 56 288 | }, 289 | "status" : "passed", 290 | "steps" : [ ], 291 | "attachments" : [ ], 292 | "parameters" : [ ], 293 | "stepsCount" : 0, 294 | "hasContent" : false, 295 | "attachmentsCount" : 0, 296 | "shouldDisplayMessage" : false 297 | } ], 298 | "attachments" : [ ], 299 | "parameters" : [ ], 300 | "stepsCount" : 1, 301 | "hasContent" : true, 302 | "attachmentsCount" : 0, 303 | "shouldDisplayMessage" : false 304 | }, { 305 | "name" : "Clearing editor with name \"SQL editor\"", 306 | "time" : { 307 | "start" : 1676194854560, 308 | "stop" : 1676194854631, 309 | "duration" : 71 310 | }, 311 | "status" : "passed", 312 | "steps" : [ ], 313 | "attachments" : [ ], 314 | "parameters" : [ ], 315 | "stepsCount" : 0, 316 | "hasContent" : false, 317 | "attachmentsCount" : 0, 318 | "shouldDisplayMessage" : false 319 | }, { 320 | "name" : "Typing value \"SELECT * FROM Customers WHERE CustomerName=\"tKEGXG2rvIaH\";\" to editor with name \"SQL editor\"", 321 | "time" : { 322 | "start" : 1676194854631, 323 | "stop" : 1676194854689, 324 | "duration" : 58 325 | }, 326 | "status" : "passed", 327 | "steps" : [ ], 328 | "attachments" : [ ], 329 | "parameters" : [ ], 330 | "stepsCount" : 0, 331 | "hasContent" : false, 332 | "attachmentsCount" : 0, 333 | "shouldDisplayMessage" : false 334 | } ], 335 | "attachments" : [ ], 336 | "parameters" : [ { 337 | "name" : "sql", 338 | "value" : "'SELECT * FROM Customers WHERE CustomerName=\"tKEGXG2rvIaH\";'" 339 | } ], 340 | "stepsCount" : 4, 341 | "hasContent" : true, 342 | "attachmentsCount" : 0, 343 | "shouldDisplayMessage" : false 344 | }, { 345 | "name" : "Running SQL script", 346 | "time" : { 347 | "start" : 1676194854689, 348 | "stop" : 1676194854819, 349 | "duration" : 130 350 | }, 351 | "status" : "passed", 352 | "steps" : [ { 353 | "name" : "Clicking button with name \"Run SQL »\"", 354 | "time" : { 355 | "start" : 1676194854689, 356 | "stop" : 1676194854819, 357 | "duration" : 130 358 | }, 359 | "status" : "passed", 360 | "steps" : [ { 361 | "name" : "Getting button with name \"Run SQL »\" and locator \"//button[text()=\"Run SQL »\"]\"", 362 | "time" : { 363 | "start" : 1676194854689, 364 | "stop" : 1676194854750, 365 | "duration" : 61 366 | }, 367 | "status" : "passed", 368 | "steps" : [ ], 369 | "attachments" : [ ], 370 | "parameters" : [ ], 371 | "stepsCount" : 0, 372 | "hasContent" : false, 373 | "attachmentsCount" : 0, 374 | "shouldDisplayMessage" : false 375 | } ], 376 | "attachments" : [ ], 377 | "parameters" : [ ], 378 | "stepsCount" : 1, 379 | "hasContent" : true, 380 | "attachmentsCount" : 0, 381 | "shouldDisplayMessage" : false 382 | } ], 383 | "attachments" : [ ], 384 | "parameters" : [ { 385 | "name" : "empty_result", 386 | "value" : "False" 387 | }, { 388 | "name" : "rows_affected", 389 | "value" : "None" 390 | } ], 391 | "stepsCount" : 2, 392 | "hasContent" : true, 393 | "attachmentsCount" : 0, 394 | "shouldDisplayMessage" : false 395 | }, { 396 | "name" : "Checking that result table row have values \"('tKEGXG2rvIaH', 'bsDUYD5g6', 'Wd8Xmkiwl8', 'aY911Xx7Z2p5a', '497', '5hv60jUzO')\"", 397 | "time" : { 398 | "start" : 1676194854819, 399 | "stop" : 1676194856088, 400 | "duration" : 1269 401 | }, 402 | "status" : "passed", 403 | "steps" : [ { 404 | "name" : "Checking that result table data has text \"'tKEGXG2rvIaH'\" in column \"\"", 405 | "time" : { 406 | "start" : 1676194854820, 407 | "stop" : 1676194855014, 408 | "duration" : 194 409 | }, 410 | "status" : "passed", 411 | "steps" : [ { 412 | "name" : "Checking that table data \"Result table data\" is visible", 413 | "time" : { 414 | "start" : 1676194854820, 415 | "stop" : 1676194854943, 416 | "duration" : 123 417 | }, 418 | "status" : "passed", 419 | "steps" : [ { 420 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[2]\"", 421 | "time" : { 422 | "start" : 1676194854820, 423 | "stop" : 1676194854879, 424 | "duration" : 59 425 | }, 426 | "status" : "passed", 427 | "steps" : [ ], 428 | "attachments" : [ ], 429 | "parameters" : [ ], 430 | "stepsCount" : 0, 431 | "hasContent" : false, 432 | "attachmentsCount" : 0, 433 | "shouldDisplayMessage" : false 434 | } ], 435 | "attachments" : [ ], 436 | "parameters" : [ ], 437 | "stepsCount" : 1, 438 | "hasContent" : true, 439 | "attachmentsCount" : 0, 440 | "shouldDisplayMessage" : false 441 | }, { 442 | "name" : "Checking that table data \"Result table data\" has text \"tKEGXG2rvIaH\"", 443 | "time" : { 444 | "start" : 1676194854943, 445 | "stop" : 1676194855014, 446 | "duration" : 71 447 | }, 448 | "status" : "passed", 449 | "steps" : [ { 450 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[2]\"", 451 | "time" : { 452 | "start" : 1676194854943, 453 | "stop" : 1676194854999, 454 | "duration" : 56 455 | }, 456 | "status" : "passed", 457 | "steps" : [ ], 458 | "attachments" : [ ], 459 | "parameters" : [ ], 460 | "stepsCount" : 0, 461 | "hasContent" : false, 462 | "attachmentsCount" : 0, 463 | "shouldDisplayMessage" : false 464 | } ], 465 | "attachments" : [ ], 466 | "parameters" : [ ], 467 | "stepsCount" : 1, 468 | "hasContent" : true, 469 | "attachmentsCount" : 0, 470 | "shouldDisplayMessage" : false 471 | } ], 472 | "attachments" : [ ], 473 | "parameters" : [ { 474 | "name" : "expected_text", 475 | "value" : "'tKEGXG2rvIaH'" 476 | }, { 477 | "name" : "reference_text", 478 | "value" : "'tKEGXG2rvIaH'" 479 | }, { 480 | "name" : "column", 481 | "value" : "" 482 | } ], 483 | "stepsCount" : 4, 484 | "hasContent" : true, 485 | "attachmentsCount" : 0, 486 | "shouldDisplayMessage" : false 487 | }, { 488 | "name" : "Checking that result table data has text \"'bsDUYD5g6'\" in column \"\"", 489 | "time" : { 490 | "start" : 1676194855015, 491 | "stop" : 1676194855214, 492 | "duration" : 199 493 | }, 494 | "status" : "passed", 495 | "steps" : [ { 496 | "name" : "Checking that table data \"Result table data\" is visible", 497 | "time" : { 498 | "start" : 1676194855015, 499 | "stop" : 1676194855145, 500 | "duration" : 130 501 | }, 502 | "status" : "passed", 503 | "steps" : [ { 504 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[3]\"", 505 | "time" : { 506 | "start" : 1676194855015, 507 | "stop" : 1676194855073, 508 | "duration" : 58 509 | }, 510 | "status" : "passed", 511 | "steps" : [ ], 512 | "attachments" : [ ], 513 | "parameters" : [ ], 514 | "stepsCount" : 0, 515 | "hasContent" : false, 516 | "attachmentsCount" : 0, 517 | "shouldDisplayMessage" : false 518 | } ], 519 | "attachments" : [ ], 520 | "parameters" : [ ], 521 | "stepsCount" : 1, 522 | "hasContent" : true, 523 | "attachmentsCount" : 0, 524 | "shouldDisplayMessage" : false 525 | }, { 526 | "name" : "Checking that table data \"Result table data\" has text \"bsDUYD5g6\"", 527 | "time" : { 528 | "start" : 1676194855145, 529 | "stop" : 1676194855213, 530 | "duration" : 68 531 | }, 532 | "status" : "passed", 533 | "steps" : [ { 534 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[3]\"", 535 | "time" : { 536 | "start" : 1676194855145, 537 | "stop" : 1676194855189, 538 | "duration" : 44 539 | }, 540 | "status" : "passed", 541 | "steps" : [ ], 542 | "attachments" : [ ], 543 | "parameters" : [ ], 544 | "stepsCount" : 0, 545 | "hasContent" : false, 546 | "attachmentsCount" : 0, 547 | "shouldDisplayMessage" : false 548 | } ], 549 | "attachments" : [ ], 550 | "parameters" : [ ], 551 | "stepsCount" : 1, 552 | "hasContent" : true, 553 | "attachmentsCount" : 0, 554 | "shouldDisplayMessage" : false 555 | } ], 556 | "attachments" : [ ], 557 | "parameters" : [ { 558 | "name" : "expected_text", 559 | "value" : "'bsDUYD5g6'" 560 | }, { 561 | "name" : "reference_text", 562 | "value" : "'tKEGXG2rvIaH'" 563 | }, { 564 | "name" : "column", 565 | "value" : "" 566 | } ], 567 | "stepsCount" : 4, 568 | "hasContent" : true, 569 | "attachmentsCount" : 0, 570 | "shouldDisplayMessage" : false 571 | }, { 572 | "name" : "Checking that result table data has text \"'Wd8Xmkiwl8'\" in column \"\"", 573 | "time" : { 574 | "start" : 1676194855214, 575 | "stop" : 1676194855427, 576 | "duration" : 213 577 | }, 578 | "status" : "passed", 579 | "steps" : [ { 580 | "name" : "Checking that table data \"Result table data\" is visible", 581 | "time" : { 582 | "start" : 1676194855214, 583 | "stop" : 1676194855353, 584 | "duration" : 139 585 | }, 586 | "status" : "passed", 587 | "steps" : [ { 588 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[4]\"", 589 | "time" : { 590 | "start" : 1676194855214, 591 | "stop" : 1676194855302, 592 | "duration" : 88 593 | }, 594 | "status" : "passed", 595 | "steps" : [ ], 596 | "attachments" : [ ], 597 | "parameters" : [ ], 598 | "stepsCount" : 0, 599 | "hasContent" : false, 600 | "attachmentsCount" : 0, 601 | "shouldDisplayMessage" : false 602 | } ], 603 | "attachments" : [ ], 604 | "parameters" : [ ], 605 | "stepsCount" : 1, 606 | "hasContent" : true, 607 | "attachmentsCount" : 0, 608 | "shouldDisplayMessage" : false 609 | }, { 610 | "name" : "Checking that table data \"Result table data\" has text \"Wd8Xmkiwl8\"", 611 | "time" : { 612 | "start" : 1676194855353, 613 | "stop" : 1676194855427, 614 | "duration" : 74 615 | }, 616 | "status" : "passed", 617 | "steps" : [ { 618 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[4]\"", 619 | "time" : { 620 | "start" : 1676194855353, 621 | "stop" : 1676194855411, 622 | "duration" : 58 623 | }, 624 | "status" : "passed", 625 | "steps" : [ ], 626 | "attachments" : [ ], 627 | "parameters" : [ ], 628 | "stepsCount" : 0, 629 | "hasContent" : false, 630 | "attachmentsCount" : 0, 631 | "shouldDisplayMessage" : false 632 | } ], 633 | "attachments" : [ ], 634 | "parameters" : [ ], 635 | "stepsCount" : 1, 636 | "hasContent" : true, 637 | "attachmentsCount" : 0, 638 | "shouldDisplayMessage" : false 639 | } ], 640 | "attachments" : [ ], 641 | "parameters" : [ { 642 | "name" : "expected_text", 643 | "value" : "'Wd8Xmkiwl8'" 644 | }, { 645 | "name" : "reference_text", 646 | "value" : "'tKEGXG2rvIaH'" 647 | }, { 648 | "name" : "column", 649 | "value" : "" 650 | } ], 651 | "stepsCount" : 4, 652 | "hasContent" : true, 653 | "attachmentsCount" : 0, 654 | "shouldDisplayMessage" : false 655 | }, { 656 | "name" : "Checking that result table data has text \"'aY911Xx7Z2p5a'\" in column \"\"", 657 | "time" : { 658 | "start" : 1676194855427, 659 | "stop" : 1676194855677, 660 | "duration" : 250 661 | }, 662 | "status" : "passed", 663 | "steps" : [ { 664 | "name" : "Checking that table data \"Result table data\" is visible", 665 | "time" : { 666 | "start" : 1676194855427, 667 | "stop" : 1676194855582, 668 | "duration" : 155 669 | }, 670 | "status" : "passed", 671 | "steps" : [ { 672 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[5]\"", 673 | "time" : { 674 | "start" : 1676194855427, 675 | "stop" : 1676194855509, 676 | "duration" : 82 677 | }, 678 | "status" : "passed", 679 | "steps" : [ ], 680 | "attachments" : [ ], 681 | "parameters" : [ ], 682 | "stepsCount" : 0, 683 | "hasContent" : false, 684 | "attachmentsCount" : 0, 685 | "shouldDisplayMessage" : false 686 | } ], 687 | "attachments" : [ ], 688 | "parameters" : [ ], 689 | "stepsCount" : 1, 690 | "hasContent" : true, 691 | "attachmentsCount" : 0, 692 | "shouldDisplayMessage" : false 693 | }, { 694 | "name" : "Checking that table data \"Result table data\" has text \"aY911Xx7Z2p5a\"", 695 | "time" : { 696 | "start" : 1676194855582, 697 | "stop" : 1676194855677, 698 | "duration" : 95 699 | }, 700 | "status" : "passed", 701 | "steps" : [ { 702 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[5]\"", 703 | "time" : { 704 | "start" : 1676194855582, 705 | "stop" : 1676194855650, 706 | "duration" : 68 707 | }, 708 | "status" : "passed", 709 | "steps" : [ ], 710 | "attachments" : [ ], 711 | "parameters" : [ ], 712 | "stepsCount" : 0, 713 | "hasContent" : false, 714 | "attachmentsCount" : 0, 715 | "shouldDisplayMessage" : false 716 | } ], 717 | "attachments" : [ ], 718 | "parameters" : [ ], 719 | "stepsCount" : 1, 720 | "hasContent" : true, 721 | "attachmentsCount" : 0, 722 | "shouldDisplayMessage" : false 723 | } ], 724 | "attachments" : [ ], 725 | "parameters" : [ { 726 | "name" : "expected_text", 727 | "value" : "'aY911Xx7Z2p5a'" 728 | }, { 729 | "name" : "reference_text", 730 | "value" : "'tKEGXG2rvIaH'" 731 | }, { 732 | "name" : "column", 733 | "value" : "" 734 | } ], 735 | "stepsCount" : 4, 736 | "hasContent" : true, 737 | "attachmentsCount" : 0, 738 | "shouldDisplayMessage" : false 739 | }, { 740 | "name" : "Checking that result table data has text \"'497'\" in column \"\"", 741 | "time" : { 742 | "start" : 1676194855677, 743 | "stop" : 1676194855881, 744 | "duration" : 204 745 | }, 746 | "status" : "passed", 747 | "steps" : [ { 748 | "name" : "Checking that table data \"Result table data\" is visible", 749 | "time" : { 750 | "start" : 1676194855677, 751 | "stop" : 1676194855807, 752 | "duration" : 130 753 | }, 754 | "status" : "passed", 755 | "steps" : [ { 756 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[6]\"", 757 | "time" : { 758 | "start" : 1676194855677, 759 | "stop" : 1676194855750, 760 | "duration" : 73 761 | }, 762 | "status" : "passed", 763 | "steps" : [ ], 764 | "attachments" : [ ], 765 | "parameters" : [ ], 766 | "stepsCount" : 0, 767 | "hasContent" : false, 768 | "attachmentsCount" : 0, 769 | "shouldDisplayMessage" : false 770 | } ], 771 | "attachments" : [ ], 772 | "parameters" : [ ], 773 | "stepsCount" : 1, 774 | "hasContent" : true, 775 | "attachmentsCount" : 0, 776 | "shouldDisplayMessage" : false 777 | }, { 778 | "name" : "Checking that table data \"Result table data\" has text \"497\"", 779 | "time" : { 780 | "start" : 1676194855807, 781 | "stop" : 1676194855881, 782 | "duration" : 74 783 | }, 784 | "status" : "passed", 785 | "steps" : [ { 786 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[6]\"", 787 | "time" : { 788 | "start" : 1676194855807, 789 | "stop" : 1676194855861, 790 | "duration" : 54 791 | }, 792 | "status" : "passed", 793 | "steps" : [ ], 794 | "attachments" : [ ], 795 | "parameters" : [ ], 796 | "stepsCount" : 0, 797 | "hasContent" : false, 798 | "attachmentsCount" : 0, 799 | "shouldDisplayMessage" : false 800 | } ], 801 | "attachments" : [ ], 802 | "parameters" : [ ], 803 | "stepsCount" : 1, 804 | "hasContent" : true, 805 | "attachmentsCount" : 0, 806 | "shouldDisplayMessage" : false 807 | } ], 808 | "attachments" : [ ], 809 | "parameters" : [ { 810 | "name" : "expected_text", 811 | "value" : "'497'" 812 | }, { 813 | "name" : "reference_text", 814 | "value" : "'tKEGXG2rvIaH'" 815 | }, { 816 | "name" : "column", 817 | "value" : "" 818 | } ], 819 | "stepsCount" : 4, 820 | "hasContent" : true, 821 | "attachmentsCount" : 0, 822 | "shouldDisplayMessage" : false 823 | }, { 824 | "name" : "Checking that result table data has text \"'5hv60jUzO'\" in column \"\"", 825 | "time" : { 826 | "start" : 1676194855881, 827 | "stop" : 1676194856088, 828 | "duration" : 207 829 | }, 830 | "status" : "passed", 831 | "steps" : [ { 832 | "name" : "Checking that table data \"Result table data\" is visible", 833 | "time" : { 834 | "start" : 1676194855881, 835 | "stop" : 1676194855995, 836 | "duration" : 114 837 | }, 838 | "status" : "passed", 839 | "steps" : [ { 840 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[7]\"", 841 | "time" : { 842 | "start" : 1676194855881, 843 | "stop" : 1676194855936, 844 | "duration" : 55 845 | }, 846 | "status" : "passed", 847 | "steps" : [ ], 848 | "attachments" : [ ], 849 | "parameters" : [ ], 850 | "stepsCount" : 0, 851 | "hasContent" : false, 852 | "attachmentsCount" : 0, 853 | "shouldDisplayMessage" : false 854 | } ], 855 | "attachments" : [ ], 856 | "parameters" : [ ], 857 | "stepsCount" : 1, 858 | "hasContent" : true, 859 | "attachmentsCount" : 0, 860 | "shouldDisplayMessage" : false 861 | }, { 862 | "name" : "Checking that table data \"Result table data\" has text \"5hv60jUzO\"", 863 | "time" : { 864 | "start" : 1676194855995, 865 | "stop" : 1676194856088, 866 | "duration" : 93 867 | }, 868 | "status" : "passed", 869 | "steps" : [ { 870 | "name" : "Getting table data with name \"Result table data\" and locator \"//tr/td[text()=\"tKEGXG2rvIaH\"]/../td[7]\"", 871 | "time" : { 872 | "start" : 1676194855995, 873 | "stop" : 1676194856072, 874 | "duration" : 77 875 | }, 876 | "status" : "passed", 877 | "steps" : [ ], 878 | "attachments" : [ ], 879 | "parameters" : [ ], 880 | "stepsCount" : 0, 881 | "hasContent" : false, 882 | "attachmentsCount" : 0, 883 | "shouldDisplayMessage" : false 884 | } ], 885 | "attachments" : [ ], 886 | "parameters" : [ ], 887 | "stepsCount" : 1, 888 | "hasContent" : true, 889 | "attachmentsCount" : 0, 890 | "shouldDisplayMessage" : false 891 | } ], 892 | "attachments" : [ ], 893 | "parameters" : [ { 894 | "name" : "expected_text", 895 | "value" : "'5hv60jUzO'" 896 | }, { 897 | "name" : "reference_text", 898 | "value" : "'tKEGXG2rvIaH'" 899 | }, { 900 | "name" : "column", 901 | "value" : "" 902 | } ], 903 | "stepsCount" : 4, 904 | "hasContent" : true, 905 | "attachmentsCount" : 0, 906 | "shouldDisplayMessage" : false 907 | } ], 908 | "attachments" : [ ], 909 | "parameters" : [ { 910 | "name" : "expected_texts", 911 | "value" : "('tKEGXG2rvIaH', 'bsDUYD5g6', 'Wd8Xmkiwl8', 'aY911Xx7Z2p5a', '497', '5hv60jUzO')" 912 | }, { 913 | "name" : "reference_text", 914 | "value" : "'tKEGXG2rvIaH'" 915 | }, { 916 | "name" : "columns", 917 | "value" : "[, , , , , ]" 918 | } ], 919 | "stepsCount" : 30, 920 | "hasContent" : true, 921 | "attachmentsCount" : 0, 922 | "shouldDisplayMessage" : false 923 | } ], 924 | "attachments" : [ { 925 | "uid" : "26df83d53f3fd1b5", 926 | "name" : "log", 927 | "source" : "26df83d53f3fd1b5.txt", 928 | "type" : "text/plain", 929 | "size" : 3473 930 | } ], 931 | "parameters" : [ ], 932 | "stepsCount" : 52, 933 | "hasContent" : true, 934 | "attachmentsCount" : 1, 935 | "shouldDisplayMessage" : false 936 | }, 937 | "afterStages" : [ { 938 | "name" : "page::0", 939 | "time" : { 940 | "start" : 1676194856104, 941 | "stop" : 1676194856248, 942 | "duration" : 144 943 | }, 944 | "status" : "passed", 945 | "steps" : [ ], 946 | "attachments" : [ { 947 | "uid" : "1ff1771b8f3f9c2d", 948 | "name" : "Page", 949 | "source" : "1ff1771b8f3f9c2d.png", 950 | "type" : "image/png", 951 | "size" : 103953 952 | } ], 953 | "parameters" : [ ], 954 | "stepsCount" : 0, 955 | "hasContent" : true, 956 | "attachmentsCount" : 1, 957 | "shouldDisplayMessage" : false 958 | } ], 959 | "labels" : [ { 960 | "name" : "suite", 961 | "value" : "Smoke" 962 | }, { 963 | "name" : "severity", 964 | "value" : "critical" 965 | }, { 966 | "name" : "feature", 967 | "value" : "Customers" 968 | }, { 969 | "name" : "tag", 970 | "value" : "customers_sql" 971 | }, { 972 | "name" : "tag", 973 | "value" : "ui" 974 | }, { 975 | "name" : "parentSuite", 976 | "value" : "tests" 977 | }, { 978 | "name" : "subSuite", 979 | "value" : "TestCustomersSQL" 980 | }, { 981 | "name" : "host", 982 | "value" : "LAPTOP-VDKHCJMI" 983 | }, { 984 | "name" : "thread", 985 | "value" : "1712-MainThread" 986 | }, { 987 | "name" : "framework", 988 | "value" : "pytest" 989 | }, { 990 | "name" : "language", 991 | "value" : "cpython3" 992 | }, { 993 | "name" : "package", 994 | "value" : "tests.test_customers_sql" 995 | }, { 996 | "name" : "resultFormat", 997 | "value" : "allure2" 998 | } ], 999 | "parameters" : [ { 1000 | "name" : "create_customer", 1001 | "value" : "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" 1002 | } ], 1003 | "links" : [ ], 1004 | "hidden" : false, 1005 | "retry" : false, 1006 | "extra" : { 1007 | "severity" : "critical", 1008 | "retries" : [ ], 1009 | "categories" : [ ], 1010 | "tags" : [ "ui", "customers_sql" ] 1011 | }, 1012 | "source" : "dfff93f27ad3dbb6.json", 1013 | "parameterValues" : [ "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" ] 1014 | } -------------------------------------------------------------------------------- /docs/data/timeline.json: -------------------------------------------------------------------------------- 1 | { 2 | "uid" : "ab17fc5a4eb3bca4b216b548c7f9fcbc", 3 | "name" : "timeline", 4 | "children" : [ { 5 | "name" : "LAPTOP-VDKHCJMI", 6 | "children" : [ { 7 | "name" : "1712-MainThread", 8 | "children" : [ { 9 | "name" : "Select all customers", 10 | "uid" : "50e101896b909c0f", 11 | "parentUid" : "997dcfab5c892c95b8dd00ed926dddcb", 12 | "status" : "passed", 13 | "time" : { 14 | "start" : 1676194845034, 15 | "stop" : 1676194848602, 16 | "duration" : 3568 17 | }, 18 | "flaky" : false, 19 | "newFailed" : false, 20 | "parameters" : [ "'Via Ludovico il Moro 22'", "'Via Ludovico il Moro 22'" ] 21 | }, { 22 | "name" : "Delete customer", 23 | "uid" : "75cc35bef1b0498", 24 | "parentUid" : "997dcfab5c892c95b8dd00ed926dddcb", 25 | "status" : "passed", 26 | "time" : { 27 | "start" : 1676194860801, 28 | "stop" : 1676194863253, 29 | "duration" : 2452 30 | }, 31 | "flaky" : false, 32 | "newFailed" : false, 33 | "parameters" : [ "1" ] 34 | }, { 35 | "name" : "Update customer", 36 | "uid" : "27c7177e1c0b2bec", 37 | "parentUid" : "997dcfab5c892c95b8dd00ed926dddcb", 38 | "status" : "passed", 39 | "time" : { 40 | "start" : 1676194856526, 41 | "stop" : 1676194860261, 42 | "duration" : 3735 43 | }, 44 | "flaky" : false, 45 | "newFailed" : false, 46 | "parameters" : [ "1", "CreateCustomer(customer_name='cKQflX7kixwt', contact_name='4NpuUqp6MZOSWRq', address='bYOP1MTWCDy', city='i8rKllqcsxCUw', postal_code='461', country='et3QbrRx6DlAQE')" ] 47 | }, { 48 | "name" : "Create customer", 49 | "uid" : "dfff93f27ad3dbb6", 50 | "parentUid" : "997dcfab5c892c95b8dd00ed926dddcb", 51 | "status" : "passed", 52 | "time" : { 53 | "start" : 1676194851420, 54 | "stop" : 1676194856088, 55 | "duration" : 4668 56 | }, 57 | "flaky" : false, 58 | "newFailed" : false, 59 | "parameters" : [ "CreateCustomer(customer_name='tKEGXG2rvIaH', contact_name='bsDUYD5g6', address='Wd8Xmkiwl8', city='aY911Xx7Z2p5a', postal_code='497', country='5hv60jUzO')" ] 60 | }, { 61 | "name" : "Filter customers", 62 | "uid" : "4016c4e2667a7019", 63 | "parentUid" : "997dcfab5c892c95b8dd00ed926dddcb", 64 | "status" : "passed", 65 | "time" : { 66 | "start" : 1676194849047, 67 | "stop" : 1676194850946, 68 | "duration" : 1899 69 | }, 70 | "flaky" : false, 71 | "newFailed" : false, 72 | "parameters" : [ "6" ] 73 | } ], 74 | "uid" : "997dcfab5c892c95b8dd00ed926dddcb" 75 | } ], 76 | "uid" : "817c70284293979ce2ceda7b8105bd14" 77 | } ] 78 | } -------------------------------------------------------------------------------- /docs/export/influxDbData.txt: -------------------------------------------------------------------------------- 1 | launch_status failed=0 1676194880000000000 2 | launch_status broken=0 1676194880000000000 3 | launch_status passed=5 1676194880000000000 4 | launch_status skipped=0 1676194880000000000 5 | launch_status unknown=0 1676194880000000000 6 | launch_time duration=18219 1676194880000000000 7 | launch_time min_duration=1899 1676194880000000000 8 | launch_time max_duration=4668 1676194880000000000 9 | launch_time sum_duration=16322 1676194880000000000 10 | launch_retries retries=0 1676194880000000000 11 | launch_retries run=5 1676194880000000000 12 | -------------------------------------------------------------------------------- /docs/export/mail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Allure Report summary mail 6 | 7 | 8 | Mail body 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/export/prometheusData.txt: -------------------------------------------------------------------------------- 1 | launch_status_failed 0 2 | launch_status_broken 0 3 | launch_status_passed 5 4 | launch_status_skipped 0 5 | launch_status_unknown 0 6 | launch_time_duration 18219 7 | launch_time_min_duration 1899 8 | launch_time_max_duration 4668 9 | launch_time_sum_duration 16322 10 | launch_retries_retries 0 11 | launch_retries_run 5 12 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- 1 | module.exports = __webpack_public_path__ + "favicon.ico"; -------------------------------------------------------------------------------- /docs/history/categories-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { } 3 | } ] -------------------------------------------------------------------------------- /docs/history/duration-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "duration" : 18219 4 | } 5 | } ] -------------------------------------------------------------------------------- /docs/history/history-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "failed" : 0, 4 | "broken" : 0, 5 | "skipped" : 0, 6 | "passed" : 5, 7 | "unknown" : 0, 8 | "total" : 5 9 | } 10 | } ] -------------------------------------------------------------------------------- /docs/history/history.json: -------------------------------------------------------------------------------- 1 | { 2 | "3a722f825f913a3567a3ee74e65a269a" : { 3 | "statistic" : { 4 | "failed" : 0, 5 | "broken" : 0, 6 | "skipped" : 0, 7 | "passed" : 1, 8 | "unknown" : 0, 9 | "total" : 1 10 | }, 11 | "items" : [ { 12 | "uid" : "75cc35bef1b0498", 13 | "status" : "passed", 14 | "time" : { 15 | "start" : 1676194860801, 16 | "stop" : 1676194863253, 17 | "duration" : 2452 18 | } 19 | } ] 20 | }, 21 | "088e0ec3d7b20c5ba461617dbc04052f" : { 22 | "statistic" : { 23 | "failed" : 0, 24 | "broken" : 0, 25 | "skipped" : 0, 26 | "passed" : 1, 27 | "unknown" : 0, 28 | "total" : 1 29 | }, 30 | "items" : [ { 31 | "uid" : "50e101896b909c0f", 32 | "status" : "passed", 33 | "time" : { 34 | "start" : 1676194845034, 35 | "stop" : 1676194848602, 36 | "duration" : 3568 37 | } 38 | } ] 39 | }, 40 | "eefc3149fd1eb267c387c9b9181e4dbd" : { 41 | "statistic" : { 42 | "failed" : 0, 43 | "broken" : 0, 44 | "skipped" : 0, 45 | "passed" : 1, 46 | "unknown" : 0, 47 | "total" : 1 48 | }, 49 | "items" : [ { 50 | "uid" : "dfff93f27ad3dbb6", 51 | "status" : "passed", 52 | "time" : { 53 | "start" : 1676194851420, 54 | "stop" : 1676194856088, 55 | "duration" : 4668 56 | } 57 | } ] 58 | }, 59 | "bd54304e84c2b2f8797da1928314c689" : { 60 | "statistic" : { 61 | "failed" : 0, 62 | "broken" : 0, 63 | "skipped" : 0, 64 | "passed" : 1, 65 | "unknown" : 0, 66 | "total" : 1 67 | }, 68 | "items" : [ { 69 | "uid" : "4016c4e2667a7019", 70 | "status" : "passed", 71 | "time" : { 72 | "start" : 1676194849047, 73 | "stop" : 1676194850946, 74 | "duration" : 1899 75 | } 76 | } ] 77 | }, 78 | "6216326c8137f798d57e523bbd484eb6" : { 79 | "statistic" : { 80 | "failed" : 0, 81 | "broken" : 0, 82 | "skipped" : 0, 83 | "passed" : 1, 84 | "unknown" : 0, 85 | "total" : 1 86 | }, 87 | "items" : [ { 88 | "uid" : "27c7177e1c0b2bec", 89 | "status" : "passed", 90 | "time" : { 91 | "start" : 1676194856526, 92 | "stop" : 1676194860261, 93 | "duration" : 3735 94 | } 95 | } ] 96 | } 97 | } -------------------------------------------------------------------------------- /docs/history/retry-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "run" : 5, 4 | "retry" : 0 5 | } 6 | } ] -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Allure Report 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/plugins/behaviors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | allure.api.addTranslation('en', { 4 | tab: { 5 | behaviors: { 6 | name: 'Behaviors' 7 | } 8 | }, 9 | widget: { 10 | behaviors: { 11 | name: 'Features by stories', 12 | showAll: 'show all' 13 | } 14 | } 15 | }); 16 | 17 | allure.api.addTranslation('ru', { 18 | tab: { 19 | behaviors: { 20 | name: 'Функциональность' 21 | } 22 | }, 23 | widget: { 24 | behaviors: { 25 | name: 'Функциональность', 26 | showAll: 'показать все' 27 | } 28 | } 29 | }); 30 | 31 | allure.api.addTranslation('zh', { 32 | tab: { 33 | behaviors: { 34 | name: '功能' 35 | } 36 | }, 37 | widget: { 38 | behaviors: { 39 | name: '特性场景', 40 | showAll: '显示所有' 41 | } 42 | } 43 | }); 44 | 45 | allure.api.addTranslation('de', { 46 | tab: { 47 | behaviors: { 48 | name: 'Verhalten' 49 | } 50 | }, 51 | widget: { 52 | behaviors: { 53 | name: 'Features nach Stories', 54 | showAll: 'Zeige alle' 55 | } 56 | } 57 | }); 58 | 59 | allure.api.addTranslation('nl', { 60 | tab: { 61 | behaviors: { 62 | name: 'Functionaliteit' 63 | } 64 | }, 65 | widget: { 66 | behaviors: { 67 | name: 'Features en story’s', 68 | showAll: 'Toon alle' 69 | } 70 | } 71 | }); 72 | 73 | allure.api.addTranslation('he', { 74 | tab: { 75 | behaviors: { 76 | name: 'התנהגויות' 77 | } 78 | }, 79 | widget: { 80 | behaviors: { 81 | name: 'תכונות לפי סיפורי משתמש', 82 | showAll: 'הצג הכול' 83 | } 84 | } 85 | }); 86 | 87 | allure.api.addTranslation('br', { 88 | tab: { 89 | behaviors: { 90 | name: 'Comportamentos' 91 | } 92 | }, 93 | widget: { 94 | behaviors: { 95 | name: 'Funcionalidades por história', 96 | showAll: 'Mostrar tudo' 97 | } 98 | } 99 | }); 100 | 101 | allure.api.addTranslation('ja', { 102 | tab: { 103 | behaviors: { 104 | name: '振る舞い' 105 | } 106 | }, 107 | widget: { 108 | behaviors: { 109 | name: 'ストーリー別の機能', 110 | showAll: '全て表示' 111 | } 112 | } 113 | }); 114 | 115 | allure.api.addTranslation('es', { 116 | tab: { 117 | behaviors: { 118 | name: 'Funcionalidades' 119 | } 120 | }, 121 | widget: { 122 | behaviors: { 123 | name: 'Funcionalidades por Historias de Usuario', 124 | showAll: 'mostrar todo' 125 | } 126 | } 127 | }); 128 | 129 | allure.api.addTranslation('kr', { 130 | tab: { 131 | behaviors: { 132 | name: '동작' 133 | } 134 | }, 135 | widget: { 136 | behaviors: { 137 | name: '스토리별 기능', 138 | showAll: '전체 보기' 139 | } 140 | } 141 | }); 142 | 143 | allure.api.addTranslation('fr', { 144 | tab: { 145 | behaviors: { 146 | name: 'Comportements' 147 | } 148 | }, 149 | widget: { 150 | behaviors: { 151 | name: 'Thèmes par histoires', 152 | showAll: 'Montrer tout' 153 | } 154 | } 155 | }); 156 | 157 | allure.api.addTab('behaviors', { 158 | title: 'tab.behaviors.name', icon: 'fa fa-list', 159 | route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)', 160 | onEnter: (function (testGroup, testResult, testResultTab) { 161 | return new allure.components.TreeLayout({ 162 | testGroup: testGroup, 163 | testResult: testResult, 164 | testResultTab: testResultTab, 165 | tabName: 'tab.behaviors.name', 166 | baseUrl: 'behaviors', 167 | url: 'data/behaviors.json', 168 | csvUrl: 'data/behaviors.csv' 169 | }); 170 | }) 171 | }); 172 | 173 | allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.extend({ 174 | rowTag: 'a', 175 | title: 'widget.behaviors.name', 176 | baseUrl: 'behaviors', 177 | showLinks: true 178 | })); 179 | -------------------------------------------------------------------------------- /docs/plugins/packages/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | allure.api.addTranslation('en', { 4 | tab: { 5 | packages: { 6 | name: 'Packages' 7 | } 8 | } 9 | }); 10 | 11 | allure.api.addTranslation('ru', { 12 | tab: { 13 | packages: { 14 | name: 'Пакеты' 15 | } 16 | } 17 | }); 18 | 19 | allure.api.addTranslation('zh', { 20 | tab: { 21 | packages: { 22 | name: '包' 23 | } 24 | } 25 | }); 26 | 27 | allure.api.addTranslation('de', { 28 | tab: { 29 | packages: { 30 | name: 'Pakete' 31 | } 32 | } 33 | }); 34 | 35 | allure.api.addTranslation('nl', { 36 | tab: { 37 | packages: { 38 | name: 'Packages' 39 | } 40 | } 41 | }); 42 | 43 | allure.api.addTranslation('he', { 44 | tab: { 45 | packages: { 46 | name: 'חבילות' 47 | } 48 | } 49 | }); 50 | 51 | allure.api.addTranslation('br', { 52 | tab: { 53 | packages: { 54 | name: 'Pacotes' 55 | } 56 | } 57 | }); 58 | 59 | allure.api.addTranslation('ja', { 60 | tab: { 61 | packages: { 62 | name: 'パッケージ' 63 | } 64 | } 65 | }); 66 | 67 | allure.api.addTranslation('es', { 68 | tab: { 69 | packages: { 70 | name: 'Paquetes' 71 | } 72 | } 73 | }); 74 | 75 | allure.api.addTranslation('kr', { 76 | tab: { 77 | packages: { 78 | name: '패키지' 79 | } 80 | } 81 | }); 82 | 83 | allure.api.addTranslation('fr', { 84 | tab: { 85 | packages: { 86 | name: 'Paquets' 87 | } 88 | } 89 | }); 90 | 91 | allure.api.addTab('packages', { 92 | title: 'tab.packages.name', icon: 'fa fa-align-left', 93 | route: 'packages(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)', 94 | onEnter: (function (testGroup, testResult, testResultTab) { 95 | return new allure.components.TreeLayout({ 96 | testGroup: testGroup, 97 | testResult: testResult, 98 | testResultTab: testResultTab, 99 | tabName: 'tab.packages.name', 100 | baseUrl: 'packages', 101 | url: 'data/packages.json' 102 | }); 103 | }) 104 | }); 105 | -------------------------------------------------------------------------------- /docs/plugins/screen-diff/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var settings = allure.getPluginSettings('screen-diff', { diffType: 'diff' }); 3 | 4 | function renderImage(src) { 5 | return ( 6 | '
' + 7 | '' + 10 | '
' 11 | ); 12 | } 13 | 14 | function findImage(data, name) { 15 | if (data.testStage && data.testStage.attachments) { 16 | var matchedImage = data.testStage.attachments.filter(function (attachment) { 17 | return attachment.name === name; 18 | })[0]; 19 | if (matchedImage) { 20 | return 'data/attachments/' + matchedImage.source; 21 | } 22 | } 23 | return null; 24 | } 25 | 26 | function renderDiffContent(type, diffImage, actualImage, expectedImage) { 27 | if (type === 'diff') { 28 | if (diffImage) { 29 | return renderImage(diffImage); 30 | } 31 | } 32 | if (type === 'overlay' && expectedImage) { 33 | return ( 34 | '
' + 35 | '' + 38 | '
' + 39 | '' + 42 | '
' + 43 | '
' 44 | ); 45 | } 46 | if (actualImage) { 47 | return renderImage(actualImage); 48 | } 49 | return 'No diff data provided'; 50 | } 51 | 52 | var TestResultView = Backbone.Marionette.View.extend({ 53 | regions: { 54 | subView: '.screen-diff-view', 55 | }, 56 | template: function () { 57 | return '
'; 58 | }, 59 | onRender: function () { 60 | var data = this.model.toJSON(); 61 | var testType = data.labels.filter(function (label) { 62 | return label.name === 'testType'; 63 | })[0]; 64 | var diffImage = findImage(data, 'diff'); 65 | var actualImage = findImage(data, 'actual'); 66 | var expectedImage = findImage(data, 'expected'); 67 | if (!testType || testType.value !== 'screenshotDiff') { 68 | return; 69 | } 70 | this.showChildView( 71 | 'subView', 72 | new ScreenDiffView({ 73 | diffImage: diffImage, 74 | actualImage: actualImage, 75 | expectedImage: expectedImage, 76 | }), 77 | ); 78 | }, 79 | }); 80 | var ErrorView = Backbone.Marionette.View.extend({ 81 | templateContext: function () { 82 | return this.options; 83 | }, 84 | template: function (data) { 85 | return '
' + data.error + '
'; 86 | }, 87 | }); 88 | var AttachmentView = Backbone.Marionette.View.extend({ 89 | regions: { 90 | subView: '.screen-diff-view', 91 | }, 92 | template: function () { 93 | return '
'; 94 | }, 95 | onRender: function () { 96 | jQuery 97 | .getJSON(this.options.sourceUrl) 98 | .then(this.renderScreenDiffView.bind(this), this.renderErrorView.bind(this)); 99 | }, 100 | renderErrorView: function (error) { 101 | console.log(error); 102 | this.showChildView( 103 | 'subView', 104 | new ErrorView({ 105 | error: error.statusText, 106 | }), 107 | ); 108 | }, 109 | renderScreenDiffView: function (data) { 110 | this.showChildView( 111 | 'subView', 112 | new ScreenDiffView({ 113 | diffImage: data.diff, 114 | actualImage: data.actual, 115 | expectedImage: data.expected, 116 | }), 117 | ); 118 | }, 119 | }); 120 | 121 | var ScreenDiffView = Backbone.Marionette.View.extend({ 122 | className: 'pane__section', 123 | events: function () { 124 | return { 125 | ['click [name="screen-diff-type-' + this.cid + '"]']: 'onDiffTypeChange', 126 | 'mousemove .screen-diff__overlay': 'onOverlayMove', 127 | }; 128 | }, 129 | initialize: function (options) { 130 | this.diffImage = options.diffImage; 131 | this.actualImage = options.actualImage; 132 | this.expectedImage = options.expectedImage; 133 | this.radioName = 'screen-diff-type-' + this.cid; 134 | }, 135 | templateContext: function () { 136 | return { 137 | diffType: settings.get('diffType'), 138 | diffImage: this.diffImage, 139 | actualImage: this.actualImage, 140 | expectedImage: this.expectedImage, 141 | radioName: this.radioName, 142 | }; 143 | }, 144 | template: function (data) { 145 | if (!data.diffImage && !data.actualImage && !data.expectedImage) { 146 | return ''; 147 | } 148 | 149 | return ( 150 | '

Screen Diff

' + 151 | '
' + 152 | '
' + 153 | '' + 156 | '' + 159 | '
' + 160 | renderDiffContent( 161 | data.diffType, 162 | data.diffImage, 163 | data.actualImage, 164 | data.expectedImage, 165 | ) + 166 | '
' 167 | ); 168 | }, 169 | adjustImageSize: function (event) { 170 | var overImage = this.$(event.target); 171 | overImage.width(overImage.width()); 172 | }, 173 | onRender: function () { 174 | const diffType = settings.get('diffType'); 175 | this.$('[name="' + this.radioName + '"][value="' + diffType + '"]').prop( 176 | 'checked', 177 | true, 178 | ); 179 | if (diffType === 'overlay') { 180 | this.$('.screen-diff__image-over img').on('load', this.adjustImageSize.bind(this)); 181 | } 182 | }, 183 | onOverlayMove: function (event) { 184 | var pageX = event.pageX; 185 | var containerScroll = this.$('.screen-diff__container').scrollLeft(); 186 | var elementX = event.currentTarget.getBoundingClientRect().left; 187 | var delta = pageX - elementX + containerScroll; 188 | this.$('.screen-diff__image-over').width(delta); 189 | }, 190 | onDiffTypeChange: function (event) { 191 | settings.save('diffType', event.target.value); 192 | this.render(); 193 | }, 194 | }); 195 | allure.api.addTestResultBlock(TestResultView, { position: 'before' }); 196 | allure.api.addAttachmentViewer('application/vnd.allure.image.diff', { 197 | View: AttachmentView, 198 | icon: 'fa fa-exchange', 199 | }); 200 | })(); 201 | -------------------------------------------------------------------------------- /docs/plugins/screen-diff/styles.css: -------------------------------------------------------------------------------- 1 | .screen-diff__switchers { 2 | margin-bottom: 1em; 3 | } 4 | 5 | .screen-diff__switchers label + label { 6 | margin-left: 1em; 7 | } 8 | 9 | .screen-diff__overlay { 10 | position: relative; 11 | cursor: col-resize; 12 | } 13 | 14 | .screen-diff__container { 15 | overflow-x: auto; 16 | } 17 | 18 | .screen-diff__image-over { 19 | top: 0; 20 | left: 0; 21 | bottom: 0; 22 | background: #fff; 23 | position: absolute; 24 | overflow: hidden; 25 | box-shadow: 2px 0 1px -1px #aaa; 26 | } 27 | 28 | .screen-diff-error { 29 | color: #fd5a3e; 30 | } 31 | -------------------------------------------------------------------------------- /docs/widgets/behaviors.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 1, 3 | "items" : [ { 4 | "uid" : "9817bdee84068b56fffc2b215a9a538d", 5 | "name" : "Customers", 6 | "statistic" : { 7 | "failed" : 0, 8 | "broken" : 0, 9 | "skipped" : 0, 10 | "passed" : 5, 11 | "unknown" : 0, 12 | "total" : 5 13 | } 14 | } ] 15 | } -------------------------------------------------------------------------------- /docs/widgets/categories-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { } 3 | } ] -------------------------------------------------------------------------------- /docs/widgets/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 0, 3 | "items" : [ ] 4 | } -------------------------------------------------------------------------------- /docs/widgets/duration-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "duration" : 18219 4 | } 5 | } ] -------------------------------------------------------------------------------- /docs/widgets/duration.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "dfff93f27ad3dbb6", 3 | "name" : "Create customer", 4 | "time" : { 5 | "start" : 1676194851420, 6 | "stop" : 1676194856088, 7 | "duration" : 4668 8 | }, 9 | "status" : "passed", 10 | "severity" : "critical" 11 | }, { 12 | "uid" : "50e101896b909c0f", 13 | "name" : "Select all customers", 14 | "time" : { 15 | "start" : 1676194845034, 16 | "stop" : 1676194848602, 17 | "duration" : 3568 18 | }, 19 | "status" : "passed", 20 | "severity" : "critical" 21 | }, { 22 | "uid" : "75cc35bef1b0498", 23 | "name" : "Delete customer", 24 | "time" : { 25 | "start" : 1676194860801, 26 | "stop" : 1676194863253, 27 | "duration" : 2452 28 | }, 29 | "status" : "passed", 30 | "severity" : "critical" 31 | }, { 32 | "uid" : "4016c4e2667a7019", 33 | "name" : "Filter customers", 34 | "time" : { 35 | "start" : 1676194849047, 36 | "stop" : 1676194850946, 37 | "duration" : 1899 38 | }, 39 | "status" : "passed", 40 | "severity" : "critical" 41 | }, { 42 | "uid" : "27c7177e1c0b2bec", 43 | "name" : "Update customer", 44 | "time" : { 45 | "start" : 1676194856526, 46 | "stop" : 1676194860261, 47 | "duration" : 3735 48 | }, 49 | "status" : "passed", 50 | "severity" : "critical" 51 | } ] -------------------------------------------------------------------------------- /docs/widgets/environment.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /docs/widgets/executors.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /docs/widgets/history-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "failed" : 0, 4 | "broken" : 0, 5 | "skipped" : 0, 6 | "passed" : 5, 7 | "unknown" : 0, 8 | "total" : 5 9 | } 10 | } ] -------------------------------------------------------------------------------- /docs/widgets/launch.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /docs/widgets/retry-trend.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "data" : { 3 | "run" : 5, 4 | "retry" : 0 5 | } 6 | } ] -------------------------------------------------------------------------------- /docs/widgets/severity.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "4016c4e2667a7019", 3 | "name" : "Filter customers", 4 | "time" : { 5 | "start" : 1676194849047, 6 | "stop" : 1676194850946, 7 | "duration" : 1899 8 | }, 9 | "status" : "passed", 10 | "severity" : "critical" 11 | }, { 12 | "uid" : "27c7177e1c0b2bec", 13 | "name" : "Update customer", 14 | "time" : { 15 | "start" : 1676194856526, 16 | "stop" : 1676194860261, 17 | "duration" : 3735 18 | }, 19 | "status" : "passed", 20 | "severity" : "critical" 21 | }, { 22 | "uid" : "50e101896b909c0f", 23 | "name" : "Select all customers", 24 | "time" : { 25 | "start" : 1676194845034, 26 | "stop" : 1676194848602, 27 | "duration" : 3568 28 | }, 29 | "status" : "passed", 30 | "severity" : "critical" 31 | }, { 32 | "uid" : "dfff93f27ad3dbb6", 33 | "name" : "Create customer", 34 | "time" : { 35 | "start" : 1676194851420, 36 | "stop" : 1676194856088, 37 | "duration" : 4668 38 | }, 39 | "status" : "passed", 40 | "severity" : "critical" 41 | }, { 42 | "uid" : "75cc35bef1b0498", 43 | "name" : "Delete customer", 44 | "time" : { 45 | "start" : 1676194860801, 46 | "stop" : 1676194863253, 47 | "duration" : 2452 48 | }, 49 | "status" : "passed", 50 | "severity" : "critical" 51 | } ] -------------------------------------------------------------------------------- /docs/widgets/status-chart.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "uid" : "dfff93f27ad3dbb6", 3 | "name" : "Create customer", 4 | "time" : { 5 | "start" : 1676194851420, 6 | "stop" : 1676194856088, 7 | "duration" : 4668 8 | }, 9 | "status" : "passed", 10 | "severity" : "critical" 11 | }, { 12 | "uid" : "50e101896b909c0f", 13 | "name" : "Select all customers", 14 | "time" : { 15 | "start" : 1676194845034, 16 | "stop" : 1676194848602, 17 | "duration" : 3568 18 | }, 19 | "status" : "passed", 20 | "severity" : "critical" 21 | }, { 22 | "uid" : "75cc35bef1b0498", 23 | "name" : "Delete customer", 24 | "time" : { 25 | "start" : 1676194860801, 26 | "stop" : 1676194863253, 27 | "duration" : 2452 28 | }, 29 | "status" : "passed", 30 | "severity" : "critical" 31 | }, { 32 | "uid" : "4016c4e2667a7019", 33 | "name" : "Filter customers", 34 | "time" : { 35 | "start" : 1676194849047, 36 | "stop" : 1676194850946, 37 | "duration" : 1899 38 | }, 39 | "status" : "passed", 40 | "severity" : "critical" 41 | }, { 42 | "uid" : "27c7177e1c0b2bec", 43 | "name" : "Update customer", 44 | "time" : { 45 | "start" : 1676194856526, 46 | "stop" : 1676194860261, 47 | "duration" : 3735 48 | }, 49 | "status" : "passed", 50 | "severity" : "critical" 51 | } ] -------------------------------------------------------------------------------- /docs/widgets/suites.json: -------------------------------------------------------------------------------- 1 | { 2 | "total" : 1, 3 | "items" : [ { 4 | "uid" : "e387fa4bb326b54ea8c19c2822aba374", 5 | "name" : "tests", 6 | "statistic" : { 7 | "failed" : 0, 8 | "broken" : 0, 9 | "skipped" : 0, 10 | "passed" : 5, 11 | "unknown" : 0, 12 | "total" : 5 13 | } 14 | } ] 15 | } -------------------------------------------------------------------------------- /docs/widgets/summary.json: -------------------------------------------------------------------------------- 1 | { 2 | "reportName" : "Allure Report", 3 | "testRuns" : [ ], 4 | "statistic" : { 5 | "failed" : 0, 6 | "broken" : 0, 7 | "skipped" : 0, 8 | "passed" : 5, 9 | "unknown" : 0, 10 | "total" : 5 11 | }, 12 | "time" : { 13 | "start" : 1676194845034, 14 | "stop" : 1676194863253, 15 | "duration" : 18219, 16 | "minDuration" : 1899, 17 | "maxDuration" : 4668, 18 | "sumDuration" : 16322 19 | } 20 | } -------------------------------------------------------------------------------- /models/customer.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | from utils.fakers.numbers import get_random_number 4 | from utils.fakers.strings import get_random_string 5 | 6 | 7 | class CreateCustomer(BaseModel): 8 | customer_name: str = Field(alias='CustomerName') 9 | contact_name: str = Field(alias='ContactName') 10 | address: str = Field(alias='Address') 11 | city: str = Field(alias='City') 12 | postal_code: str = Field(alias='PostalCode') 13 | country: str = Field(alias='Country') 14 | 15 | class Config: 16 | allow_population_by_field_name = True 17 | 18 | @classmethod 19 | def get_random(cls) -> 'CreateCustomer': 20 | return CreateCustomer( 21 | customer_name=get_random_string(), 22 | contact_name=get_random_string(), 23 | address=get_random_string(), 24 | city=get_random_string(), 25 | postal_code=str(get_random_number()), 26 | country=get_random_string() 27 | ) 28 | 29 | def columns(self) -> tuple[str, ...]: 30 | return tuple(self.dict(by_alias=True).keys()) 31 | 32 | def values(self) -> tuple[str, ...]: 33 | return tuple(self.dict().values()) 34 | 35 | def join_values(self) -> str: 36 | values = self.values() 37 | columns = self.columns() 38 | 39 | return ', '.join([ 40 | f'{column}="{value}"' for column, value in zip(columns, values) 41 | ]) 42 | -------------------------------------------------------------------------------- /page_factory/button.py: -------------------------------------------------------------------------------- 1 | 2 | from page_factory.component import Component 3 | 4 | 5 | class Button(Component): 6 | @property 7 | def type_of(self) -> str: 8 | return 'button' 9 | -------------------------------------------------------------------------------- /page_factory/component.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from selenium.common.exceptions import StaleElementReferenceException 3 | 4 | from utils.webdriver.driver.element import Element 5 | from utils.webdriver.driver.elements import Elements 6 | from utils.webdriver.driver.page import Page 7 | 8 | 9 | class Component: 10 | def __init__(self, page: Page, locator: str, name: str) -> None: 11 | self._page = page 12 | self._locator = locator 13 | self._name = name 14 | 15 | @property 16 | def type_of(self) -> str: 17 | return 'component' 18 | 19 | @property 20 | def name(self) -> str: 21 | return self._name 22 | 23 | def get_element(self, **kwargs) -> Element: 24 | locator = self._locator.format(**kwargs) 25 | 26 | with allure.step(f'Getting {self.type_of} with name "{self.name}" and locator "{locator}"'): 27 | return self._page.get_xpath(locator) 28 | 29 | def get_elements(self, **kwargs) -> Elements: 30 | locator = self._locator.format(**kwargs) 31 | 32 | with allure.step(f'Getting {self.type_of}s with name "{self.name}" and locator "{locator}"'): 33 | return self._page.find_xpath(locator) 34 | 35 | def click(self, **kwargs) -> None: 36 | with allure.step(f'Clicking {self.type_of} with name "{self.name}"'): 37 | element = self.get_element(**kwargs) 38 | element.click() 39 | 40 | def should_be_visible(self, **kwargs) -> None: 41 | with allure.step(f'Checking that {self.type_of} "{self.name}" is visible'): 42 | try: 43 | element = self.get_element(**kwargs) 44 | element.should().be_visible() 45 | except StaleElementReferenceException: 46 | self.should_be_visible(**kwargs) 47 | 48 | def should_have_text(self, text: str, **kwargs) -> None: 49 | with allure.step(f'Checking that {self.type_of} "{self.name}" has text "{text}"'): 50 | try: 51 | element = self.get_element(**kwargs) 52 | element.should().have_text(text) 53 | except StaleElementReferenceException: 54 | self.should_have_text(text, **kwargs) 55 | 56 | def is_displayed(self, **kwargs) -> bool: 57 | with allure.step(f'Checking if {self.type_of} "{self.name}" is visible'): 58 | element = self.get_element(**kwargs) 59 | return element.is_displayed() 60 | -------------------------------------------------------------------------------- /page_factory/editor.py: -------------------------------------------------------------------------------- 1 | import allure 2 | 3 | from page_factory.textarea import Textarea 4 | 5 | 6 | class Editor(Textarea): 7 | @property 8 | def type_of(self) -> str: 9 | return 'editor' 10 | 11 | def clear(self): 12 | with allure.step(f'Clearing {self.type_of} with name "{self.name}"'): 13 | self._page.execute_script("window.editor.setValue('');") 14 | 15 | def type(self, value: str, **kwargs): 16 | with allure.step(f'Typing value "{value}" to {self.type_of} with name "{self.name}"'): 17 | self._page.execute_script( 18 | f"window.editor.setValue(arguments[0]);", value 19 | ) 20 | -------------------------------------------------------------------------------- /page_factory/input.py: -------------------------------------------------------------------------------- 1 | import allure 2 | 3 | from page_factory.component import Component 4 | 5 | 6 | class Input(Component): 7 | @property 8 | def type_of(self) -> str: 9 | return 'input' 10 | 11 | def type(self, value: str, **kwargs): 12 | with allure.step(f'Typing {self.type_of} "{self.name}" to value "{value}"'): 13 | element = self.get_element(**kwargs) 14 | element.type(value) 15 | 16 | def fill(self, value: str, **kwargs): 17 | with allure.step(f'Fill {self.type_of} "{self.name}" to value "{value}"'): 18 | element = self.get_element(**kwargs) 19 | element.fill(value) 20 | 21 | def clear(self, **kwargs): 22 | with allure.step(f'Clearing {self.type_of} "{self.name}"'): 23 | element = self.get_element(**kwargs) 24 | element.clear() 25 | -------------------------------------------------------------------------------- /page_factory/table_data.py: -------------------------------------------------------------------------------- 1 | from page_factory.component import Component 2 | 3 | 4 | class TableData(Component): 5 | @property 6 | def type_of(self) -> str: 7 | return 'table data' 8 | -------------------------------------------------------------------------------- /page_factory/table_row.py: -------------------------------------------------------------------------------- 1 | import allure 2 | 3 | from page_factory.component import Component 4 | 5 | 6 | class TableRow(Component): 7 | @property 8 | def type_of(self) -> str: 9 | return 'table row' 10 | 11 | def should_have_number_of_rows(self, number_of_rows: int, **kwargs): 12 | step_name = f'Checking that {self.type_of} with name "{self.name}" has length {number_of_rows}' 13 | with allure.step(step_name): 14 | elements = self.get_elements(**kwargs) 15 | elements.should().not_be_empty() 16 | elements.should().have_length(number_of_rows) 17 | -------------------------------------------------------------------------------- /page_factory/text.py: -------------------------------------------------------------------------------- 1 | from page_factory.component import Component 2 | 3 | 4 | class Text(Component): 5 | @property 6 | def type_of(self) -> str: 7 | return 'text' 8 | -------------------------------------------------------------------------------- /page_factory/textarea.py: -------------------------------------------------------------------------------- 1 | from page_factory.input import Input 2 | 3 | 4 | class Textarea(Input): 5 | @property 6 | def type_of(self) -> str: 7 | return 'textarea' 8 | -------------------------------------------------------------------------------- /page_factory/title.py: -------------------------------------------------------------------------------- 1 | from page_factory.component import Component 2 | 3 | 4 | class Title(Component): 5 | @property 6 | def type_of(self) -> str: 7 | return 'title' 8 | -------------------------------------------------------------------------------- /pages/base_page.py: -------------------------------------------------------------------------------- 1 | import allure 2 | 3 | from components.cookies_modal import CookiesModal 4 | from utils.webdriver.driver.page import Page 5 | 6 | 7 | class BasePage: 8 | def __init__(self, page: Page) -> None: 9 | self.page = page 10 | self.cookies_modal = CookiesModal(page) 11 | 12 | def visit(self, url: str) -> None: 13 | with allure.step(f'Opening the url "{url}"'): 14 | self.page.visit(url) 15 | self.cookies_modal.accept_cookies() 16 | 17 | def reload(self) -> Page: 18 | page_url = self.page.url() 19 | with allure.step(f'Reloading page with url "{page_url}"'): 20 | return self.page.reload() 21 | -------------------------------------------------------------------------------- /pages/try_sql_page.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | import allure 4 | 5 | from page_factory.button import Button 6 | from page_factory.editor import Editor 7 | from page_factory.table_data import TableData 8 | from page_factory.table_row import TableRow 9 | from page_factory.text import Text 10 | from page_factory.title import Title 11 | from pages.base_page import BasePage 12 | from utils.webdriver.driver.page import Page 13 | 14 | 15 | class TrySQLPageResultColumn(IntEnum): 16 | CUSTOMER_ID = 1 17 | CUSTOMER_NAME = 2 18 | CONTACT_NAME = 3 19 | ADDRESS = 4 20 | CITY = 5 21 | POSTAL_CODE = 6 22 | COUNTRY = 7 23 | 24 | @classmethod 25 | def to_list( 26 | cls, 27 | exclude: list['TrySQLPageResultColumn'] | None = None 28 | ) -> list['TrySQLPageResultColumn']: 29 | safe_exclude = exclude or [] 30 | return [column for column in cls if (column not in safe_exclude)] 31 | 32 | 33 | class TrySQLPage(BasePage): 34 | def __init__(self, page: Page) -> None: 35 | super().__init__(page) 36 | 37 | self.run_sql_button = Button( 38 | page, locator='//button[text()="Run SQL »"]', name="Run SQL »" 39 | ) 40 | self.sql_editor = Editor( 41 | page, 42 | locator='//div[@class="CodeMirror cm-s-default CodeMirror-wrap"]', 43 | name="SQL editor" 44 | ) 45 | self.query_result_text = Text( 46 | page, 47 | locator='//div[@id="divResultSQL"]//div', 48 | name="Query result" 49 | ) 50 | self.number_of_records_title = Title( 51 | page, 52 | locator='//div[@id="divResultSQL"]//div//div', 53 | name="Number of records" 54 | ) 55 | self.result_table_row = TableRow( 56 | page, 57 | locator='//div[@id="divResultSQL"]//tbody//tr', 58 | name='Result table row' 59 | ) 60 | self.result_table_data = TableData( 61 | page, 62 | locator='//tr/td[text()="{reference_text}"]/../td[{column}]', 63 | name='Result table data' 64 | ) 65 | 66 | @allure.step('Fill SQL editor with value "{sql}"') 67 | def fill_sql_editor(self, sql: str): 68 | self.sql_editor.should_be_visible() 69 | self.sql_editor.clear() 70 | self.sql_editor.type(sql) 71 | 72 | @allure.step('Running SQL script') 73 | def run_sql( 74 | self, 75 | empty_result: bool = False, 76 | rows_affected: int | None = None 77 | ): 78 | self.run_sql_button.click() 79 | 80 | if rows_affected: 81 | self.query_result_text.should_be_visible() 82 | self.query_result_text.should_have_text( 83 | f'You have made changes to the database. Rows affected: {rows_affected}' 84 | ) 85 | 86 | if empty_result: 87 | self.query_result_text.should_be_visible() 88 | self.query_result_text.should_have_text('No result.') 89 | 90 | @allure.step('Checking that result table data has text "{expected_text}" in column "{column}"') 91 | def check_result_table_data( 92 | self, 93 | expected_text: str, 94 | reference_text: str, 95 | column: TrySQLPageResultColumn 96 | ): 97 | self.result_table_data.should_be_visible( 98 | reference_text=reference_text, column=column 99 | ) 100 | self.result_table_data.should_have_text( 101 | expected_text, reference_text=reference_text, column=column 102 | ) 103 | 104 | @allure.step('Checking that result table row have values "{expected_texts}"') 105 | def check_result_table_row( 106 | self, 107 | expected_texts: tuple[str, ...], 108 | reference_text: str, 109 | columns: list[TrySQLPageResultColumn] 110 | ): 111 | for expected_text, column in zip(expected_texts, columns): 112 | self.check_result_table_data( 113 | expected_text=expected_text, 114 | reference_text=reference_text, 115 | column=column 116 | ) 117 | 118 | @allure.step('Checking that number of records in result equals to "{number_of_records}"') 119 | def check_number_of_records(self, number_of_records: int): 120 | self.number_of_records_title.should_be_visible() 121 | self.number_of_records_title.should_have_text( 122 | f'Number of Records: {number_of_records}' 123 | ) 124 | 125 | self.result_table_row.should_have_number_of_rows(number_of_records + 1) 126 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | [ui] 4 | ui: marks tests as ui tests (deselect with '-m "not ui"') 5 | customers_sql: marks tests as customers_sql tests (deselect with '-m "not customers_sql"') 6 | 7 | 8 | addopts = -s -v --durations=10 9 | 10 | testpaths = tests 11 | 12 | python_classes = Test* 13 | 14 | python_functions = test_* 15 | 16 | python_files = test_*.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | allure-pytest==2.12.0 2 | pytest==7.2.1 3 | pydantic==1.10.4 4 | python-dotenv==0.21.1 5 | selenium==4.8.0 6 | selenium-wire==5.1.0 7 | webdriver-manager==3.8.5 -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pages.try_sql_page import TrySQLPage 4 | from utils.webdriver.driver.page import Page 5 | 6 | 7 | @pytest.fixture(scope="function") 8 | def try_sql_page(page: Page) -> TrySQLPage: 9 | return TrySQLPage(page=page) 10 | -------------------------------------------------------------------------------- /tests/test_customers_sql.py: -------------------------------------------------------------------------------- 1 | import allure 2 | import pytest 3 | 4 | from models.customer import CreateCustomer 5 | from pages.try_sql_page import TrySQLPage, TrySQLPageResultColumn 6 | from utils.constants.features import Feature 7 | from utils.constants.routes import UIRoutes 8 | from utils.constants.suites import Suite 9 | 10 | 11 | @pytest.mark.ui 12 | @pytest.mark.customers_sql 13 | @allure.severity(allure.severity_level.CRITICAL) 14 | @allure.suite(Suite.SMOKE) 15 | @allure.feature(Feature.CUSTOMERS) 16 | class TestCustomersSQL: 17 | SQL_TRYSQL_URL = f'{UIRoutes.SQL_TRYSQL}?filename=trysql_select_all' 18 | 19 | @allure.id('1') 20 | @allure.title('Select all customers') 21 | @pytest.mark.parametrize('text', ['Via Ludovico il Moro 22']) 22 | @pytest.mark.parametrize('reference_text', ['Via Ludovico il Moro 22']) 23 | def test_select_all_customers(self, try_sql_page: TrySQLPage, text: str, reference_text: str): 24 | try_sql_page.visit(self.SQL_TRYSQL_URL) 25 | try_sql_page.fill_sql_editor('SELECT * FROM Customers;') 26 | try_sql_page.run_sql() 27 | try_sql_page.check_result_table_data( 28 | expected_text=text, 29 | reference_text=reference_text, 30 | column=TrySQLPageResultColumn.ADDRESS 31 | ) 32 | 33 | @allure.id('2') 34 | @allure.title('Filter customers') 35 | @pytest.mark.parametrize('number_of_records', [6]) 36 | def test_filter_customers(self, try_sql_page: TrySQLPage, number_of_records: int): 37 | try_sql_page.visit(self.SQL_TRYSQL_URL) 38 | try_sql_page.fill_sql_editor( 39 | 'SELECT * FROM Customers WHERE City="London";' 40 | ) 41 | try_sql_page.run_sql() 42 | try_sql_page.check_number_of_records(number_of_records) 43 | 44 | @allure.id('3') 45 | @allure.title('Create customer') 46 | @pytest.mark.parametrize('create_customer', [CreateCustomer.get_random()]) 47 | def test_create_customer(self, try_sql_page: TrySQLPage, create_customer: CreateCustomer): 48 | try_sql_page.visit(self.SQL_TRYSQL_URL) 49 | try_sql_page.fill_sql_editor( 50 | f'INSERT INTO Customers {create_customer.columns()} VALUES {create_customer.values()};' 51 | ) 52 | try_sql_page.run_sql(rows_affected=1) 53 | 54 | try_sql_page.fill_sql_editor( 55 | f'SELECT * FROM Customers WHERE CustomerName="{create_customer.customer_name}";' 56 | ) 57 | try_sql_page.run_sql() 58 | try_sql_page.check_result_table_row( 59 | expected_texts=create_customer.values(), 60 | reference_text=create_customer.customer_name, 61 | columns=TrySQLPageResultColumn.to_list( 62 | exclude=[TrySQLPageResultColumn.CUSTOMER_ID] 63 | ) 64 | ) 65 | 66 | @allure.id('4') 67 | @allure.title('Update customer') 68 | @pytest.mark.parametrize('customer_id', ['1']) 69 | @pytest.mark.parametrize('update_customer', [CreateCustomer.get_random()]) 70 | def test_update_customer( 71 | self, 72 | try_sql_page: TrySQLPage, 73 | customer_id: str, 74 | update_customer: CreateCustomer, 75 | ): 76 | try_sql_page.visit(self.SQL_TRYSQL_URL) 77 | try_sql_page.fill_sql_editor( 78 | f'UPDATE Customers SET {update_customer.join_values()} WHERE CustomerID={customer_id};' 79 | ) 80 | try_sql_page.run_sql(rows_affected=1) 81 | 82 | try_sql_page.fill_sql_editor( 83 | f'SELECT * FROM Customers WHERE CustomerID="{customer_id}";' 84 | ) 85 | try_sql_page.run_sql() 86 | try_sql_page.check_result_table_row( 87 | expected_texts=(customer_id, *update_customer.values()), 88 | reference_text=customer_id, 89 | columns=TrySQLPageResultColumn.to_list() 90 | ) 91 | 92 | @allure.id('5') 93 | @allure.title('Delete customer') 94 | @pytest.mark.parametrize('customer_id', ['1']) 95 | def test_delete_customer(self, try_sql_page: TrySQLPage, customer_id: str): 96 | try_sql_page.visit(self.SQL_TRYSQL_URL) 97 | try_sql_page.fill_sql_editor( 98 | f'DELETE FROM Customers WHERE CustomerID="{customer_id}";' 99 | ) 100 | try_sql_page.run_sql(rows_affected=1) 101 | 102 | try_sql_page.fill_sql_editor( 103 | f'SELECT * FROM Customers WHERE CustomerID="{customer_id}";' 104 | ) 105 | try_sql_page.run_sql(empty_result=True) 106 | -------------------------------------------------------------------------------- /utils/constants/features.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Feature(str, Enum): 5 | USERS = 'Users' 6 | CUSTOMERS = 'Customers' 7 | -------------------------------------------------------------------------------- /utils/constants/routes.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class UIRoutes(str, Enum): 5 | LOGIN = '/log-in' 6 | SQL = '/sql' 7 | SQL_TRYSQL = '/sql/trysql.asp' 8 | -------------------------------------------------------------------------------- /utils/constants/suites.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Suite(str, Enum): 5 | SANITY = 'Sanity' 6 | SMOKE = 'Smoke' 7 | CORE_REGRESSION = 'Core Regression', 8 | EXTENDED_REGRESSION = 'Extended Regression' 9 | -------------------------------------------------------------------------------- /utils/fakers/numbers.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | 4 | def get_random_number(start: int = 100, end=1000) -> int: 5 | return randint(start, end) 6 | -------------------------------------------------------------------------------- /utils/fakers/strings.py: -------------------------------------------------------------------------------- 1 | from random import choice, randint 2 | from string import ascii_letters, digits 3 | 4 | 5 | def get_random_string(start: int = 9, end: int = 15) -> str: 6 | return ''.join(choice(ascii_letters + digits) for _ in range(randint(start, end))) 7 | -------------------------------------------------------------------------------- /utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger = logging.getLogger('Logger') 4 | logger.setLevel(logging.INFO) 5 | 6 | 7 | handler = logging.StreamHandler() 8 | handler.setLevel(logging.INFO) 9 | 10 | formatter = logging.Formatter( 11 | '%(asctime)s [%(levelname)s] %(message)s', 12 | '%Y-%m-%d %H:%M:%S' 13 | ) 14 | handler.setFormatter(formatter) 15 | 16 | logger.addHandler(handler) 17 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/element.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING 3 | 4 | from selenium.webdriver.remote.webdriver import WebElement 5 | 6 | if TYPE_CHECKING: 7 | from utils.types.webdriver.driver.element_should import \ 8 | ElementShouldInterface 9 | 10 | 11 | class ElementInterface(ABC): 12 | @property 13 | @abstractmethod 14 | def web_element(self) -> WebElement: 15 | ... 16 | 17 | @abstractmethod 18 | def should(self, timeout: int = 0, ignored_exceptions: list = None) -> "ElementShouldInterface": 19 | ... 20 | 21 | @abstractmethod 22 | def click(self, force=False) -> "ElementInterface": 23 | ... 24 | 25 | @abstractmethod 26 | def type(self, *args) -> "ElementInterface": 27 | ... 28 | 29 | @abstractmethod 30 | def fill(self, *args) -> "ElementInterface": 31 | ... 32 | 33 | @abstractmethod 34 | def clear(self) -> "ElementInterface": 35 | ... 36 | 37 | @abstractmethod 38 | def is_displayed(self) -> bool: 39 | ... 40 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/element_should.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING 3 | 4 | if TYPE_CHECKING: 5 | from utils.types.webdriver.driver.element import ElementInterface 6 | from utils.types.webdriver.driver.element_wait import ElementWaitInterface 7 | from utils.types.webdriver.driver.page import PageInterface 8 | 9 | 10 | class ElementShouldInterface(ABC): 11 | _page: "PageInterface" 12 | _wait: "ElementWaitInterface" 13 | _element: "ElementInterface" 14 | 15 | @abstractmethod 16 | def __init__( 17 | self, 18 | page: "PageInterface", 19 | element: "ElementInterface", 20 | timeout: int, 21 | ignored_exceptions: list = None 22 | ): 23 | ... 24 | 25 | @abstractmethod 26 | def be_clickable(self) -> "ElementInterface": 27 | ... 28 | 29 | @abstractmethod 30 | def be_hidden(self) -> "ElementInterface": 31 | ... 32 | 33 | @abstractmethod 34 | def be_visible(self) -> "ElementInterface": 35 | ... 36 | 37 | @abstractmethod 38 | def have_text(self, text: str, case_sensitive=True) -> "ElementInterface": 39 | ... 40 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/element_wait.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.remote.webdriver import WebElement 4 | 5 | from utils.types.webdriver.driver.page_wait import WebElementUntilMethod 6 | 7 | 8 | class ElementWaitInterface(ABC): 9 | _timeout: int 10 | _web_element: WebElement 11 | _ignored_exceptions: tuple 12 | 13 | @abstractmethod 14 | def __init__(self, web_element: WebElement, timeout: int, ignored_exceptions: tuple = None): 15 | ... 16 | 17 | @abstractmethod 18 | def until(self, method: WebElementUntilMethod, message=""): 19 | ... 20 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/elements.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.remote.webdriver import WebElement 4 | 5 | from utils.types.webdriver.driver.element import ElementInterface 6 | from utils.types.webdriver.driver.page import PageInterface 7 | 8 | 9 | class ElementsInterface(ABC): 10 | _list: list[ElementInterface] 11 | _page: PageInterface 12 | locator: tuple[str, str] | None 13 | 14 | @abstractmethod 15 | def __init__( 16 | self, 17 | page: PageInterface, 18 | web_elements: list[WebElement], 19 | locator: tuple[str, str] | None 20 | ): 21 | ... 22 | 23 | @abstractmethod 24 | def length(self) -> int: 25 | ... 26 | 27 | @abstractmethod 28 | def is_empty(self) -> bool: 29 | ... 30 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/elements_should.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING, Union 3 | 4 | from selenium.webdriver.support.wait import WebDriverWait 5 | 6 | if TYPE_CHECKING: 7 | from utils.types.webdriver.driver.elements import ElementsInterface 8 | from utils.types.webdriver.driver.page import PageInterface 9 | from utils.types.webdriver.driver.page_wait import PageWaitInterface 10 | 11 | 12 | class ElementsShouldInterface(ABC): 13 | _page: "PageInterface" 14 | _wait: Union[WebDriverWait, "PageWaitInterface"] 15 | _elements: "ElementsInterface" 16 | 17 | @abstractmethod 18 | def __init__( 19 | self, 20 | page: "PageInterface", 21 | elements: "ElementsInterface", 22 | timeout: int, 23 | ignored_exceptions: list = None 24 | ): 25 | ... 26 | 27 | @abstractmethod 28 | def have_length(self, length: int) -> bool: 29 | ... 30 | 31 | @abstractmethod 32 | def not_be_empty(self) -> "ElementsInterface": 33 | ... 34 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/page.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING, Union 3 | 4 | from selenium.webdriver.remote.webdriver import WebDriver 5 | from selenium.webdriver.support.wait import WebDriverWait 6 | 7 | from config import UIConfig 8 | 9 | if TYPE_CHECKING: 10 | from utils.types.webdriver.driver.element import ElementInterface 11 | from utils.types.webdriver.driver.elements import ElementsInterface 12 | from utils.types.webdriver.driver.page_wait import PageWaitInterface 13 | 14 | 15 | class PageInterface(ABC): 16 | config: UIConfig 17 | 18 | @abstractmethod 19 | def __init__(self, config: UIConfig) -> None: 20 | ... 21 | 22 | @property 23 | @abstractmethod 24 | def webdriver(self) -> WebDriver: 25 | ... 26 | 27 | @abstractmethod 28 | def wait( 29 | self, 30 | timeout: int = None, 31 | use_self: bool = False, 32 | ignored_exceptions: list = None 33 | ) -> Union[WebDriverWait, "PageWaitInterface"]: 34 | ... 35 | 36 | @abstractmethod 37 | def init_webdriver(self) -> WebDriver: 38 | ... 39 | 40 | @abstractmethod 41 | def url(self) -> str: 42 | ... 43 | 44 | @abstractmethod 45 | def visit(self, url: str) -> "PageInterface": 46 | ... 47 | 48 | @abstractmethod 49 | def reload(self) -> "PageInterface": 50 | ... 51 | 52 | @abstractmethod 53 | def get_xpath(self, xpath: str, timeout: int = None) -> "ElementInterface": 54 | ... 55 | 56 | @abstractmethod 57 | def find_xpath(self, xpath: str, timeout: int = None) -> "ElementsInterface": 58 | ... 59 | 60 | @abstractmethod 61 | def quit(self): 62 | ... 63 | 64 | @abstractmethod 65 | def screenshot(self, filename: str) -> str: 66 | ... 67 | 68 | @abstractmethod 69 | def maximize_window(self) -> "PageInterface": 70 | ... 71 | 72 | @abstractmethod 73 | def execute_script(self, script: str, *args) -> "PageInterface": 74 | ... 75 | -------------------------------------------------------------------------------- /utils/types/webdriver/driver/page_wait.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import TYPE_CHECKING, Callable, Union 3 | 4 | from selenium.webdriver.remote.webdriver import WebDriver, WebElement 5 | from selenium.webdriver.support.wait import WebDriverWait 6 | 7 | if TYPE_CHECKING: 8 | from utils.types.webdriver.driver.page import PageInterface 9 | 10 | WebDriverUntilMethod = Callable[[WebDriver], bool] 11 | WebElementUntilMethod = Callable[[WebElement], bool] 12 | 13 | 14 | class PageWaitInterface(ABC): 15 | _page: "PageInterface" 16 | _wait: WebDriverWait 17 | _webdriver: WebDriver 18 | 19 | @abstractmethod 20 | def __init__( 21 | self, 22 | page: "PageInterface", 23 | webdriver: WebDriver, 24 | timeout: int, 25 | ignored_exceptions: tuple | None = None 26 | ): 27 | ... 28 | 29 | @abstractmethod 30 | def until(self, method: WebDriverUntilMethod, message=""): 31 | ... 32 | 33 | @abstractmethod 34 | def until_not(self, method: WebDriverUntilMethod, message=""): 35 | ... 36 | 37 | @abstractmethod 38 | def build( 39 | self, 40 | timeout: int, 41 | use_self=False, 42 | ignored_exceptions: list = None 43 | ) -> Union[WebDriverWait, "PageWaitInterface"]: 44 | ... 45 | -------------------------------------------------------------------------------- /utils/webdriver/driver/element.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver import ActionChains 2 | from selenium.webdriver.remote.webdriver import WebElement 3 | 4 | from utils.logger import logger 5 | from utils.types.webdriver.driver.element import ElementInterface 6 | from utils.types.webdriver.driver.page import PageInterface 7 | from utils.webdriver.driver.element_should import ElementShould 8 | 9 | 10 | class Element(ElementInterface): 11 | """Element API: Represents a single DOM webelement and includes the commands to work with it.""" 12 | 13 | def __init__( 14 | self, 15 | page: PageInterface, 16 | web_element: WebElement, 17 | locator: tuple[str, str] | None 18 | ): 19 | self._page = page 20 | self._web_element = web_element 21 | self.locator = locator 22 | 23 | @property 24 | def web_element(self) -> WebElement: 25 | return self._web_element 26 | 27 | def should(self, timeout: int = 0, ignored_exceptions: list = None) -> ElementShould: 28 | """A collection of expectations for this element""" 29 | if timeout: 30 | wait_time = timeout 31 | else: 32 | wait_time = self._page.config.driver.wait_time 33 | 34 | return ElementShould(self._page, self, wait_time, ignored_exceptions) 35 | 36 | def click(self, force=False) -> "Element": 37 | """Clicks the element""" 38 | logger.info("Element.click() - Click this element") 39 | 40 | if force: 41 | self._page.webdriver.execute_script( 42 | "arguments[0].click()", 43 | self.web_element 44 | ) 45 | else: 46 | self.web_element.click() 47 | 48 | return self 49 | 50 | def type(self, *args) -> "Element": 51 | """Simulate a user typing keys into the input""" 52 | logger.info( 53 | "Element.type() - Type keys `%s` into this element", (args,) 54 | ) 55 | 56 | ActionChains(self._page.webdriver) \ 57 | .move_to_element(self.web_element) \ 58 | .send_keys(*args) \ 59 | .perform() 60 | 61 | return self 62 | 63 | def fill(self, *args) -> "Element": 64 | """Fill input element with value""" 65 | logger.info( 66 | "Element.fill() - Fill value `%s` into this element", (args,) 67 | ) 68 | 69 | self.web_element.send_keys(args) 70 | return self 71 | 72 | def clear(self) -> "Element": 73 | """Clears the text of the input or textarea element""" 74 | logger.info("Element.clear() - Clear the input of this element") 75 | 76 | self.web_element.clear() 77 | return self 78 | 79 | def is_displayed(self) -> bool: 80 | """Check that this element is displayed""" 81 | 82 | logger.info( 83 | "Element.is_displayed() - Check if this element is displayed" 84 | ) 85 | 86 | return self.web_element.is_displayed() 87 | -------------------------------------------------------------------------------- /utils/webdriver/driver/element_should.py: -------------------------------------------------------------------------------- 1 | from selenium.common.exceptions import TimeoutException 2 | 3 | from utils.types.webdriver.driver.element import ElementInterface 4 | from utils.types.webdriver.driver.element_should import ElementShouldInterface 5 | from utils.types.webdriver.driver.page import PageInterface 6 | from utils.webdriver.driver.element_wait import ElementWait 7 | 8 | 9 | class ElementShould(ElementShouldInterface): 10 | """ElementShould API: Commands (aka Expectations) for the current Element""" 11 | 12 | def __init__( 13 | self, 14 | page: PageInterface, 15 | element: ElementInterface, 16 | timeout: int, 17 | ignored_exceptions: list = None 18 | ): 19 | self._page = page 20 | self._element = element 21 | self._wait = ElementWait( 22 | element.web_element, timeout, ignored_exceptions 23 | ) 24 | 25 | def be_clickable(self) -> ElementInterface: 26 | """An expectation that the element is displayed and enabled so you can click it""" 27 | try: 28 | value = self._wait.until( 29 | lambda e: e.is_displayed() and e.is_enabled() 30 | ) 31 | except TimeoutException: 32 | value = False 33 | if value: 34 | return self._element 35 | 36 | raise AssertionError("Element was not clickable") 37 | 38 | def be_hidden(self) -> ElementInterface: 39 | """An expectation that the element is not displayed but still in the DOM (aka hidden)""" 40 | try: 41 | value = self._wait.until(lambda e: e and not e.is_displayed()) 42 | except TimeoutException: 43 | value = False 44 | 45 | if value: 46 | return self._element 47 | raise AssertionError("Element was not hidden") 48 | 49 | def be_visible(self) -> ElementInterface: 50 | """An expectation that the element is displayed""" 51 | try: 52 | value = self._wait.until(lambda e: e and e.is_displayed()) 53 | except TimeoutException: 54 | value = False 55 | 56 | if value: 57 | return self._element 58 | 59 | raise AssertionError("Element was not visible") 60 | 61 | def have_text(self, text: str, case_sensitive=True) -> "ElementInterface": 62 | """An expectation that the element has the given text""" 63 | try: 64 | if case_sensitive: 65 | value = self._wait.until(lambda e: e.text == text) 66 | else: 67 | value = self._wait.until( 68 | lambda e: e.text.strip().lower() == text.lower() 69 | ) 70 | except TimeoutException: 71 | value = False 72 | 73 | if value: 74 | return self._element 75 | 76 | raise AssertionError( 77 | f"Expected text: `{text}` - Actual text: `{self._element.text()}`" 78 | ) 79 | -------------------------------------------------------------------------------- /utils/webdriver/driver/element_wait.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.common.exceptions import NoSuchElementException, TimeoutException 4 | from selenium.webdriver.remote.webdriver import WebElement 5 | 6 | from utils.types.webdriver.driver.element_wait import ElementWaitInterface 7 | from utils.types.webdriver.driver.page_wait import WebElementUntilMethod 8 | 9 | 10 | class ElementWait(ElementWaitInterface): 11 | def __init__(self, web_element: WebElement, timeout: int, ignored_exceptions: tuple = None): 12 | self._web_element = web_element 13 | self._timeout = 10 if timeout == 0 else timeout 14 | 15 | if ignored_exceptions: 16 | self._ignored_exceptions = ignored_exceptions 17 | else: 18 | self._ignored_exceptions = (NoSuchElementException,) 19 | 20 | def until(self, method: WebElementUntilMethod, message=""): 21 | screen = None 22 | stacktrace = None 23 | 24 | end_time = time.time() + self._timeout 25 | while True: 26 | try: 27 | value = method(self._web_element) 28 | if value: 29 | return value 30 | except self._ignored_exceptions as exc: 31 | screen = getattr(exc, "screen", None) 32 | stacktrace = getattr(exc, "stacktrace", None) 33 | 34 | time.sleep(0.5) 35 | if time.time() > end_time: 36 | break 37 | 38 | raise TimeoutException(message, screen, stacktrace) 39 | -------------------------------------------------------------------------------- /utils/webdriver/driver/elements.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.remote.webdriver import WebElement 2 | 3 | from utils.types.webdriver.driver.element import ElementInterface 4 | from utils.types.webdriver.driver.elements import ElementsInterface 5 | from utils.types.webdriver.driver.page import PageInterface 6 | from utils.webdriver.driver.elements_should import ElementsShould 7 | 8 | 9 | class Elements(ElementsInterface): 10 | 11 | def __init__( 12 | self, 13 | page: PageInterface, 14 | web_elements: list[WebElement], 15 | locator: tuple[str, str] | None 16 | ): 17 | from utils.webdriver.driver.element import Element 18 | 19 | self._list: list[ElementInterface] = [ 20 | Element(page, element, None) for element in web_elements 21 | ] 22 | self._page = page 23 | self.locator = locator 24 | 25 | def length(self) -> int: 26 | return len(self._list) 27 | 28 | def is_empty(self) -> bool: 29 | """Checks if there are zero elements in the list.""" 30 | return self.length() == 0 31 | 32 | def should(self, timeout: int = 0, ignored_exceptions: list = None) -> ElementsShould: 33 | if timeout: 34 | wait_time = timeout 35 | else: 36 | wait_time = self._page.config.driver.wait_time 37 | return ElementsShould(self._page, self, wait_time, ignored_exceptions) 38 | -------------------------------------------------------------------------------- /utils/webdriver/driver/elements_should.py: -------------------------------------------------------------------------------- 1 | from selenium.common.exceptions import TimeoutException 2 | 3 | from utils.logger import logger 4 | from utils.types.webdriver.driver.elements import ElementsInterface 5 | from utils.types.webdriver.driver.elements_should import \ 6 | ElementsShouldInterface 7 | from utils.types.webdriver.driver.page import PageInterface 8 | 9 | 10 | class ElementsShould(ElementsShouldInterface): 11 | """ElementsShould API: Commands (aka Expectations) for the current list of Elements""" 12 | 13 | def __init__( 14 | self, 15 | page: PageInterface, 16 | elements: "ElementsInterface", 17 | timeout: int, 18 | ignored_exceptions: list = None 19 | ): 20 | self._page = page 21 | self._elements = elements 22 | self._wait = page.wait( 23 | timeout=timeout, 24 | use_self=True, 25 | ignored_exceptions=ignored_exceptions 26 | ) 27 | 28 | def have_length(self, length: int) -> bool: 29 | """ 30 | An expectation that the number of elements in the list is equal to the given length 31 | """ 32 | logger.info("Elements.should().have_length(): %s", length) 33 | 34 | try: 35 | if self._elements.length() == length: 36 | return True 37 | 38 | locator = self._elements.locator 39 | value = self._wait.until( 40 | lambda drvr: len(drvr.find_elements(*locator)) == length 41 | ) 42 | except TimeoutException: 43 | value = False 44 | 45 | if value: 46 | return True 47 | 48 | raise AssertionError(f"Length of elements was not equal to {length}") 49 | 50 | def not_be_empty(self) -> ElementsInterface: 51 | """An expectation that the list has at least one element""" 52 | from utils.webdriver.driver.elements import Elements 53 | 54 | logger.info("Elements.should().not_be_empty()") 55 | 56 | try: 57 | if not self._elements.is_empty(): 58 | return self._elements 59 | 60 | locator = self._elements.locator 61 | value = self._wait.until(lambda drvr: drvr.find_elements(*locator)) 62 | except TimeoutException: 63 | value = False 64 | 65 | if isinstance(value, list): 66 | return Elements(self._page, value, self._elements.locator) 67 | 68 | raise AssertionError("List of elements was empty") 69 | -------------------------------------------------------------------------------- /utils/webdriver/driver/page.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from selenium.common.exceptions import TimeoutException 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.remote.webdriver import WebDriver, WebElement 6 | from selenium.webdriver.support.wait import WebDriverWait 7 | from urllib3.exceptions import MaxRetryError 8 | 9 | from config import UIConfig 10 | from utils.logger import logger 11 | from utils.types.webdriver.driver.page import PageInterface 12 | from utils.webdriver.driver.element import Element 13 | from utils.webdriver.driver.elements import Elements 14 | from utils.webdriver.driver.page_wait import PageWait 15 | from utils.webdriver.factory.factory import build_from_config 16 | 17 | 18 | class Page(PageInterface): 19 | """ 20 | Page interface that representing interactions with page 21 | like finding locators, opening url etc. 22 | """ 23 | 24 | def __init__(self, config: UIConfig): 25 | self.config = config 26 | 27 | self._webdriver = None 28 | self._wait = None 29 | 30 | def init_webdriver(self) -> WebDriver: 31 | """Initialize WebDriver using the UIConfig""" 32 | self._webdriver = build_from_config(self.config) 33 | 34 | self._wait = PageWait( 35 | self, self._webdriver, self.config.driver.wait_time, ignored_exceptions=None 36 | ) 37 | 38 | if self.config.driver.page_load_wait_time: 39 | self.set_page_load_timeout(self.config.driver.page_load_wait_time) 40 | 41 | if self.config.viewport.maximize: 42 | self.maximize_window() 43 | else: 44 | self.viewport( 45 | self.config.viewport.width, 46 | self.config.viewport.height, 47 | self.config.viewport.orientation 48 | ) 49 | return self._webdriver 50 | 51 | @property 52 | def webdriver(self) -> WebDriver: 53 | """The current instance of Selenium's `WebDriver` API.""" 54 | return self.init_webdriver() if self._webdriver is None else self._webdriver 55 | 56 | def url(self) -> str: 57 | """Get the current page's URL""" 58 | return self.webdriver.current_url 59 | 60 | def visit(self, url: str) -> "Page": 61 | """Navigate to the given URL""" 62 | normalized_url = url if url.startswith( 63 | 'http') else (self.config.base_url + url) 64 | 65 | logger.info("Page.visit() - Visit URL: `%s`", normalized_url) 66 | 67 | self.webdriver.get(normalized_url) 68 | return self 69 | 70 | def reload(self) -> "Page": 71 | """Reload (aka refresh) the current window""" 72 | logger.info("Page.reload() - Reloading the page") 73 | 74 | self.webdriver.refresh() 75 | return self 76 | 77 | def wait_until_stable(self) -> WebDriver: 78 | """Waits until webdriver will be stable""" 79 | logger.info("Page.wait_until_stable() - Page wait until driver stable") 80 | 81 | try: 82 | return self.webdriver 83 | except MaxRetryError: 84 | sleep(0.5) 85 | self.wait_until_stable() 86 | 87 | def get_xpath(self, xpath: str, timeout: int = None) -> Element: 88 | """ 89 | Finds the DOM element that match the XPATH selector. 90 | 91 | * If `timeout=None` (default), use the default wait_time. 92 | * If `timeout > 0`, override the default wait_time. 93 | * If `timeout=0`, poll the DOM immediately without any waiting. 94 | """ 95 | logger.info( 96 | "Page.get_xpath() - Get the element with xpath: `%s`", xpath 97 | ) 98 | 99 | by = By.XPATH 100 | 101 | if timeout == 0: 102 | element = self.webdriver.find_element(by, xpath) 103 | else: 104 | element = self.wait(timeout).until( 105 | lambda x: x.find_element(by, xpath), 106 | f"Could not find an element with xpath: `{xpath}`" 107 | ) 108 | 109 | return Element(self, element, locator=(by, xpath)) 110 | 111 | def find_xpath(self, xpath: str, timeout: int = None) -> Elements: 112 | """ 113 | Finds the DOM elements that match the XPATH selector. 114 | 115 | * If `timeout=None` (default), use the default wait_time. 116 | * If `timeout > 0`, override the default wait_time. 117 | * If `timeout=0`, poll the DOM immediately without any waiting. 118 | """ 119 | by = By.XPATH 120 | elements: list[WebElement] = [] 121 | 122 | logger.info( 123 | "Page.find_xpath() - Get the elements with xpath: `%s`", xpath 124 | ) 125 | 126 | try: 127 | if timeout == 0: 128 | elements = self.webdriver.find_elements(by, xpath) 129 | else: 130 | elements = self.wait(timeout).until( 131 | lambda x: x.find_elements(by, xpath), 132 | f"Could not find an element with xpath: `{xpath}`" 133 | ) 134 | except TimeoutException: 135 | pass 136 | 137 | return Elements(self, elements, locator=(by, xpath)) 138 | 139 | def wait( 140 | self, timeout: int = None, use_self: bool = False, ignored_exceptions: list = None 141 | ) -> WebDriverWait | PageWait: 142 | """The Wait object with the given timeout in seconds""" 143 | if timeout: 144 | return self._wait.build(timeout, use_self, ignored_exceptions) 145 | 146 | return self._wait.build(self.config.driver.wait_time, use_self, ignored_exceptions) 147 | 148 | def quit(self): 149 | """Quits the driver""" 150 | logger.info( 151 | "Page.quit() - Quit page and close all windows from the browser session" 152 | ) 153 | 154 | self.webdriver.quit() 155 | 156 | def screenshot(self, filename: str) -> str: 157 | """Take a screenshot of the current Window""" 158 | logger.info("Page.screenshot() - Save screenshot to: `%s`", filename) 159 | 160 | self.webdriver.save_screenshot(filename) 161 | return filename 162 | 163 | def maximize_window(self) -> "Page": 164 | """Maximizes the current Window""" 165 | logger.info("Page.maximize_window() - Maximize browser window") 166 | 167 | self.webdriver.maximize_window() 168 | return self 169 | 170 | def execute_script(self, script: str, *args) -> "Page": 171 | """Executes javascript in the current window or frame""" 172 | logger.info( 173 | "Page.execute_script() - Execute javascript `%s` into the Browser", script 174 | ) 175 | 176 | self.webdriver.execute_script(script, *args) 177 | return self 178 | 179 | def set_page_load_timeout(self, timeout: int) -> "Page": 180 | """Set the amount of time to wait for a page load to complete before throwing an error""" 181 | logger.info( 182 | "Page.set_page_load_timeout() - Set page load timeout: `%s`", timeout 183 | ) 184 | 185 | self.webdriver.set_page_load_timeout(timeout) 186 | return self 187 | 188 | def viewport(self, width: int, height: int, orientation: str = "portrait") -> "Page": 189 | """Control the size and orientation of the current context's browser window""" 190 | logger.info( 191 | "Page.viewport() - Set viewport width: `%s`, height: `%s`, orientation: `%s`", 192 | width, height, orientation 193 | ) 194 | 195 | if orientation == "portrait": 196 | self.webdriver.set_window_size(width, height) 197 | elif orientation == "landscape": 198 | self.webdriver.set_window_size(height, width) 199 | else: 200 | raise ValueError("Orientation must be `portrait` or `landscape`.") 201 | return self 202 | -------------------------------------------------------------------------------- /utils/webdriver/driver/page_wait.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from selenium.webdriver.remote.webdriver import WebDriver 4 | from selenium.webdriver.remote.webelement import WebElement 5 | from selenium.webdriver.support.wait import WebDriverWait 6 | 7 | from utils.types.webdriver.driver.page import PageInterface 8 | from utils.types.webdriver.driver.page_wait import (PageWaitInterface, 9 | WebDriverUntilMethod) 10 | from utils.webdriver.driver.element import Element 11 | from utils.webdriver.driver.elements import Elements 12 | 13 | 14 | class PageWait(PageWaitInterface): 15 | def __init__( 16 | self, 17 | page: PageInterface, 18 | webdriver: WebDriver, 19 | timeout: int, 20 | ignored_exceptions: tuple | None = None 21 | ): 22 | self._page = page 23 | self._webdriver = webdriver 24 | self._wait = WebDriverWait( 25 | driver=webdriver, 26 | timeout=timeout, 27 | ignored_exceptions=ignored_exceptions 28 | ) 29 | 30 | def until(self, method: WebDriverUntilMethod, message=""): 31 | value = self._wait.until(method, message) 32 | 33 | if isinstance(value, WebElement): 34 | return Element(self._page, value, None) 35 | 36 | if isinstance(value, list): 37 | return Elements(self._page, value, None) 38 | 39 | return value 40 | 41 | def until_not(self, method: WebDriverUntilMethod, message=""): 42 | value = self._wait.until_not(method, message) 43 | 44 | if isinstance(value, WebElement): 45 | return Element(self._page, value, None) 46 | 47 | if isinstance(value, list): 48 | return Elements(self._page, value, None) 49 | 50 | return value 51 | 52 | def build( 53 | self, timeout: int, use_self=False, ignored_exceptions: list = None 54 | ) -> Union[WebDriverWait, "PageWait"]: 55 | if use_self: 56 | return PageWait(self._page, self._webdriver, timeout, ignored_exceptions) 57 | 58 | return WebDriverWait(self._webdriver, timeout, ignored_exceptions=ignored_exceptions) 59 | -------------------------------------------------------------------------------- /utils/webdriver/driver/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import allure 4 | 5 | from config import get_ui_config 6 | from utils.webdriver.driver.page import Page 7 | 8 | 9 | def attach_screenshot(page: Page, test_name: str): 10 | ui_config = get_ui_config() 11 | 12 | if not os.path.exists(ui_config.logging.screenshots_dir): 13 | os.mkdir(ui_config.logging.screenshots_dir) 14 | 15 | screenshot = page.screenshot(f'screenshots/{test_name}.png') 16 | 17 | allure.attach.file( 18 | screenshot, name='Page', attachment_type=allure.attachment_type.PNG 19 | ) 20 | -------------------------------------------------------------------------------- /utils/webdriver/factory/browser.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Browser(str, Enum): 5 | CHROME = "chrome" 6 | EDGE = "edge" 7 | FIREFOX = "firefox" 8 | IE = "ie" 9 | OPERA = "opera" 10 | -------------------------------------------------------------------------------- /utils/webdriver/factory/builders.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service as ChromeService 3 | from selenium.webdriver.firefox.service import Service as FirefoxService 4 | from seleniumwire import webdriver as wire_driver 5 | from webdriver_manager.chrome import ChromeDriverManager 6 | from webdriver_manager.firefox import GeckoDriverManager 7 | 8 | from utils.webdriver.factory.browser import Browser 9 | from utils.webdriver.factory.capabilities import build_capabilities 10 | from utils.webdriver.factory.options import build_options 11 | 12 | 13 | def build_chrome( 14 | version: str | None, 15 | options: list[str] | None, 16 | capabilities: dict | None, 17 | experimental_options: list[dict], 18 | seleniumwire_options: dict | None, 19 | extension_paths: list[str] | None, 20 | local_path: str | None, 21 | webdriver_kwargs: dict | None, 22 | ): 23 | """Build a Chrome WebDriver. 24 | 25 | Args: 26 | version: The desired version of Chrome. 27 | options: The list of options/arguments to include. 28 | capabilities: The dict of capabilities to include. 29 | experimental_options: The list of experimental options to include. 30 | seleniumwire_options: The dict of seleniumwire options to include. 31 | extension_paths: The list of extension filepaths to add to the browser. 32 | local_path: The path to the driver binary (only to bypass WebDriverManager) 33 | webdriver_kwargs: additional keyword arguments to pass. 34 | 35 | Usage: 36 | driver = webdriver_factory.build_chrome("latest", ["headless", "incognito"]) 37 | """ 38 | wire_options = seleniumwire_options or {} 39 | browser_options = build_options( 40 | Browser.CHROME, options, experimental_options, extension_paths 41 | ) 42 | capabilities = build_capabilities(Browser.CHROME, capabilities) 43 | 44 | for capability in capabilities: 45 | browser_options.set_capability(capability, capabilities[capability]) 46 | 47 | service = ChromeService( 48 | local_path or ChromeDriverManager(version=version).install() 49 | ) 50 | driver = wire_driver.Chrome( 51 | service=service, 52 | options=browser_options, 53 | seleniumwire_options=wire_options, 54 | **(webdriver_kwargs or {}), 55 | ) 56 | 57 | # enable Performance Metrics from Chrome Dev Tools 58 | driver.execute_cdp_cmd("Performance.enable", {}) 59 | return driver 60 | 61 | 62 | def build_firefox( 63 | version: str | None, 64 | options: list[str] | None, 65 | capabilities: dict | None, 66 | experimental_options: list[dict] | None, 67 | seleniumwire_options: dict | None, 68 | extension_paths: list[str] | None, 69 | local_path: str | None, 70 | webdriver_kwargs: dict | None, 71 | ): 72 | """Build a Firefox WebDriver. 73 | 74 | Args: 75 | version: The desired version of Firefox. 76 | options: The list of options/arguments to include. 77 | capabilities: The dict of capabilities to include. 78 | experimental_options: The list of experimental options to include. 79 | seleniumwire_options: The dict of seleniumwire options to include. 80 | extension_paths: The list of extensions to add to the browser. 81 | local_path: The path to the driver binary. 82 | webdriver_kwargs: additional keyword arguments to pass. 83 | 84 | Usage: 85 | driver = webdriver_factory.build_firefox("latest", ["headless", "incognito"]) 86 | """ 87 | wire_options = seleniumwire_options or {} 88 | capabilities = build_capabilities(Browser.FIREFOX, capabilities) 89 | browser_options = build_options( 90 | Browser.FIREFOX, options, experimental_options, extension_paths 91 | ) 92 | 93 | service = FirefoxService( 94 | local_path or GeckoDriverManager(version=version).install() 95 | ) 96 | return wire_driver.Firefox( 97 | service=service, 98 | capabilities=capabilities, 99 | options=browser_options, 100 | seleniumwire_options=wire_options, 101 | **(webdriver_kwargs or {}), 102 | ) 103 | 104 | 105 | def build_remote( 106 | browser: Browser, 107 | remote_url: str, 108 | options: list[str] | None, 109 | capabilities: dict | None, 110 | experimental_options: list[dict] | None, 111 | extension_paths: list[str] | None, 112 | webdriver_kwargs: dict | None, 113 | ): 114 | """ 115 | Build a RemoteDriver connected to a Grid. 116 | 117 | Args: 118 | browser: Name of the browser to connect to. 119 | remote_url: The URL to connect to the Grid. 120 | options: The list of options/arguments to include. 121 | capabilities: The dict of capabilities to include. 122 | experimental_options: The list of experimental options to include. 123 | extension_paths: The list of extensions to add to the browser. 124 | webdriver_kwargs: additional keyword arguments to pass. 125 | 126 | Usage: 127 | driver = webdriver_factory.build_remote("chrome", "http://localhost:4444/wd/hub", ["headless"]) 128 | 129 | Returns: 130 | The instance of WebDriver once the connection is successful 131 | """ 132 | capabilities = build_capabilities(browser, capabilities) 133 | browser_options = build_options( 134 | browser, options, experimental_options, extension_paths 135 | ) 136 | 137 | for capability in capabilities: 138 | browser_options.set_capability(capability, capabilities[capability]) 139 | 140 | return webdriver.Remote( 141 | command_executor=remote_url, 142 | options=browser_options, 143 | **(webdriver_kwargs or {}), 144 | ) 145 | -------------------------------------------------------------------------------- /utils/webdriver/factory/capabilities.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | 3 | from utils.webdriver.factory.browser import Browser 4 | 5 | Capabilities = dict[str, str] 6 | 7 | 8 | def build_capabilities(browser: Browser, capabilities: Capabilities | None) -> Capabilities: 9 | """ 10 | Build the capabilities dictionary for the given browser. 11 | 12 | Some WebDrivers pass in capabilities directly, but others (ie Chrome) require it be added via the Options object. 13 | 14 | Args: 15 | browser: The name of the browser. 16 | capabilities: The dict of capabilities to include. If None, default caps are used. 17 | 18 | Usage: 19 | caps = build_capabilities("chrome", {"enableVNC": True, "enableVideo": False}) 20 | """ 21 | capabilities: Capabilities = {} 22 | 23 | match browser: 24 | case Browser.CHROME: 25 | capabilities.update(webdriver.DesiredCapabilities.CHROME.copy()) 26 | case Browser.FIREFOX: 27 | capabilities.update(webdriver.DesiredCapabilities.FIREFOX.copy()) 28 | case _: 29 | raise ValueError( 30 | f"{browser} is not supported. Cannot build capabilities." 31 | ) 32 | 33 | if capabilities: 34 | capabilities.update(capabilities) 35 | 36 | return capabilities 37 | -------------------------------------------------------------------------------- /utils/webdriver/factory/factory.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.remote.webdriver import WebDriver 2 | 3 | from config import UIConfig 4 | from utils.webdriver.factory.browser import Browser 5 | from utils.webdriver.factory.builders import (build_chrome, build_firefox, 6 | build_remote) 7 | 8 | 9 | def build_from_config(config: UIConfig) -> WebDriver: 10 | """ 11 | The "main" method for building a WebDriver using UIConfig. 12 | Args: 13 | config: UIConfig from config.py 14 | Usage: 15 | driver = webdriver_factory.build_from_config(config) 16 | Returns: 17 | An instance of WebDriver. 18 | """ 19 | browser = config.driver.browser 20 | remote_url = config.driver.remote_url 21 | 22 | build_config = { 23 | "options": config.driver.options, 24 | "capabilities": config.driver.capabilities, 25 | "experimental_options": config.driver.experimental_options, 26 | "extension_paths": config.driver.extension_paths, 27 | "webdriver_kwargs": config.driver.webdriver_kwargs, 28 | } 29 | 30 | if remote_url: 31 | return build_remote(browser, remote_url, **build_config) 32 | 33 | # Start with SeleniumWire drivers 34 | # Set fields for the rest of the non-remote drivers 35 | build_config["version"] = config.driver.version 36 | build_config["local_path"] = config.driver.local_path 37 | 38 | match browser: 39 | case Browser.CHROME: 40 | return build_chrome( 41 | seleniumwire_options=config.driver.seleniumwire_options, **build_config 42 | ) 43 | case Browser.FIREFOX: 44 | return build_firefox( 45 | seleniumwire_options=config.driver.seleniumwire_options, **build_config 46 | ) 47 | case _: 48 | raise ValueError( 49 | f"{config.driver.browser} is not supported. Cannot build WebDriver from config." 50 | ) 51 | -------------------------------------------------------------------------------- /utils/webdriver/factory/options.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | 3 | from utils.webdriver.factory.browser import Browser 4 | 5 | Options = webdriver.ChromeOptions | webdriver.FirefoxOptions 6 | 7 | 8 | def build_options( 9 | browser: Browser, 10 | browser_options: list[str] | None = None, 11 | experimental_options: list[dict] | None = None, 12 | extension_paths: list[str] | None = None, 13 | ) -> Options: 14 | """Build the Options object for the given browser. 15 | 16 | Args: 17 | browser: The name of the browser. 18 | browser_options: The list of options/arguments to include. 19 | experimental_options: The list of experimental options to include. 20 | extension_paths: The list of extension filepaths to add to the browser session. 21 | 22 | Usage: 23 | options = build_options("chrome", ["headless", "incognito"], [{"useAutomationExtension", False}]) 24 | """ 25 | options: Options | None = None 26 | 27 | match browser: 28 | case Browser.CHROME: 29 | options = webdriver.ChromeOptions() 30 | case Browser.FIREFOX: 31 | options = webdriver.FirefoxOptions() 32 | case _: 33 | raise ValueError( 34 | f"{browser} is not supported. Cannot build options." 35 | ) 36 | 37 | for option in browser_options: 38 | normalized_option = option if option.startswith( 39 | "--") else f"--{option}" 40 | options.add_argument(normalized_option) 41 | 42 | if experimental_options: 43 | for exp_option in experimental_options: 44 | ((name, value),) = exp_option.items() 45 | options.add_experimental_option(name, value) 46 | 47 | if extension_paths: 48 | for path in extension_paths: 49 | options.add_extension(path) 50 | 51 | return options 52 | --------------------------------------------------------------------------------