From afd3192965afd5c7a7aaa08d1346650621d46981 Mon Sep 17 00:00:00 2001 From: Alireza Rezvani Date: Thu, 5 Mar 2026 13:51:16 +0100 Subject: [PATCH] Dev (#255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo * ci: Add VirusTotal security scan for skills (#252) * Dev (#231) * Improve senior-fullstack skill description and workflow validation - Expand frontmatter description with concrete actions and trigger clauses - Add validation steps to scaffolding workflow (verify scaffold succeeded) - Add re-run verification step to audit workflow (confirm P0 fixes) * chore: sync codex skills symlinks [automated] * fix(skill): normalize senior-fullstack frontmatter to inline format Normalize YAML description from block scalar (>) to inline single-line format matching all other 50+ skills. Align frontmatter trigger phrases with the body's Trigger Phrases section to eliminate duplication. Co-Authored-By: Claude Opus 4.6 * fix(ci): add GITHUB_TOKEN to checkout + restore corrupted skill descriptions - Add token: ${{ secrets.GITHUB_TOKEN }} to actions/checkout@v4 in sync-codex-skills.yml so git-auto-commit-action can push back to branch (fixes: fatal: could not read Username, exit 128) - Restore correct description for incident-commander (was: 'Skill from engineering-team') - Restore correct description for senior-fullstack (was: '>') * fix(ci): pass PROJECTS_TOKEN to fix automated commits + remove duplicate checkout Fixes PROJECTS_TOKEN passthrough for git-auto-commit-action and removes duplicate checkout step in pr-issue-auto-close workflow. * fix(ci): remove stray merge conflict marker in sync-codex-skills.yml (#221) Co-authored-by: Leo * fix(ci): fix workflow errors + add OpenClaw support (#222) * feat: add 20 new practical skills for professional Claude Code users New skills across 5 categories: Engineering (12): - git-worktree-manager: Parallel dev with port isolation & env sync - ci-cd-pipeline-builder: Generate GitHub Actions/GitLab CI from stack analysis - mcp-server-builder: Build MCP servers from OpenAPI specs - changelog-generator: Conventional commits to structured changelogs - pr-review-expert: Blast radius analysis & security scan for PRs - api-test-suite-builder: Auto-generate test suites from API routes - env-secrets-manager: .env management, leak detection, rotation workflows - database-schema-designer: Requirements to migrations & types - codebase-onboarding: Auto-generate onboarding docs from codebase - performance-profiler: Node/Python/Go profiling & optimization - runbook-generator: Operational runbooks from codebase analysis - monorepo-navigator: Turborepo/Nx/pnpm workspace management Engineering Team (2): - stripe-integration-expert: Subscriptions, webhooks, billing patterns - email-template-builder: React Email/MJML transactional email systems Product Team (3): - saas-scaffolder: Full SaaS project generation from product brief - landing-page-generator: High-converting landing pages with copy frameworks - competitive-teardown: Structured competitive product analysis Business Growth (1): - contract-and-proposal-writer: Contracts, SOWs, NDAs per jurisdiction Marketing (1): - prompt-engineer-toolkit: Systematic prompt development & A/B testing Designed for daily professional use and commercial distribution. * chore: sync codex skills symlinks [automated] * docs: update README with 20 new skills, counts 65→86, new skills section * docs: add commercial distribution plan (Stan Store + Gumroad) * docs: rewrite CHANGELOG.md with v2.0.0 release (65 skills, 9 domains) (#226) * docs: rewrite CHANGELOG.md with v2.0.0 release (65 skills, 9 domains) - Consolidate 191 commits since v1.0.2 into proper v2.0.0 entry - Document 12 POWERFUL-tier skills, 37 refactored skills - Add new domains: business-growth, finance - Document Codex support and marketplace integration - Update version history summary table - Clean up [Unreleased] to only planned work * docs: add 24 POWERFUL-tier skills to plugin, fix counts to 85 across all docs - Add engineering-advanced-skills plugin (24 POWERFUL-tier skills) to marketplace.json - Add 13 missing skills to CHANGELOG v2.0.0 (agent-workflow-designer, api-test-suite-builder, changelog-generator, ci-cd-pipeline-builder, codebase-onboarding, database-schema-designer, env-secrets-manager, git-worktree-manager, mcp-server-builder, monorepo-navigator, performance-profiler, pr-review-expert, runbook-generator) - Fix skill count: 86→85 (excl sample-skill) across README, CHANGELOG, marketplace.json - Fix stale 53→85 references in README - Add engineering-advanced-skills install command to README - Update marketplace.json version to 2.0.0 --------- Co-authored-by: Leo * feat: add skill-security-auditor POWERFUL-tier skill (#230) Security audit and vulnerability scanner for AI agent skills before installation. Scans for: - Code execution risks (eval, exec, os.system, subprocess shell injection) - Data exfiltration (outbound HTTP, credential harvesting, env var extraction) - Prompt injection in SKILL.md (system override, role hijack, safety bypass) - Dependency supply chain (typosquatting, unpinned versions, runtime installs) - File system abuse (boundary violations, binaries, symlinks, hidden files) - Privilege escalation (sudo, SUID, cron manipulation, shell config writes) - Obfuscation (base64, hex encoding, chr chains, codecs) Produces clear PASS/WARN/FAIL verdict with per-finding remediation guidance. Supports local dirs, git repo URLs, JSON output, strict mode, and CI/CD integration. Includes: - scripts/skill_security_auditor.py (1049 lines, zero dependencies) - references/threat-model.md (complete attack vector documentation) - SKILL.md with usage guide and report format Tested against: rag-architect (PASS), agent-designer (PASS), senior-secops (FAIL - correctly flagged eval/exec patterns). Co-authored-by: Leo * docs: add skill-security-auditor to marketplace, README, and CHANGELOG - Add standalone plugin entry for skill-security-auditor in marketplace.json - Update engineering-advanced-skills plugin description to include it - Update skill counts: 85→86 across README, CHANGELOG, marketplace - Add install command to README Quick Install section - Add to CHANGELOG [Unreleased] section --------- Co-authored-by: Baptiste Fernandez Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 Co-authored-by: Leo Co-authored-by: Leo * Dev (#249) * docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo --------- Co-authored-by: Leo * Dev (#250) * docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo --------- Co-authored-by: Leo * ci: add VirusTotal security scan for skills - Scans changed skill directories on PRs to dev/main - Scans all skills on release publish - Posts scan results as PR comment with analysis links - Rate-limited to 4 req/min (free tier compatible) - Appends VirusTotal links to release body on publish * fix: resolve YAML lint errors in virustotal workflow - Add document start marker (---) - Quote 'on' key for truthy lint rule - Remove trailing spaces - Break long lines under 160 char limit --------- Co-authored-by: Baptiste Fernandez Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 Co-authored-by: Leo Co-authored-by: Leo * feat: add playwright-pro plugin — production-grade Playwright testing toolkit (#254) 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 --------- Co-authored-by: Leo Co-authored-by: Baptiste Fernandez Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 Co-authored-by: Leo --- .../playwright-pro/.claude-plugin/plugin.json | 28 ++ engineering-team/playwright-pro/.mcp.json | 27 ++ engineering-team/playwright-pro/CLAUDE.md | 84 ++++++ engineering-team/playwright-pro/LICENSE | 21 ++ engineering-team/playwright-pro/README.md | 133 +++++++++ .../agents/migration-planner.md | 121 ++++++++ .../playwright-pro/agents/test-architect.md | 105 +++++++ .../playwright-pro/agents/test-debugger.md | 117 ++++++++ .../playwright-pro/hooks/detect-playwright.sh | 23 ++ .../playwright-pro/hooks/hooks.json | 25 ++ .../playwright-pro/hooks/validate-test.sh | 58 ++++ .../browserstack-mcp/package.json | 18 ++ .../browserstack-mcp/src/client.ts | 97 +++++++ .../browserstack-mcp/src/index.ts | 183 ++++++++++++ .../browserstack-mcp/src/types.ts | 61 ++++ .../browserstack-mcp/tsconfig.json | 14 + .../integrations/testrail-mcp/package.json | 18 ++ .../integrations/testrail-mcp/src/client.ts | 147 ++++++++++ .../integrations/testrail-mcp/src/index.ts | 270 ++++++++++++++++++ .../integrations/testrail-mcp/src/types.ts | 105 +++++++ .../integrations/testrail-mcp/tsconfig.json | 14 + .../playwright-pro/reference/assertions.md | 89 ++++++ .../reference/common-pitfalls.md | 137 +++++++++ .../playwright-pro/reference/fixtures.md | 121 ++++++++ .../playwright-pro/reference/flaky-tests.md | 56 ++++ .../playwright-pro/reference/golden-rules.md | 12 + .../playwright-pro/reference/locators.md | 77 +++++ engineering-team/playwright-pro/settings.json | 8 + .../skills/browserstack/SKILL.md | 168 +++++++++++ .../playwright-pro/skills/coverage/SKILL.md | 98 +++++++ .../playwright-pro/skills/fix/SKILL.md | 113 ++++++++ .../skills/fix/flaky-taxonomy.md | 134 +++++++++ .../playwright-pro/skills/generate/SKILL.md | 144 ++++++++++ .../skills/generate/patterns.md | 163 +++++++++++ .../playwright-pro/skills/init/SKILL.md | 201 +++++++++++++ .../playwright-pro/skills/migrate/SKILL.md | 135 +++++++++ .../skills/migrate/cypress-mapping.md | 79 +++++ .../skills/migrate/selenium-mapping.md | 94 ++++++ .../playwright-pro/skills/report/SKILL.md | 126 ++++++++ .../playwright-pro/skills/review/SKILL.md | 102 +++++++ .../skills/review/anti-patterns.md | 182 ++++++++++++ .../playwright-pro/skills/testrail/SKILL.md | 129 +++++++++ .../playwright-pro/templates/README.md | 123 ++++++++ .../templates/accessibility/color-contrast.md | 162 +++++++++++ .../accessibility/keyboard-navigation.md | 149 ++++++++++ .../templates/accessibility/screen-reader.md | 159 +++++++++++ .../templates/api/auth-headers.md | 148 ++++++++++ .../templates/api/error-responses.md | 157 ++++++++++ .../playwright-pro/templates/api/graphql.md | 174 +++++++++++ .../templates/api/rate-limiting.md | 152 ++++++++++ .../playwright-pro/templates/api/rest-crud.md | 152 ++++++++++ .../playwright-pro/templates/auth/login.md | 119 ++++++++ .../playwright-pro/templates/auth/logout.md | 112 ++++++++ .../playwright-pro/templates/auth/mfa.md | 125 ++++++++ .../templates/auth/password-reset.md | 129 +++++++++ .../playwright-pro/templates/auth/rbac.md | 132 +++++++++ .../templates/auth/remember-me.md | 127 ++++++++ .../templates/auth/session-timeout.md | 113 ++++++++ .../playwright-pro/templates/auth/sso.md | 115 ++++++++ .../templates/checkout/add-to-cart.md | 112 ++++++++ .../templates/checkout/apply-coupon.md | 123 ++++++++ .../templates/checkout/order-confirm.md | 108 +++++++ .../templates/checkout/order-history.md | 119 ++++++++ .../templates/checkout/payment.md | 148 ++++++++++ .../templates/checkout/update-quantity.md | 125 ++++++++ .../templates/crud/bulk-operations.md | 129 +++++++++ .../playwright-pro/templates/crud/create.md | 118 ++++++++ .../playwright-pro/templates/crud/delete.md | 116 ++++++++ .../playwright-pro/templates/crud/read.md | 117 ++++++++ .../templates/crud/soft-delete.md | 113 ++++++++ .../playwright-pro/templates/crud/update.md | 129 +++++++++ .../templates/dashboard/chart-rendering.md | 131 +++++++++ .../templates/dashboard/data-loading.md | 128 +++++++++ .../templates/dashboard/date-range-filter.md | 136 +++++++++ .../templates/dashboard/export.md | 146 ++++++++++ .../templates/dashboard/realtime-updates.md | 143 ++++++++++ .../templates/forms/autosave.md | 135 +++++++++ .../templates/forms/conditional-fields.md | 120 ++++++++ .../templates/forms/file-upload.md | 136 +++++++++ .../templates/forms/multi-step.md | 137 +++++++++ .../templates/forms/single-step.md | 124 ++++++++ .../templates/forms/validation.md | 141 +++++++++ .../templates/notifications/in-app.md | 125 ++++++++ .../notifications/notification-center.md | 128 +++++++++ .../templates/notifications/toast-messages.md | 139 +++++++++ .../onboarding/email-verification.md | 118 ++++++++ .../templates/onboarding/first-time-setup.md | 130 +++++++++ .../templates/onboarding/registration.md | 131 +++++++++ .../templates/onboarding/welcome-tour.md | 128 +++++++++ .../templates/search/basic-search.md | 118 ++++++++ .../templates/search/empty-state.md | 109 +++++++ .../templates/search/filters.md | 128 +++++++++ .../templates/search/pagination.md | 123 ++++++++ .../templates/search/sorting.md | 131 +++++++++ .../templates/settings/account-delete.md | 136 +++++++++ .../templates/settings/notification-prefs.md | 139 +++++++++ .../templates/settings/password-change.md | 143 ++++++++++ .../templates/settings/profile-update.md | 130 +++++++++ 98 files changed, 11375 insertions(+) create mode 100644 engineering-team/playwright-pro/.claude-plugin/plugin.json create mode 100644 engineering-team/playwright-pro/.mcp.json create mode 100644 engineering-team/playwright-pro/CLAUDE.md create mode 100644 engineering-team/playwright-pro/LICENSE create mode 100644 engineering-team/playwright-pro/README.md create mode 100644 engineering-team/playwright-pro/agents/migration-planner.md create mode 100644 engineering-team/playwright-pro/agents/test-architect.md create mode 100644 engineering-team/playwright-pro/agents/test-debugger.md create mode 100755 engineering-team/playwright-pro/hooks/detect-playwright.sh create mode 100644 engineering-team/playwright-pro/hooks/hooks.json create mode 100755 engineering-team/playwright-pro/hooks/validate-test.sh create mode 100644 engineering-team/playwright-pro/integrations/browserstack-mcp/package.json create mode 100644 engineering-team/playwright-pro/integrations/browserstack-mcp/src/client.ts create mode 100644 engineering-team/playwright-pro/integrations/browserstack-mcp/src/index.ts create mode 100644 engineering-team/playwright-pro/integrations/browserstack-mcp/src/types.ts create mode 100644 engineering-team/playwright-pro/integrations/browserstack-mcp/tsconfig.json create mode 100644 engineering-team/playwright-pro/integrations/testrail-mcp/package.json create mode 100644 engineering-team/playwright-pro/integrations/testrail-mcp/src/client.ts create mode 100644 engineering-team/playwright-pro/integrations/testrail-mcp/src/index.ts create mode 100644 engineering-team/playwright-pro/integrations/testrail-mcp/src/types.ts create mode 100644 engineering-team/playwright-pro/integrations/testrail-mcp/tsconfig.json create mode 100644 engineering-team/playwright-pro/reference/assertions.md create mode 100644 engineering-team/playwright-pro/reference/common-pitfalls.md create mode 100644 engineering-team/playwright-pro/reference/fixtures.md create mode 100644 engineering-team/playwright-pro/reference/flaky-tests.md create mode 100644 engineering-team/playwright-pro/reference/golden-rules.md create mode 100644 engineering-team/playwright-pro/reference/locators.md create mode 100644 engineering-team/playwright-pro/settings.json create mode 100644 engineering-team/playwright-pro/skills/browserstack/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/coverage/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/fix/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/fix/flaky-taxonomy.md create mode 100644 engineering-team/playwright-pro/skills/generate/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/generate/patterns.md create mode 100644 engineering-team/playwright-pro/skills/init/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/migrate/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/migrate/cypress-mapping.md create mode 100644 engineering-team/playwright-pro/skills/migrate/selenium-mapping.md create mode 100644 engineering-team/playwright-pro/skills/report/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/review/SKILL.md create mode 100644 engineering-team/playwright-pro/skills/review/anti-patterns.md create mode 100644 engineering-team/playwright-pro/skills/testrail/SKILL.md create mode 100644 engineering-team/playwright-pro/templates/README.md create mode 100644 engineering-team/playwright-pro/templates/accessibility/color-contrast.md create mode 100644 engineering-team/playwright-pro/templates/accessibility/keyboard-navigation.md create mode 100644 engineering-team/playwright-pro/templates/accessibility/screen-reader.md create mode 100644 engineering-team/playwright-pro/templates/api/auth-headers.md create mode 100644 engineering-team/playwright-pro/templates/api/error-responses.md create mode 100644 engineering-team/playwright-pro/templates/api/graphql.md create mode 100644 engineering-team/playwright-pro/templates/api/rate-limiting.md create mode 100644 engineering-team/playwright-pro/templates/api/rest-crud.md create mode 100644 engineering-team/playwright-pro/templates/auth/login.md create mode 100644 engineering-team/playwright-pro/templates/auth/logout.md create mode 100644 engineering-team/playwright-pro/templates/auth/mfa.md create mode 100644 engineering-team/playwright-pro/templates/auth/password-reset.md create mode 100644 engineering-team/playwright-pro/templates/auth/rbac.md create mode 100644 engineering-team/playwright-pro/templates/auth/remember-me.md create mode 100644 engineering-team/playwright-pro/templates/auth/session-timeout.md create mode 100644 engineering-team/playwright-pro/templates/auth/sso.md create mode 100644 engineering-team/playwright-pro/templates/checkout/add-to-cart.md create mode 100644 engineering-team/playwright-pro/templates/checkout/apply-coupon.md create mode 100644 engineering-team/playwright-pro/templates/checkout/order-confirm.md create mode 100644 engineering-team/playwright-pro/templates/checkout/order-history.md create mode 100644 engineering-team/playwright-pro/templates/checkout/payment.md create mode 100644 engineering-team/playwright-pro/templates/checkout/update-quantity.md create mode 100644 engineering-team/playwright-pro/templates/crud/bulk-operations.md create mode 100644 engineering-team/playwright-pro/templates/crud/create.md create mode 100644 engineering-team/playwright-pro/templates/crud/delete.md create mode 100644 engineering-team/playwright-pro/templates/crud/read.md create mode 100644 engineering-team/playwright-pro/templates/crud/soft-delete.md create mode 100644 engineering-team/playwright-pro/templates/crud/update.md create mode 100644 engineering-team/playwright-pro/templates/dashboard/chart-rendering.md create mode 100644 engineering-team/playwright-pro/templates/dashboard/data-loading.md create mode 100644 engineering-team/playwright-pro/templates/dashboard/date-range-filter.md create mode 100644 engineering-team/playwright-pro/templates/dashboard/export.md create mode 100644 engineering-team/playwright-pro/templates/dashboard/realtime-updates.md create mode 100644 engineering-team/playwright-pro/templates/forms/autosave.md create mode 100644 engineering-team/playwright-pro/templates/forms/conditional-fields.md create mode 100644 engineering-team/playwright-pro/templates/forms/file-upload.md create mode 100644 engineering-team/playwright-pro/templates/forms/multi-step.md create mode 100644 engineering-team/playwright-pro/templates/forms/single-step.md create mode 100644 engineering-team/playwright-pro/templates/forms/validation.md create mode 100644 engineering-team/playwright-pro/templates/notifications/in-app.md create mode 100644 engineering-team/playwright-pro/templates/notifications/notification-center.md create mode 100644 engineering-team/playwright-pro/templates/notifications/toast-messages.md create mode 100644 engineering-team/playwright-pro/templates/onboarding/email-verification.md create mode 100644 engineering-team/playwright-pro/templates/onboarding/first-time-setup.md create mode 100644 engineering-team/playwright-pro/templates/onboarding/registration.md create mode 100644 engineering-team/playwright-pro/templates/onboarding/welcome-tour.md create mode 100644 engineering-team/playwright-pro/templates/search/basic-search.md create mode 100644 engineering-team/playwright-pro/templates/search/empty-state.md create mode 100644 engineering-team/playwright-pro/templates/search/filters.md create mode 100644 engineering-team/playwright-pro/templates/search/pagination.md create mode 100644 engineering-team/playwright-pro/templates/search/sorting.md create mode 100644 engineering-team/playwright-pro/templates/settings/account-delete.md create mode 100644 engineering-team/playwright-pro/templates/settings/notification-prefs.md create mode 100644 engineering-team/playwright-pro/templates/settings/password-change.md create mode 100644 engineering-team/playwright-pro/templates/settings/profile-update.md diff --git a/engineering-team/playwright-pro/.claude-plugin/plugin.json b/engineering-team/playwright-pro/.claude-plugin/plugin.json new file mode 100644 index 0000000..c2dd822 --- /dev/null +++ b/engineering-team/playwright-pro/.claude-plugin/plugin.json @@ -0,0 +1,28 @@ +{ + "name": "pw", + "description": "Production-grade Playwright testing toolkit. Generate tests from specs, fix flaky failures, migrate from Cypress/Selenium, sync with TestRail, run on BrowserStack. 55+ ready-to-use templates, 3 specialized agents, smart reporting that plugs into your existing workflow.", + "version": "1.0.0", + "author": { + "name": "Reza Rezvani", + "email": "reza.rezvani73@googlemail.com" + }, + "homepage": "https://github.com/alirezarezvani/claude-skills/tree/main/engineering-team/playwright-pro", + "repository": { + "type": "git", + "url": "https://github.com/alirezarezvani/claude-skills" + }, + "license": "MIT", + "keywords": [ + "playwright", + "testing", + "e2e", + "qa", + "browserstack", + "testrail", + "test-automation", + "cross-browser", + "migration", + "cypress", + "selenium" + ] +} diff --git a/engineering-team/playwright-pro/.mcp.json b/engineering-team/playwright-pro/.mcp.json new file mode 100644 index 0000000..1c435e8 --- /dev/null +++ b/engineering-team/playwright-pro/.mcp.json @@ -0,0 +1,27 @@ +{ + "mcpServers": { + "pw-testrail": { + "command": "npx", + "args": [ + "tsx", + "${CLAUDE_PLUGIN_ROOT}/integrations/testrail-mcp/src/index.ts" + ], + "env": { + "TESTRAIL_URL": "${TESTRAIL_URL}", + "TESTRAIL_USER": "${TESTRAIL_USER}", + "TESTRAIL_API_KEY": "${TESTRAIL_API_KEY}" + } + }, + "pw-browserstack": { + "command": "npx", + "args": [ + "tsx", + "${CLAUDE_PLUGIN_ROOT}/integrations/browserstack-mcp/src/index.ts" + ], + "env": { + "BROWSERSTACK_USERNAME": "${BROWSERSTACK_USERNAME}", + "BROWSERSTACK_ACCESS_KEY": "${BROWSERSTACK_ACCESS_KEY}" + } + } + } +} diff --git a/engineering-team/playwright-pro/CLAUDE.md b/engineering-team/playwright-pro/CLAUDE.md new file mode 100644 index 0000000..2bb253b --- /dev/null +++ b/engineering-team/playwright-pro/CLAUDE.md @@ -0,0 +1,84 @@ +# Playwright Pro — Agent Context + +You are working in a project with the Playwright Pro plugin installed. Follow these rules for all test-related work. + +## Golden Rules (Non-Negotiable) + +1. **`getByRole()` over CSS/XPath** — resilient to markup changes, mirrors how users see the page +2. **Never `page.waitForTimeout()`** — use `expect(locator).toBeVisible()` or `page.waitForURL()` +3. **Web-first assertions** — `expect(locator)` auto-retries; `expect(await locator.textContent())` does not +4. **Isolate every test** — no shared state, no execution-order dependencies +5. **`baseURL` in config** — zero hardcoded URLs in tests +6. **Retries: `2` in CI, `0` locally** — surface flakiness where it matters +7. **Traces: `'on-first-retry'`** — rich debugging without CI slowdown +8. **Fixtures over globals** — share state via `test.extend()`, not module-level variables +9. **One behavior per test** — multiple related `expect()` calls are fine +10. **Mock external services only** — never mock your own app + +## Locator Priority + +Always use the first option that works: + +```typescript +page.getByRole('button', { name: 'Submit' }) // 1. Role (default) +page.getByLabel('Email address') // 2. Label (form fields) +page.getByText('Welcome back') // 3. Text (non-interactive) +page.getByPlaceholder('Search...') // 4. Placeholder +page.getByAltText('Company logo') // 5. Alt text (images) +page.getByTitle('Close dialog') // 6. Title attribute +page.getByTestId('checkout-summary') // 7. Test ID (last semantic) +page.locator('.legacy-widget') // 8. CSS (last resort) +``` + +## How to Use This Plugin + +### Generating Tests + +When generating tests, always: + +1. Use the `Explore` subagent to scan the project structure first +2. Check `playwright.config.ts` for `testDir`, `baseURL`, and project settings +3. Load relevant templates from `templates/` directory +4. Match the project's language (check for `tsconfig.json` → TypeScript, else JavaScript) +5. Place tests in the configured `testDir` (default: `tests/` or `e2e/`) +6. Include a descriptive test name that explains the behavior being verified + +### Reviewing Tests + +When reviewing, check against: + +1. All 10 golden rules above +2. The anti-patterns in `skills/review/anti-patterns.md` +3. Missing edge cases (empty state, error state, loading state) +4. Proper use of fixtures for shared setup + +### Fixing Flaky Tests + +When fixing flaky tests: + +1. Categorize first: timing, isolation, environment, or infrastructure +2. Use `npx playwright test --repeat-each=10` to reproduce +3. Use `--trace=on` for every attempt +4. Apply the targeted fix from `skills/fix/flaky-taxonomy.md` + +### Using Built-in Commands + +Leverage Claude Code's built-in capabilities: + +- **Large migrations**: Use `/batch` for parallel file-by-file conversion +- **Post-generation cleanup**: Use `/simplify` after generating a test suite +- **Debugging sessions**: Use `/debug` alongside `/pw:fix` for trace analysis +- **Code review**: Use `/review` for general code quality, `/pw:review` for Playwright-specific + +### Integrations + +- **TestRail**: Configured via `TESTRAIL_URL`, `TESTRAIL_USER`, `TESTRAIL_API_KEY` env vars +- **BrowserStack**: Configured via `BROWSERSTACK_USERNAME`, `BROWSERSTACK_ACCESS_KEY` env vars +- Both are optional. The plugin works fully without them. + +## File Conventions + +- Test files: `*.spec.ts` or `*.spec.js` +- Page objects: `*.page.ts` in a `pages/` directory +- Fixtures: `fixtures.ts` or `fixtures/` directory +- Test data: `test-data/` directory with JSON/factory files diff --git a/engineering-team/playwright-pro/LICENSE b/engineering-team/playwright-pro/LICENSE new file mode 100644 index 0000000..d06c943 --- /dev/null +++ b/engineering-team/playwright-pro/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Reza Rezvani + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/engineering-team/playwright-pro/README.md b/engineering-team/playwright-pro/README.md new file mode 100644 index 0000000..6f5dc03 --- /dev/null +++ b/engineering-team/playwright-pro/README.md @@ -0,0 +1,133 @@ +# Playwright Pro + +> Production-grade Playwright testing toolkit for AI coding agents. + +Generate tests, fix flaky failures, migrate from Cypress/Selenium, sync with TestRail, run on BrowserStack — all from your AI agent. + +## Install + +```bash +# Claude Code plugin +claude plugin install pw@claude-skills + +# Or load directly +claude --plugin-dir ./engineering-team/playwright-pro +``` + +## Commands + +| Command | What it does | +|---|---| +| `/pw:init` | Set up Playwright in your project — detects framework, generates config, CI, first test | +| `/pw:generate ` | Generate tests from a user story, URL, or component name | +| `/pw:review` | Review existing tests for anti-patterns and coverage gaps | +| `/pw:fix ` | Diagnose and fix a failing or flaky test | +| `/pw:migrate` | Migrate from Cypress or Selenium to Playwright | +| `/pw:coverage` | Analyze what's tested vs. what's missing | +| `/pw:testrail` | Sync with TestRail — read cases, push results, create runs | +| `/pw:browserstack` | Run tests on BrowserStack, pull cross-browser reports | +| `/pw:report` | Generate a test report in your preferred format | + +## Quick Start + +```bash +# In Claude Code: +/pw:init # Set up Playwright +/pw:generate "user can log in" # Generate your first test +# Tests are auto-validated by hooks — no extra steps +``` + +## What's Inside + +### 9 Skills + +Slash commands that turn natural language into production-ready Playwright tests. Each skill leverages Claude Code's built-in capabilities (`/batch` for parallel work, `Explore` for codebase analysis, `/debug` for trace inspection). + +### 3 Specialized Agents + +- **test-architect** — Plans test strategy for complex applications +- **test-debugger** — Diagnoses flaky tests using a systematic taxonomy +- **migration-planner** — Creates file-by-file migration plans from Cypress/Selenium + +### 55 Test Templates + +Ready-to-use, parametrizable templates covering: + +| Category | Count | Examples | +|---|---|---| +| Authentication | 8 | Login, logout, SSO, MFA, password reset, RBAC | +| CRUD | 6 | Create, read, update, delete, bulk ops | +| Checkout | 6 | Cart, payment, coupon, order history | +| Search | 5 | Basic search, filters, sorting, pagination | +| Forms | 6 | Multi-step, validation, file upload | +| Dashboard | 5 | Data loading, charts, export | +| Settings | 4 | Profile, password, notifications | +| Onboarding | 4 | Registration, email verify, welcome tour | +| Notifications | 3 | In-app, toast, notification center | +| API | 5 | REST CRUD, GraphQL, error handling | +| Accessibility | 3 | Keyboard nav, screen reader, contrast | + +### 2 MCP Integrations + +- **TestRail** — Read test cases, create runs, push pass/fail results +- **BrowserStack** — Trigger cross-browser runs, pull session reports with video/screenshots + +### Smart Hooks + +- Auto-validates test quality when you write `*.spec.ts` files +- Auto-detects Playwright projects on session start +- Zero configuration required + +## Integrations Setup + +### TestRail (Optional) + +Set environment variables: + +```bash +export TESTRAIL_URL="https://your-instance.testrail.io" +export TESTRAIL_USER="your@email.com" +export TESTRAIL_API_KEY="your-api-key" +``` + +Then use `/pw:testrail` to sync test cases and push results. + +### BrowserStack (Optional) + +```bash +export BROWSERSTACK_USERNAME="your-username" +export BROWSERSTACK_ACCESS_KEY="your-access-key" +``` + +Then use `/pw:browserstack` to run tests across browsers. + +## Works With + +| Agent | How | +|---|---| +| **Claude Code** | Full plugin — slash commands, MCP tools, hooks, agents | +| **Codex CLI** | Copy `CLAUDE.md` to your project root as `AGENTS.md` | +| **OpenClaw** | Use as a skill with `SKILL.md` entry point | + +## Built-in Command Integration + +Playwright Pro doesn't reinvent what your AI agent already does. It orchestrates built-in capabilities: + +- `/pw:generate` uses Claude's `Explore` subagent to understand your codebase before generating tests +- `/pw:migrate` uses `/batch` for parallel file-by-file conversion on large test suites +- `/pw:fix` uses `/debug` for trace analysis alongside Playwright-specific diagnostics +- `/pw:review` extends `/review` with Playwright anti-pattern detection + +## Reference + +Based on battle-tested patterns from production test suites. Includes curated guidance on: + +- Locator strategies and priority hierarchy +- Assertion patterns and auto-retry behavior +- Fixture architecture and composition +- Common pitfalls (top 20, ranked by frequency) +- Flaky test diagnosis taxonomy + +## License + +MIT diff --git a/engineering-team/playwright-pro/agents/migration-planner.md b/engineering-team/playwright-pro/agents/migration-planner.md new file mode 100644 index 0000000..3e5de3a --- /dev/null +++ b/engineering-team/playwright-pro/agents/migration-planner.md @@ -0,0 +1,121 @@ +--- +name: migration-planner +description: >- + Analyzes Cypress or Selenium test suites and creates a file-by-file + migration plan. Invoked by /pw:migrate before conversion starts. +allowed-tools: + - Read + - Grep + - Glob + - LS +--- + +# Migration Planner Agent + +You are a test migration specialist. Your job is to analyze an existing Cypress or Selenium test suite and create a detailed, ordered migration plan. + +## Planning Protocol + +### Step 1: Detect Source Framework + +Scan the project: + +**Cypress indicators:** +- `cypress/` directory +- `cypress.config.ts` or `cypress.config.js` +- `@cypress` packages in `package.json` +- `.cy.ts` or `.cy.js` test files + +**Selenium indicators:** +- `selenium-webdriver` in dependencies +- `webdriver` or `wdio` in dependencies +- Test files importing `selenium-webdriver` +- `chromedriver` or `geckodriver` in dependencies +- Python files importing `selenium` + +### Step 2: Inventory All Test Files + +List every test file with: +- File path +- Number of tests (count `it()`, `test()`, or test methods) +- Dependencies (custom commands, page objects, fixtures) +- Complexity (simple/medium/complex based on lines and patterns) + +``` +## Test Inventory + +| # | File | Tests | Dependencies | Complexity | +|---|---|---|---|---| +| 1 | cypress/e2e/login.cy.ts | 5 | login command | Simple | +| 2 | cypress/e2e/checkout.cy.ts | 12 | api helpers, fixtures | Complex | +| 3 | cypress/e2e/search.cy.ts | 8 | none | Medium | +``` + +### Step 3: Map Dependencies + +Identify shared resources that need migration: + +**Custom commands** (`cypress/support/commands.ts`): +- List each command and what it does +- Map to Playwright equivalent (fixture, helper function, or page object) + +**Fixtures** (`cypress/fixtures/`): +- List data files +- Plan: copy to `test-data/` with any format adjustments + +**Plugins** (`cypress/plugins/`): +- List plugin functionality +- Map to Playwright config options or fixtures + +**Page Objects** (if used): +- List page object files +- Plan: convert API calls (minimal structural change) + +**Support files** (`cypress/support/`): +- List setup/teardown logic +- Map to `playwright.config.ts` or `fixtures/` + +### Step 4: Determine Migration Order + +Order files by dependency graph: + +1. **Shared resources first**: custom commands → fixtures, page objects → helpers +2. **Simple tests next**: files with no dependencies, few tests +3. **Complex tests last**: files with many dependencies, custom commands + +``` +## Migration Order + +### Phase 1: Foundation (do first) +1. Convert custom commands → fixtures.ts +2. Copy fixtures → test-data/ +3. Convert page objects (API changes only) + +### Phase 2: Simple Tests (quick wins) +4. login.cy.ts → auth/login.spec.ts (5 tests, ~15 min) +5. about.cy.ts → static/about.spec.ts (2 tests, ~5 min) + +### Phase 3: Complex Tests +6. checkout.cy.ts → checkout/checkout.spec.ts (12 tests, ~45 min) +7. search.cy.ts → search/search.spec.ts (8 tests, ~30 min) +``` + +### Step 5: Estimate Effort + +| Complexity | Time per test | Notes | +|---|---|---| +| Simple | 2-3 min | Direct API mapping | +| Medium | 5-10 min | Needs locator upgrade | +| Complex | 10-20 min | Custom commands, plugins, complex flows | + +### Step 6: Identify Risks + +Flag tests that may need manual intervention: +- Tests using Cypress-only features (`cy.origin()`, `cy.session()`) +- Tests with complex `cy.intercept()` patterns +- Tests relying on Cypress retry-ability semantics +- Tests using Cypress plugins with no Playwright equivalent + +### Step 7: Return Plan + +Return the complete migration plan to `/pw:migrate` for execution. diff --git a/engineering-team/playwright-pro/agents/test-architect.md b/engineering-team/playwright-pro/agents/test-architect.md new file mode 100644 index 0000000..f0cf0ee --- /dev/null +++ b/engineering-team/playwright-pro/agents/test-architect.md @@ -0,0 +1,105 @@ +--- +name: test-architect +description: >- + Plans test strategy for complex applications. Invoked by /pw:generate and + /pw:coverage when the app has multiple routes, complex state, or requires + a structured test plan before writing tests. +allowed-tools: + - Read + - Grep + - Glob + - LS +--- + +# Test Architect Agent + +You are a test architecture specialist. Your job is to analyze an application's structure and create a comprehensive test plan before any tests are written. + +## Your Responsibilities + +1. **Map the application surface**: routes, components, API endpoints, user flows +2. **Identify critical paths**: the flows that, if broken, cause revenue loss or user churn +3. **Design test structure**: folder organization, fixture strategy, data management +4. **Prioritize**: which tests deliver the most confidence per effort +5. **Select patterns**: which template or approach fits each test scenario + +## How You Work + +You are a read-only agent. You analyze and plan — you do not write test files. + +### Step 1: Scan the Codebase + +- Read route definitions (Next.js `app/`, React Router, Vue Router, Angular routes) +- Read `package.json` for framework and dependencies +- Check for existing tests and their patterns +- Identify state management (Redux, Zustand, Pinia, etc.) +- Check for API layer (REST, GraphQL, tRPC) + +### Step 2: Catalog Testable Surfaces + +Create a structured inventory: + +``` +## Application Surface + +### Pages (by priority) +1. /login — Auth entry point [CRITICAL] +2. /dashboard — Main user view [CRITICAL] +3. /settings — User preferences [HIGH] +4. /admin — Admin panel [HIGH] +5. /about — Static page [LOW] + +### Interactive Components +1. SearchBar — complex state, debounced API calls +2. DataTable — sorting, filtering, pagination +3. FileUploader — drag-drop, progress, error handling + +### API Endpoints +1. POST /api/auth/login — authentication +2. GET /api/users — user list with pagination +3. PUT /api/users/:id — user update + +### User Flows (multi-page) +1. Registration → Email Verify → Onboarding → Dashboard +2. Search → Filter → Select → Add to Cart → Checkout → Confirm +``` + +### Step 3: Design Test Plan + +``` +## Test Plan + +### Folder Structure +e2e/ +├── auth/ # Authentication tests +├── dashboard/ # Dashboard tests +├── checkout/ # Checkout flow tests +├── fixtures/ # Shared fixtures +├── pages/ # Page object models +└── test-data/ # Test data files + +### Fixture Strategy +- Auth fixture: shared `storageState` for logged-in tests +- API fixture: request context for data seeding +- Data fixture: factory functions for test entities + +### Test Distribution +| Area | Tests | Template | Effort | +|---|---|---|---| +| Auth | 8 | auth/* | 1h | +| Dashboard | 6 | dashboard/* | 1h | +| Checkout | 10 | checkout/* | 2h | +| Search | 5 | search/* | 45m | +| Settings | 4 | settings/* | 30m | +| API | 5 | api/* | 45m | + +### Priority Order +1. Auth (blocks everything else) +2. Core user flow (the main thing users do) +3. Payment/checkout (revenue-critical) +4. Everything else +``` + +### Step 4: Return Plan + +Return the complete plan to the calling skill. Do not write files. diff --git a/engineering-team/playwright-pro/agents/test-debugger.md b/engineering-team/playwright-pro/agents/test-debugger.md new file mode 100644 index 0000000..67a96a1 --- /dev/null +++ b/engineering-team/playwright-pro/agents/test-debugger.md @@ -0,0 +1,117 @@ +--- +name: test-debugger +description: >- + Diagnoses flaky or failing Playwright tests using systematic taxonomy. + Invoked by /pw:fix when a test needs deep analysis including running + tests, reading traces, and identifying root causes. +allowed-tools: + - Read + - Grep + - Glob + - LS + - Bash +--- + +# Test Debugger Agent + +You are a Playwright test debugging specialist. Your job is to systematically diagnose why a test fails or behaves flakily, identify the root cause category, and return a specific fix. + +## Debugging Protocol + +### Step 1: Read the Test + +Read the test file and understand: +- What behavior it's testing +- Which pages/URLs it visits +- Which locators it uses +- Which assertions it makes +- Any setup/teardown (fixtures, beforeEach) + +### Step 2: Run the Test + +Run it multiple ways to classify the failure: + +```bash +# Single run — get the error +npx playwright test --grep "" --reporter=list 2>&1 + +# Burn-in — expose timing issues +npx playwright test --grep "" --repeat-each=10 --reporter=list 2>&1 + +# Isolation check — expose state leaks +npx playwright test --grep "" --workers=1 --reporter=list 2>&1 + +# Full suite — expose interaction +npx playwright test --reporter=list 2>&1 +``` + +### Step 3: Capture Trace + +```bash +npx playwright test --grep "" --trace=on --retries=0 2>&1 +``` + +Read the trace output for: +- Network requests that failed or were slow +- Elements that weren't visible when expected +- Navigation timing issues +- Console errors + +### Step 4: Classify + +| Category | Evidence | +|---|---| +| **Timing/Async** | Fails on `--repeat-each=10`; error mentions timeout or element not found intermittently | +| **Test Isolation** | Passes alone (`--workers=1 --grep`), fails in full suite | +| **Environment** | Passes locally, fails in CI (check viewport, fonts, timezone) | +| **Infrastructure** | Random crash errors, OOM, browser process killed | + +### Step 5: Identify Specific Cause + +Common root causes per category: + +**Timing:** +- Missing `await` on a Playwright call +- `waitForTimeout()` that's too short +- Clicking before element is actionable +- Asserting before data loads +- Animation interference + +**Isolation:** +- Global variable shared between tests +- Database not cleaned between tests +- localStorage/cookies leaking +- Test creates data with non-unique identifier + +**Environment:** +- Different viewport size in CI +- Font rendering differences affect screenshots +- Timezone affects date assertions +- Network latency in CI is higher + +**Infrastructure:** +- Browser runs out of memory with too many workers +- File system race condition +- DNS resolution failure + +### Step 6: Return Diagnosis + +Return to the calling skill: + +``` +## Diagnosis + +**Category:** Timing/Async +**Root Cause:** Missing await on line 23 — `page.goto('/dashboard')` runs without +waiting, so the assertion on line 24 runs before navigation completes. +**Evidence:** Fails 3/10 times on `--repeat-each=10`. Trace shows assertion firing +before navigation response received. + +## Fix + +Line 23: Add `await` before `page.goto('/dashboard')` + +## Verification + +After fix: 10/10 passes on `--repeat-each=10` +``` diff --git a/engineering-team/playwright-pro/hooks/detect-playwright.sh b/engineering-team/playwright-pro/hooks/detect-playwright.sh new file mode 100755 index 0000000..1089813 --- /dev/null +++ b/engineering-team/playwright-pro/hooks/detect-playwright.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Session start hook: detects if the project uses Playwright. +# Outputs context hint for Claude if playwright.config exists. + +set -euo pipefail + +# Check for Playwright config in current directory or common locations +PW_CONFIG="" +for config in playwright.config.ts playwright.config.js playwright.config.mjs; do + if [[ -f "$config" ]]; then + PW_CONFIG="$config" + break + fi +done + +if [[ -z "$PW_CONFIG" ]]; then + exit 0 +fi + +# Count existing test files +TEST_COUNT=$(find . -name "*.spec.ts" -o -name "*.spec.js" -o -name "*.test.ts" -o -name "*.test.js" 2>/dev/null | grep -v node_modules | wc -l | tr -d ' ') + +echo "🎭 Playwright detected ($PW_CONFIG) — $TEST_COUNT test files found. Use /pw: commands for testing workflows." diff --git a/engineering-team/playwright-pro/hooks/hooks.json b/engineering-team/playwright-pro/hooks/hooks.json new file mode 100644 index 0000000..aede756 --- /dev/null +++ b/engineering-team/playwright-pro/hooks/hooks.json @@ -0,0 +1,25 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/validate-test.sh" + } + ] + } + ], + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/detect-playwright.sh" + } + ] + } + ] + } +} diff --git a/engineering-team/playwright-pro/hooks/validate-test.sh b/engineering-team/playwright-pro/hooks/validate-test.sh new file mode 100755 index 0000000..055b33c --- /dev/null +++ b/engineering-team/playwright-pro/hooks/validate-test.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Post-write hook: validates Playwright test files for common anti-patterns. +# Runs silently — only outputs warnings if issues found. +# Input: JSON on stdin with tool_input.file_path + +set -euo pipefail + +# Read the file path from stdin JSON +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | python3 -c " +import sys, json +try: + data = json.load(sys.stdin) + print(data.get('tool_input', {}).get('file_path', '')) +except: + print('') +" 2>/dev/null || echo "") + +# Only check .spec.ts and .spec.js files +if [[ ! "$FILE_PATH" =~ \.(spec|test)\.(ts|js|mjs)$ ]]; then + exit 0 +fi + +# Check if file exists +if [[ ! -f "$FILE_PATH" ]]; then + exit 0 +fi + +WARNINGS="" + +# Check for waitForTimeout +if grep -n 'waitForTimeout' "$FILE_PATH" >/dev/null 2>&1; then + LINES=$(grep -n 'waitForTimeout' "$FILE_PATH" | head -3) + WARNINGS="${WARNINGS}\n⚠️ waitForTimeout() found — use web-first assertions instead:\n${LINES}\n" +fi + +# Check for non-web-first assertions +if grep -n 'expect(await ' "$FILE_PATH" >/dev/null 2>&1; then + LINES=$(grep -n 'expect(await ' "$FILE_PATH" | head -3) + WARNINGS="${WARNINGS}\n⚠️ Non-web-first assertion — use expect(locator) instead:\n${LINES}\n" +fi + +# Check for hardcoded localhost URLs +if grep -n "http://localhost\|https://localhost\|http://127.0.0.1" "$FILE_PATH" >/dev/null 2>&1; then + LINES=$(grep -n "http://localhost\|https://localhost\|http://127.0.0.1" "$FILE_PATH" | head -3) + WARNINGS="${WARNINGS}\n⚠️ Hardcoded URL — use baseURL from config:\n${LINES}\n" +fi + +# Check for page.$() usage +if grep -n 'page\.\$(' "$FILE_PATH" >/dev/null 2>&1; then + LINES=$(grep -n 'page\.\$(' "$FILE_PATH" | head -3) + WARNINGS="${WARNINGS}\n⚠️ page.\$() is deprecated — use page.locator() or getByRole():\n${LINES}\n" +fi + +# Output warnings if any found +if [[ -n "$WARNINGS" ]]; then + echo -e "\n🎭 Playwright Pro — Test Validation${WARNINGS}" +fi diff --git a/engineering-team/playwright-pro/integrations/browserstack-mcp/package.json b/engineering-team/playwright-pro/integrations/browserstack-mcp/package.json new file mode 100644 index 0000000..4f85b3e --- /dev/null +++ b/engineering-team/playwright-pro/integrations/browserstack-mcp/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pw/browserstack-mcp", + "version": "1.0.0", + "description": "MCP server for BrowserStack integration with Playwright Pro", + "type": "module", + "main": "src/index.ts", + "scripts": { + "start": "tsx src/index.ts", + "build": "tsc" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0" + }, + "devDependencies": { + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } +} diff --git a/engineering-team/playwright-pro/integrations/browserstack-mcp/src/client.ts b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/client.ts new file mode 100644 index 0000000..be36ee0 --- /dev/null +++ b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/client.ts @@ -0,0 +1,97 @@ +import type { + BrowserStackConfig, + BrowserStackPlan, + BrowserStackBrowser, + BrowserStackBuild, + BrowserStackSession, + BrowserStackSessionUpdate, +} from './types.js'; + +export class BrowserStackClient { + private readonly baseUrl = 'https://api.browserstack.com'; + private readonly headers: Record; + + constructor(config: BrowserStackConfig) { + const auth = Buffer.from(`${config.username}:${config.accessKey}`).toString('base64'); + this.headers = { + Authorization: `Basic ${auth}`, + 'Content-Type': 'application/json', + }; + } + + private async request( + method: string, + endpoint: string, + body?: unknown, + ): Promise { + const url = `${this.baseUrl}${endpoint}`; + const options: RequestInit = { + method, + headers: this.headers, + }; + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(url, options); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `BrowserStack API error ${response.status}: ${errorText}`, + ); + } + + return response.json() as Promise; + } + + async getPlan(): Promise { + return this.request('GET', '/automate/plan.json'); + } + + async getBrowsers(): Promise { + return this.request('GET', '/automate/browsers.json'); + } + + async getBuilds(limit?: number, status?: string): Promise { + let endpoint = '/automate/builds.json'; + const params: string[] = []; + if (limit) params.push(`limit=${limit}`); + if (status) params.push(`status=${status}`); + if (params.length > 0) endpoint += `?${params.join('&')}`; + return this.request('GET', endpoint); + } + + async getSessions(buildId: string, limit?: number): Promise { + let endpoint = `/automate/builds/${buildId}/sessions.json`; + if (limit) endpoint += `?limit=${limit}`; + return this.request('GET', endpoint); + } + + async getSession(sessionId: string): Promise { + return this.request( + 'GET', + `/automate/sessions/${sessionId}.json`, + ); + } + + async updateSession( + sessionId: string, + update: BrowserStackSessionUpdate, + ): Promise { + return this.request( + 'PUT', + `/automate/sessions/${sessionId}.json`, + update, + ); + } + + async getSessionLogs(sessionId: string): Promise { + const url = `${this.baseUrl}/automate/sessions/${sessionId}/logs`; + const response = await fetch(url, { headers: this.headers }); + if (!response.ok) { + throw new Error(`BrowserStack logs error ${response.status}`); + } + return response.text(); + } +} diff --git a/engineering-team/playwright-pro/integrations/browserstack-mcp/src/index.ts b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/index.ts new file mode 100644 index 0000000..5d14f88 --- /dev/null +++ b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/index.ts @@ -0,0 +1,183 @@ +#!/usr/bin/env npx tsx +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { BrowserStackClient } from './client.js'; +import type { BrowserStackSessionUpdate } from './types.js'; + +const config = { + username: process.env.BROWSERSTACK_USERNAME ?? '', + accessKey: process.env.BROWSERSTACK_ACCESS_KEY ?? '', +}; + +if (!config.username || !config.accessKey) { + console.error( + 'Missing BrowserStack configuration. Set BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY.', + ); + process.exit(1); +} + +const client = new BrowserStackClient(config); + +const server = new Server( + { name: 'pw-browserstack', version: '1.0.0' }, + { capabilities: { tools: {} } }, +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'browserstack_get_plan', + description: 'Get BrowserStack Automate plan details including parallel session limits', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'browserstack_get_browsers', + description: 'List all available browser and OS combinations for Playwright testing', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'browserstack_get_builds', + description: 'List recent test builds with status', + inputSchema: { + type: 'object', + properties: { + limit: { type: 'number', description: 'Max builds to return (default 10)' }, + status: { + type: 'string', + enum: ['running', 'done', 'failed', 'timeout'], + description: 'Filter by status', + }, + }, + }, + }, + { + name: 'browserstack_get_sessions', + description: 'List test sessions within a build', + inputSchema: { + type: 'object', + properties: { + build_id: { type: 'string', description: 'Build hashed ID' }, + limit: { type: 'number', description: 'Max sessions to return' }, + }, + required: ['build_id'], + }, + }, + { + name: 'browserstack_get_session', + description: 'Get detailed session info including video URL, logs, and screenshots', + inputSchema: { + type: 'object', + properties: { + session_id: { type: 'string', description: 'Session hashed ID' }, + }, + required: ['session_id'], + }, + }, + { + name: 'browserstack_update_session', + description: 'Update session status (mark as passed/failed) and name', + inputSchema: { + type: 'object', + properties: { + session_id: { type: 'string', description: 'Session hashed ID' }, + status: { + type: 'string', + enum: ['passed', 'failed'], + description: 'Test result status', + }, + name: { type: 'string', description: 'Updated session name' }, + reason: { type: 'string', description: 'Reason for failure' }, + }, + required: ['session_id'], + }, + }, + { + name: 'browserstack_get_logs', + description: 'Get text logs for a specific test session', + inputSchema: { + type: 'object', + properties: { + session_id: { type: 'string', description: 'Session hashed ID' }, + }, + required: ['session_id'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case 'browserstack_get_plan': { + const plan = await client.getPlan(); + return { content: [{ type: 'text', text: JSON.stringify(plan, null, 2) }] }; + } + + case 'browserstack_get_browsers': { + const browsers = await client.getBrowsers(); + const playwrightBrowsers = browsers.filter( + (b) => + ['chrome', 'firefox', 'playwright-chromium', 'playwright-firefox', 'playwright-webkit'].includes( + b.browser?.toLowerCase() ?? '', + ) || b.browser?.toLowerCase().includes('playwright'), + ); + const summary = playwrightBrowsers.length > 0 ? playwrightBrowsers : browsers.slice(0, 50); + return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }; + } + + case 'browserstack_get_builds': { + const builds = await client.getBuilds( + (args?.limit as number) ?? 10, + args?.status as string | undefined, + ); + return { content: [{ type: 'text', text: JSON.stringify(builds, null, 2) }] }; + } + + case 'browserstack_get_sessions': { + const sessions = await client.getSessions( + args!.build_id as string, + args?.limit as number | undefined, + ); + return { content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }] }; + } + + case 'browserstack_get_session': { + const session = await client.getSession(args!.session_id as string); + return { content: [{ type: 'text', text: JSON.stringify(session, null, 2) }] }; + } + + case 'browserstack_update_session': { + const update: BrowserStackSessionUpdate = {}; + if (args?.status) update.status = args.status as 'passed' | 'failed'; + if (args?.name) update.name = args.name as string; + if (args?.reason) update.reason = args.reason as string; + const updated = await client.updateSession(args!.session_id as string, update); + return { content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }] }; + } + + case 'browserstack_get_logs': { + const logs = await client.getSessionLogs(args!.session_id as string); + return { content: [{ type: 'text', text: logs }] }; + } + + default: + return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }; + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true }; + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch(console.error); diff --git a/engineering-team/playwright-pro/integrations/browserstack-mcp/src/types.ts b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/types.ts new file mode 100644 index 0000000..72141a6 --- /dev/null +++ b/engineering-team/playwright-pro/integrations/browserstack-mcp/src/types.ts @@ -0,0 +1,61 @@ +export interface BrowserStackConfig { + username: string; + accessKey: string; +} + +export interface BrowserStackPlan { + automate_plan: string; + parallel_sessions_running: number; + team_parallel_sessions_max_allowed: number; + parallel_sessions_max_allowed: number; + queued_sessions: number; + queued_sessions_max_allowed: number; +} + +export interface BrowserStackBrowser { + os: string; + os_version: string; + browser: string; + browser_version: string; + device: string | null; + real_mobile: boolean | null; +} + +export interface BrowserStackBuild { + automation_build: { + name: string; + hashed_id: string; + duration: number; + status: string; + build_tag: string | null; + }; +} + +export interface BrowserStackSession { + automation_session: { + name: string; + duration: number; + os: string; + os_version: string; + browser_version: string; + browser: string; + device: string | null; + status: string; + hashed_id: string; + reason: string; + build_name: string; + project_name: string; + logs: string; + browser_url: string; + public_url: string; + video_url: string; + browser_console_logs_url: string; + har_logs_url: string; + }; +} + +export interface BrowserStackSessionUpdate { + name?: string; + status?: 'passed' | 'failed'; + reason?: string; +} diff --git a/engineering-team/playwright-pro/integrations/browserstack-mcp/tsconfig.json b/engineering-team/playwright-pro/integrations/browserstack-mcp/tsconfig.json new file mode 100644 index 0000000..6282e8a --- /dev/null +++ b/engineering-team/playwright-pro/integrations/browserstack-mcp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/engineering-team/playwright-pro/integrations/testrail-mcp/package.json b/engineering-team/playwright-pro/integrations/testrail-mcp/package.json new file mode 100644 index 0000000..fe9a1df --- /dev/null +++ b/engineering-team/playwright-pro/integrations/testrail-mcp/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pw/testrail-mcp", + "version": "1.0.0", + "description": "MCP server for TestRail integration with Playwright Pro", + "type": "module", + "main": "src/index.ts", + "scripts": { + "start": "tsx src/index.ts", + "build": "tsc" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0" + }, + "devDependencies": { + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } +} diff --git a/engineering-team/playwright-pro/integrations/testrail-mcp/src/client.ts b/engineering-team/playwright-pro/integrations/testrail-mcp/src/client.ts new file mode 100644 index 0000000..b6186db --- /dev/null +++ b/engineering-team/playwright-pro/integrations/testrail-mcp/src/client.ts @@ -0,0 +1,147 @@ +import type { + TestRailConfig, + TestRailProject, + TestRailSuite, + TestRailCase, + TestRailCasePayload, + TestRailRun, + TestRailRunPayload, + TestRailResult, + TestRailResultPayload, +} from './types.js'; + +export class TestRailClient { + private readonly baseUrl: string; + private readonly headers: Record; + + constructor(config: TestRailConfig) { + this.baseUrl = config.url.replace(/\/+$/, ''); + const auth = Buffer.from(`${config.user}:${config.apiKey}`).toString('base64'); + this.headers = { + Authorization: `Basic ${auth}`, + 'Content-Type': 'application/json', + }; + } + + private async request( + method: string, + endpoint: string, + body?: unknown, + ): Promise { + const url = `${this.baseUrl}/index.php?/api/v2/${endpoint}`; + const options: RequestInit = { + method, + headers: this.headers, + }; + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(url, options); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `TestRail API error ${response.status}: ${errorText}`, + ); + } + + return response.json() as Promise; + } + + async getProjects(): Promise { + const result = await this.request<{ projects: TestRailProject[] }>( + 'GET', + 'get_projects', + ); + return result.projects ?? result as unknown as TestRailProject[]; + } + + async getSuites(projectId: number): Promise { + return this.request('GET', `get_suites/${projectId}`); + } + + async getCases( + projectId: number, + suiteId?: number, + sectionId?: number, + limit?: number, + offset?: number, + filter?: string, + ): Promise { + let endpoint = `get_cases/${projectId}`; + const params: string[] = []; + if (suiteId) params.push(`suite_id=${suiteId}`); + if (sectionId) params.push(`section_id=${sectionId}`); + if (limit) params.push(`limit=${limit}`); + if (offset) params.push(`offset=${offset}`); + if (filter) params.push(`filter=${encodeURIComponent(filter)}`); + if (params.length > 0) endpoint += `&${params.join('&')}`; + + const result = await this.request<{ cases: TestRailCase[] }>( + 'GET', + endpoint, + ); + return result.cases ?? result as unknown as TestRailCase[]; + } + + async addCase( + sectionId: number, + payload: TestRailCasePayload, + ): Promise { + return this.request( + 'POST', + `add_case/${sectionId}`, + payload, + ); + } + + async updateCase( + caseId: number, + payload: Partial, + ): Promise { + return this.request( + 'POST', + `update_case/${caseId}`, + payload, + ); + } + + async addRun( + projectId: number, + payload: TestRailRunPayload, + ): Promise { + return this.request( + 'POST', + `add_run/${projectId}`, + payload, + ); + } + + async addResultForCase( + runId: number, + caseId: number, + payload: TestRailResultPayload, + ): Promise { + return this.request( + 'POST', + `add_result_for_case/${runId}/${caseId}`, + payload, + ); + } + + async getResultsForCase( + runId: number, + caseId: number, + limit?: number, + ): Promise { + let endpoint = `get_results_for_case/${runId}/${caseId}`; + if (limit) endpoint += `&limit=${limit}`; + + const result = await this.request<{ results: TestRailResult[] }>( + 'GET', + endpoint, + ); + return result.results ?? result as unknown as TestRailResult[]; + } +} diff --git a/engineering-team/playwright-pro/integrations/testrail-mcp/src/index.ts b/engineering-team/playwright-pro/integrations/testrail-mcp/src/index.ts new file mode 100644 index 0000000..a373628 --- /dev/null +++ b/engineering-team/playwright-pro/integrations/testrail-mcp/src/index.ts @@ -0,0 +1,270 @@ +#!/usr/bin/env npx tsx +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { TestRailClient } from './client.js'; +import type { TestRailCasePayload, TestRailRunPayload, TestRailResultPayload } from './types.js'; + +const config = { + url: process.env.TESTRAIL_URL ?? '', + user: process.env.TESTRAIL_USER ?? '', + apiKey: process.env.TESTRAIL_API_KEY ?? '', +}; + +if (!config.url || !config.user || !config.apiKey) { + console.error( + 'Missing TestRail configuration. Set TESTRAIL_URL, TESTRAIL_USER, and TESTRAIL_API_KEY.', + ); + process.exit(1); +} + +const client = new TestRailClient(config); + +const server = new Server( + { name: 'pw-testrail', version: '1.0.0' }, + { capabilities: { tools: {} } }, +); + +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'testrail_get_projects', + description: 'List all TestRail projects', + inputSchema: { type: 'object', properties: {} }, + }, + { + name: 'testrail_get_suites', + description: 'List test suites in a project', + inputSchema: { + type: 'object', + properties: { + project_id: { type: 'number', description: 'Project ID' }, + }, + required: ['project_id'], + }, + }, + { + name: 'testrail_get_cases', + description: 'Get test cases from a project. Supports filtering by suite, section, and search text.', + inputSchema: { + type: 'object', + properties: { + project_id: { type: 'number', description: 'Project ID' }, + suite_id: { type: 'number', description: 'Suite ID (optional)' }, + section_id: { type: 'number', description: 'Section ID (optional)' }, + limit: { type: 'number', description: 'Max results (default 250)' }, + offset: { type: 'number', description: 'Offset for pagination' }, + filter: { type: 'string', description: 'Search text filter' }, + }, + required: ['project_id'], + }, + }, + { + name: 'testrail_add_case', + description: 'Create a new test case in a section', + inputSchema: { + type: 'object', + properties: { + section_id: { type: 'number', description: 'Section ID to add the case to' }, + title: { type: 'string', description: 'Test case title' }, + template_id: { type: 'number', description: 'Template ID (2 = Test Case Steps)' }, + priority_id: { type: 'number', description: 'Priority (1=Low, 2=Medium, 3=High, 4=Critical)' }, + custom_preconds: { type: 'string', description: 'Preconditions text' }, + custom_steps_separated: { + type: 'array', + items: { + type: 'object', + properties: { + content: { type: 'string', description: 'Step action' }, + expected: { type: 'string', description: 'Expected result' }, + }, + }, + description: 'Test steps with expected results', + }, + }, + required: ['section_id', 'title'], + }, + }, + { + name: 'testrail_update_case', + description: 'Update an existing test case', + inputSchema: { + type: 'object', + properties: { + case_id: { type: 'number', description: 'Case ID to update' }, + title: { type: 'string', description: 'Updated title' }, + custom_preconds: { type: 'string', description: 'Updated preconditions' }, + custom_steps_separated: { + type: 'array', + items: { + type: 'object', + properties: { + content: { type: 'string' }, + expected: { type: 'string' }, + }, + }, + description: 'Updated test steps', + }, + }, + required: ['case_id'], + }, + }, + { + name: 'testrail_add_run', + description: 'Create a new test run in a project', + inputSchema: { + type: 'object', + properties: { + project_id: { type: 'number', description: 'Project ID' }, + name: { type: 'string', description: 'Run name' }, + description: { type: 'string', description: 'Run description' }, + suite_id: { type: 'number', description: 'Suite ID' }, + include_all: { type: 'boolean', description: 'Include all cases (default true)' }, + case_ids: { + type: 'array', + items: { type: 'number' }, + description: 'Specific case IDs to include (if include_all is false)', + }, + }, + required: ['project_id', 'name'], + }, + }, + { + name: 'testrail_add_result', + description: 'Add a test result for a specific case in a run', + inputSchema: { + type: 'object', + properties: { + run_id: { type: 'number', description: 'Run ID' }, + case_id: { type: 'number', description: 'Case ID' }, + status_id: { + type: 'number', + description: 'Status: 1=Passed, 2=Blocked, 3=Untested, 4=Retest, 5=Failed', + }, + comment: { type: 'string', description: 'Result comment or error message' }, + elapsed: { type: 'string', description: 'Time spent (e.g., "30s", "1m 45s")' }, + defects: { type: 'string', description: 'Defect IDs (comma-separated)' }, + }, + required: ['run_id', 'case_id', 'status_id'], + }, + }, + { + name: 'testrail_get_results', + description: 'Get historical results for a test case in a run', + inputSchema: { + type: 'object', + properties: { + run_id: { type: 'number', description: 'Run ID' }, + case_id: { type: 'number', description: 'Case ID' }, + limit: { type: 'number', description: 'Max results to return' }, + }, + required: ['run_id', 'case_id'], + }, + }, + ], +})); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case 'testrail_get_projects': { + const projects = await client.getProjects(); + return { content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }] }; + } + + case 'testrail_get_suites': { + const suites = await client.getSuites(args!.project_id as number); + return { content: [{ type: 'text', text: JSON.stringify(suites, null, 2) }] }; + } + + case 'testrail_get_cases': { + const cases = await client.getCases( + args!.project_id as number, + args?.suite_id as number | undefined, + args?.section_id as number | undefined, + args?.limit as number | undefined, + args?.offset as number | undefined, + args?.filter as string | undefined, + ); + return { content: [{ type: 'text', text: JSON.stringify(cases, null, 2) }] }; + } + + case 'testrail_add_case': { + const payload: TestRailCasePayload = { + title: args!.title as string, + template_id: args?.template_id as number | undefined, + priority_id: args?.priority_id as number | undefined, + custom_preconds: args?.custom_preconds as string | undefined, + custom_steps_separated: args?.custom_steps_separated as TestRailCasePayload['custom_steps_separated'], + }; + const newCase = await client.addCase(args!.section_id as number, payload); + return { content: [{ type: 'text', text: JSON.stringify(newCase, null, 2) }] }; + } + + case 'testrail_update_case': { + const updatePayload: Partial = {}; + if (args?.title) updatePayload.title = args.title as string; + if (args?.custom_preconds) updatePayload.custom_preconds = args.custom_preconds as string; + if (args?.custom_steps_separated) { + updatePayload.custom_steps_separated = args.custom_steps_separated as TestRailCasePayload['custom_steps_separated']; + } + const updated = await client.updateCase(args!.case_id as number, updatePayload); + return { content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }] }; + } + + case 'testrail_add_run': { + const runPayload: TestRailRunPayload = { + name: args!.name as string, + description: args?.description as string | undefined, + suite_id: args?.suite_id as number | undefined, + include_all: (args?.include_all as boolean) ?? true, + case_ids: args?.case_ids as number[] | undefined, + }; + const run = await client.addRun(args!.project_id as number, runPayload); + return { content: [{ type: 'text', text: JSON.stringify(run, null, 2) }] }; + } + + case 'testrail_add_result': { + const resultPayload: TestRailResultPayload = { + status_id: args!.status_id as number, + comment: args?.comment as string | undefined, + elapsed: args?.elapsed as string | undefined, + defects: args?.defects as string | undefined, + }; + const result = await client.addResultForCase( + args!.run_id as number, + args!.case_id as number, + resultPayload, + ); + return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; + } + + case 'testrail_get_results': { + const results = await client.getResultsForCase( + args!.run_id as number, + args!.case_id as number, + args?.limit as number | undefined, + ); + return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] }; + } + + default: + return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true }; + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true }; + } +}); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch(console.error); diff --git a/engineering-team/playwright-pro/integrations/testrail-mcp/src/types.ts b/engineering-team/playwright-pro/integrations/testrail-mcp/src/types.ts new file mode 100644 index 0000000..cc76237 --- /dev/null +++ b/engineering-team/playwright-pro/integrations/testrail-mcp/src/types.ts @@ -0,0 +1,105 @@ +export interface TestRailConfig { + url: string; + user: string; + apiKey: string; +} + +export interface TestRailProject { + id: number; + name: string; + announcement: string; + is_completed: boolean; + suite_mode: number; + url: string; +} + +export interface TestRailSuite { + id: number; + name: string; + description: string | null; + project_id: number; + url: string; +} + +export interface TestRailSection { + id: number; + suite_id: number; + name: string; + description: string | null; + parent_id: number | null; + depth: number; +} + +export interface TestRailCaseStep { + content: string; + expected: string; +} + +export interface TestRailCase { + id: number; + title: string; + section_id: number; + template_id: number; + type_id: number; + priority_id: number; + estimate: string | null; + refs: string | null; + custom_preconds: string | null; + custom_steps_separated: TestRailCaseStep[] | null; + custom_steps: string | null; + custom_expected: string | null; +} + +export interface TestRailRun { + id: number; + suite_id: number; + name: string; + description: string | null; + assignedto_id: number | null; + include_all: boolean; + is_completed: boolean; + passed_count: number; + failed_count: number; + untested_count: number; + url: string; +} + +export interface TestRailResult { + id: number; + test_id: number; + status_id: number; + comment: string | null; + created_on: number; + elapsed: string | null; + defects: string | null; +} + +export interface TestRailResultPayload { + status_id: number; + comment?: string; + elapsed?: string; + defects?: string; +} + +export interface TestRailRunPayload { + suite_id?: number; + name: string; + description?: string; + assignedto_id?: number; + include_all?: boolean; + case_ids?: number[]; + refs?: string; +} + +export interface TestRailCasePayload { + title: string; + template_id?: number; + type_id?: number; + priority_id?: number; + estimate?: string; + refs?: string; + custom_preconds?: string; + custom_steps_separated?: TestRailCaseStep[]; + custom_steps?: string; + custom_expected?: string; +} diff --git a/engineering-team/playwright-pro/integrations/testrail-mcp/tsconfig.json b/engineering-team/playwright-pro/integrations/testrail-mcp/tsconfig.json new file mode 100644 index 0000000..6282e8a --- /dev/null +++ b/engineering-team/playwright-pro/integrations/testrail-mcp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/engineering-team/playwright-pro/reference/assertions.md b/engineering-team/playwright-pro/reference/assertions.md new file mode 100644 index 0000000..d5538a4 --- /dev/null +++ b/engineering-team/playwright-pro/reference/assertions.md @@ -0,0 +1,89 @@ +# Assertions Reference + +## Web-First Assertions (Always Use These) + +Auto-retry until timeout. Safe for dynamic content. + +```typescript +// Visibility +await expect(locator).toBeVisible(); +await expect(locator).not.toBeVisible(); +await expect(locator).toBeHidden(); + +// Text +await expect(locator).toHaveText('exact text'); +await expect(locator).toHaveText(/partial/i); +await expect(locator).toContainText('partial'); + +// Value (inputs) +await expect(locator).toHaveValue('entered text'); +await expect(locator).toHaveValues(['option1', 'option2']); + +// Attributes +await expect(locator).toHaveAttribute('href', '/dashboard'); +await expect(locator).toHaveClass(/active/); +await expect(locator).toHaveId('main-nav'); + +// State +await expect(locator).toBeEnabled(); +await expect(locator).toBeDisabled(); +await expect(locator).toBeChecked(); +await expect(locator).toBeEditable(); +await expect(locator).toBeFocused(); +await expect(locator).toBeAttached(); + +// Count +await expect(locator).toHaveCount(5); +await expect(locator).toHaveCount(0); // element doesn't exist + +// CSS +await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)'); + +// Screenshots +await expect(locator).toHaveScreenshot('button.png'); +await expect(page).toHaveScreenshot('full-page.png'); +``` + +## Page Assertions + +```typescript +await expect(page).toHaveURL('/dashboard'); +await expect(page).toHaveURL(/\/dashboard/); +await expect(page).toHaveTitle('Dashboard - App'); +await expect(page).toHaveTitle(/Dashboard/); +``` + +## Anti-Patterns (Never Do This) + +```typescript +// BAD — no auto-retry +const text = await locator.textContent(); +expect(text).toBe('Hello'); + +// BAD — snapshot in time, not reactive +const isVisible = await locator.isVisible(); +expect(isVisible).toBe(true); + +// BAD — evaluating in page context +const value = await page.evaluate(() => + document.querySelector('input')?.value +); +expect(value).toBe('test'); +``` + +## Custom Timeout + +```typescript +// Override timeout for slow operations +await expect(locator).toBeVisible({ timeout: 30_000 }); +``` + +## Soft Assertions + +Continue test even if assertion fails (report all failures at end): + +```typescript +await expect.soft(locator).toHaveText('Expected'); +await expect.soft(page).toHaveURL('/next'); +// Test continues even if above fail +``` diff --git a/engineering-team/playwright-pro/reference/common-pitfalls.md b/engineering-team/playwright-pro/reference/common-pitfalls.md new file mode 100644 index 0000000..e59ed8b --- /dev/null +++ b/engineering-team/playwright-pro/reference/common-pitfalls.md @@ -0,0 +1,137 @@ +# Common Pitfalls (Top 10) + +## 1. waitForTimeout + +**Symptom:** Slow, flaky tests. + +```typescript +// BAD +await page.waitForTimeout(3000); + +// GOOD +await expect(page.getByTestId('result')).toBeVisible(); +``` + +## 2. Non-Web-First Assertions + +**Symptom:** Assertions fail on dynamic content. + +```typescript +// BAD — checks once, no retry +const text = await page.textContent('.msg'); +expect(text).toBe('Done'); + +// GOOD — retries until timeout +await expect(page.getByText('Done')).toBeVisible(); +``` + +## 3. Missing await + +**Symptom:** Random passes/failures, tests seem to skip steps. + +```typescript +// BAD +page.goto('/dashboard'); +expect(page.getByText('Welcome')).toBeVisible(); + +// GOOD +await page.goto('/dashboard'); +await expect(page.getByText('Welcome')).toBeVisible(); +``` + +## 4. Hardcoded URLs + +**Symptom:** Tests break in different environments. + +```typescript +// BAD +await page.goto('http://localhost:3000/login'); + +// GOOD — uses baseURL from config +await page.goto('/login'); +``` + +## 5. CSS Selectors Instead of Roles + +**Symptom:** Tests break after CSS refactors. + +```typescript +// BAD +await page.click('#submit-btn'); + +// GOOD +await page.getByRole('button', { name: 'Submit' }).click(); +``` + +## 6. Shared State Between Tests + +**Symptom:** Tests pass alone, fail in suite. + +```typescript +// BAD — test B depends on test A +let userId: string; +test('create user', async () => { userId = '123'; }); +test('edit user', async () => { /* uses userId */ }); + +// GOOD — each test is independent +test('edit user', async ({ request }) => { + const res = await request.post('/api/users', { data: { name: 'Test' } }); + const { id } = await res.json(); + // ... +}); +``` + +## 7. Using networkidle + +**Symptom:** Tests hang or timeout unpredictably. + +```typescript +// BAD — waits for all network activity to stop +await page.goto('/dashboard', { waitUntil: 'networkidle' }); + +// GOOD — wait for specific content +await page.goto('/dashboard'); +await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); +``` + +## 8. Not Waiting for Navigation + +**Symptom:** Assertions run on wrong page. + +```typescript +// BAD — click navigates but we don't wait +await page.getByRole('link', { name: 'Settings' }).click(); +await expect(page.getByRole('heading')).toHaveText('Settings'); + +// GOOD — wait for URL change +await page.getByRole('link', { name: 'Settings' }).click(); +await expect(page).toHaveURL('/settings'); +await expect(page.getByRole('heading')).toHaveText('Settings'); +``` + +## 9. Testing Implementation, Not Behavior + +**Symptom:** Tests break on every refactor. + +```typescript +// BAD — tests CSS class (implementation detail) +await expect(page.locator('.btn')).toHaveClass('btn-primary active'); + +// GOOD — tests what the user sees +await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled(); +``` + +## 10. No Error Case Tests + +**Symptom:** App breaks on errors but all tests pass. + +```typescript +// Missing: what happens when the API fails? +test('should handle API error', async ({ page }) => { + await page.route('**/api/data', (route) => + route.fulfill({ status: 500 }) + ); + await page.goto('/dashboard'); + await expect(page.getByText(/error|try again/i)).toBeVisible(); +}); +``` diff --git a/engineering-team/playwright-pro/reference/fixtures.md b/engineering-team/playwright-pro/reference/fixtures.md new file mode 100644 index 0000000..f7f57ff --- /dev/null +++ b/engineering-team/playwright-pro/reference/fixtures.md @@ -0,0 +1,121 @@ +# Fixtures Reference + +## What Are Fixtures + +Fixtures provide setup/teardown for each test. They replace `beforeEach`/`afterEach` for shared state and are composable, type-safe, and lazy (only run when used). + +## Creating Custom Fixtures + +```typescript +// fixtures.ts +import { test as base, expect } from '@playwright/test'; + +// Define fixture types +type MyFixtures = { + authenticatedPage: Page; + testUser: { email: string; password: string }; + apiClient: APIRequestContext; +}; + +export const test = base.extend({ + // Simple value fixture + testUser: async ({}, use) => { + await use({ + email: `test-${Date.now()}@example.com`, + password: 'Test123!', + }); + }, + + // Fixture with setup and teardown + authenticatedPage: async ({ page, testUser }, use) => { + // Setup: log in + await page.goto('/login'); + await page.getByLabel('Email').fill(testUser.email); + await page.getByLabel('Password').fill(testUser.password); + await page.getByRole('button', { name: 'Sign in' }).click(); + await expect(page).toHaveURL('/dashboard'); + + // Provide the authenticated page to the test + await use(page); + + // Teardown: clean up (optional) + await page.goto('/logout'); + }, + + // API client fixture + apiClient: async ({ playwright }, use) => { + const context = await playwright.request.newContext({ + baseURL: 'http://localhost:3000', + extraHTTPHeaders: { + Authorization: `Bearer ${process.env.API_TOKEN}`, + }, + }); + await use(context); + await context.dispose(); + }, +}); + +export { expect }; +``` + +## Using Fixtures in Tests + +```typescript +import { test, expect } from './fixtures'; + +test('should show dashboard for logged in user', async ({ authenticatedPage }) => { + // authenticatedPage is already logged in + await expect(authenticatedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); +}); + +test('should create item via API', async ({ apiClient }) => { + const response = await apiClient.post('/api/items', { + data: { name: 'Test Item' }, + }); + expect(response.ok()).toBeTruthy(); +}); +``` + +## Shared Auth State (storageState) + +For performance, authenticate once and reuse: + +```typescript +// auth.setup.ts +import { test as setup } from '@playwright/test'; + +setup('authenticate', async ({ page }) => { + await page.goto('/login'); + await page.getByLabel('Email').fill('admin@example.com'); + await page.getByLabel('Password').fill('password'); + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForURL('/dashboard'); + await page.context().storageState({ path: '.auth/user.json' }); +}); +``` + +```typescript +// playwright.config.ts +export default defineConfig({ + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + { + name: 'chromium', + use: { + storageState: '.auth/user.json', + }, + dependencies: ['setup'], + }, + ], +}); +``` + +## When to Use What + +| Need | Use | +|---|---| +| Shared login state | `storageState` + setup project | +| Per-test data creation | Custom fixture with API calls | +| Reusable page helpers | Custom fixture returning page | +| Test data cleanup | Fixture teardown (after `use()`) | +| Config values | Simple value fixture | diff --git a/engineering-team/playwright-pro/reference/flaky-tests.md b/engineering-team/playwright-pro/reference/flaky-tests.md new file mode 100644 index 0000000..dc3cbdf --- /dev/null +++ b/engineering-team/playwright-pro/reference/flaky-tests.md @@ -0,0 +1,56 @@ +# Flaky Test Quick Reference + +## Diagnosis Commands + +```bash +# Burn-in: expose timing issues +npx playwright test tests/checkout.spec.ts --repeat-each=10 + +# Isolation: expose state leaks +npx playwright test tests/checkout.spec.ts --grep "adds item" --workers=1 + +# Full trace: capture everything +npx playwright test tests/checkout.spec.ts --trace=on --retries=0 + +# Parallel stress: expose race conditions +npx playwright test --fully-parallel --workers=4 --repeat-each=5 +``` + +## Four Categories + +| Category | Symptom | Fix | +|---|---|---| +| **Timing** | Fails intermittently | Replace waits with assertions | +| **Isolation** | Fails in suite, passes alone | Remove shared state | +| **Environment** | Fails in CI only | Match viewport, fonts, timezone | +| **Infrastructure** | Random crashes | Reduce workers, increase memory | + +## Quick Fixes + +**Timing → Add proper waits:** +```typescript +// Wait for specific response +const response = page.waitForResponse('**/api/data'); +await page.getByRole('button', { name: 'Load' }).click(); +await response; +await expect(page.getByTestId('results')).toBeVisible(); +``` + +**Isolation → Unique test data:** +```typescript +const uniqueEmail = `test-${Date.now()}@example.com`; +``` + +**Environment → Explicit viewport:** +```typescript +test.use({ viewport: { width: 1280, height: 720 } }); +``` + +**Infrastructure → CI-safe config:** +```typescript +export default defineConfig({ + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 2 : undefined, + timeout: process.env.CI ? 60_000 : 30_000, +}); +``` diff --git a/engineering-team/playwright-pro/reference/golden-rules.md b/engineering-team/playwright-pro/reference/golden-rules.md new file mode 100644 index 0000000..d63fd89 --- /dev/null +++ b/engineering-team/playwright-pro/reference/golden-rules.md @@ -0,0 +1,12 @@ +# Golden Rules + +1. **`getByRole()` over CSS/XPath** — resilient to markup changes, mirrors assistive technology +2. **Never `page.waitForTimeout()`** — use `expect(locator).toBeVisible()` or `page.waitForURL()` +3. **Web-first assertions** — `expect(locator)` auto-retries; `expect(await locator.textContent())` does not +4. **Isolate every test** — no shared state, no execution-order dependencies +5. **`baseURL` in config** — zero hardcoded URLs in tests +6. **Retries: `2` in CI, `0` locally** — surface flakiness where it matters +7. **Traces: `'on-first-retry'`** — rich debugging artifacts without CI slowdown +8. **Fixtures over globals** — share state via `test.extend()`, not module-level variables +9. **One behavior per test** — multiple related `expect()` calls are fine +10. **Mock external services only** — never mock your own app; mock third-party APIs, payment gateways, email diff --git a/engineering-team/playwright-pro/reference/locators.md b/engineering-team/playwright-pro/reference/locators.md new file mode 100644 index 0000000..d06c25a --- /dev/null +++ b/engineering-team/playwright-pro/reference/locators.md @@ -0,0 +1,77 @@ +# Locator Priority + +Use the first option that works: + +| Priority | Locator | Use for | +|---|---|---| +| 1 | `getByRole('button', { name: 'Submit' })` | Buttons, links, headings, form elements | +| 2 | `getByLabel('Email address')` | Form fields with associated labels | +| 3 | `getByText('Welcome back')` | Non-interactive text content | +| 4 | `getByPlaceholder('Search...')` | Inputs with placeholder text | +| 5 | `getByAltText('Company logo')` | Images with alt text | +| 6 | `getByTitle('Close dialog')` | Elements with title attribute | +| 7 | `getByTestId('checkout-summary')` | When no semantic option exists | +| 8 | `page.locator('.legacy-widget')` | CSS/XPath — absolute last resort | + +## Role Locator Cheat Sheet + +```typescript +// Buttons —