Wait Steps

Every wait phrasing in the suite lives in this group. All of them are backed by webship-js's Behavior-Based Robotics (BBR) principle: react to the environment, not the clock. A wait step never blocks for a fixed duration - it returns as soon as the page is at the edge of activity, bounded by a budget so a runaway page cannot stall the run. In practice, When I wait 5 seconds means "wait up to five seconds for the page to become quiet," and it typically returns in a fraction of that.

The core probe is smartSettle: "settle" means the DOM is ready, no fetch/XHR request is in flight, no tracked setTimeout callback is pending, and there has been no MutationObserver activity for at least 250 ms. These signals come from a small init script webship-js installs in every browser context (counters such as window.__webshipAjaxCount and window.__webshipPendingTimers). This catches AJAX-driven updates and purely client-side changes - animations, debounced renders, fade-outs - that Playwright's plain networkidle misses.

You need these steps less often than you might think: after every state-changing action (click, press, fill, submit, select, navigate, ...) webship-js silently runs a 1.5-second smart settle, and the web-first assertions auto-retry on their own. Reach for an explicit wait when a page is unusually slow, when you need a targeted edge (a specific selector, text, URL, or element count), or when porting legacy suites that used sleep-style phrasings.

Quick reference

StepWhat it does
When I wait for 1 secondWait up to N seconds for the page to settle; returns early on idle.
When I wait for 1 second for AJAX to finishSame smart settle; legacy phrasing kept for older feature files.
When I wait 1 secondWait up to N seconds for the page to settle (also wait 2s).
When I wait max of 1 secondWait up to a maximum of N seconds for the page to settle.
When I wait 1 minuteWait up to N minutes for the page to settle (also wait 2m).
When I wait max of 1 minuteWait up to a maximum of N minutes for the page to settle.
When I wait until the page is loadedSmart settle with a 10 s budget.
When I wait for AJAX to finishWait for active XHR/fetch requests to complete (10 s budget).
When I wait for the modal to appearWait for a modal dialog to appear or disappear.
When I wait for "#dashboard" to appearWait until a CSS selector becomes visible.
When I wait for "#loading-spinner" to disappearWait until a CSS selector becomes hidden or detached.
When I wait for the text "Dashboard" to appearWait until visible text appears anywhere on the page.
When I wait for the text "Loading…" to disappearWait until visible text is gone from the page.
When I wait until the URL contains "/dashboard"Wait until the current URL contains a fragment.
When I wait until the page title is "Dashboard - MyApp"Wait until document.title equals (is) or contains a value.
When I wait until 5 elements match ".product-card"Wait until exactly N elements match a CSS selector.
When I wait until at least 3 elements match ".item"Wait until at least N elements match a CSS selector.
When I wait until the network is idleWait until the network goes quiet (full smart settle, 10 s budget).
When I wait until the page is interactiveWait for DOMContentLoaded + body attached (lighter than full settle).
When I wait until pending timers settleWait until every tracked setTimeout callback has fired.
Then eventually I should see "Done"Polling text assertion; optional within N seconds budget.

Steps in detail

When I wait for N seconds / I wait N seconds / I wait max of N seconds

All three phrasings run the same bounded smart settle: return as soon as the page is quiet, or after N seconds, whichever comes first. The shorthand form accepts wait 2s, and the pronoun is flexible (I or we, or omitted after And). The minute variants - wait 1 minute, wait max of 2 minutes, wait 2m - multiply the budget accordingly and suit genuinely slow backends. Use these when a real animation or slow operation has a known upper bound; the step still exits early when the page settles sooner.

When I press "Save"
And I wait for 2 seconds
Then I should see "Saved"
When we wait max of 3s

When I wait for N seconds for AJAX to finish

Identical probe to wait for N seconds - kept for phrasing parity with older feature files that named AJAX explicitly. New scenarios can use either form interchangeably.

When I press "Search"
And I wait for 5 seconds for AJAX to finish
Then I should see "Results"

When I wait until the page is loaded / I wait for AJAX to finish / I wait until the network is idle

Pure edge waits with a fixed 10-second budget: they return the moment the page settles. wait until the network is idle also accepts wait until requests are complete and wait until network goes quiet - three equivalent phrasings for the same probe. The loaded phrasing is tolerant too: wait until page loaded works.

When I press "Refresh"
And I wait until requests are complete
Then I should see "Up to date"

When I wait for the modal to appear / to disappear

Waits for a modal dialog to become visible or hidden, using webship-js's shared modal probes (the same detection used by the modal steps). The phrasing is flexible: the and dialog are optional, so wait for modal to appear and wait for the modal dialog to disappear both match. Budget is 10 seconds.

When I press "Delete"
And I wait for the modal to appear
Then I should see "Are you sure?"

When I wait for "<selector>" to appear / to disappear

The canonical "wait until X appears" probe. to appear returns the moment a matching element is actually on screen - display is not none, visibility is not hidden, and it has a non-zero box. to disappear waits for the opposite: hidden, detached, or zero-size. Both take a CSS selector and time out after 10 seconds. The disappear form is the standard way to wait out spinners and skeletons.

When I press "Submit"
And I wait for ".loader" to disappear
And I wait for ".toast-success" to appear
Then I should see "Saved"

When I wait for the text "<text>" to appear / to disappear

Polls document.body.innerText every 100 ms for up to 10 seconds. The match is a case-sensitive substring anywhere on the page, so no selector is needed. Ideal for confirmation messages, and - per the BBR guide - the right tool for backends that push updates over WebSockets, where no fetch/XHR signal exists to settle on.

When I press "Submit"
And I wait for the text "Validating" to disappear
And I wait for the text "Thank you" to appear

When I wait until the URL contains "<fragment>"

Polls window.location.href until it contains the fragment. Works for full redirects, push-state navigations in SPAs, and hash-based routing alike - anything that changes the address bar.

When I follow "Sign in"
And I wait until the URL contains "/auth/callback"

When I wait until the page title is "<value>" / contains "<value>"

Waits on document.title: is requires exact equality, contains a substring. Handy for apps that update the title asynchronously - unread counters, route-driven titles.

When I follow "Inbox"
And I wait until the page title contains "(3 unread)"

When I wait until N elements match "<selector>" / at least N elements match

Polls document.querySelectorAll(selector).length every 100 ms. The exact form waits for the count to equal N - use 0 to wait for elements to vanish. The at least form waits for a lower bound, which suits infinite scroll and pagination where the exact count varies. Both accept singular grammar: 1 element matches.

When I press "Load more"
And I wait until at least 25 elements match ".feed-item"
And I wait until 0 elements match ".loading-skeleton"

When I wait until the page is interactive

Waits only for DOMContentLoaded plus an attached <body> - deliberately lighter than a full smart settle, since it does not wait for late fetch/XHR. Use it on heavy pages when you need to start interacting with the document early.

Given I am on "/spa"
When I wait until the page is interactive
Then "#root" should be attached

When I wait until pending timers settle

Waits until every tracked setTimeout callback has fired, reading the window.__webshipPendingTimers counter installed by the init script. This catches "fade out then display:none" close transitions, debounced renders, and any delayed UI update that makes no network request. Note that setInterval heartbeats are deliberately not tracked, so a polling widget will not block this wait.

When I press "Close"
And I wait until pending timers settle
Then I should not see the modal

Then eventually I should see "<text>" (optionally within N seconds)

A pollable text assertion: retries until the text appears in document.body.innerText or the deadline passes (default 10 seconds). Use sparingly - prefer explicit edge waits like wait for "X" to appear, which document the exact condition being waited on.

When I press "Submit"
Then eventually I should see "Submitted" within 8 seconds

Complete example

Adapted from tests/features/ajax-wait-examples.feature, which runs against the fixture page examples/ajax-wait-examples.html:

Feature: AJAX Wait Examples
  As a tester
  I want to wait for AJAX requests to complete
  So that I can verify content that loads asynchronously

  Background:
    Given I am on "/ajax-wait-examples.html"

  Scenario: Wait for AJAX to finish after search
    When I fill in "laptop" for "#search-input" by attr
    And I press "Search" button
    And I wait for AJAX to finish
    Then I should see "Search Results"
    And I should see "Laptop Pro 15"

  Scenario: Wait for AJAX to finish after submitting form
    When I fill in "John Doe" for "#name-input" by attr
    And I fill in "john@example.com" for "#email-input" by attr
    And I press "Submit" button
    And I wait for AJAX to finish
    Then I should see "Form submitted successfully"
    And I should see "Thank you, John Doe"

Configuration

SettingDefaultMeaning
WEBSHIP_AUTO_SETTLEonSet to off to disable the automatic 1.5 s smart settle that runs after every state-changing step.
Targeted edge waits10 s budget, 100 ms pollingSelector/text/URL/title/count waits time out after 10 seconds; bounded waits use the duration you specify.

For the full explanation of the settle signals and what BBR waits eliminate (sleep-driven testing, networkidle blind spots, race-condition flake), see the BBR Smart Waits guide in the repository docs.

Back to all step groups