Impara / Cheat sheet
Cheat sheet di Playwright
Le API di Playwright Test che usi ogni giorno, in un solo foglio. I locator sono elencati nell’ordine raccomandato da Playwright: ruolo, label e testo prima dei test id, e CSS o XPath per ultimi. Ogni asserzione qui è web-first: riprova finché non passa o va in timeout, quindi non servono attese manuali.
Verificato su Playwright Test 1.5x, TypeScript
Esecuzione e debug
| Attività | Codice |
|---|---|
| Nuovo progetto | npm init playwright@latest |
| Esegui tutti i test | npx playwright test |
| Un file, una riga | npx playwright test tests/login.spec.ts:12 |
| Per titolo o tag | npx playwright test --grep @smoke |
| Un solo progetto browser | npx playwright test --project=chromium |
| Vedi il browser | npx playwright test --headed |
| Esegui passo passo con l’inspector | npx playwright test --debug |
| UI mode (watch, time travel) | npx playwright test --ui |
| Solo i fallimenti dell’ultima esecuzione | npx playwright test --last-failed |
| Stana un flaky test | npx playwright test my.spec.ts --repeat-each=20 |
| Suddividi (shard) su più macchine CI | npx playwright test --shard=1/4 |
| Apri il report HTML | npx playwright show-report |
| Apri una trace | npx playwright show-trace trace.zip |
| Registra uno script | npx playwright codegen https://example.com |
Locator (dal preferito)
| Attività | Codice |
|---|---|
| Per ruolo e nome accessibile | page.getByRole('button', { name: 'Sign in' }) |
| Campo del form tramite la sua label | page.getByLabel('Email') |
| Per placeholder | page.getByPlaceholder('Search') |
| Per testo (esatto) | page.getByText('Welcome back', { exact: true }) |
| Per test id (data-testid) | page.getByTestId('checkout-submit') |
| CSS | page.locator('#orders tbody tr') |
| XPath | page.locator('xpath=//table[@id="orders"]//tr') |
| Filtra per testo | page.getByRole('row').filter({ hasText: 'Mei Lin' }) |
| Filtra per elemento figlio | page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) }) |
| Concatena dentro un risultato | row.getByRole('button', { name: 'Cancel' }) |
| Scegline uno tra tanti | .first() .last() .nth(2) |
| Dentro un iframe | page.frameLocator('#payment').getByLabel('Card number') |
I locator sono lazy: non viene cercato nulla finché non esegui un’azione o un’asserzione, quindi definiscili una volta e riusali.
Azioni
| Attività | Codice |
|---|---|
| Naviga (relativo a baseURL) | await page.goto('/login') |
| Clic | await locator.click() |
| Sostituisci il valore di un input | await locator.fill('mei@example.com') |
| Digita tasto per tasto | await locator.pressSequentially('abc', { delay: 50 }) |
| Premi un tasto | await locator.press('Enter') |
| Checkbox o radio | await locator.check() / await locator.uncheck() |
| Seleziona un’opzione | await page.getByLabel('Country').selectOption('UK') |
| Carica un file | await locator.setInputFiles('fixtures/invoice.pdf') |
| Hover | await locator.hover() |
| Drag and drop | await source.dragTo(target) |
| Accetta la prossima finestra di dialogo | page.once('dialog', (d) => d.accept()) |
Asserzioni web-first
| Attività | Codice |
|---|---|
| Visibile / nascosto | await expect(locator).toBeVisible() / .toBeHidden() |
| Testo esatto | await expect(locator).toHaveText('Order placed') |
| Il testo contiene | await expect(locator).toContainText('placed') |
| Valore di un input | await expect(input).toHaveValue('42') |
| Numero di corrispondenze | await expect(page.getByRole('row')).toHaveCount(5) |
| Abilitato / disabilitato / selezionato | toBeEnabled() toBeDisabled() toBeChecked() |
| Attributo | await expect(menu).toHaveAttribute('aria-expanded', 'true') |
| URL e titolo | await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders') |
| Negazione | await expect(spinner).not.toBeVisible() |
| Continua dopo un fallimento | await expect.soft(total).toHaveText('£40.00') |
| Riprova qualsiasi controllo asincrono | await expect.poll(async () => (await api.get(url)).status()).toBe(200) |
| Confronto visivo | await expect(page).toHaveScreenshot('home.png') |
Rete e API
| Attività | Codice |
|---|---|
| Simula (mock) una risposta | await page.route('**/api/orders', (r) => r.fulfill({ json: [] })) |
| Fai fallire una richiesta | await page.route('**/api/orders', (r) => r.abort()) |
| Modifica una risposta reale | await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); }) |
| Attendi una risposta | const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy(); |
| Test di API | test('list users', async ({ request }) => {
const res = await request.get('/api/users');
await expect(res).toBeOK();
expect(await res.json()).toHaveLength(3);
}); |
Struttura, fixture e hook
| Attività | Codice |
|---|---|
| Raggruppa i test | test.describe('checkout', () => { /* tests */ }) |
| Esegui prima di ogni test | test.beforeEach(async ({ page }) => { await page.goto('/'); }) |
| Assegna un tag a un test | test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ }) |
| Salta in modo condizionale | test.skip(browserName === 'webkit', 'Not supported on WebKit') |
| Parallelismo dentro un file | test.describe.configure({ mode: 'parallel' }) |
| Fixture personalizzata | export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
}); |
| Riusa uno stato autenticato | test.use({ storageState: 'playwright/.auth/user.json' }) |
| Dai un nome a uno step nel report | await test.step('add to cart', async () => { /* ... */ }) |
playwright.config.ts
| Impostazione | Esempio |
|---|---|
| URL base per page.goto | use: { baseURL: 'http://localhost:3000' } |
| Evidenze in caso di fallimento | use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } |
| Retry solo in CI | retries: process.env.CI ? 2 : 0 |
| Parallelismo | fullyParallel: true, workers: process.env.CI ? 2 : undefined |
| Fallisci se viene committato un test.only | forbidOnly: !!process.env.CI |
| Avvia prima l’applicazione | webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } |
| Browser | projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] |
| Timeout predefiniti | timeout: 30_000, expect: { timeout: 5_000 } |