# Actions and Auto-Waiting

Course: [Playwright](https://www.devtutorials.ai/learn/playwright) · Section: Core Test Writing
Source: https://www.devtutorials.ai/learn/playwright/core-test-writing/actions-and-auto-waiting

> Navigation (goto options, reload, back and forward, waitForURL, waitForLoadState), mouse actions (click options, double-click, right-click, hover), typing (fill, clear, pressSequentially, press, focus, blur), form actions (check, selectOption, radio buttons), scrolling, and how auto-waiting works: actionability checks, retries, waitFor, why fixed sleeps fail, and the force and trial options.

## Actions and Auto-Waiting

- **Actions** are the things a test does: open pages, click, type, tick boxes and pick options.
- Before every action, Playwright waits until the element is ready. This is **auto-waiting**.
- In this lesson, you will use every common action, and see exactly what Playwright waits for.

**Simple lesson:** actions do what users do, and auto-waiting makes them wait for the page.

### A Real-World Example

- A customer writes to the Practice Shop's support team.
- They type their name, pick a topic, choose how to get a reply, agree to the terms and send.
- This test does the same, one action per step.

- The **Send message** button starts disabled. It becomes enabled when you tick the terms box.
- Playwright clicked it only after it was enabled. You did not write any waiting code.

### What You Will Learn

1. How to move between pages.
2. Mouse actions: click and its options, double-click, right-click and hover.
3. Typing: fill, clear, type key by key, and press keys.
4. Form actions: checkboxes, radio buttons and drop-down menus.
5. Scrolling.
6. How auto-waiting works, and why fixed sleeps are a bad idea.

## Navigation

### page.goto() Options

- `page.goto(url)` opens a page and waits until it has loaded.
- `waitUntil` says what "loaded" means:

| Value | Waits until |
|---|---|
| `'load'` (default) | the page and its images and styles have loaded |
| `'domcontentloaded'` | the HTML is read, before images finish |
| `'networkidle'` | no network traffic for 500 ms. Discouraged: slow and unreliable |
| `'commit'` | the first bytes of the response arrive |

- `timeout` sets how long to wait, in milliseconds: `page.goto(url, { timeout: 10_000 })`.
- `goto()` returns the **response**. A 404 page does not throw an error, so check `response.status()` if it matters.

### reload(), goBack() and goForward()

- `page.reload()` reloads the page, like pressing F5.
- `page.goBack()` and `page.goForward()` work like the browser's back and forward buttons.

### page.waitForURL() and page.waitForLoadState()

- After a click that opens another page, `page.waitForURL('**/account')` waits for that URL.
- `**` matches any text, so you do not need the full address.
- `page.waitForLoadState()` waits for the current page to finish loading. You rarely need it, because `goto()` and `waitForURL()` already wait.

**Simple lesson:** `goto()` opens a page. `waitForURL()` waits for a page that a click opens.

## Mouse Actions

### click() and Its Options

| Option | Example | Does |
|---|---|---|
| `button` | `click({ button: 'right' })` | Right-click (or `'middle'`) |
| `clickCount` | `click({ clickCount: 2 })` | Clicks more than once |
| `modifiers` | `click({ modifiers: ['Shift'] })` | Holds keys while clicking: `'Alt'`, `'Control'`, `'ControlOrMeta'`, `'Meta'`, `'Shift'` |
| `position` | `click({ position: { x: 5, y: 5 } })` | Clicks a point inside the element |

### dblclick(), Right-Click and hover()

- `dblclick()` double-clicks, for example to edit a value in place.
- `click({ button: 'right' })` opens a custom right-click menu.
- `hover()` moves the mouse over an element, for example to show a tooltip or a menu.

**Simple lesson:** `click()`, `dblclick()` and `hover()` cover almost every mouse need. Options change the button and keys.

## Text and Keyboard Actions

### fill() and clear()

- `fill('text')` clears the field and types the whole text at once. It is fast and the best default.
- `clear()` empties the field.

### pressSequentially()

- `pressSequentially('Ko')` types one key at a time, like a person.
- Use it when the page reacts to each key, for example a search box that suggests results.
- Add `{ delay: 100 }` to wait 100 ms between keys.

### press()

- `press('Enter')` presses one key or a combination: `'Tab'`, `'Escape'`, `'ArrowDown'`, `'Control+A'`, `'Shift+Tab'`.
- `ControlOrMeta` means Control on Windows and Linux, and Command on macOS.

### focus() and blur()

- `focus()` puts the cursor in a field. `blur()` takes it out.
- Some forms check a field only when you leave it, on **blur**.

`fill()` puts in the whole text in one go. Pages that listen for each key press (`keydown`, `keyup`) may not react to it. Use `pressSequentially()` for those.

**Simple lesson:** `fill()` by default, `pressSequentially()` when every key matters, `press()` for special keys.

## Form Actions

### check(), uncheck() and setChecked()

- `check()` ticks a checkbox. If it is already ticked, nothing happens.
- `uncheck()` unticks it. `setChecked(true)` or `setChecked(false)` does either, from a variable.
- After the click, Playwright checks that the box really changed. If not, the action fails.

### selectOption()

- `selectOption('returns')` picks an option in a `<select>` menu.
- You can pass the value, the visible label, `{ label: 'Returns' }` or `{ index: 2 }`.
- For multi-select menus, pass a list: `selectOption(['red', 'blue'])`.

### Radio Buttons

- A radio button is ticked with `check()`. Ticking one unticks the others in the group.
- You cannot `uncheck()` a radio button. Playwright throws an error, because a user cannot do it either.

**Simple lesson:** `check()` for boxes and radios, `selectOption()` for menus.

## Scrolling

- Before an action, Playwright scrolls the element into view by itself.
- `scrollIntoViewIfNeeded()` scrolls without doing anything else, for example before a screenshot.
- To scroll the page with the mouse wheel, use `page.mouse.wheel(0, 500)`. **Handling Complex Page Elements** shows it.

```ts
await page.getByRole('button', { name: 'Back to top' }).scrollIntoViewIfNeeded();
await page.getByRole('button', { name: 'Back to top' }).click(); // also scrolls by itself
```

**Simple lesson:** Playwright scrolls for you. You rarely need to scroll yourself.

## Auto-Waiting

### What Auto-Waiting Is

- Web pages change all the time. Buttons appear late, menus slide in, forms enable their buttons.
- Before each action, Playwright waits until the element is ready for **that** action.
- It checks again and again until the checks pass or the time runs out.

### Actionability Checks

<figure className="plw-fig">
<svg viewBox="0 0 640 230" role="img" aria-labelledby="fig-plw-actionable" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-plw-actionable">Animation: a button goes through four states: hidden, sliding in, disabled, and covered by a banner. Each time a check fails, Playwright waits and retries. When all checks pass, it clicks</title>
  <rect x="15" y="20" width="270" height="190" rx="12" style={{ fill: "var(--plw-win)", stroke: "var(--plw-slate)" }} />
  <rect x="85" y="110" width="130" height="40" rx="8" style={{ fill: "var(--plw-blue)" }} opacity="0">
    <animate attributeName="opacity" values="0;0;1;1;1;1;1" keyTimes="0;0.15;0.25;0.4;0.6;0.85;1" dur="10s" repeatCount="indefinite" />
    <animate attributeName="x" values="250;250;85;85;85;85;85" keyTimes="0;0.15;0.3;0.4;0.6;0.85;1" dur="10s" repeatCount="indefinite" />
    <animate attributeName="fill-opacity" values="1;1;1;0.4;1;1;1" keyTimes="0;0.15;0.35;0.4;0.55;0.85;1" dur="10s" repeatCount="indefinite" />
  </rect>
  <rect x="15" y="150" width="270" height="60" rx="0" style={{ fill: "var(--plw-slate)" }} opacity="0">
    <animate attributeName="opacity" values="0;0;0.85;0.85;0;0" keyTimes="0;0.6;0.62;0.78;0.8;1" dur="10s" repeatCount="indefinite" />
    <animate attributeName="y" values="150;150;110;110;110;110" keyTimes="0;0.6;0.62;0.78;0.8;1" dur="10s" repeatCount="indefinite" />
  </rect>
  <g style={{ fontFamily: "var(--sans)", fontSize: 14 }}>
    <text x="320" y="50" style={{ fill: "var(--ink)", fontWeight: 700 }}>locator.click() checks:</text>
    <text x="330" y="82" style={{ fill: "var(--plw-red)" }} opacity="0">✘ not visible, retrying<animate attributeName="opacity" values="0;1;1;0;0" keyTimes="0;0.02;0.14;0.15;1" dur="10s" repeatCount="indefinite" /></text>
    <text x="330" y="82" style={{ fill: "var(--plw-red)" }} opacity="0">✘ not stable (moving), retrying<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.16;0.18;0.29;0.3;1" dur="10s" repeatCount="indefinite" /></text>
    <text x="330" y="82" style={{ fill: "var(--plw-red)" }} opacity="0">✘ not enabled, retrying<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.36;0.38;0.54;0.55;1" dur="10s" repeatCount="indefinite" /></text>
    <text x="330" y="82" style={{ fill: "var(--plw-red)" }} opacity="0">✘ a banner receives the click, retrying<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.61;0.63;0.78;0.8;1" dur="10s" repeatCount="indefinite" /></text>
    <text x="330" y="82" style={{ fill: "var(--plw-green)", fontWeight: 700 }} opacity="0">✓ visible, stable, enabled, not covered<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.85;0.86;0.98;1" dur="10s" repeatCount="indefinite" /></text>
    <text x="330" y="112" style={{ fill: "var(--plw-orange)", fontWeight: 700 }} opacity="0">→ click<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.88;0.9;0.98;1" dur="10s" repeatCount="indefinite" /></text>
  </g>
  <text x="330" y="170" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontSize: 12 }}>Each check runs again and again,</text>
  <text x="330" y="188" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontSize: 12 }}>until all pass or time runs out.</text>
</svg>
<figcaption>Hidden, moving, disabled, covered: each problem makes Playwright wait, not fail.</figcaption>
</figure>

| Check | Means | Needed by |
|---|---|---|
| **Visible** | The element has a size and is not hidden | click, hover, fill, check, selectOption |
| **Stable** | It is not moving (no animation in progress) | click, hover, check |
| **Enabled** | It is not `disabled` | click, fill, check, selectOption |
| **Editable** | It is enabled and not read-only | fill, clear |
| **Receives events** | Nothing covers it, so the click lands on it | click, hover, check |
| **Not inert** | It is not blocked by an open modal dialog | click, hover, check |

### How Actions Retry

- If a check fails, Playwright waits a moment and checks again.
- It stops when every check passes, or when the **action timeout** ends.
- The error then lists what it waited for in the **Call log**.

### locator.waitFor() and Its States

- Sometimes you need to wait for an element without acting on it. `waitFor()` does that.

| State | Waits until the element is |
|---|---|
| `'visible'` (default) | on the page and visible |
| `'hidden'` | gone or hidden |
| `'attached'` | in the page, visible or not |
| `'detached'` | removed from the page |

- Most of the time, a web-first assertion is better: `await expect(locator).toBeVisible()`.

### Why Fixed Sleeps Are Discouraged

- `page.waitForTimeout(3000)` always waits 3 seconds. It does not look at the page.
- On a fast day, it wastes time. On a slow day, it is not long enough, and the test fails.
- Run this test. It waits half a second, then reads the status once. The order needs 1.2 seconds.

- The fix: `await expect(page.getByRole('status')).toHaveText('Order confirmed')`. It waits as long as needed, and no longer.

Use `page.waitForTimeout()` only while you debug, never in a finished test. The Playwright docs say the same.

### force and trial Options

- `click({ force: true })` skips the actionability checks and clicks at once.
- A forced click still lands on whatever is on top. If a banner covers the button, the banner gets the click.
- Use `force` only when you know a check is wrong for your page. It hides real problems.
- `click({ trial: true })` runs the checks but does not click. It tells you if the click **would** work.

When an action times out, read the **Call log** in the error. Lines such as "element is not enabled" or "intercepts pointer events" tell you exactly what blocked it.

**Simple lesson:** Playwright waits until an element is visible, stable, enabled and not covered. Do not add sleeps.

## Try It in Your Project

- These steps take the lesson from the lab to your own app.
- Search your tests for `waitForTimeout` and replace each one with a web-first assertion.
- Test one form with keyboard only: `press('Tab')`, `press('Enter')`.
- Find one slow element and see which actionability check Playwright waits for in the call log.

**Simple lesson:** use one idea from this lesson on your own app this week.

## TL;DR

- `goto()`, `reload()`, `goBack()`, `goForward()` and `waitForURL()` move between pages.
- `click()`, `dblclick()`, `hover()` and `click({ button: 'right' })` cover mouse actions.
- `fill()` types fast, `pressSequentially()` types key by key, `press()` presses special keys.
- `check()`, `uncheck()`, `setChecked()` and `selectOption()` fill in forms.
- Every action auto-waits for actionability checks. Never use fixed sleeps in a finished test.

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| `waitForTimeout()` before every step | Slow tests that still fail on slow days | Remove it. Actions and web-first assertions wait |
| `fill()` on a box that reacts to each key | No suggestions appear | Use `pressSequentially()` |
| `click({ force: true })` to get past an error | The test hides a real bug, or clicks a banner | Find out what blocks the click in the Call log |
| `click()` on a checkbox, then no check | The box may already have been ticked | Use `check()`. It checks the result for you |
| Reading text right after a click | The page has not updated yet | Use `await expect(locator).toHaveText(...)` |

## Check Yourself

## Exercises

## What to Remember

- Navigation: `goto()`, `waitForURL()`, `goBack()`, `goForward()`, `reload()`.
- `fill()` is the default for typing. `pressSequentially()` is for key-by-key pages.
- `check()` and `selectOption()` fill in forms and verify the result.
- Auto-waiting checks visible, stable, enabled, editable and not covered.
- Replace sleeps with web-first assertions.

Next: **Assertions**
