npx claudepluginhub montinou/triqual --plugin triqual-pluginWant just this skill?
Then install: npx claudepluginhub u/[userId]/[slug]
Unified Playwright test automation. Default runs full autonomous loop. Use --explore for interactive, --ticket ENG-123 for Linear tickets, --describe for text descriptions.
This skill uses the workspace's default tool permissions.
/test - Unified Test Automation
Generate production-ready Playwright tests with multiple input modes. Default mode runs the full autonomous loop with pattern learning.
Quick Start
/test login # Full autonomous (default) - explore → plan → generate → heal → learn
/test --explore login # Interactive exploration only - opens visible browser
/test --ticket ENG-123 # From Linear ticket - fetches AC, generates tests
/test --describe "..." # From user description - skips exploration
Modes Comparison
| Mode | Explore | Plan | Generate | Heal | Learn |
|---|---|---|---|---|---|
/test login (default) | ✅ Auto | ✅ | ✅ | ✅ 5x | ✅ |
/test --explore login | ✅ Interactive | ❌ | ❌ | ❌ | ❌ |
/test --ticket ENG-123 | ❌ (from AC) | ✅ | ✅ | ✅ | ✅ |
/test --describe "..." | ❌ (from text) | ✅ | ✅ | ✅ | ✅ |
Workflow Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ /test {feature} │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ INPUT SOURCES: │
│ ├── Default → EXPLORE with Playwright MCP │
│ ├── --ticket → Linear ticket acceptance criteria │
│ ├── --describe → User text description │
│ └── --explore → Interactive exploration only (STOPS after explore) │
│ │
│ PHASES: │
│ 0. SETUP → Auto-config, load patterns, discover credentials │
│ 0.6 LOAD CONTEXT → triqual_load_context tool builds context files (MANDATORY, BLOCKING)│
│ 1. EXPLORE → Playwright MCP (skip with --ticket/--describe) │
│ 2. PLAN → Quoth context output + input source │
│ 3. GENERATE → .spec.ts in .draft/tests/ (NEVER directly to tests/) │
│ 4. HEAL LOOP → Run → Fix → Re-run (max 5 iterations) │
│ 5. PROMOTE → User approves → Move to production test directory │
│ 6. LEARN → Save patterns + anti-patterns │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Phase 0: SETUP (Auto-Config & Authentication)
This phase loads configuration and handles authentication before any exploration.
0.1 Load Configuration
Read triqual.config.ts if it exists:
# Check for TypeScript config
ls triqual.config.ts 2>/dev/null
If TypeScript config exists, read it to extract configuration values. The config uses defineConfig from triqual for type safety:
// triqual.config.ts
import { defineConfig } from 'triqual';
export default defineConfig({
project_id: 'my-app',
testDir: './tests',
baseUrl: 'http://localhost:3000',
auth: { strategy: 'storageState', ... },
// ...
});
If missing, prompt user to run /init first or auto-detect basic config.
0.2 Load Existing Patterns
Read from ${PLUGIN_ROOT}/context/:
patterns-learned.json- Successful patterns from previous runsanti-patterns-learned.json- Known failure→fix mappings
0.3 Handle Authentication
Based on auth.strategy from config, authenticate before exploration:
Strategy: storageState (Fastest)
If .auth/user.json exists and is configured:
// Load saved auth state via browser_run_code
mcp__plugin_triqual-plugin_playwright__browser_run_code({
code: `async (page) => {
const fs = require('fs');
const state = JSON.parse(fs.readFileSync('.auth/user.json', 'utf8'));
// Add cookies
if (state.cookies?.length) {
await page.context().addCookies(state.cookies);
}
// Navigate to trigger cookie application
await page.goto('${baseUrl}');
// Restore localStorage if present
if (state.origins?.[0]?.localStorage?.length) {
await page.evaluate((items) => {
items.forEach(({ name, value }) => localStorage.setItem(name, value));
}, state.origins[0].localStorage);
}
return 'Auth state loaded';
}`
})
Strategy: uiLogin (When no saved state)
If credentials are configured but no storageState:
// Navigate to login page
mcp__plugin_triqual-plugin_playwright__browser_navigate({
url: config.auth.uiLogin.loginUrl // e.g., "/login"
})
// Get page snapshot to find form elements
mcp__plugin_triqual-plugin_playwright__browser_snapshot({})
// Fill login form using configured selectors
mcp__plugin_triqual-plugin_playwright__browser_fill_form({
fields: [
{
name: 'Email',
type: 'textbox',
ref: '{email-field-ref}', // From snapshot
value: credentials.email
},
{
name: 'Password',
type: 'textbox',
ref: '{password-field-ref}', // From snapshot
value: credentials.password
}
]
})
// Click submit
mcp__plugin_triqual-plugin_playwright__browser_click({
ref: '{submit-button-ref}',
element: 'Login submit button'
})
// Wait for navigation to success URL
mcp__plugin_triqual-plugin_playwright__browser_wait_for({
text: 'Dashboard' // Or wait for URL change
})
Strategy: setupProject (Playwright native)
If project uses Playwright's setup project pattern:
# Run setup project first
npx playwright test --project=setup
Then proceed with tests that have dependencies: ['setup'].
Strategy: none (No auth needed)
Skip authentication, proceed directly to exploration.
0.4 Verify Authentication
After auth, verify we're logged in:
// Take snapshot to confirm auth state
mcp__plugin_triqual-plugin_playwright__browser_snapshot({})
// Check for logged-in indicators:
// - User avatar/menu visible
// - Dashboard or protected content visible
// - No login form visible
0.5 Optionally Save Auth State
After successful UI login, save state for future runs:
// Save state for future runs (optional)
mcp__plugin_triqual-plugin_playwright__browser_run_code({
code: `async (page) => {
const state = await page.context().storageState();
require('fs').writeFileSync('.auth/user.json', JSON.stringify(state, null, 2));
return 'Auth state saved to .auth/user.json';
}`
})
Phase 0.6: LOAD CONTEXT (EXECUTE IMMEDIATELY AFTER SETUP)
This is a blocking gate. The hooks will BLOCK test writing and test-planner dispatch until context files exist.
Execute Context Loading
Call the context loading tool with just the feature name - the system automatically determines the optimal depth:
triqual_load_context({ feature: "{feature}" })
If you have a Linear ticket:
triqual_load_context({ feature: "{feature}", ticket: "ENG-123" })
If you have a description:
triqual_load_context({ feature: "{feature}", description: "..." })
Wait for the tool to complete before proceeding to Phase 1.
What Happens Automatically
The tool intelligently analyzes your request and optimizes context depth:
- Simple features with existing patterns → Fast local scan
- New test generation → Quoth patterns + codebase analysis
- Complex features or tickets → Full context with failure history
This optimization saves ~70% tokens on average without any manual configuration.
Output Files
Context files are written to .triqual/context/{feature}/:
patterns.md— Proven patterns from Quothcodebase.md— Relevant source files, selectors, routesexisting-tests.md— Reusable tests and page objectssummary.md— Index of all context
Additional files are added automatically when needed:
anti-patterns.md— Known failures to avoidfailures.md— Exolar failure historyrequirements.md— Ticket details
If tool fails (MCP unavailable), retry with force: true or check MCP connectivity with /mcp.
Phase 1: EXPLORE
Default Mode (Autonomous)
After authentication, explore the feature:
// Navigate to feature (already authenticated)
mcp__plugin_triqual-plugin_playwright__browser_navigate({ url: featureUrl })
// Capture state
mcp__plugin_triqual-plugin_playwright__browser_snapshot({})
--explore Mode (Interactive)
For exploration-only mode:
- Detect dev server: Check running servers
- Open visible browser:
headless: false - User controls exploration: Navigate, click, observe
- Take screenshots: Capture key states
- STOP: Do not proceed to Plan/Generate
Output exploration notes but do NOT generate test files.
--ticket Mode (Skip Explore)
Fetch ticket from Linear:
mcp__linear__get_issue({ issueId: "ENG-123" })
Parse acceptance criteria as test requirements. Skip exploration phase.
--describe Mode (Skip Explore)
Use the provided description as test requirements. Skip exploration phase.
Phase 2: PLAN
Using Context Files
test-planner reads the context files at .triqual/context/{feature}/ and uses them for:
- Existing Page Objects (from codebase.md and existing-tests.md)
- Proven patterns (from patterns.md)
- Anti-patterns to avoid (from anti-patterns.md)
- Failure history (from failures.md)
- Locator strategies (from codebase.md and patterns.md)
Create Test Plan
Based on input source (exploration notes, ticket AC, or description):
Output: specs/{feature}-plan.md
# Test Plan: {feature}
## Scope
- Feature: {feature}
- URL: /feature-path
- Auth required: Yes/No
## Test Cases
### TC-001: Should display feature page
- Navigate to /feature-path
- Assert page title is visible
- Assert main elements present
### TC-002: Should submit form successfully
- Fill form with valid data
- Click submit
- Assert success message visible
### TC-003: Should show validation errors
- Submit empty form
- Assert error messages visible
## Page Object
- Use existing: FeaturePage (from Quoth)
- Or create new with identified selectors
Phase 3: GENERATE
Reuse Existing Code (MANDATORY)
Before creating ANY new code, check what already exists:
- Read
.triqual/context/{feature}/existing-tests.mdfor available Page Objects, helpers, fixtures - Read every existing Page Object referenced in the PLAN stage
- Read every existing helper and fixture referenced in the PLAN stage
- IMPORT and USE existing code — do NOT recreate functionality that exists
- Only create new artifacts when nothing existing covers the need
- If creating something new, document WHY in the WRITE stage
Prepare seed.spec.ts
Copy from ${PLUGIN_ROOT}/context/seed.template.ts and customize:
// .draft/tests/seed.spec.ts
import { test as base } from '@playwright/test';
import { testUsers } from '../../shared/test-data/users';
type AuthFixtures = {
authenticatedPage: Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', testUsers.standard.email);
await page.fill('[data-testid="password"]', testUsers.standard.password);
await page.click('[type="submit"]');
await page.waitForURL('**/dashboard');
await use(page);
},
});
Generate Test File
Output: .draft/tests/{feature}.spec.ts
import { test, expect } from './seed.spec';
import { FeaturePage } from '../pages/feature.page';
test.describe('{Feature}', () => {
let featurePage: FeaturePage;
test.beforeEach(async ({ authenticatedPage }) => {
featurePage = new FeaturePage(authenticatedPage);
await featurePage.goto();
});
test('should display feature page', async () => {
await expect(featurePage.title).toBeVisible();
await expect(featurePage.mainContainer).toBeVisible();
});
test('should submit form successfully', async () => {
await featurePage.fillForm({ field1: 'value1' });
await featurePage.submit();
await expect(featurePage.successMessage).toBeVisible();
});
test('should show validation errors', async () => {
await featurePage.submit(); // Empty form
await expect(featurePage.errorMessage).toBeVisible();
});
});
Create Page Object (if needed)
Only if Quoth didn't find an existing one:
// tests/pages/{feature}.page.ts
import { Page, Locator } from '@playwright/test';
export class FeaturePage {
readonly page: Page;
readonly title: Locator;
readonly mainContainer: Locator;
readonly submitButton: Locator;
readonly successMessage: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.title = page.locator('h1');
this.mainContainer = page.locator('[data-testid="feature-container"]');
this.submitButton = page.locator('[data-testid="submit-btn"]');
this.successMessage = page.locator('.success-message');
this.errorMessage = page.locator('.error-message');
}
async goto() {
await this.page.goto('/feature-path');
}
async fillForm(data: Record<string, string>) {
// Fill form fields
}
async submit() {
await this.submitButton.click();
}
}
Phase 4: HEAL LOOP
Run Test
npx playwright test .draft/tests/{feature}.spec.ts --reporter=list
If PASS → Go to Phase 5
If FAIL → Heal (Max 5 Iterations)
4a. Query Exolar for Similar Failures
mcp__exolar-qa__query_exolar_data({
dataset: "failures",
filters: { error_pattern: "{error_message}" }
})
If found, use previous fix as guidance.
4b. Analyze Failure
Common fixes:
| Error | Fix |
|---|---|
strict mode violation | Add :visible or more specific selector |
Timeout exceeded | Add explicit wait or increase timeout |
Element not found | Verify selector with Playwright MCP snapshot |
Multiple elements | Use .first() or nth() with :visible |
4c. Visual Verification (Optional)
If unsure about element existence:
mcp__plugin_triqual-plugin_playwright__browser_navigate({ url: failingPageUrl })
mcp__plugin_triqual-plugin_playwright__browser_snapshot({})
// Inspect snapshot to find correct selector
4d. Apply Fix
Edit the test file with the fix.
4e. Track Anti-Pattern
{
"error": "strict mode violation: getByRole('button') resolved to 3 elements",
"fix": "Use getByRole('button', { name: 'Submit' })",
"feature": "{feature}",
"iteration": 1
}
4f. Re-run
npx playwright test .draft/tests/{feature}.spec.ts --reporter=list
Repeat until passing or max iterations (5) reached.
Phase 5: PROMOTE (Requires User Approval)
⚠️ Promotion MUST NOT happen automatically. Ask the user first.
Ask User for Approval
Present the passing test results and ask:
✅ Tests PASSING in .draft/tests/{feature}.spec.ts
Files ready for promotion:
- .draft/tests/{feature}.spec.ts → tests/{category}/{feature}.spec.ts
- .draft/pages/{Page}.ts → pages/{Page}.ts (if applicable)
**Promote these files to production?** (say "yes" to confirm)
Move from Draft to Production (only after user confirms)
# Determine target directory based on feature
# auth/ for login, signup
# dashboard/ for dashboard, home
# settings/ for settings, profile
mkdir -p tests/{category}/
mv .draft/tests/{feature}.spec.ts tests/{category}/{feature}.spec.ts
mv .draft/pages/*.ts pages/ 2>/dev/null || true # Page Objects if created
Verify Final Location
npx playwright test tests/{category}/{feature}.spec.ts --reporter=list
Phase 6: LEARN
Save Successful Patterns
Append to ${PLUGIN_ROOT}/context/patterns-learned.json:
{
"id": "{feature}-{date}",
"feature": "{feature}",
"date": "2025-01-27",
"selectors": {
"submitButton": "[data-testid='submit-btn']",
"successMessage": ".success-message"
},
"waits": ["waitForURL('**/dashboard')"],
"assertions": ["expect(element).toBeVisible()"]
}
Save Anti-Patterns
Append to ${PLUGIN_ROOT}/context/anti-patterns-learned.json:
{
"id": "{error-type}-{date}",
"error": "strict mode violation...",
"fix": "Use more specific selector with name",
"feature": "{feature}",
"date": "2025-01-27"
}
Propose to Quoth (Significant Discoveries)
If a pattern is reusable across features:
"I discovered that for this project, modals always use [data-testid='modal-overlay'].
Should I propose adding this to Quoth documentation?"
Report Success
## Test Complete: {feature}
**Generated Files:**
- `tests/{category}/{feature}.spec.ts` - 3 test cases
- `tests/pages/{feature}.page.ts` - Page Object
**Test Results:**
- 3/3 tests passing
- 2 heal iterations required
**Patterns Learned:**
- Selector: [data-testid="submit-btn"] for submit buttons
- Wait: Always waitForURL after form submission
**Anti-Patterns Recorded:**
- Don't use getByRole('button') without name in this project
**Next Steps:**
1. Review generated tests
2. Add to version control
3. Run full test suite to ensure no conflicts
Mode-Specific Behavior
--explore Mode
/test --explore login
Behavior:
- Opens visible browser (headless: false)
- Uses Playwright MCP for exploration
- Captures screenshots and observations
- STOPS after exploration - no test generation
Use when you want to:
- Understand the UI before writing tests
- Debug a specific interaction
- Take screenshots for documentation
--ticket Mode
/test --ticket ENG-123
Behavior:
- Fetches ticket from Linear MCP
- Parses acceptance criteria
- Searches Quoth for patterns
- Generates tests from AC (skips exploration)
- Runs heal loop
- Provides PR instructions
--describe Mode
/test --describe "user can reset password via email link"
Behavior:
- Uses description as requirements
- Searches Quoth for patterns
- Generates tests from description (skips exploration)
- Runs heal loop
Troubleshooting
Playwright MCP Not Connected
Run /mcp to check connection status
Test Credentials Not Found
Check these locations:
automation/shared/test-data/users.tstests/fixtures/users.ts.env.testfor environment variables
Heal Loop Exhausted
If 5 iterations fail:
- Review the final error carefully
- Use Playwright MCP for manual exploration
- Check if the feature actually works manually
- Consider if this is a BUG (create Linear ticket) vs TEST_ISSUE
No Config Found
Run /init first, or provide --ticket or --describe with explicit requirements.
What This Skill Does
- Default mode: Full autonomous test generation with learning
- --explore: Interactive browser exploration only
- --ticket: Generate tests from Linear acceptance criteria
- --describe: Generate tests from text description
What This Skill Does NOT Do
- Lint tests for violations (use
/check) - Show best practice rules (use
/rules) - Initialize project config (use
/init, though auto-config works)
Similar Skills
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.