Playwright on NixOS for webdev
In this post, I will explain how to run Playwright tests on NixOS. Playwright is a framework for running automated tests on web pages in real browsers. However, the recommended method of installing browsers doesn't work on NixOS due to its unique nature. This post will demonstrate how to set up a flake for a Vite project to run the Playwright version of your choice.
§ Introduction
Nixpkgs also provides the
playwright-driver
package. This package builds and installs the Playwright executable. However, in
my case, I don't need it because I install and run Playwright via npm. The only
thing I need is a Playwright browser since the recommended
npx playwright install
fails on NixOS.
Playwright releases new versions frequently, and often by the time a pull request to update the version in Nixpkgs is merged, a newer Playwright version has already been released. Sometimes, I also need specific older versions for certain projects.
I needed a solution that allows me the flexibility to choose any Playwright version. However, I couldn't figure out how to override the Nixpkgs package to install the specific version I wanted.
What Playwright essentially needs is a Chromium browser in a specific directory with a versioned name. Here’s how the Playwright driver provides the browser:
First, it downloads browser.json
from the Playwright repository for the
specified version, which records the required browser versions. It then parses
the browser version, creates a derivation with a structure that matches
Playwright's expectations, and places Nixpkgs Chromium into it. Finally, the
PLAYWRIGHT_BROWSERS_PATH
environment variable is set so that Playwright looks
in the Nix store instead of the default directory in the home folder.
This approach is a bit of a workaround because the Chromium version isn't exactly the one specified by Playwright. But it usually works, which is better than nothing.
What I did was copy the relevant code from Nixpkgs that handles only the browser for Linux, and I created a local package that allows me to change the version as needed.
The usage is demonstrated in the following tutorial. The sources can also be found on GitHub: PrimaMateria/blog-playwright-nixos-webdev.
§ Init flake and direnv
I use Playwright in web development for functional tests in TypeScript projects. To simulate these conditions, let's generate a new Vite project.
Start with project's flake.nix
.
#flake.nix
{
description = "Blog Playwright NixOS webdev";
outputs = inputs @ {
self,
nixpkgs,
utils,
devToolkit,
haumea,
...
}:
utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
};
# not used yet
src = haumea.lib.load {
src = ./.nix;
inputs = {inherit pkgs;};
};
in {
devShell = devToolkit.lib.${system}.buildDevShell {
name = "blog.playwright-nixos-webdev";
profiles = [
"node"
];
};
}
);
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
devToolkit = {
url = "github:primamateria/dev-toolkit-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
haumea = {
url = "github:nix-community/haumea/v0.2.2";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}
I'll be using my own development toolkit to set up Node and npm. I've prepared Hamuea and flake-utils for best practices.
#.envrc
use flake
Create direnv's config and enable it with direnv allow
. Add .direnv
to
.gitignore
. Try if node is ready.
~/dev/blog-playwright-nixos-webdev$ node --version
v18.20.4
§ Generate vite project
Create typescript react vite project.
~/dev/blog-playwright-nixos-webdev$ npm create vite@latest
Need to install the following packages:
create-vite@5.5.2
Ok to proceed? (y) y
> npx
> create-vite
✔ Project name: … foo
✔ Select a framework: › React
✔ Select a variant: › TypeScript
Scaffolding project in /home/primamateria/dev/blog-playwright-nixos-webdev/foo...
Done. Now run:
cd foo
npm install
npm run dev
Merge foo/.gitignore
with root .gitignore
and move all content from /foo
to root. Current file tree looks like this:
.
├── .direnv
├── .envrc
├── .git
├── .gitignore
├── .nix
├── eslint.config.js
├── flake.lock
├── flake.nix
├── index.html
├── package.json
├── public
├── README.md
├── src
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
Now run npm install
and npm run dev
and verify that vite dev server is
running.
§ Install Playwright
Initialize Playwright without browsers and without system dependencies.
~/dev/blog-playwright-nixos-webdev$ npm init playwright@latest
Need to install the following packages:
create-playwright@1.17.133
Ok to proceed? (y) y
> foo@0.0.0 npx
> create-playwright
Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (y/N) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · false
✔ Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo npx playwright install-deps')? (y/N) · false
Installing Playwright Test (npm install --save-dev @playwright/test)…
...
Happy hacking! 🎭
If you would try install browsers and system deps it would fail with following error:
Downloading browsers (npx playwright install --with-deps)…
BEWARE: your OS is not officially supported by Playwright; installing dependencies for ubuntu20.04-x64 as a fallback.
Installing dependencies...
Switching to root user to install dependencies...
sh: line 1: apt-get: command not found
Failed to install browsers
Error: Installation process exited with code: 127
Error: Command failed: npx playwright install --with-deps
at genericNodeError (node:internal/errors:984:15)
at wrappedFn (node:internal/errors:538:14)
at checkExecSyncError (node:child_process:890:11)
at execSync (node:child_process:962:15)
at executeCommands (/home/primamateria/.npm/_npx/d352e76cc6b4974c/node_modules/create-playwright/lib/index.js:4730:39)
at Generator.run (/home/primamateria/.npm/_npx/d352e76cc6b4974c/node_modules/create-playwright/lib/index.js:4910:5)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async /home/primamateria/.npm/_npx/d352e76cc6b4974c/node_modules/create-playwright/lib/index.js:5205:3 {
status: 1,
signal: null,
output: [ null, null, null ],
pid: 124425,
stdout: null,
stderr: null
}
§ Installing chromium browser
Create paramterized base package:
#.nix/_playwright-browsers-base.nix
{pkgs}: {
version,
sha256,
}: let
fontconfig = pkgs.makeFontsConf {fontDirectories = [];};
playwright-browsers-json = pkgs.stdenv.mkDerivation rec {
name = "playwright-${version}-browsers";
src = pkgs.fetchFromGitHub {
owner = "Microsoft";
repo = "playwright";
rev = "v${version}";
sha256 = sha256;
};
installPhase = ''
mkdir -p $out
cp packages/playwright-core/browsers.json $out/browsers.json
'';
};
in
pkgs.runCommand "playwright-browsers-chromium"
{
nativeBuildInputs = [
pkgs.makeWrapper
pkgs.jq
];
}
''
BROWSERS_JSON=${playwright-browsers-json}/browsers.json
CHROMIUM_REVISION=$(jq -r '.browsers[] | select(.name == "chromium").revision' $BROWSERS_JSON)
mkdir -p $out/chromium-$CHROMIUM_REVISION/chrome-linux
# See here for the Chrome options:
# https://github.com/NixOS/nixpkgs/issues/136207#issuecomment-908637738
makeWrapper ${pkgs.chromium}/bin/chromium $out/chromium-$CHROMIUM_REVISION/chrome-linux/chrome \
--set SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt \
--set FONTCONFIG_FILE ${fontconfig}
FFMPEG_REVISION=$(jq -r '.browsers[] | select(.name == "ffmpeg").revision' $BROWSERS_JSON)
mkdir -p $out/ffmpeg-$FFMPEG_REVISION
ln -s ${pkgs.ffmpeg}/bin/ffmpeg $out/ffmpeg-$FFMPEG_REVISION/ffmpeg-linux
''
This part I have extracted from github:kalekseev's PR. I used only the linux browsers, but the mac browsers should be possible to extract in similar way.
Create version specific package:
#.nix/playwright-browsers-1_46_1.nix
{
pkgs,
super,
}: (super.playwright-browsers-base {
version = "1.46.1";
sha256 = "sha256-HEDBaUqszemnWseKHjPJqivPlYq7jSuhQL1MQa47264=";
})
If you need a different version, simply change the version to match a tag in the Playwright repository and set sha256 to an empty string. When you run it for the first time, an error will occur due to the mismatched hashes. Copy the expected hash from the error message and replace the empty string with it.
Extend the dev shell with an extra package and an additional shell hook. Use Haumea to include the newly created package.
#flake.nix
{
description = "Blog Playwright NixOS webdev";
outputs = inputs @ {
self,
nixpkgs,
utils,
devToolkit,
haumea,
...
}:
utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs {
inherit system;
};
src = haumea.lib.load {
src = ./.nix;
inputs = {inherit pkgs;};
};
in {
devShell = devToolkit.lib.${system}.buildDevShell {
name = "blog.playwright-nixos-webdev";
profiles = [
"node"
];
extraPackages = [
src.playwright-browsers-1_46_1
];
extraShellHook = ''
# Prepare playwright
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
export PLAYWRIGHT_BROWSERS_PATH=${src.playwright-browsers-1_46_1}
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
'';
};
}
);
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
devToolkit = {
url = "github:primamateria/dev-toolkit-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
haumea = {
url = "github:nix-community/haumea/v0.2.2";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}
Disable Firefox and WebKit, which are enabled by default after running Playwright init. Keep only Chromium in the projects list.
//playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
// ...
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
// ...
});
Verify that tests are running:
~/dev/blog-playwright-nixos-webdev$ npx playwright test
Running 2 tests using 2 workers
[chromium] › example.spec.ts:10:1 › get started link
Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.
[chromium] › example.spec.ts:3:1 › has title
Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.
2 passed (2.1s)
To open last HTML report run:
npx playwright show-report
Playwright is defined in my development toolkit under the profile
'playwright'
. In hindsight, it might have been better not to include the dev
toolkit here, as it's a personal tool that I often modify without considering
others who might use it. However, I realized this late and didn't want to redo
the tutorial. I'm confident you can replace it with your own packages and shell
hook for Node installation. The Node profile
definition
is straightforward.
Reddit comments
See on RedditGitHub comments