install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/asi/skills/qa-regression" ~/.claude/skills/plurigrid-asi-qa-regression && rm -rf "$T"
manifest:
plugins/asi/skills/qa-regression/SKILL.mdsource content
QA Regression Testing
Build and run automated regression tests using Playwright. Each test is a reusable skill that can be composed into full test suites.
Setup
npm init -y npm install playwright @playwright/test npx playwright install
Test Structure
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
Common Test Skills
Login Test
// 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(); }); });
Dashboard Load Test
// 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); }); });
Create User Test
// 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(); }); });
Shared Helpers
// 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 }), }); }
Full Regression Suite
// 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
// 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'] }, }, ], });
Running Tests
# 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
CI Integration
# .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/
Best Practices
- Use data-testid attributes - More stable than CSS selectors
- Clean up test data - Always delete what you create
- Avoid hardcoded waits - Use
instead ofwaitForSelectorwaitForTimeout - Run in parallel - Faster feedback on CI
- Screenshot on failure - Easier debugging
- Environment variables - Never commit credentials
Quick Commands
| Task | Command |
|---|---|
| Run all | |
| Run one file | |
| Debug mode | |
| UI mode | |
| Update snapshots | |
Scientific Skill Interleaving
This skill connects to the K-Dense-AI/claude-scientific-skills ecosystem:
Graph Theory
- networkx [○] via bicomodule
- Universal graph hub
Bibliography References
: 734 citations in bib.duckdbgeneral
Cat# Integration
This 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
GF(3) Naturality
The skill participates in triads satisfying:
(-1) + (0) + (+1) ≡ 0 (mod 3)
This ensures compositional coherence in the Cat# equipment structure.