In modern software development, automated testing is a key factor in ensuring application quality and reliability. In this post, as a software architect and expert in testing and software quality, I will discuss UI testing with Playwright – a powerful tool that excels in end-to-end (E2E) testing while also being flexible for use with mocking strategies and containerized test environments.
Why Playwright?
Playwright has become one of the leading frameworks for browser automation in recent years. Supporting multiple browser engines (Chromium, Firefox, WebKit), it allows applications to be tested in real browser environments, providing developers and testers with authentic feedback on user interactions and experiences.
Testing Against Real Systems
A major advantage of Playwright is its ability to run tests against real systems, such as a staging environment. This provides a highly realistic testing scenario. However, some prerequisites must be met:
- Stable Test Data: Tests must run against consistent and predictable data. Unstable or changing test data can lead to flaky test results.
- Reproducible Data State: The test environment must be reset to a known state after each test run. This ensures reliable debugging and standardized test execution.
With these conditions in place, Playwright can perform tests in real environments with high reliability, making it particularly useful in late-stage development or release candidate cycles.
Isolated UI Tests with Mocked API Calls
In many cases, it is impractical—or even impossible—to run tests against a fully operational system. In such situations, pure UI tests can be performed using mocked API calls. This offers several advantages:
- Faster Execution: Since external services are not contacted, tests complete more quickly.
- Increased Stability: Mocking eliminates external factors such as network issues or unstable backends.
- Focus on the Frontend: Developers can concentrate on the UI behavior without worrying about backend interactions.
However, relying solely on mocked API calls is not a silver bullet, as critical integration aspects between the UI and backend remain untested.
Additionally, teams must define a clear boundary between UI tests with mocked API calls and unit/component tests using frameworks such as Jest and React Testing Library. This helps avoid redundancy and unnecessary extra work without added value.
Integration with Testcontainers and DockerComposeEnvironment
Another powerful approach to running Playwright tests independently of a real system is using containerized test environments. With Testcontainers & DockerComposeEnvironment
(documentation), complex systems can be simulated in isolated containers. This offers several benefits:
- Reproducible Environments: Containers—such as a service providing a REST API for the UI and its database—can be launched in a predefined state, ensuring consistent test data and configurations.
- Isolation of Dependencies: External services are controlled within the container landscape and can be started and stopped as needed.
- Continuous Integration: Containerization makes it easier to integrate UI tests into automated CI/CD pipelines.
This strategy combines the benefits of real system tests (such as realistic integration and behavior) with the stability and control of isolated test environments. However, it is also essential to maintain a clean test bootstrap and teardown process to ensure reliable execution in CI/CD pipelines.
For example, when bootstrapping a PostgreSQL database, the setup could look like this:
const environment = await new DockerComposeEnvironment("./", "docker-compose.yml").up();
const container = environment.getContainer('postgres');
const host = container.getHost();
const port = container.getMappedPort(databasePort);
process.env.URI_DATABASE = `postgresql://myUser:myPassword@${host}:${port}/my-service-db`;
The connection string can then be used as an environment variable to configure the API service container. A similar approach can be applied for URLs that the UI needs to connect to the API, and so on.
The Test Pyramid and Challenges of End-to-End Testing
In the context of the Test Pyramid, the general recommendation is to focus on unit and integration tests, as they are typically faster, more robust, and easier to maintain. However, end-to-end tests, including Playwright UI tests, are essential to ensure the overall functionality of the application. Several risks and challenges must be considered:
- High Effort and Maintenance Costs: End-to-end tests are often more complex and more sensitive to system changes. Even minor UI or backend modifications can cause test failures that require time-consuming adjustments.
- Slow Execution: Due to the numerous interactions and the need to launch full systems, E2E tests are significantly slower than unit tests.
- Flakiness: Even with stable test data, factors such as network conditions, asynchronous processes, or external dependencies can lead to intermittent test failures, reducing confidence in the test framework.
Therefore, E2E tests should be used sparingly and strategically—primarily as smoke tests or for critical application flows. The majority of test coverage should be achieved through faster, more deterministic unit and integration tests.
Conclusion
Playwright offers a powerful toolset for UI testing, whether through real system tests in staging environments (which require stable test data and reproducibility) or isolated tests using mocked API calls. Additionally, Testcontainers and DockerComposeEnvironment provide a flexible and controlled test setup.
However, the challenges of end-to-end testing—such as high effort and potential flakiness—must always be considered. Following a well-balanced test strategy that leverages the strengths of different test types will help create robust and maintainable test suites that contribute to long-term software quality.