fbpx
Uncategorized

Test Automation With Python

According to IEEE Spectrum Python is the top programming language of 2021, and since April Loadero supports Python as a test script language. Many engineers agree that test automation with Python is an excellent choice, and in this blog post, we’ll show the basics of it.

Python tests in Loadero use the Py-TestUI framework. Py-TestUI wraps and implements additional features for Selenium Python binding. This means that Loadero Python tests can utilize the full power of Selenium and other cool features provided by Py-TestUI.

Test Structure

Every Python test starts with

def test_on_loadero(driver: TestUIDriver) -> None:

This function is like the main function in other programming languages. It will be called once the test execution is started. All of the test logic must originate from this function. This means that additional functions outside of the test_on_loadero function can be added, but they must be called from the test_on_loadero function to have any effect.

Driver

Loadero tests automate browser interactions as if they were done by a human to allow for repeated execution, parallel tests, and scale-up for load testing. Tests can do almost any action that a human can in a browser, for example:

These actions are achieved in a test with the driver object. driver wraps the workhorse of the browser test – Selenium WebDriver and is of type TestUIDriver. TestUIDriver implements commonly used browser interactions to improve ease of use but at the same time allows to access the underlying Selenium WebDriver for more complex browser interactions.

driver object is initialized and configured at the start of every test and passed as an argument to the test_on_loadero function.

Let’s look at common browser interactions and how to implement them in a test script. 

To start interacting with a web page, it first has to be opened. To do that call the following method

TestUIDriver.navigate_to(url: str) -> None

Argument url is the URL of a web page to open.

Here is a simple test that demonstrates the usage of navigate_to.

def test_on_loadero(driver: TestUIDriver) -> None:
    driver.navigate_to("https://duckduckgo.com/")
    driver.save_screenshot("duckduckgo.png")

Additionally, the test calls

TestUIDriver.save_screenshot(image_name: str) -> str

This method will, as the name implies, capture the state of the browser at the moment of the save_screenshot method call and save it with the name supplied by image_name argument. After the test finishes execution, all screenshots taken during the test will be available in the artifacts tab in the Loadero result page of a single test participant.

Tip: Taking screenshots during a test is a common practice that helps test debugging or manually reviewing what was on the test participant’s screen at the important stages of your test.

Clicks

A very fundamental browser interaction is the click. To interact with an element it first has to be located with the e method.

TestUIDriver.e(locator_type: str, locator: str) -> Elements:

locator_type argument describes the location method of an element and can have values:

  • css – locate element by CSS selector
  • name – locate element by name
  • className – locate the element by its class name
  • xpath – locate element by XPath

locator – describes how to locate the element with the location method set by locator_type argument. To learn more about using CSS selectors and XPath locators in your Selenium test automation scripts make sure to read this blog post.

An attentive reader might have noticed that e method has return type Elements – plural, but I have been using elements in a singular form so far. This is because Elements object has more advanced features (for example – locating multiple elements at once, image matching) that this blog post does not cover. So in an attempt not to confuse the reader these topics will be skipped.

After locating the desired element it can be clicked on with the click method.

Elements.click() -> Elements:

There is nothing in the way for calling click right after an element has been located, but a good practice is to check if the element is visible with a runtime assertion before clicking on it. If the element is visible then it has had enough time to load and can be interacted with. Doing this allows avoiding test failures caused by attempting to click on an element that has not loaded yet. We use a lot in tests we create for our customers and advise you to do the same.

wait_until_visible method does as the name suggests – waits for the element to load and become visible before attempting further interactions.

Elements.wait_until_visible(seconds=10.0, log=True) -> Elements:

Argument seconds with a default value of 10.0 specifies the amount of time the test will wait until the element becomes visible. If the element is not visible after waiting the specified time an error will be raised and the test will fail.

Argument log with a default value True specifies whether to log the result of the wait_until_visible method call. This argument generally should not be changed and can be ignored. Many Elements methods have a log argument and it can be ignored in all of them.

Here is a simple test script showcasing everything shown so far. You can put everything learned so far to test with Loadero’s free trial. You can also find the script example in Loadero’s Github repository.

def test_on_loadero(driver: TestUIDriver) -> None:
    # Opens the search engine DuckDuckGo with default search query of
    # google.com
    driver.navigate_to("https://duckduckgo.com/?q=google.com")

    # Locates an element with a CSS selector that is pointing to the first
    # search results title, waits for the element to become visible and clicks
    # on it.
    driver.e("css", "#r1-0 > div > h2").wait_until_visible().click()

    # Ensures that the web page has loaded before taking a screenshot.
    driver.e("css", "body > div:first-of-type").wait_until_visible()
    driver.save_screenshot("google.png")

Input Text

The previous test script example was cheating a bit. Realistically a human user would insert the search query “google.com” themselves and wouldn’t be using a specially prepared URL. To simulate this behavior, tests can input text with the .send_keys method.

Elements.send_keys(value: str, log=True) -> Elements:

value argument specifies the text that will be sent to an element.

To clear the input value that was set by send_keys or an element has by default use clear method.

Elements.clear() -> None:

Retrieving Content of an element

Browser interactions can be generalized as a feedback loop where the next interaction is determined by the resulting content of a web page. Tests need a way to retrieve the content of a web page or more specifically the content of a single element to implement logic that decides what the next interaction should be. This section will cover the other half of the browser interaction feedback loop – content retrieval.

Most HTML elements have content, with few exceptions like <br> element, that is rendered to the web page and can be interacted with.

To retrieve the textual value of an element use

Elements.get_text() -> str:

Putting Everything Learned To Use

It’s time to combine all the commands we have described in a single test automation script. This script example performs a search for a specific query on the DuckDuckGo search engine and prints out the top results.

def test_on_loadero(driver: TestUIDriver) -> None:
    # The main logic of the test is in top_search_results function
    print(top_search_results(driver, "QA Processes", 5))


# top_search_results returns the top n search results for search_query.
def top_search_results(driver: TestUIDriver, search_query: str, n: int) -> str:
    # Limits the search result count to 10, because a single search query in
    # DuckDuckGo search engine returns only 10 results.
    n = min(n, 10)

    # Selectors.
    home_page_search_bar = "#search_form_input_homepage"
    home_page_search_button = "#search_button_homepage"

    # Opens the search engine DuckDuckGo.
    driver.navigate_to("https://duckduckgo.com/")

    # Locates search bar, waits for the element to load and sends a search query
    # to it
    driver.e("css", home_page_search_bar).wait_until_visible().send_keys(
        search_query
    )

    # Locates search button, verifies that it is visible and clicks it.
    driver.e("css", home_page_search_button).wait_until_visible().click()

    result = f"Top {n} DuckDuckGo search results for {search_query} ->\n"

    # Iterates over top n search results
    for i in range(n):
        # Creates a CSS selector that indicates to the search result title.
        selector = f"#r1-{i} > div > h2 > a.result__a"

        # Locates the search element and verifies that it is visible.
        search_result_element = driver.e("css", selector).wait_until_visible()

        # Retrives the title of search result and adds it to result string.
        title = search_result_element.get_text()
        result += f"\t {title} \n"

    return result

You can find the output of this test script in Loadero’s single participants result pages logs tab under Selenium log. The output would log something like this:

Top 5 DuckDuckGo search results for QA Processes ->
    QA Process: A Complete Guide to QA Stages, Steps, & Tools
    13 Best Practices For A Comprehensive And Careful QA Process
    The QA Process: A beginner's guide to the main stages
    The Quality Assurance Process: Roles, Methods & Tools
    Quality assurance - Wikipedia

We covered the basics of working on test automation with Python. You can create a test similar to what we described and run it multiple times free of charge with Loadero’s free trial. To continue learning to use Python for your tests in Loadero make sure to visit our Wiki, where you can find information about custom commands that extend the functionality WebDriver and PyTestUI, variables that hold the relevant information about the test participant, and more script examples of test automation with Python.