diff options
Diffstat (limited to 'playwright-py')
| -rw-r--r-- | playwright-py/SKILL.md | 15 | ||||
| -rw-r--r-- | playwright-py/examples/broken_links.py | 6 | ||||
| -rw-r--r-- | playwright-py/examples/login_flow.py | 2 | ||||
| -rw-r--r-- | playwright-py/examples/responsive_sweep.py | 2 |
4 files changed, 13 insertions, 12 deletions
diff --git a/playwright-py/SKILL.md b/playwright-py/SKILL.md index 0ed912b..54e1cb7 100644 --- a/playwright-py/SKILL.md +++ b/playwright-py/SKILL.md @@ -26,7 +26,8 @@ User task → Is it static HTML? │ Then use the helper + write simplified Playwright script │ └─ Yes → Reconnaissance-then-action: - 1. Navigate and wait for networkidle + 1. Navigate and wait for a visible app landmark + (expect(page.get_by_role('main')).to_be_visible()) 2. Take screenshot or inspect DOM 3. Identify selectors from rendered state 4. Execute actions with discovered selectors @@ -51,13 +52,13 @@ python scripts/with_server.py \ To create an automation script, include only Playwright logic (servers are managed automatically): ```python -from playwright.sync_api import sync_playwright +from playwright.sync_api import sync_playwright, expect with sync_playwright() as p: browser = p.chromium.launch(headless=True) # headless for CI/pytest; headless=False for interactive debugging page = browser.new_page() page.goto('http://localhost:5173') # Server already running and ready - page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute + expect(page.get_by_role('main')).to_be_visible() # wait for a visible app landmark, not network quiet # ... your automation logic browser.close() ``` @@ -77,16 +78,16 @@ with sync_playwright() as p: ## Common Pitfall -❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps -✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection +**Don't** inspect the DOM before the app has rendered on a dynamic page — you get stale content or an empty skeleton. +**Do** wait for a visible, app-specific landmark before inspecting: `expect(page.get_by_role('main')).to_be_visible()` or `page.get_by_text('Dashboard').wait_for()`. These auto-wait for the element to appear, which is what "ready" means. Avoid `page.wait_for_load_state('networkidle')` as the readiness check — Playwright discourages it, since a page can be interactive long before the network quiets (or never quiet at all, with polling or analytics). ## Best Practices - **Use bundled scripts as black boxes** - To accomplish a task, consider whether one of the scripts available in `scripts/` can help. These scripts handle common, complex workflows reliably without cluttering the context window. Use `--help` to see usage, then invoke directly. - Use `sync_playwright()` for synchronous scripts - Always close the browser when done -- Use descriptive selectors: `text=`, `role=`, CSS selectors, or IDs -- Add appropriate waits: `page.wait_for_selector()` or `page.wait_for_timeout()` +- Prefer user-visible locators: `page.get_by_role(...)`, `page.get_by_label(...)`, `page.get_by_text(...)`. Fall back to CSS/`text=` selectors only when those don't fit. +- For readiness, lead with web assertions and locator waits — `expect(locator).to_be_visible()`, `locator.wait_for()` — which auto-wait for a real, visible condition. Reach for `page.wait_for_selector()` only when a locator won't express the wait. Avoid `wait_for_load_state('networkidle')` as a readiness check (Playwright discourages it) and avoid fixed `page.wait_for_timeout()` delays. - **Choose headed vs headless by purpose, not habit.** This skill defaults to *headless* (`headless=True`) because it targets CI and pytest. The companion `/playwright-js` defaults to *headed* for interactive visual debugging. Pick by what you're doing, and only override when the purpose flips: | Purpose | Mode | diff --git a/playwright-py/examples/broken_links.py b/playwright-py/examples/broken_links.py index c78520f..a292f74 100644 --- a/playwright-py/examples/broken_links.py +++ b/playwright-py/examples/broken_links.py @@ -41,13 +41,13 @@ def main() -> int: status = resp.status if status < 400: ok += 1 - print(f"✓ {status} {url}") + print(f"[ok] {status} {url}") else: bad += 1 - print(f"✗ {status} {url}") + print(f"[fail] {status} {url}") except Exception as ex: err += 1 - print(f"✗ ERR {url} ({type(ex).__name__}: {ex})") + print(f"[fail] ERR {url} ({type(ex).__name__}: {ex})") print(f"\n{ok} ok, {bad} broken, {err} errored out of {len(urls)} total") browser.close() diff --git a/playwright-py/examples/login_flow.py b/playwright-py/examples/login_flow.py index d114ac6..6d2fa45 100644 --- a/playwright-py/examples/login_flow.py +++ b/playwright-py/examples/login_flow.py @@ -45,7 +45,7 @@ def main() -> int: safe_click(page, 'button[type="submit"]') page.wait_for_url("**/dashboard", timeout=5000) - print(f"✓ Logged in; redirected to {page.url}") + print(f"[ok] Logged in; redirected to {page.url}") browser.close() return 0 diff --git a/playwright-py/examples/responsive_sweep.py b/playwright-py/examples/responsive_sweep.py index d890d5b..eb6e216 100644 --- a/playwright-py/examples/responsive_sweep.py +++ b/playwright-py/examples/responsive_sweep.py @@ -41,7 +41,7 @@ def main() -> int: page.wait_for_load_state("networkidle") path = OUTPUT_DIR / f"responsive-{name}.png" page.screenshot(path=str(path), full_page=True) - print(f"✓ {name:<8} ({width:>4}x{height:<4}) → {path}") + print(f"[ok] {name:<8} ({width:>4}x{height:<4}) -> {path}") context.close() browser.close() return 0 |
