Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Claude/agents/e2e-runner.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ npx playwright show-report # View HTML report
### 2. Create
- Use Page Object Model (POM) pattern
- Prefer `data-testid` locators over CSS/XPath
- Prefer locator-based Playwright actions over raw `page.click()` and `page.fill()`
- Add assertions at key steps
- Capture screenshots at critical points
- Use proper waits (never `waitForTimeout`)
Expand All @@ -80,7 +81,7 @@ npx playwright show-report # View HTML report

```typescript
// Quarantine
test('flaky: market search', async ({ page }) => {
test('flaky: should show market search results', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
})

Expand All @@ -90,6 +91,11 @@ test('flaky: market search', async ({ page }) => {

Common causes: race conditions (use auto-wait locators), network timing (wait for response), animation timing (wait for `networkidle`).

## Coverage Expectations

- Browser: Chromium, Firefox, WebKit for critical web flows
- Mobile web: at least one Android-sized and one iPhone-sized device profile when touch behavior matters

## Success Metrics

- All critical journeys passing (100%)
Expand Down
11 changes: 7 additions & 4 deletions Claude/rules/common/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ MANDATORY workflow:
Prefer Arrange-Act-Assert structure for tests:

```typescript
test('calculates similarity correctly', () => {
test('should calculate similarity correctly', () => {
// Arrange
const vector1 = [1, 0, 0]
const vector2 = [0, 1, 0]
Expand All @@ -50,8 +50,11 @@ test('calculates similarity correctly', () => {

Use descriptive names that explain the behavior under test:

- Prefer `should` phrasing for test names.
- Use present-tense behavior statements that read clearly in test output.

```typescript
test('returns empty array when no markets match query', () => {})
test('throws error when API key is missing', () => {})
test('falls back to substring search when Redis is unavailable', () => {})
test('should return empty array when no markets match query', () => {})
test('should throw error when API key is missing', () => {})
test('should fall back to substring search when Redis is unavailable', () => {})
```
4 changes: 3 additions & 1 deletion Claude/rules/web/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,21 @@

- Minimum: Chrome, Firefox, Safari
- Test scrolling, motion, and fallback behavior
- Use Playwright projects for Chromium, Firefox, and WebKit coverage

### 5. Responsive

- Test 320, 375, 768, 1024, 1440, 1920
- Verify no overflow
- Verify touch interactions
- Add at least one Android-sized and one iPhone-sized mobile-web project when touch behavior matters

## E2E Shape

```ts
import { test, expect } from '@playwright/test';

test('landing hero loads', async ({ page }) => {
test('should load landing hero', async ({ page }) => {
await page.goto('/');
await expect(page.locator('h1')).toBeVisible();
});
Expand Down
132 changes: 101 additions & 31 deletions Claude/skills/e2e-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ tests/
├── fixtures/
│ ├── auth.ts
│ └── data.ts
├── pageObjects/
│ ├── components/
│ │ ├── base.component.ts
│ │ └── header.component.ts
│ └── pages/
│ ├── base.page.ts
│ └── items.page.ts
├── types/
│ ├── searchData.ts
│ └── loginData.ts
├── utils/
│ ├── browserHelpers.ts
│ └── dateUtils.ts
└── playwright.config.ts
```

Expand All @@ -34,41 +47,64 @@ tests/
```typescript
import { Page, Locator } from '@playwright/test'

export class Header {
readonly profileMenuButton: Locator

constructor(private readonly page: Page) {
this.profileMenuButton = page.getByTestId('profile-menu-button')
}

async openProfileMenu(): Promise<void> {
await this.profileMenuButton.click()
}
}

export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
readonly header: Header

constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
constructor(private readonly page: Page) {
this.searchInput = page.getByTestId('search-input')
this.itemCards = page.getByTestId('item-card')
this.createButton = page.getByTestId('create-btn')
this.header = new Header(page)
}

async goto() {
async goto(): Promise<void> {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
await this.searchInput.waitFor({ state: 'visible', timeout: 5000 })
}
Comment on lines +75 to 78
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within this TypeScript snippet, most lines omit semicolons, but the added waitFor(...) line ends with a semicolon. For consistency (and to avoid mixed-style examples), consider removing the semicolon here and in similar added examples in this doc.

Copilot uses AI. Check for mistakes.

async search(query: string) {
async search(query: string): Promise<void> {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
await this.itemCards.first().waitFor({ state: 'visible', timeout: 5000 })
}

async openCreateFlow(): Promise<void> {
await this.createButton.click()
}

async getItemCount() {
return await this.itemCards.count()
async getItemCount(): Promise<number> {
return this.itemCards.count()
}
}
```

### POM Rules

- Exported page object methods should have explicit return types.
- Prefer `getByTestId`, `getByRole`, and `getByLabel` before CSS selectors.
- Keep repeated UI fragments such as headers, dialogs, or sidebars in reusable component objects.
- Do not hide assertions inside page objects; keep high-signal business assertions visible in the test.
Comment thread
HassaanAhmadFarooqi marked this conversation as resolved.

## Test Structure

```typescript
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
import { ItemsPage } from '../../pageObjects/pages/items.page'

test.describe('Item Search', () => {
let itemsPage: ItemsPage
Expand All @@ -91,12 +127,45 @@ test.describe('Item Search', () => {
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')

await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
await expect(page.getByTestId('no-results')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
```

## Fixtures, Types, and Utils Setup

### Fixtures

```typescript
import { LoginData } from '../types/loginData'

export const auth: LoginData = {
emailAddress: 'testing@testmail.com',
password: 'Password123',
}
```

### Types

```typescript
export interface LoginData {
emailAddress: string;
password: string;
}
```

### Utils

```typescript
export class DateUtils {
static formatToISO(date: Date): string {
return date.toISOString().split('T')[0]
}
// more functions...
}
```

## Playwright Configuration

```typescript
Expand Down Expand Up @@ -125,7 +194,8 @@ export default defineConfig({
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-android', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-ios', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run dev',
Expand All @@ -141,12 +211,12 @@ export default defineConfig({
### Quarantine

```typescript
test('flaky: complex search', async ({ page }) => {
test('flaky: should perform a complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
// test code...
})

test('conditional skip', async ({ page }) => {
test('flaky in CI: should show filtered results', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
// test code...
})
Expand All @@ -167,7 +237,7 @@ npx playwright test tests/search.spec.ts --retries=3
await page.click('[data-testid="button"]')

// Good: auto-wait locator
await page.locator('[data-testid="button"]').click()
await page.getByTestId('button').click()
```

**Network timing:**
Expand All @@ -185,9 +255,9 @@ await page.waitForResponse(resp => resp.url().includes('/api/data'))
await page.click('[data-testid="menu-item"]')

// Good: wait for stability
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
const menuItem = page.getByTestId('menu-item')
await menuItem.waitFor({ state: 'visible', timeout: 5000 })
await menuItem.click()
```

## Artifact Management
Expand All @@ -197,7 +267,7 @@ await page.locator('[data-testid="menu-item"]').click()
```typescript
await page.screenshot({ path: 'artifacts/after-login.png' })
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
await page.getByTestId('chart').screenshot({ path: 'artifacts/chart.png' })
```

### Traces
Expand Down Expand Up @@ -280,7 +350,7 @@ jobs:
## Wallet / Web3 Testing

```typescript
test('wallet connection', async ({ page, context }) => {
test('should connect wallet', async ({ page, context }) => {
// Mock wallet provider
await context.addInitScript(() => {
window.ethereum = {
Expand All @@ -294,33 +364,33 @@ test('wallet connection', async ({ page, context }) => {
})

await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
await page.getByTestId('connect-wallet').click()
await expect(page.getByTestId('wallet-address')).toContainText('0x1234')
})
```

## Financial / Critical Flow Testing

```typescript
test('trade execution', async ({ page }) => {
test('should execute trade', async ({ page }) => {
// Skip on production — real money
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')

await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
await page.getByTestId('position-yes').click()
await page.getByTestId('trade-amount').fill('1.0')

// Verify preview
const preview = page.locator('[data-testid="trade-preview"]')
const preview = page.getByTestId('trade-preview')
await expect(preview).toContainText('1.0')

// Confirm and wait for blockchain
await page.locator('[data-testid="confirm-trade"]').click()
await page.getByTestId('confirm-trade').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)

await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
await expect(page.getByTestId('trade-success')).toBeVisible()
})
```
Loading