aboutsummaryrefslogtreecommitdiff
path: root/playwright-js/SKILL.md
diff options
context:
space:
mode:
Diffstat (limited to 'playwright-js/SKILL.md')
-rw-r--r--playwright-js/SKILL.md59
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.