afterburn-cli 1.0.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.
Files changed (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/ai/gemini-client.d.ts +21 -0
  4. package/dist/ai/gemini-client.js +105 -0
  5. package/dist/ai/gemini-client.js.map +1 -0
  6. package/dist/ai/index.d.ts +1 -0
  7. package/dist/ai/index.js +3 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/analysis/diagnosis-schema.d.ts +106 -0
  10. package/dist/analysis/diagnosis-schema.js +54 -0
  11. package/dist/analysis/diagnosis-schema.js.map +1 -0
  12. package/dist/analysis/error-analyzer.d.ts +9 -0
  13. package/dist/analysis/error-analyzer.js +573 -0
  14. package/dist/analysis/error-analyzer.js.map +1 -0
  15. package/dist/analysis/index.d.ts +4 -0
  16. package/dist/analysis/index.js +6 -0
  17. package/dist/analysis/index.js.map +1 -0
  18. package/dist/analysis/source-mapper.d.ts +19 -0
  19. package/dist/analysis/source-mapper.js +329 -0
  20. package/dist/analysis/source-mapper.js.map +1 -0
  21. package/dist/analysis/ui-auditor.d.ts +9 -0
  22. package/dist/analysis/ui-auditor.js +104 -0
  23. package/dist/analysis/ui-auditor.js.map +1 -0
  24. package/dist/artifacts/artifact-storage.d.ts +44 -0
  25. package/dist/artifacts/artifact-storage.js +99 -0
  26. package/dist/artifacts/artifact-storage.js.map +1 -0
  27. package/dist/artifacts/index.d.ts +1 -0
  28. package/dist/artifacts/index.js +3 -0
  29. package/dist/artifacts/index.js.map +1 -0
  30. package/dist/browser/browser-manager.d.ts +45 -0
  31. package/dist/browser/browser-manager.js +88 -0
  32. package/dist/browser/browser-manager.js.map +1 -0
  33. package/dist/browser/challenge-detector.d.ts +10 -0
  34. package/dist/browser/challenge-detector.js +58 -0
  35. package/dist/browser/challenge-detector.js.map +1 -0
  36. package/dist/browser/cookie-dismisser.d.ts +18 -0
  37. package/dist/browser/cookie-dismisser.js +76 -0
  38. package/dist/browser/cookie-dismisser.js.map +1 -0
  39. package/dist/browser/index.d.ts +4 -0
  40. package/dist/browser/index.js +6 -0
  41. package/dist/browser/index.js.map +1 -0
  42. package/dist/browser/stealth-browser.d.ts +13 -0
  43. package/dist/browser/stealth-browser.js +59 -0
  44. package/dist/browser/stealth-browser.js.map +1 -0
  45. package/dist/cli/commander-cli.d.ts +2 -0
  46. package/dist/cli/commander-cli.js +150 -0
  47. package/dist/cli/commander-cli.js.map +1 -0
  48. package/dist/cli/doctor.d.ts +34 -0
  49. package/dist/cli/doctor.js +124 -0
  50. package/dist/cli/doctor.js.map +1 -0
  51. package/dist/cli/first-run.d.ts +6 -0
  52. package/dist/cli/first-run.js +58 -0
  53. package/dist/cli/first-run.js.map +1 -0
  54. package/dist/cli/index.d.ts +3 -0
  55. package/dist/cli/index.js +5 -0
  56. package/dist/cli/index.js.map +1 -0
  57. package/dist/cli/progress.d.ts +11 -0
  58. package/dist/cli/progress.js +30 -0
  59. package/dist/cli/progress.js.map +1 -0
  60. package/dist/core/engine.d.ts +33 -0
  61. package/dist/core/engine.js +269 -0
  62. package/dist/core/engine.js.map +1 -0
  63. package/dist/core/index.d.ts +3 -0
  64. package/dist/core/index.js +4 -0
  65. package/dist/core/index.js.map +1 -0
  66. package/dist/core/validation.d.ts +52 -0
  67. package/dist/core/validation.js +228 -0
  68. package/dist/core/validation.js.map +1 -0
  69. package/dist/discovery/crawler.d.ts +58 -0
  70. package/dist/discovery/crawler.js +240 -0
  71. package/dist/discovery/crawler.js.map +1 -0
  72. package/dist/discovery/discovery-pipeline.d.ts +22 -0
  73. package/dist/discovery/discovery-pipeline.js +256 -0
  74. package/dist/discovery/discovery-pipeline.js.map +1 -0
  75. package/dist/discovery/element-mapper.d.ts +21 -0
  76. package/dist/discovery/element-mapper.js +422 -0
  77. package/dist/discovery/element-mapper.js.map +1 -0
  78. package/dist/discovery/index.d.ts +8 -0
  79. package/dist/discovery/index.js +8 -0
  80. package/dist/discovery/index.js.map +1 -0
  81. package/dist/discovery/link-validator.d.ts +15 -0
  82. package/dist/discovery/link-validator.js +137 -0
  83. package/dist/discovery/link-validator.js.map +1 -0
  84. package/dist/discovery/sitemap-builder.d.ts +19 -0
  85. package/dist/discovery/sitemap-builder.js +166 -0
  86. package/dist/discovery/sitemap-builder.js.map +1 -0
  87. package/dist/discovery/spa-detector.d.ts +12 -0
  88. package/dist/discovery/spa-detector.js +271 -0
  89. package/dist/discovery/spa-detector.js.map +1 -0
  90. package/dist/execution/error-detector.d.ts +10 -0
  91. package/dist/execution/error-detector.js +87 -0
  92. package/dist/execution/error-detector.js.map +1 -0
  93. package/dist/execution/evidence-capture.d.ts +8 -0
  94. package/dist/execution/evidence-capture.js +37 -0
  95. package/dist/execution/evidence-capture.js.map +1 -0
  96. package/dist/execution/index.d.ts +5 -0
  97. package/dist/execution/index.js +7 -0
  98. package/dist/execution/index.js.map +1 -0
  99. package/dist/execution/step-handlers.d.ts +48 -0
  100. package/dist/execution/step-handlers.js +349 -0
  101. package/dist/execution/step-handlers.js.map +1 -0
  102. package/dist/execution/test-data.d.ts +50 -0
  103. package/dist/execution/test-data.js +160 -0
  104. package/dist/execution/test-data.js.map +1 -0
  105. package/dist/execution/workflow-executor.d.ts +56 -0
  106. package/dist/execution/workflow-executor.js +331 -0
  107. package/dist/execution/workflow-executor.js.map +1 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +5 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/mcp/entry.d.ts +2 -0
  112. package/dist/mcp/entry.js +5 -0
  113. package/dist/mcp/entry.js.map +1 -0
  114. package/dist/mcp/index.d.ts +2 -0
  115. package/dist/mcp/index.js +4 -0
  116. package/dist/mcp/index.js.map +1 -0
  117. package/dist/mcp/server.d.ts +3 -0
  118. package/dist/mcp/server.js +19 -0
  119. package/dist/mcp/server.js.map +1 -0
  120. package/dist/mcp/tools.d.ts +2 -0
  121. package/dist/mcp/tools.js +162 -0
  122. package/dist/mcp/tools.js.map +1 -0
  123. package/dist/planning/heuristic-planner.d.ts +7 -0
  124. package/dist/planning/heuristic-planner.js +238 -0
  125. package/dist/planning/heuristic-planner.js.map +1 -0
  126. package/dist/planning/index.d.ts +3 -0
  127. package/dist/planning/index.js +5 -0
  128. package/dist/planning/index.js.map +1 -0
  129. package/dist/planning/plan-schema.d.ts +74 -0
  130. package/dist/planning/plan-schema.js +39 -0
  131. package/dist/planning/plan-schema.js.map +1 -0
  132. package/dist/planning/workflow-planner.d.ts +39 -0
  133. package/dist/planning/workflow-planner.js +211 -0
  134. package/dist/planning/workflow-planner.js.map +1 -0
  135. package/dist/reports/health-scorer.d.ts +14 -0
  136. package/dist/reports/health-scorer.js +88 -0
  137. package/dist/reports/health-scorer.js.map +1 -0
  138. package/dist/reports/html-generator.d.ts +10 -0
  139. package/dist/reports/html-generator.js +155 -0
  140. package/dist/reports/html-generator.js.map +1 -0
  141. package/dist/reports/index.d.ts +4 -0
  142. package/dist/reports/index.js +6 -0
  143. package/dist/reports/index.js.map +1 -0
  144. package/dist/reports/markdown-generator.d.ts +10 -0
  145. package/dist/reports/markdown-generator.js +334 -0
  146. package/dist/reports/markdown-generator.js.map +1 -0
  147. package/dist/reports/priority-ranker.d.ts +22 -0
  148. package/dist/reports/priority-ranker.js +608 -0
  149. package/dist/reports/priority-ranker.js.map +1 -0
  150. package/dist/screenshots/dual-format.d.ts +14 -0
  151. package/dist/screenshots/dual-format.js +59 -0
  152. package/dist/screenshots/dual-format.js.map +1 -0
  153. package/dist/screenshots/index.d.ts +2 -0
  154. package/dist/screenshots/index.js +4 -0
  155. package/dist/screenshots/index.js.map +1 -0
  156. package/dist/screenshots/screenshot-manager.d.ts +33 -0
  157. package/dist/screenshots/screenshot-manager.js +86 -0
  158. package/dist/screenshots/screenshot-manager.js.map +1 -0
  159. package/dist/testing/accessibility-auditor.d.ts +23 -0
  160. package/dist/testing/accessibility-auditor.js +44 -0
  161. package/dist/testing/accessibility-auditor.js.map +1 -0
  162. package/dist/testing/index.d.ts +4 -0
  163. package/dist/testing/index.js +5 -0
  164. package/dist/testing/index.js.map +1 -0
  165. package/dist/testing/meta-auditor.d.ts +16 -0
  166. package/dist/testing/meta-auditor.js +268 -0
  167. package/dist/testing/meta-auditor.js.map +1 -0
  168. package/dist/testing/performance-monitor.d.ts +15 -0
  169. package/dist/testing/performance-monitor.js +64 -0
  170. package/dist/testing/performance-monitor.js.map +1 -0
  171. package/dist/types/artifacts.d.ts +58 -0
  172. package/dist/types/artifacts.js +3 -0
  173. package/dist/types/artifacts.js.map +1 -0
  174. package/dist/types/discovery.d.ts +124 -0
  175. package/dist/types/discovery.js +3 -0
  176. package/dist/types/discovery.js.map +1 -0
  177. package/dist/types/execution.d.ts +154 -0
  178. package/dist/types/execution.js +3 -0
  179. package/dist/types/execution.js.map +1 -0
  180. package/dist/types/index.d.ts +2 -0
  181. package/dist/types/index.js +4 -0
  182. package/dist/types/index.js.map +1 -0
  183. package/dist/utils/sanitizer.d.ts +25 -0
  184. package/dist/utils/sanitizer.js +98 -0
  185. package/dist/utils/sanitizer.js.map +1 -0
  186. package/package.json +86 -0
  187. package/templates/report.hbs +202 -0
  188. package/templates/styles/report.css +607 -0
@@ -0,0 +1,14 @@
1
+ import type { Page } from 'playwright-core';
2
+ import type { ScreenshotRef } from '../types/artifacts.js';
3
+ /**
4
+ * Converts a PNG buffer to WebP format with optimal compression settings
5
+ */
6
+ export declare function convertToWebP(pngBuffer: Buffer): Promise<Buffer>;
7
+ /**
8
+ * Captures a full-page screenshot in both PNG and WebP formats with content-hash naming
9
+ * @param page - Playwright page instance
10
+ * @param name - Human-readable screenshot name
11
+ * @param outputDir - Directory to save screenshots
12
+ * @returns ScreenshotRef with paths and metadata
13
+ */
14
+ export declare function captureDualFormat(page: Page, name: string, outputDir: string, existingPngBuffer?: Buffer, existingHash?: string): Promise<ScreenshotRef>;
@@ -0,0 +1,59 @@
1
+ // Dual-format screenshot capture: PNG for LLM analysis, WebP for display
2
+ import sharp from 'sharp';
3
+ import { createHash } from 'node:crypto';
4
+ import { join } from 'node:path';
5
+ import fs from 'fs-extra';
6
+ /**
7
+ * Converts a PNG buffer to WebP format with optimal compression settings
8
+ */
9
+ export async function convertToWebP(pngBuffer) {
10
+ return sharp(pngBuffer)
11
+ .webp({ quality: 80, effort: 4, smartSubsample: true })
12
+ .toBuffer();
13
+ }
14
+ /**
15
+ * Captures a full-page screenshot in both PNG and WebP formats with content-hash naming
16
+ * @param page - Playwright page instance
17
+ * @param name - Human-readable screenshot name
18
+ * @param outputDir - Directory to save screenshots
19
+ * @returns ScreenshotRef with paths and metadata
20
+ */
21
+ export async function captureDualFormat(page, name, outputDir, existingPngBuffer, existingHash) {
22
+ // Ensure output directory exists
23
+ fs.ensureDirSync(outputDir);
24
+ // Reuse provided PNG buffer or capture a new one
25
+ const pngBuffer = existingPngBuffer || await page.screenshot({ type: 'png', fullPage: true });
26
+ // Reuse provided hash or compute from buffer
27
+ const hash = existingHash || createHash('sha256').update(pngBuffer).digest('hex').substring(0, 12);
28
+ // Convert to WebP
29
+ const webpBuffer = await convertToWebP(pngBuffer);
30
+ // Build cross-platform file paths
31
+ const pngPath = join(outputDir, `${name}-${hash}.png`);
32
+ const webpPath = join(outputDir, `${name}-${hash}.webp`);
33
+ // Write both formats to disk
34
+ await fs.outputFile(pngPath, pngBuffer);
35
+ await fs.outputFile(webpPath, webpBuffer);
36
+ // Get image dimensions
37
+ const metadata = await sharp(pngBuffer).metadata();
38
+ const width = metadata.width ?? 0;
39
+ const height = metadata.height ?? 0;
40
+ // Calculate size reduction
41
+ const pngSize = pngBuffer.length;
42
+ const webpSize = webpBuffer.length;
43
+ const reduction = Math.round(((pngSize - webpSize) / pngSize) * 100);
44
+ return {
45
+ id: hash,
46
+ name,
47
+ pngPath,
48
+ webpPath,
49
+ width,
50
+ height,
51
+ sizes: {
52
+ png: pngSize,
53
+ webp: webpSize,
54
+ reduction: `${reduction}%`,
55
+ },
56
+ capturedAt: new Date().toISOString(),
57
+ };
58
+ }
59
+ //# sourceMappingURL=dual-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dual-format.js","sourceRoot":"","sources":["../../src/screenshots/dual-format.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,UAAU,CAAC;AAI1B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,SAAiB;IACnD,OAAO,KAAK,CAAC,SAAS,CAAC;SACpB,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;SACtD,QAAQ,EAAE,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAU,EACV,IAAY,EACZ,SAAiB,EACjB,iBAA0B,EAC1B,YAAqB;IAErB,iCAAiC;IACjC,EAAE,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAE5B,iDAAiD;IACjD,MAAM,SAAS,GAAG,iBAAiB,IAAI,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9F,6CAA6C;IAC7C,MAAM,IAAI,GAAG,YAAY,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEnG,kBAAkB;IAClB,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,SAAS,CAAC,CAAC;IAElD,kCAAkC;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC;IAEzD,6BAA6B;IAC7B,MAAM,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACxC,MAAM,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAE1C,uBAAuB;IACvB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;IAEpC,2BAA2B;IAC3B,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC;IACjC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;IAErE,OAAO;QACL,EAAE,EAAE,IAAI;QACR,IAAI;QACJ,OAAO;QACP,QAAQ;QACR,KAAK;QACL,MAAM;QACN,KAAK,EAAE;YACL,GAAG,EAAE,OAAO;YACZ,IAAI,EAAE,QAAQ;YACd,SAAS,EAAE,GAAG,SAAS,GAAG;SAC3B;QACD,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { convertToWebP, captureDualFormat } from './dual-format.js';
2
+ export { ScreenshotManager } from './screenshot-manager.js';
@@ -0,0 +1,4 @@
1
+ // Barrel export for screenshots module
2
+ export { convertToWebP, captureDualFormat } from './dual-format.js';
3
+ export { ScreenshotManager } from './screenshot-manager.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/screenshots/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Page } from 'playwright-core';
2
+ import type { ScreenshotRef } from '../types/artifacts.js';
3
+ /**
4
+ * Manages screenshot capture with automatic deduplication by content hash
5
+ */
6
+ export declare class ScreenshotManager {
7
+ private readonly outputDir;
8
+ private readonly dedupMap;
9
+ private readonly currentSessionFiles;
10
+ constructor(outputDir?: string);
11
+ /**
12
+ * Captures a screenshot in dual format (PNG + WebP) with deduplication
13
+ * @param page - Playwright page instance
14
+ * @param name - Human-readable screenshot name
15
+ * @returns ScreenshotRef (existing ref if duplicate detected)
16
+ */
17
+ capture(page: Page, name: string): Promise<ScreenshotRef>;
18
+ /**
19
+ * Returns all captured screenshots
20
+ */
21
+ getAll(): ScreenshotRef[];
22
+ /**
23
+ * Finds a screenshot by name
24
+ */
25
+ getByName(name: string): ScreenshotRef | undefined;
26
+ /**
27
+ * Removes screenshots older than specified days based on file modification time.
28
+ * Excludes screenshots from the current session.
29
+ * @param olderThanDays - Age threshold in days (default: 7). Set to 0 to clean all old screenshots.
30
+ * @returns Count of deleted files
31
+ */
32
+ cleanup(olderThanDays?: number): Promise<number>;
33
+ }
@@ -0,0 +1,86 @@
1
+ // Screenshot capture coordination with content-hash deduplication
2
+ import { createHash } from 'node:crypto';
3
+ import { join } from 'node:path';
4
+ import fs from 'fs-extra';
5
+ import { captureDualFormat } from './dual-format.js';
6
+ /**
7
+ * Manages screenshot capture with automatic deduplication by content hash
8
+ */
9
+ export class ScreenshotManager {
10
+ outputDir;
11
+ dedupMap;
12
+ currentSessionFiles;
13
+ constructor(outputDir = '.afterburn/screenshots') {
14
+ this.outputDir = outputDir;
15
+ this.dedupMap = new Map();
16
+ this.currentSessionFiles = new Set();
17
+ fs.ensureDirSync(this.outputDir);
18
+ }
19
+ /**
20
+ * Captures a screenshot in dual format (PNG + WebP) with deduplication
21
+ * @param page - Playwright page instance
22
+ * @param name - Human-readable screenshot name
23
+ * @returns ScreenshotRef (existing ref if duplicate detected)
24
+ */
25
+ async capture(page, name) {
26
+ // Capture PNG buffer to compute hash
27
+ const pngBuffer = await page.screenshot({ type: 'png', fullPage: true });
28
+ const hash = createHash('sha256').update(pngBuffer).digest('hex').substring(0, 12);
29
+ // Check if this content hash already exists (deduplication)
30
+ const existing = this.dedupMap.get(hash);
31
+ if (existing) {
32
+ return existing;
33
+ }
34
+ // New screenshot - capture dual format, reuse buffer and hash from above
35
+ const ref = await captureDualFormat(page, name, this.outputDir, pngBuffer, hash);
36
+ this.dedupMap.set(hash, ref);
37
+ // Track current session files
38
+ this.currentSessionFiles.add(ref.pngPath);
39
+ this.currentSessionFiles.add(ref.webpPath);
40
+ return ref;
41
+ }
42
+ /**
43
+ * Returns all captured screenshots
44
+ */
45
+ getAll() {
46
+ return Array.from(this.dedupMap.values());
47
+ }
48
+ /**
49
+ * Finds a screenshot by name
50
+ */
51
+ getByName(name) {
52
+ return this.getAll().find((ref) => ref.name === name);
53
+ }
54
+ /**
55
+ * Removes screenshots older than specified days based on file modification time.
56
+ * Excludes screenshots from the current session.
57
+ * @param olderThanDays - Age threshold in days (default: 7). Set to 0 to clean all old screenshots.
58
+ * @returns Count of deleted files
59
+ */
60
+ async cleanup(olderThanDays = 7) {
61
+ const files = await fs.readdir(this.outputDir);
62
+ const now = Date.now();
63
+ const threshold = olderThanDays * 24 * 60 * 60 * 1000; // Convert days to milliseconds
64
+ let deletedCount = 0;
65
+ for (const file of files) {
66
+ const filepath = join(this.outputDir, file);
67
+ // Skip files from current session
68
+ if (this.currentSessionFiles.has(filepath)) {
69
+ continue;
70
+ }
71
+ try {
72
+ const stats = await fs.stat(filepath);
73
+ const age = now - stats.mtimeMs;
74
+ if (age > threshold) {
75
+ await fs.unlink(filepath);
76
+ deletedCount++;
77
+ }
78
+ }
79
+ catch {
80
+ // File might have been deleted already, continue
81
+ }
82
+ }
83
+ return deletedCount;
84
+ }
85
+ }
86
+ //# sourceMappingURL=screenshot-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot-manager.js","sourceRoot":"","sources":["../../src/screenshots/screenshot-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Page } from 'playwright';
2
+ interface AccessibilityViolation {
3
+ id: string;
4
+ impact: 'critical' | 'serious' | 'moderate' | 'minor';
5
+ description: string;
6
+ nodes: number;
7
+ helpUrl: string;
8
+ }
9
+ interface AccessibilityReport {
10
+ url: string;
11
+ violationCount: number;
12
+ violations: AccessibilityViolation[];
13
+ passes: number;
14
+ incomplete: number;
15
+ }
16
+ /**
17
+ * Run WCAG 2.0/2.1 A/AA accessibility audit on a Playwright page
18
+ *
19
+ * @param page - Playwright page to audit
20
+ * @returns Accessibility report with violations, passes, and incomplete checks
21
+ */
22
+ export declare function auditAccessibility(page: Page): Promise<AccessibilityReport>;
23
+ export {};
@@ -0,0 +1,44 @@
1
+ // WCAG accessibility audit via axe-core for Playwright pages
2
+ import { AxeBuilder } from '@axe-core/playwright';
3
+ /**
4
+ * Run WCAG 2.0/2.1 A/AA accessibility audit on a Playwright page
5
+ *
6
+ * @param page - Playwright page to audit
7
+ * @returns Accessibility report with violations, passes, and incomplete checks
8
+ */
9
+ export async function auditAccessibility(page) {
10
+ try {
11
+ const url = page.url();
12
+ // Run axe-core analysis with WCAG 2.0/2.1 A/AA tags
13
+ const results = await new AxeBuilder({ page })
14
+ .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
15
+ .analyze();
16
+ // Map violations to AccessibilityViolation type
17
+ const violations = results.violations.map(violation => ({
18
+ id: violation.id,
19
+ impact: violation.impact,
20
+ description: violation.description,
21
+ nodes: violation.nodes.length,
22
+ helpUrl: violation.helpUrl,
23
+ }));
24
+ return {
25
+ url,
26
+ violationCount: violations.length,
27
+ violations,
28
+ passes: results.passes.length,
29
+ incomplete: results.incomplete.length,
30
+ };
31
+ }
32
+ catch (error) {
33
+ console.error('Accessibility audit failed:', error);
34
+ // Return safe defaults on error
35
+ return {
36
+ url: page.url(),
37
+ violationCount: 0,
38
+ violations: [],
39
+ passes: 0,
40
+ incomplete: 0,
41
+ };
42
+ }
43
+ }
44
+ //# sourceMappingURL=accessibility-auditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility-auditor.js","sourceRoot":"","sources":["../../src/testing/accessibility-auditor.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAoBlD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAU;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,oDAAoD;QACpD,MAAM,OAAO,GAAG,MAAM,IAAI,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC;aAC3C,QAAQ,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;aACtD,OAAO,EAAE,CAAC;QAEb,gDAAgD;QAChD,MAAM,UAAU,GAA6B,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAChF,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,MAAM,EAAE,SAAS,CAAC,MAAuD;YACzE,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM;YAC7B,OAAO,EAAE,SAAS,CAAC,OAAO;SAC3B,CAAC,CAAC,CAAC;QAEJ,OAAO;YACL,GAAG;YACH,cAAc,EAAE,UAAU,CAAC,MAAM;YACjC,UAAU;YACV,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;YAC7B,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,MAAM;SACtC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC;QAEpD,gCAAgC;QAChC,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,cAAc,EAAE,CAAC;YACjB,UAAU,EAAE,EAAE;YACd,MAAM,EAAE,CAAC;YACT,UAAU,EAAE,CAAC;SACd,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { auditAccessibility } from './accessibility-auditor.js';
2
+ export { capturePerformanceMetrics } from './performance-monitor.js';
3
+ export { auditMeta } from './meta-auditor.js';
4
+ export type { MetaAuditResult, MetaIssue } from './meta-auditor.js';
@@ -0,0 +1,5 @@
1
+ // Testing module barrel exports
2
+ export { auditAccessibility } from './accessibility-auditor.js';
3
+ export { capturePerformanceMetrics } from './performance-monitor.js';
4
+ export { auditMeta } from './meta-auditor.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Page } from 'playwright';
2
+ export interface MetaIssue {
3
+ id: string;
4
+ severity: 'high' | 'medium' | 'low';
5
+ description: string;
6
+ suggestion: string;
7
+ }
8
+ export interface MetaAuditResult {
9
+ url: string;
10
+ issues: MetaIssue[];
11
+ }
12
+ /**
13
+ * Audit a page for common meta tag and structural SEO issues.
14
+ * These are heuristic checks that don't require AI — just DOM inspection.
15
+ */
16
+ export declare function auditMeta(page: Page): Promise<MetaAuditResult>;
@@ -0,0 +1,268 @@
1
+ // Lightweight heuristic checks for common meta tag and SEO issues (no AI needed)
2
+ /**
3
+ * Audit a page for common meta tag and structural SEO issues.
4
+ * These are heuristic checks that don't require AI — just DOM inspection.
5
+ */
6
+ export async function auditMeta(page) {
7
+ const url = page.url();
8
+ const issues = [];
9
+ try {
10
+ const checks = await page.evaluate(() => {
11
+ const html = document.documentElement;
12
+ const head = document.head;
13
+ // Check lang attribute
14
+ const hasLang = !!html.getAttribute('lang');
15
+ // Check viewport meta tag
16
+ const hasViewport = !!head.querySelector('meta[name="viewport"]');
17
+ // Check meta description
18
+ const hasDescription = !!head.querySelector('meta[name="description"]');
19
+ // Check title
20
+ const title = document.title?.trim();
21
+ const hasTitle = !!title && title.length > 0;
22
+ const titleLength = title?.length || 0;
23
+ // Check H1 count (multiple H1s is a common SEO mistake)
24
+ const h1Count = document.querySelectorAll('h1').length;
25
+ // Check for heading hierarchy gaps (e.g., h1 → h3, skipping h2)
26
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
27
+ let hasSkippedHeading = false;
28
+ for (let i = 1; i < headings.length; i++) {
29
+ const prevLevel = parseInt(headings[i - 1].tagName[1]);
30
+ const currLevel = parseInt(headings[i].tagName[1]);
31
+ if (currLevel > prevLevel + 1) {
32
+ hasSkippedHeading = true;
33
+ break;
34
+ }
35
+ }
36
+ // Check for images without alt text (supplements axe-core)
37
+ const imagesWithoutAlt = document.querySelectorAll('img:not([alt])').length;
38
+ // Check for links with empty href
39
+ const emptyHrefLinks = document.querySelectorAll('a[href=""], a[href="#"]').length;
40
+ // Check for external links without rel="noopener"
41
+ const unsafeExternalLinks = Array.from(document.querySelectorAll('a[target="_blank"]'))
42
+ .filter(a => {
43
+ const rel = a.getAttribute('rel') || '';
44
+ return !rel.includes('noopener') && !rel.includes('noreferrer');
45
+ }).length;
46
+ // Check for render-blocking scripts in <head> (no defer or async)
47
+ const renderBlockingScripts = Array.from(head.querySelectorAll('script[src]'))
48
+ .filter(s => !s.hasAttribute('defer') && !s.hasAttribute('async') && !s.getAttribute('type')?.includes('module'))
49
+ .length;
50
+ // Check for potential exposed secrets in inline scripts
51
+ const inlineScripts = Array.from(document.querySelectorAll('script:not([src])'))
52
+ .map(s => s.textContent || '');
53
+ const secretPatterns = [
54
+ /['"]sk-[a-zA-Z0-9]{10,}['"]/, // OpenAI-style keys
55
+ /['"](api[_-]?key|apiKey)['"]?\s*[:=]\s*['"][^'"]{8,}['"]/i, // api_key, api-key, apiKey = "..."
56
+ /['"](secret|secretKey|secret_key)['"]?\s*[:=]\s*['"][^'"]{8,}['"]/i, // secret = "..."
57
+ /['"](password|passwd)['"]?\s*[:=]\s*['"][^'"]{4,}['"]/i, // password = "..."
58
+ /['"](token|accessToken|access_token|auth_token)['"]?\s*[:=]\s*['"][^'"]{10,}['"]/i, // token = "..."
59
+ /['"]AKIA[A-Z0-9]{12,}['"]/, // AWS access keys
60
+ /['"]ghp_[a-zA-Z0-9]{30,}['"]/, // GitHub PATs
61
+ ];
62
+ let exposedSecretsCount = 0;
63
+ for (const script of inlineScripts) {
64
+ for (const pattern of secretPatterns) {
65
+ if (pattern.test(script)) {
66
+ exposedSecretsCount++;
67
+ break; // one secret per script is enough
68
+ }
69
+ }
70
+ }
71
+ // Check for javascript:void(0) links (dead links)
72
+ const jsVoidLinks = document.querySelectorAll('a[href^="javascript:"]').length;
73
+ // Check for favicon
74
+ const hasFavicon = !!head.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
75
+ // Check for canonical URL
76
+ const hasCanonical = !!head.querySelector('link[rel="canonical"]');
77
+ // Check for Open Graph tags (social sharing previews)
78
+ const hasOgTitle = !!head.querySelector('meta[property="og:title"]');
79
+ const hasOgDescription = !!head.querySelector('meta[property="og:description"]');
80
+ const hasOgImage = !!head.querySelector('meta[property="og:image"]');
81
+ // Check for mixed content (HTTP resources on HTTPS pages)
82
+ let mixedContentCount = 0;
83
+ if (window.location.protocol === 'https:') {
84
+ const httpResources = document.querySelectorAll('img[src^="http:"], script[src^="http:"], link[href^="http:"], iframe[src^="http:"], video[src^="http:"], audio[src^="http:"]');
85
+ mixedContentCount = httpResources.length;
86
+ }
87
+ return {
88
+ hasLang,
89
+ hasViewport,
90
+ hasDescription,
91
+ hasTitle,
92
+ titleLength,
93
+ h1Count,
94
+ hasSkippedHeading,
95
+ imagesWithoutAlt,
96
+ emptyHrefLinks,
97
+ unsafeExternalLinks,
98
+ renderBlockingScripts,
99
+ exposedSecretsCount,
100
+ jsVoidLinks,
101
+ hasFavicon,
102
+ hasCanonical,
103
+ hasOgTitle,
104
+ hasOgDescription,
105
+ hasOgImage,
106
+ mixedContentCount,
107
+ };
108
+ });
109
+ // Evaluate results
110
+ if (!checks.hasViewport) {
111
+ issues.push({
112
+ id: 'missing-viewport',
113
+ severity: 'high',
114
+ description: 'Missing viewport meta tag — page won\'t display correctly on mobile devices',
115
+ suggestion: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> to the <head>',
116
+ });
117
+ }
118
+ if (!checks.hasDescription) {
119
+ issues.push({
120
+ id: 'missing-meta-description',
121
+ severity: 'medium',
122
+ description: 'Missing meta description — search engines won\'t have a summary to show',
123
+ suggestion: 'Add <meta name="description" content="Your page description here"> to the <head>',
124
+ });
125
+ }
126
+ if (!checks.hasTitle) {
127
+ issues.push({
128
+ id: 'missing-title',
129
+ severity: 'high',
130
+ description: 'Missing or empty page title',
131
+ suggestion: 'Add a descriptive <title> tag in the <head>',
132
+ });
133
+ }
134
+ else if (checks.titleLength > 60) {
135
+ issues.push({
136
+ id: 'title-too-long',
137
+ severity: 'low',
138
+ description: 'Page title is longer than 60 characters — it may get truncated in search results',
139
+ suggestion: 'Shorten the title to 60 characters or fewer',
140
+ });
141
+ }
142
+ if (checks.h1Count === 0) {
143
+ issues.push({
144
+ id: 'missing-h1',
145
+ severity: 'medium',
146
+ description: 'No H1 heading found — every page should have exactly one H1',
147
+ suggestion: 'Add a single H1 heading that describes the page content',
148
+ });
149
+ }
150
+ else if (checks.h1Count > 1) {
151
+ issues.push({
152
+ id: 'multiple-h1',
153
+ severity: 'medium',
154
+ description: `Found ${checks.h1Count} H1 headings — there should be exactly one per page`,
155
+ suggestion: 'Keep one H1 and change the others to H2 or lower',
156
+ });
157
+ }
158
+ if (checks.hasSkippedHeading) {
159
+ issues.push({
160
+ id: 'skipped-heading-level',
161
+ severity: 'low',
162
+ description: 'Heading levels are skipped (e.g., H1 followed by H3, missing H2)',
163
+ suggestion: 'Use headings in order: H1, then H2, then H3, etc.',
164
+ });
165
+ }
166
+ if (checks.emptyHrefLinks > 0) {
167
+ issues.push({
168
+ id: 'empty-href',
169
+ severity: 'medium',
170
+ description: `Found ${checks.emptyHrefLinks} link(s) with empty or "#" href — they go nowhere`,
171
+ suggestion: 'Add a real URL to each link\'s href attribute, or remove the link',
172
+ });
173
+ }
174
+ if (checks.unsafeExternalLinks > 0) {
175
+ issues.push({
176
+ id: 'unsafe-external-link',
177
+ severity: 'low',
178
+ description: `Found ${checks.unsafeExternalLinks} external link(s) opening in new tab without rel="noopener"`,
179
+ suggestion: 'Add rel="noopener noreferrer" to all links with target="_blank"',
180
+ });
181
+ }
182
+ if (checks.renderBlockingScripts > 0) {
183
+ issues.push({
184
+ id: 'render-blocking-script',
185
+ severity: 'medium',
186
+ description: `Found ${checks.renderBlockingScripts} render-blocking script(s) in <head> — they slow down page load`,
187
+ suggestion: 'Add "defer" or "async" to <script> tags, or move them to the end of <body>',
188
+ });
189
+ }
190
+ if (checks.exposedSecretsCount > 0) {
191
+ issues.push({
192
+ id: 'exposed-secret',
193
+ severity: 'high',
194
+ description: `Found ${checks.exposedSecretsCount} potential API key(s) or secret(s) exposed in inline scripts`,
195
+ suggestion: 'Never put API keys in client-side code — move them to environment variables on the server',
196
+ });
197
+ }
198
+ if (checks.jsVoidLinks > 0) {
199
+ issues.push({
200
+ id: 'javascript-void-link',
201
+ severity: 'medium',
202
+ description: `Found ${checks.jsVoidLinks} link(s) using javascript:void(0) — they don't navigate anywhere`,
203
+ suggestion: 'Replace javascript: links with real URLs or use <button> elements for actions',
204
+ });
205
+ }
206
+ if (!checks.hasLang) {
207
+ issues.push({
208
+ id: 'missing-lang',
209
+ severity: 'medium',
210
+ description: 'Missing lang attribute on <html> — screen readers won\'t know what language to use',
211
+ suggestion: 'Add lang="en" (or your language) to the <html> tag',
212
+ });
213
+ }
214
+ if (checks.imagesWithoutAlt > 0) {
215
+ issues.push({
216
+ id: 'images-missing-alt',
217
+ severity: 'medium',
218
+ description: `Found ${checks.imagesWithoutAlt} image(s) without alt text — screen readers can't describe them`,
219
+ suggestion: 'Add descriptive alt="" attributes to all <img> tags (use alt="" for decorative images)',
220
+ });
221
+ }
222
+ if (!checks.hasFavicon) {
223
+ issues.push({
224
+ id: 'missing-favicon',
225
+ severity: 'low',
226
+ description: 'No favicon found — browsers show a generic icon in the tab',
227
+ suggestion: 'Add <link rel="icon" href="/favicon.ico"> to the <head>',
228
+ });
229
+ }
230
+ if (!checks.hasCanonical) {
231
+ issues.push({
232
+ id: 'missing-canonical',
233
+ severity: 'low',
234
+ description: 'No canonical URL set — search engines may index duplicate versions of this page',
235
+ suggestion: 'Add <link rel="canonical" href="https://yoursite.com/page"> to the <head>',
236
+ });
237
+ }
238
+ if (!checks.hasOgTitle || !checks.hasOgDescription || !checks.hasOgImage) {
239
+ const missing = [];
240
+ if (!checks.hasOgTitle)
241
+ missing.push('og:title');
242
+ if (!checks.hasOgDescription)
243
+ missing.push('og:description');
244
+ if (!checks.hasOgImage)
245
+ missing.push('og:image');
246
+ issues.push({
247
+ id: 'missing-open-graph',
248
+ severity: 'low',
249
+ description: `Missing Open Graph tag(s): ${missing.join(', ')} — social media shares won't have a rich preview`,
250
+ suggestion: 'Add Open Graph meta tags for better social sharing previews',
251
+ });
252
+ }
253
+ if (checks.mixedContentCount > 0) {
254
+ issues.push({
255
+ id: 'mixed-content',
256
+ severity: 'high',
257
+ description: `Found ${checks.mixedContentCount} HTTP resource(s) loaded on an HTTPS page — browsers may block them`,
258
+ suggestion: 'Change all resource URLs from http:// to https:// to avoid mixed content warnings',
259
+ });
260
+ }
261
+ }
262
+ catch (error) {
263
+ // Graceful degradation — meta audit failure doesn't break the pipeline
264
+ console.warn('Meta audit failed:', error);
265
+ }
266
+ return { url, issues };
267
+ }
268
+ //# sourceMappingURL=meta-auditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta-auditor.js","sourceRoot":"","sources":["../../src/testing/meta-auditor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Page } from 'playwright';
2
+ interface PerformanceMetrics {
3
+ lcp: number;
4
+ domContentLoaded: number;
5
+ totalLoadTime: number;
6
+ url: string;
7
+ }
8
+ /**
9
+ * Capture browser performance metrics from a Playwright page
10
+ *
11
+ * @param page - Playwright page to measure
12
+ * @returns Performance metrics including LCP, DOM load time, and total load time
13
+ */
14
+ export declare function capturePerformanceMetrics(page: Page): Promise<PerformanceMetrics>;
15
+ export {};