Home / Skills / Developer Productivity / Web Application Testing
Developer Productivity

Web Application Testing

Anthropic, PBCApache-2.0Skill language: English
License verifiedIncludes scripts
DemoReal run · claude-sonnet-5-5 · October 2026
I have a local React application already running at http://localhost:5173. Create a Python Playwright smoke-test script that first waits for the dynamic page to become idle, then discovers the visible buttons, clicks the button named "Save profile", captures browser console messages, and saves a full-page screenshot. Explain any assumptions about selectors. Use the webapp-testing skill.
Skill: webapp-testing

I couldn't save the script to disk because the Write tool is disabled in this session. The full script is below, so you can save it as smoke_test.py yourself. I haven't run it.

import sys
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout

URL = "http://localhost:5173"
BUTTON_NAME = "Save profile"
SCREENSHOT_PATH = "smoke_test.png"

console_messages = []


def main() -> int:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()

        # Register listeners before navigation so early messages are captured
        page.on("console", lambda msg: console_messages.append((msg.type, msg.text)))
        page.on("pageerror", lambda err: console_messages.append(("pageerror", str(err))))

        exit_code = 0
        try:
            page.goto(URL)
            # Critical for dynamic apps: wait for JS to render and network to settle
            page.wait_for_load_state("networkidle")

            # Discover visible buttons
            buttons = [b for b in page.get_by_role("button").all() if b.is_visible()]
            print(f"Found {len(buttons)} visible button(s):")
            for i, b in enumerate(buttons):
                label = (b.inner_text() or b.get_attribute("aria-label") or "").strip()
                print(f"  [{i}] {label!r}")

            # Click by accessible name
            target = page.get_by_role("button", name=BUTTON_NAME, exact=True)
            target.wait_for(state="visible", timeout=5000)
            target.click()
            page.wait_for_load_state("networkidle")
            print(f"Clicked button {BUTTON_NAME!r}")
        except PlaywrightTimeout as e:
            print(f"FAIL: button {BUTTON_NAME!r} not found/visible: {e}")
            exit_code = 1
        finally:
            page.screenshot(path=SCREENSHOT_PATH, full_page=True)
            print(f"Screenshot saved to {SCREENSHOT_PATH}")

            print(f"\nConsole messages ({len(console_messages)}):")
            for kind, text in console_messages:
                print(f"  [{kind}] {text}")
            if any(k in ("error", "pageerror") for k, _ in console_messages):
                print("WARNING: console errors detected")
                exit_code = exit_code or 1

            browser.close()
        return exit_code


if __name__ == "__main__":
    sys.exit(main())

Run it with python smoke_test.py. Your server is already up, so you don't need with_server.py. If you haven't installed Playwright yet, run pip install playwright && playwright install chromium first.

What it does, in order

  1. It registers the console and pageerror listeners before goto, so messages logged during the initial render are captured.
  2. It waits for networkidle so the dynamic page has finished rendering before it inspects anything.
  3. It lists every visible button, using get_by_role("button") plus an is_visible() filter.
  4. It clicks the button named "Save profile", waits for the network to settle again, and prints the captured console messages.
  5. It saves a full-page screenshot to smoke_test.png. This happens in finally, so you get a screenshot even if the click fails.

Assumptions about selectors

  • Role and name: I used get_by_role("button", name="Save profile", exact=True). This assumes the control is a real <button>, <input type="submit">, or an element with role="button". Its accessible name must be exactly "Save profile", whether from its text, aria-label, or an associated label. It also keeps working if CSS classes change.
  • A <div onClick> won't match: If "Save profile" is a styled <div> without role="button", the lookup will time out. In that case, switch to page.get_by_text("Save profile") or add a data-testid and use get_by_test_id.
  • Exact match: exact=True is a case-sensitive, full-string match. Remove it if the label might change, for example to "Save profile…" or "Saving…".
  • One match only: Playwright's strict mode raises an error if more than one button matches. If the page has duplicates, such as one per tab, scope the lookup, for example page.locator("form").get_by_role(...).
  • Idle waiting: networkidle can hang if the app polls, uses websockets, or runs HMR traffic. If that happens, wait for a specific element instead of network idle.
  • Failure condition: The script exits non-zero if the button isn't found or if any console error or uncaught page error appears.

Real run recorded with claude-code / claude-sonnet-5-5. Output is shown verbatim, unmodified.

What it does

Guides agents to test local web applications with native Python Playwright scripts.

How it works

  1. Checks whether the application is static or dynamic.
  2. Waits for a dynamic page to reach an idle state before inspecting its rendered DOM.
  3. Uses visible selectors, screenshots, and browser logs to automate and diagnose UI behavior.

Good for

Smoke tests, UI regression investigation, selector discovery, screenshots, and console-log capture.

Notes & risks

Includes Python examples and a server-lifecycle helper. It can open local URLs, launch browser automation, write screenshots or logs, and start shell commands supplied as local development servers. It does not include credentials; review the commands and paths before running them.