This project aims to modernize Django’s integration testing by introducing Playwright as an alternative to Selenium. The work includes integrating Playwright with Django’s test framework, migrating existing browser-based tests, and ensuring compatibility with Django’s CI infrastructure. The goal is to improve test reliability, performance, and developer experience for Django core contributors.
My plan is to update Django’s browser testing setup by moving from
Selenium to Playwright
Why Playwright is Better
● Playwright doesn’t need external WebDrivers
● Playwright has built-in auto-waiting for actions.
● Playwright is much simpler to write and read, lowering the cognitive load for
contributors.
● We can delete custom helper functions that we needed for Selenium.
Project Size: As of today, there are 106 selenium test cases in main branch that need to be converted.
Project Goals:
Modernize the testing infrastructure.
Rewrite existing tests
Boost CI execution performance.
Enhance test reliability.
Improve the developer experience with better tools
The project preserves Django’s testing architecture and only replaces the browser
automation layer (Selenium to Playwright).
Even though the translated tests use self.assertEqual(), some waiting is still needed, such as wait_for_load_state() or wait_for(), because a few tests are flaky.
I am leaning towards using Playwright’s expect() since it provides built-in auto-waiting and retrying. Also we could use inbuilt helpers in expect(). However, I don’t think expect() is needed for every test case. For example, tests without page navigation or tests that only verify backend/database changes can continue using self.assertEqual().
My concern is that having both approaches in the same test file may not be contributor friendly. I am also slightly concerned about importing expect in every test case (from playwright.sync_api import expect).
Another option could be to import expect at the top level, (this could be improved in the future with Python 3.15’s lazy imports) but I’m not sure whether ImportError handling is necessary, considering Playwright is already listed in our requirements and expect is available in playwright.sync_api.
Nice progress! You’ve outlined the reasons to use or not use expect(), and I don’t expect that to be too confusing for contributors. About the imports, what about consolidating them in one place in an instance method on the base class? Then test methods would call self.expect().
This Selenium test uses selenium.minimize_window() to minimize the browser window, whereas Playwright does not seem to have an API to minimize the browser.
We could use a CDP session for this, but that would be a Chromium-only solution, and we would need to skip this test on non-Chromium browsers.
For the web first locators, I would suggest that this is out of scope for the migration
We could chose to rework tests that do not involve testing translations, however tests decorated with @screenshot_cases(["rtl"]) are testing a translated page and we add this decorator to existing tests fairly frequently. Therefore, it is nice if the tests remain translation friendly
I agree web first locators test accessibility better but we can do any updates here aligned with accessibility testing tickets
The current Selenium setup uses headed mode by default and --headless option for headless mode.
I’d like to propose making headless the default and introducing --headed for headed mode. I believe Playwright uses headless mode by default. We could also add a --slowmo option for slow-motion execution and --debug for Playwright Inspector. else we could document about PWDEBUG=1 environment variable to launch tests in debug mode.
“Making headless the default and introducing --headed for headed mode” - +1, I think most people want to run the tests in headless
“Add a --slowmo option for slow-motion execution” - +1 (I have found this useful in the past using playwright tests, however this is a “nice to have” that shouldn’t block any existing work)
“Add --debug for Playwright Inspector” - 1, partly because I don’t have experience of the value and I feel that this option would be confusing for the usual tests
“Document about PWDEBUG=1 environment variable to launch tests in debug mode” - +0, if this is a useful option it is worth documenting but I do not have experience using myself
I see self.assertGreater() equivalent is not available in playwright expect helpers. something like this helps:
Instead of “count > 1”, assert the 2nd item exists. For example:
self.expect(pages.nth(1)).to_be_attached() # at least 2
instead of
self.assertGreater(pages.count(), 1)
What do you think, this is a good idea?
also, no helper in expect to do self.assertLess() work.
I’ve been using Playwright for a while now, and it’s been much less painful than Selenium for browser tests. The auto-waiting alone removes a lot of the boilerplate. Curious to see how the migration goes and whether there are any tricky cases in Django’s test suite.