IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Beginner·9 / 16

Assertions

The most important thing to understand: locator assertions auto-retry. expect(locator).toBeVisible() polls until the element appears — you don't write any waiting loops. Value assertions like expect(someString).toBe('x') are instant and can be flaky on async UIs.

Locator assertions — they wait automatically

Locator assertions poll the DOM in a loop — no manual waitFor needed

Every assertion that takes a Locator retries until the condition is met or the timeout expires (default: 5 seconds). Playwright re-queries the element and checks the condition in a loop. You don't write waitFor manually — the assertion does it.

The most common ones I use every day:

ts
// Видимість — найпоширеніша перевірка
await expect(page.getByRole('heading', { name: 'Orders' })).toBeVisible()
await expect(page.getByTestId('error-banner')).not.toBeVisible()
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()

// Текст — підрядок або повний збіг
await expect(page.getByTestId('status-badge')).toHaveText('Shipped')
await expect(page.getByTestId('order-count')).toContainText('24 orders')

// URL і заголовок
await expect(page).toHaveURL('/dashboard')
await expect(page).toHaveURL(/\/orders\/\d+/)
await expect(page).toHaveTitle('Orders | CRM')

// Поля вводу
await expect(page.getByLabel('Email')).toHaveValue('admin@example.com')
await expect(page.getByLabel('Status')).toHaveValue('pending')

// Кількість рядків у таблиці
await expect(page.getByRole('row')).toHaveCount(11) // 1 header + 10 rows

// Checkbox
await expect(page.getByRole('checkbox', { name: 'Notify client' })).toBeChecked()

Value assertions — instant, no retry

These assert plain JavaScript values — strings, numbers, arrays, objects. They run once and fail immediately if the condition isn't met. Use them for data you've already extracted from the page, not for UI state that might still be loading.

ts
// ✅ Правильно — витягуємо значення, потім перевіряємо
const count = await page.getByRole('row').count()
expect(count).toBeGreaterThan(0)

const title = await page.title()
expect(title).toContain('Orders')

// ✅ Перевірка об'єктів і масивів (не пов'язана з DOM)
const ids = ['ORD-001', 'ORD-002', 'ORD-003']
expect(ids).toHaveLength(3)
expect(ids).toContain('ORD-002')
expect(ids[0]).toMatch(/^ORD-\d+/)

// ❌ Небезпечно — значення може ще не завантажитися
const text = await page.locator('.status').textContent()
expect(text).toBe('Shipped') // краще: await expect(locator).toHaveText('Shipped')

Negating with .not

Any assertion can be negated with .not. It works on both locator and value assertions. For locator assertions, .not also waits — it retries until the condition becomes false.

ts
// Після логауту — форма входу видима, dashboard — ні
await expect(page.getByRole('form', { name: 'Login' })).toBeVisible()
await expect(page.getByTestId('dashboard')).not.toBeVisible()

// Кнопка Submit вимкнена поки поля не заповнені
await expect(page.getByRole('button', { name: 'Submit' })).not.toBeEnabled()

// Значення не порожнє
expect(orderId).not.toBeUndefined()
expect(orderId).not.toBe('')

Soft assertions — fail later, not immediately

A normal assertion stops the test immediately when it fails. A soft assertion (expect.soft) marks the test as failed but lets it continue running. I use this when I want to check multiple things on a page and see all failures in one test run.

ts
test('order confirmation page is complete', async ({ page }) => {
  await page.goto('/orders/42/confirmation')

  // Перевіряємо всі елементи — не зупиняємося при першій помилці
  await expect.soft(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible()
  await expect.soft(page.getByTestId('order-number')).toHaveText('ORD-042')
  await expect.soft(page.getByTestId('total-amount')).toContainText('$')
  await expect.soft(page.getByRole('link', { name: 'View all orders' })).toBeVisible()

  // Наприкінці — явно перевірити що не було soft-падінь
  // (опціонально, тест все одно зафейлиться якщо були)
  expect(test.info().errors).toHaveLength(0)
})

expect.poll — retry any async check

expect.poll runs a function repeatedly until a value assertion passes. Use it when you need to check something that isn't a locator — like an API response, a database value, or a count you computed yourself.

ts
// Polling API поки не повернеться 200
await expect.poll(async () => {
  const response = await page.request.get('/api/orders/42/status')
  return response.status()
}, {
  message: 'order export should eventually succeed',
  timeout: 15000,
}).toBe(200)

// Polling поки кількість рядків не збіжиться
await expect.poll(async () => {
  return await page.getByRole('row').count()
}, { timeout: 5000 }).toBe(11)

Add a message to assertions

Pass a second argument to expect() to label the assertion in reports. When the test fails, the message appears in the error output — much easier to find which assertion failed than reading a locator description.

ts
await expect(
  page.getByTestId('status-badge'),
  'order should be in shipped state after submit'
).toHaveText('Shipped')

// Якщо впаде — в репорті побачиш:
// Error: order should be in shipped state after submit
// Expected: "Shipped"
// Received: "Pending"