From asi
Automates QA regression testing with Playwright using reusable TypeScript test skills for login, logout, dashboard loads, and user CRUD. Composes into full suites.
npx claudepluginhub plurigrid/asi --plugin asiThis skill uses the workspace's default tool permissions.
Build and run automated regression tests using Playwright. Each test is a reusable skill that can be composed into full test suites.
Plans, organizes, and optimizes Playwright regression test suites for web apps using TypeScript. Covers change-based test selection, sharding, parallel execution, GitHub Actions CI/CD, flaky test management, and suite monitoring.
Tests local web applications using Playwright: setup, navigation, clicks, forms, assertions, screenshots, console logs, and network interception.
Writes and reviews unit, integration, and E2E tests using Jest, Vitest, Playwright. Validates regression, reviews coverage, configures QA strategies before release.
Share bugs, ideas, or general feedback.
Build and run automated regression tests using Playwright. Each test is a reusable skill that can be composed into full test suites.
npm init -y
npm install playwright @playwright/test
npx playwright install
Create tests in tests/ folder:
tests/
├── auth/
│ ├── login.spec.ts
│ └── logout.spec.ts
├── dashboard/
│ └── load.spec.ts
├── users/
│ ├── create.spec.ts
│ └── delete.spec.ts
└── regression.spec.ts # Full suite
// tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('should login with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', process.env.TEST_EMAIL!);
await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD!);
await page.click('[data-testid="submit"]');
// Verify redirect to dashboard
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'wrong@example.com');
await page.fill('[data-testid="password"]', 'wrongpassword');
await page.click('[data-testid="submit"]');
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
});
});
// tests/dashboard/load.spec.ts
import { test, expect } from '@playwright/test';
import { login } from '../helpers/auth';
test.describe('Dashboard', () => {
test.beforeEach(async ({ page }) => {
await login(page);
});
test('should load dashboard within 3 seconds', async ({ page }) => {
const start = Date.now();
await page.goto('/dashboard');
await page.waitForSelector('[data-testid="dashboard-content"]');
const loadTime = Date.now() - start;
expect(loadTime).toBeLessThan(3000);
});
test('should display all widgets', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.locator('[data-testid="stats-widget"]')).toBeVisible();
await expect(page.locator('[data-testid="chart-widget"]')).toBeVisible();
await expect(page.locator('[data-testid="activity-widget"]')).toBeVisible();
});
test('should refresh data on button click', async ({ page }) => {
await page.goto('/dashboard');
const initialValue = await page.locator('[data-testid="last-updated"]').textContent();
await page.click('[data-testid="refresh-button"]');
await page.waitForTimeout(1000);
const newValue = await page.locator('[data-testid="last-updated"]').textContent();
expect(newValue).not.toBe(initialValue);
});
});
// tests/users/create.spec.ts
import { test, expect } from '@playwright/test';
import { login } from '../helpers/auth';
import { generateTestUser, deleteTestUser } from '../helpers/users';
test.describe('User Creation', () => {
let testUser: { email: string; name: string };
test.beforeEach(async ({ page }) => {
await login(page);
testUser = generateTestUser();
});
test.afterEach(async () => {
// Cleanup
await deleteTestUser(testUser.email);
});
test('should create new user successfully', async ({ page }) => {
await page.goto('/users/new');
await page.fill('[data-testid="user-name"]', testUser.name);
await page.fill('[data-testid="user-email"]', testUser.email);
await page.selectOption('[data-testid="user-role"]', 'member');
await page.click('[data-testid="create-user-btn"]');
// Verify success
await expect(page.locator('[data-testid="success-toast"]')).toBeVisible();
await expect(page).toHaveURL(/users/);
// Verify user appears in list
await expect(page.locator(`text=${testUser.email}`)).toBeVisible();
});
test('should validate required fields', async ({ page }) => {
await page.goto('/users/new');
await page.click('[data-testid="create-user-btn"]');
await expect(page.locator('[data-testid="name-error"]')).toBeVisible();
await expect(page.locator('[data-testid="email-error"]')).toBeVisible();
});
});
// tests/helpers/auth.ts
import { Page } from '@playwright/test';
export async function login(page: Page) {
await page.goto('/login');
await page.fill('[data-testid="email"]', process.env.TEST_EMAIL!);
await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD!);
await page.click('[data-testid="submit"]');
await page.waitForURL(/dashboard/);
}
export async function logout(page: Page) {
await page.click('[data-testid="user-menu"]');
await page.click('[data-testid="logout"]');
await page.waitForURL(/login/);
}
// tests/helpers/users.ts
export function generateTestUser() {
const id = Date.now();
return {
name: `Test User ${id}`,
email: `test-${id}@example.com`,
};
}
export async function deleteTestUser(email: string) {
// API call to cleanup test user
await fetch(`${process.env.API_URL}/admin/users`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.ADMIN_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
}
// tests/regression.spec.ts
import { test } from '@playwright/test';
// Import all test suites
import './auth/login.spec';
import './auth/logout.spec';
import './dashboard/load.spec';
import './users/create.spec';
import './users/delete.spec';
test.describe('Full Regression Suite', () => {
// Tests run in order defined above
});
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
# Run all tests
npx playwright test
# Run specific test file
npx playwright test tests/auth/login.spec.ts
# Run tests with UI
npx playwright test --ui
# Run in headed mode (see browser)
npx playwright test --headed
# Generate report
npx playwright show-report
# .github/workflows/regression.yml
name: Regression Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * *' # Daily at 6 AM
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test
env:
BASE_URL: ${{ secrets.STAGING_URL }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
waitForSelector instead of waitForTimeout| Task | Command |
|---|---|
| Run all | npx playwright test |
| Run one file | npx playwright test login.spec.ts |
| Debug mode | npx playwright test --debug |
| UI mode | npx playwright test --ui |
| Update snapshots | npx playwright test --update-snapshots |
This skill connects to the K-Dense-AI/claude-scientific-skills ecosystem:
general: 734 citations in bib.duckdbThis skill maps to Cat# = Comod(P) as a bicomodule in the equipment structure:
Trit: 0 (ERGODIC)
Home: Prof
Poly Op: ⊗
Kan Role: Adj
Color: #26D826
The skill participates in triads satisfying:
(-1) + (0) + (+1) ≡ 0 (mod 3)
This ensures compositional coherence in the Cat# equipment structure.