# Py-TestUI Framework [![PyPI - Version](https://img.shields.io/pypi/v/python-testui)](https://pypi.org/project/python-testui/)
# Installation
Installation is a simple by the helps of `pip`, Py-TestUI can be installed using
this `pip` command.
```bash
pip3 install python-testui==1.2.3
```
Or if you prefer `requirements.txt`, you can add the following dependency to
the file.
```txt
python-testui==1.2.3
```
# Appium driver
You can create `TestUIDriver` for Appium automation like so:
```py
from testui.support.appium_driver import NewDriver
from testui.support.testui_driver import TestUIDriver
driver: TestUIDriver = (
NewDriver()
.set_app_path("app.apk")
.set_app_package_activity("com.package.package", "com.activity.Activity")
.set_logger("pytest")
.set_appium_driver()
)
```
By default it takes the first connected device to your machine, but you can
specify device by calling `.set_udid("udid")` before the `.set_appium_driver()`
call.
# Selenium Desktop driver
Py-TestUI supports the following browser drivers:
- Google Chrome (`'chrome'`)
- Mozilla Firefox (`'firefox'`)
- Safari (`'safari'`)
- Microsoft Edge (`'edge'`)
- Microsoft Internet Explorer (`'ie'`)
- Opera (`'opera'`)
> All these drivers must be installed and added to you `$PATH` variable
> Mozilla Firefox is downloaded automatically and added to `$PATH` but
> double-check that you have installed
> [Python certificates if you are using macOS](https://stackoverflow.com/a/53310545/13179904)
You can create `TestUIDriver` for Selenium Desktop automation like so
```python
from testui.support.appium_driver import NewDriver
from testui.support.testui_driver import TestUIDriver
driver: TestUIDriver = (
NewDriver()
.set_logger("pytest")
.set_soft_assert(True)
.set_selenium_driver()
)
```
# Configuration
Py-TestUI allows configuring the following default global parameters
- `screenshot_path: str` - sets default path where screenshot will be saved
(default: project root directory).
- `save_screenshot_on_fail: bool` - sets whether save screenshot on failure
(default: `True`)
- `save_full_stacktrace: bool` - sets whether save full stacktrace for error
(default: `True`)
## Configuration via `NewDriver()`
Configuration parameters can be set while creating a new `TestUIDriver` object
like this
```py
from testui.support.appium_driver import NewDriver
from testui.support.testui_driver import TestUIDriver
driver: TestUIDriver = (
NewDriver()
.set_screenshot_path("path/to/default/screenshot/location")
.set_save_screenshot_on_fail(False)
.set_save_full_stacktrace(False)
.set_selenium_driver()
)
```
## Configuration via `driver.configuration`
Another possible way to change default global parameters is to use
`configuration` attribute located under `driver: TestUIDriver` object. These
parameters can be changed like this at any point of the execution:
```py
driver.configuration.screenshot_path = "path/to/default/screenshot/location"
driver.configuration.save_screenshot_on_fail = False
driver.configuration.save_full_stacktrace = False
```
# Scripts
The automation infrastructure implements the Page Object Model (POM) pattern.
This pattern calls for application elements and methods specific to a given
screen to be included in the same Class. This improves maintainability as well
as usability since any changes to elements in the application would only have to
be edited in one place. This is an example:
```py
from testui.elements.testui_collection import ee
from testui.elements.testui_element import e
class LoggedInScreen:
# Page Element Definitions
def __init__(self, driver: TestUIDriver):
self.driver = driver
# Settings drawer
self.__settings_button = e(driver, "accessibility", "Settings")
self.__edit_profile = e(driver, "id", "textview_settings")
self.__log_out_button = e(driver, "id", "textview_settings_drawer")
.
.
.
def click_and_check_settings(self):
self.__settings_button.wait_until_visible().click()
self.__log_out_button.wait_until_visible()
self.__edit_profile.wait_until_visible()
.
.
.
```
The test case scripts are in a different class. The scripts import the
respective screen package along with additional packages such as PyTest. Each
class has any amount of tests. All test methods start with the word "test\_"
such as:
```py
def test_add_existing_contact(self, appium_driver):
from tests.tests_signup import TestSignUp
username = TestSignUp().test_sign_up_flow(appium_driver)
logged_in_page = LoggedInScreen(appium_driver)
logged_in_page.log_out()
.
.
.
```
### Element Locators Methods:
The class "Elements" implements default locator methods that are part of the
Appium WedDriver package. It adds additional functionality to wait for a
configurable amount of time for an element to appear and provides improved error
logging. It also implements methods for scrolling and swiping. Example of
locators:
```py
def e(driver, locator_type, locator):
"""locator types: id, css, className, name, xpath,
accessibility, uiautomator, classChain, predicate"""
return Elements(driver, locator_type, locator)
```
Once you create the definition of the element with the static method e(driver,
l_type, locator) you can start using a series of action/assertion methods that
are built-in within Elements class.
Action methods are the ones meant to do something over the UI. In case they
cannot be performed, an error will be raised that will show all the information
needed for debugging:
```py
element = e(driver, 'id', 'some_id')
element.click()
element.send_keys('send some text to input')
element.swipe(end_y=end_y, end_x=end_x)
element.press_hold_for(milliseconds)
element.click_by_coordinates(x, y)
# swipes from element to the element 2
element.swipe_until_text(text='some_text', el=e(driver, 'id', 'id_2'))
```
Assertion methods are the ones meant to check whether the UI is presented with
the correct elements and values:
```py
element = e(driver, 'id', 'some_id')
element.wait_until_visible(seconds=10)
element.wait_until_attribute(attr='text', text='something', seconds=10)
element.wait_until_contains_attribute(attr='text', text='something')
element.wait_until_contains_sensitive_attribute(
attr='text', text='something'
)
# Checks if visible along the set amount of time
element.visible_for(seconds=1)
# Takes screenshot of the element and compares with the provided image
element.find_image_match('relative/path/image.png', threshold)
```
This methods will rise a ElementException in case the conditions are not met.
There is one last method to check whether an element is visible or not which
will not rise an error but return a boolean value which is element.is_visible().
Sometimes one locator identifies more that one single element, in which case you
can choose among them by using element.get(index=0).
### Collection Methods:
Collections are defined as a list of elements, and you can perform different
kind of actions over them, which will improve in performance or functionality.
Such methods include finding element within the collection by visibility, or
checking their visibility in parallel to improve performance or to check page
load time:
```py
from testui.elements.testui_collection import ee
from testui.elements.testui_element import e
collection = ee(e(driver, 'id', 'some_id'), e(driver, 'id', 'some_id_2'))
collection.find_visible(seconds) # Returns the first found visible element
collection.wait_until_all_visible(seconds)
collection.wait_until_attribute([attr_1, attr_2],[value_1, value_2])
collection.get(0) # Returns first element
```
### Image Recognition:
In some cases it is useful to check or assert whether a specific element or view
is present during the automation, and for this reason, Py-TestUI includes OpenCV
as one of the dependencies. You can use directly the methods from OpenCV
directly, but you have also built-in methods from testUI:
```py
# First field is the image path with which you want to compare the device
# screenshot.
# Second field is the Threshold or how much alike the images should be
# (0.75-1 is considere high)
# Third is if you want to rise an exception when the image is not found
# The forth is if you want to keep the image of the matched region, which
# will be marked with a rectangle
testui_driver.find_image_match(
'relative/path/image.png', 0.95, True, 'path/matched/image.png'
)
testui_driver.click_by_image('relative/path/image.png', threshold)
```
![Image Recognition](resources/image_reco.png)
If you want to use the compare method by using your own two images, you can use
the following methods:
```
ImageRecognition(original, comparison, threshold, device_name).compare()
ImageRecognition(original, comparison, threshold, device_name).draw_image_match()
ImageRecognition(original, comparison, threshold, device_name).image_original_size()
ImageRecognition(original, comparison, threshold, device_name).image_comparison_size()
ImageRecognition(original, comparison, threshold, device_name).get_middle_point()
```
Note: the image that you use for comparison can be a small portion of the
screenshot/bigger one
### Drivers:
The testui_driver.py declares the TestUIDriver class which implements methods
from the Elements class.
The appium_driver.py declares the NewDriver class which implements TestUIDriver.
It also implements the desired capabilities such as the location of the .apk.,
the Chrome or iOS drivers, and others such as the Android version.
NewDriver class is meant to generate appium or selenium drivers in a simple way,
using default values when not specified, and starting appium server or
retrieving chrome drivers automatically if necessary. An example of minimum code
to start an appium session for Android app:
```py
driver = NewDriver().set_app_path('app.apk') \
.set_app_package_activity('package', 'activity') \
.set_appium_driver()
```
Android chrome browser:
```py
driver = NewDriver().set_chrome_driver('78.0.3904.70') \
.set_appium_driver()
```
IOS app:
```py
driver = NewDriver().set_platform('iOS').set_udid('udid') \
.set_appium_driver()
```
Chrome desktop browser:
```py
driver = NewDriver() \
.set_selenium_driver()
```
Before setting the driver you can choose among a different stacks of class
methods to add different capabilities or functionality out-of-the-box from the
framework such as "soft asserts" or "log types". Soft asserts make the
automation run even when element assertions find errors, but in the end of said
automation you can include the following line to rise the errors found:
```py
driver = NewDriver() \
.soft_assert(true).set_selenium_driver()
e(driver, 'id', 'fake_id').wait_until_visible(5) # time in seconds
driver.raise_errors() # raise the error found with previous command
```
The driver creation and stop should be located under pytest fixtures, which
works as before/after hooks. This fixtures are located under conftest.py file
and you can call those functions by passing them as variables in the test cases.
Raw data
{
"_id": null,
"home_page": "https://github.com/testdevlab/Py-TestUI",
"name": "python-testui",
"maintainer": null,
"docs_url": null,
"requires_python": "<4,>=3.6",
"maintainer_email": null,
"keywords": "Py-TestUI, mobile automation, browser automation",
"author": "Alvaro Santos Laserna Lopez",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/95/c4/add466316a347ded156af454103350e464e5b9980749230f9cbe0c48f356/python_testui-1.2.3.tar.gz",
"platform": null,
"description": "# Py-TestUI Framework [![PyPI - Version](https://img.shields.io/pypi/v/python-testui)](https://pypi.org/project/python-testui/)\n\n# Installation\n\nInstallation is a simple by the helps of `pip`, Py-TestUI can be installed using \nthis `pip` command.\n\n```bash\npip3 install python-testui==1.2.3\n```\n\nOr if you prefer `requirements.txt`, you can add the following dependency to \nthe file.\n\n```txt\npython-testui==1.2.3\n```\n\n# Appium driver\n\nYou can create `TestUIDriver` for Appium automation like so:\n\n```py\nfrom testui.support.appium_driver import NewDriver\nfrom testui.support.testui_driver import TestUIDriver\n\ndriver: TestUIDriver = (\n NewDriver()\n .set_app_path(\"app.apk\")\n .set_app_package_activity(\"com.package.package\", \"com.activity.Activity\")\n .set_logger(\"pytest\")\n .set_appium_driver()\n)\n```\n\nBy default it takes the first connected device to your machine, but you can\nspecify device by calling `.set_udid(\"udid\")` before the `.set_appium_driver()`\ncall.\n\n# Selenium Desktop driver\n\nPy-TestUI supports the following browser drivers:\n\n- Google Chrome (`'chrome'`)\n- Mozilla Firefox (`'firefox'`)\n- Safari (`'safari'`)\n- Microsoft Edge (`'edge'`)\n- Microsoft Internet Explorer (`'ie'`)\n- Opera (`'opera'`)\n\n> All these drivers must be installed and added to you `$PATH` variable\n\n> Mozilla Firefox is downloaded automatically and added to `$PATH` but\n> double-check that you have installed\n> [Python certificates if you are using macOS](https://stackoverflow.com/a/53310545/13179904)\n\nYou can create `TestUIDriver` for Selenium Desktop automation like so\n\n```python\nfrom testui.support.appium_driver import NewDriver\nfrom testui.support.testui_driver import TestUIDriver\n\ndriver: TestUIDriver = (\n NewDriver()\n .set_logger(\"pytest\")\n .set_soft_assert(True)\n .set_selenium_driver()\n)\n```\n\n# Configuration\n\nPy-TestUI allows configuring the following default global parameters\n\n- `screenshot_path: str` - sets default path where screenshot will be saved\n (default: project root directory).\n- `save_screenshot_on_fail: bool` - sets whether save screenshot on failure\n (default: `True`)\n- `save_full_stacktrace: bool` - sets whether save full stacktrace for error\n (default: `True`)\n\n## Configuration via `NewDriver()`\n\nConfiguration parameters can be set while creating a new `TestUIDriver` object\nlike this\n\n```py\nfrom testui.support.appium_driver import NewDriver\nfrom testui.support.testui_driver import TestUIDriver\n\ndriver: TestUIDriver = (\n NewDriver()\n .set_screenshot_path(\"path/to/default/screenshot/location\")\n .set_save_screenshot_on_fail(False)\n .set_save_full_stacktrace(False)\n .set_selenium_driver()\n)\n```\n\n## Configuration via `driver.configuration`\n\nAnother possible way to change default global parameters is to use\n`configuration` attribute located under `driver: TestUIDriver` object. These\nparameters can be changed like this at any point of the execution:\n\n```py\ndriver.configuration.screenshot_path = \"path/to/default/screenshot/location\"\ndriver.configuration.save_screenshot_on_fail = False\ndriver.configuration.save_full_stacktrace = False\n```\n\n# Scripts\n\nThe automation infrastructure implements the Page Object Model (POM) pattern.\nThis pattern calls for application elements and methods specific to a given\nscreen to be included in the same Class. This improves maintainability as well\nas usability since any changes to elements in the application would only have to\nbe edited in one place. This is an example:\n\n```py\n from testui.elements.testui_collection import ee\n from testui.elements.testui_element import e\n class LoggedInScreen:\n # Page Element Definitions\n def __init__(self, driver: TestUIDriver):\n self.driver = driver\n # Settings drawer\n self.__settings_button = e(driver, \"accessibility\", \"Settings\")\n self.__edit_profile = e(driver, \"id\", \"textview_settings\")\n self.__log_out_button = e(driver, \"id\", \"textview_settings_drawer\")\n .\n .\n .\n\n def click_and_check_settings(self):\n self.__settings_button.wait_until_visible().click()\n self.__log_out_button.wait_until_visible()\n self.__edit_profile.wait_until_visible()\n .\n .\n .\n```\n\nThe test case scripts are in a different class. The scripts import the\nrespective screen package along with additional packages such as PyTest. Each\nclass has any amount of tests. All test methods start with the word \"test\\_\"\nsuch as:\n\n```py\n def test_add_existing_contact(self, appium_driver):\n from tests.tests_signup import TestSignUp\n username = TestSignUp().test_sign_up_flow(appium_driver)\n logged_in_page = LoggedInScreen(appium_driver)\n logged_in_page.log_out()\n .\n .\n .\n```\n\n### Element Locators Methods:\n\nThe class \"Elements\" implements default locator methods that are part of the\nAppium WedDriver package. It adds additional functionality to wait for a\nconfigurable amount of time for an element to appear and provides improved error\nlogging. It also implements methods for scrolling and swiping. Example of\nlocators:\n\n```py\n def e(driver, locator_type, locator):\n \"\"\"locator types: id, css, className, name, xpath,\n accessibility, uiautomator, classChain, predicate\"\"\"\n\n return Elements(driver, locator_type, locator)\n```\n\nOnce you create the definition of the element with the static method e(driver,\nl_type, locator) you can start using a series of action/assertion methods that\nare built-in within Elements class.\n\nAction methods are the ones meant to do something over the UI. In case they\ncannot be performed, an error will be raised that will show all the information\nneeded for debugging:\n\n```py\n element = e(driver, 'id', 'some_id')\n element.click()\n element.send_keys('send some text to input')\n element.swipe(end_y=end_y, end_x=end_x)\n element.press_hold_for(milliseconds)\n element.click_by_coordinates(x, y)\n # swipes from element to the element 2\n element.swipe_until_text(text='some_text', el=e(driver, 'id', 'id_2'))\n```\n\nAssertion methods are the ones meant to check whether the UI is presented with\nthe correct elements and values:\n\n```py\n element = e(driver, 'id', 'some_id')\n element.wait_until_visible(seconds=10)\n element.wait_until_attribute(attr='text', text='something', seconds=10)\n element.wait_until_contains_attribute(attr='text', text='something')\n element.wait_until_contains_sensitive_attribute(\n attr='text', text='something'\n )\n # Checks if visible along the set amount of time\n element.visible_for(seconds=1)\n # Takes screenshot of the element and compares with the provided image\n element.find_image_match('relative/path/image.png', threshold)\n```\n\nThis methods will rise a ElementException in case the conditions are not met.\n\nThere is one last method to check whether an element is visible or not which\nwill not rise an error but return a boolean value which is element.is_visible().\n\nSometimes one locator identifies more that one single element, in which case you\ncan choose among them by using element.get(index=0).\n\n### Collection Methods:\n\nCollections are defined as a list of elements, and you can perform different\nkind of actions over them, which will improve in performance or functionality.\nSuch methods include finding element within the collection by visibility, or\nchecking their visibility in parallel to improve performance or to check page\nload time:\n\n```py\nfrom testui.elements.testui_collection import ee\nfrom testui.elements.testui_element import e\n\ncollection = ee(e(driver, 'id', 'some_id'), e(driver, 'id', 'some_id_2'))\ncollection.find_visible(seconds) # Returns the first found visible element\ncollection.wait_until_all_visible(seconds)\ncollection.wait_until_attribute([attr_1, attr_2],[value_1, value_2])\ncollection.get(0) # Returns first element\n```\n\n### Image Recognition:\n\nIn some cases it is useful to check or assert whether a specific element or view\nis present during the automation, and for this reason, Py-TestUI includes OpenCV\nas one of the dependencies. You can use directly the methods from OpenCV\ndirectly, but you have also built-in methods from testUI:\n\n```py\n # First field is the image path with which you want to compare the device \n # screenshot.\n # Second field is the Threshold or how much alike the images should be \n # (0.75-1 is considere high)\n # Third is if you want to rise an exception when the image is not found\n # The forth is if you want to keep the image of the matched region, which \n # will be marked with a rectangle\n testui_driver.find_image_match(\n 'relative/path/image.png', 0.95, True, 'path/matched/image.png'\n )\n\n testui_driver.click_by_image('relative/path/image.png', threshold)\n```\n\n![Image Recognition](resources/image_reco.png)\n\nIf you want to use the compare method by using your own two images, you can use\nthe following methods:\n\n```\n ImageRecognition(original, comparison, threshold, device_name).compare()\n ImageRecognition(original, comparison, threshold, device_name).draw_image_match()\n ImageRecognition(original, comparison, threshold, device_name).image_original_size()\n ImageRecognition(original, comparison, threshold, device_name).image_comparison_size()\n ImageRecognition(original, comparison, threshold, device_name).get_middle_point()\n```\n\nNote: the image that you use for comparison can be a small portion of the\nscreenshot/bigger one\n\n### Drivers:\n\nThe testui_driver.py declares the TestUIDriver class which implements methods\nfrom the Elements class.\n\nThe appium_driver.py declares the NewDriver class which implements TestUIDriver.\nIt also implements the desired capabilities such as the location of the .apk.,\nthe Chrome or iOS drivers, and others such as the Android version.\n\nNewDriver class is meant to generate appium or selenium drivers in a simple way,\nusing default values when not specified, and starting appium server or\nretrieving chrome drivers automatically if necessary. An example of minimum code\nto start an appium session for Android app:\n\n```py\n driver = NewDriver().set_app_path('app.apk') \\\n .set_app_package_activity('package', 'activity') \\\n .set_appium_driver()\n```\n\nAndroid chrome browser:\n\n```py\n driver = NewDriver().set_chrome_driver('78.0.3904.70') \\\n .set_appium_driver()\n```\n\nIOS app:\n\n```py\n driver = NewDriver().set_platform('iOS').set_udid('udid') \\\n .set_appium_driver()\n```\n\nChrome desktop browser:\n\n```py\n driver = NewDriver() \\\n .set_selenium_driver()\n```\n\nBefore setting the driver you can choose among a different stacks of class\nmethods to add different capabilities or functionality out-of-the-box from the\nframework such as \"soft asserts\" or \"log types\". Soft asserts make the\nautomation run even when element assertions find errors, but in the end of said\nautomation you can include the following line to rise the errors found:\n\n```py\n driver = NewDriver() \\\n .soft_assert(true).set_selenium_driver()\n e(driver, 'id', 'fake_id').wait_until_visible(5) # time in seconds\n driver.raise_errors() # raise the error found with previous command\n```\n\nThe driver creation and stop should be located under pytest fixtures, which\nworks as before/after hooks. This fixtures are located under conftest.py file\nand you can call those functions by passing them as variables in the test cases.\n",
"bugtrack_url": null,
"license": "Apache LICENSE 2.0",
"summary": "Browser and Mobile automation framework",
"version": "1.2.3",
"project_urls": {
"Homepage": "https://github.com/testdevlab/Py-TestUI",
"Source": "https://github.com/testdevlab/Py-TestUI",
"TestDevLab": "https://www.testdevlab.com/",
"Tracker": "https://github.com/testdevlab/Py-TestUI/issues"
},
"split_keywords": [
"py-testui",
" mobile automation",
" browser automation"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "897d482546199e4bf252e7027e9681c2fb7282b4ab90b330dac06124a25ef863",
"md5": "779c34085e461b68266b8238109a68e4",
"sha256": "882db5d3a2533171691ce4600efc4d82700e89605ed4ddb1dcde4d563d7fce45"
},
"downloads": -1,
"filename": "python_testui-1.2.3-py3-none-any.whl",
"has_sig": false,
"md5_digest": "779c34085e461b68266b8238109a68e4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4,>=3.6",
"size": 41984,
"upload_time": "2024-10-01T11:10:40",
"upload_time_iso_8601": "2024-10-01T11:10:40.518691Z",
"url": "https://files.pythonhosted.org/packages/89/7d/482546199e4bf252e7027e9681c2fb7282b4ab90b330dac06124a25ef863/python_testui-1.2.3-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "95c4add466316a347ded156af454103350e464e5b9980749230f9cbe0c48f356",
"md5": "2b0d4bd9f11090f76e81cd2bdc1258f7",
"sha256": "fb6b9b7add9705ed4212b0736d2d803025154fab392cdb7d0bac1a35ff6c0436"
},
"downloads": -1,
"filename": "python_testui-1.2.3.tar.gz",
"has_sig": false,
"md5_digest": "2b0d4bd9f11090f76e81cd2bdc1258f7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4,>=3.6",
"size": 34162,
"upload_time": "2024-10-01T11:10:41",
"upload_time_iso_8601": "2024-10-01T11:10:41.973039Z",
"url": "https://files.pythonhosted.org/packages/95/c4/add466316a347ded156af454103350e464e5b9980749230f9cbe0c48f356/python_testui-1.2.3.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-01 11:10:41",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "testdevlab",
"github_project": "Py-TestUI",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"tox": true,
"lcname": "python-testui"
}