Best practices

General

  • (Required) Use named arguments of the transactions to keep the natural speech

# do
app.at(home.Search, by_text="foo").at(home.CreateResource, named="bla")

# don't (the code will fail)
app.at(home.Search, "foo").at(home.CreateResource, "bla")
  • Add documentation of transactions in the class level describing all arguments and the returned value.

class SaveFlightData(AbstractTransaction):
    """
    Saves flight data to a JSON file after filtering outdated data.

    Args:
        flights_data (list): The list of flight data to save.
        history_days (int): The number of days to retain in history.

    Returns:
        str: A message indicating the data was saved.
    """
    def do(self, flights_data, history_days):
        existing_data = read_json_file("flights_data.json", [])
        current_date = datetime.now().date()
  • Use the app.result attribute to return the result of the last transaction

urls = app.when(GetURLs).result
for url in urls:
    print(url)
  • Use the app.asserts method directly to validate the result of the last transaction. Sometimes the user wants to get the result of the transaction to use it later.

ids, data, has_more = app.when(GetArticleIDs, page=page).result
app.asserts(it.IsGreaterThan, 0)  # Verify we got articles
  • Use the proper verb method of app to make the statements as natural speech

app.at(home.OpenInfo, ...)
app.when(OpenInfo, ...).and_(ChangeToEnglish, ...)
  • Use chained verb methods to build your statements in natural language

app.at(home.OpenInfo, ...).and_(ChangeToEnglish, ...).asserts(...)
app.when(OpenInfo, ...).and_(ChangeToEnglish, ...).asserts(...)
app.at(home.OpenInfo, ...).at(info.ChangeToEnglish, ...).asserts(...)
  • Give good names to parameters of transactions to keep a smooth reading

# do
app.at(home.Search, by_text="foo").at(home.CreateResource, with_name="bla")

# don't
app.at(home.Search, value="foo").at(home.CreateResource, value="bla")
  • Use named arguments in the transaction’s signature

# recommended
class SaveFlightData(AbstractTransaction):
    def do(self, flights_data, history_days):
        existing_data = read_json_file(flights_data)
        current_date = timedelta(days=history_days)

# not recommended
class SaveFlightData(AbstractTransaction):
    def do(self, **kwargs):
        existing_data = read_json_file(kwargs.get(flights_data))
        current_date = timedelta(days=kwargs.get(history_days))

Automation Testing

  • Add typing to self._driver in transactions to allow the autocomplete of IDEs

class CloseBrowser(AbstractTransaction):
    def __init__(self, driver):
        self._driver : Chrome = driver
  • For a better readability of the code it is recommended to use a high-level tools instead of raw Selenium commands. In this example there is the complete implementation of a test using Browserist. This is one of the transactions.

from browserist import Browser
from guara.transaction import AbstractTransaction


class SubmitText(AbstractTransaction):
    def __init__(self, driver):
        super().__init__(driver)
        self._driver: Browser

    def do(self, text):
        TEXT = '//*[@id="input"]'
        BUTTON_TEST = '//*[@id="button"]'
        RESULT = '//*[@id="result"]'
        self._driver.input.value(TEXT, text)
        self._driver.click.button(BUTTON_TEST)
        return self._driver.get.text(RESULT)
  • Other interesting tool is Helium. More details in this example. Here is a transaction implementation:

from helium import find_all, write, click, S, Text
from guara.transaction import AbstractTransaction


class SubmitText(AbstractTransaction):
    def __init__(self, driver):
        super().__init__(driver)

    def do(self, text):
        TEXT = '//*[@id="input"]'
        BUTTON_TEST = "button"
        text_field = find_all(S(TEXT))[0]
        write(text, text_field)
        click(find_all(S(BUTTON_TEST))[0])
        return Text("It works!").value

Advanced

For more examples of implementations check the examples folder.