What does this solve?
There is a class of work where a person does the same thing in the browser every day: open the admin panel, set the period, click “Upload”, put the file in a folder. Or after each rollout, go through five screens and make sure that registration and payment are alive. Playwright does exactly this: the script is described by code once and then executed in a real browser - with rendering, JavaScript, cookies and everything that a live user sees. The second closed case is access to data where there is no API or it is closed: if a number exists only on the screen, it can be taken from the page programmatically.
How it works
What does it consist of?
The package is installed in the project by the team npm i -D @playwright/test, browsers download separately - npx playwright install. Own assemblies of Chromium, Firefox and WebKit of fixed versions are downloaded. System Chrome is not used by default, so that running on a laptop and running in CI run on the same engine and crash the same way. On a bare Linux server you usually need npx playwright install --with-deps - it will deliver system libraries, without which the browser will not start and gives a vague loading error.
Three levels of objects
browser — running browser process. context —an isolated profile inside it: its own cookies, its own localStorage, its own window size, its own locale and geolocation. page —tab. The context is created in milliseconds, so the correct way to separate the scripts is with a new context, not a new browser. One logged-in context for the manager, the second for the client - in one run, without session conflicts and without a second computer.
Locators and waiting
Record page.getByRole('button', { name: 'Оплатить' }) describes how to find an element, rather than finding it right away. The real search happens at the moment of action. Before clicking, the library checks: the element is in the DOM, visible, not blocked by another element, has stopped moving, and receives events. Only after this does it hit the coordinates. Because of this, pauses like “wait three seconds”—the main source of floating crashes—are eliminated from scripts. Check expect(locator).toHaveText(...) works the same way: it rereads the page until the condition matches or the timeout expires.
Order of preference when searching for an element: role and visible name (getByRole), field signature (getByLabel), text (getByText), an explicitly specified attribute (getByTestId). CSS and XPath paths like div > div:nth-child(3) > span work exactly until the first layout edit, after which the script crashes out of nowhere.
Runner and config
File playwright.config.ts sets the base URL, timeouts, number of parallel workers, retries for restarting a failed test, set projects (the same script on three browsers or on a mobile viewport) and a block webServer —the command to use to launch the application before running it, and the address at which to wait for it to be ready. Test files are distributed among workers, so the scripts must be independent: any order of execution must produce the same result.
Track
When setting trace: 'on-first-retry' The failed test is restarted with a write. The output is a zip archive: a snapshot of the DOM at each step, network requests, the console, screenshots before and after the action. Team npx playwright show-trace trace.zip opens this up with a timeline that shows at what step the page looked different from what the code expected. Reading the route is almost always faster than manually running it again and trying to catch the moment.
Network and authorization
Call page.route('**/api/**', route => ...) intercepts requests: replace the response, send prepared JSON, block external analytics that slow down the run and make noise on the route. Method context.storageState({ path: 'auth.json' }) saves cookies and localStorage after login; Then contexts are created with this file and start already authorized. Login is performed once in the preparatory project, and not in each script - saving minutes on each run.
Record draft
Team npx playwright codegen https://example.com opens the browser and writes code on clicks. The draft almost always requires editing - the generator takes the selectors as it turns out - but the skeleton of the script appears in minutes, and then all that remains is to manually change the method of searching for elements to a stable one.
Useful scripts
- Smoke after rollout: open the main page, log in, create an order, make sure it appears in the list - a couple of minutes of computer time instead of manual bypass.
- Upload from the panel without API: log in, set the period, click export, catch the file via
page.waitForEvent('download')and put it in the desired folder. - Monitoring of the critical path according to a schedule: the same scenario once an hour from cron, crash - message in messenger.
- Screenshots of pages in two themes and three viewports (
page.screenshot({ fullPage: true })) for reconciliation before and after redesign. - Verifying the application on behalf of the client: fill out the form, send, make sure that the letter is sent and the record is in the database.
- Analysis of the complaint “it doesn’t work for me”: run the script with a trace recording and see what the server actually returned at that moment.
Limitations
The script is based on markings. We changed the text of the button - it crashed. A set of scenarios needs an owner, otherwise after a couple of months half of them are red, they stop looking at the red, and the whole idea turns into noise.
Anti-bot protection and captcha stop automation by design. For other people's sites, they additionally look at the terms of use - technical feasibility and the right to act are two different things. Two-factor authorization also requires a solution: either a service account without a second factor, or a manual primary login with saving storageState and regularly updating the file.
The browser costs resources. Each parallel worker is a process with hundreds of megabytes of memory; on a small CI runner, a dozen workers put down the build due to lack of memory. Runs count in minutes, not seconds.
Where there is an API - data is taken through the API. Parsing HTML instead of a ready-made JSON endpoint is slower and breaks more often, because the layout changes without warning, and there is usually no endpoint contract.
Browser check says “the form was not sent”, but is silent about which function there is an error. Relying only on it is expensive in terms of runtime and painful when parsing.
Separate concern - secrets. The service account password should not be in the repository: environment variables and CI secret storage, file auth.json — in .gitignore.
How to check the result
Run in visible mode: npx playwright test --headed or --ui, where every step is visible and you can rewind. If a script runs in visible mode and crashes in the background, it's almost always a matter of window size or animation that no one expects in the background.
Stability check: npx playwright test --repeat-each=5 runs the same script five times in a row. A test that fails once out of five is repaired immediately - in CI it will turn red randomly and devalue the entire set.
Trace at first fall: enable trace: 'on-first-retry', wait for the fall in CI, download the artifact, open the viewer. Look at the step where the action did not find the element, and next to the network tab - often it is not the selector that is to blame, but 500 from the server.
Return code and report: a failed run must return a non-zero code and stop the pipeline. npx playwright show-report opens an HTML report with steps, screenshots and time of each action.
Training crash: break something on purpose on the test bench - change the button text, return 500 on the desired route - and make sure the script turns red. A set of checks that has never crashed does not prove anything about the system; it only proves that there are no checks.
