specweave 0.23.14 → 0.23.18
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/.claude-plugin/marketplace.json +11 -0
- package/CLAUDE.md +77 -7
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +57 -0
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-content.js +3 -0
- package/dist/src/cli/commands/sync-spec-content.js.map +1 -1
- package/dist/src/core/progress/progress-tracker.d.ts +4 -1
- package/dist/src/core/progress/progress-tracker.d.ts.map +1 -1
- package/dist/src/core/progress/progress-tracker.js +33 -4
- package/dist/src/core/progress/progress-tracker.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +1 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/spec-detector.d.ts +5 -0
- package/dist/src/core/spec-detector.d.ts.map +1 -1
- package/dist/src/core/spec-detector.js +91 -33
- package/dist/src/core/spec-detector.js.map +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -1
- package/dist/src/integrations/ado/ado-dependency-loader.js +39 -7
- package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +1 -1
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh.bak +245 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/lib/sync-spec-content.sh.bak +149 -0
- package/plugins/specweave/hooks/lib/update-status-line.sh +34 -4
- package/plugins/specweave/hooks/lib/validate-spec-status.sh +1 -1
- package/plugins/specweave/hooks/lib/validate-spec-status.sh.bak +163 -0
- package/plugins/specweave/hooks/post-first-increment.sh +1 -1
- package/plugins/specweave/hooks/post-first-increment.sh.bak +61 -0
- package/plugins/specweave/hooks/post-spec-update.sh +1 -1
- package/plugins/specweave/hooks/post-spec-update.sh.bak +158 -0
- package/plugins/specweave/hooks/post-user-story-complete.sh +1 -1
- package/plugins/specweave/hooks/post-user-story-complete.sh.bak +179 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +1 -1
- package/plugins/specweave/hooks/pre-command-deduplication.sh.bak +83 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh.bak +386 -0
- package/plugins/specweave/skills/specweave-framework/SKILL.md +1 -1
- package/plugins/specweave-ado/agents/ado-manager/AGENT.md +23 -0
- package/plugins/specweave-ado/agents/ado-multi-project-mapper/AGENT.md +23 -0
- package/plugins/specweave-ado/agents/ado-sync-judge/AGENT.md +23 -0
- package/plugins/specweave-backend/agents/database-optimizer/AGENT.md +23 -0
- package/plugins/specweave-confluent/agents/confluent-architect/AGENT.md +23 -0
- package/plugins/specweave-diagrams/agents/diagrams-architect/AGENT.md +23 -0
- package/plugins/specweave-github/.claude-plugin/plugin.json +15 -1
- package/plugins/specweave-github/agents/github-manager/AGENT.md +23 -0
- package/plugins/specweave-github/agents/github-task-splitter/AGENT.md +25 -0
- package/plugins/specweave-github/agents/user-story-updater/AGENT.md +25 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +16 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh +53 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.js +49 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +67 -0
- package/plugins/specweave-infrastructure/agents/devops/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/network-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/observability-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/performance-engineer/AGENT.md +26 -0
- package/plugins/specweave-infrastructure/agents/sre/AGENT.md +26 -0
- package/plugins/specweave-jira/agents/jira-manager/AGENT.md +26 -0
- package/plugins/specweave-kafka/agents/kafka-architect/AGENT.md +26 -0
- package/plugins/specweave-kafka/agents/kafka-devops/AGENT.md +26 -0
- package/plugins/specweave-kafka/agents/kafka-observability/AGENT.md +26 -0
- package/plugins/specweave-kubernetes/agents/kubernetes-architect/AGENT.md +26 -0
- package/plugins/specweave-ml/.claude-plugin/plugin.json +2 -2
- package/plugins/specweave-ml/agents/data-scientist/AGENT.md +26 -0
- package/plugins/specweave-ml/agents/ml-engineer/AGENT.md +26 -0
- package/plugins/specweave-ml/agents/mlops-engineer/AGENT.md +26 -0
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +26 -0
- package/plugins/specweave-payments/agents/payment-integration/AGENT.md +26 -0
- package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +19 -0
- package/plugins/specweave-plugin-dev/skills/plugin-expert/SKILL.md +1231 -0
- package/plugins/specweave-release/agents/release-manager/AGENT.md +27 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +24 -0
- package/plugins/specweave/skills/plugin-expert/SKILL.md +0 -340
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +0 -21
- package/plugins/specweave-alternatives/skills/bmad-method-expert/SKILL.md +0 -626
- package/plugins/specweave-alternatives/skills/bmad-method-expert/scripts/analyze-project.js +0 -318
- package/plugins/specweave-alternatives/skills/bmad-method-expert/scripts/check-setup.js +0 -208
- package/plugins/specweave-alternatives/skills/bmad-method-expert/scripts/generate-template.js +0 -1149
- package/plugins/specweave-alternatives/skills/bmad-method-expert/scripts/validate-documents.js +0 -340
- package/plugins/specweave-alternatives/skills/spec-kit-expert/SKILL.md +0 -1010
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +0 -20
- package/plugins/specweave-cost-optimizer/skills/cost-optimizer/SKILL.md +0 -190
- package/plugins/specweave-docs/.claude-plugin/plugin.json +0 -19
- package/plugins/specweave-docs/skills/docusaurus/SKILL.md +0 -613
- package/plugins/specweave-docs/skills/spec-driven-brainstorming/README.md +0 -264
- package/plugins/specweave-docs/skills/spec-driven-brainstorming/SKILL.md +0 -439
- package/plugins/specweave-docs/skills/spec-driven-debugging/README.md +0 -479
- package/plugins/specweave-docs/skills/spec-driven-debugging/SKILL.md +0 -652
- package/plugins/specweave-figma/.claude-plugin/.mcp.json +0 -12
- package/plugins/specweave-figma/.claude-plugin/plugin.json +0 -20
- package/plugins/specweave-figma/ARCHITECTURE.md +0 -453
- package/plugins/specweave-figma/README.md +0 -728
- package/plugins/specweave-figma/skills/figma-to-code/SKILL.md +0 -632
- package/plugins/specweave-figma/skills/figma-to-code/test-1-token-generation.yaml +0 -29
- package/plugins/specweave-figma/skills/figma-to-code/test-2-component-generation.yaml +0 -27
- package/plugins/specweave-figma/skills/figma-to-code/test-3-typescript-generation.yaml +0 -28
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +0 -21
- package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +0 -107
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +0 -177
- package/plugins/specweave-frontend/skills/nextjs/SKILL.md +0 -176
- package/plugins/specweave-testing/.claude-plugin/plugin.json +0 -20
- package/plugins/specweave-testing/skills/e2e-playwright/README.md +0 -506
- package/plugins/specweave-testing/skills/e2e-playwright/SKILL.md +0 -457
- package/plugins/specweave-testing/skills/e2e-playwright/execute.js +0 -373
- package/plugins/specweave-testing/skills/e2e-playwright/lib/utils.js +0 -514
- package/plugins/specweave-testing/skills/e2e-playwright/package.json +0 -33
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +0 -19
- package/plugins/specweave-tooling/skills/skill-creator/LICENSE.txt +0 -202
- package/plugins/specweave-tooling/skills/skill-creator/SKILL.md +0 -209
- package/plugins/specweave-tooling/skills/skill-creator/scripts/init_skill.py +0 -303
- package/plugins/specweave-tooling/skills/skill-creator/scripts/package_skill.py +0 -110
- package/plugins/specweave-tooling/skills/skill-creator/scripts/quick_validate.py +0 -65
- package/plugins/specweave-tooling/skills/skill-router/SKILL.md +0 -479
- package/plugins/specweave-ui/.claude-plugin/plugin.json +0 -26
- package/plugins/specweave-ui/.mcp.json +0 -10
- package/plugins/specweave-ui/README.md +0 -492
- package/plugins/specweave-ui/skills/browser-automation/SKILL.md +0 -676
|
@@ -1,514 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,33 +0,0 @@
|
|
|
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/anton-abyzov/specweave"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "specweave-tooling",
|
|
3
|
-
"description": "SpecWeave skill development and orchestration tools. Create new skills with proper structure, test cases, and activation triggers. Includes skill router for intelligent skill activation based on context. Meta-tooling for extending SpecWeave.",
|
|
4
|
-
"version": "0.22.14",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "SpecWeave Team",
|
|
7
|
-
"url": "https://spec-weave.com"
|
|
8
|
-
},
|
|
9
|
-
"homepage": "https://spec-weave.com",
|
|
10
|
-
"repository": "https://github.com/anton-abyzov/specweave",
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"keywords": [
|
|
13
|
-
"tooling",
|
|
14
|
-
"skills",
|
|
15
|
-
"skill-creation",
|
|
16
|
-
"development",
|
|
17
|
-
"specweave"
|
|
18
|
-
]
|
|
19
|
-
}
|