Skip to content

Working with SPAs

Single-page applications load content dynamically without full page reloads. Claudezilla provides tools to wait for specific conditions and detect changes in dynamic pages.

firefox_wait_for blocks until a condition is met on the page. It supports three mutually exclusive conditions:

Wait until a CSS element appears in the DOM:

// Wait for a modal to appear
firefox_wait_for({ selector: ".modal-dialog" })
// Wait for results to load
firefox_wait_for({ selector: "#search-results .result-item" })
// Wait for a loading spinner to disappear (negate with :not or check absence after)
firefox_wait_for({ selector: ".content:not(.loading)" })

Wait until specific text appears in document.body.innerText:

// Wait for a success message
firefox_wait_for({ text: "Your order has been placed" })
// Wait for data to render
firefox_wait_for({ text: "Showing 1-10 of" })

Wait until window.location.href matches a glob pattern. Useful for SPA route changes and redirects:

// Wait for navigation to dashboard after login
firefox_type({ selector: "#password", text: "..." })
firefox_click({ selector: "#login-btn" })
firefox_wait_for({ url: "**/dashboard" })
// Wait for a specific route
firefox_wait_for({ url: "**/users/*/profile" })

All wait conditions accept an optional timeout (default: 10000ms):

// Give a slow API response more time
firefox_wait_for({
selector: "#report-table",
timeout: 30000
})

If the timeout is reached, an error is returned rather than silently proceeding.

Use firefox_get_page_state and firefox_diff_page_state together to detect what changed after an action.

// 1. Capture the "before" state
const before = firefox_get_page_state({ tabId: 42 })
// 2. Perform an action
firefox_click({ selector: "#load-more" })
firefox_wait_for({ selector: ".item:nth-child(11)" })
// 3. Capture the "after" state
const after = firefox_get_page_state({ tabId: 42 })
// 4. Diff the two snapshots
firefox_diff_page_state({ before: before, after: after })

The diff returns added, removed, and changed elements across headings, links, buttons, and inputs:

{
"added": {
"links": [
{ "text": "Page 2", "href": "/results?page=2" }
],
"buttons": [
{ "text": "Load More", "selector": "#load-more-2" }
]
},
"removed": {
"buttons": [
{ "text": "Load More", "selector": "#load-more" }
]
},
"changed": {}
}
  • Form submission verification: Diff before/after submitting to confirm success messages appeared
  • Navigation effects: Verify that clicking a link changed the expected page elements
  • Regression detection: Compare page state across test runs to catch missing elements
firefox_create_window({ url: "https://app.example.com/login" })
firefox_wait_for({ selector: "#email" })
firefox_type({ selector: "#email", text: "user@example.com" })
firefox_type({ selector: "#password", text: "..." })
firefox_click({ selector: "button[type=submit]" })
// Wait for the SPA to route to dashboard
firefox_wait_for({ url: "**/dashboard" })
// Load initial content
firefox_create_window({ url: "https://app.example.com/feed" })
firefox_wait_for({ selector: ".feed-item" })
// Scroll to bottom to trigger load
firefox_scroll({ y: 99999 })
// Wait for new items
firefox_wait_for({ selector: ".feed-item:nth-child(21)" })
// Click a tab in the SPA
firefox_click({ selector: "[data-tab='settings']" })
// Wait for the tab panel content to appear
firefox_wait_for({ selector: "#settings-panel .form-group" })
const before = firefox_get_page_state({ tabId: 42 })
firefox_click({ selector: "#save-btn" })
// Wait for success toast or updated content
firefox_wait_for({ text: "Changes saved" })
const after = firefox_get_page_state({ tabId: 42 })
firefox_diff_page_state({ before, after })
  • Always wait_for after actions that trigger async updates. SPAs do not trigger page load events.
  • Prefer firefox_get_page_state over screenshots when you need to understand page structure — it is faster and does not require the screenshot mutex.
  • Use URL waiting for route changes, selector waiting for DOM changes, and text waiting for content that renders asynchronously.
  • Set appropriate timeouts for slow APIs. The default 10s is fine for most pages but may not be enough for complex reports or data-heavy views.