``If you have time to write test code, implement the function.''
Six months ago, I seriously thought so. That was one Friday night. Never in my wildest dreams did I imagine that the moment I hit the release button, the login functionality in the production environment was wiped out, and I would experience the thrill that would make my blood boil.
Discord was flooded with notifications from users saying, ``I can't log in?'' and in the end, I had to switch everything back. The feeling of despair I felt at that time was already at a traumatic level. Needless to say, that night I vowed to quit the Russian roulette of manual deployment and adopt the "shield" of Playwright.
1. [Black History] The story of the suicide bombing in Selecta Hell and the only escape route
The reason why writing tests is so tedious is because everyone aims too much at the ``perfect test.'' Don't aim for 100% coverage from the beginning. First of all, we will automate only ``Can you log in and view my page?'' This alone will dramatically lower your heart rate during release.
Playwright is the best because of the magic called `codegen`.
npx playwright codegen http://localhost:3000
`Just hit this and click the UI and the code will be generated, but you need to be careful here. ** At first, I used the generated code as is and got burned. I was writing a structure like `div > div > span:nth-child(2)` in a CSS selector.
Just by adding one `div` in the design adjustment, all the tests turn bright red. This is a situation where the UI cannot be changed because the test will be broken.
Lesson: Trust `data-testid`.
Embed `data-testid="login-button"` in the HTML. In this case, even if the design changes, the test will never die.
#### How to write good test code (refactoring perspective)
The code generated by `codegen` is just a "draft". This will be sublimated into a highly maintainable form.
// Before modification: CSS selector dependent (fragile)
await page.click('div > div > button');
// After modification: Identify by data-testid (robust)
await page.getByTestId('login-button').click();
`
2. To never repeat the nightmare of that night
It is natural for the test to pass at hand. There is no point in using GitHub Actions unless you create an environment where the test runs the moment you submit a pull request. That night, I learned the horror of manual deployment to the core, and wrote this YAML to ensure the safety of the merge.
`
name: Playwright Tests
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
-run: npm install
-run: npx playwright install --with-deps
-run: npx playwright test
`Now, if the test fails, you won't be able to press the merge button. This "force" is the strongest barrier that protects us engineers.
3. [Parallel execution trap] Why it passes locally but dies in CI
As the number of tests increases, execution time becomes a concern. Playwright runs it in parallel by default, which is another trick.
Example of what not to do:
// User created in test A, deleted in test B
test('User creation', async () => { await createUser(); });
test('delete user', async () => { await deleteUser(); });
`If this is run in parallel, B will delete the user before A creates it, or B will steal the user created by A, resulting in an error. Since the problem cannot be reproduced in local single-threaded execution, it is a hell of a situation where you only notice it after it crashes in CI.
Code after modification:
The golden rule is to create an independent environment for each test.
// Generate a unique user for each test and clean up after finishing
test.afterEach(async ({ page }) => {
await page.evaluate(() => localStorage.clear());
// Call the DB cleanup process here if necessary
});
`
Finally: Testing is an investment in slacking off.
Test automation is not "insurance". It's an "engine for speeding up development." Because we have tests, we can deploy with confidence on a Friday night, and we don't have to fear bold refactoring.
Start by writing one test for the login screen. The next step is to automate critical paths such as ``payment flow'' and ``form submission,'' which can be fatal if they fail.
It may be a hassle at first. But if you don't want to experience that cold sweat again, hit `npm init playwright@latest` now.
Tests are not something you write. The task is to create a shield to protect yourself. With that shield, we can write code more freely and faster.