diff options
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. |
