Robot players: end-to-end tests
All green, still broken
Every unit and integration test for Hyper Shards passes. Then Remy opens the game on a phone: the new logo covers the Start button, and nobody can start a run.
Small tests never open the game, so they couldn't see it. An end-to-end test (e2e test) does: it runs the whole game from one end to the other, in a real browser, the way a player would. Open the page, click Start, press keys, then check what is on screen.
Meet Playwright
Our studio writes e2e tests with Playwright, a free testing tool made by Microsoft. It drives three real browser engines:
- Chromium, the engine inside Chrome and Edge
- Firefox
- WebKit, the engine inside Safari
npx playwright test runs every test in all three, several at once, and headless: the browsers run out of sight, with no window on screen. A bug that only shows up in Safari still gets caught.
Bolt says: npx playwright codegen opens a browser and writes test code while you click and type. It makes a good first draft, which you then tidy up.
A test, line by line
await page.goto('/'); opens the game.await page.getByRole('button', { name: 'Start' }).click(); finds the Start button the way a player sees it, and clicks it.await page.keyboard.press('ArrowLeft'); taps the left arrow.await expect(page.getByTestId('wave')).toHaveText('Wave 1'); checks the screen.
await means "finish this step before the next one". Playwright also auto-waits: it waits until the button is visible and ready before clicking, and expect keeps retrying until the text matches or time runs out.
Finish the test
1import { test, expect } from '@playwright/test';2 3test('hit an alien, score 10', async ({ page }) => {4 await page.goto('/');5 await page.getByRole('button', { name: 'Start' })6 .click();7 // ...the ship flies up and fires at one alien8 9});
Kai's test hits an alien but never checks anything, so it passes even when scoring is broken. Which line belongs at line 8?
- await expect(page.getByTestId('score')).toHaveText('10');
- await page.waitForTimeout(5000);
- console.log('probably worked');
Show the answer
await expect(page.getByTestId('score')).toHaveText('10');
A test with no expect only proves nothing crashed. The expect checks what the player sees, and keeps retrying until the score shows 10. A fixed 5 second wait checks nothing, and Playwright's own docs warn that tests that wait for time are flaky.
Canvas is a closed box
Hyper Shards draws everything with PixiJS onto one canvas: a single picture, redrawn about 60 times a second. To Playwright, the ship and the aliens are just coloured pixels. There is no button or text inside to find.
So studios leave a way in:
- HUD as HTMLThe HUD (heads-up display: score, lives, wave) and the menus stay normal page elements, tagged like data-testid="score", so getByTestId finds them.
- A test hookThe game shares its state in a small object, like window.__game. The test reads it: page.evaluate(() => window.__game.lives).
Quick check
Playwright can find each alien ship in Hyper Shards with getByRole, because it reads everything drawn on the canvas.
- True
- False
Show the answer
False
A canvas is one big picture, so there are no elements inside it to find. Tests check the HTML HUD with getByTestId, or read the game's state through a test hook with page.evaluate.
Phone-sized screens
Garden Defense is built for phones, and many Hyper Shards players will install the PWA on a phone. Playwright can emulate (imitate) devices from a built-in list. In playwright.config.js you add one project per device:
{ name: 'phone', use: { ...devices['Pixel 7'] } }
That makes the page about 412 pixels wide, turns on touch, and tells the game it is a phone browser. Every test now runs at desktop and phone sizes, which is how the hidden Start button gets caught.
Bolt says: Emulation is close, not perfect. Before a release, still play on a real phone.
Slow, flaky, and how to debug
- ScreenshotWith screenshot: 'only-on-failure', Playwright saves a picture of the screen at the moment a test failed.
- TraceA recording of every step: the page before and after each action, plus network requests and console errors. Open it from the report with npx playwright show-report.
- RetriesWith retries: 2, a failed test runs again. If it then passes, the report marks it flaky: a warning to fix, not to ignore.
E2e tests have two weaknesses. They are slow: each one starts a browser and loads the game, so it takes seconds, not milliseconds. And they can be flaky: passing one run and failing the next, with no code change, because of timing, animations or random alien spawns.
Pick the e2e test
Remy wants to add one more end-to-end test to Hyper Shards. Which one deserves it?
- Play until game over, then check the final score appears on the leaderboard
- Check that scoreFor(1, 2) returns 20, by playing until a combo happens
- Check the colour of every alien, one test per alien
Show the answer
Play until game over, then check the final score appears on the leaderboard
Finishing a run and saving the score is a journey every player takes, and it crosses many pieces at once. Combo maths is a job for a fast unit test, and colours belong in a visual test, not in dozens of slow e2e tests.
Few and focused
Because e2e tests are slow, studios keep them few, and save them for the journeys that would hurt most if they broke. Ours:
- Start a gameLoad the page, click Start, see Wave 1.
- Finish a runLose all lives and reach the game over screen with the right score.
- Save the scoreThe score reaches the leaderboard, using a separate test backend, never the real one.
- Buy the gameThe pay-once purchase works, using Stripe's test mode, where pretend card numbers make pretend payments.
Bolt says: Who runs all these tests on every push? Next lesson: the robot tester.
Fact-checked 9 October 2026
- Playwright: installation playwright.dev
- Playwright: writing tests playwright.dev
- Playwright: locators playwright.dev
- Playwright: emulation playwright.dev
- Playwright: test options playwright.dev
- Playwright: trace viewer playwright.dev
- Playwright: retries playwright.dev
- Playwright: codegen playwright.dev
- Playwright: CI playwright.dev