How to stop worrying
and learn to love Playwright🎭

This post talks about why worth to choose the combination of Python + Playwright for beginners as well as experienced software test automation engineers. Once again, break the stereotype that Playwright is not only for JavaScript hipsters. We focus on the Python implementation of this testing framework.


Hi guys! It is Oleksii Ostapov from QA Mania Community. Passionate Automation QA engineer, Locust load testing and Playwright tools evangelist. Speaker at many conferences, QA DJ podcaster, lecturer. Last year Oleksii published the fantastic course “Test automation with Playwright and Python” in Ukrainian language on Youtube. If you are not worrying about Ukrainian, you can check the videos or code below ↩️

If you are wondering – why Python? Playwright supports JS natively! If you are not sure – try a programming language you like. I develop using Python because I love it ❤️. I know about its pros and cons (every programming language has its pros and cons), but I enjoy using it in my development and automation tasks. I believe it is very important to enjoy your job, whatever you are doing 🙂

I’m 100% sure Playwright – the best thing that could happen with web test automation!

It is fast, reliable and user-friendly. Furthermore – new releases are published regularly, issues are fixed, community – growing!

Let’s leave all concerns about the pros and cons of test automation itself for another time and check what awesome you can do with Playwright, if you have never heard about it.

Top 5 awesome Playwright features

1#: Playwright can work with web browser network requests

This feature sold me the framework 2 years ago. Just imagine – your test can open a website, do some steps which cause ajax request to the backend. And you have instruments to catch, analyze, replace request and even replace response! Previously negative tests were really hard to automate in E2E testing – you can’t just make a backend to respond HTTP 400 or HTTP 500 whenever you want – you need some mock servers, complicated infrastructure and tests.

Now I can cause a BE error with a few lines of code! But wait, it is more – if you want to send some additional HTTP requests from your browser, using the same web session with all headers, tokens, cookies, just do it with a single command! Check out documentation: API testing and request routing.

# response fulfill
page.route("**/*", lambda route: route.fulfill(
   body="not found!"))".donetworkAction")

# send request
response = page.request.get("/response")

#2: Selectors – one love!

When I used to work with Selenium, I always struggled with some tricky elements, hard to locate. No, I’m just having fun!

  • Except of generic selectors like CSS, XPath, id or label, Playwright devs added a lot of CSS pseudo-classes to improve search
  • There is a possibility to chain selectors. If you ever dreamed of using XPath and CSS in a single selector – just do it!
  • Moreover, you can select not the last element in the selector chain, but the element in the middle! Useful, if you need to locate some div by a tricky element inside of it. In XPath I’ve used scary constructions like parent and child
  • If searched element changes in time, you can put several selectors and make Playwright find it through one of them
  • Your front-end developers like to use iframes? Don’t worry – Playwright handles them natively
# pseudo class :has finds element which has elements
page.locator('tbody tr:has(.delete_1) .passBtn').click()

# selectors chain. * selects element in the middle
page.locator('xpath=//table >> *css=tr >> text="login"').click()

# Clicks a <button> that has either a "Log in" or "Sign in" text.
page.locator('button:has-text("Log in"), button:has-text("Sign in")').click()

# Locate element inside frame
# Get frame using any other selector
username = page.frame_locator('.frame-class').get_by_label('User Name')

3#: Work with web browser context

Earlier I always had issues, if my test opens a link in a new tab. I’ve tried to avoid such cases. If I had a test with 2 users doing some scenarios, I’d often skip them, because test were long and unstable. But now Playwright allows to create separate browser contexts to keep user sessions isolated. If I need a few tabs – just create them and cat manage at any moment!

context1 = chromium.new_context()
page1 = context1.new_page()
page1.fill('#username', 'alice')
page1.fill('#password', 'alice')'text="Login"')
context2 = chromium.new_context()
page2 = context1.new_page()
page2.fill('#username', 'bob')
page2.fill('#password', 'bob')'text="Login"')".button")".button")

4#: Playwright is a resilient and auto-waiting element tool

Forget stuff like checking ElementNotFoundException after each test record if, the element appears in DOM 1 second later than expected. You can just type selector and enjoy the results

5#: Playwright is headless out of the box.

I was surprised in the very beginning – how to check and debug why my test failed? But then I;ve realized – tests work as you expect – no need to debug them too often! 🙂

Out of this top I have to mention the async mode of using Playwright. You will not need it in 99% of cases, but I had 1% when I needed to do weird performance test with real browser instances. And I did it!

from playwright.browser import Browser

import asyncio
from playwright import async_playwright
import random

PAGES = (('Home', 'QA Mania'), ('Blog', 'Blog'), ('Useful links', 'Useful links'))

# here you can pass login/password to test function
async def open_test(browser: Browser, data: tuple, id: int):
    print(f'start #{id} thread')
    context = await browser.newContext()
    page = await context.newPage()
    await page.goto(BASE_URL)
    title = await page.title()
    result = 'ok' if data[1] in title else 'not ok'
    print(f'Thread #{id}: result for page {data[0]} --> {result}')
    await page.close()
    await context.close()

async def main():
    print('start main')
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False)
        tasks = list()
        for thread in range(10):
            tasks.append(open_test(browser, random.choice(PAGES), thread))
        await asyncio.gather(*tasks)
        await browser.close()

That’s my Playwright overview!

I am glad to share my top with you 🤗 Take the Playwright testing framework for a spin.

Last year I published a course “Test automation with Playwright and Python” on the Udemy testing course marketplace in Ukrainian language. Then I decided to publish it for free with English subtitles on Youtube due to my followers grow proficiently without any obstacles. It is still awesome. If you are not worrying about Ukrainian, you can check videos or code on Github Have fun and let’s get testing!

Note, the JUnit XML Format Support test management system allows you to consolidate all your Python Playwright automated tests and manual tests together in one play for analysis and better decision-making. And that is yet another reason for getting started with Playwright.

Let’s Talk…

  • What feature are you interested in the most?
  • Or if you are already using Playwright, what top features do you have?

Please feel free to spread your opinion in a comment, I would be glad to discuss the above with you.

Playwright extent report 📊
Simultaneously output manual and automated tests with the single test management tool
Follow us