diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index d9a06fe..c54c231 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -2,6 +2,8 @@ name: Bug report
description: Create a report to help us improve
title: "[BUG]: "
labels: [ bug ]
+assignees: Alex-Reif
+
body:
- type: textarea
id: description
@@ -71,4 +73,4 @@ body:
attributes:
label: Additional context
description: Add any other context about the problem here
- placeholder: For example, screenshot or using of additional frameworks like Sizzle library, Robot Framework or JDI, etc. If you can please, send a link to your project.
\ No newline at end of file
+ placeholder: For example, screenshot or using of additional frameworks like Sizzle library, Robot Framework or JDI, etc. If you can please, send a link to your project.
diff --git a/.github/ISSUE_TEMPLATE/custom.yml b/.github/ISSUE_TEMPLATE/custom.yml
index 68845c8..bdf75c4 100644
--- a/.github/ISSUE_TEMPLATE/custom.yml
+++ b/.github/ISSUE_TEMPLATE/custom.yml
@@ -2,7 +2,7 @@ name: Help-support template
description: Describe you problem happened using Healenium and team will help
title: "[Need support]: "
labels: [ help wanted ]
-assignees: ElenaStepuro
+assignees: Alex-Reif
body:
- type: markdown
attributes:
@@ -58,4 +58,4 @@ body:
attributes:
label: Additional context
description: Add any other context about the problem here
- placeholder: For example, screenshot or using of additional frameworks like Sizzle library, Robot Framework or JDI, etc. If you can please, send a link to your project.
\ No newline at end of file
+ placeholder: For example, screenshot or using of additional frameworks like Sizzle library, Robot Framework or JDI, etc. If you can please, send a link to your project.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b0b6f3a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,160 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/healenium-example-python.iml b/.idea/healenium-example-python.iml
deleted file mode 100644
index 170d602..0000000
--- a/.idea/healenium-example-python.iml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 2d83d70..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index e30bcea..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index f249b4e..e1b14f3 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,113 @@
-# healenium
+# Python Example with Healenium
+
+[](https://hub.docker.com/u/healenium)
+[](https://www.apache.org/licenses/LICENSE-2.0)
+
Python 3.9.5 + Pytest project with healenium usage example
+[1. Start Healenium components](#1-start-healenium-components)
+* [Healenium with Selenium-Grid](#run-healenium-with-selenium-grid)
+* [Healenium with Selenoid](#run-healenium-with-selenoid)
+
+
+[2. Configuration RemoteWebDriver for Healenium](#2-configuration-remotewebdriver-for-healenium)
+
+[3. Run test](#3-run-test)
+
+[4. Monitoring tests running](#4-monitoring-tests-running)
+
## How to start
-### 1.Start Healenium backend from 'infra' folder
-```cd infra```
+### 1. Start Healenium components
+
+Go into healenium folder
+
+```sh
+cd healenium
+```
+
+#### Run Healenium with Selenium-Grid:
+```sh
+docker-compose up -d
+```
+
+#### Run Healenium with Selenoid
+
+> Note: `browsers.json` consists of target browsers and appropriate versions.
+> Before run healenium you have to manually pull selenoid browser docker images with version specified in browsers.json
+
+Example pull selenoid chrome image:
+```sh
+docker pull selenoid/vnc:chrome_111.0
+```
+Full list of browser images you can find [here](https://hub.docker.com/u/selenoid)
+
+
+Run healenium with Selenoid:
+```sh
+docker-compose -f docker-compose-selenoid.yaml up -d
+```
+
+ATTENTION
+
+Verify the next images are Up and Running
+- `postgres-db` (PostgreSQL database to store etalon selector / healing / report)
+- `hlm-proxy` (Proxy client request to Selenium server)
+- `hlm-backend` (CRUD service)
+- `selector imitator` (Convert healed locator to convenient format)
+- `selenoid`/`selenium-grid` (Selenium server)
+
+### 2. Configuration RemoteWebDriver for Healenium
+
+To run using Healenium create RemoteWebDriver with URL ```http://:8085```:
+
+```py
+ options = webdriver.ChromeOptions()
+ self.driver = webdriver.Remote('http://localhost:8085/', options=options)
+```
+
+To temporarily disable the healing mechanism for certain sections of your code, use the following syntax:
-```docker-compose up -d```
+```py
+ self.driver.execute_script("disable_healing_true")
+ ... // Your code that does not require healing
+ self.driver.execute_script("disable_healing_false")
+```
-Verify that images ```healenium/hlm-backend:3.2.0```, ```postgres:11-alpine```, ```healenium/hlm-selector-imitator```, ```healenium/hlm-selenium-4-standalone-xpra``` and ```healenium/hlm-proxy:0.2.1``` are up and running
+### 3. Run test
+To run tests in terminal with pytest you need to go to execute next commands:
-### 2. Project structure
+```sh
+python3 -m venv env
```
-|__healenium-example-python (root)
- |__src
- |__main
- |__locators
- |__pages
- |__tests
- |__selenium tests
- |__infra
- |__docker-compose.yml
-```
-
-### 3.Run test
-To run tests in terminal with pytest you need to go to ```cd healenium_selenium\tests``` project folder
+```sh
+source ./env/bin/activate
+```
-> If you want to execute tests from test_callback.py file, please use the command: ```pytest test_callback.py```
-> And appropriate command for test_markup.py file: ```pytest test_markup.py```
->> In case you want to run all tests in project use ```pytest``` command
+```sh
+python -m pip install -U pytest
+```
+
+```sh
+python -m pip install -U selenium
+```
+
+```sh
+pytest
+```
+
+> If you want to execute tests from specified file, please use the command: ```python -m pytest ./tests/test_css.py```
+>> In case you want to run all tests in project use ```python -m pytest ./tests/``` command
### 4. Monitoring tests running
-You can monitor tests running. To do this go to ```http://:8086```
+You can monitor tests running if you using Healenium with Selenoid plus Selenoid Ui, go to:
+```sh
+http://localhost:8080
+```
+
+## Community / Support
+
+* [Telegram chat](https://t.me/healenium)
+* [YouTube Channel](https://www.youtube.com/channel/UCsZJ0ri-Hp7IA1A6Fgi4Hvg)
+
diff --git a/healenium/LICENSE b/healenium/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/healenium/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/healenium/README.md b/healenium/README.md
new file mode 100644
index 0000000..bce8fee
--- /dev/null
+++ b/healenium/README.md
@@ -0,0 +1,175 @@
+# Healenium
+
+[](https://hub.docker.com/u/healenium)
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://t.me/healenium)
+
+### Table of Contents
+
+[Overall information](#overall-information)
+
+[Healenium installation](#healenium-installation)
+* [Healenium with Selenium-Grid](#run-healenium-with-selenium-grid)
+* [Healenium with Selenoid](#run-healenium-with-selenoid)
+* [Healenium with Appium](#run-healenium-with-appium-only)
+
+[Healenium installation without Docker](#healenium-installation-without-docker)
+
+[Language Examples](#language-examples)
+* [Java](#java)
+* [Python](#python)
+* [C#](#c#)
+* [JavaScript](#javascript)
+
+### Overall information
+Self-healing framework based on Selenium and able to use all Selenium supported languages like Java/Python/JS/C#
+Healenium acts as proxy between client and selenium server.
+
+`Docker-compose` includes the following services:
+- `postgres-db` (PostgreSQL database to store etalon selector / healing / report)
+- `hlm-proxy` (Proxy client request to Selenium server)
+- `hlm-backend` (CRUD service)
+- `selector imitator` (Convert healed locator to convenient format)
+- `selenoid`/`selenium-grid` (Selenium server)
+
+
+
+
+### Healenium installation
+
+Clone Healenium repository:
+```sh
+git clone https://github.com/healenium/healenium.git
+```
+
+#### Run Healenium with Selenium-Grid:
+```sh
+docker-compose up -d
+```
+
+#### Run Healenium with Selenoid
+
+> Note: `browsers.json` consists of target browsers and appropriate versions.
+> Before run healenium you have to manually pull selenoid browser docker images with version specified in browsers.json
+
+Example pull selenoid chrome image:
+```sh
+docker pull selenoid/vnc:chrome_111.0
+```
+Full list of browser images you can find [here](https://hub.docker.com/u/selenoid)
+
+
+Run healenium with Selenoid:
+```sh
+docker-compose -f docker-compose-selenoid.yaml up -d
+```
+
+#### Run Healenium with Appium only
+
+```sh
+docker-compose -f docker-compose-appium.yaml up -d
+```
+More details about integration Healenium with Appium [here](https://github.com/healenium/healenium-appium)
+
+
+### Healenium installation without Docker
+
+Go to shell-installation:
+
+```sh
+cd shell-installatio
+```
+
+There are web and remote options to run healenium.
+
+1. Start PostgeSql server.
+- Create user (healenium_user/YDk2nmNs4s9aCP6K) (example data)
+- Set attribute 'Can Login' (true) to user
+- Create database (healenium) and set owner healenium_user
+- Create schema (healenium) and set owner healenium_user
+
+2. Specify your db user and password data in the bash script 'start_healenium.sh'.
+
+3. Setup selenium server (selenium-grid)
+
+Download healenium services
+```sh
+download_services.sh
+```
+
+Run shell command to launch healenium components
+```sh
+start_healenium.sh
+```
+
+
+### Language examples
+
+```
+ /**
+ * "http://127.0.0.1:8085/" OR "http://localhost:8085/" if you are using locally running proxy server
+ *
+ * if you want to use a remote proxy server,
+ * specify the ip address of this server - "http://remote_ip_address:8085/"
+ */
+```
+
+###### Java:
+```java
+ String nodeURL = "http://localhost:8085/";
+
+ ChromeOptions options = new ChromeOptions();
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+
+ WebDriver driver = new RemoteWebDriver(new URL(nodeURL), options);
+```
+
+###### Python
+```py
+ nodeURL = "http://localhost:8085/"
+
+ options = webdriver.ChromeOptions()
+ options.add_argument('--no-sandbox')
+
+ current_webdriver = webdriver.Remote(
+ command_executor=nodeURL,
+ desired_capabilities=webdriver.DesiredCapabilities.CHROME,
+ options=options,
+ )
+```
+
+###### C#
+```csharp
+ String nodeURL = "http://localhost:8085/";
+
+ ChromeOptions optionsChrome = new ChromeOptions();
+ optionsChrome.AddArguments("--no-sandbox");
+
+ RemoteWebDriver driverChrome = new RemoteWebDriver(new Uri(nodeURL), optionsChrome);
+```
+
+###### JavaScript
+```javascript
+ const NODE_URL = "http://localhost:8085/";
+
+ let args = [
+ "--no-sandbox"
+ ];
+
+ let chromeCapabilities = selenium.Capabilities.chrome()
+ .set('chromeOptions', { args });
+
+ let builder = new selenium.Builder()
+ .forBrowser('chrome')
+ .withCapabilities(chromeCapabilities);
+
+ let driver = await builder.usingServer(NODE_URL).build();
+```
+
+
+## Community / Support
+
+* [Telegram chat](https://t.me/healenium)
+* [GitHub Issues](https://github.com/healenium/healenium/issues)
+* [YouTube Channel](https://www.youtube.com/channel/UCsZJ0ri-Hp7IA1A6Fgi4Hvg)
diff --git a/infra/db/sql/init.sql b/healenium/db/sql/init.sql
similarity index 100%
rename from infra/db/sql/init.sql
rename to healenium/db/sql/init.sql
diff --git a/healenium/docker-compose-appium.yaml b/healenium/docker-compose-appium.yaml
new file mode 100644
index 0000000..d1472db
--- /dev/null
+++ b/healenium/docker-compose-appium.yaml
@@ -0,0 +1,75 @@
+version: "3.8"
+
+services:
+
+ postgres-db:
+ image: postgres:15.5-alpine
+ container_name: postgres-db
+ restart: always
+ ports:
+ - "5432:5432"
+ volumes:
+ - ./db/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ environment:
+ - POSTGRES_DB=healenium
+ - POSTGRES_USER=healenium_user
+ - POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ networks:
+ - healenium
+
+ healenium:
+ image: healenium/hlm-backend:3.4.6
+ container_name: healenium
+ restart: on-failure
+ ports:
+ - "7878:7878"
+ links:
+ - postgres-db
+ environment:
+ - SPRING_POSTGRES_DB=healenium
+ - SPRING_POSTGRES_SCHEMA=healenium
+ - SPRING_POSTGRES_USER=healenium_user
+ - SPRING_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ - SPRING_POSTGRES_DB_HOST=postgres-db
+ - KEY_SELECTOR_URL=false
+ - COLLECT_METRICS=true
+ - FIND_ELEMENTS_AUTO_HEALING=false
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./screenshots/:/screenshots
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ selector-imitator:
+ image: healenium/hlm-selector-imitator:1.4
+ container_name: selector-imitator
+ restart: on-failure
+ ports:
+ - "8000:8000"
+ networks:
+ - healenium
+
+ hlm-proxy:
+ image: healenium/hlm-proxy:2.1.1
+ container_name: hlm-proxy
+ restart: on-failure
+ ports:
+ - "8085:8085"
+ environment:
+ - RECOVERY_TRIES=1
+ - SCORE_CAP=.6
+ - HEAL_ENABLED=true
+ - SELENIUM_SERVER_URL=http://host.docker.internal:4723/wd/hub
+ - HEALENIUM_SERVER_URL=http://localhost:7878
+ - HEALENIUM_SERVICE=http://healenium:7878
+ - IMITATE_SERVICE=http://selector-imitator:8000
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+networks:
+ healenium:
+ name: healenium
diff --git a/healenium/docker-compose-selenoid.yaml b/healenium/docker-compose-selenoid.yaml
new file mode 100644
index 0000000..3cba688
--- /dev/null
+++ b/healenium/docker-compose-selenoid.yaml
@@ -0,0 +1,99 @@
+version: "3.8"
+
+services:
+
+ postgres-db:
+ image: postgres:15.5-alpine
+ container_name: postgres-db
+ restart: always
+ ports:
+ - "5432:5432"
+ volumes:
+ - ./db/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ environment:
+ - POSTGRES_DB=healenium
+ - POSTGRES_USER=healenium_user
+ - POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ networks:
+ - healenium
+
+ healenium:
+ image: healenium/hlm-backend:3.4.6
+ container_name: healenium
+ restart: on-failure
+ ports:
+ - "7878:7878"
+ links:
+ - postgres-db
+ environment:
+ - SPRING_POSTGRES_DB=healenium
+ - SPRING_POSTGRES_SCHEMA=healenium
+ - SPRING_POSTGRES_USER=healenium_user
+ - SPRING_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ - SPRING_POSTGRES_DB_HOST=postgres-db
+ - KEY_SELECTOR_URL=false
+ - COLLECT_METRICS=true
+ - FIND_ELEMENTS_AUTO_HEALING=false
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./screenshots/:/screenshots
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ selector-imitator:
+ image: healenium/hlm-selector-imitator:1.4
+ container_name: selector-imitator
+ restart: on-failure
+ ports:
+ - "8000:8000"
+ networks:
+ - healenium
+
+ hlm-proxy:
+ image: healenium/hlm-proxy:2.1.1
+ container_name: hlm-proxy
+ restart: on-failure
+ ports:
+ - "8085:8085"
+ environment:
+ - RECOVERY_TRIES=1
+ - SCORE_CAP=.6
+ - HEAL_ENABLED=true
+ - SELENIUM_SERVER_URL=http://selenoid:4444/wd/hub
+ - HEALENIUM_SERVER_URL=http://localhost:7878
+ - HEALENIUM_SERVICE=http://healenium:7878
+ - IMITATE_SERVICE=http://selector-imitator:8000
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ selenoid:
+ image: aerokube/selenoid:latest-release
+ volumes:
+ - ./selenoid-config:/etc/selenoid
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ./selenoid-config/video:/opt/selenoid/video
+ - ./selenoid-config/logs:/opt/selenoid/logs
+ environment:
+ - OVERRIDE_VIDEO_OUTPUT_DIR=/path/to/config/video
+ command: ["-conf", "/etc/selenoid/browsers.json", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-container-network", "healenium"]
+ ports:
+ - "4444:4444"
+ networks:
+ - healenium
+
+ selenoid-ui:
+ image: aerokube/selenoid-ui:latest-release
+ restart: on-failure
+ ports:
+ - "8080:8080"
+ command: [ "--selenoid-uri", "http://selenoid:4444/" ]
+ networks:
+ - healenium
+
+networks:
+ healenium:
+ name: healenium
diff --git a/healenium/docker-compose-web.yaml b/healenium/docker-compose-web.yaml
new file mode 100644
index 0000000..12f9291
--- /dev/null
+++ b/healenium/docker-compose-web.yaml
@@ -0,0 +1,54 @@
+version: "3.8"
+
+services:
+
+ postgres-db:
+ image: postgres:15.5-alpine
+ container_name: postgres-db
+ restart: always
+ ports:
+ - "5432:5432"
+ volumes:
+ - ./db/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ environment:
+ - POSTGRES_DB=healenium
+ - POSTGRES_USER=healenium_user
+ - POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ networks:
+ - healenium
+
+ healenium:
+ image: healenium/hlm-backend:3.4.6
+ container_name: healenium
+ restart: on-failure
+ ports:
+ - "7878:7878"
+ links:
+ - postgres-db
+ environment:
+ - SPRING_POSTGRES_DB=healenium
+ - SPRING_POSTGRES_SCHEMA=healenium
+ - SPRING_POSTGRES_USER=healenium_user
+ - SPRING_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ - SPRING_POSTGRES_DB_HOST=postgres-db
+ - KEY_SELECTOR_URL=false
+ - COLLECT_METRICS=true
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./screenshots/:/screenshots
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ selector-imitator:
+ image: healenium/hlm-selector-imitator:1.4
+ container_name: selector-imitator
+ restart: on-failure
+ ports:
+ - "8000:8000"
+ networks:
+ - healenium
+
+networks:
+ healenium:
+ name: healenium
diff --git a/healenium/docker-compose.yaml b/healenium/docker-compose.yaml
new file mode 100644
index 0000000..2a8ff27
--- /dev/null
+++ b/healenium/docker-compose.yaml
@@ -0,0 +1,127 @@
+version: "3.8"
+
+services:
+
+ postgres-db:
+ image: postgres:15.5-alpine
+ container_name: postgres-db
+ restart: always
+ ports:
+ - "5432:5432"
+ volumes:
+ - ./db/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ environment:
+ - POSTGRES_DB=healenium
+ - POSTGRES_USER=healenium_user
+ - POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ networks:
+ - healenium
+
+ healenium:
+ image: healenium/hlm-backend:3.4.6
+ container_name: healenium
+ restart: on-failure
+ ports:
+ - "7878:7878"
+ links:
+ - postgres-db
+ environment:
+ - SPRING_POSTGRES_DB=healenium
+ - SPRING_POSTGRES_SCHEMA=healenium
+ - SPRING_POSTGRES_USER=healenium_user
+ - SPRING_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+ - SPRING_POSTGRES_DB_HOST=postgres-db
+ - KEY_SELECTOR_URL=false
+ - COLLECT_METRICS=true
+ - FIND_ELEMENTS_AUTO_HEALING=false
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./screenshots/:/screenshots
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ selector-imitator:
+ image: healenium/hlm-selector-imitator:1.4
+ container_name: selector-imitator
+ restart: on-failure
+ ports:
+ - "8000:8000"
+ networks:
+ - healenium
+
+ hlm-proxy:
+ image: healenium/hlm-proxy:2.1.1
+ container_name: hlm-proxy
+ restart: on-failure
+ ports:
+ - "8085:8085"
+ environment:
+ - RECOVERY_TRIES=1
+ - SCORE_CAP=.6
+ - HEAL_ENABLED=true
+ - SELENIUM_SERVER_URL=http://selenium-hub:4444/wd/hub
+ - HEALENIUM_SERVER_URL=http://localhost:7878
+ - HEALENIUM_SERVICE=http://healenium:7878
+ - IMITATE_SERVICE=http://selector-imitator:8000
+ - HLM_LOG_LEVEL=info
+ volumes:
+ - ./logs/:/logs
+ networks:
+ - healenium
+
+ chrome:
+ image: selenium/node-chrome:latest
+ container_name: node-chrome
+ shm_size: 2gb
+ depends_on:
+ - selenium-hub
+ environment:
+ - SE_EVENT_BUS_HOST=selenium-hub
+ - SE_EVENT_BUS_PUBLISH_PORT=4442
+ - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+ - SE_NODE_MAX_INSTANCES=5
+ - SE_NODE_MAX_SESSIONS=5
+ - SE_NODE_SESSION_TIMEOUT=20
+ networks:
+ - healenium
+
+ edge:
+ image: selenium/node-edge:latest
+ container_name: node-edge
+ shm_size: 2gb
+ depends_on:
+ - selenium-hub
+ environment:
+ - SE_EVENT_BUS_HOST=selenium-hub
+ - SE_EVENT_BUS_PUBLISH_PORT=4442
+ - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+ networks:
+ - healenium
+
+ firefox:
+ image: selenium/node-firefox:latest
+ container_name: node-firefox
+ shm_size: 2gb
+ depends_on:
+ - selenium-hub
+ environment:
+ - SE_EVENT_BUS_HOST=selenium-hub
+ - SE_EVENT_BUS_PUBLISH_PORT=4442
+ - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
+ networks:
+ - healenium
+
+ selenium-hub:
+ image: selenium/hub:latest
+ container_name: selenium-hub
+ ports:
+ - "4442:4442"
+ - "4443:4443"
+ - "4444:4444"
+ networks:
+ - healenium
+
+networks:
+ healenium:
+ name: healenium
diff --git a/healenium/selenoid-config/browsers.json b/healenium/selenoid-config/browsers.json
new file mode 100644
index 0000000..9807ec3
--- /dev/null
+++ b/healenium/selenoid-config/browsers.json
@@ -0,0 +1,30 @@
+{
+ "chrome": {
+ "default": "111.0",
+ "versions": {
+ "111.0": {
+ "image": "selenoid/vnc:chrome_111.0",
+ "port": "4444"
+ },
+ "110.0": {
+ "image": "selenoid/vnc:chrome_110.0",
+ "port": "4444"
+ }
+ }
+ },
+ "firefox": {
+ "default": "111.0",
+ "versions": {
+ "111.0": {
+ "image": "selenoid/vnc:firefox_111.0",
+ "port": "4444",
+ "path": "/wd/hub"
+ },
+ "110.0": {
+ "image": "selenoid/vnc:firefox_110.0",
+ "port": "4444",
+ "path": "/wd/hub"
+ }
+ }
+ }
+}
diff --git a/healenium/shell-installation/selenium-grid/download_services.sh b/healenium/shell-installation/selenium-grid/download_services.sh
new file mode 100644
index 0000000..90b33c1
--- /dev/null
+++ b/healenium/shell-installation/selenium-grid/download_services.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Versions of the services
+HLM_BACKEND_VERSION="3.4.6"
+HLM_PROXY_VERSION="2.1.1"
+
+# Downloading
+curl -L https://github.com/healenium/healenium-backend/releases/download/${HLM_BACKEND_VERSION}/healenium-backend-${HLM_BACKEND_VERSION}.jar > hlm-backend.jar
+
+curl -L https://github.com/healenium/healenium-proxy/releases/download/${HLM_PROXY_VERSION}/hlm-proxy-${HLM_PROXY_VERSION}.jar > hlm-proxy.jar
+
+git clone https://github.com/healenium/healenium-selector-imitator.git
diff --git a/healenium/shell-installation/selenium-grid/start_healenium.sh b/healenium/shell-installation/selenium-grid/start_healenium.sh
new file mode 100644
index 0000000..88fe583
--- /dev/null
+++ b/healenium/shell-installation/selenium-grid/start_healenium.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Requirements hlm_backend
+HLM_POSTGRES_DB=healenium
+HLM_POSTGRES_SCHEMA=healenium
+HLM_POSTGRES_USER=healenium_user
+HLM_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+HLM_COLLECT_METRICS=true
+HLM_SERVER_PORT=7878
+HLM_LOG_LEVEL=info
+
+# Requirements hlm_proxy
+RECOVERY_TRIES=1
+SCORE_CAP=.6
+HEAL_ENABLED=true
+SELENIUM_SERVER_URL=http://localhost:4444
+HEALENIUM_SERVER_URL=http://localhost:7878
+HEALENIUM_SERVICE=http://localhost:7878
+IMITATE_SERVICE=http://localhost:8000
+
+# Deploy the hlm-backend service
+SPRING_POSTGRES_DB=$HLM_POSTGRES_DB SPRING_POSTGRES_SCHEMA=$HLM_POSTGRES_SCHEMA SPRING_POSTGRES_USER=$HLM_POSTGRES_USER SPRING_POSTGRES_PASSWORD=$HLM_POSTGRES_PASSWORD COLLECT_METRICS=$HLM_COLLECT_METRICS SPRING_SERVER_PORT=$HLM_SERVER_PORT HLM_LOG_LEVEL=$HLM_LOG_LEVEL java -jar hlm-backend.jar 2>&1 & echo $! > ./pid-hlm-backend.file &
+
+# Deploy the hlm-proxy service
+RECOVERY_TRIES=$RECOVERY_TRIES SCORE_CAP=$SCORE_CAP HEAL_ENABLED=$HEAL_ENABLED SELENIUM_SERVER_URL=$SELENIUM_SERVER_URL HEALENIUM_SERVER_URL=$HEALENIUM_SERVER_URL HEALENIUM_SERVICE=$HEALENIUM_SERVICE IMITATE_SERVICE=$IMITATE_SERVICE HLM_LOG_LEVEL=$HLM_LOG_LEVEL java -jar hlm-proxy.jar 2>&1 & echo $! > ./pid-hlm-proxy.file &
+
+# Deploy the imitator service
+pip install --upgrade pip
+
+pip install -r healenium-selector-imitator/requirements.txt
+
+if [[ $OSTYPE == 'msys'* ]];
+then
+ python healenium-selector-imitator/app.py & echo $! > ./pid-selector.file &
+else
+ python3 healenium-selector-imitator/app.py & echo $! > ./pid-selector.file &
+fi
diff --git a/healenium/shell-installation/selenium-grid/stop_healenium.sh b/healenium/shell-installation/selenium-grid/stop_healenium.sh
new file mode 100644
index 0000000..813ac0d
--- /dev/null
+++ b/healenium/shell-installation/selenium-grid/stop_healenium.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+kill $(cat ./pid-hlm-backend.file) $(cat ./pid-hlm-proxy.file) $(cat ./pid-selector.file)
\ No newline at end of file
diff --git a/healenium/shell-installation/web/download_services.sh b/healenium/shell-installation/web/download_services.sh
new file mode 100644
index 0000000..a35c6be
--- /dev/null
+++ b/healenium/shell-installation/web/download_services.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# Versions of the services
+HLM_BACKEND_VERSION="3.4.6"
+
+# Downloading
+curl -L https://github.com/healenium/healenium-backend/releases/download/${HLM_BACKEND_VERSION}/healenium-backend-${HLM_BACKEND_VERSION}.jar > hlm-backend.jar
+
+git clone https://github.com/healenium/healenium-selector-imitator.git
diff --git a/healenium/shell-installation/web/start_healenium.sh b/healenium/shell-installation/web/start_healenium.sh
new file mode 100644
index 0000000..d05a43e
--- /dev/null
+++ b/healenium/shell-installation/web/start_healenium.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Requirements hlm_backend
+HLM_POSTGRES_DB=healenium
+HLM_POSTGRES_SCHEMA=healenium
+HLM_POSTGRES_USER=healenium_user
+HLM_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
+HLM_COLLECT_METRICS=true
+HLM_SERVER_PORT=7878
+HLM_LOG_LEVEL=info
+
+# Deploy the hlm-backend service
+SPRING_POSTGRES_DB=$HLM_POSTGRES_DB SPRING_POSTGRES_SCHEMA=$HLM_POSTGRES_SCHEMA SPRING_POSTGRES_USER=$HLM_POSTGRES_USER SPRING_POSTGRES_PASSWORD=$HLM_POSTGRES_PASSWORD COLLECT_METRICS=$HLM_COLLECT_METRICS SPRING_SERVER_PORT=$HLM_SERVER_PORT HLM_LOG_LEVEL=$HLM_LOG_LEVEL java -jar hlm-backend.jar 2>&1 & echo $! > ./pid-hlm-backend.file &
+
+# Deploy the imitator service
+pip install --upgrade pip
+
+pip install -r healenium-selector-imitator/requirements.txt
+
+if [[ $OSTYPE == 'msys'* ]];
+then
+ python healenium-selector-imitator/app.py & echo $! > ./pid-selector.file &
+else
+ python3 healenium-selector-imitator/app.py & echo $! > ./pid-selector.file &
+fi
diff --git a/healenium/shell-installation/web/stop_healenium.sh b/healenium/shell-installation/web/stop_healenium.sh
new file mode 100644
index 0000000..d71537d
--- /dev/null
+++ b/healenium/shell-installation/web/stop_healenium.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+kill $(cat ./pid-hlm-backend.file) $(cat ./pid-selector.file)
\ No newline at end of file
diff --git a/infra/docker-compose.yaml b/infra/docker-compose.yaml
deleted file mode 100644
index 002fae1..0000000
--- a/infra/docker-compose.yaml
+++ /dev/null
@@ -1,81 +0,0 @@
-version: "3.9"
-
-services:
-
- db:
- image: postgres:11-alpine
- ports:
- - "5432:5432"
- volumes:
- - ./db/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
- restart: always
- environment:
- - POSTGRES_DB=healenium
- - POSTGRES_USER=healenium_user
- - POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
- networks:
- - healenium
-
- healenium:
- image: healenium/hlm-backend:3.2.0
- container_name: healenium
- ports:
- - "7878:7878"
- links:
- - db
- environment:
- - SPRING_CONTAINER_NAME=healenium
- - SPRING_POSTGRES_DB=healenium
- - SPRING_POSTGRES_USER=healenium_user
- - SPRING_POSTGRES_PASSWORD=YDk2nmNs4s9aCP6K
- - SPRING_POSTGRES_URL=jdbc:postgresql://db:5432/healenium?currentSchema=healenium
- networks:
- - healenium
-
- selector-imitator:
- image: healenium/hlm-selector-imitator:1
- container_name: selector-imitator
- restart: on-failure
- ports:
- - "8000:8000"
- networks:
- - healenium
-
- hlm-proxy:
- image: healenium/hlm-proxy:0.2.1
- container_name: hlm-proxy
- ports:
- - "8085:8085"
- volumes:
- - docker-log-volume:/var/log/dockerlogs
- environment:
- - "recovery-tries=1"
- - "score-cap=.6"
- - "heal-enabled=true"
- - "serverHost=localhost"
- - "serverPort=7878"
- - "imitatePort=8000"
- networks:
- - healenium
-
- hlm-selenium-3-standalone-tigervnc:
- # image: healenium/hlm-selenium-4-standalone-xpra:0.1.1 #для версии XPRA селениум 4.0.0-rc-1
- #image: healenium/hlm-selenium-4-standalone-xpra:1.0
- image: healenium/hlm-selenium-3-standalone-tigervnc:0.1.0 #для версии vnc селениум 3.141.59
-
- restart: on-failure
- container_name: hlm-selenium-3-standalone-tigervnc
- ports:
- - "4444:4444"
- - "8086:6080"
- # - "8086:10000"
- volumes:
- - docker-log-volume:/var/log/dockerlogs
- networks:
- - healenium
-
-volumes:
- docker-log-volume:
-
-networks:
- healenium:
\ No newline at end of file
diff --git a/src/main/constants/locator_type.py b/src/main/constants/locator_type.py
new file mode 100644
index 0000000..3c5beec
--- /dev/null
+++ b/src/main/constants/locator_type.py
@@ -0,0 +1,12 @@
+import enum
+
+
+class LocatorType(enum.Enum):
+ xpath = 'xpath',
+ css = 'css',
+ id = 'id',
+ link_text = 'link text',
+ name = 'name',
+ partial_link_text = 'partial link text',
+ tag = 'tag',
+ class_name = 'class name'
diff --git a/src/main/locators/__pycache__/callback_locators.cpython-38.pyc b/src/main/locators/__pycache__/callback_locators.cpython-38.pyc
deleted file mode 100644
index 7ba6e49..0000000
Binary files a/src/main/locators/__pycache__/callback_locators.cpython-38.pyc and /dev/null differ
diff --git a/src/main/locators/__pycache__/callback_locators.cpython-39.pyc b/src/main/locators/__pycache__/callback_locators.cpython-39.pyc
deleted file mode 100644
index 1d6c5d6..0000000
Binary files a/src/main/locators/__pycache__/callback_locators.cpython-39.pyc and /dev/null differ
diff --git a/src/main/locators/__pycache__/markup_locators.cpython-39.pyc b/src/main/locators/__pycache__/markup_locators.cpython-39.pyc
deleted file mode 100644
index 2bc3e8b..0000000
Binary files a/src/main/locators/__pycache__/markup_locators.cpython-39.pyc and /dev/null differ
diff --git a/src/main/locators/callback_locators.py b/src/main/locators/callback_locators.py
deleted file mode 100644
index ef49be1..0000000
--- a/src/main/locators/callback_locators.py
+++ /dev/null
@@ -1,9 +0,0 @@
-
-class Locators(object):
-
- add_square_button = '//button[contains(@class, "add")]'
- update_square_button = '//button[contains(@class, "update")]'
- remove_square_button = '//button[contains(@class, "remove")]'
-
- test_button = '//custom-square[contains(@c, "red")]'
- test_button_css = '[c="red"]'
\ No newline at end of file
diff --git a/src/main/locators/markup_locators.py b/src/main/locators/markup_locators.py
deleted file mode 100644
index dced6ca..0000000
--- a/src/main/locators/markup_locators.py
+++ /dev/null
@@ -1,25 +0,0 @@
-
-class Locators(object):
- # by id
- generate_markup_btn_id = 'markup-generation-button'
- test_generated_button = 'random-id'
- # by class name
- test_button = 'default-btn'
-
- # by xpath
- checkbox_account = '//*[@class="checkbox checkbox_size_m checkbox_theme_alfa-on-white"]'
- text_first_select = '(//*[text()="Select Account"])[1]'
- text_second_select = '(//*[text()="Select Account"])[2]'
-
-from selenium.webdriver.common.by import By
-class Locators_By(object):
- generate_markup_btn_id=(By.ID, 'markup-generation-button')
- test_button=(By.XPATH, '//button[contains(@class,"default-btn")]')
-
- button_invisible=(By.ID,'for-invisible-test')
- field_parent=(By.ID,'field-parent')
-
-class Locators_Request(object):
- generate_markup_btn_id = {'by': By.ID, 'value': 'markup-generation-button'}
- test_generated_button = {'by': By.ID, 'value': 'random-id'}
- test_button = {'by': By.CLASS_NAME, 'value': 'default-btn'}
\ No newline at end of file
diff --git a/src/main/pages/base_page.py b/src/main/pages/base_page.py
index 316bde4..eea3cba 100644
--- a/src/main/pages/base_page.py
+++ b/src/main/pages/base_page.py
@@ -1,10 +1,14 @@
-class Base_Page(object):
+from selenium.webdriver.common.alert import Alert
+
+
+class BasePage(object):
mainPageUrl = 'https://sha-test-app.herokuapp.com/'
callbackTestPageUrl = 'https://mdn.github.io/web-components-examples/life-cycle-callbacks/'
+ testEnvPageUrl = 'https://elenastepuro.github.io/test_env/index.html'
def __init__(self, driver):
self.driver = driver
def confirm_alert(self):
- allert = self.driver.switch_to_alert()
- allert.accept()
+ Alert(self.driver).accept()
+
diff --git a/src/main/pages/callback_page.py b/src/main/pages/callback_page.py
index 94f32a0..fc7bcb7 100644
--- a/src/main/pages/callback_page.py
+++ b/src/main/pages/callback_page.py
@@ -1,30 +1,44 @@
+from selenium import webdriver
from selenium.webdriver.common.by import By
+from src.main.pages.base_page import BasePage
-from src.main.locators.callback_locators import Locators
-from src.main.pages.base_page import Base_Page
+class CallbackPage(BasePage):
-class Callback_Page(Base_Page):
+ add_square_button = '//button[contains(@class, "add")]'
+ update_square_button = '//button[contains(@class, "update")]'
+ remove_square_button = '//button[contains(@class, "remove")]'
+
+ test_button = '//custom-square[contains(@c, "red")]'
+ test_button_css = '[color="red"]'
+
+ driver: webdriver
+
+ def __init__(self):
+ options = webdriver.ChromeOptions()
+ options.add_argument('--no-sandbox')
+ options.add_argument("--disable-dev-shm-usage")
+ self.driver = webdriver.Remote(
+ command_executor="http://localhost:8085/",
+ options=options)
def open_browser(self):
- self.driver.get(Base_Page.callbackTestPageUrl);
+ self.driver.get(BasePage.callbackTestPageUrl)
return self
def click_add_square_button(self):
- self.add_square_button = self.driver.find_element(By.XPATH, Locators.add_square_button)
- self.add_square_button.click()
-
- def verify_shadow_element(self):
- shadowRoot = self.driver.find_element(By.XPATH, Locators.test_button);
- button = self.driver.execute_script("return arguments[0].shadowRoot", shadowRoot)
- return button.is_enabled()
+ add_square_button = self.driver.find_element(By.XPATH, self.add_square_button)
+ add_square_button.click()
def verify_square_element(self):
- square = self.driver.find_element(By.CSS_SELECTOR, Locators.test_button_css)
+ square = self.driver.find_element(By.CSS_SELECTOR, self.test_button_css)
return square.is_enabled()
def click_update_square_button(self):
- self.driver.find_element(By.XPATH, Locators.update_square_button).click()
+ self.driver.find_element(By.XPATH, self.update_square_button).click()
def click_remove_square_button(self):
- self.driver.find_element(By.XPATH, Locators.remove_square_button).click()
+ self.driver.find_element(By.XPATH, self.remove_square_button).click()
+
+ def close(self):
+ self.driver.quit()
diff --git a/src/main/pages/markup_page.py b/src/main/pages/markup_page.py
index 1476b42..f4c8a1c 100644
--- a/src/main/pages/markup_page.py
+++ b/src/main/pages/markup_page.py
@@ -1,95 +1,45 @@
-from selenium.common.exceptions import NoSuchElementException
-from selenium.webdriver.support import expected_conditions
-from selenium.webdriver.support.ui import WebDriverWait
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.support import expected_conditions as ec
-from src.main.locators.markup_locators import *
-from src.main.pages.base_page import Base_Page
+from src.main.pages.base_page import BasePage
-class Markup_Page(Base_Page):
+class MarkupPage(BasePage):
- def open_browser(self):
- self.driver.get(Base_Page.mainPageUrl);
- return self
-
- def generate_markup(self):
- generate_markup = self.driver.find_element_by_id(Locators.generate_markup_btn_id)
- generate_markup.click()
- return self
-
- def click_test_button(self):
- self.driver.find_element_by_class_name(Locators.test_button).click()
+ # by id
+ generate_markup_btn_id = 'markup-generation-button'
+ # by class name
+ test_button = 'default-btn'
- def click_test_generated_button(self):
- self.driver.find_element_by_id(Locators.test_generated_button).click()
+ driver: webdriver
- def test_button_id_enabled(self):
- try:
- return self.driver.find_element_by_id(Locators.test_generated_button).is_enabled()
- except(NoSuchElementException):
- return False
-
- def displayed_text(self):
- try:
- return self.driver.find_element_by_xpath(Locators.text_first_select).is_enabled()
- except(NoSuchElementException):
- return False
-
- def select_first_checkbox(self):
- self.driver.find_element_by_xpath(Locators.checkbox_account).click()
-
- def verify_first_checkbox(self):
- return self.driver.find_element_by_xpath(Locators.checkbox_account).is_enabled()
-
-
-class Markup_Page_By(Base_Page):
+ def __init__(self):
+ options = webdriver.ChromeOptions()
+ options.add_argument('--no-sandbox')
+ options.add_argument("--disable-dev-shm-usage")
+ self.driver = webdriver.Remote(
+ command_executor="http://localhost:8085/",
+ desired_capabilities=webdriver.DesiredCapabilities.CHROME,
+ options=options)
def open_browser(self):
- self.driver.get(Base_Page.mainPageUrl);
+ self.driver.get(BasePage.mainPageUrl)
return self
def generate_markup(self):
- generate_markup = self.driver.find_element(*Locators_By.generate_markup_btn_id)
+ generate_markup = self.driver.find_element_by_id(self.generate_markup_btn_id)
generate_markup.click()
return self
def click_test_button(self):
- test_button = self.driver.find_element(*Locators_By.test_button)
- test_button.click()
- return self
-
- def click_button_for_invisible(self):
- button_invisible = self.driver.find_element(*Locators_By.button_invisible)
- button_invisible.click()
- return self
-
- def check_that_button_invisible(self):
- try:
- WebDriverWait(self.driver, 5).until(
- expected_conditions.invisibility_of_element(*Locators_By.button_invisible))
- return True
- except (Exception):
- return False
-
-
-class Markup_Page_Request(Base_Page):
+ self.driver.find_element_by_class_name(self.test_button).click()
- def open_browser(self):
- self.driver.get(Base_Page.mainPageUrl)
- return self
+ def close(self):
+ self.driver.quit()
- def generate_markup(self):
- generate_markup = self.driver.find_element(**Locators_Request.generate_markup_btn_id)
- generate_markup.click()
- return self
-
- def generate_markup(self):
- generate_markup = self.driver.find_element(**Locators_Request.generate_markup_btn_id)
- generate_markup.click()
- return self
-
- def click_test_button(self):
- self.driver.find_element(**Locators_Request.test_button).click()
+ def click_test_button_wait(self, seconds):
+ test_btn = WebDriverWait(self.driver, seconds).until(ec.visibility_of_element_located((By.XPATH, "//*[contains(@class, 'default-btn')]")))
+ test_btn.click()
- def click_test_generated_button(self):
- self.driver.find_element(**Locators_Request.test_generated_button).click()
\ No newline at end of file
diff --git a/src/main/pages/testenv_page.py b/src/main/pages/testenv_page.py
new file mode 100644
index 0000000..65916b8
--- /dev/null
+++ b/src/main/pages/testenv_page.py
@@ -0,0 +1,63 @@
+import logging
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
+
+from src.main.pages.base_page import BasePage
+from src.main.search.context import Context
+from selenium.webdriver.support import expected_conditions as ec
+
+
+class TestEnvPage(BasePage):
+ __test__ = False
+ submit_btn = 'Submit'
+ submit_form_btn = 'Submit_checkbox'
+
+ driver: webdriver
+
+ def __init__(self):
+ options = webdriver.ChromeOptions()
+ self.driver = webdriver.Remote('http://localhost:8085/', options=options)
+
+ def open_browser(self):
+ self.driver.get(BasePage.testEnvPageUrl)
+ return self
+
+ def select_checkboxes(self):
+ checkboxes = self.driver.find_elements(By.XPATH, "//*[contains(@class,'test-form')]//*[@class='input1']")
+ for ch in checkboxes:
+ ch.click()
+ return self
+
+ def click_form_submit_btn(self):
+ self.driver.find_element_by_id(self.submit_form_btn).click()
+ return self
+
+ def click_submit_btn(self):
+ self.driver.find_element_by_id(self.submit_btn).click()
+ return self
+
+ def select_checkboxes_under_parent(self):
+ checkboxes = self.driver.find_element(By.XPATH, "//*[contains(@class,'test-form')]").find_elements(By.XPATH,
+ ".//*[@class='input1']")
+ for ch in checkboxes:
+ ch.click()
+ return self
+
+ def find_test_element(self, locator_type, selector):
+ logging.info("Find element By ")
+ result = Context().set_strategy(self.driver, locator_type).execute_strategy(selector)
+ assert result == True
+
+ def click_wait_btn(self):
+ self.driver.find_element(By.ID, "Wait_Submit").click()
+
+ def execute_script(self, script):
+ self.driver.execute_script(script)
+
+ def click_test_button_wait(self):
+ WebDriverWait(self.driver, 10).until(ec.visibility_of_element_located((By.ID, "wait_new_element")))
+
+ def close(self):
+ self.driver.quit()
diff --git a/src/main/search/context.py b/src/main/search/context.py
new file mode 100644
index 0000000..1b0ae15
--- /dev/null
+++ b/src/main/search/context.py
@@ -0,0 +1,37 @@
+from src.main.constants.locator_type import LocatorType
+from src.main.search.locators.class_name_strategy import ClassNameStrategy
+from src.main.search.locators.css_strategy import CssStrategy
+from src.main.search.locators.id_strategy import IdStrategy
+from src.main.search.locators.link_text_strategy import LinkTextStrategy
+from src.main.search.locators.name_strategy import NameStrategy
+from src.main.search.locators.partial_link_text_strategy import PartialLinkTextStrategy
+from src.main.search.locators.tag_strategy import TagStrategy
+from src.main.search.locators.xpath_strategy import XpathStrategy
+from src.main.search.strategy import Strategy
+
+
+class Context:
+ strategy: Strategy
+
+ def set_strategy(self, driver, locator_type):
+ if locator_type == LocatorType.class_name:
+ self.strategy = ClassNameStrategy(driver)
+ if locator_type == LocatorType.xpath:
+ self.strategy = XpathStrategy(driver)
+ if locator_type == LocatorType.css:
+ self.strategy = CssStrategy(driver)
+ if locator_type == LocatorType.name:
+ self.strategy = NameStrategy(driver)
+ if locator_type == LocatorType.tag:
+ self.strategy = TagStrategy(driver)
+ if locator_type == LocatorType.partial_link_text:
+ self.strategy = PartialLinkTextStrategy(driver)
+ if locator_type == LocatorType.link_text:
+ self.strategy = LinkTextStrategy(driver)
+ if locator_type == LocatorType.id:
+ self.strategy = IdStrategy(driver)
+
+ return self
+
+ def execute_strategy(self, selector):
+ return self.strategy.do_action(selector)
diff --git a/src/main/search/locators/class_name_strategy.py b/src/main/search/locators/class_name_strategy.py
new file mode 100644
index 0000000..a9bf4a5
--- /dev/null
+++ b/src/main/search/locators/class_name_strategy.py
@@ -0,0 +1,7 @@
+from src.main.search.strategy import Strategy
+
+
+class ClassNameStrategy(Strategy):
+
+ def do_action(self) -> str:
+ pass
diff --git a/src/main/search/locators/css_strategy.py b/src/main/search/locators/css_strategy.py
new file mode 100644
index 0000000..a38670c
--- /dev/null
+++ b/src/main/search/locators/css_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class CssStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.CSS_SELECTOR, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/id_strategy.py b/src/main/search/locators/id_strategy.py
new file mode 100644
index 0000000..b9c0c20
--- /dev/null
+++ b/src/main/search/locators/id_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class IdStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.ID, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/link_text_strategy.py b/src/main/search/locators/link_text_strategy.py
new file mode 100644
index 0000000..7fa9cf0
--- /dev/null
+++ b/src/main/search/locators/link_text_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class LinkTextStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.LINK_TEXT, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/name_strategy.py b/src/main/search/locators/name_strategy.py
new file mode 100644
index 0000000..a2452c6
--- /dev/null
+++ b/src/main/search/locators/name_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class NameStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.NAME, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/partial_link_text_strategy.py b/src/main/search/locators/partial_link_text_strategy.py
new file mode 100644
index 0000000..5da8397
--- /dev/null
+++ b/src/main/search/locators/partial_link_text_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class PartialLinkTextStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.PARTIAL_LINK_TEXT, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/tag_strategy.py b/src/main/search/locators/tag_strategy.py
new file mode 100644
index 0000000..afcae87
--- /dev/null
+++ b/src/main/search/locators/tag_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class TagStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.TAG_NAME, selector)
+ return element.is_displayed()
diff --git a/src/main/search/locators/xpath_strategy.py b/src/main/search/locators/xpath_strategy.py
new file mode 100644
index 0000000..e500d9f
--- /dev/null
+++ b/src/main/search/locators/xpath_strategy.py
@@ -0,0 +1,15 @@
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+
+from src.main.search.strategy import Strategy
+
+
+class XpathStrategy(Strategy):
+ driver: webdriver
+
+ def __init__(self, driver1):
+ self.driver = driver1
+
+ def do_action(self, selector) -> str:
+ element = self.driver.find_element(By.XPATH, selector)
+ return element.is_displayed()
diff --git a/src/main/search/strategy.py b/src/main/search/strategy.py
new file mode 100644
index 0000000..3458fa3
--- /dev/null
+++ b/src/main/search/strategy.py
@@ -0,0 +1,7 @@
+from abc import ABC
+
+
+class Strategy(ABC):
+
+ def do_action(self, selector) -> str:
+ pass
diff --git a/tests/test_base.py b/tests/test_base.py
deleted file mode 100644
index 3fefabe..0000000
--- a/tests/test_base.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import pytest
-from selenium import webdriver
-
-
-class Test_Base():
-
- @pytest.fixture()
- def setup_method(self):
- options = webdriver.ChromeOptions()
- options.add_argument('--no-sandbox')
- self.driver = webdriver.Remote(
- command_executor="http://localhost:8085/",
- desired_capabilities=webdriver.DesiredCapabilities.CHROME,
- options=options)
-
- # options = webdriver.FirefoxOptions()
- # browser = webdriver.Remote(
- # command_executor="http://localhost:4444/wd/hub/",
- # desired_capabilities=webdriver.DesiredCapabilities.FIREFOX,
- # options=options)
-
- def teardown_method(self, method):
- self.driver.quit()
diff --git a/tests/test_callback.py b/tests/test_callback.py
deleted file mode 100644
index bc36fcf..0000000
--- a/tests/test_callback.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from src.main.pages.callback_page import Callback_Page
-from test_base import Test_Base
-
-
-class Test_Callback(Test_Base):
-
- def test_element_from_shadow_root(self, setup_method):
- callback_page=Callback_Page(self.driver)
-
- callback_page.open_browser()
- callback_page.click_add_square_button()
- result = callback_page.verify_shadow_element()
- assert result == True
-
- callback_page.click_update_square_button()
- result = callback_page.verify_shadow_element() #should be healed
- assert result == True
-
- def test_css_locators(self, setup_method):
- callback_page=Callback_Page(self.driver)
-
- callback_page.open_browser()
- callback_page.click_add_square_button()
- result = callback_page.verify_square_element()
- assert result==True
-
- callback_page.click_update_square_button()
- result = callback_page.verify_square_element() #should be healed
- assert result == True
\ No newline at end of file
diff --git a/tests/test_css.py b/tests/test_css.py
new file mode 100644
index 0000000..3772205
--- /dev/null
+++ b/tests/test_css.py
@@ -0,0 +1,90 @@
+from src.main.constants.locator_type import LocatorType
+from src.main.pages.callback_page import CallbackPage
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestCss:
+
+ def test_css_attribute(self):
+ callback_page = CallbackPage()
+
+ callback_page.open_browser()
+ callback_page.click_add_square_button()
+ result = callback_page.verify_square_element()
+ assert result == True
+
+ callback_page.click_update_square_button()
+ result = callback_page.verify_square_element() # should be healed
+ assert result == True
+
+ callback_page.close()
+
+ def test_css_id(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "#change_id")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "#change_id")
+
+ test_page.close()
+
+ def test_css_id_special_character(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "input#change\\:name")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "input#change\\:name")
+
+ test_page.close()
+
+ def test_css_element(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "test_tag")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "test_tag")
+
+ test_page.close()
+
+ def test_css_disabled(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "input:disabled")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "input:disabled")
+
+ test_page.close()
+
+ def test_css_enabled(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "textarea:enabled")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "textarea:enabled")
+
+ test_page.close()
+
+ def test_css_checked(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, "input:checked")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, "input:checked")
+
+ test_page.close()
+
+ def test_css_class_name(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.css, ".test_class")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.css, ".test_class")
+
+ test_page.close()
diff --git a/tests/test_general.py b/tests/test_general.py
new file mode 100644
index 0000000..37be91f
--- /dev/null
+++ b/tests/test_general.py
@@ -0,0 +1,14 @@
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestGeneral:
+
+ def test_select_checkboxes(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.select_checkboxes()
+ test_page.click_form_submit_btn()
+ test_page.select_checkboxes() # should be healed
+
+ test_page.close()
diff --git a/tests/test_markup.py b/tests/test_markup.py
deleted file mode 100644
index 21e4025..0000000
--- a/tests/test_markup.py
+++ /dev/null
@@ -1,72 +0,0 @@
-from src.main.pages.markup_page import *
-from test_base import Test_Base
-
-
-class Test_Markup(Test_Base):
-
-# Markup_Page: different locator types with string property
- def test_button_click_specific_find_element(self, setup_method):
- main_page = Markup_Page(self.driver)
-
- main_page.open_browser()
- main_page.click_test_button()
- main_page.confirm_alert()
-
- main_page.generate_markup()
- main_page.click_test_button() #should be healed
- main_page.confirm_alert()
-
- def test_select_checkboxes(self, setup_method):
- main_page = Markup_Page(self.driver)
- main_page.open_browser().generate_markup()
-
- while main_page.displayed_text() != True:
- main_page.generate_markup()
-
- for i in [0,1,2,3,4,5]:
- main_page.select_first_checkbox() #should be healed
-
- result = main_page.verify_first_checkbox() #should be healed
- assert result == True
-
- def test_ButtonClickWithId(self, setup_method):
- main_page = Markup_Page(self.driver)
-
- main_page.open_browser().click_test_button()
- main_page.confirm_alert()
-
- while main_page.test_button_id_enabled()!=True:
- main_page.generate_markup()
-
- for i in [0,1,2]:
- main_page.click_test_generated_button() #should be healed
- main_page.confirm_alert()
- main_page.generate_markup()
-
-# Markup_Page_By: different locator types with By in property
- def test_button_click_find_by(self, setup_method):
- markup_page_by=Markup_Page_By(self.driver)
-
- markup_page_by.open_browser()
- markup_page_by.click_button_for_invisible()
- invisible = markup_page_by.check_that_button_invisible()
-
- markup_page_by.open_browser()
- markup_page_by.click_test_button()
- markup_page_by.confirm_alert()
-
- markup_page_by.generate_markup()
- markup_page_by.click_test_button() #should be healed
- markup_page_by.confirm_alert()
-
-# Markup_Page_Request: different locator types with By in request
- def test_button_click_find_request(self, setup_method):
- markup_page_request=Markup_Page_Request(self.driver)
-
- markup_page_request.open_browser()
- markup_page_request.click_test_button()
- markup_page_request.confirm_alert()
-
- markup_page_request.generate_markup()
- markup_page_request.click_test_button() #should be healed
- markup_page_request.confirm_alert()
\ No newline at end of file
diff --git a/tests/test_parent_child.py b/tests/test_parent_child.py
new file mode 100644
index 0000000..e233101
--- /dev/null
+++ b/tests/test_parent_child.py
@@ -0,0 +1,45 @@
+from src.main.constants.locator_type import LocatorType
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestParentChild:
+
+ def test_select_checkboxes_under_parent(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.select_checkboxes_under_parent()
+ test_page.click_form_submit_btn()
+ test_page.select_checkboxes_under_parent()
+
+ test_page.close()
+
+ def test_parent_xpath(self):
+ testenv_page = TestEnvPage()
+
+ testenv_page.open_browser()
+ testenv_page.find_test_element(LocatorType.xpath, "(//*[@class='input1']//parent::*[contains(@class, 'input1')])[8]")
+ testenv_page.click_submit_btn()
+ testenv_page.find_test_element(LocatorType.xpath, "(//*[@class='input1']//parent::*[contains(@class, 'input1')])[8]")
+
+ testenv_page.close()
+
+ def test_css_first_child(self):
+ testenv_page = TestEnvPage()
+
+ testenv_page.open_browser()
+ testenv_page.find_test_element(LocatorType.css, "test_tag:first-child")
+ testenv_page.click_submit_btn()
+ testenv_page.find_test_element(LocatorType.css, "test_tag:first-child")
+
+ testenv_page.close()
+
+ def test_css_last_child(self):
+ testenv_page = TestEnvPage()
+
+ testenv_page.open_browser()
+ testenv_page.find_test_element(LocatorType.css, "child_tag:last-child")
+ testenv_page.click_submit_btn()
+ testenv_page.find_test_element(LocatorType.css, "child_tag:last-child")
+
+ testenv_page.close()
diff --git a/tests/test_semantic.py b/tests/test_semantic.py
new file mode 100644
index 0000000..cbad9d7
--- /dev/null
+++ b/tests/test_semantic.py
@@ -0,0 +1,69 @@
+from src.main.constants.locator_type import LocatorType
+from src.main.pages.markup_page import MarkupPage
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestSemantic:
+
+ # def test_semantic_class_name(self):
+ # main_page = MarkupPage()
+ #
+ # main_page.open_browser()
+ # main_page.click_test_button()
+ # main_page.confirm_alert()
+ #
+ # main_page.generate_markup()
+ # main_page.click_test_button() # should be healed
+ # main_page.confirm_alert()
+ #
+ # main_page.close()
+
+ def test_semantic_id(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.id, "change_id")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.id, "change_id") # should be healed
+
+ test_page.close()
+
+ def test_semantic_link_text(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.link_text, "Change: LinkText, PartialLinkText")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.link_text, "Change: LinkText, PartialLinkText") # should be healed
+
+ test_page.close()
+
+ def test_semantic_partial_link_text(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.partial_link_text, "PartialLinkText")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.partial_link_text, "PartialLinkText") # should be healed
+
+ test_page.close()
+
+ def test_semantic_name(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.name, "change_name")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.name, "change_name") # should be healed
+
+ test_page.close()
+
+ def test_semantic_element(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.tag, "test_tag")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.tag, "test_tag") # should be healed
+
+ test_page.close()
diff --git a/tests/test_wait.py b/tests/test_wait.py
new file mode 100644
index 0000000..9c7e021
--- /dev/null
+++ b/tests/test_wait.py
@@ -0,0 +1,15 @@
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestWait:
+
+ def test_conditional_wait(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.click_wait_btn()
+ test_page.execute_script("disable_healing_true")
+ test_page.click_test_button_wait()
+ test_page.execute_script("disable_healing_false")
+
+ test_page.close()
diff --git a/tests/test_xpath.py b/tests/test_xpath.py
new file mode 100644
index 0000000..95e6dee
--- /dev/null
+++ b/tests/test_xpath.py
@@ -0,0 +1,115 @@
+from src.main.constants.locator_type import LocatorType
+from src.main.pages.testenv_page import TestEnvPage
+
+
+class TestXpath:
+
+ def test_xpath_special_character(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change:name']")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change:name']")
+
+ test_page.close()
+
+ def test_xpath_following(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_className']/following::test_tag")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_className']/following::test_tag")
+
+ test_page.close()
+
+ def test_xpath_contains(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//input[contains(@class, 'test')]")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//input[contains(@class, 'test')]")
+
+ test_page.close()
+
+ def test_xpath_not_contains(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//input[not(contains(@class, 'input1'))]")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//input[not(contains(@class, 'input1'))]")
+
+ test_page.close()
+
+ def test_xpath_following_sibling(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[starts-with(@class, 'test')]/following-sibling::*")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[starts-with(@class, 'test')]/following-sibling::*")
+
+ test_page.close()
+
+ def test_xpath_ancestor(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "(//*[starts-with(@class, 'test')]/ancestor::div[@class='healenium-form validate-form']//input)[1]")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "(//*[starts-with(@class, 'test')]/ancestor::div[@class='healenium-form validate-form']//input)[1]")
+
+ test_page.close()
+
+ def test_xpath_or(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_id' or @id='omg']")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_id' or @id='omg']")
+
+ test_page.close()
+
+ def test_xpath_and(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_id' and @type='text']")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_id' and @type='text']")
+
+ test_page.close()
+
+ def test_xpath_starts_with(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[starts-with(@class, 'test')]")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[starts-with(@class, 'test')]")
+
+ test_page.close()
+
+ def test_xpath_precending(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_className']/preceding::*[@id='change_id']")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='change_className']/preceding::*[@id='change_id']")
+
+ test_page.close()
+
+ def test_xpath_descendant(self):
+ test_page = TestEnvPage()
+
+ test_page.open_browser()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='descendant_change']/descendant::input")
+ test_page.click_submit_btn()
+ test_page.find_test_element(LocatorType.xpath, "//*[@id='descendant_change']/descendant::input")
+
+ test_page.close()