Recording with Codegen
Playwright ships a recorder. You click through the flow in a real browser and it writes the test for you, with locators that follow the recommended priority. This is the Recording step of the JMeter track, with one difference: the recorded output is a starting point you will clean up, not the finished test.
Why Record?
- It gives you correct locators for free. The recorder prefers roles, labels and placeholders over CSS.
- It captures the flow faster than typing it.
- It teaches you the API. Watching what the recorder emits is the fastest way to learn
getByRoleand friends.
What it does not do: structure your test, name it well, add meaningful assertions, or handle test data. That is your job after recording.
Before You Start
- Have the flow written down from Section 2: steps and pass condition
- Know the test account you will use
- Have the project from Section 1 open in VS Code
Option A: Record from the Command Line
Steps:
-
Start the recorder with the URL of the app:
npx playwright codegen https://www.saucedemo.com/Two windows open: a browser and the Playwright Inspector with the generated code.
-
In the browser, perform the flow as a real user. Every click and keystroke appears in the Inspector as code.
-
Add assertions from the Inspector toolbar. Hover the toolbar to see the buttons:
- Assert visibility: click it, then click an element, to generate
toBeVisible() - Assert text: generates
toContainText() - Assert value: generates
toHaveValue()for inputs - Assert snapshot: generates
toMatchAriaSnapshot()for a chunk of the page structure
- Assert visibility: click it, then click an element, to generate
-
Use Pick locator any time you want to see what locator Playwright would generate for an element without recording an action.
-
Click Copy in the Inspector, or save straight to a file:
npx playwright codegen https://www.saucedemo.com/ -o tests/login.spec.ts
Useful flags:
| Flag | Use it when |
|---|---|
--viewport-size="1280,720" | The app is responsive and you want a fixed size |
--device="iPhone 13" | Recording a mobile flow |
--save-storage=auth.json | You want to log in once and save the session (see Section 9) |
--load-storage=auth.json | Start the recorder already logged in |
--test-id-attribute=data-test | The app uses a test id attribute other than data-testid |
--target=python | You want Python instead of TypeScript output |
Note: By default the recorder generates a
toBeVisible()assertion after each navigation. That is a good starting point but not a real pass condition. Replace it with what the flow is actually supposed to prove.
Option B: Record from VS Code
Steps:
- Open the Testing sidebar
- In the Playwright section, click Record new. VS Code creates a new spec file and opens a browser
- Perform the flow. Code is written into the file as you go
- Use the toolbar in the browser to add assertions, same buttons as Option A
- Close the browser or click Cancel to stop
- Rename the file and the test
The same sidebar has Record at cursor (append steps into an existing test at the cursor position) and Pick locator.
Tip: Recording at cursor is the fastest way to extend an existing test: put the cursor at the end of the test, press Record at cursor, do the extra steps, stop.
What the Recorder Produces
A raw recording of the sample login flow looks like this:
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.getByPlaceholder('Username').click();
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').click();
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Products')).toBeVisible();
});
It works, but it is raw, the same way a fresh Blazemeter .jmx is raw:
- The test is named
test - Every field has a pointless
click()beforefill() - The URL and credentials are hardcoded
- The assertion checks a word, not the pass condition
Clean Up the Recording
Do these in order. Each one is a small edit.
- Name the test after the flow and its expected result:
valid user lands on the products page - Delete the
click()before everyfill().fill()focuses the field itself - Replace the hardcoded URL with
page.goto('/')and setbaseURLin the config (Section 8) - Move credentials into
tests/data/users.json(Section 6) - Replace the assertion with the real pass condition, usually the URL plus the page title:
await expect(page).toHaveURL(/inventory\.html/);await expect(page.getByTestId('title')).toHaveText('Products');
- Group related tests with
test.describeand move shared navigation intobeforeEach
After cleanup the same test is the one in playwright/sample/tests/login.spec.ts.
Tips
-
Record once, then write by hand. After the first couple of recordings you will find it faster to type tests directly using the recorder only to pick locators.
-
Do not record logins over and over. Save the session once with
--save-storageand load it. Section 9 turns this into an automatic setup step. -
Record with the right viewport. A responsive app shows different buttons on mobile widths. Record at the size you will test at.
-
Recorder output is not the final test. Treat it like the raw
.jmx: it needs correlation-style cleanup before it goes into the suite.