Bun is one of the latest evolutions in the javascript ecosystem and so I wanted to learn how it can be leveraged in an automation testing project using Playwright. In this post, I will step through how we can setup our developer environment for using Playwright, Typescript and Bun.
Bun is not essential, I just wanted to try it out. You can easily swap it out for npm
, pnpm
or yarn
.
Installing Bun
To begin, we will install Bun. I would recommend following their installation process here for your operating system. Once installed, we can run bun --version
to verify the installation has worked. At the time of writing this, I am using version 1.1.21
.
Creating the project structure
With Bun installed we will now create a new directory for our project. You can name it whatever you like.
Inside our new directory we can create a new Playwright project with the initial structure setup using the create playwright
command.
You will be prompted to select some options. We want to select Typescript, add the default Github Actions workflow and install the Playwright browsers.
)
) )
This will create the files and directories of a Playwright project for you. However, notice that even though we have run the command with bun
, it has installed the project dependencies with npm
. So for us to make use of Bun, we can remove the package-lock.json
file that npm
creates and instead install the dependencies again with Bun.
This will create a new lock file and our initial project should now look something like this.
There are several things to understand here that you will need to know in all Playwright projects.
- Your dependencies are kept in the
node_modules
directory (which can get pretty large!) - You can manage your dependencies and project in the
package.json
file, which we will come to later - The Playwright configuration is managed in
playwright.config.ts
- It is common practice to keep all test files in a
tests
directory
With an understanding of the basic project structure we can now try and run the tests.
You should see in your terminal that the tests are running and it will show you the pass or failure rate and the time taken. This is the bare minimum you need to get started writing automated tests. To make our project more robust we want to include some tooling to help our code stay idiomatic and mostly error free. For this we need a formatter and a linter.
Formatting with Prettier
Prettier is the standard formatter for Typescript and Javascript projects. We first need to install it as a development dependency to our project.
This will install Prettier for us, but we also need to create a configuration file for Prettier so that we can define the formatting rules we want to enforce.
Open the .prettierrc
file in your chosen editor and add the following basic configuration. Remember, this is the default configuration recommended by Prettier and you can change it to your own stylistic preferences using the many options available, which are documented here.
To format the code we can run the Prettier cli on our project.
This will output to the terminal which files have been formatted. Luckily, by default, Prettier ignores version control files and node_modules
.
Now our code will conform to a set of formatting rules. However, there is more to be done.
Configuring Typescript
Playwright uses Typescript and transforms it to Javascript at runtime. However, it does not do any type checking so it will still run with non-critical errors. You can read about it more here. If we want a consistent and robust codebase we can utilise the Typescript compiler to help us catch these errors.
Let's add typescript
as a development dependency.
This gives us access to the binary tsc
, which is the Typescript compiler.
The first step is to create a tsconfig.json
which contains the settings that the Typescript compiler will use for our project. We can conveniently create this by running tsc
with the --init
flag.
This creates a tsconfig.json
file in the root of our project. We want to start off with the following settings.
We just want to check that the Typescript compiles with no errors, we do not need the Javascript output. To do this we can run tsc
with the --noEmit
flag. But first, go to the tests
directory and update the following line in the example.spec.ts
file to introduce a call to a function that does not exist.
// Before
await page/Playwright/
// After
await page/Playwright/
Now we can run the Typescript compiler.
Once that command has run we should see an error displayed explaining what is wrong in our code.
This allows us to catch issues with our code before we get to running our tests. Unfortunately it will not catch all the issues. That is where we can add linting to our project as another line of defence.
Linting with Eslint
Linting is a way to analyse our code for any incorrect usage or non-idiomatic programming that may cause our code to not run as we intended. A common one is missing the await
keyword which causes incorrect behaviour in asynchronous code, which you will see all over your Playwright project. Therefore we will leverage Eslint as another tool in our arsenal. More specifically, we will use typescript-eslint and eslint-plugin-playwright.
There are a few packages to install for this to work how we want it to.
Similar to the other tools we have installed, we need a configuration file. For this we can create a eslint.config.mjs
file in the root of our project.
Inside this file we can declare that we want to use the recommended linting rules for eslint
, typescript-eslint
and eslint-plugin-playwright
as well as explicitly declare any specific rules we want our linter to enforce. For this all to work we must tell eslint
that our project is a Typescript project and that our tsconfig.json
file is in the root of our project. All together we want something like this.
We can try this out by removing await
from one of the lines in example.spec.ts
.
// Before
await page/Playwright/
// After
page/Playwright/
Now we can lint our test files that are in the tests
directory and see that our linter has caught the missing await
.
These tools will assist us with keeping our project consistent and with finding issues in our code before we run our tests. Having to manually run each command independently is inefficient and it slows down the rate at which we can iterate on our tests. Fortunately we can use the scripts
functionality in our package.json
file to bundle these commands together.
Adding scripts to package.json
We can create a script each for formatting, linting and type checking and add them to the scripts
object in package.json
.
"scripts":
We can run each of these commands individually with bun run
whenever we need to. We can also streamline this by creating a pretest
and test
script. The pretest
script will automatically run before the test
script.
"scripts":
Now each time we run the command bun run test
our project will be formatted, type-checked and linted before the tests are run. This will tighten our feedback loop so that our code always stays consistent and correct. Most editors can also integrate with these tools and provide this feedback immediately without needing to run the scripts.
Using Bun in Github Actions
Our final task is to update the Github Action that we created when initialising the project. By default it uses npm
but as we are using bun
we must make a couple of changes. We can use oven-sh/setup-bun
instead of actions/setup-node
and update the subsequent run commands accordingly.
name: Playwright Tests
on:
push:
branches:
pull_request:
branches:
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Install Playwright Browsers
run: bun playwright install --with-deps
- name: Run Playwright tests
run: bun playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
This project structure sets up a good foundation to build upon and allows you to focus on the most important thing, writing tests.
Back to top^