diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-22 15:48:48 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-22 15:48:48 -0500 |
| commit | 8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8 (patch) | |
| tree | 9f4d2fc7d3dff7f5404576759fee9d4a44f74b73 /playwright-js/SKILL.md | |
| parent | e6f8db82fb3e97ebf11866de2166ff4505871c21 (diff) | |
| download | rulesets-8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8.tar.gz rulesets-8c291b81cd7fb10479a55fb47e9a9cebcfc1b9b8.zip | |
refactor(skills): locator-first playwright guidance, drop emoji markers
Two cleanups to the playwright skills, landed together since they overlap the same files.
The skills taught networkidle as the readiness check and leaned on raw page.click/fill/waitForSelector. Playwright discourages networkidle for readiness, so the guidance in both SKILL.md files now waits for a visible app landmark via a web assertion or locator, the login and form examples use getByLabel/getByRole plus expect, the API reference leads with that pattern, and lib/helpers.js defaults waitForPageReady to load (preferring a caller-supplied landmark) and races the success indicator in authenticate instead of waiting on networkidle.
The second cleanup strips emoji console markers across run.js, helpers.js, both SKILL.md files, and the py examples, replacing each with a plain ASCII tag like [ok], [error], or [scan]. node --check and py_compile pass, and an emoji grep comes back clean.
Diffstat (limited to 'playwright-js/SKILL.md')
| -rw-r--r-- | playwright-js/SKILL.md | 59 |
1 files changed, 33 insertions, 26 deletions
diff --git a/playwright-js/SKILL.md b/playwright-js/SKILL.md index b4b037b..40427c3 100644 --- a/playwright-js/SKILL.md +++ b/playwright-js/SKILL.md @@ -82,7 +82,7 @@ const TARGET_URL = 'http://localhost:3001'; // <-- Auto-detected or from user console.log('Page loaded:', await page.title()); await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true }); - console.log('📸 Screenshot saved to /tmp/screenshot.png'); + console.log('[screenshot] Saved to /tmp/screenshot.png'); await browser.close(); })(); @@ -127,6 +127,7 @@ const TARGET_URL = 'http://localhost:3001'; // Auto-detected ```javascript // /tmp/playwright-test-login.js const { chromium } = require('playwright'); +const { expect } = require('@playwright/test'); const TARGET_URL = 'http://localhost:3001'; // Auto-detected @@ -136,13 +137,15 @@ const TARGET_URL = 'http://localhost:3001'; // Auto-detected await page.goto(`${TARGET_URL}/login`); - await page.fill('input[name="email"]', 'test@example.com'); - await page.fill('input[name="password"]', 'password123'); - await page.click('button[type="submit"]'); + // Prefer user-visible locators; they auto-wait for the field to be ready. + await page.getByLabel('Email').fill('test@example.com'); + await page.getByLabel('Password').fill('password123'); + await page.getByRole('button', { name: /sign in|log in/i }).click(); - // Wait for redirect + // Wait for redirect, then assert on a landmark of the destination. await page.waitForURL('**/dashboard'); - console.log('✅ Login successful, redirected to dashboard'); + await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible(); + console.log('[ok] Login successful, redirected to dashboard'); await browser.close(); })(); @@ -153,6 +156,7 @@ const TARGET_URL = 'http://localhost:3001'; // Auto-detected ```javascript // /tmp/playwright-test-form.js const { chromium } = require('playwright'); +const { expect } = require('@playwright/test'); const TARGET_URL = 'http://localhost:3001'; // Auto-detected @@ -162,14 +166,14 @@ const TARGET_URL = 'http://localhost:3001'; // Auto-detected await page.goto(`${TARGET_URL}/contact`); - await page.fill('input[name="name"]', 'John Doe'); - await page.fill('input[name="email"]', 'john@example.com'); - await page.fill('textarea[name="message"]', 'Test message'); - await page.click('button[type="submit"]'); + await page.getByLabel('Name').fill('John Doe'); + await page.getByLabel('Email').fill('john@example.com'); + await page.getByLabel('Message').fill('Test message'); + await page.getByRole('button', { name: /submit|send/i }).click(); - // Verify submission - await page.waitForSelector('.success-message'); - console.log('✅ Form submitted successfully'); + // Verify submission via a web assertion (auto-waits for the message to appear). + await expect(page.getByText(/thank you|message sent|success/i)).toBeVisible(); + console.log('[ok] Form submitted successfully'); await browser.close(); })(); @@ -203,8 +207,8 @@ const { chromium } = require('playwright'); } } - console.log(`✅ Working links: ${results.working}`); - console.log(`❌ Broken links:`, results.broken); + console.log(`[ok] Working links: ${results.working}`); + console.log(`[fail] Broken links:`, results.broken); await browser.close(); })(); @@ -221,7 +225,7 @@ const { chromium } = require('playwright'); try { await page.goto('http://localhost:3000', { - waitUntil: 'networkidle', + waitUntil: 'load', timeout: 10000, }); @@ -230,9 +234,9 @@ const { chromium } = require('playwright'); fullPage: true, }); - console.log('📸 Screenshot saved to /tmp/screenshot.png'); + console.log('[screenshot] Saved to /tmp/screenshot.png'); } catch (error) { - console.error('❌ Error:', error.message); + console.error('[error]', error.message); } finally { await browser.close(); } @@ -276,7 +280,7 @@ const TARGET_URL = 'http://localhost:3001'; // Auto-detected }); } - console.log('✅ All viewports tested'); + console.log('[ok] All viewports tested'); await browser.close(); })(); ``` @@ -395,7 +399,7 @@ For comprehensive Playwright API documentation, see [API_REFERENCE.md](API_REFER - **DEFAULT: Visible browser** - Always use `headless: false` unless user explicitly asks for headless mode - **Headless mode** - Only use `headless: true` when user specifically requests "headless" or "background" execution - **Slow down:** Use `slowMo: 100` to make actions visible and easier to follow -- **Wait strategies:** Use `waitForURL`, `waitForSelector`, `waitForLoadState` instead of fixed timeouts +- **Wait strategies:** Lead with web assertions and locators — `await expect(locator).toBeVisible()`, `await locator.waitFor()` — and `waitForURL`. They auto-wait for the real condition. Reach for `waitForSelector` only when a locator won't express the wait. Avoid `waitForLoadState('networkidle')` as a readiness check; Playwright discourages it. Don't use fixed timeouts. - **Error handling:** Always use try-catch for robust automation - **Console output:** Use `console.log()` to track progress and show what's happening @@ -414,7 +418,7 @@ Ensure running from skill directory via `run.js` wrapper Check `headless: false` and ensure display available **Element not found:** -Add wait: `await page.waitForSelector('.element', { timeout: 10000 })` +Wait on a locator: `await page.getByRole('button', { name: 'Save' }).waitFor({ timeout: 10000 })` (or `await expect(locator).toBeVisible()`). These auto-wait for a visible, app-specific element rather than network state. ## Example Usage @@ -445,7 +449,7 @@ User: "Use 3001" [Writes login automation to /tmp/playwright-test-login.js] [Runs: cd $SKILL_DIR && node run.js /tmp/playwright-test-login.js] -[Reports: ✅ Login successful, redirected to /dashboard] +[Reports: [ok] Login successful, redirected to /dashboard] ``` ## Notes @@ -470,27 +474,30 @@ User task → Is it static HTML (file:// or plain server-rendered)? │ └─ No (dynamic webapp) → 1. Navigate to the page - 2. Wait for networkidle: await page.waitForLoadState('networkidle'); + 2. Wait for a visible app landmark: await expect(page.getByRole('main')).toBeVisible(); + (or: await page.getByText('Dashboard').waitFor();) 3. Inspect rendered DOM (screenshot, page.content(), or locator().all()) 4. Identify selectors from the rendered state, not the source 5. Execute actions with those selectors ``` -**Common pitfall:** inspecting the DOM before `networkidle` on a dynamic app returns stale content or an empty skeleton. Every "element not found" bug on dynamic pages should trigger a "did I wait for networkidle?" check first. +**Common pitfall:** inspecting the DOM before the app has rendered returns stale content or an empty skeleton. The fix is to wait for a *visible, app-specific landmark* — a heading, a `role=main` region, a known piece of text — via a web assertion (`expect(locator).toBeVisible()`) or `locator.waitFor()`. These auto-wait for the element to appear, which is what "ready" actually means. Don't wait on `networkidle` for this; Playwright explicitly discourages it as a readiness signal, since a page can be interactive long before the network goes quiet (or never go quiet at all, with polling or analytics). Every "element not found" bug on dynamic pages should trigger a "did I wait for a visible landmark?" check first. ## Added: Reconnaissance-Then-Action Pattern For any non-trivial interaction on a dynamic page: -1. **Reconnoiter.** Navigate, wait for load, capture state: +1. **Reconnoiter.** Navigate, wait for a visible landmark, capture state: ```javascript await page.goto(TARGET_URL); - await page.waitForLoadState('networkidle'); + await expect(page.getByRole('main')).toBeVisible(); // wait for a real app landmark, not network quiet await page.screenshot({ path: '/tmp/inspect.png', fullPage: true }); const html = await page.content(); const buttons = await page.locator('button').all(); ``` + `expect` comes from `const { expect } = require('@playwright/test');`. If the landmark isn't known yet, `await page.getByText('<some text you expect>').waitFor()` works the same way. Both auto-wait; neither relies on `networkidle`. + 2. **Decide.** From the screenshot + content + locator list, pick the selectors you'll use. Don't guess from source. 3. **Act.** Execute the interaction with the discovered selectors. |
