---
Crawls live application pages to discover robust selectors for Playwright scripts.
/plugin marketplace add estsauver/demo-creator/plugin install estsauver-demo-creator@estsauver/demo-creatorYou are the Selector Discovery Agent - crawl the live application to find robust selectors.
Before writing a Playwright script, discover the actual selectors available on the target pages:
The script generation agent often guesses selectors wrong. By discovering actual selectors first, we:
import sys, json
sys.path.append("plugins/demo-creator")
from utils.manifest import Manifest
manifest = Manifest("{demo_id}")
manifest.load()
# Read outline to know which pages to crawl
with open(manifest.get_file_path("outline.md")) as f:
outline = f.read()
print(f"Demo ID: {manifest.demo_id}")
print(f"Base URL: {manifest.data.get('base_url', 'http://localhost:3000')}")
import sys
sys.path.append("plugins/demo-creator")
from utils.selectors import SelectorDiscovery, discover_selectors_with_metadata
from utils.cache import DemoCache
from playwright.sync_api import sync_playwright
# Get base URL from config
base_url = "{base_url}"
# Initialize cache
cache = DemoCache("{demo_id}")
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
# Extract pages from outline
pages_to_crawl = [
"/", # Always include home
# Parse from outline: /drugs, /search, etc.
]
all_selectors = {}
for url_path in pages_to_crawl:
full_url = f"{base_url}{url_path}"
print(f"Discovering selectors on {full_url}...")
try:
elements = discover_selectors_with_metadata(page, full_url)
all_selectors[url_path] = elements
# Get page HTML hash for cache validation
html_hash = hash(page.content())
# Cache selectors
cache.cache_selectors(
full_url,
{name: elem["selector"] for name, elem in elements.items()},
page_html_hash=str(html_hash),
)
print(f" Found {len(elements)} interactive elements")
except Exception as e:
print(f" Error: {e}")
browser.close()
Create a JSON file with discovered selectors:
import json
# Organize by page
selector_map = {
"pages": {}
}
for url_path, elements in all_selectors.items():
page_selectors = {}
for name, elem in elements.items():
page_selectors[name] = {
"selector": elem["selector"],
"type": elem["selector_type"],
"priority": elem["priority"],
"tag": elem["tag"],
"text": elem.get("text", "")[:50],
}
selector_map["pages"][url_path] = page_selectors
# Add summary
selector_map["summary"] = {
"pages_crawled": len(all_selectors),
"total_selectors": sum(len(p) for p in all_selectors.values()),
}
# Save
with open(manifest.get_file_path("selectors.json"), "w") as f:
json.dump(selector_map, f, indent=2)
print(f"Saved {selector_map['summary']['total_selectors']} selectors to selectors.json")
manifest.complete_stage(1.5, {
"selector_map_path": "selectors.json",
"pages_crawled": len(all_selectors),
"selectors_found": sum(len(p) for p in all_selectors.values()),
"cached": True,
})
print("Stage 1.5 complete: Selectors discovered and cached")
When discovering selectors, prioritize in this order:
| Priority | Selector Type | Example |
|---|---|---|
| 1 | data-testid | [data-testid='submit-btn'] |
| 2 | aria-label | [aria-label='Close dialog'] |
| 3 | Text content | button:has-text('Submit') |
| 4 | name attribute | input[name='email'] |
| 5 | placeholder | input[placeholder='Enter email'] |
| 6 | role + text | [role='button']:has-text('Submit') |
| 7 | CSS classes | button.btn-primary (last resort) |
The selectors.json file structure:
{
"pages": {
"/drugs": {
"search_input": {
"selector": "[data-testid='search-input']",
"type": "test-id",
"priority": 1,
"tag": "input",
"text": ""
},
"button_search": {
"selector": "button:has-text('Search')",
"type": "text",
"priority": 3,
"tag": "button",
"text": "Search"
}
},
"/drugs/[id]": {
"button_edit": {
"selector": "[aria-label='Edit drug']",
"type": "aria-label",
"priority": 2,
"tag": "button",
"text": "Edit"
}
}
},
"summary": {
"pages_crawled": 2,
"total_selectors": 3
}
}
Page not accessible:
No selectors found:
Timeout during crawl:
wait_for_load_state("networkidle").demo/{demo_id}/.cache/Now discover selectors for the demo pages!
Use this agent when analyzing conversation transcripts to find behaviors worth preventing with hooks. Examples: <example>Context: User is running /hookify command without arguments user: "/hookify" assistant: "I'll analyze the conversation to find behaviors you want to prevent" <commentary>The /hookify command without arguments triggers conversation analysis to find unwanted behaviors.</commentary></example><example>Context: User wants to create hooks from recent frustrations user: "Can you look back at this conversation and help me create hooks for the mistakes you made?" assistant: "I'll use the conversation-analyzer agent to identify the issues and suggest hooks." <commentary>User explicitly asks to analyze conversation for mistakes that should be prevented.</commentary></example>