/e2e-testing
Test web applications end-to-end by simulating user interactions and verifying expected outcomes. Trigger when the user asks to: test a web app, verify a user flow, run end-to-end tests, QA a feature, check that a page works correctly, validate user journeys, or test a
$ npx -y skills add billy-enrizky/openbrowser-ai --skill e2e-testing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/e2e-testing
Context preview
The summary Claude sees to decide when to auto-load this skill.
Test web applications end-to-end by simulating user interactions and verifying expected outcomes. Trigger when the user asks to: test a web app, verify a user flow, run end-to-end tests, QA a feature, check that a page works correctly, validate user journeys, or test a
SKILL.md
e2e-testing.SKILL.mdname: e2e-testing
description: |
Test web applications end-to-end by simulating user interactions and verifying expected outcomes.
Trigger when the user asks to: test a web app, verify a user flow, run end-to-end tests,
QA a feature, check that a page works correctly, validate user journeys, or test a deployment.
allowed-tools: Bash(openbrowser-ai:*) Bash(curl:*) Bash(uv:*) Bash(irm:*) Read Write
End-to-End Testing
Simulate real user interactions and verify web application behavior using Python code execution. Covers navigation, form interaction, content assertions, and multi-page flows.
All code runs via `openbrowser-ai -c`. The daemon starts automatically and persists variables across calls. All browser functions are async -- use `await`.
The CLI daemon also persists cookies and login state in `~/.config/openbrowser/profiles/daemon/storage_state.json`, so authenticated sessions can be reused across later runs.
Setup
Before running, verify openbrowser-ai is installed:
openbrowser-ai --help
If not found, install:
# macOS/Linux
curl -fsSL https://raw.githubusercontent.com/billy-enrizky/openbrowser-ai/main/install.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/billy-enrizky/openbrowser-ai/main/install.ps1 | iex
Workflow
Step 1 -- Navigate and verify page load
openbrowser-ai -c - <<'EOF'
await navigate("https://staging.example.com")
state = await browser.get_browser_state_summary()
assert "example" in state.url.lower(), f"Unexpected URL: {state.url}"
assert state.title, "Page title is empty"
print(f"Page loaded: {state.title} ({state.url})")
EOFStep 2 -- Content assertions
openbrowser-ai -c - <<'EOF'
# Check for expected text using JS
has_welcome = await evaluate("""
(function(){ return !!document.body.textContent.match(/Welcome to Example App/i) })()
""")
assert has_welcome, "Welcome message not found"
# Check specific element content
h1_text = await evaluate("document.querySelector('h1')?.textContent?.trim()")
assert h1_text == "Example App", f'Expected "Example App", got "{h1_text}"'
# Check no error messages
error_count = await evaluate("document.querySelectorAll('.error-message').length")
assert error_count == 0, f"Found {error_count} error messages on page"
print("All content assertions passed")
EOFStep 3 -- Test user interactions (login flow)
openbrowser-ai -c - <<'EOF'
# Get form fields
state = await browser.get_browser_state_summary()
for idx, el in state.dom_state.selector_map.items():
if el.attributes.get("type") in ("email", "text", "password") or el.tag_name == "button":
etype = el.attributes.get("type", "")
placeholder = el.attributes.get("placeholder", "")
print(f"[{idx}] <{el.tag_name}> type={etype} placeholder=\"{placeholder}\"")
# Fill and submit
await input_text(index=3, text="test@example.com")
await input_text(index=4, text="test-password")
await click(index=5) # Login button
await wait(2)
# Assert logged in
state = await browser.get_browser_state_summary()
assert "dashboard" in state.url.lower() or "welcome" in state.title.lower(), \
f"Login may have failed. URL: {state.url}, Title: {state.title}"
print("Login test passed")
EOFStep 4 -- Test navigation flows
openbrowser-ai -c - <<'EOF'
# Click settings link
state = await browser.get_browser_state_summary()
for idx, el in state.dom_state.selector_map.items():
if "settings" in el.get_all_children_text(max_depth=1).lower():
await click(index=idx)
break
await wait(1)
# Assert URL changed
path = await evaluate("window.location.pathname")
assert "/settings" in path, f"Expected /settings path, got {path}"
# Test back button
await go_back()
await wait(1)
state = await browser.get_browser_state_summary()
print(f"After back: {state.url}")
EOFStep 5 -- Test error handling
openbrowser-ai -c - <<'EOF'
# Submit form with invalid data
await input_text(index=3, text="not-an-email")
await click(index=5)
await wait(1)
# Assert validation errors appear
errors = await evaluate("""
(function(){
const errs = document.querySelectorAll(".error, .invalid-feedback, [aria-invalid=\"true\"]");
return Array.from(errs).map(e => e.textContent.trim());
})()
""")
assert len(errors) > 0, "Expected validation errors but found none"
print(f"Validation errors shown: {errors}")
# Assert page did not navigate
path = await evaluate("window.location.pathname")
print(f"Still on: {path}")
EOFStep 6 -- Test responsive behavior
openbrowser-ai -c - <<'EOF'
viewport = await evaluate("""
(function(){
return {
width: window.innerWidth,
height: window.innerHeight
}
})()
""")
vw = viewport["width"]
vh = viewport["height"]
print(f"Viewport: {vw}x{vh}")
# Check mobile menu visibility
mobile_display = await evaluate("""
(function(){
const el = document.querySelector(".mobile-menu");
return el ? window.getComputedStyle(el).display : "not found";
})()
""")
print(f"Mobile menu display: {mobile_display}")
EOFStep 7 -- Test multi-page flows
openbrowser-ai -c - <<'EOF'
test_results = []
# Cart page
await navigate("https://staging.example.com/cart")
await wait(1)
cart_title = await evaluate("document.querySelector('h1')?.textContent?.trim()")
test_results.append({"test": "cart_page_loads", "passed": cart_title is not None, "detail": cart_title})
# Checkout
cart_count = await evaluate("JSON.parse(localStorage.getItem('cart'))?.items?.length || 0")
test_results.append({"test": "cart_has_items", "passed": cart_count > 0, "detail": f"{cart_count} items"})
# Print results
import json
passed = sum(1 for t in test_results if t["passed"])
total = len(test_results)
print(f"\nResults: {passed}/{total} passed")
print(json.dumps(test_results, indent=2))
EOFTips
- Code is piped via stdin using heredoc (`-c - <<'EOF'`), so all Python syntax works without shell escaping issues.
- Use Python `as
Read more
name: e2e-testing description: | Test web applications end-to-end by simulating user interactions and verifying expected outcomes. Trigger when the user asks to: test a web app, verify a user flow, run end-to-end tests, QA a feature, check that a page works correctly, validate user journeys, or test a deployment. allowed-tools: Bash(openbrowser-ai:*) Bash(curl:*) Bash(uv:*) Bash(irm:*) Read Write
End-to-End Testing
Simulate real user interactions and verify web application behavior using Python code execution. Covers navigation, form interaction, content assertions, and multi-page flows.
All code runs via `openbrowser-ai -c`. The daemon starts automatically and persists variables across calls. All browser functions are async -- use `await`.
The CLI daemon also persists cookies and login state in `~/.config/openbrowser/profiles/daemon/storage_state.json`, so authenticated sessions can be reused across later runs.
Setup
Before running, verify openbrowser-ai is installed:
openbrowser-ai --help
If not found, install:
# macOS/Linux curl -fsSL https://raw.githubusercontent.com/billy-enrizky/openbrowser-ai/main/install.sh | sh # Windows (PowerShell) irm https://raw.githubusercontent.com/billy-enrizky/openbrowser-ai/main/install.ps1 | iex
Workflow
Step 1 -- Navigate and verify page load
openbrowser-ai -c - <<'EOF'
await navigate("https://staging.example.com")
state = await browser.get_browser_state_summary()
assert "example" in state.url.lower(), f"Unexpected URL: {state.url}"
assert state.title, "Page title is empty"
print(f"Page loaded: {state.title} ({state.url})")
EOFStep 2 -- Content assertions
openbrowser-ai -c - <<'EOF'
# Check for expected text using JS
has_welcome = await evaluate("""
(function(){ return !!document.body.textContent.match(/Welcome to Example App/i) })()
""")
assert has_welcome, "Welcome message not found"
# Check specific element content
h1_text = await evaluate("document.querySelector('h1')?.textContent?.trim()")
assert h1_text == "Example App", f'Expected "Example App", got "{h1_text}"'
# Check no error messages
error_count = await evaluate("document.querySelectorAll('.error-message').length")
assert error_count == 0, f"Found {error_count} error messages on page"
print("All content assertions passed")
EOFStep 3 -- Test user interactions (login flow)
openbrowser-ai -c - <<'EOF'
# Get form fields
state = await browser.get_browser_state_summary()
for idx, el in state.dom_state.selector_map.items():
if el.attributes.get("type") in ("email", "text", "password") or el.tag_name == "button":
etype = el.attributes.get("type", "")
placeholder = el.attributes.get("placeholder", "")
print(f"[{idx}] <{el.tag_name}> type={etype} placeholder=\"{placeholder}\"")
# Fill and submit
await input_text(index=3, text="test@example.com")
await input_text(index=4, text="test-password")
await click(index=5) # Login button
await wait(2)
# Assert logged in
state = await browser.get_browser_state_summary()
assert "dashboard" in state.url.lower() or "welcome" in state.title.lower(), \
f"Login may have failed. URL: {state.url}, Title: {state.title}"
print("Login test passed")
EOFStep 4 -- Test navigation flows
openbrowser-ai -c - <<'EOF'
# Click settings link
state = await browser.get_browser_state_summary()
for idx, el in state.dom_state.selector_map.items():
if "settings" in el.get_all_children_text(max_depth=1).lower():
await click(index=idx)
break
await wait(1)
# Assert URL changed
path = await evaluate("window.location.pathname")
assert "/settings" in path, f"Expected /settings path, got {path}"
# Test back button
await go_back()
await wait(1)
state = await browser.get_browser_state_summary()
print(f"After back: {state.url}")
EOFStep 5 -- Test error handling
openbrowser-ai -c - <<'EOF'
# Submit form with invalid data
await input_text(index=3, text="not-an-email")
await click(index=5)
await wait(1)
# Assert validation errors appear
errors = await evaluate("""
(function(){
const errs = document.querySelectorAll(".error, .invalid-feedback, [aria-invalid=\"true\"]");
return Array.from(errs).map(e => e.textContent.trim());
})()
""")
assert len(errors) > 0, "Expected validation errors but found none"
print(f"Validation errors shown: {errors}")
# Assert page did not navigate
path = await evaluate("window.location.pathname")
print(f"Still on: {path}")
EOFStep 6 -- Test responsive behavior
openbrowser-ai -c - <<'EOF'
viewport = await evaluate("""
(function(){
return {
width: window.innerWidth,
height: window.innerHeight
}
})()
""")
vw = viewport["width"]
vh = viewport["height"]
print(f"Viewport: {vw}x{vh}")
# Check mobile menu visibility
mobile_display = await evaluate("""
(function(){
const el = document.querySelector(".mobile-menu");
return el ? window.getComputedStyle(el).display : "not found";
})()
""")
print(f"Mobile menu display: {mobile_display}")
EOFStep 7 -- Test multi-page flows
openbrowser-ai -c - <<'EOF'
test_results = []
# Cart page
await navigate("https://staging.example.com/cart")
await wait(1)
cart_title = await evaluate("document.querySelector('h1')?.textContent?.trim()")
test_results.append({"test": "cart_page_loads", "passed": cart_title is not None, "detail": cart_title})
# Checkout
cart_count = await evaluate("JSON.parse(localStorage.getItem('cart'))?.items?.length || 0")
test_results.append({"test": "cart_has_items", "passed": cart_count > 0, "detail": f"{cart_count} items"})
# Print results
import json
passed = sum(1 for t in test_results if t["passed"])
total = len(test_results)
print(f"\nResults: {passed}/{total} passed")
print(json.dumps(test_results, indent=2))
EOFTips
- Code is piped via stdin using heredoc (`-c - <<'EOF'`), so all Python syntax works without shell escaping issues.
- Use Python `as
OpenBrowser is a framework for intelligent browser automation. It combines direct CDP communication with a CodeAgent architecture, where the LLM writes Python code executed in a persistent namespace, to navigate, interact with, and extract information from web pages autonomously.
Repo: billy-enrizky/openbrowser-ai
Other skills on openbrowser-ai.
- /accessibility-audit
Audit web pages for accessibility issues, WCAG compliance, and screen reader compatibility. Trigger when the user asks to: check accessibility, run an a11y audit, test WCAG compliance, check screen reader support, audit ARIA attributes, verify keyboard navigation, find
Open skill - /deep-research
Conduct deep web research using the openbrowser-ai agent: decompose a query, investigate sub-questions across multiple sources, and produce a cited markdown report plus structured JSON under local_docs/research/. Trigger when the user asks to: research a topic, do a deep dive,
Open skill - /file-download
Download files from websites, save PDFs, and read downloaded content. Trigger when the user asks to: download a file, save a PDF, export a document, fetch a file from a URL, grab a report, download and read a PDF, or save page content as a file.
Open skill - /form-filling
Fill out web forms, submit data, and handle login or registration flows. Trigger when the user asks to: fill a form, submit data on a website, log in to a site, register an account, complete a checkout, enter information into fields, or automate form submission.
Open skill - /page-analysis
Analyze web page content, structure, and layout to understand what a page contains and how it is organized. Trigger when the user asks to: analyze a page, understand page structure, inspect a website, summarize page content, examine page layout, review a web page, or describe
Open skill - /web-scraping
Extract structured data from websites, scrape page content, and collect information across multiple pages. Trigger when the user asks to: extract data from a website, scrape a page, collect information from URLs, pull content from web pages, gather data across multiple pages, or
Open skill

