aboutsummaryrefslogtreecommitdiff
path: root/playwright-py/SKILL.md
diff options
context:
space:
mode:
Diffstat (limited to 'playwright-py/SKILL.md')
-rw-r--r--playwright-py/SKILL.md15
1 files changed, 8 insertions, 7 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 |