Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
114 changes: 92 additions & 22 deletions Claude/skills/e2e-testing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,49 +26,85 @@ tests/
├── fixtures/
│ ├── auth.ts
│ └── data.ts
├── pageObjects/
│ └── components/
│ │ └── base.component.ts
│ │ └── header.component.ts
│ └── pages/
│ └── base.page.ts
│ └── items.page.ts
├── types/
│ └── searchData.ts

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

The directory tree diagram under pageObjects/ has inconsistent indentation and uses └── for multiple sibling entries (e.g., both base.component.ts and header.component.ts). This makes the structure ambiguous; update the tree markers/indentation so siblings use ├──/└── correctly.

Suggested change
── components/
│ │ ── base.component.ts
│ │ └── header.component.ts
│ └── pages/
── base.page.ts
│ └── items.page.ts
├── types/
── searchData.ts
── components/
│ │ ── base.component.ts
│ │ └── header.component.ts
│ └── pages/
── base.page.ts
│ └── items.page.ts
├── types/
── searchData.ts

Copilot uses AI. Check for mistakes.

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

The directory tree diagram shows multiple sibling files as └── (e.g., base.page.ts and items.page.ts, and the entries under types/). Please adjust the tree drawing so only the final sibling uses └── and preceding siblings use ├──, otherwise readers may misinterpret the intended layout.

Suggested change
── components/
│ │ ── base.component.ts
│ │ └── header.component.ts
│ └── pages/
── base.page.ts
│ └── items.page.ts
├── types/
── searchData.ts
── components/
│ │ ── base.component.ts
│ │ └── header.component.ts
│ └── pages/
── base.page.ts
│ └── items.page.ts
├── types/
── searchData.ts

Copilot uses AI. Check for mistakes.
│ └── loginData.ts
├── utils/
│ ├── browserHelpers.ts
│ └── dateUtils.ts
└── playwright.config.ts
```

## Page Object Model (POM)

```typescript
import { Page, Locator } from '@playwright/test'
import { expect, Locator, Page } from '@playwright/test'

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

This snippet imports expect but doesn't use it anywhere in the page-object example. Since this is presented as copy/pasteable TypeScript, it will trigger unused-import lint/TS warnings; remove expect from the import (or use it in a test snippet instead).

Suggested change
import { expect, Locator, Page } from '@playwright/test'
import { Locator, Page } from '@playwright/test'

Copilot uses AI. Check for mistakes.

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

Copilot AI Apr 27, 2026

Copy link

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 Down Expand Up @@ -97,6 +133,39 @@ test.describe('Item Search', () => {
})
```

## 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 Down Expand Up @@ -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 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 @@ -302,7 +372,7 @@ test('wallet connection', async ({ page, context }) => {
## 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')

Expand Down
38 changes: 19 additions & 19 deletions Claude/skills/tdd-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,19 @@ For each user journey, create comprehensive test cases:

```typescript
describe('Semantic Search', () => {
it('returns relevant markets for query', async () => {
it('should return relevant markets for query', async () => {
// Test implementation
})

it('handles empty query gracefully', async () => {
it('should handle empty query gracefully', async () => {
// Test edge case
})

it('falls back to substring search when Redis unavailable', async () => {
it('should fall back to substring search when Redis unavailable', async () => {
// Test fallback behavior
})

it('sorts results by similarity score', async () => {
it('should sort results by similarity score', async () => {
// Test sorting logic
})
})
Expand Down Expand Up @@ -177,12 +177,12 @@ import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'

describe('Button Component', () => {
it('renders with correct text', () => {
it('should render the button with correct text', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})

it('calls onClick when clicked', () => {
it('should call onClick when clicked', () => {
const handleClick = jest.fn()
render(<Button onClick={handleClick}>Click</Button>)

Expand All @@ -191,7 +191,7 @@ describe('Button Component', () => {
expect(handleClick).toHaveBeenCalledTimes(1)
})

it('is disabled when disabled prop is true', () => {
it('should disable the button when disabled prop is true', () => {
render(<Button disabled>Click</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
Expand Down Expand Up @@ -233,46 +233,46 @@ describe('GET /api/markets', () => {
```typescript
import { test, expect } from '@playwright/test'

test('user can search and filter markets', async ({ page }) => {
test('should let user search and filter markets', async ({ page }) => {
// Navigate to markets page
await page.goto('/')
await page.click('a[href="/markets"]')
await page.locator('a[href="/markets"]').click()

// Verify page loaded
await expect(page.locator('h1')).toContainText('Markets')

// Search for markets
await page.fill('input[placeholder="Search markets"]', 'election')
await page.locator('input[placeholder="Search markets"]').fill('election')

// Wait for debounce and results
await page.waitForTimeout(600)
// Wait for the filtered result set to settle
const results = page.locator('[data-testid="market-card"]')
await results.first().waitFor({ state: 'visible', timeout: 5000 });

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

There’s trailing whitespace at the end of the comment line here, and the following waitFor(...) line ends with a semicolon while the rest of the snippet omits them. Please remove the trailing space and keep the example’s formatting consistent.

Suggested change
// Wait for the filtered result set to settle
const results = page.locator('[data-testid="market-card"]')
await results.first().waitFor({ state: 'visible', timeout: 5000 });
// Wait for the filtered result set to settle
const results = page.locator('[data-testid="market-card"]')
await results.first().waitFor({ state: 'visible', timeout: 5000 })

Copilot uses AI. Check for mistakes.

// Verify search results displayed
const results = page.locator('[data-testid="market-card"]')
await expect(results).toHaveCount(5, { timeout: 5000 })

// Verify results contain search term
const firstResult = results.first()
await expect(firstResult).toContainText('election', { ignoreCase: true })

// Filter by status
await page.click('button:has-text("Active")')
await page.locator('button:has-text("Active")').click()

// Verify filtered results
await expect(results).toHaveCount(3)
})

test('user can create a new market', async ({ page }) => {
test('should let user create a new market', async ({ page }) => {
// Login first
await page.goto('/creator-dashboard')

// Fill market creation form
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test description')
await page.fill('input[name="endDate"]', '2025-12-31')
await page.locator('input[name="name"]').fill('Test Market')
await page.locator('textarea[name="description"]').fill('Test description')
await page.locator('input[name="endDate"]').fill('2025-12-31')

// Submit form
await page.click('button[type="submit"]')
await page.locator('button[type="submit"]').click()

// Verify success message
await expect(page.locator('text=Market created successfully')).toBeVisible()
Expand Down