Skip to content

Commit

Permalink
Add example of integration with Pyautogui (#55)
Browse files Browse the repository at this point in the history
* Fix linter issues and add new libraries to test-requirements.txt

* Add example of integration with Pyautogui
- Add and an example to sum 1 and 2
- Add the raw test scenario for comparison
- Add README.md with details about the inteagration

* remove unused file

* fix comments

* Add assertions

* fix readme

* Small fixes
  • Loading branch information
douglasdcm authored Jan 20, 2025
1 parent 9ff04c1 commit bf5f254
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 8 deletions.
Empty file removed =0.3
Empty file.
22 changes: 22 additions & 0 deletions examples/linux_desktop/pyautogui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Introduction
[Pyautogui](https://pyautogui.readthedocs.io/en/latest/) lets your Python scripts control the mouse and keyboard to automate interactions with other applications. The API is designed to be simple. PyAutoGUI works on Windows, macOS, and Linux, and runs on Python 2 and 3.

## Test explanation
Before executing this example make sure your host matches the [requirements](https://pyautogui.readthedocs.io/en/latest/install.html) to install Pyautogui ans dogtail.

If you are usinf Python 3.11 and faces errors `No module named 'pyatspi'` while runnig the tests, then try [this](https://gitlab.com/dogtail/dogtail/-/issues/37#note_2304763633)

This example opens the Linux `GNOME Calculator`, divides 1 by 2. You will find the transactions to `Open` and `Close` the `GNOME Calculator` and the ones to click the buttons to `Divide` the numbers.
The outpout is:
```
test_integration_with_pyautogui.py::TestLinuxCalculatorWithPyautogui::test_calculator
--------------------------------------------------------------- live log setup ---------------------------------------------------------------
2025-01-17 18:50:38.453 INFO Transaction 'setup.OpenApp'
--------------------------------------------------------------- live log call ----------------------------------------------------------------
2025-01-17 18:50:39.749 INFO Transaction 'calculator.Divide'
2025-01-17 18:50:39.750 INFO a: 1
2025-01-17 18:50:39.750 INFO b: 2
PASSED [100%]
------------------------------------------------------------- live log teardown --------------------------------------------------------------
2025-01-17 18:50:40.501 INFO Transaction 'setup.CloseApp'
```
1 change: 1 addition & 0 deletions examples/linux_desktop/pyautogui/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BASE_PATH = "./examples/linux_desktop/pyautogui/images/"
Binary file added examples/linux_desktop/pyautogui/images/button_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/linux_desktop/pyautogui/images/button_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/linux_desktop/pyautogui/images/displays_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions examples/linux_desktop/pyautogui/screens/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pyautogui
from constants import BASE_PATH
from guara.transaction import AbstractTransaction


class Divide(AbstractTransaction):
"""
Divide two numbers
Args:
Just the numbers 1 and 2 are allowed for now.
It is necessary to add more images in `images` folder if you want to
divide other numbers
a (int): The number to be divided
b (int): The number that divides
Returns:
(Application) The application (self._driver)
"""

def __init__(self, driver):
super().__init__(driver)

def _get_button_path(self, button_name):
return f"{BASE_PATH}{button_name}.png"

def _click_buton(self, button, CONFIDENCE):
button = pyautogui.locateOnScreen(button, confidence=CONFIDENCE)
if not button:
raise ValueError(f"Button image {button} not found.")
pyautogui.click(button)

def do(self, a, b):
BUTTON_1 = self._get_button_path(f"button_{str(a)}")
BUTTON_2 = self._get_button_path(f"button_{str(b)}")
# The tool confuses "+" and "÷", but this example does not worry about it
BUTTON_DIVIDE = self._get_button_path("button_sum")
BUTTON_EQUALS = self._get_button_path("button_equals")
CONFIDENCE = 0.9

self._click_buton(BUTTON_1, CONFIDENCE)
self._click_buton(BUTTON_DIVIDE, CONFIDENCE)
self._click_buton(BUTTON_2, CONFIDENCE)
self._click_buton(BUTTON_EQUALS, CONFIDENCE)
return self._driver
28 changes: 28 additions & 0 deletions examples/linux_desktop/pyautogui/screens/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from dogtail.procedural import click
from dogtail.utils import screenshot
from guara.transaction import AbstractTransaction


class OpenApp(AbstractTransaction):
"""
Opens the App using dogtail for convenience
"""

def __init__(self, driver):
super().__init__(driver)

def do(self):
return self._driver


class CloseApp(AbstractTransaction):
"""
Closes the App using dogtail for convenience
"""

def __init__(self, driver):
super().__init__(driver)

def do(self):
screenshot()
click("Close")
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import pytest
from guara.transaction import Application
from guara import it


class ItShows(it.IAssertion):
"""
It checks if the value is shown in the calculator
Args:
actual (application): The calculator object
expected (number): the value that should be present in the screen
"""

def __init__(self):
super().__init__()

def asserts(self, actual, expected):
assert actual.child(str(expected)).showing


@pytest.mark.skip("Skipped due to the complexity to integrate it in pipeline")
class TestLinuxCalculatorWithPyautogui:

def setup_method(self, method):
# I opted for lazy imports just to not break the pipeline.
# Do not do it.
from screens import setup
from dogtail.tree import root
from dogtail.procedural import run, focus

app_name = "gnome-calculator"
run(app_name)
focus.application(app_name)
driver = root.application(app_name)

self._calculator = Application(driver=driver)
self._calculator.at(setup.OpenApp)

def teardown_method(self, method):
from screens import setup

self._calculator.at(setup.CloseApp)

def test_calculator(self):
from screens import calculator

# Pyautogui seems not to enforce assertions. It also does not have a driver
# which the tester could use to get information about the app. It just interacts
# with whatever is shown in your host. One possible way to make assertions is
# check if an specific image like `images/displays_3.png` is present in the screen
# The tester has to be creative while asserting things with Pyautogui.
# I'm using dogtail to return information about the opened app.
# In this case, dogtail has to be passed as the driver to the `Application`.
# Check the examples in `examples/linux_desktop/dogtail` for more information.
self._calculator.at(
calculator.Divide,
a=1,
b=2,
).asserts(ItShows, 0.5)
2 changes: 1 addition & 1 deletion examples/mobile/appium/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, driver):

def do(self, text):
TEXT = '//*[@id="input"]'
BUTTON_TEST = 'button'
BUTTON_TEST = "button"
text_field = self._driver.find_element_by_xpath(TEXT)
text_field.send_keys(text)
button = self._driver.find_element_by_id(BUTTON_TEST)
Expand Down
1 change: 1 addition & 0 deletions examples/mobile/appium/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def do(self, screenshot_filename="./captures/guara-capture"):
Args:
screenshot_filename (str): The base filename for the screenshot.
Defaults to "./captures/guara-capture".
Returns:
None
Expand Down
13 changes: 7 additions & 6 deletions examples/mobile/appium/test_appium_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@ class TestAppiumIntegration:
def setup_method(self, method):
file_path = pathlib.Path(__file__).parent.resolve()
desired_caps = {
'platformName': 'Android',
'deviceName': 'emulator-5554',
'browserName': 'Chrome',
"platformName": "Android",
"deviceName": "emulator-5554",
"browserName": "Chrome",
"app": "/absolute/path/to/sample.apk",
"automationName": "UiAutomator2",
"noReset": True,
"appWaitActivity": "*",
'goog:chromeOptions': {'args': ['--headless']}
"goog:chromeOptions": {"args": ["--headless"]},
}
uniform_resource_locator: str = "http://localhost:4723/wd/hub"
self.driver = webdriver.Remote(uniform_resource_locator, desired_capabilities=desired_caps)
self.driver = webdriver.Remote(
"http://localhost:4723/wd/hub", desired_capabilities=desired_caps
)
self._app = Application(self.driver)
self._app.at(OpenAppiumApp, url=f"file:///{file_path}/sample.html")

Expand Down
5 changes: 4 additions & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ testify
stestr
green
appium-python-client
splinter
pyautogui
opencv-python
dogtail
splinter

0 comments on commit bf5f254

Please sign in to comment.