specweave 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/INSTALL.md +848 -0
- package/LICENSE +21 -0
- package/README.md +675 -0
- package/SPECWEAVE.md +665 -0
- package/bin/install-agents.sh +57 -0
- package/bin/install-all.sh +49 -0
- package/bin/install-commands.sh +56 -0
- package/bin/install-skills.sh +57 -0
- package/bin/specweave.js +81 -0
- package/dist/adapters/adapter-base.d.ts +50 -0
- package/dist/adapters/adapter-base.d.ts.map +1 -0
- package/dist/adapters/adapter-base.js +146 -0
- package/dist/adapters/adapter-base.js.map +1 -0
- package/dist/adapters/adapter-interface.d.ts +108 -0
- package/dist/adapters/adapter-interface.d.ts.map +1 -0
- package/dist/adapters/adapter-interface.js +9 -0
- package/dist/adapters/adapter-interface.js.map +1 -0
- package/dist/adapters/claude/adapter.d.ts +54 -0
- package/dist/adapters/claude/adapter.d.ts.map +1 -0
- package/dist/adapters/claude/adapter.js +184 -0
- package/dist/adapters/claude/adapter.js.map +1 -0
- package/dist/adapters/copilot/adapter.d.ts +42 -0
- package/dist/adapters/copilot/adapter.d.ts.map +1 -0
- package/dist/adapters/copilot/adapter.js +239 -0
- package/dist/adapters/copilot/adapter.js.map +1 -0
- package/dist/adapters/cursor/adapter.d.ts +42 -0
- package/dist/adapters/cursor/adapter.d.ts.map +1 -0
- package/dist/adapters/cursor/adapter.js +297 -0
- package/dist/adapters/cursor/adapter.js.map +1 -0
- package/dist/adapters/generic/adapter.d.ts +40 -0
- package/dist/adapters/generic/adapter.d.ts.map +1 -0
- package/dist/adapters/generic/adapter.js +155 -0
- package/dist/adapters/generic/adapter.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +247 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/install.d.ts +7 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +160 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/list.d.ts +6 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +154 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/package.json +90 -0
- package/src/adapters/README.md +312 -0
- package/src/adapters/adapter-base.ts +146 -0
- package/src/adapters/adapter-interface.ts +120 -0
- package/src/adapters/claude/README.md +241 -0
- package/src/adapters/claude/adapter.ts +157 -0
- package/src/adapters/copilot/.github/copilot/instructions.md +376 -0
- package/src/adapters/copilot/README.md +200 -0
- package/src/adapters/copilot/adapter.ts +210 -0
- package/src/adapters/cursor/.cursor/context/docs-context.md +62 -0
- package/src/adapters/cursor/.cursor/context/increments-context.md +71 -0
- package/src/adapters/cursor/.cursor/context/strategy-context.md +73 -0
- package/src/adapters/cursor/.cursor/context/tests-context.md +89 -0
- package/src/adapters/cursor/.cursorrules +325 -0
- package/src/adapters/cursor/README.md +243 -0
- package/src/adapters/cursor/adapter.ts +268 -0
- package/src/adapters/generic/README.md +277 -0
- package/src/adapters/generic/SPECWEAVE-MANUAL.md +676 -0
- package/src/adapters/generic/adapter.ts +159 -0
- package/src/adapters/registry.yaml +126 -0
- package/src/agents/architect/AGENT.md +416 -0
- package/src/agents/devops/AGENT.md +1738 -0
- package/src/agents/docs-writer/AGENT.md +239 -0
- package/src/agents/performance/AGENT.md +228 -0
- package/src/agents/pm/AGENT.md +751 -0
- package/src/agents/qa-lead/AGENT.md +150 -0
- package/src/agents/security/AGENT.md +179 -0
- package/src/agents/sre/AGENT.md +582 -0
- package/src/agents/sre/modules/backend-diagnostics.md +481 -0
- package/src/agents/sre/modules/database-diagnostics.md +509 -0
- package/src/agents/sre/modules/infrastructure.md +561 -0
- package/src/agents/sre/modules/monitoring.md +439 -0
- package/src/agents/sre/modules/security-incidents.md +421 -0
- package/src/agents/sre/modules/ui-diagnostics.md +302 -0
- package/src/agents/sre/playbooks/01-high-cpu-usage.md +204 -0
- package/src/agents/sre/playbooks/02-database-deadlock.md +241 -0
- package/src/agents/sre/playbooks/03-memory-leak.md +252 -0
- package/src/agents/sre/playbooks/04-slow-api-response.md +269 -0
- package/src/agents/sre/playbooks/05-ddos-attack.md +293 -0
- package/src/agents/sre/playbooks/06-disk-full.md +314 -0
- package/src/agents/sre/playbooks/07-service-down.md +333 -0
- package/src/agents/sre/playbooks/08-data-corruption.md +337 -0
- package/src/agents/sre/playbooks/09-cascade-failure.md +430 -0
- package/src/agents/sre/playbooks/10-rate-limit-exceeded.md +464 -0
- package/src/agents/sre/scripts/health-check.sh +230 -0
- package/src/agents/sre/scripts/log-analyzer.py +213 -0
- package/src/agents/sre/scripts/metrics-collector.sh +294 -0
- package/src/agents/sre/scripts/trace-analyzer.js +257 -0
- package/src/agents/sre/templates/incident-report.md +249 -0
- package/src/agents/sre/templates/mitigation-plan.md +375 -0
- package/src/agents/sre/templates/post-mortem.md +418 -0
- package/src/agents/sre/templates/runbook-template.md +412 -0
- package/src/agents/tech-lead/AGENT.md +263 -0
- package/src/commands/add-tasks.md +176 -0
- package/src/commands/close-increment.md +347 -0
- package/src/commands/create-increment.md +223 -0
- package/src/commands/create-project.md +528 -0
- package/src/commands/generate-docs.md +623 -0
- package/src/commands/list-increments.md +180 -0
- package/src/commands/review-docs.md +331 -0
- package/src/commands/start-increment.md +139 -0
- package/src/commands/sync-github.md +115 -0
- package/src/commands/validate-increment.md +800 -0
- package/src/hooks/README.md +252 -0
- package/src/hooks/docs-changed.sh +59 -0
- package/src/hooks/human-input-required.sh +55 -0
- package/src/hooks/post-task-completion.sh +57 -0
- package/src/hooks/pre-implementation.sh +47 -0
- package/src/skills/ado-sync/README.md +449 -0
- package/src/skills/ado-sync/SKILL.md +245 -0
- package/src/skills/ado-sync/test-cases/test-1.yaml +9 -0
- package/src/skills/ado-sync/test-cases/test-2.yaml +8 -0
- package/src/skills/ado-sync/test-cases/test-3.yaml +9 -0
- package/src/skills/bmad-method-expert/SKILL.md +628 -0
- package/src/skills/bmad-method-expert/scripts/analyze-project.js +318 -0
- package/src/skills/bmad-method-expert/scripts/check-setup.js +208 -0
- package/src/skills/bmad-method-expert/scripts/generate-template.js +1149 -0
- package/src/skills/bmad-method-expert/scripts/validate-documents.js +340 -0
- package/src/skills/bmad-method-expert/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/bmad-method-expert/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/bmad-method-expert/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/brownfield-analyzer/SKILL.md +523 -0
- package/src/skills/brownfield-analyzer/test-cases/test-1-basic-analysis.yaml +48 -0
- package/src/skills/brownfield-analyzer/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/brownfield-analyzer/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/brownfield-onboarder/SKILL.md +625 -0
- package/src/skills/brownfield-onboarder/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/brownfield-onboarder/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/brownfield-onboarder/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/calendar-system/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/calendar-system/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/calendar-system/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/context-loader/SKILL.md +734 -0
- package/src/skills/context-loader/test-cases/test-1-basic-loading.yaml +39 -0
- package/src/skills/context-loader/test-cases/test-2-token-budget-exceeded.yaml +44 -0
- package/src/skills/context-loader/test-cases/test-3-section-anchors.yaml +45 -0
- package/src/skills/context-optimizer/SKILL.md +618 -0
- package/src/skills/context-optimizer/test-cases/test-1-bug-fix-narrow.yaml +97 -0
- package/src/skills/context-optimizer/test-cases/test-2-feature-focused.yaml +109 -0
- package/src/skills/context-optimizer/test-cases/test-3-architecture-broad.yaml +98 -0
- package/src/skills/cost-optimizer/SKILL.md +190 -0
- package/src/skills/cost-optimizer/test-cases/test-1-basic-comparison.yaml +75 -0
- package/src/skills/cost-optimizer/test-cases/test-2-budget-constraint.yaml +52 -0
- package/src/skills/cost-optimizer/test-cases/test-3-scale-requirement.yaml +63 -0
- package/src/skills/cost-optimizer/test-results/README.md +46 -0
- package/src/skills/design-system-architect/SKILL.md +107 -0
- package/src/skills/design-system-architect/test-cases/test-1-token-structure.yaml +23 -0
- package/src/skills/design-system-architect/test-cases/test-2-component-hierarchy.yaml +24 -0
- package/src/skills/design-system-architect/test-cases/test-3-accessibility-checklist.yaml +23 -0
- package/src/skills/diagrams-architect/SKILL.md +763 -0
- package/src/skills/diagrams-generator/SKILL.md +25 -0
- package/src/skills/diagrams-generator/test-cases/test-1.yaml +9 -0
- package/src/skills/diagrams-generator/test-cases/test-2.yaml +9 -0
- package/src/skills/diagrams-generator/test-cases/test-3.yaml +8 -0
- package/src/skills/docs-updater/README.md +48 -0
- package/src/skills/docs-updater/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/docs-updater/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/docs-updater/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/dotnet-backend/SKILL.md +250 -0
- package/src/skills/e2e-playwright/README.md +506 -0
- package/src/skills/e2e-playwright/SKILL.md +457 -0
- package/src/skills/e2e-playwright/execute.js +373 -0
- package/src/skills/e2e-playwright/lib/utils.js +514 -0
- package/src/skills/e2e-playwright/package.json +33 -0
- package/src/skills/e2e-playwright/test-cases/TC-001-basic-navigation.yaml +54 -0
- package/src/skills/e2e-playwright/test-cases/TC-002-form-interaction.yaml +64 -0
- package/src/skills/e2e-playwright/test-cases/TC-003-specweave-integration.yaml +74 -0
- package/src/skills/e2e-playwright/test-cases/TC-004-accessibility-check.yaml +98 -0
- package/src/skills/figma-designer/SKILL.md +149 -0
- package/src/skills/figma-implementer/SKILL.md +148 -0
- package/src/skills/figma-mcp-connector/SKILL.md +136 -0
- package/src/skills/figma-mcp-connector/test-cases/test-1-read-file-desktop.yaml +22 -0
- package/src/skills/figma-mcp-connector/test-cases/test-2-read-file-framelink.yaml +21 -0
- package/src/skills/figma-mcp-connector/test-cases/test-3-error-handling.yaml +18 -0
- package/src/skills/figma-to-code/SKILL.md +128 -0
- package/src/skills/figma-to-code/test-cases/test-1-token-generation.yaml +29 -0
- package/src/skills/figma-to-code/test-cases/test-2-component-generation.yaml +27 -0
- package/src/skills/figma-to-code/test-cases/test-3-typescript-generation.yaml +28 -0
- package/src/skills/frontend/SKILL.md +177 -0
- package/src/skills/github-sync/SKILL.md +252 -0
- package/src/skills/github-sync/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/github-sync/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/github-sync/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/hetzner-provisioner/README.md +308 -0
- package/src/skills/hetzner-provisioner/SKILL.md +251 -0
- package/src/skills/hetzner-provisioner/test-cases/test-1-basic-provision.yaml +71 -0
- package/src/skills/hetzner-provisioner/test-cases/test-2-postgres-provision.yaml +85 -0
- package/src/skills/hetzner-provisioner/test-cases/test-3-ssl-config.yaml +126 -0
- package/src/skills/hetzner-provisioner/test-results/README.md +259 -0
- package/src/skills/increment-planner/SKILL.md +889 -0
- package/src/skills/increment-planner/scripts/feature-utils.js +250 -0
- package/src/skills/increment-planner/test-cases/test-1-basic-feature.yaml +27 -0
- package/src/skills/increment-planner/test-cases/test-2-complex-feature.yaml +30 -0
- package/src/skills/increment-planner/test-cases/test-3-auto-numbering.yaml +24 -0
- package/src/skills/increment-quality-judge/SKILL.md +566 -0
- package/src/skills/increment-quality-judge/test-cases/test-1-good-spec.yaml +95 -0
- package/src/skills/increment-quality-judge/test-cases/test-2-poor-spec.yaml +108 -0
- package/src/skills/increment-quality-judge/test-cases/test-3-export-suggestions.yaml +87 -0
- package/src/skills/jira-sync/README.md +328 -0
- package/src/skills/jira-sync/SKILL.md +209 -0
- package/src/skills/jira-sync/test-cases/test-1.yaml +9 -0
- package/src/skills/jira-sync/test-cases/test-2.yaml +9 -0
- package/src/skills/jira-sync/test-cases/test-3.yaml +10 -0
- package/src/skills/nextjs/SKILL.md +176 -0
- package/src/skills/nodejs-backend/SKILL.md +181 -0
- package/src/skills/notification-system/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/notification-system/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/notification-system/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/python-backend/SKILL.md +226 -0
- package/src/skills/role-orchestrator/README.md +197 -0
- package/src/skills/role-orchestrator/SKILL.md +1184 -0
- package/src/skills/role-orchestrator/test-cases/test-1-simple-product.yaml +98 -0
- package/src/skills/role-orchestrator/test-cases/test-2-quality-gate-failure.yaml +73 -0
- package/src/skills/role-orchestrator/test-cases/test-3-security-workflow.yaml +121 -0
- package/src/skills/role-orchestrator/test-cases/test-4-parallel-execution.yaml +145 -0
- package/src/skills/role-orchestrator/test-cases/test-5-feedback-loops.yaml +149 -0
- package/src/skills/skill-creator/LICENSE.txt +202 -0
- package/src/skills/skill-creator/SKILL.md +209 -0
- package/src/skills/skill-creator/scripts/init_skill.py +303 -0
- package/src/skills/skill-creator/scripts/package_skill.py +110 -0
- package/src/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/src/skills/skill-creator/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/skill-creator/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/skill-creator/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/skill-router/SKILL.md +497 -0
- package/src/skills/skill-router/test-cases/test-1-basic-routing.yaml +33 -0
- package/src/skills/skill-router/test-cases/test-2-ambiguous-request.yaml +42 -0
- package/src/skills/skill-router/test-cases/test-3-nested-orchestration.yaml +50 -0
- package/src/skills/spec-driven-brainstorming/README.md +264 -0
- package/src/skills/spec-driven-brainstorming/SKILL.md +439 -0
- package/src/skills/spec-driven-brainstorming/test-cases/TC-001-simple-idea-to-design.yaml +148 -0
- package/src/skills/spec-driven-brainstorming/test-cases/TC-002-complex-ultrathink-design.yaml +190 -0
- package/src/skills/spec-driven-brainstorming/test-cases/TC-003-unclear-requirements-socratic.yaml +233 -0
- package/src/skills/spec-driven-debugging/README.md +479 -0
- package/src/skills/spec-driven-debugging/SKILL.md +652 -0
- package/src/skills/spec-driven-debugging/test-cases/TC-001-simple-auth-bug.yaml +212 -0
- package/src/skills/spec-driven-debugging/test-cases/TC-002-race-condition-ultrathink.yaml +461 -0
- package/src/skills/spec-driven-debugging/test-cases/TC-003-brownfield-missing-spec.yaml +366 -0
- package/src/skills/spec-kit-expert/SKILL.md +1012 -0
- package/src/skills/spec-kit-expert/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/spec-kit-expert/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/spec-kit-expert/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/specweave-ado-mapper/SKILL.md +501 -0
- package/src/skills/specweave-detector/SKILL.md +420 -0
- package/src/skills/specweave-detector/test-cases/test-1-basic-detection.yaml +37 -0
- package/src/skills/specweave-detector/test-cases/test-2-missing-config.yaml +37 -0
- package/src/skills/specweave-detector/test-cases/test-3-non-specweave-project.yaml +34 -0
- package/src/skills/specweave-jira-mapper/SKILL.md +500 -0
- package/src/skills/stripe-integrator/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/stripe-integrator/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/stripe-integrator/test-cases/test-3-placeholder.yaml +12 -0
- package/src/skills/task-builder/README.md +90 -0
- package/src/skills/task-builder/test-cases/test-1-placeholder.yaml +12 -0
- package/src/skills/task-builder/test-cases/test-2-placeholder.yaml +12 -0
- package/src/skills/task-builder/test-cases/test-3-placeholder.yaml +12 -0
- package/src/templates/.env.example +144 -0
- package/src/templates/.gitignore.template +81 -0
- package/src/templates/CLAUDE.md.template +383 -0
- package/src/templates/README.md.template +240 -0
- package/src/templates/config.yaml +333 -0
- package/src/templates/docs/README.md +124 -0
- package/src/templates/docs/adr-template.md +118 -0
- package/src/templates/docs/hld-template.md +220 -0
- package/src/templates/docs/lld-template.md +580 -0
- package/src/templates/docs/prd-template.md +132 -0
- package/src/templates/docs/rfc-template.md +229 -0
- package/src/templates/docs/runbook-template.md +298 -0
- package/src/templates/environments/minimal/.env.production +16 -0
- package/src/templates/environments/minimal/README.md +54 -0
- package/src/templates/environments/minimal/deploy-production.yml +52 -0
- package/src/templates/environments/progressive/.env.qa +28 -0
- package/src/templates/environments/progressive/README.md +129 -0
- package/src/templates/environments/progressive/deploy-production.yml +93 -0
- package/src/templates/environments/progressive/deploy-qa.yml +62 -0
- package/src/templates/environments/progressive/deploy-staging.yml +67 -0
- package/src/templates/environments/standard/.env.development +20 -0
- package/src/templates/environments/standard/.env.production +30 -0
- package/src/templates/environments/standard/.env.staging +23 -0
- package/src/templates/environments/standard/README.md +97 -0
- package/src/templates/environments/standard/deploy-production.yml +68 -0
- package/src/templates/environments/standard/deploy-staging.yml +61 -0
- package/src/templates/environments/standard/docker-compose.yml +43 -0
- package/src/templates/increment-metadata-template.yaml +138 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright Helper Utilities for SpecWeave
|
|
3
|
+
*
|
|
4
|
+
* Collection of helper functions for common Playwright testing patterns.
|
|
5
|
+
* SpecWeave-aware utilities for enhanced testing workflows.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect running development servers on common ports
|
|
14
|
+
*
|
|
15
|
+
* @returns {Promise<Array<{port: number, url: string, name: string}>>}
|
|
16
|
+
*/
|
|
17
|
+
async function detectServers() {
|
|
18
|
+
const commonPorts = [3000, 3001, 3002, 3010, 4000, 5000, 5173, 8000, 8080, 8888];
|
|
19
|
+
const servers = [];
|
|
20
|
+
|
|
21
|
+
for (const port of commonPorts) {
|
|
22
|
+
try {
|
|
23
|
+
// Use lsof to check if port is in use (macOS/Linux)
|
|
24
|
+
const result = execSync(`lsof -i :${port} -sTCP:LISTEN -t`, {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (result.trim()) {
|
|
30
|
+
servers.push({
|
|
31
|
+
port,
|
|
32
|
+
url: `http://localhost:${port}`,
|
|
33
|
+
name: guessServerName(port)
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Port not in use, continue
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return servers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Guess server name based on common port conventions
|
|
46
|
+
*/
|
|
47
|
+
function guessServerName(port) {
|
|
48
|
+
const nameMap = {
|
|
49
|
+
3000: 'Next.js / React Dev',
|
|
50
|
+
3001: 'Secondary Dev Server',
|
|
51
|
+
5173: 'Vite',
|
|
52
|
+
4000: 'Express / Backend',
|
|
53
|
+
8000: 'Python / Django',
|
|
54
|
+
8080: 'Java / Spring Boot'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return nameMap[port] || 'Dev Server';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Safe click with automatic wait and retry
|
|
62
|
+
*
|
|
63
|
+
* @param {Page} page - Playwright page object
|
|
64
|
+
* @param {string} selector - CSS selector
|
|
65
|
+
* @param {object} options - Click options
|
|
66
|
+
*/
|
|
67
|
+
async function safeClick(page, selector, options = {}) {
|
|
68
|
+
const timeout = options.timeout || 10000;
|
|
69
|
+
const retries = options.retries || 3;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < retries; i++) {
|
|
72
|
+
try {
|
|
73
|
+
await page.waitForSelector(selector, { timeout, state: 'visible' });
|
|
74
|
+
await page.click(selector, { timeout });
|
|
75
|
+
return;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (i === retries - 1) {
|
|
78
|
+
throw new Error(`Failed to click "${selector}" after ${retries} attempts: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
// Wait before retry
|
|
81
|
+
await page.waitForTimeout(1000);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Safe type with automatic focus and validation
|
|
88
|
+
*
|
|
89
|
+
* @param {Page} page - Playwright page object
|
|
90
|
+
* @param {string} selector - CSS selector
|
|
91
|
+
* @param {string} text - Text to type
|
|
92
|
+
* @param {object} options - Type options
|
|
93
|
+
*/
|
|
94
|
+
async function safeType(page, selector, text, options = {}) {
|
|
95
|
+
const timeout = options.timeout || 10000;
|
|
96
|
+
const clearFirst = options.clear !== false; // Default true
|
|
97
|
+
|
|
98
|
+
await page.waitForSelector(selector, { timeout, state: 'visible' });
|
|
99
|
+
|
|
100
|
+
// Focus the input
|
|
101
|
+
await page.focus(selector);
|
|
102
|
+
|
|
103
|
+
// Clear existing value if requested
|
|
104
|
+
if (clearFirst) {
|
|
105
|
+
await page.fill(selector, '');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Type with slight delay for realism
|
|
109
|
+
await page.type(selector, text, { delay: options.delay || 50 });
|
|
110
|
+
|
|
111
|
+
// Verify text was entered (optional)
|
|
112
|
+
if (options.verify !== false) {
|
|
113
|
+
const value = await page.inputValue(selector);
|
|
114
|
+
if (value !== text && !options.partial) {
|
|
115
|
+
throw new Error(`Failed to type into "${selector}". Expected "${text}", got "${value}"`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Capture timestamped screenshot
|
|
122
|
+
*
|
|
123
|
+
* @param {Page} page - Playwright page object
|
|
124
|
+
* @param {string} name - Base name for screenshot
|
|
125
|
+
* @param {object} options - Screenshot options
|
|
126
|
+
* @returns {string} Path to screenshot
|
|
127
|
+
*/
|
|
128
|
+
async function captureScreenshot(page, name, options = {}) {
|
|
129
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
130
|
+
const filename = `${name}-${timestamp}.png`;
|
|
131
|
+
const filepath = path.join(options.dir || '/tmp', filename);
|
|
132
|
+
|
|
133
|
+
await page.screenshot({
|
|
134
|
+
path: filepath,
|
|
135
|
+
fullPage: options.fullPage !== false, // Default true
|
|
136
|
+
...options
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log(`📸 Screenshot saved: ${filepath}`);
|
|
140
|
+
return filepath;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Handle common cookie consent banners
|
|
145
|
+
*
|
|
146
|
+
* @param {Page} page - Playwright page object
|
|
147
|
+
*/
|
|
148
|
+
async function handleCookieBanner(page) {
|
|
149
|
+
const commonSelectors = [
|
|
150
|
+
'button:has-text("Accept")',
|
|
151
|
+
'button:has-text("Accept All")',
|
|
152
|
+
'button:has-text("Agree")',
|
|
153
|
+
'button:has-text("OK")',
|
|
154
|
+
'button:has-text("I Agree")',
|
|
155
|
+
'button[id*="accept"]',
|
|
156
|
+
'button[class*="accept"]',
|
|
157
|
+
'button[class*="cookie"]',
|
|
158
|
+
'[data-testid="cookie-accept"]'
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
for (const selector of commonSelectors) {
|
|
162
|
+
try {
|
|
163
|
+
const element = await page.locator(selector).first();
|
|
164
|
+
if (await element.isVisible({ timeout: 2000 })) {
|
|
165
|
+
await element.click();
|
|
166
|
+
console.log('✅ Cookie banner dismissed');
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
// Continue to next selector
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extract data from HTML table
|
|
179
|
+
*
|
|
180
|
+
* @param {Page} page - Playwright page object
|
|
181
|
+
* @param {string} selector - Table selector
|
|
182
|
+
* @returns {Array<object>} Array of row objects
|
|
183
|
+
*/
|
|
184
|
+
async function extractTableData(page, selector) {
|
|
185
|
+
return await page.evaluate((sel) => {
|
|
186
|
+
const table = document.querySelector(sel);
|
|
187
|
+
if (!table) return [];
|
|
188
|
+
|
|
189
|
+
const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim());
|
|
190
|
+
const rows = Array.from(table.querySelectorAll('tbody tr'));
|
|
191
|
+
|
|
192
|
+
return rows.map(row => {
|
|
193
|
+
const cells = Array.from(row.querySelectorAll('td'));
|
|
194
|
+
const rowData = {};
|
|
195
|
+
cells.forEach((cell, index) => {
|
|
196
|
+
rowData[headers[index] || `column${index}`] = cell.textContent.trim();
|
|
197
|
+
});
|
|
198
|
+
return rowData;
|
|
199
|
+
});
|
|
200
|
+
}, selector);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Wait for DOM to stabilize (no changes for specified duration)
|
|
205
|
+
*
|
|
206
|
+
* @param {Page} page - Playwright page object
|
|
207
|
+
* @param {number} stabilityTime - Time in ms to wait for stability
|
|
208
|
+
*/
|
|
209
|
+
async function waitForStableDOM(page, stabilityTime = 1000) {
|
|
210
|
+
let lastMutationTime = Date.now();
|
|
211
|
+
let stabilityTimeout;
|
|
212
|
+
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
const observer = page.evaluateHandle((ms) => {
|
|
215
|
+
return new Promise((res) => {
|
|
216
|
+
let lastChange = Date.now();
|
|
217
|
+
let timeout;
|
|
218
|
+
|
|
219
|
+
const checkStability = () => {
|
|
220
|
+
if (Date.now() - lastChange >= ms) {
|
|
221
|
+
observer.disconnect();
|
|
222
|
+
res();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const observer = new MutationObserver(() => {
|
|
227
|
+
lastChange = Date.now();
|
|
228
|
+
clearTimeout(timeout);
|
|
229
|
+
timeout = setTimeout(checkStability, ms);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
observer.observe(document.body, {
|
|
233
|
+
childList: true,
|
|
234
|
+
subtree: true,
|
|
235
|
+
attributes: true
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Initial stability check
|
|
239
|
+
timeout = setTimeout(checkStability, ms);
|
|
240
|
+
});
|
|
241
|
+
}, stabilityTime);
|
|
242
|
+
|
|
243
|
+
observer.then(() => resolve());
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Basic accessibility checks
|
|
249
|
+
*
|
|
250
|
+
* @param {Page} page - Playwright page object
|
|
251
|
+
* @returns {Array<{type: string, element: string, message: string}>}
|
|
252
|
+
*/
|
|
253
|
+
async function checkAccessibility(page) {
|
|
254
|
+
return await page.evaluate(() => {
|
|
255
|
+
const issues = [];
|
|
256
|
+
|
|
257
|
+
// Check for images without alt text
|
|
258
|
+
document.querySelectorAll('img').forEach(img => {
|
|
259
|
+
if (!img.alt) {
|
|
260
|
+
issues.push({
|
|
261
|
+
type: 'missing-alt',
|
|
262
|
+
element: img.outerHTML.substring(0, 100),
|
|
263
|
+
message: 'Image missing alt text'
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Check for form inputs without labels
|
|
269
|
+
document.querySelectorAll('input, textarea, select').forEach(input => {
|
|
270
|
+
if (input.type === 'hidden') return;
|
|
271
|
+
|
|
272
|
+
const hasLabel = !!input.closest('label') ||
|
|
273
|
+
!!document.querySelector(`label[for="${input.id}"]`) ||
|
|
274
|
+
!!input.getAttribute('aria-label') ||
|
|
275
|
+
!!input.getAttribute('aria-labelledby');
|
|
276
|
+
|
|
277
|
+
if (!hasLabel) {
|
|
278
|
+
issues.push({
|
|
279
|
+
type: 'missing-label',
|
|
280
|
+
element: input.outerHTML.substring(0, 100),
|
|
281
|
+
message: 'Form input missing label or ARIA label'
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Check for buttons without accessible text
|
|
287
|
+
document.querySelectorAll('button').forEach(button => {
|
|
288
|
+
const hasText = button.textContent.trim() ||
|
|
289
|
+
button.getAttribute('aria-label') ||
|
|
290
|
+
button.querySelector('img')?.alt;
|
|
291
|
+
|
|
292
|
+
if (!hasText) {
|
|
293
|
+
issues.push({
|
|
294
|
+
type: 'missing-button-text',
|
|
295
|
+
element: button.outerHTML.substring(0, 100),
|
|
296
|
+
message: 'Button missing accessible text'
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Check for links without text
|
|
302
|
+
document.querySelectorAll('a').forEach(link => {
|
|
303
|
+
const hasText = link.textContent.trim() ||
|
|
304
|
+
link.getAttribute('aria-label') ||
|
|
305
|
+
link.querySelector('img')?.alt;
|
|
306
|
+
|
|
307
|
+
if (!hasText) {
|
|
308
|
+
issues.push({
|
|
309
|
+
type: 'missing-link-text',
|
|
310
|
+
element: link.outerHTML.substring(0, 100),
|
|
311
|
+
message: 'Link missing accessible text'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return issues;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate SpecWeave test report
|
|
322
|
+
*
|
|
323
|
+
* @param {object} results - Test results object
|
|
324
|
+
* @param {string} incrementId - Increment ID (e.g., "0003-user-auth")
|
|
325
|
+
* @returns {string} Report content in Markdown
|
|
326
|
+
*/
|
|
327
|
+
function generateTestReport(results, incrementId) {
|
|
328
|
+
const {
|
|
329
|
+
tests = [],
|
|
330
|
+
summary = {},
|
|
331
|
+
performance = {},
|
|
332
|
+
accessibility = [],
|
|
333
|
+
recommendations = []
|
|
334
|
+
} = results;
|
|
335
|
+
|
|
336
|
+
const date = new Date().toISOString().split('T')[0];
|
|
337
|
+
const status = summary.failed === 0 ? '✅ Passed' : '❌ Failed';
|
|
338
|
+
|
|
339
|
+
let report = `# E2E Test Report - Increment ${incrementId}\n\n`;
|
|
340
|
+
report += `**Date**: ${date}\n`;
|
|
341
|
+
report += `**Duration**: ${summary.duration || 'N/A'}\n`;
|
|
342
|
+
report += `**Status**: ${status}\n\n`;
|
|
343
|
+
|
|
344
|
+
report += `## Test Summary\n\n`;
|
|
345
|
+
report += `- Total Tests: ${summary.total || 0}\n`;
|
|
346
|
+
report += `- Passed: ${summary.passed || 0}\n`;
|
|
347
|
+
report += `- Failed: ${summary.failed || 0}\n`;
|
|
348
|
+
report += `- Skipped: ${summary.skipped || 0}\n\n`;
|
|
349
|
+
|
|
350
|
+
if (tests.length > 0) {
|
|
351
|
+
report += `## Test Results\n\n`;
|
|
352
|
+
tests.forEach(test => {
|
|
353
|
+
report += `### ${test.name}\n`;
|
|
354
|
+
report += `- **Status**: ${test.status}\n`;
|
|
355
|
+
report += `- **Duration**: ${test.duration}\n`;
|
|
356
|
+
if (test.screenshot) {
|
|
357
|
+
report += `- **Screenshot**: \`${test.screenshot}\`\n`;
|
|
358
|
+
}
|
|
359
|
+
if (test.error) {
|
|
360
|
+
report += `- **Error**: ${test.error}\n`;
|
|
361
|
+
}
|
|
362
|
+
report += `\n`;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (Object.keys(performance).length > 0) {
|
|
367
|
+
report += `## Performance Metrics\n\n`;
|
|
368
|
+
Object.entries(performance).forEach(([key, value]) => {
|
|
369
|
+
report += `- ${key}: ${value}\n`;
|
|
370
|
+
});
|
|
371
|
+
report += `\n`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (accessibility.length > 0) {
|
|
375
|
+
report += `## Accessibility Issues\n\n`;
|
|
376
|
+
const grouped = accessibility.reduce((acc, issue) => {
|
|
377
|
+
acc[issue.type] = (acc[issue.type] || 0) + 1;
|
|
378
|
+
return acc;
|
|
379
|
+
}, {});
|
|
380
|
+
|
|
381
|
+
Object.entries(grouped).forEach(([type, count]) => {
|
|
382
|
+
report += `- ${type}: ${count} instance${count > 1 ? 's' : ''}\n`;
|
|
383
|
+
});
|
|
384
|
+
report += `\n`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (recommendations.length > 0) {
|
|
388
|
+
report += `## Recommendations\n\n`;
|
|
389
|
+
recommendations.forEach((rec, i) => {
|
|
390
|
+
report += `${i + 1}. ${rec}\n`;
|
|
391
|
+
});
|
|
392
|
+
report += `\n`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
report += `---\n`;
|
|
396
|
+
report += `*Generated by e2e-playwright skill*\n`;
|
|
397
|
+
|
|
398
|
+
return report;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Save test report to SpecWeave increment folder
|
|
403
|
+
*
|
|
404
|
+
* @param {string} reportContent - Markdown report content
|
|
405
|
+
* @param {string} incrementPath - Path to increment folder
|
|
406
|
+
* @param {string} filename - Report filename
|
|
407
|
+
*/
|
|
408
|
+
function saveTestReport(reportContent, incrementPath, filename = 'e2e-test-report.md') {
|
|
409
|
+
const reportsDir = path.join(incrementPath, 'reports');
|
|
410
|
+
|
|
411
|
+
// Create reports directory if it doesn't exist
|
|
412
|
+
if (!fs.existsSync(reportsDir)) {
|
|
413
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const reportPath = path.join(reportsDir, filename);
|
|
417
|
+
fs.writeFileSync(reportPath, reportContent, 'utf8');
|
|
418
|
+
|
|
419
|
+
console.log(`📄 Test report saved: ${reportPath}`);
|
|
420
|
+
return reportPath;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Wait for network to be idle
|
|
425
|
+
*
|
|
426
|
+
* @param {Page} page - Playwright page object
|
|
427
|
+
* @param {number} timeout - Timeout in ms
|
|
428
|
+
*/
|
|
429
|
+
async function waitForNetworkIdle(page, timeout = 30000) {
|
|
430
|
+
try {
|
|
431
|
+
await page.waitForLoadState('networkidle', { timeout });
|
|
432
|
+
} catch (e) {
|
|
433
|
+
console.warn('⚠️ Network idle timeout - continuing anyway');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Scroll to element
|
|
439
|
+
*
|
|
440
|
+
* @param {Page} page - Playwright page object
|
|
441
|
+
* @param {string} selector - CSS selector
|
|
442
|
+
*/
|
|
443
|
+
async function scrollToElement(page, selector) {
|
|
444
|
+
await page.locator(selector).scrollIntoViewIfNeeded();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Wait for element to be visible
|
|
449
|
+
*
|
|
450
|
+
* @param {Page} page - Playwright page object
|
|
451
|
+
* @param {string} selector - CSS selector
|
|
452
|
+
* @param {number} timeout - Timeout in ms
|
|
453
|
+
*/
|
|
454
|
+
async function waitForVisible(page, selector, timeout = 10000) {
|
|
455
|
+
await page.waitForSelector(selector, { state: 'visible', timeout });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Check if element exists
|
|
460
|
+
*
|
|
461
|
+
* @param {Page} page - Playwright page object
|
|
462
|
+
* @param {string} selector - CSS selector
|
|
463
|
+
* @returns {boolean}
|
|
464
|
+
*/
|
|
465
|
+
async function elementExists(page, selector) {
|
|
466
|
+
try {
|
|
467
|
+
await page.waitForSelector(selector, { timeout: 1000 });
|
|
468
|
+
return true;
|
|
469
|
+
} catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get text content of element
|
|
476
|
+
*
|
|
477
|
+
* @param {Page} page - Playwright page object
|
|
478
|
+
* @param {string} selector - CSS selector
|
|
479
|
+
* @returns {string}
|
|
480
|
+
*/
|
|
481
|
+
async function getText(page, selector) {
|
|
482
|
+
return await page.locator(selector).textContent();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Get all matching elements count
|
|
487
|
+
*
|
|
488
|
+
* @param {Page} page - Playwright page object
|
|
489
|
+
* @param {string} selector - CSS selector
|
|
490
|
+
* @returns {number}
|
|
491
|
+
*/
|
|
492
|
+
async function countElements(page, selector) {
|
|
493
|
+
return await page.locator(selector).count();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Export all utilities
|
|
497
|
+
module.exports = {
|
|
498
|
+
detectServers,
|
|
499
|
+
safeClick,
|
|
500
|
+
safeType,
|
|
501
|
+
captureScreenshot,
|
|
502
|
+
handleCookieBanner,
|
|
503
|
+
extractTableData,
|
|
504
|
+
waitForStableDOM,
|
|
505
|
+
checkAccessibility,
|
|
506
|
+
generateTestReport,
|
|
507
|
+
saveTestReport,
|
|
508
|
+
waitForNetworkIdle,
|
|
509
|
+
scrollToElement,
|
|
510
|
+
waitForVisible,
|
|
511
|
+
elementExists,
|
|
512
|
+
getText,
|
|
513
|
+
countElements
|
|
514
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "e2e-playwright-skill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "End-to-end browser automation and testing skill for SpecWeave using Playwright",
|
|
5
|
+
"main": "execute.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"setup": "npm install && npx playwright install chromium",
|
|
8
|
+
"install-browsers": "npx playwright install",
|
|
9
|
+
"test": "node execute.js --version",
|
|
10
|
+
"clean": "rm -f /tmp/e2e-execution-*.js /tmp/e2e-test-*.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"playwright",
|
|
14
|
+
"e2e",
|
|
15
|
+
"testing",
|
|
16
|
+
"browser-automation",
|
|
17
|
+
"specweave",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"skill"
|
|
20
|
+
],
|
|
21
|
+
"author": "SpecWeave",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"playwright": "^1.48.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14.0.0"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/specweave/specweave"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
id: TC-001
|
|
2
|
+
name: Basic Navigation Test
|
|
3
|
+
description: Verify that the skill can navigate to a webpage and capture a screenshot
|
|
4
|
+
priority: high
|
|
5
|
+
category: functional
|
|
6
|
+
|
|
7
|
+
preconditions:
|
|
8
|
+
- Playwright is installed
|
|
9
|
+
- Chromium browser is available
|
|
10
|
+
- Development server is running on localhost:3000
|
|
11
|
+
|
|
12
|
+
test_steps:
|
|
13
|
+
- step: 1
|
|
14
|
+
action: Create test script that navigates to localhost:3000
|
|
15
|
+
expected: Test script is created in /tmp/
|
|
16
|
+
|
|
17
|
+
- step: 2
|
|
18
|
+
action: Execute test script via execute.js
|
|
19
|
+
expected: Browser launches and navigates to homepage
|
|
20
|
+
|
|
21
|
+
- step: 3
|
|
22
|
+
action: Capture full-page screenshot
|
|
23
|
+
expected: Screenshot is saved to /tmp/ with timestamp
|
|
24
|
+
|
|
25
|
+
- step: 4
|
|
26
|
+
action: Verify page title
|
|
27
|
+
expected: Page title is captured and logged to console
|
|
28
|
+
|
|
29
|
+
- step: 5
|
|
30
|
+
action: Close browser
|
|
31
|
+
expected: Browser closes cleanly without errors
|
|
32
|
+
|
|
33
|
+
acceptance_criteria:
|
|
34
|
+
- Browser launches in visible mode (headless: false)
|
|
35
|
+
- Navigation completes successfully
|
|
36
|
+
- Screenshot file is created with correct naming pattern
|
|
37
|
+
- Console output shows execution steps
|
|
38
|
+
- No errors are thrown during execution
|
|
39
|
+
|
|
40
|
+
test_data:
|
|
41
|
+
url: http://localhost:3000
|
|
42
|
+
expected_title: SpecWeave
|
|
43
|
+
screenshot_pattern: /tmp/homepage-*.png
|
|
44
|
+
|
|
45
|
+
expected_results:
|
|
46
|
+
- Browser opens visibly
|
|
47
|
+
- Page loads within 10 seconds
|
|
48
|
+
- Screenshot file exists and is valid PNG
|
|
49
|
+
- Console shows "✅ Test passed" message
|
|
50
|
+
- Process exits with code 0
|
|
51
|
+
|
|
52
|
+
notes: |
|
|
53
|
+
This is the most basic test case to verify core functionality.
|
|
54
|
+
All other tests depend on this working correctly.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
id: TC-002
|
|
2
|
+
name: Form Interaction Test
|
|
3
|
+
description: Verify that the skill can interact with form elements using helper utilities
|
|
4
|
+
priority: high
|
|
5
|
+
category: functional
|
|
6
|
+
|
|
7
|
+
preconditions:
|
|
8
|
+
- TC-001 passes
|
|
9
|
+
- Test page has a login form with email and password fields
|
|
10
|
+
- safeType and safeClick utilities are available
|
|
11
|
+
|
|
12
|
+
test_steps:
|
|
13
|
+
- step: 1
|
|
14
|
+
action: Navigate to login page
|
|
15
|
+
expected: Login form is visible
|
|
16
|
+
|
|
17
|
+
- step: 2
|
|
18
|
+
action: Use safeType() to enter email address
|
|
19
|
+
expected: Email field is filled with test email
|
|
20
|
+
|
|
21
|
+
- step: 3
|
|
22
|
+
action: Use safeType() to enter password
|
|
23
|
+
expected: Password field is filled (masked)
|
|
24
|
+
|
|
25
|
+
- step: 4
|
|
26
|
+
action: Use safeClick() to submit form
|
|
27
|
+
expected: Form submission is triggered
|
|
28
|
+
|
|
29
|
+
- step: 5
|
|
30
|
+
action: Wait for navigation or success message
|
|
31
|
+
expected: User is redirected or sees success state
|
|
32
|
+
|
|
33
|
+
- step: 6
|
|
34
|
+
action: Capture screenshot of result
|
|
35
|
+
expected: Screenshot shows post-login state
|
|
36
|
+
|
|
37
|
+
acceptance_criteria:
|
|
38
|
+
- safeType() handles focus and clearing automatically
|
|
39
|
+
- safeType() verifies text was entered correctly
|
|
40
|
+
- safeClick() waits for element to be visible
|
|
41
|
+
- Form submission completes without timeout
|
|
42
|
+
- Screenshots capture both pre and post submission states
|
|
43
|
+
|
|
44
|
+
test_data:
|
|
45
|
+
email: test@example.com
|
|
46
|
+
password: testpassword123
|
|
47
|
+
login_url: http://localhost:3000/login
|
|
48
|
+
expected_redirect: /dashboard
|
|
49
|
+
|
|
50
|
+
expected_results:
|
|
51
|
+
- Email field contains entered value
|
|
52
|
+
- Password field is masked
|
|
53
|
+
- Form submits successfully
|
|
54
|
+
- Navigation completes within 10 seconds
|
|
55
|
+
- Success state is reached
|
|
56
|
+
|
|
57
|
+
error_scenarios:
|
|
58
|
+
- If email field not found: Error message with selector details
|
|
59
|
+
- If click times out: Retry up to 3 times
|
|
60
|
+
- If verification fails: Clear error about mismatch
|
|
61
|
+
|
|
62
|
+
notes: |
|
|
63
|
+
Tests the core interaction utilities that make Playwright easier to use.
|
|
64
|
+
safeType() and safeClick() should handle common edge cases automatically.
|