The stage is set, let the automation begin.
A Pedagogical Pastiche of Playwright Programming
Works on Windows, macOS and Linux.
"Our lives now are an internship for the eschaton."
Russell D. Moore, Onward: Engaging the Culture without Losing the Gospel
Tip
This repo is meant to be studied and played around with.
Look at how the various projects are set up; look at what happens when you execute them; look at some of the design decisions. Nothing in here is touted as the "right" way necessarily. What is here is a means to show what does work and give you a starting point for your own learning. I recommend looking at the Execution section below to get a feel for what's in place. You can also see Jeff's Principles of Coding further down for some of my overall design thinking.
If you find any of this useful, consider leaving a ⭐️ for this repo.
Make sure you have Node.js. The LTS version should be fine. You will also need the npm
package manager, which comes with Node.js. A development environment or IDE with TypeScript/JavaScript support will help. Visual Studio Code is a good choice.
Clone the repository and then set everything up:
npm ci
Make sure to install the browsers that Playwright will need.
npx playwright install
In Playwright, a project is a logical group of tests that run using the same configuration. The sections below will each be shown with a command that executes the specific project. For any examples that are marked as UI, you can pass the --headed
argument in order to see the browser execution.
You can run any Playwright tests using the VS Code extension. This project will recommend that extension if you are using VS Code. However, it is highly recommended that you understand how to execute from the CLI.
These tests do not use a browser at all. They are meant to showcase the idea of simply writing tests and having some general tautologies that validate the basic operation of the testing framework.
One of the tautology tests isn't entirely frivolous since it serves as a small API test as well. My Swagger UI is set up with some simple OpenAPI specs, one of which is my tautology spec which is run as part of this project.
npx playwright test --project "Tautology Tests"
Try running just the tests marked as @canary
. You have to do this differently based on the operating system. For any POSIX-based system:
npx playwright test --project "Tautology Tests" --grep @canary
For Windows, particularly in Powershell:
npx playwright test --project "Tautology Tests" --grep "@canary"
This is an example project that shows how project dependencies in Playwright work. The idea is that you can login to an application and save those logged in credentials in storage state. Tests that rely on the logged in setup can then use this storage stage when executing, which means they don't have to login again before each test.
This project also shows the use of the .env
file to set environment variables that can be used as part of your tests. To run these tests, you'll want to create an .env
in the project directory if you don't already have one. By design, the .env
file is not version controlled.
cp .env.sample .env.
Then specify these two settings:
SAUCE_USERNAME=standard_user
SAUCE_PASSWORD=secret_sauce
The setup test will use these credentials to log in. You could actually run the setup test by itself since it's a distinct project. You would just do this:
npx playwright test --project "Sauce Setup"
This setup script will generate a file called .auth/sauce_user.json
. However you don't actually have to do that! That setup project is a dependency of another project, one that executes tests against the Sauce Labs site. Those tests live in a project that calls the setup project as a dependency. To run the tests that depend on the setup project:
npx playwright test --project "Sauce Logged In Tests"
This project uses a storageState
to indicate which storage state information should be used. That's stored in a constant called SAUCE_STORAGE_STATE
and that constant points to the above mentioned sauce_user.json
that was generated.
There's also a project that does not use the setup project and that's because this other project does not require a login. To run that, you can do this:
npx playwright test --project "Sauce Logged Out Tests"
This would be a good example where you might have tests that run only when logged in and other tests that check happens when not logged in.
This small little project is actually showcasing a series of things, such as environment settings, the use of storage state and project dependencies that are conditionalized based on whether the storage state is needed.
In terms of the tests, you might also note that this project indicates a specific testIdAttribute
that is used by the Sauce Labs demo, which is data-test
. This is what will be looked for when calls are made to the getByTestId()
function. You might also check out how the projects use testMatch
and testIgnore
settings based on the name of the spec files.
The Ludic pages are simply designed as blog content pages. Their complexity comes in from how the header and the scroll-to-top functionality have dynamic aspects, in terms of how and when they display. The dark-light mode is a relatively simply implementation that also accounts for the system setting. There are also "click to enlarge" elements on the page that provide a modal view for images.
To run these tests:
npx playwright test --project "Ludic UI Tests"
The playground area is designed to provide a simple landing page but then add some complexity. For example, the navigation pull out menu can have some challenges around checking for visibility and whether the widgets are in the viewport or not.
To run all the tests:
npx playwright test --project "Playground UI Tests"
The various pages within the playground are meant to run the gamut from relatively simple implementations of forms to slightly complex tables to elements that dynamically update but only upon the detection of certain user actions.
The planet weight area has two tests: one that uses a page object and one that does not. The same applies to the landing page tests. In both of those areas, you can see examples of iterating over data conditions while providing a single test condition.
I have provided my own Todo MVC application. You can run the tests:
npx playwright test --project "Todo UI Tests"
The Page Object Model is the often recommended approach for effective code organization in tests. Yet the very naming of the model -- page object -- tends to have people modeling only a whole page. Yet, there are often sections of a page that make sense to model on their own.
Some of these might be common elements between all pages, such as any navigation, headers and footers. Others might simply be defined areas of use that make sense to model distinctly. We can call this a Component Model approach.
A component approach would provide a bit more possibility for reuse but also for composability. The basic idea would be to create an intermediate layer of components that model aspects of the application and that all tests can utilie.
In this project, the components for the Todo MVC application can be found in the components
folder for the project. The ts-todo-app.spec.ts
shows the tests using my TesterStories application but using the component model.
The demo-todo-app.spec.ts
file is the same file that comes with Playwright as a working example. What I've done is show that this test, without modification, works on my slightly modified Todo MVC example. What this does is allow you to see the component approach side-by-side with the non-component approach.
To run the tests for this you need to obtain an account and API key from Weather API. You can see the OpenAPI spec I have available for the OpenWeatherMap API, which I'm using for this example.
This project, like the Sauce Labs example, shows the use of the .env
file to set environment variables that can be used as part of your tests. To run these tests, you'll want to create an .env
in the project directory if you don't already have one. By design, the .env
file is not version controlled.
cp .env.sample .env.
Then specify these two settings:
WEATHER_API_KEY=YOUR API KEY GOES HERE
WEATHER_API_URL=https://api.weatherapi.com/v1
You need to place your personal API key as the value for WEATHER_API_KEY
. Once you have that in place, you can run the Playwright specific tests for this project:
npx playwright test --project="Weather API Tests"
One thing to note about these Playwright tests is how they are using the APIRequestContext
. This is used to provide an abstraction layer for the API tests, which can be seen in tests/api/weather/activity/invoke.ts
.
You can also run the Playwright tests via the following command:
npm run test:weather:pw
The reason for the alternate execution method is because part of the goal of this particular set of tests it to also show how to leverage multiple tools. For example, I have some Newman tests for this as well. These tests are based on Postman collection files. You can run these with:
npm run test:weather:postman
There is also a supplemental project that runs the exact same API tests as the Playwright example but using Jest alone. You can run that with:
npm run test:weather:jest
What all of these examples show is that you can run multiple test styles within the same project.
The goal of this project is to demonstrate how to create an abstraction layer on top of standard testing practices, specifically focusing on API interactions using Playwright. To run the tests:
npx playwright test --project="Booker API Tests"
By building the abstraction, I've effectively created a test harness that wraps Playwright's internal operations, allowing for more streamlined and reusable test code. Here the abstraction is placed in the harness
directory of the Booker project although this would likely be put in a more central location for any API tests.
The harness/api.ts
file encapsulates all the logic for making API requests within your tests. It defines a class named API, which acts as a dedicated client for interacting with the API. By abstracting API communication into this class, we separate concerns and provide a clean interface for the rest of the test suite.
The API class is initialized with an APIRequestContext
, a Playwright object that manages HTTP requests. The core method, generateRequest
, handles the complexity of sending HTTP requests. This method dynamically constructs requests based on parameters such as the HTTP method, endpoint, request body, and authentication token, and returns the resulting response. Helper methods like getRequest
, postRequest
, and others are built on top of generateRequest, simplifying the process of making common API calls in your tests. This setup abstracts away the underlying details of API communication, allowing tests to focus on validation logic rather than request construction.
The harness/fixture.ts
file defines a fixture that integrates the API class into the Playwright test environment. This fixture sets up the API instance and makes it available to all tests via Playwright’s dependency injection mechanism.
The fixture is built using Playwright’s extend function, which allows you to augment the base test environment with custom fixtures. In this case, the API fixture is created to inject an instance of the API class, initialized with the Playwright request context, into the test environment. This ensures that every test block has access to the same, consistent API client, facilitating more uniform and reliable tests.
Additionally, the fixture file defines TypeScript interfaces like AuthResponse
and BookingResponse
to describe the expected structure of API responses. These interfaces not only provide type safety but also serve as documentation, clearly defining what the tests expect from the API.
The test suite in booker.spec.ts
showcases how to use the API abstraction and fixture to write clean, maintainable tests. The test blocks leverage the API parameter, injected by the fixture, to interact with the API. This allows the tests to focus purely on the validation of API behavior, without worrying about the intricacies of request handling.
Each test case follows a straightforward structure: making an API call using the appropriate method from the API
class and then asserting the response using Playwright’s assertion library. Notably, the tests make use of soft assertions provided by Playwright, which allow the test execution to continue even if an assertion fails. This is particularly useful in scenarios where you want to validate multiple aspects of a response without halting the test prematurely.
By structuring the Booker API project this way, what's achieved is a clear separation of concerns: the API class handles the communication logic, the fixture integrates this logic into the test environment, and the test files focus on validation. This design promotes reusability, maintainability, and clarity in API testing.
This project generates all of the standard reporting possible for tests in a Playwright context. You can always view the Playwright generated report with:
npm run report:pw
It's also possible to write custom reporters and one is provided with this project.
This is a custom reporter is provided that generates a very condensed view of test execution. This condensed format could be used to publish test results to something like an SMS message or a Slack channel update. The default output location is to the root project directory as a file called summary.txt
. You can change this location in the reporter configuration, which this project does.
There is a Stats
interface provided that the custom summary reporter uses but this can also be used by your own custom reporter, essentially based on the summary report. For an example of how to do that, you'll see a file called concise.ts
in the support/reporter
directory. This project uses that concise report as part of the reporter configuration.
This repo shows the usage of Playwright in a Cucumber context. To that end, the repo provides some settings that try to configure Visual Studio Code for you. These are provided in the .vscode
folder that will activate if you are using that editor. This should hook up the feature files and the step definitions so that the editor is aware of both and how they are connected.
To run a dry run of the specifications:
npm run specs:dryrun
To run the specs:
npm run specs
If you want to look at the Cucumber report:
npm run specs:report
If you want to generate and show the dashboard report:
npm run specs:dashboard
npm run specs:dashboard:show
This repo is set up to have Cucumber generate a "rerun" file for tests that failed. You can run that execution profile like this:
npm run specs:rerun
Obviously for that work you would need some failing tests.
Finally, Cucumber.js doesn't easily allow simple execution of one feature file from the command line. It does allow you to pass a regular expression which will check all scenarios for the identifier you provide. Some examples:
npx cucumber-js test --name "Weight on Mercury"
npx cucumber-js test --name "Mercury"
This project uses Prettier.
This is critical for any automation-based project. To run Prettier and automatically fix any issues, you can do this:
npm run format:fix
This project uses ESLint.
You can run linting in this project by doing this:
npm run lint
If you're feeling confident that the linter will be able to auto-fix your isue, you can run it like this:
npm run lint:fix
This project is using GitHub Actions. Check playwright.yml.
To run tests in Docker containers, follow these steps:
- Build the Docker Image
Install Docker and then build the Docker image from the provided Dockerfile:
docker build -t <image-name> .
- Create and Launch the Container
Create a container from the image and launch it in detached mode:
docker run -it -d <image-name>
- Verify Container is Running
Check that the container is up and running by listing all containers:
docker ps -a
Copy the container-id
of your running container.
- Log into the Container
Log into the running container's shell:
docker exec -it <container-id> bash
- Run Playwright Tests
Inside the Docker container, run your Playwright tests as usual:
npx playwright test
- Stop and Remove the Container (optional)
After your tests are done, you can stop and remove the container to clean up:
docker stop <container-id>
docker rm <container-id>
Build the Docker image:
docker build -t playwright-eschaton .
Create and run the container:
docker run -it -d playwright-eschaton
Log into the container:
docker exec -it <container-id> bash
Run tests inside the container:
npx playwright test
I'm using my own site material for this. One is a sample article called A Ludic Historian Précis. The other is my Playwright Playground.
- Embrace small code.
- Abstraction encourages clarity.
- No computation is too small to be put into a helper function.
- No expression is too simple to be given a name.
- Small code is more easily seen to be obviously correct.
- Code that’s more obviously correct can be more easily composed.
- Be willing to trade elegance of design for practicality of implementation.
- Embrace brevity, but do not sacrifice readability. Concise, not terse.
- Prefer elegance over efficiency where efficiency is less than critical.
As an existential note, this project is not intended to immanentize the eschaton. (Just in case anyone was worried.)
The eschaton is from the ancient Greek term ἔσχατον (éskhaton). The word literally refers to the 'last thing' or even 'the end.' When construed as the latter, the phrase tends to refer to the final events, or last days, of history. In theological circles it also refers to the ultimate destiny of the human race.
A fairly grandiose set of meanings, to say the least. All my current project does, however, is indicate what I believe to be the 'last thing' I intend to put up regarding my experiments with development and automation around Playwright.
"I am the Eschaton. I am not your God.
I am descended from you, and exist in your future.
Thou shalt not violate causality within my historic light cone.
Or else."
Charles Stross, Singularity Sky
The code used in this project is licensed under the MIT license.