better playwright fixtures: cwd
, edit
, $
#14402
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
I'm bringing over the testing utils I created for react-router-templates which are mostly Playwright fixtures.
The goal is for end-to-end tests to stick as close to real user setups and workflows as possible while making the pit of success for writing good (correct, thorough) tests as wide as possible.
What do Playwright fixtures do?
1. Playwright statically analyzes which fixtures are used for each test
In this test, we use the built-in
page
fixture as well as our own customedit
and$
fixtures2. Playwright determines dependency graph for fixtures
Our
edit
and$
fixtures depend on another one of our custom fixtures calledcwd
which is in charge of setting up a temporary directory for out test. What's cool about this is thatedit
and$
know aboutcwd
, so I've set them up to automatically do everything relative to that path.3. Playwright runs pre-test code for fixtures in dependency order
For us, this means that
cwd
pre-test actually runs now and creates the temporary directory.edit
and$
don't do much in the pre-test phase. Notably, the fixture get executed separately for each test, so we get file system isolation for each test by default!cwd
also registers the temporary directory path as an attachment. That means any time a test fails that used these new fixtures, it will include thecwd
path neatly in the test output so you can immediately dive into that temporary dir and do some debugging.4. Playwright runs your test
We start by running
pnpm typecheck
just like a user would in their own terminal. If the command fails, we'll get an error thrown here with the exit code and error message. Then we asynchronously spin up a Vite dev server viapnpm dev
using the--port
flag with a unique port so we can run any other tests with Vite dev servers in parallel and without port conflicts.We could create an abstraction for this like
const { process, port } = viteDev()
but I personally like how thepnpm dev
command is right there in front of you.Next, we wait until the
dev
process has a URL with our expected port instdout
viaStream.match
(this API is in flight, up for bikeshedding)Then, we make edits to our app code while the dev server is running. You can either provide a string or a
(contents: string) => string
function to manipulate the contents of each edited file.At any point, you can use Playwright's built-in
page
fixture to test the page.5. Playwright runs post-test code for fixtures in reverse dependency order
Now we give our fixtures a chance to clean up.
edit
doesn't have any clean up, but$
keep track of each process it spawned and gracefully terminates them via.kill()
after the test is done to prevent any lingering, zombie processes from tests.Currently,
cwd
does not clean up its own temporary directory because I am emulating how our tests worked previously, where we would only clean upintegration/.tmp
when all tests passed. Not sure if this was intentional or just a limitation of our previous flow, so I'm up to change that if we want.Fixtures-options
By default, tests use the
vite-6-template
, but I've also created two fixtures-options that let you configure tests before they run:template
andfiles
.test.use
configures all tests in its scope, so you can usetest.describe
to do multiple different setups in the same file:You can even do this programmatically with a loop: