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
| Step | What it does |
|---|---|
When I wait for 1 second | Wait up to N seconds for the page to settle; returns early on idle. |
When I wait for 1 second for AJAX to finish | Same smart settle; legacy phrasing kept for older feature files. |
When I wait 1 second | Wait up to N seconds for the page to settle (also wait 2s). |
When I wait max of 1 second | Wait up to a maximum of N seconds for the page to settle. |
When I wait 1 minute | Wait up to N minutes for the page to settle (also wait 2m). |
When I wait max of 1 minute | Wait up to a maximum of N minutes for the page to settle. |
When I wait until the page is loaded | Smart settle with a 10 s budget. |
When I wait for AJAX to finish | Wait for active XHR/fetch requests to complete (10 s budget). |
When I wait for the modal to appear | Wait for a modal dialog to appear or disappear. |
When I wait for "#dashboard" to appear | Wait until a CSS selector becomes visible. |
When I wait for "#loading-spinner" to disappear | Wait until a CSS selector becomes hidden or detached. |
When I wait for the text "Dashboard" to appear | Wait until visible text appears anywhere on the page. |
When I wait for the text "Loading…" to disappear | Wait 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 idle | Wait until the network goes quiet (full smart settle, 10 s budget). |
When I wait until the page is interactive | Wait for DOMContentLoaded + body attached (lighter than full settle). |
When I wait until pending timers settle | Wait 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
| Setting | Default | Meaning |
|---|---|---|
WEBSHIP_AUTO_SETTLE | on | Set to off to disable the automatic 1.5 s smart settle that runs after every state-changing step. |
| Targeted edge waits | 10 s budget, 100 ms polling | Selector/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.