Qxf2 explored an advanced capability with Playwright recently – writing asynchronous tests. Given our admiration for pytest we picked the Playwright-pytest plugin for our research. While we were impressed with the pytest integration, we noticed the official and community documentation only featured synchronous examples using pytest. This post will hopefully bridge this gap. We figured working on fitting pytest with asynchronous Playwright will also help us understand Playwright internals better. In this post we will show you how to go about creating a simple Playwright UI test using pytest.
Note: This post assumes that: you are already familiar with Playwright and have used pytest to write synchronous UI tests.
Creating asynchronous pytest fixture
pytest’s preloaded fixtures for Playwright makes writing tests a very straightforward task but unfortunately the pytest fixtures only uses the synchronous APIs. The challenge is to create a coroutine fixture. We had used pytest_asyncio before to create asynchronous API test, we will be using pytest_asyncio to create a asynchronous fixture to:
– Start Playwright
– Launch a browser
– Create a new page
# Contents of conftest.py file import pytest_asyncio from playwright.async_api import async_playwright @pytest_asyncio.fixture async def async_page(pytestconfig): "Async fixture to return a Playwright Page" browser = pytestconfig.getoption("--browser") async with async_playwright() as playwright: browser_obj = getattr(playwright,browser[0]) if browser_obj: browser = await browser_obj.launch() page = await browser.new_page() yield page |
The asynchorous fixture uses a context manager to start the Playwright service and stop it at exit, it supports a --browser
CLI parameter to get the browser input and returns a new page
Disclaimer: The code in this blog post is intended for demonstration purposes only. We recommend following better coding standards for real-world applications.
Creating an Asynchronous UI Automation test
Now that we have an asynchronous fixture, creating a test is an easy task. We can create coroutine test functions using pytest_asyncio. The tests we create will validate the following scenarios:
– Landing on a page
– Form in the page
Validate landing on a page
The test scenario for the first test function is to validate landing on Selenium Tutorial Page. We can use the async_page fixture we created to create the test function. The @pytest.mark.asyncio marker needs to be used for pytest to collect the coroutine as a test.
@pytest.mark.asyncio async def test_has_title(async_page): "Validate Tutorial Main page title" await async_page.goto("https://qxf2.com/selenium-tutorial-main") title = await async_page.title() assert title == "Qxf2 Services: Selenium training main" |
Validate the form in the page
The test scenario for the second test function is to validate the example form in the same page
@pytest.mark.asyncio async def test_contact_form(async_page): "Validate contact form" await async_page.goto("https://qxf2.com/selenium-tutorial-main") await async_page.get_by_role("textbox", name="name").fill("Jonathan Wick") await async_page.locator("xpath=//input[@name='email']").fill("[email protected]") await async_page.locator("xpath=//input[@name='phone']").fill("0000000000") await async_page.locator("xpath=//button[@type='submit']").click() redirect_title = await async_page.title() assert redirect_title == "Qxf2 Services: Selenium training redirect" |
Putting it all together
Consolidating the test functions in a test_async_tutorial_page.py module:
@pytest.mark.asyncio async def test_has_title(async_page): "Validate Tutorial Main page title" await async_page.goto("https://qxf2.com/selenium-tutorial-main") title = await async_page.title() assert title == "Qxf2 Services: Selenium training main" @pytest.mark.asyncio async def test_contact_form(async_page): "Validate contact form" await async_page.goto("https://qxf2.com/selenium-tutorial-main") await async_page.get_by_role("textbox", name="name").fill("Jonathan Wick") await async_page.locator("xpath=//input[@name='email']").fill("[email protected]") await async_page.locator("xpath=//input[@name='phone']").fill("0000000000") await async_page.locator("xpath=//button[@type='submit']").click() redirect_title = await async_page.title() assert redirect_title == "Qxf2 Services: Selenium training redirect" |
Note: In the real world, this example would be very useful if you are automating a survey with dozens of questions.
Running the test
To run the test use the following command:
pytest --browser chromium -s -v |
There you have it, a simple asynchronous Playwright UI test that uses pytest. Like we mentioned at the start of this post only features a basic example on how to use pytest to create an asynchronous Playwright UI test but if you wish to create/modify your own offshoot of Playwright pytest framework and wish to make it support async refer Playwright-Python’s own async conftest.py file to create your async fixtures.
Hire testers from Qxf2
To stay competitive and relevant Qxf2 services make every effort to adapt to the latest technological trends. We constantly explore new tools and frameworks to improve our technical footprint and to identify and implement useful features to our open-source framework on GitHub here – Qxf2 Page Object Model Framework. If you are looking for technical testers to help create automation tests for your ever evolving product contact Qxf2.
My expertise lies in engineering high-quality software. I began my career as a manual tester at Cognizant Technology Solutions, where I worked on a healthcare project. However, due to personal reasons, I eventually left CTS and tried my hand at freelancing as a trainer. During this time, I mentored aspiring engineers on employability skills. As a hobby, I enjoyed exploring various applications and always sought out testing jobs that offered a good balance of exploratory, scripted, and automated testing.
In 2015, I joined Qxf2 and was introduced to Python, my first programming language. Over the years, I have also had the opportunity to learn other languages like JavaScript, Shell scripting (if it can be called a language at all), and more recently, Rust. Despite this exposure, Python remains my favorite language due to its simplicity and the extensive support it offers for libraries.
In my free time, I like to watch football (I support Arsenal Football Club), play football myself, and read books.
Nice article on implementing asynchronous testing using Playwright.