Complete Claude Code plugin with: - 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report) - 3 specialized agents (test-architect, test-debugger, migration-planner) - 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility) - TestRail MCP server (TypeScript) — 8 tools for bidirectional sync - BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing - Smart hooks (auto-validate tests, auto-detect Playwright projects) - 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests) - Leverages Claude Code built-ins (/batch, /debug, Explore subagent) - Zero-config for core features; TestRail/BrowserStack via env vars - Both TypeScript and JavaScript support throughout Co-authored-by: Leo <leo@openclaw.ai>
5.1 KiB
5.1 KiB
Search Sorting Template
Tests sorting results by name, date, and price.
Prerequisites
- Search results for
{{searchQuery}}with multiple items - App running at
{{baseUrl}}
TypeScript
import { test, expect } from '@playwright/test';
test.describe('Search Sorting', () => {
test.beforeEach(async ({ page }) => {
await page.goto('{{baseUrl}}/search?q={{searchQuery}}');
});
// Happy path: sort by name A-Z
test('sorts results alphabetically A-Z', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('name_asc');
await expect(page).toHaveURL(/sort=name_asc/);
const names = page.getByTestId('result-name');
const first = await names.first().textContent();
const second = await names.nth(1).textContent();
expect(first!.localeCompare(second!)).toBeLessThanOrEqual(0);
});
// Happy path: sort by name Z-A
test('sorts results alphabetically Z-A', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('name_desc');
const names = page.getByTestId('result-name');
const first = await names.first().textContent();
const second = await names.nth(1).textContent();
expect(first!.localeCompare(second!)).toBeGreaterThanOrEqual(0);
});
// Happy path: sort by date newest
test('sorts results by newest date first', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('date_desc');
await expect(page).toHaveURL(/sort=date_desc/);
const dates = page.getByTestId('result-date');
const firstDate = new Date(await dates.first().getAttribute('datetime') ?? '');
const secondDate = new Date(await dates.nth(1).getAttribute('datetime') ?? '');
expect(firstDate.getTime()).toBeGreaterThanOrEqual(secondDate.getTime());
});
// Happy path: sort by price low-high
test('sorts by price low to high', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('price_asc');
const prices = page.getByTestId('result-price');
const firstText = await prices.first().textContent() ?? '';
const secondText = await prices.nth(1).textContent() ?? '';
const first = parseFloat(firstText.replace(/[^0-9.]/g, ''));
const second = parseFloat(secondText.replace(/[^0-9.]/g, ''));
expect(first).toBeLessThanOrEqual(second);
});
// Happy path: sort by price high-low
test('sorts by price high to low', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('price_desc');
const prices = page.getByTestId('result-price');
const firstText = await prices.first().textContent() ?? '';
const secondText = await prices.nth(1).textContent() ?? '';
const first = parseFloat(firstText.replace(/[^0-9.]/g, ''));
const second = parseFloat(secondText.replace(/[^0-9.]/g, ''));
expect(first).toBeGreaterThanOrEqual(second);
});
// Happy path: sort persists with filters
test('sort selection persists when filter applied', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('price_asc');
await page.getByRole('checkbox', { name: '{{filterCategory}}' }).check();
await expect(page).toHaveURL(/sort=price_asc/);
await expect(page.getByRole('combobox', { name: /sort by/i })).toHaveValue('price_asc');
});
// Edge case: default sort is relevance
test('default sort is relevance', async ({ page }) => {
await expect(page.getByRole('combobox', { name: /sort by/i })).toHaveValue('relevance');
});
});
JavaScript
const { test, expect } = require('@playwright/test');
test.describe('Search Sorting', () => {
test.beforeEach(async ({ page }) => {
await page.goto('{{baseUrl}}/search?q={{searchQuery}}');
});
test('sorts alphabetically A-Z', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('name_asc');
await expect(page).toHaveURL(/sort=name_asc/);
const names = page.getByTestId('result-name');
const first = await names.first().textContent();
const second = await names.nth(1).textContent();
expect(first.localeCompare(second)).toBeLessThanOrEqual(0);
});
test('sorts by price low to high', async ({ page }) => {
await page.getByRole('combobox', { name: /sort by/i }).selectOption('price_asc');
const prices = page.getByTestId('result-price');
const a = parseFloat((await prices.first().textContent()).replace(/[^0-9.]/g, ''));
const b = parseFloat((await prices.nth(1).textContent()).replace(/[^0-9.]/g, ''));
expect(a).toBeLessThanOrEqual(b);
});
test('default sort is relevance', async ({ page }) => {
await expect(page.getByRole('combobox', { name: /sort by/i })).toHaveValue('relevance');
});
});
Variants
| Variant | Description |
|---|---|
| Name A-Z | First result ≤ second alphabetically |
| Name Z-A | First result ≥ second alphabetically |
| Date newest | Dates in descending order |
| Price low-high | Prices in ascending order |
| Price high-low | Prices in descending order |
| Sort + filter | Sort param persists when filter applied |
| Default sort | Relevance selected by default |