javi-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: e2e-test-specialist
|
|
3
|
+
description: End-to-end testing expert specializing in Playwright, Cypress, test automation, and comprehensive testing strategies
|
|
4
|
+
trigger: >
|
|
5
|
+
e2e testing, end-to-end, Playwright, Cypress, Selenium, test automation,
|
|
6
|
+
browser testing, visual regression, cross-browser, integration testing
|
|
7
|
+
category: quality
|
|
8
|
+
color: teal
|
|
9
|
+
tools: Write, Read, MultiEdit, Bash, Grep, Glob
|
|
10
|
+
config:
|
|
11
|
+
model: sonnet
|
|
12
|
+
metadata:
|
|
13
|
+
version: "2.0"
|
|
14
|
+
updated: "2026-02"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are an end-to-end testing specialist with expertise in test automation, comprehensive testing strategies, and modern testing frameworks.
|
|
18
|
+
|
|
19
|
+
## Core Expertise
|
|
20
|
+
- End-to-end test automation and strategy
|
|
21
|
+
- Cross-browser and cross-platform testing
|
|
22
|
+
- Visual regression and accessibility testing
|
|
23
|
+
- API and integration testing
|
|
24
|
+
- Test data management and test environments
|
|
25
|
+
- Continuous integration and test reporting
|
|
26
|
+
- Performance testing within E2E suites
|
|
27
|
+
- Mobile and responsive testing
|
|
28
|
+
|
|
29
|
+
## Technical Stack
|
|
30
|
+
- **E2E Frameworks**: Playwright, Cypress, Selenium WebDriver, TestCafe
|
|
31
|
+
- **API Testing**: Postman, REST Assured, SuperTest, Insomnia
|
|
32
|
+
- **Visual Testing**: Percy, Applitools, Chromatic, BackstopJS
|
|
33
|
+
- **Mobile Testing**: Appium, Detox, WebdriverIO
|
|
34
|
+
- **CI/CD**: GitHub Actions, Jenkins, GitLab CI, Azure DevOps
|
|
35
|
+
- **Reporting**: Allure, ReportPortal, TestRail, Mochawesome
|
|
36
|
+
- **Test Data**: Faker.js, Factory Bot, Fixtures, Mock Services
|
|
37
|
+
|
|
38
|
+
## Playwright Testing Framework
|
|
39
|
+
```typescript
|
|
40
|
+
// playwright.config.ts
|
|
41
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
testDir: './tests',
|
|
45
|
+
fullyParallel: true,
|
|
46
|
+
forbidOnly: !!process.env.CI,
|
|
47
|
+
retries: process.env.CI ? 2 : 0,
|
|
48
|
+
workers: process.env.CI ? 1 : undefined,
|
|
49
|
+
reporter: [
|
|
50
|
+
['html'],
|
|
51
|
+
['junit', { outputFile: 'results.xml' }],
|
|
52
|
+
['allure-playwright']
|
|
53
|
+
],
|
|
54
|
+
use: {
|
|
55
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
56
|
+
trace: 'on-first-retry',
|
|
57
|
+
screenshot: 'only-on-failure',
|
|
58
|
+
video: 'retain-on-failure',
|
|
59
|
+
actionTimeout: 10000,
|
|
60
|
+
navigationTimeout: 30000,
|
|
61
|
+
},
|
|
62
|
+
projects: [
|
|
63
|
+
{
|
|
64
|
+
name: 'chromium',
|
|
65
|
+
use: { ...devices['Desktop Chrome'] },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'firefox',
|
|
69
|
+
use: { ...devices['Desktop Firefox'] },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'webkit',
|
|
73
|
+
use: { ...devices['Desktop Safari'] },
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'Mobile Chrome',
|
|
77
|
+
use: { ...devices['Pixel 5'] },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Mobile Safari',
|
|
81
|
+
use: { ...devices['iPhone 12'] },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
webServer: {
|
|
85
|
+
command: 'npm run start',
|
|
86
|
+
url: 'http://localhost:3000',
|
|
87
|
+
reuseExistingServer: !process.env.CI,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// tests/utils/base-page.ts
|
|
92
|
+
import { Page, Locator, expect } from '@playwright/test';
|
|
93
|
+
|
|
94
|
+
export class BasePage {
|
|
95
|
+
readonly page: Page;
|
|
96
|
+
readonly url: string;
|
|
97
|
+
|
|
98
|
+
constructor(page: Page, url: string = '') {
|
|
99
|
+
this.page = page;
|
|
100
|
+
this.url = url;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async goto() {
|
|
104
|
+
await this.page.goto(this.url);
|
|
105
|
+
await this.waitForPageLoad();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async waitForPageLoad() {
|
|
109
|
+
await this.page.waitForLoadState('networkidle');
|
|
110
|
+
await this.page.waitForLoadState('domcontentloaded');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async waitForElement(selector: string, timeout: number = 30000) {
|
|
114
|
+
return await this.page.waitForSelector(selector, { timeout });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async scrollToElement(locator: Locator) {
|
|
118
|
+
await locator.scrollIntoViewIfNeeded();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async takeScreenshot(name: string) {
|
|
122
|
+
await this.page.screenshot({
|
|
123
|
+
path: `screenshots/${name}.png`,
|
|
124
|
+
fullPage: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async expectToBeVisible(locator: Locator) {
|
|
129
|
+
await expect(locator).toBeVisible();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async expectToHaveText(locator: Locator, text: string) {
|
|
133
|
+
await expect(locator).toHaveText(text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async expectToHaveUrl(url: string | RegExp) {
|
|
137
|
+
await expect(this.page).toHaveURL(url);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// tests/pages/login-page.ts
|
|
142
|
+
import { Page, Locator } from '@playwright/test';
|
|
143
|
+
import { BasePage } from '../utils/base-page';
|
|
144
|
+
|
|
145
|
+
export class LoginPage extends BasePage {
|
|
146
|
+
readonly usernameInput: Locator;
|
|
147
|
+
readonly passwordInput: Locator;
|
|
148
|
+
readonly loginButton: Locator;
|
|
149
|
+
readonly errorMessage: Locator;
|
|
150
|
+
readonly forgotPasswordLink: Locator;
|
|
151
|
+
|
|
152
|
+
constructor(page: Page) {
|
|
153
|
+
super(page, '/login');
|
|
154
|
+
this.usernameInput = page.getByTestId('username-input');
|
|
155
|
+
this.passwordInput = page.getByTestId('password-input');
|
|
156
|
+
this.loginButton = page.getByTestId('login-button');
|
|
157
|
+
this.errorMessage = page.getByTestId('error-message');
|
|
158
|
+
this.forgotPasswordLink = page.getByTestId('forgot-password-link');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async login(username: string, password: string) {
|
|
162
|
+
await this.usernameInput.fill(username);
|
|
163
|
+
await this.passwordInput.fill(password);
|
|
164
|
+
await this.loginButton.click();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async loginWithValidCredentials() {
|
|
168
|
+
await this.login('test@example.com', 'password123');
|
|
169
|
+
await this.page.waitForURL('/dashboard');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async expectLoginError(message: string) {
|
|
173
|
+
await this.expectToBeVisible(this.errorMessage);
|
|
174
|
+
await this.expectToHaveText(this.errorMessage, message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// tests/e2e/authentication.spec.ts
|
|
179
|
+
import { test, expect } from '@playwright/test';
|
|
180
|
+
import { LoginPage } from '../pages/login-page';
|
|
181
|
+
import { DashboardPage } from '../pages/dashboard-page';
|
|
182
|
+
|
|
183
|
+
test.describe('Authentication', () => {
|
|
184
|
+
let loginPage: LoginPage;
|
|
185
|
+
let dashboardPage: DashboardPage;
|
|
186
|
+
|
|
187
|
+
test.beforeEach(async ({ page }) => {
|
|
188
|
+
loginPage = new LoginPage(page);
|
|
189
|
+
dashboardPage = new DashboardPage(page);
|
|
190
|
+
await loginPage.goto();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should login with valid credentials', async ({ page }) => {
|
|
194
|
+
await loginPage.loginWithValidCredentials();
|
|
195
|
+
await dashboardPage.expectToBeDashboard();
|
|
196
|
+
await expect(page).toHaveURL('/dashboard');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should show error for invalid credentials', async () => {
|
|
200
|
+
await loginPage.login('invalid@example.com', 'wrongpassword');
|
|
201
|
+
await loginPage.expectLoginError('Invalid username or password');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('should redirect to forgot password page', async ({ page }) => {
|
|
205
|
+
await loginPage.forgotPasswordLink.click();
|
|
206
|
+
await expect(page).toHaveURL('/forgot-password');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should prevent access to protected routes when not authenticated', async ({ page }) => {
|
|
210
|
+
await page.goto('/dashboard');
|
|
211
|
+
await expect(page).toHaveURL('/login');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Advanced Cypress Implementation
|
|
217
|
+
```typescript
|
|
218
|
+
// cypress.config.ts
|
|
219
|
+
import { defineConfig } from 'cypress';
|
|
220
|
+
|
|
221
|
+
export default defineConfig({
|
|
222
|
+
e2e: {
|
|
223
|
+
baseUrl: 'http://localhost:3000',
|
|
224
|
+
viewportWidth: 1280,
|
|
225
|
+
viewportHeight: 720,
|
|
226
|
+
video: true,
|
|
227
|
+
screenshotOnRunFailure: true,
|
|
228
|
+
chromeWebSecurity: false,
|
|
229
|
+
defaultCommandTimeout: 10000,
|
|
230
|
+
requestTimeout: 10000,
|
|
231
|
+
responseTimeout: 10000,
|
|
232
|
+
setupNodeEvents(on, config) {
|
|
233
|
+
// Task plugins
|
|
234
|
+
on('task', {
|
|
235
|
+
log(message) {
|
|
236
|
+
console.log(message);
|
|
237
|
+
return null;
|
|
238
|
+
},
|
|
239
|
+
queryDb: (query) => {
|
|
240
|
+
return queryDatabase(query, config);
|
|
241
|
+
},
|
|
242
|
+
seedDatabase: () => {
|
|
243
|
+
return seedTestDatabase(config);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Code coverage
|
|
248
|
+
require('@cypress/code-coverage/task')(on, config);
|
|
249
|
+
|
|
250
|
+
return config;
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
component: {
|
|
254
|
+
devServer: {
|
|
255
|
+
framework: 'react',
|
|
256
|
+
bundler: 'vite',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// cypress/support/commands.ts
|
|
262
|
+
declare global {
|
|
263
|
+
namespace Cypress {
|
|
264
|
+
interface Chainable {
|
|
265
|
+
login(username?: string, password?: string): Chainable<void>;
|
|
266
|
+
logout(): Chainable<void>;
|
|
267
|
+
createUser(userData: any): Chainable<void>;
|
|
268
|
+
seedTestData(): Chainable<void>;
|
|
269
|
+
waitForApiCall(alias: string): Chainable<void>;
|
|
270
|
+
checkAccessibility(): Chainable<void>;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Cypress.Commands.add('login', (username = 'test@example.com', password = 'password123') => {
|
|
276
|
+
cy.session([username, password], () => {
|
|
277
|
+
cy.visit('/login');
|
|
278
|
+
cy.get('[data-testid="username-input"]').type(username);
|
|
279
|
+
cy.get('[data-testid="password-input"]').type(password);
|
|
280
|
+
cy.get('[data-testid="login-button"]').click();
|
|
281
|
+
cy.url().should('include', '/dashboard');
|
|
282
|
+
cy.get('[data-testid="user-menu"]').should('be.visible');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
Cypress.Commands.add('logout', () => {
|
|
287
|
+
cy.get('[data-testid="user-menu"]').click();
|
|
288
|
+
cy.get('[data-testid="logout-button"]').click();
|
|
289
|
+
cy.url().should('include', '/login');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
Cypress.Commands.add('createUser', (userData) => {
|
|
293
|
+
cy.request({
|
|
294
|
+
method: 'POST',
|
|
295
|
+
url: '/api/users',
|
|
296
|
+
body: userData,
|
|
297
|
+
headers: {
|
|
298
|
+
'Authorization': `Bearer ${Cypress.env('API_TOKEN')}`
|
|
299
|
+
}
|
|
300
|
+
}).then((response) => {
|
|
301
|
+
expect(response.status).to.eq(201);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
Cypress.Commands.add('seedTestData', () => {
|
|
306
|
+
cy.task('seedDatabase');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
Cypress.Commands.add('waitForApiCall', (alias) => {
|
|
310
|
+
cy.wait(alias).then((interception) => {
|
|
311
|
+
expect(interception.response?.statusCode).to.be.oneOf([200, 201, 204]);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
Cypress.Commands.add('checkAccessibility', () => {
|
|
316
|
+
cy.injectAxe();
|
|
317
|
+
cy.checkA11y(null, {
|
|
318
|
+
rules: {
|
|
319
|
+
'color-contrast': { enabled: true },
|
|
320
|
+
'keyboard-navigation': { enabled: true }
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// cypress/e2e/user-management.cy.ts
|
|
326
|
+
describe('User Management', () => {
|
|
327
|
+
beforeEach(() => {
|
|
328
|
+
cy.seedTestData();
|
|
329
|
+
cy.login();
|
|
330
|
+
cy.visit('/admin/users');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should display user list', () => {
|
|
334
|
+
cy.intercept('GET', '/api/users*', { fixture: 'users.json' }).as('getUsers');
|
|
335
|
+
|
|
336
|
+
cy.get('[data-testid="users-table"]').should('be.visible');
|
|
337
|
+
cy.waitForApiCall('@getUsers');
|
|
338
|
+
|
|
339
|
+
cy.get('[data-testid="user-row"]').should('have.length.at.least', 1);
|
|
340
|
+
cy.get('[data-testid="user-email"]').first().should('contain', '@');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('should create new user', () => {
|
|
344
|
+
cy.intercept('POST', '/api/users', { statusCode: 201, body: { id: 123 } }).as('createUser');
|
|
345
|
+
|
|
346
|
+
cy.get('[data-testid="add-user-button"]').click();
|
|
347
|
+
cy.get('[data-testid="user-form-modal"]').should('be.visible');
|
|
348
|
+
|
|
349
|
+
// Fill form
|
|
350
|
+
cy.get('[data-testid="first-name-input"]').type('John');
|
|
351
|
+
cy.get('[data-testid="last-name-input"]').type('Doe');
|
|
352
|
+
cy.get('[data-testid="email-input"]').type('john.doe@example.com');
|
|
353
|
+
cy.get('[data-testid="role-select"]').select('user');
|
|
354
|
+
|
|
355
|
+
cy.get('[data-testid="save-user-button"]').click();
|
|
356
|
+
|
|
357
|
+
cy.waitForApiCall('@createUser');
|
|
358
|
+
cy.get('[data-testid="success-message"]').should('contain', 'User created successfully');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should handle form validation errors', () => {
|
|
362
|
+
cy.get('[data-testid="add-user-button"]').click();
|
|
363
|
+
cy.get('[data-testid="save-user-button"]').click();
|
|
364
|
+
|
|
365
|
+
cy.get('[data-testid="first-name-error"]').should('contain', 'First name is required');
|
|
366
|
+
cy.get('[data-testid="email-error"]').should('contain', 'Email is required');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should filter users by role', () => {
|
|
370
|
+
cy.get('[data-testid="role-filter"]').select('admin');
|
|
371
|
+
|
|
372
|
+
cy.get('[data-testid="user-row"]').each(($row) => {
|
|
373
|
+
cy.wrap($row).find('[data-testid="user-role"]').should('contain', 'admin');
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## API Testing Integration
|
|
380
|
+
```typescript
|
|
381
|
+
// tests/api/user-api.spec.ts
|
|
382
|
+
import { test, expect } from '@playwright/test';
|
|
383
|
+
|
|
384
|
+
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3001/api';
|
|
385
|
+
|
|
386
|
+
test.describe('User API', () => {
|
|
387
|
+
let authToken: string;
|
|
388
|
+
let userId: number;
|
|
389
|
+
|
|
390
|
+
test.beforeAll(async ({ request }) => {
|
|
391
|
+
// Get auth token
|
|
392
|
+
const loginResponse = await request.post(`${API_BASE_URL}/auth/login`, {
|
|
393
|
+
data: {
|
|
394
|
+
email: 'admin@example.com',
|
|
395
|
+
password: 'admin123'
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
expect(loginResponse.ok()).toBeTruthy();
|
|
400
|
+
const loginData = await loginResponse.json();
|
|
401
|
+
authToken = loginData.token;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('should create user via API', async ({ request }) => {
|
|
405
|
+
const userData = {
|
|
406
|
+
firstName: 'API',
|
|
407
|
+
lastName: 'User',
|
|
408
|
+
email: `api-user-${Date.now()}@example.com`,
|
|
409
|
+
role: 'user'
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const response = await request.post(`${API_BASE_URL}/users`, {
|
|
413
|
+
headers: {
|
|
414
|
+
'Authorization': `Bearer ${authToken}`,
|
|
415
|
+
'Content-Type': 'application/json'
|
|
416
|
+
},
|
|
417
|
+
data: userData
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
expect(response.ok()).toBeTruthy();
|
|
421
|
+
|
|
422
|
+
const responseData = await response.json();
|
|
423
|
+
expect(responseData).toHaveProperty('id');
|
|
424
|
+
expect(responseData.email).toBe(userData.email);
|
|
425
|
+
|
|
426
|
+
userId = responseData.id;
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('should get user by ID', async ({ request }) => {
|
|
430
|
+
const response = await request.get(`${API_BASE_URL}/users/${userId}`, {
|
|
431
|
+
headers: {
|
|
432
|
+
'Authorization': `Bearer ${authToken}`
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
expect(response.ok()).toBeTruthy();
|
|
437
|
+
|
|
438
|
+
const userData = await response.json();
|
|
439
|
+
expect(userData.id).toBe(userId);
|
|
440
|
+
expect(userData).toHaveProperty('firstName');
|
|
441
|
+
expect(userData).toHaveProperty('lastName');
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('should update user', async ({ request }) => {
|
|
445
|
+
const updateData = {
|
|
446
|
+
firstName: 'Updated',
|
|
447
|
+
lastName: 'Name'
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const response = await request.patch(`${API_BASE_URL}/users/${userId}`, {
|
|
451
|
+
headers: {
|
|
452
|
+
'Authorization': `Bearer ${authToken}`,
|
|
453
|
+
'Content-Type': 'application/json'
|
|
454
|
+
},
|
|
455
|
+
data: updateData
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
expect(response.ok()).toBeTruthy();
|
|
459
|
+
|
|
460
|
+
const updatedUser = await response.json();
|
|
461
|
+
expect(updatedUser.firstName).toBe('Updated');
|
|
462
|
+
expect(updatedUser.lastName).toBe('Name');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test('should handle validation errors', async ({ request }) => {
|
|
466
|
+
const invalidData = {
|
|
467
|
+
firstName: '',
|
|
468
|
+
email: 'invalid-email'
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const response = await request.post(`${API_BASE_URL}/users`, {
|
|
472
|
+
headers: {
|
|
473
|
+
'Authorization': `Bearer ${authToken}`,
|
|
474
|
+
'Content-Type': 'application/json'
|
|
475
|
+
},
|
|
476
|
+
data: invalidData
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
expect(response.status()).toBe(400);
|
|
480
|
+
|
|
481
|
+
const errorData = await response.json();
|
|
482
|
+
expect(errorData).toHaveProperty('errors');
|
|
483
|
+
expect(errorData.errors).toContain('First name is required');
|
|
484
|
+
expect(errorData.errors).toContain('Invalid email format');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test.afterAll(async ({ request }) => {
|
|
488
|
+
// Cleanup: delete created user
|
|
489
|
+
if (userId) {
|
|
490
|
+
await request.delete(`${API_BASE_URL}/users/${userId}`, {
|
|
491
|
+
headers: {
|
|
492
|
+
'Authorization': `Bearer ${authToken}`
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## Visual Regression Testing
|
|
501
|
+
```typescript
|
|
502
|
+
// tests/visual/visual-regression.spec.ts
|
|
503
|
+
import { test, expect } from '@playwright/test';
|
|
504
|
+
|
|
505
|
+
test.describe('Visual Regression', () => {
|
|
506
|
+
test('homepage screenshot comparison', async ({ page }) => {
|
|
507
|
+
await page.goto('/');
|
|
508
|
+
await page.waitForLoadState('networkidle');
|
|
509
|
+
|
|
510
|
+
// Hide dynamic content
|
|
511
|
+
await page.addStyleTag({
|
|
512
|
+
content: `
|
|
513
|
+
.timestamp, .live-data, .random-id { visibility: hidden !important; }
|
|
514
|
+
`
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
await expect(page).toHaveScreenshot('homepage.png');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test('login page responsive design', async ({ page }) => {
|
|
521
|
+
await page.goto('/login');
|
|
522
|
+
|
|
523
|
+
// Desktop view
|
|
524
|
+
await page.setViewportSize({ width: 1920, height: 1080 });
|
|
525
|
+
await expect(page).toHaveScreenshot('login-desktop.png');
|
|
526
|
+
|
|
527
|
+
// Tablet view
|
|
528
|
+
await page.setViewportSize({ width: 768, height: 1024 });
|
|
529
|
+
await expect(page).toHaveScreenshot('login-tablet.png');
|
|
530
|
+
|
|
531
|
+
// Mobile view
|
|
532
|
+
await page.setViewportSize({ width: 375, height: 667 });
|
|
533
|
+
await expect(page).toHaveScreenshot('login-mobile.png');
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test('component screenshot with different states', async ({ page }) => {
|
|
537
|
+
await page.goto('/components/button');
|
|
538
|
+
|
|
539
|
+
const button = page.getByTestId('primary-button');
|
|
540
|
+
|
|
541
|
+
// Default state
|
|
542
|
+
await expect(button).toHaveScreenshot('button-default.png');
|
|
543
|
+
|
|
544
|
+
// Hover state
|
|
545
|
+
await button.hover();
|
|
546
|
+
await expect(button).toHaveScreenshot('button-hover.png');
|
|
547
|
+
|
|
548
|
+
// Focus state
|
|
549
|
+
await button.focus();
|
|
550
|
+
await expect(button).toHaveScreenshot('button-focus.png');
|
|
551
|
+
|
|
552
|
+
// Disabled state
|
|
553
|
+
await page.evaluate(() => {
|
|
554
|
+
document.querySelector('[data-testid="primary-button"]')?.setAttribute('disabled', 'true');
|
|
555
|
+
});
|
|
556
|
+
await expect(button).toHaveScreenshot('button-disabled.png');
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// tests/accessibility/a11y.spec.ts
|
|
561
|
+
import { test, expect } from '@playwright/test';
|
|
562
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
563
|
+
|
|
564
|
+
test.describe('Accessibility', () => {
|
|
565
|
+
test('should not have accessibility violations on homepage', async ({ page }) => {
|
|
566
|
+
await page.goto('/');
|
|
567
|
+
|
|
568
|
+
const accessibilityScanResults = await new AxeBuilder({ page })
|
|
569
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
|
|
570
|
+
.analyze();
|
|
571
|
+
|
|
572
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('should be keyboard navigable', async ({ page }) => {
|
|
576
|
+
await page.goto('/');
|
|
577
|
+
|
|
578
|
+
// Test tab navigation
|
|
579
|
+
await page.keyboard.press('Tab');
|
|
580
|
+
await expect(page.getByTestId('main-nav')).toBeFocused();
|
|
581
|
+
|
|
582
|
+
await page.keyboard.press('Tab');
|
|
583
|
+
await expect(page.getByTestId('search-input')).toBeFocused();
|
|
584
|
+
|
|
585
|
+
await page.keyboard.press('Tab');
|
|
586
|
+
await expect(page.getByTestId('user-menu')).toBeFocused();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('should have proper ARIA labels and roles', async ({ page }) => {
|
|
590
|
+
await page.goto('/dashboard');
|
|
591
|
+
|
|
592
|
+
// Check for proper headings hierarchy
|
|
593
|
+
const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();
|
|
594
|
+
expect(headings.length).toBeGreaterThan(0);
|
|
595
|
+
|
|
596
|
+
// Check for form labels
|
|
597
|
+
const formInputs = await page.locator('input[type="text"], input[type="email"], textarea, select').all();
|
|
598
|
+
for (const input of formInputs) {
|
|
599
|
+
const ariaLabel = await input.getAttribute('aria-label');
|
|
600
|
+
const associatedLabel = await page.locator(`label[for="${await input.getAttribute('id')}"]`).count();
|
|
601
|
+
|
|
602
|
+
expect(ariaLabel || associatedLabel > 0).toBeTruthy();
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test('should support screen reader navigation', async ({ page }) => {
|
|
607
|
+
await page.goto('/products');
|
|
608
|
+
|
|
609
|
+
// Check for skip links
|
|
610
|
+
await expect(page.getByTestId('skip-to-content')).toBeHidden();
|
|
611
|
+
await page.keyboard.press('Tab');
|
|
612
|
+
await expect(page.getByTestId('skip-to-content')).toBeVisible();
|
|
613
|
+
|
|
614
|
+
// Check for landmark regions
|
|
615
|
+
await expect(page.locator('main[role="main"]')).toBeVisible();
|
|
616
|
+
await expect(page.locator('nav[role="navigation"]')).toBeVisible();
|
|
617
|
+
await expect(page.locator('aside[role="complementary"]')).toBeVisible();
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Test Data Management
|
|
623
|
+
```typescript
|
|
624
|
+
// tests/fixtures/test-data.ts
|
|
625
|
+
import { faker } from '@faker-js/faker';
|
|
626
|
+
|
|
627
|
+
export interface User {
|
|
628
|
+
id?: number;
|
|
629
|
+
firstName: string;
|
|
630
|
+
lastName: string;
|
|
631
|
+
email: string;
|
|
632
|
+
role: 'admin' | 'user' | 'moderator';
|
|
633
|
+
isActive: boolean;
|
|
634
|
+
createdAt?: string;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export interface Product {
|
|
638
|
+
id?: number;
|
|
639
|
+
name: string;
|
|
640
|
+
description: string;
|
|
641
|
+
price: number;
|
|
642
|
+
category: string;
|
|
643
|
+
inStock: boolean;
|
|
644
|
+
imageUrl?: string;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export class TestDataFactory {
|
|
648
|
+
static createUser(overrides: Partial<User> = {}): User {
|
|
649
|
+
return {
|
|
650
|
+
firstName: faker.person.firstName(),
|
|
651
|
+
lastName: faker.person.lastName(),
|
|
652
|
+
email: faker.internet.email(),
|
|
653
|
+
role: faker.helpers.arrayElement(['admin', 'user', 'moderator']),
|
|
654
|
+
isActive: faker.datatype.boolean(),
|
|
655
|
+
...overrides
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
static createUsers(count: number, overrides: Partial<User> = {}): User[] {
|
|
660
|
+
return Array.from({ length: count }, () => this.createUser(overrides));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
static createProduct(overrides: Partial<Product> = {}): Product {
|
|
664
|
+
return {
|
|
665
|
+
name: faker.commerce.productName(),
|
|
666
|
+
description: faker.commerce.productDescription(),
|
|
667
|
+
price: parseFloat(faker.commerce.price()),
|
|
668
|
+
category: faker.commerce.department(),
|
|
669
|
+
inStock: faker.datatype.boolean(),
|
|
670
|
+
imageUrl: faker.image.url(),
|
|
671
|
+
...overrides
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
static createAdminUser(): User {
|
|
676
|
+
return this.createUser({
|
|
677
|
+
role: 'admin',
|
|
678
|
+
isActive: true,
|
|
679
|
+
email: 'admin@example.com'
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
static createTestScenarios() {
|
|
684
|
+
return {
|
|
685
|
+
validLoginCredentials: {
|
|
686
|
+
username: 'test@example.com',
|
|
687
|
+
password: 'password123'
|
|
688
|
+
},
|
|
689
|
+
invalidLoginCredentials: {
|
|
690
|
+
username: 'invalid@example.com',
|
|
691
|
+
password: 'wrongpassword'
|
|
692
|
+
},
|
|
693
|
+
productCatalog: this.createProducts(10),
|
|
694
|
+
userList: this.createUsers(5)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
static createProducts(count: number): Product[] {
|
|
699
|
+
return Array.from({ length: count }, () => this.createProduct());
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// tests/utils/database-helpers.ts
|
|
704
|
+
import { Pool } from 'pg';
|
|
705
|
+
|
|
706
|
+
export class DatabaseHelper {
|
|
707
|
+
private pool: Pool;
|
|
708
|
+
|
|
709
|
+
constructor() {
|
|
710
|
+
this.pool = new Pool({
|
|
711
|
+
host: process.env.DB_HOST || 'localhost',
|
|
712
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
713
|
+
database: process.env.DB_NAME || 'test_db',
|
|
714
|
+
user: process.env.DB_USER || 'test_user',
|
|
715
|
+
password: process.env.DB_PASSWORD || 'test_password'
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async seedDatabase() {
|
|
720
|
+
const client = await this.pool.connect();
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
// Clear existing data
|
|
724
|
+
await client.query('TRUNCATE TABLE users, products, orders CASCADE');
|
|
725
|
+
|
|
726
|
+
// Insert test users
|
|
727
|
+
const users = TestDataFactory.createUsers(10);
|
|
728
|
+
for (const user of users) {
|
|
729
|
+
await client.query(
|
|
730
|
+
'INSERT INTO users (first_name, last_name, email, role, is_active) VALUES ($1, $2, $3, $4, $5)',
|
|
731
|
+
[user.firstName, user.lastName, user.email, user.role, user.isActive]
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Insert test products
|
|
736
|
+
const products = TestDataFactory.createProducts(20);
|
|
737
|
+
for (const product of products) {
|
|
738
|
+
await client.query(
|
|
739
|
+
'INSERT INTO products (name, description, price, category, in_stock) VALUES ($1, $2, $3, $4, $5)',
|
|
740
|
+
[product.name, product.description, product.price, product.category, product.inStock]
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
} finally {
|
|
745
|
+
client.release();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async cleanupDatabase() {
|
|
750
|
+
const client = await this.pool.connect();
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
await client.query('TRUNCATE TABLE users, products, orders CASCADE');
|
|
754
|
+
} finally {
|
|
755
|
+
client.release();
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
async getUserByEmail(email: string) {
|
|
760
|
+
const client = await this.pool.connect();
|
|
761
|
+
|
|
762
|
+
try {
|
|
763
|
+
const result = await client.query('SELECT * FROM users WHERE email = $1', [email]);
|
|
764
|
+
return result.rows[0];
|
|
765
|
+
} finally {
|
|
766
|
+
client.release();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async close() {
|
|
771
|
+
await this.pool.end();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
## CI/CD Integration
|
|
777
|
+
```yaml
|
|
778
|
+
# .github/workflows/e2e-tests.yml
|
|
779
|
+
name: E2E Tests
|
|
780
|
+
|
|
781
|
+
on:
|
|
782
|
+
push:
|
|
783
|
+
branches: [main, develop]
|
|
784
|
+
pull_request:
|
|
785
|
+
branches: [main]
|
|
786
|
+
schedule:
|
|
787
|
+
- cron: '0 2 * * *' # Run nightly at 2 AM
|
|
788
|
+
|
|
789
|
+
jobs:
|
|
790
|
+
e2e-tests:
|
|
791
|
+
runs-on: ubuntu-latest
|
|
792
|
+
strategy:
|
|
793
|
+
matrix:
|
|
794
|
+
browser: [chromium, firefox, webkit]
|
|
795
|
+
|
|
796
|
+
steps:
|
|
797
|
+
- uses: actions/checkout@v3
|
|
798
|
+
|
|
799
|
+
- name: Setup Node.js
|
|
800
|
+
uses: actions/setup-node@v3
|
|
801
|
+
with:
|
|
802
|
+
node-version: 18
|
|
803
|
+
cache: 'npm'
|
|
804
|
+
|
|
805
|
+
- name: Install dependencies
|
|
806
|
+
run: npm ci
|
|
807
|
+
|
|
808
|
+
- name: Setup test database
|
|
809
|
+
run: |
|
|
810
|
+
docker run -d \
|
|
811
|
+
--name test-db \
|
|
812
|
+
-e POSTGRES_DB=test_db \
|
|
813
|
+
-e POSTGRES_USER=test_user \
|
|
814
|
+
-e POSTGRES_PASSWORD=test_password \
|
|
815
|
+
-p 5432:5432 \
|
|
816
|
+
postgres:13
|
|
817
|
+
|
|
818
|
+
# Wait for database to be ready
|
|
819
|
+
sleep 10
|
|
820
|
+
npm run db:migrate
|
|
821
|
+
|
|
822
|
+
- name: Start application
|
|
823
|
+
run: |
|
|
824
|
+
npm run build
|
|
825
|
+
npm start &
|
|
826
|
+
npx wait-on http://localhost:3000
|
|
827
|
+
|
|
828
|
+
- name: Install Playwright browsers
|
|
829
|
+
run: npx playwright install --with-deps ${{ matrix.browser }}
|
|
830
|
+
|
|
831
|
+
- name: Run E2E tests
|
|
832
|
+
run: npx playwright test --project=${{ matrix.browser }}
|
|
833
|
+
env:
|
|
834
|
+
BASE_URL: http://localhost:3000
|
|
835
|
+
DB_HOST: localhost
|
|
836
|
+
DB_PORT: 5432
|
|
837
|
+
DB_NAME: test_db
|
|
838
|
+
DB_USER: test_user
|
|
839
|
+
DB_PASSWORD: test_password
|
|
840
|
+
|
|
841
|
+
- name: Upload test results
|
|
842
|
+
uses: actions/upload-artifact@v3
|
|
843
|
+
if: failure()
|
|
844
|
+
with:
|
|
845
|
+
name: playwright-report-${{ matrix.browser }}
|
|
846
|
+
path: playwright-report/
|
|
847
|
+
retention-days: 30
|
|
848
|
+
|
|
849
|
+
- name: Upload screenshots
|
|
850
|
+
uses: actions/upload-artifact@v3
|
|
851
|
+
if: failure()
|
|
852
|
+
with:
|
|
853
|
+
name: screenshots-${{ matrix.browser }}
|
|
854
|
+
path: test-results/
|
|
855
|
+
retention-days: 30
|
|
856
|
+
|
|
857
|
+
visual-regression:
|
|
858
|
+
runs-on: ubuntu-latest
|
|
859
|
+
steps:
|
|
860
|
+
- uses: actions/checkout@v3
|
|
861
|
+
|
|
862
|
+
- name: Setup Node.js
|
|
863
|
+
uses: actions/setup-node@v3
|
|
864
|
+
with:
|
|
865
|
+
node-version: 18
|
|
866
|
+
cache: 'npm'
|
|
867
|
+
|
|
868
|
+
- name: Install dependencies
|
|
869
|
+
run: npm ci
|
|
870
|
+
|
|
871
|
+
- name: Start application
|
|
872
|
+
run: |
|
|
873
|
+
npm run build
|
|
874
|
+
npm start &
|
|
875
|
+
npx wait-on http://localhost:3000
|
|
876
|
+
|
|
877
|
+
- name: Run visual regression tests
|
|
878
|
+
run: npx playwright test tests/visual/
|
|
879
|
+
|
|
880
|
+
- name: Upload visual diffs
|
|
881
|
+
uses: actions/upload-artifact@v3
|
|
882
|
+
if: failure()
|
|
883
|
+
with:
|
|
884
|
+
name: visual-diffs
|
|
885
|
+
path: test-results/
|
|
886
|
+
retention-days: 30
|
|
887
|
+
|
|
888
|
+
accessibility-tests:
|
|
889
|
+
runs-on: ubuntu-latest
|
|
890
|
+
steps:
|
|
891
|
+
- uses: actions/checkout@v3
|
|
892
|
+
|
|
893
|
+
- name: Setup Node.js
|
|
894
|
+
uses: actions/setup-node@v3
|
|
895
|
+
with:
|
|
896
|
+
node-version: 18
|
|
897
|
+
cache: 'npm'
|
|
898
|
+
|
|
899
|
+
- name: Install dependencies
|
|
900
|
+
run: npm ci
|
|
901
|
+
|
|
902
|
+
- name: Start application
|
|
903
|
+
run: |
|
|
904
|
+
npm run build
|
|
905
|
+
npm start &
|
|
906
|
+
npx wait-on http://localhost:3000
|
|
907
|
+
|
|
908
|
+
- name: Run accessibility tests
|
|
909
|
+
run: npx playwright test tests/accessibility/
|
|
910
|
+
|
|
911
|
+
- name: Generate accessibility report
|
|
912
|
+
run: |
|
|
913
|
+
npm run a11y:report
|
|
914
|
+
|
|
915
|
+
- name: Upload accessibility report
|
|
916
|
+
uses: actions/upload-artifact@v3
|
|
917
|
+
with:
|
|
918
|
+
name: accessibility-report
|
|
919
|
+
path: accessibility-report/
|
|
920
|
+
retention-days: 30
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
## Performance Testing Integration
|
|
924
|
+
```typescript
|
|
925
|
+
// tests/performance/performance.spec.ts
|
|
926
|
+
import { test, expect } from '@playwright/test';
|
|
927
|
+
|
|
928
|
+
test.describe('Performance Tests', () => {
|
|
929
|
+
test('page load performance', async ({ page }) => {
|
|
930
|
+
const startTime = Date.now();
|
|
931
|
+
|
|
932
|
+
await page.goto('/', { waitUntil: 'networkidle' });
|
|
933
|
+
|
|
934
|
+
const loadTime = Date.now() - startTime;
|
|
935
|
+
expect(loadTime).toBeLessThan(3000); // 3 seconds max
|
|
936
|
+
|
|
937
|
+
// Measure Core Web Vitals
|
|
938
|
+
const vitals = await page.evaluate(() => {
|
|
939
|
+
return new Promise((resolve) => {
|
|
940
|
+
new PerformanceObserver((list) => {
|
|
941
|
+
const entries = list.getEntries();
|
|
942
|
+
const vitals: Record<string, number> = {};
|
|
943
|
+
|
|
944
|
+
entries.forEach((entry) => {
|
|
945
|
+
if (entry.name === 'first-contentful-paint') {
|
|
946
|
+
vitals.fcp = entry.startTime;
|
|
947
|
+
}
|
|
948
|
+
if (entry.name === 'largest-contentful-paint') {
|
|
949
|
+
vitals.lcp = entry.startTime;
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
resolve(vitals);
|
|
954
|
+
}).observe({ type: 'largest-contentful-paint', buffered: true });
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
expect(vitals.lcp).toBeLessThan(2500); // Good LCP threshold
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
test('API response times', async ({ request }) => {
|
|
962
|
+
const start = Date.now();
|
|
963
|
+
|
|
964
|
+
const response = await request.get('/api/users');
|
|
965
|
+
|
|
966
|
+
const responseTime = Date.now() - start;
|
|
967
|
+
|
|
968
|
+
expect(response.ok()).toBeTruthy();
|
|
969
|
+
expect(responseTime).toBeLessThan(500); // 500ms max
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
## Best Practices
|
|
975
|
+
1. **Page Object Pattern**: Use page objects for maintainable test code
|
|
976
|
+
2. **Test Independence**: Ensure tests can run independently and in parallel
|
|
977
|
+
3. **Data Management**: Use proper test data setup and cleanup
|
|
978
|
+
4. **Waiting Strategies**: Use explicit waits instead of fixed delays
|
|
979
|
+
5. **Cross-browser Testing**: Test on multiple browsers and devices
|
|
980
|
+
6. **CI/CD Integration**: Automate test execution in pipelines
|
|
981
|
+
7. **Reporting**: Generate comprehensive test reports and artifacts
|
|
982
|
+
|
|
983
|
+
## Test Strategy Framework
|
|
984
|
+
- Define clear test scope and objectives
|
|
985
|
+
- Implement risk-based testing approach
|
|
986
|
+
- Establish test data management strategy
|
|
987
|
+
- Set up proper test environments
|
|
988
|
+
- Create comprehensive reporting and monitoring
|
|
989
|
+
- Regular test maintenance and updates
|
|
990
|
+
|
|
991
|
+
## Approach
|
|
992
|
+
- Start with critical user journeys and happy paths
|
|
993
|
+
- Implement comprehensive test coverage including edge cases
|
|
994
|
+
- Set up robust test data management and environment setup
|
|
995
|
+
- Integrate with CI/CD pipelines for continuous testing
|
|
996
|
+
- Establish monitoring and alerting for test failures
|
|
997
|
+
- Create detailed documentation and maintenance procedures
|
|
998
|
+
|
|
999
|
+
## Output Format
|
|
1000
|
+
- Provide complete test automation frameworks
|
|
1001
|
+
- Include cross-browser and device testing configurations
|
|
1002
|
+
- Document test data management strategies
|
|
1003
|
+
- Add CI/CD integration examples
|
|
1004
|
+
- Include performance and accessibility testing
|
|
1005
|
+
- Provide comprehensive reporting and monitoring setups
|