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.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/ai/gemini-client.d.ts +21 -0
- package/dist/ai/gemini-client.js +105 -0
- package/dist/ai/gemini-client.js.map +1 -0
- package/dist/ai/index.d.ts +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/analysis/diagnosis-schema.d.ts +106 -0
- package/dist/analysis/diagnosis-schema.js +54 -0
- package/dist/analysis/diagnosis-schema.js.map +1 -0
- package/dist/analysis/error-analyzer.d.ts +9 -0
- package/dist/analysis/error-analyzer.js +573 -0
- package/dist/analysis/error-analyzer.js.map +1 -0
- package/dist/analysis/index.d.ts +4 -0
- package/dist/analysis/index.js +6 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/source-mapper.d.ts +19 -0
- package/dist/analysis/source-mapper.js +329 -0
- package/dist/analysis/source-mapper.js.map +1 -0
- package/dist/analysis/ui-auditor.d.ts +9 -0
- package/dist/analysis/ui-auditor.js +104 -0
- package/dist/analysis/ui-auditor.js.map +1 -0
- package/dist/artifacts/artifact-storage.d.ts +44 -0
- package/dist/artifacts/artifact-storage.js +99 -0
- package/dist/artifacts/artifact-storage.js.map +1 -0
- package/dist/artifacts/index.d.ts +1 -0
- package/dist/artifacts/index.js +3 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/browser/browser-manager.d.ts +45 -0
- package/dist/browser/browser-manager.js +88 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/challenge-detector.d.ts +10 -0
- package/dist/browser/challenge-detector.js +58 -0
- package/dist/browser/challenge-detector.js.map +1 -0
- package/dist/browser/cookie-dismisser.d.ts +18 -0
- package/dist/browser/cookie-dismisser.js +76 -0
- package/dist/browser/cookie-dismisser.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +6 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/stealth-browser.d.ts +13 -0
- package/dist/browser/stealth-browser.js +59 -0
- package/dist/browser/stealth-browser.js.map +1 -0
- package/dist/cli/commander-cli.d.ts +2 -0
- package/dist/cli/commander-cli.js +150 -0
- package/dist/cli/commander-cli.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -0
- package/dist/cli/doctor.js +124 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/first-run.d.ts +6 -0
- package/dist/cli/first-run.js +58 -0
- package/dist/cli/first-run.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/progress.d.ts +11 -0
- package/dist/cli/progress.js +30 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/core/engine.d.ts +33 -0
- package/dist/core/engine.js +269 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/validation.d.ts +52 -0
- package/dist/core/validation.js +228 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/discovery/crawler.d.ts +58 -0
- package/dist/discovery/crawler.js +240 -0
- package/dist/discovery/crawler.js.map +1 -0
- package/dist/discovery/discovery-pipeline.d.ts +22 -0
- package/dist/discovery/discovery-pipeline.js +256 -0
- package/dist/discovery/discovery-pipeline.js.map +1 -0
- package/dist/discovery/element-mapper.d.ts +21 -0
- package/dist/discovery/element-mapper.js +422 -0
- package/dist/discovery/element-mapper.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.js +8 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/link-validator.d.ts +15 -0
- package/dist/discovery/link-validator.js +137 -0
- package/dist/discovery/link-validator.js.map +1 -0
- package/dist/discovery/sitemap-builder.d.ts +19 -0
- package/dist/discovery/sitemap-builder.js +166 -0
- package/dist/discovery/sitemap-builder.js.map +1 -0
- package/dist/discovery/spa-detector.d.ts +12 -0
- package/dist/discovery/spa-detector.js +271 -0
- package/dist/discovery/spa-detector.js.map +1 -0
- package/dist/execution/error-detector.d.ts +10 -0
- package/dist/execution/error-detector.js +87 -0
- package/dist/execution/error-detector.js.map +1 -0
- package/dist/execution/evidence-capture.d.ts +8 -0
- package/dist/execution/evidence-capture.js +37 -0
- package/dist/execution/evidence-capture.js.map +1 -0
- package/dist/execution/index.d.ts +5 -0
- package/dist/execution/index.js +7 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/step-handlers.d.ts +48 -0
- package/dist/execution/step-handlers.js +349 -0
- package/dist/execution/step-handlers.js.map +1 -0
- package/dist/execution/test-data.d.ts +50 -0
- package/dist/execution/test-data.js +160 -0
- package/dist/execution/test-data.js.map +1 -0
- package/dist/execution/workflow-executor.d.ts +56 -0
- package/dist/execution/workflow-executor.js +331 -0
- package/dist/execution/workflow-executor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +5 -0
- package/dist/mcp/entry.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +19 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +162 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/planning/heuristic-planner.d.ts +7 -0
- package/dist/planning/heuristic-planner.js +238 -0
- package/dist/planning/heuristic-planner.js.map +1 -0
- package/dist/planning/index.d.ts +3 -0
- package/dist/planning/index.js +5 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-schema.d.ts +74 -0
- package/dist/planning/plan-schema.js +39 -0
- package/dist/planning/plan-schema.js.map +1 -0
- package/dist/planning/workflow-planner.d.ts +39 -0
- package/dist/planning/workflow-planner.js +211 -0
- package/dist/planning/workflow-planner.js.map +1 -0
- package/dist/reports/health-scorer.d.ts +14 -0
- package/dist/reports/health-scorer.js +88 -0
- package/dist/reports/health-scorer.js.map +1 -0
- package/dist/reports/html-generator.d.ts +10 -0
- package/dist/reports/html-generator.js +155 -0
- package/dist/reports/html-generator.js.map +1 -0
- package/dist/reports/index.d.ts +4 -0
- package/dist/reports/index.js +6 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/markdown-generator.d.ts +10 -0
- package/dist/reports/markdown-generator.js +334 -0
- package/dist/reports/markdown-generator.js.map +1 -0
- package/dist/reports/priority-ranker.d.ts +22 -0
- package/dist/reports/priority-ranker.js +608 -0
- package/dist/reports/priority-ranker.js.map +1 -0
- package/dist/screenshots/dual-format.d.ts +14 -0
- package/dist/screenshots/dual-format.js +59 -0
- package/dist/screenshots/dual-format.js.map +1 -0
- package/dist/screenshots/index.d.ts +2 -0
- package/dist/screenshots/index.js +4 -0
- package/dist/screenshots/index.js.map +1 -0
- package/dist/screenshots/screenshot-manager.d.ts +33 -0
- package/dist/screenshots/screenshot-manager.js +86 -0
- package/dist/screenshots/screenshot-manager.js.map +1 -0
- package/dist/testing/accessibility-auditor.d.ts +23 -0
- package/dist/testing/accessibility-auditor.js +44 -0
- package/dist/testing/accessibility-auditor.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/meta-auditor.d.ts +16 -0
- package/dist/testing/meta-auditor.js +268 -0
- package/dist/testing/meta-auditor.js.map +1 -0
- package/dist/testing/performance-monitor.d.ts +15 -0
- package/dist/testing/performance-monitor.js +64 -0
- package/dist/testing/performance-monitor.js.map +1 -0
- package/dist/types/artifacts.d.ts +58 -0
- package/dist/types/artifacts.js +3 -0
- package/dist/types/artifacts.js.map +1 -0
- package/dist/types/discovery.d.ts +124 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/execution.d.ts +154 -0
- package/dist/types/execution.js +3 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/sanitizer.d.ts +25 -0
- package/dist/utils/sanitizer.js +98 -0
- package/dist/utils/sanitizer.js.map +1 -0
- package/package.json +86 -0
- package/templates/report.hbs +202 -0
- 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 @@
|
|
|
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 @@
|
|
|
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":"AAAA,iFAAiF;AAgBjF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAU;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,MAAM,GAAgB,EAAE,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;YAE3B,uBAAuB;YACvB,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAE5C,0BAA0B;YAC1B,MAAM,WAAW,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,uBAAuB,CAAC,CAAC;YAElE,yBAAyB;YACzB,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;YAExE,cAAc;YACd,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7C,MAAM,WAAW,GAAG,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;YAEvC,wDAAwD;YACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;YAEvD,gEAAgE;YAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,CAAC;YACjF,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACnD,IAAI,SAAS,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;oBAC9B,iBAAiB,GAAG,IAAI,CAAC;oBACzB,MAAM;gBACR,CAAC;YACH,CAAC;YAED,2DAA2D;YAC3D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC;YAE5E,kCAAkC;YAClC,MAAM,cAAc,GAAG,QAAQ,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,MAAM,CAAC;YAEnF,kDAAkD;YAClD,MAAM,mBAAmB,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;iBACpF,MAAM,CAAC,CAAC,CAAC,EAAE;gBACV,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBACxC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAClE,CAAC,CAAC,CAAC,MAAM,CAAC;YAEZ,kEAAkE;YAClE,MAAM,qBAAqB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;iBAC3E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;iBAChH,MAAM,CAAC;YAEV,wDAAwD;YACxD,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;iBAC7E,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YACjC,MAAM,cAAc,GAAG;gBACrB,6BAA6B,EAAa,oBAAoB;gBAC9D,2DAA2D,EAAG,mCAAmC;gBACjG,oEAAoE,EAAG,iBAAiB;gBACxF,wDAAwD,EAAO,mBAAmB;gBAClF,mFAAmF,EAAE,gBAAgB;gBACrG,2BAA2B,EAAgB,kBAAkB;gBAC7D,8BAA8B,EAAa,cAAc;aAC1D,CAAC;YACF,IAAI,mBAAmB,GAAG,CAAC,CAAC;YAC5B,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;gBACnC,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;oBACrC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;wBACzB,mBAAmB,EAAE,CAAC;wBACtB,MAAM,CAAC,kCAAkC;oBAC3C,CAAC;gBACH,CAAC;YACH,CAAC;YAED,kDAAkD;YAClD,MAAM,WAAW,GAAG,QAAQ,CAAC,gBAAgB,CAAC,wBAAwB,CAAC,CAAC,MAAM,CAAC;YAE/E,oBAAoB;YACpB,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,6CAA6C,CAAC,CAAC;YAEvF,0BAA0B;YAC1B,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,uBAAuB,CAAC,CAAC;YAEnE,sDAAsD;YACtD,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,2BAA2B,CAAC,CAAC;YACrE,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,iCAAiC,CAAC,CAAC;YACjF,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,2BAA2B,CAAC,CAAC;YAErE,0DAA0D;YAC1D,IAAI,iBAAiB,GAAG,CAAC,CAAC;YAC1B,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBAC1C,MAAM,aAAa,GAAG,QAAQ,CAAC,gBAAgB,CAC7C,8HAA8H,CAC/H,CAAC;gBACF,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAAC;YAC3C,CAAC;YAED,OAAO;gBACL,OAAO;gBACP,WAAW;gBACX,cAAc;gBACd,QAAQ;gBACR,WAAW;gBACX,OAAO;gBACP,iBAAiB;gBACjB,gBAAgB;gBAChB,cAAc;gBACd,mBAAmB;gBACnB,qBAAqB;gBACrB,mBAAmB;gBACnB,WAAW;gBACX,UAAU;gBACV,YAAY;gBACZ,UAAU;gBACV,gBAAgB;gBAChB,UAAU;gBACV,iBAAiB;aAClB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,mBAAmB;QACnB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,kBAAkB;gBACtB,QAAQ,EAAE,MAAM;gBAChB,WAAW,EAAE,6EAA6E;gBAC1F,UAAU,EAAE,wFAAwF;aACrG,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,0BAA0B;gBAC9B,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,yEAAyE;gBACtF,UAAU,EAAE,kFAAkF;aAC/F,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,eAAe;gBACnB,QAAQ,EAAE,MAAM;gBAChB,WAAW,EAAE,6BAA6B;gBAC1C,UAAU,EAAE,6CAA6C;aAC1D,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,MAAM,CAAC,WAAW,GAAG,EAAE,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,gBAAgB;gBACpB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,kFAAkF;gBAC/F,UAAU,EAAE,6CAA6C;aAC1D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,YAAY;gBAChB,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,6DAA6D;gBAC1E,UAAU,EAAE,yDAAyD;aACtE,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,aAAa;gBACjB,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,SAAS,MAAM,CAAC,OAAO,qDAAqD;gBACzF,UAAU,EAAE,kDAAkD;aAC/D,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,uBAAuB;gBAC3B,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,kEAAkE;gBAC/E,UAAU,EAAE,mDAAmD;aAChE,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,cAAc,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,YAAY;gBAChB,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,SAAS,MAAM,CAAC,cAAc,mDAAmD;gBAC9F,UAAU,EAAE,mEAAmE;aAChF,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,mBAAmB,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,sBAAsB;gBAC1B,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,SAAS,MAAM,CAAC,mBAAmB,6DAA6D;gBAC7G,UAAU,EAAE,iEAAiE;aAC9E,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,qBAAqB,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,wBAAwB;gBAC5B,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,SAAS,MAAM,CAAC,qBAAqB,iEAAiE;gBACnH,UAAU,EAAE,4EAA4E;aACzF,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,mBAAmB,GAAG,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,gBAAgB;gBACpB,QAAQ,EAAE,MAAM;gBAChB,WAAW,EAAE,SAAS,MAAM,CAAC,mBAAmB,8DAA8D;gBAC9G,UAAU,EAAE,2FAA2F;aACxG,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,sBAAsB;gBAC1B,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,SAAS,MAAM,CAAC,WAAW,kEAAkE;gBAC1G,UAAU,EAAE,+EAA+E;aAC5F,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,cAAc;gBAClB,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,oFAAoF;gBACjG,UAAU,EAAE,oDAAoD;aACjE,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,oBAAoB;gBACxB,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,SAAS,MAAM,CAAC,gBAAgB,iEAAiE;gBAC9G,UAAU,EAAE,wFAAwF;aACrG,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,iBAAiB;gBACrB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,4DAA4D;gBACzE,UAAU,EAAE,yDAAyD;aACtE,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,mBAAmB;gBACvB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,iFAAiF;gBAC9F,UAAU,EAAE,2EAA2E;aACxF,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACzE,MAAM,OAAO,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,CAAC,UAAU;gBAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM,CAAC,gBAAgB;gBAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC7D,IAAI,CAAC,MAAM,CAAC,UAAU;gBAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,oBAAoB;gBACxB,QAAQ,EAAE,KAAK;gBACf,WAAW,EAAE,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,kDAAkD;gBAC/G,UAAU,EAAE,6DAA6D;aAC1E,CAAC,CAAC;QACL,CAAC;QAED,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,eAAe;gBACnB,QAAQ,EAAE,MAAM;gBAChB,WAAW,EAAE,SAAS,MAAM,CAAC,iBAAiB,qEAAqE;gBACnH,UAAU,EAAE,mFAAmF;aAChG,CAAC,CAAC;QACL,CAAC;IAEH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,uEAAuE;QACvE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -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 {};
|