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
pagefixture as well as our own customeditand$fixtures2. Playwright determines dependency graph for fixtures
Our
editand$fixtures depend on another one of our custom fixtures calledcwdwhich is in charge of setting up a temporary directory for out test. What's cool about this is thateditand$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
cwdpre-test actually runs now and creates the temporary directory.editand$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!cwdalso registers the temporary directory path as an attachment. That means any time a test fails that used these new fixtures, it will include thecwdpath 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 typecheckjust 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 devusing the--portflag 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 devcommand is right there in front of you.Next, we wait until the
devprocess has a URL with our expected port instdoutviaStream.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) => stringfunction to manipulate the contents of each edited file.At any point, you can use Playwright's built-in
pagefixture 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.
editdoesn'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,
cwddoes not clean up its own temporary directory because I am emulating how our tests worked previously, where we would only clean upintegration/.tmpwhen 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:templateandfiles.test.useconfigures all tests in its scope, so you can usetest.describeto do multiple different setups in the same file:You can even do this programmatically with a loop: