- Added 44 external skills from obra/superpowers, ui-ux-pro-max-skill, claude-codex-settings - Added 8 autonomous agents (commit-creator, pr-creator, pr-reviewer, etc.) - Added 23 slash commands for Git/GitHub, setup, and plugin development - Added hooks for code formatting, notifications, and validation - Added MCP configurations for Azure, GCloud, Supabase, MongoDB, etc. - Added awesome-openclaw-skills registry (3,002 skills referenced) - Updated comprehensive README with full documentation Sources: - github.com/obra/superpowers (14 skills) - github.com/nextlevelbuilder/ui-ux-pro-max-skill (1 skill) - github.com/fcakyon/claude-codex-settings (29 skills, 8 agents, 23 commands) - github.com/VoltAgent/awesome-openclaw-skills (registry) - skills.sh (reference) - buildwithclaude.com (reference)
334 lines
7.3 KiB
Markdown
334 lines
7.3 KiB
Markdown
---
|
|
name: playwright-testing
|
|
description: This skill should be used when user asks about "Playwright", "responsiveness test", "test with playwright", "test login flow", "file upload test", "handle authentication in tests", or "fix flaky tests".
|
|
---
|
|
|
|
# Playwright Testing Best Practices
|
|
|
|
## Test Organization
|
|
|
|
### File Structure
|
|
|
|
```
|
|
tests/
|
|
├── auth/
|
|
│ ├── login.spec.ts
|
|
│ └── signup.spec.ts
|
|
├── dashboard/
|
|
│ └── dashboard.spec.ts
|
|
├── fixtures/
|
|
│ └── test-data.ts
|
|
├── pages/
|
|
│ └── login.page.ts
|
|
└── playwright.config.ts
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
- Files: `feature-name.spec.ts`
|
|
- Tests: Describe user behavior, not implementation
|
|
- Good: `test('user can reset password via email')`
|
|
- Bad: `test('test reset password')`
|
|
|
|
## Page Object Model
|
|
|
|
### Basic Pattern
|
|
|
|
```typescript
|
|
// pages/login.page.ts
|
|
export class LoginPage {
|
|
constructor(private page: Page) {}
|
|
|
|
async goto() {
|
|
await this.page.goto("/login");
|
|
}
|
|
|
|
async login(email: string, password: string) {
|
|
await this.page.getByLabel("Email").fill(email);
|
|
await this.page.getByLabel("Password").fill(password);
|
|
await this.page.getByRole("button", { name: "Sign in" }).click();
|
|
}
|
|
}
|
|
|
|
// tests/login.spec.ts
|
|
test("successful login", async ({ page }) => {
|
|
const loginPage = new LoginPage(page);
|
|
await loginPage.goto();
|
|
await loginPage.login("user@example.com", "password");
|
|
await expect(page).toHaveURL("/dashboard");
|
|
});
|
|
```
|
|
|
|
## Locator Strategies
|
|
|
|
### Priority Order (Best to Worst)
|
|
|
|
1. **`getByRole`** - Accessible, resilient
|
|
2. **`getByLabel`** - Form inputs
|
|
3. **`getByPlaceholder`** - When no label
|
|
4. **`getByText`** - Visible text
|
|
5. **`getByTestId`** - When no better option
|
|
6. **CSS/XPath** - Last resort
|
|
|
|
### Examples
|
|
|
|
```typescript
|
|
// Preferred
|
|
await page.getByRole("button", { name: "Submit" }).click();
|
|
await page.getByLabel("Email address").fill("user@example.com");
|
|
|
|
// Acceptable
|
|
await page.getByTestId("submit-button").click();
|
|
|
|
// Avoid
|
|
await page.locator("#submit-btn").click();
|
|
await page.locator('//button[@type="submit"]').click();
|
|
```
|
|
|
|
## Authentication Handling
|
|
|
|
### Storage State (Recommended)
|
|
|
|
Save logged-in state and reuse across tests:
|
|
|
|
```typescript
|
|
// global-setup.ts
|
|
async function globalSetup() {
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage();
|
|
await page.goto("/login");
|
|
await page.getByLabel("Email").fill(process.env.TEST_USER_EMAIL);
|
|
await page.getByLabel("Password").fill(process.env.TEST_USER_PASSWORD);
|
|
await page.getByRole("button", { name: "Sign in" }).click();
|
|
await page.waitForURL("/dashboard");
|
|
await page.context().storageState({ path: "auth.json" });
|
|
await browser.close();
|
|
}
|
|
|
|
// playwright.config.ts
|
|
export default defineConfig({
|
|
globalSetup: "./global-setup.ts",
|
|
use: {
|
|
storageState: "auth.json",
|
|
},
|
|
});
|
|
```
|
|
|
|
### Multi-User Scenarios
|
|
|
|
```typescript
|
|
// Create different auth states
|
|
const adminAuth = "admin-auth.json";
|
|
const userAuth = "user-auth.json";
|
|
|
|
test.describe("admin features", () => {
|
|
test.use({ storageState: adminAuth });
|
|
// Admin tests
|
|
});
|
|
|
|
test.describe("user features", () => {
|
|
test.use({ storageState: userAuth });
|
|
// User tests
|
|
});
|
|
```
|
|
|
|
## File Upload Handling
|
|
|
|
### Basic Upload
|
|
|
|
```typescript
|
|
// Single file
|
|
await page.getByLabel("Upload file").setInputFiles("path/to/file.pdf");
|
|
|
|
// Multiple files
|
|
await page
|
|
.getByLabel("Upload files")
|
|
.setInputFiles(["path/to/file1.pdf", "path/to/file2.pdf"]);
|
|
|
|
// Clear file input
|
|
await page.getByLabel("Upload file").setInputFiles([]);
|
|
```
|
|
|
|
### Drag and Drop Upload
|
|
|
|
```typescript
|
|
// Create file from buffer
|
|
const buffer = Buffer.from("file content");
|
|
|
|
await page.getByTestId("dropzone").dispatchEvent("drop", {
|
|
dataTransfer: {
|
|
files: [{ name: "test.txt", mimeType: "text/plain", buffer }],
|
|
},
|
|
});
|
|
```
|
|
|
|
### File Download
|
|
|
|
```typescript
|
|
const downloadPromise = page.waitForEvent("download");
|
|
await page.getByRole("button", { name: "Download" }).click();
|
|
const download = await downloadPromise;
|
|
await download.saveAs("downloads/" + download.suggestedFilename());
|
|
```
|
|
|
|
## Waiting Strategies
|
|
|
|
### Auto-Wait (Preferred)
|
|
|
|
Playwright auto-waits for elements. Use assertions:
|
|
|
|
```typescript
|
|
// Auto-waits for element to be visible and stable
|
|
await page.getByRole("button", { name: "Submit" }).click();
|
|
|
|
// Auto-waits for condition
|
|
await expect(page.getByText("Success")).toBeVisible();
|
|
```
|
|
|
|
### Explicit Waits (When Needed)
|
|
|
|
```typescript
|
|
// Wait for navigation
|
|
await page.waitForURL("**/dashboard");
|
|
|
|
// Wait for network idle
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Wait for specific response
|
|
await page.waitForResponse((resp) => resp.url().includes("/api/data"));
|
|
```
|
|
|
|
## Network Mocking
|
|
|
|
### Mock API Responses
|
|
|
|
```typescript
|
|
await page.route("**/api/users", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 1, name: "Test User" }]),
|
|
});
|
|
});
|
|
|
|
// Mock error response
|
|
await page.route("**/api/users", async (route) => {
|
|
await route.fulfill({ status: 500 });
|
|
});
|
|
```
|
|
|
|
### Intercept and Modify
|
|
|
|
```typescript
|
|
await page.route("**/api/data", async (route) => {
|
|
const response = await route.fetch();
|
|
const json = await response.json();
|
|
json.modified = true;
|
|
await route.fulfill({ response, json });
|
|
});
|
|
```
|
|
|
|
## CI/CD Integration
|
|
|
|
### GitHub Actions Example
|
|
|
|
```yaml
|
|
- name: Run Playwright tests
|
|
run: npx playwright test
|
|
env:
|
|
CI: true
|
|
|
|
- name: Upload test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: playwright-report
|
|
path: playwright-report/
|
|
```
|
|
|
|
### Parallel Execution
|
|
|
|
```typescript
|
|
// playwright.config.ts
|
|
export default defineConfig({
|
|
workers: process.env.CI ? 2 : undefined,
|
|
fullyParallel: true,
|
|
});
|
|
```
|
|
|
|
## Debugging Failed Tests
|
|
|
|
### Debug Tools
|
|
|
|
```bash
|
|
# Run with UI mode
|
|
npx playwright test --ui
|
|
|
|
# Run with inspector
|
|
npx playwright test --debug
|
|
|
|
# Show browser
|
|
npx playwright test --headed
|
|
```
|
|
|
|
### Trace Viewer
|
|
|
|
```typescript
|
|
// playwright.config.ts
|
|
use: {
|
|
trace: 'on-first-retry', // Capture trace on failure
|
|
}
|
|
```
|
|
|
|
## Flaky Test Fixes
|
|
|
|
### Common Causes and Solutions
|
|
|
|
**Race conditions:**
|
|
|
|
- Use proper assertions instead of hard waits
|
|
- Wait for network requests to complete
|
|
|
|
**Animation issues:**
|
|
|
|
- Disable animations in test config
|
|
- Wait for animation to complete
|
|
|
|
**Dynamic content:**
|
|
|
|
- Use flexible locators (text content, not position)
|
|
- Wait for loading states to resolve
|
|
|
|
**Test isolation:**
|
|
|
|
- Each test should set up its own state
|
|
- Don't depend on other tests' side effects
|
|
|
|
### Anti-Patterns to Avoid
|
|
|
|
```typescript
|
|
// Bad: Hard sleep
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Good: Wait for condition
|
|
await expect(page.getByText("Loaded")).toBeVisible();
|
|
|
|
// Bad: Flaky selector
|
|
await page.locator(".btn:nth-child(3)").click();
|
|
|
|
// Good: Semantic selector
|
|
await page.getByRole("button", { name: "Submit" }).click();
|
|
```
|
|
|
|
## Responsive Design Testing
|
|
|
|
For comprehensive responsive testing across viewport breakpoints, use the **responsive-tester** agent. It automatically:
|
|
|
|
- Tests pages across 7 standard breakpoints (375px to 1536px)
|
|
- Detects horizontal overflow issues
|
|
- Verifies mobile-first design patterns
|
|
- Checks touch target sizes (44x44px minimum)
|
|
- Flags anti-patterns like fixed widths without mobile fallback
|
|
|
|
Trigger it by asking to "test responsiveness", "check breakpoints", or "test mobile/desktop layout".
|