ghost-qa 1.3.2 → 2.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/README.md +77 -24
- package/SKILL.md +78 -77
- package/dist/bin/ghost-qa.d.ts +10 -2
- package/dist/bin/ghost-qa.d.ts.map +1 -1
- package/dist/bin/ghost-qa.js +106 -16
- package/dist/bin/ghost-qa.js.map +1 -1
- package/dist/src/engine/audit-runner.d.ts +6 -5
- package/dist/src/engine/audit-runner.d.ts.map +1 -1
- package/dist/src/engine/audit-runner.js +117 -60
- package/dist/src/engine/audit-runner.js.map +1 -1
- package/dist/src/engine/browser-session.d.ts +3 -3
- package/dist/src/engine/browser-session.d.ts.map +1 -1
- package/dist/src/engine/browser-session.js +18 -7
- package/dist/src/engine/browser-session.js.map +1 -1
- package/dist/src/engine/compare.d.ts +37 -0
- package/dist/src/engine/compare.d.ts.map +1 -0
- package/dist/src/engine/compare.js +63 -0
- package/dist/src/engine/compare.js.map +1 -0
- package/dist/src/engine/health-score.d.ts +14 -0
- package/dist/src/engine/health-score.d.ts.map +1 -0
- package/dist/src/engine/health-score.js +74 -0
- package/dist/src/engine/health-score.js.map +1 -0
- package/dist/src/engine/version.d.ts +14 -0
- package/dist/src/engine/version.d.ts.map +1 -0
- package/dist/src/engine/version.js +43 -0
- package/dist/src/engine/version.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/server.d.ts +24 -4
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +43 -5
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/reporter/agent-diagnostic.d.ts +6 -0
- package/dist/src/reporter/agent-diagnostic.d.ts.map +1 -1
- package/dist/src/reporter/agent-diagnostic.js +52 -2
- package/dist/src/reporter/agent-diagnostic.js.map +1 -1
- package/dist/src/reporter/html-report.d.ts.map +1 -1
- package/dist/src/reporter/html-report.js +79 -4
- package/dist/src/reporter/html-report.js.map +1 -1
- package/dist/src/reporter/terminal-summary.d.ts.map +1 -1
- package/dist/src/reporter/terminal-summary.js +6 -0
- package/dist/src/reporter/terminal-summary.js.map +1 -1
- package/dist/src/traps/a11y-trap.d.ts +23 -0
- package/dist/src/traps/a11y-trap.d.ts.map +1 -1
- package/dist/src/traps/a11y-trap.js +185 -0
- package/dist/src/traps/a11y-trap.js.map +1 -1
- package/dist/src/traps/console-trap.d.ts +3 -1
- package/dist/src/traps/console-trap.d.ts.map +1 -1
- package/dist/src/traps/console-trap.js +41 -0
- package/dist/src/traps/console-trap.js.map +1 -1
- package/dist/src/traps/layout-trap.d.ts +31 -0
- package/dist/src/traps/layout-trap.d.ts.map +1 -1
- package/dist/src/traps/layout-trap.js +173 -0
- package/dist/src/traps/layout-trap.js.map +1 -1
- package/dist/src/traps/links-trap.d.ts +34 -0
- package/dist/src/traps/links-trap.d.ts.map +1 -0
- package/dist/src/traps/links-trap.js +136 -0
- package/dist/src/traps/links-trap.js.map +1 -0
- package/dist/src/traps/network-trap.d.ts +4 -1
- package/dist/src/traps/network-trap.d.ts.map +1 -1
- package/dist/src/traps/network-trap.js +29 -2
- package/dist/src/traps/network-trap.js.map +1 -1
- package/dist/src/traps/perf-trap.d.ts +18 -0
- package/dist/src/traps/perf-trap.d.ts.map +1 -1
- package/dist/src/traps/perf-trap.js +148 -8
- package/dist/src/traps/perf-trap.js.map +1 -1
- package/dist/src/traps/seo-trap.d.ts +30 -0
- package/dist/src/traps/seo-trap.d.ts.map +1 -0
- package/dist/src/traps/seo-trap.js +98 -0
- package/dist/src/traps/seo-trap.js.map +1 -0
- package/dist/src/traps/theme-trap.d.ts +32 -0
- package/dist/src/traps/theme-trap.d.ts.map +1 -0
- package/dist/src/traps/theme-trap.js +109 -0
- package/dist/src/traps/theme-trap.js.map +1 -0
- package/dist/src/types.d.ts +55 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +30 -2
- package/dist/src/types.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import { type QAIssue, type ViewportName } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Trap 7 — SEO, Metadata & Social Graph.
|
|
5
|
+
*
|
|
6
|
+
* What search engines and link unfurlers see, audited on the live DOM
|
|
7
|
+
* (post-hydration, exactly as a crawler rendering the page would).
|
|
8
|
+
*/
|
|
9
|
+
export interface SeoMetrics {
|
|
10
|
+
title: string;
|
|
11
|
+
hasDescription: boolean;
|
|
12
|
+
hasLang: boolean;
|
|
13
|
+
hasViewportMeta: boolean;
|
|
14
|
+
hasCanonical: boolean;
|
|
15
|
+
ogTitle: boolean;
|
|
16
|
+
ogDescription: boolean;
|
|
17
|
+
ogImage: boolean;
|
|
18
|
+
/** id attribute values used more than once. */
|
|
19
|
+
duplicateIds: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare const EMPTY_SEO_METRICS: SeoMetrics;
|
|
22
|
+
export declare const TITLE_MIN_LENGTH = 10;
|
|
23
|
+
export declare const TITLE_MAX_LENGTH = 60;
|
|
24
|
+
export declare function evaluateSeoMetrics(metrics: SeoMetrics, route: string, viewport: ViewportName): QAIssue[];
|
|
25
|
+
export declare class SeoTrap {
|
|
26
|
+
private readonly viewport;
|
|
27
|
+
constructor(viewport: ViewportName);
|
|
28
|
+
collect(page: Page, route: string): Promise<QAIssue[]>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=seo-trap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-trap.d.ts","sourceRoot":"","sources":["../../../src/traps/seo-trap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAa,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,eAAO,MAAM,iBAAiB,EAAE,UAU/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,UAAU,EACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,GACrB,OAAO,EAAE,CAoDX;AAsBD,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;gBAE5B,QAAQ,EAAE,YAAY;IAI5B,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CAS7D"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { makeIssue } from '../types.js';
|
|
2
|
+
export const EMPTY_SEO_METRICS = {
|
|
3
|
+
title: '',
|
|
4
|
+
hasDescription: false,
|
|
5
|
+
hasLang: false,
|
|
6
|
+
hasViewportMeta: false,
|
|
7
|
+
hasCanonical: false,
|
|
8
|
+
ogTitle: false,
|
|
9
|
+
ogDescription: false,
|
|
10
|
+
ogImage: false,
|
|
11
|
+
duplicateIds: [],
|
|
12
|
+
};
|
|
13
|
+
export const TITLE_MIN_LENGTH = 10;
|
|
14
|
+
export const TITLE_MAX_LENGTH = 60;
|
|
15
|
+
export function evaluateSeoMetrics(metrics, route, viewport) {
|
|
16
|
+
const issues = [];
|
|
17
|
+
const push = (severity, type, message) => issues.push(makeIssue({ category: 'seo', severity, type, message, route, viewport }));
|
|
18
|
+
const title = metrics.title.trim();
|
|
19
|
+
if (title.length === 0) {
|
|
20
|
+
push('major', 'missing-document-title', 'Missing <title>: search results, tabs and screen readers all start here. Add a unique descriptive title per route.');
|
|
21
|
+
}
|
|
22
|
+
else if (title.length < TITLE_MIN_LENGTH) {
|
|
23
|
+
push('minor', 'title-too-short', `<title> is only ${title.length} characters ("${title}"). Aim for ${TITLE_MIN_LENGTH}–${TITLE_MAX_LENGTH} descriptive characters.`);
|
|
24
|
+
}
|
|
25
|
+
else if (title.length > TITLE_MAX_LENGTH) {
|
|
26
|
+
push('minor', 'title-too-long', `<title> is ${title.length} characters — it will be truncated in search results (~${TITLE_MAX_LENGTH} shown). Shorten it and move detail into the description.`);
|
|
27
|
+
}
|
|
28
|
+
if (!metrics.hasDescription) {
|
|
29
|
+
push('minor', 'missing-meta-description', 'Missing <meta name="description">: search engines will improvise a snippet from page text. Add a 120–160 character summary.');
|
|
30
|
+
}
|
|
31
|
+
if (!metrics.hasLang) {
|
|
32
|
+
push('minor', 'missing-html-lang', 'Missing lang attribute on <html> (e.g. <html lang="en">) — hurts both SEO locale signals and screen-reader pronunciation.');
|
|
33
|
+
}
|
|
34
|
+
if (!metrics.hasViewportMeta) {
|
|
35
|
+
push('major', 'missing-viewport-meta', 'Missing <meta name="viewport">: mobile search ranking and rendering both depend on it. Add content="width=device-width, initial-scale=1".');
|
|
36
|
+
}
|
|
37
|
+
if (!metrics.hasCanonical) {
|
|
38
|
+
push('minor', 'missing-canonical', 'Missing <link rel="canonical">: duplicate URLs (query params, trailing slashes) can split ranking. Point canonical at the clean route URL.');
|
|
39
|
+
}
|
|
40
|
+
const missingOg = [
|
|
41
|
+
!metrics.ogTitle && 'og:title',
|
|
42
|
+
!metrics.ogDescription && 'og:description',
|
|
43
|
+
!metrics.ogImage && 'og:image',
|
|
44
|
+
].filter(Boolean);
|
|
45
|
+
if (missingOg.length > 0) {
|
|
46
|
+
push('minor', 'missing-opengraph', `Missing OpenGraph tags: ${missingOg.join(', ')}. Links shared to Slack/X/LinkedIn will unfurl with no (or scraped) preview. Add the full og: set.`);
|
|
47
|
+
}
|
|
48
|
+
for (const id of metrics.duplicateIds.slice(0, 5)) {
|
|
49
|
+
issues.push(makeIssue({
|
|
50
|
+
category: 'seo',
|
|
51
|
+
severity: 'major',
|
|
52
|
+
type: 'duplicate-id',
|
|
53
|
+
message: `Duplicate id attribute in the live DOM: #${id} appears more than once. ids must be unique — anchors, aria-labelledby and getElementById all silently target the first match.`,
|
|
54
|
+
route,
|
|
55
|
+
viewport,
|
|
56
|
+
details: { id },
|
|
57
|
+
selector: `#${id}`,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
return issues;
|
|
61
|
+
}
|
|
62
|
+
/** In-page probe: metadata facts from the live (post-hydration) DOM. */
|
|
63
|
+
function probeSeo() {
|
|
64
|
+
const meta = (selector) => Boolean(document.querySelector(selector));
|
|
65
|
+
const idCounts = new Map();
|
|
66
|
+
document.querySelectorAll('[id]').forEach((el) => {
|
|
67
|
+
if (el.id)
|
|
68
|
+
idCounts.set(el.id, (idCounts.get(el.id) ?? 0) + 1);
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
title: document.title ?? '',
|
|
72
|
+
hasDescription: meta('meta[name="description"]'),
|
|
73
|
+
hasLang: (document.documentElement.getAttribute('lang') ?? '').trim().length > 0,
|
|
74
|
+
hasViewportMeta: meta('meta[name="viewport"]'),
|
|
75
|
+
hasCanonical: meta('link[rel="canonical"]'),
|
|
76
|
+
ogTitle: meta('meta[property="og:title"]'),
|
|
77
|
+
ogDescription: meta('meta[property="og:description"]'),
|
|
78
|
+
ogImage: meta('meta[property="og:image"]'),
|
|
79
|
+
duplicateIds: [...idCounts.entries()].filter(([, count]) => count > 1).map(([id]) => id),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export class SeoTrap {
|
|
83
|
+
viewport;
|
|
84
|
+
constructor(viewport) {
|
|
85
|
+
this.viewport = viewport;
|
|
86
|
+
}
|
|
87
|
+
async collect(page, route) {
|
|
88
|
+
let metrics;
|
|
89
|
+
try {
|
|
90
|
+
metrics = await page.evaluate(probeSeo);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
return evaluateSeoMetrics(metrics, route, this.viewport);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=seo-trap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-trap.js","sourceRoot":"","sources":["../../../src/traps/seo-trap.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAmC,MAAM,aAAa,CAAC;AAqBzE,MAAM,CAAC,MAAM,iBAAiB,GAAe;IAC3C,KAAK,EAAE,EAAE;IACT,cAAc,EAAE,KAAK;IACrB,OAAO,EAAE,KAAK;IACd,eAAe,EAAE,KAAK;IACtB,YAAY,EAAE,KAAK;IACnB,OAAO,EAAE,KAAK;IACd,aAAa,EAAE,KAAK;IACpB,OAAO,EAAE,KAAK;IACd,YAAY,EAAE,EAAE;CACjB,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC,MAAM,UAAU,kBAAkB,CAChC,OAAmB,EACnB,KAAa,EACb,QAAsB;IAEtB,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,CAAC,QAA2B,EAAE,IAAqB,EAAE,OAAe,EAAE,EAAE,CACnF,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAExF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,IAAI,CAAC,OAAO,EAAE,wBAAwB,EAAE,oHAAoH,CAAC,CAAC;IAChK,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,KAAK,CAAC,MAAM,iBAAiB,KAAK,eAAe,gBAAgB,IAAI,gBAAgB,0BAA0B,CAAC,CAAC;IACvK,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE,gBAAgB,EAAE,cAAc,KAAK,CAAC,MAAM,0DAA0D,gBAAgB,2DAA2D,CAAC,CAAC;IACnM,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,0BAA0B,EAAE,6HAA6H,CAAC,CAAC;IAC3K,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,2HAA2H,CAAC,CAAC;IAClK,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,uBAAuB,EAAE,2IAA2I,CAAC,CAAC;IACtL,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,4IAA4I,CAAC,CAAC;IACnL,CAAC;IAED,MAAM,SAAS,GAAG;QAChB,CAAC,OAAO,CAAC,OAAO,IAAI,UAAU;QAC9B,CAAC,OAAO,CAAC,aAAa,IAAI,gBAAgB;QAC1C,CAAC,OAAO,CAAC,OAAO,IAAI,UAAU;KAC/B,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IAC9B,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,oGAAoG,CAAC,CAAC;IAC1L,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAClD,MAAM,CAAC,IAAI,CACT,SAAS,CAAC;YACR,QAAQ,EAAE,KAAK;YACf,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,4CAA4C,EAAE,gIAAgI;YACvL,KAAK;YACL,QAAQ;YACR,OAAO,EAAE,EAAE,EAAE,EAAE;YACf,QAAQ,EAAE,IAAI,EAAE,EAAE;SACnB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wEAAwE;AACxE,SAAS,QAAQ;IACf,MAAM,IAAI,GAAG,CAAC,QAAgB,EAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE;QAC/C,IAAI,EAAE,CAAC,EAAE;YAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IACH,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,EAAE;QAC3B,cAAc,EAAE,IAAI,CAAC,0BAA0B,CAAC;QAChD,OAAO,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAChF,eAAe,EAAE,IAAI,CAAC,uBAAuB,CAAC;QAC9C,YAAY,EAAE,IAAI,CAAC,uBAAuB,CAAC;QAC3C,OAAO,EAAE,IAAI,CAAC,2BAA2B,CAAC;QAC1C,aAAa,EAAE,IAAI,CAAC,iCAAiC,CAAC;QACtD,OAAO,EAAE,IAAI,CAAC,2BAA2B,CAAC;QAC1C,YAAY,EAAE,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;KACzF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,OAAO;IACD,QAAQ,CAAe;IAExC,YAAY,QAAsB;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAU,EAAE,KAAa;QACrC,IAAI,OAAmB,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,kBAAkB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC;CACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import { contrastRatio } from './a11y-trap.js';
|
|
3
|
+
import { type ColorScheme, type QAIssue, type ViewportName } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Trap 8 — Dark / Light Theme Emulation.
|
|
6
|
+
*
|
|
7
|
+
* The page is rendered twice per viewport, once per emulated colour scheme
|
|
8
|
+
* (`colorScheme: 'light' | 'dark'` media emulation). Under each scheme the
|
|
9
|
+
* trap recomputes real foreground/background contrast on visible text: the
|
|
10
|
+
* classic invisible-dark-mode bug (near-black text on a near-black
|
|
11
|
+
* background when the OS flips to dark) shows up as a theme-contrast
|
|
12
|
+
* finding tagged with the failing scheme.
|
|
13
|
+
*/
|
|
14
|
+
export interface ThemeContrastFailure {
|
|
15
|
+
descriptor: string;
|
|
16
|
+
selector: string;
|
|
17
|
+
ratio: number;
|
|
18
|
+
required: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ThemeMetrics {
|
|
21
|
+
failures: ThemeContrastFailure[];
|
|
22
|
+
}
|
|
23
|
+
export declare const EMPTY_THEME_METRICS: ThemeMetrics;
|
|
24
|
+
export declare function evaluateThemeMetrics(metrics: ThemeMetrics, route: string, viewport: ViewportName, colorScheme: ColorScheme): QAIssue[];
|
|
25
|
+
export declare class ThemeTrap {
|
|
26
|
+
private readonly viewport;
|
|
27
|
+
private readonly colorScheme;
|
|
28
|
+
constructor(viewport: ViewportName, colorScheme: ColorScheme);
|
|
29
|
+
collect(page: Page, route: string): Promise<QAIssue[]>;
|
|
30
|
+
}
|
|
31
|
+
export { contrastRatio };
|
|
32
|
+
//# sourceMappingURL=theme-trap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-trap.d.ts","sourceRoot":"","sources":["../../../src/traps/theme-trap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAa,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3F;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,EAAE,YAA+B,CAAC;AAElE,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,WAAW,GACvB,OAAO,EAAE,CAgBX;AAwED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW;IAKtD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CAS7D;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { contrastRatio } from './a11y-trap.js';
|
|
2
|
+
import { makeIssue } from '../types.js';
|
|
3
|
+
export const EMPTY_THEME_METRICS = { failures: [] };
|
|
4
|
+
export function evaluateThemeMetrics(metrics, route, viewport, colorScheme) {
|
|
5
|
+
return metrics.failures.slice(0, 5).map((failure) => makeIssue({
|
|
6
|
+
category: 'theme',
|
|
7
|
+
severity: 'major',
|
|
8
|
+
type: 'theme-contrast',
|
|
9
|
+
message: `Invisible/low-contrast text in ${colorScheme} mode: ${failure.descriptor} has contrast ${failure.ratio}:1 (needs ${failure.required}:1) ` +
|
|
10
|
+
`when the OS colour scheme is ${colorScheme}. Use theme-aware tokens (CSS variables / dark: variants) instead of hard-coded colours.`,
|
|
11
|
+
route,
|
|
12
|
+
viewport,
|
|
13
|
+
colorScheme,
|
|
14
|
+
details: { element: failure.descriptor, ratio: failure.ratio, required: failure.required, colorScheme },
|
|
15
|
+
selector: failure.selector,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
/** In-page probe: computed-contrast failures under the emulated scheme. */
|
|
19
|
+
function probeTheme() {
|
|
20
|
+
const describe = (el) => {
|
|
21
|
+
const tag = el.tagName.toLowerCase();
|
|
22
|
+
const id = el.id ? `#${el.id}` : '';
|
|
23
|
+
const text = (el.textContent ?? '').replace(/\s+/g, ' ').trim().slice(0, 40);
|
|
24
|
+
return `${tag}${id}${text ? ` "${text}"` : ''}`;
|
|
25
|
+
};
|
|
26
|
+
const visible = (el) => {
|
|
27
|
+
const style = window.getComputedStyle(el);
|
|
28
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
29
|
+
};
|
|
30
|
+
const parseRgb = (value) => {
|
|
31
|
+
const match = value.match(/rgba?\(([^)]+)\)/);
|
|
32
|
+
if (!match)
|
|
33
|
+
return null;
|
|
34
|
+
const parts = match[1].split(',').map((p) => Number.parseFloat(p));
|
|
35
|
+
if (parts.length < 3 || parts.some((n) => Number.isNaN(n)))
|
|
36
|
+
return null;
|
|
37
|
+
return [parts[0], parts[1], parts[2], parts.length > 3 ? parts[3] : 1];
|
|
38
|
+
};
|
|
39
|
+
const luminance = (rgb) => {
|
|
40
|
+
const linear = (channel) => {
|
|
41
|
+
const s = channel / 255;
|
|
42
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
43
|
+
};
|
|
44
|
+
return 0.2126 * linear(rgb[0]) + 0.7152 * linear(rgb[1]) + 0.0722 * linear(rgb[2]);
|
|
45
|
+
};
|
|
46
|
+
const effectiveBackground = (el) => {
|
|
47
|
+
let node = el;
|
|
48
|
+
while (node) {
|
|
49
|
+
const bg = parseRgb(window.getComputedStyle(node).backgroundColor);
|
|
50
|
+
if (bg && bg[3] > 0)
|
|
51
|
+
return [bg[0], bg[1], bg[2]];
|
|
52
|
+
node = node.parentElement;
|
|
53
|
+
}
|
|
54
|
+
// No painted background anywhere: browsers still paint the default
|
|
55
|
+
// white canvas (pages that never opt into dark styling render
|
|
56
|
+
// identically in both schemes). Real dark-mode bugs set a dark
|
|
57
|
+
// background via media query, which the walk above finds.
|
|
58
|
+
return [255, 255, 255];
|
|
59
|
+
};
|
|
60
|
+
const failures = [];
|
|
61
|
+
for (const el of Array.from(document.querySelectorAll('body *')).slice(0, 400)) {
|
|
62
|
+
if (failures.length >= 8)
|
|
63
|
+
break;
|
|
64
|
+
const html = el;
|
|
65
|
+
const ownText = Array.from(html.childNodes).some((n) => n.nodeType === Node.TEXT_NODE && (n.textContent ?? '').trim().length > 0);
|
|
66
|
+
if (!ownText || !visible(el))
|
|
67
|
+
continue;
|
|
68
|
+
const style = window.getComputedStyle(html);
|
|
69
|
+
const fg = parseRgb(style.color);
|
|
70
|
+
if (!fg)
|
|
71
|
+
continue;
|
|
72
|
+
const bg = effectiveBackground(el);
|
|
73
|
+
const l1 = luminance([fg[0], fg[1], fg[2]]);
|
|
74
|
+
const l2 = luminance(bg);
|
|
75
|
+
const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
|
76
|
+
const fontSize = Number.parseFloat(style.fontSize) || 16;
|
|
77
|
+
const bold = Number.parseInt(style.fontWeight, 10) >= 700 || style.fontWeight === 'bold';
|
|
78
|
+
const required = fontSize >= 24 || (fontSize >= 18.66 && bold) ? 3 : 4.5;
|
|
79
|
+
if (ratio < required) {
|
|
80
|
+
failures.push({
|
|
81
|
+
descriptor: describe(el),
|
|
82
|
+
selector: describe(el),
|
|
83
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
84
|
+
required,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { failures };
|
|
89
|
+
}
|
|
90
|
+
export class ThemeTrap {
|
|
91
|
+
viewport;
|
|
92
|
+
colorScheme;
|
|
93
|
+
constructor(viewport, colorScheme) {
|
|
94
|
+
this.viewport = viewport;
|
|
95
|
+
this.colorScheme = colorScheme;
|
|
96
|
+
}
|
|
97
|
+
async collect(page, route) {
|
|
98
|
+
let metrics;
|
|
99
|
+
try {
|
|
100
|
+
metrics = await page.evaluate(probeTheme);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return evaluateThemeMetrics(metrics, route, this.viewport, this.colorScheme);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export { contrastRatio };
|
|
109
|
+
//# sourceMappingURL=theme-trap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-trap.js","sourceRoot":"","sources":["../../../src/traps/theme-trap.ts"],"names":[],"mappings":"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"}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -5,10 +5,22 @@
|
|
|
5
5
|
* rendered for a human (terminal), serialised for CI (JSON), or fed back to a
|
|
6
6
|
* coding agent (Markdown diagnostic) without any translation layer.
|
|
7
7
|
*/
|
|
8
|
-
export type ViewportName = 'desktop' | 'mobile';
|
|
9
|
-
export type
|
|
8
|
+
export type ViewportName = 'desktop' | 'tablet' | 'mobile';
|
|
9
|
+
export type ColorScheme = 'light' | 'dark';
|
|
10
|
+
export type IssueCategory = 'console' | 'network' | 'layout' | 'accessibility' | 'performance' | 'interaction' | 'navigation' | 'links' | 'seo' | 'theme';
|
|
11
|
+
/** The eight specialised traps of the v2 Deep Browser Inspection Engine. */
|
|
12
|
+
export type TrapName = 'console' | 'network' | 'layout' | 'accessibility' | 'performance' | 'links' | 'seo' | 'theme';
|
|
13
|
+
export declare const TRAP_LABELS: Record<TrapName, string>;
|
|
14
|
+
/** Which trap owns a category (interaction/navigation fold into Links/UX). */
|
|
15
|
+
export declare const CATEGORY_TO_TRAP: Record<IssueCategory, TrapName>;
|
|
10
16
|
export type IssueSeverity = 'critical' | 'major' | 'minor';
|
|
11
|
-
export type IssueType = 'page-error' | 'unhandled-rejection' | 'hydration-mismatch' | 'console-error' | 'console-warning' | 'http-error' | 'request-failed' | 'cors-error' | 'broken-asset' | 'horizontal-overflow' | 'zero-size-interactive' | '
|
|
17
|
+
export type IssueType = 'page-error' | 'unhandled-rejection' | 'hydration-mismatch' | 'console-error' | 'console-warning' | 'duplicate-key-warning' | 'csp-violation' | 'http-error' | 'request-failed' | 'request-timeout' | 'cors-error' | 'cors-preflight-failed' | 'broken-asset' | 'broken-image' | 'broken-svg' | 'horizontal-overflow' | 'zero-size-interactive' | 'element-collision' | 'small-touch-target' | 'clipped-text' | 'offscreen-interactive' | 'interaction-failed' | 'navigation-failed' | 'missing-accessible-name' | 'img-missing-alt' | 'input-missing-label' | 'missing-document-title' | 'missing-html-lang' | 'contrast-insufficient' | 'heading-hierarchy' | 'focus-not-visible' | 'slow-page-load' | 'slow-ttfb' | 'slow-fcp' | 'slow-lcp' | 'high-cls' | 'oversized-resource' | 'unoptimized-resource' | 'excessive-payload' | 'render-blocking-resources' | 'layout-thrashing' | 'excessive-dom-size' | 'deep-dom' | 'missing-viewport-meta' | 'dead-link' | 'placeholder-link' | 'form-button-missing-type' | 'stuck-disabled' | 'title-too-short' | 'title-too-long' | 'missing-meta-description' | 'missing-canonical' | 'missing-opengraph' | 'duplicate-id' | 'theme-contrast';
|
|
18
|
+
export interface BoundingBox {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
12
24
|
export interface QAIssue {
|
|
13
25
|
/** Stable, de-duplication-friendly identifier (hash of the fingerprint). */
|
|
14
26
|
id: string;
|
|
@@ -20,6 +32,12 @@ export interface QAIssue {
|
|
|
20
32
|
/** Path of the route the issue was observed on, e.g. `/pricing`. */
|
|
21
33
|
route: string;
|
|
22
34
|
viewport: ViewportName;
|
|
35
|
+
/** Exact DOM selector (`#id`, `[data-testid=…]`, or CSS path) when known. */
|
|
36
|
+
selector?: string;
|
|
37
|
+
/** Live bounding box of the offending element, when geometry is relevant. */
|
|
38
|
+
boundingBox?: BoundingBox;
|
|
39
|
+
/** Which emulated colour scheme the finding belongs to, when relevant. */
|
|
40
|
+
colorScheme?: ColorScheme;
|
|
23
41
|
/** Optional machine-readable context (status code, selector, geometry…). */
|
|
24
42
|
details?: Record<string, string | number | boolean>;
|
|
25
43
|
stack?: string;
|
|
@@ -45,6 +63,13 @@ export interface RouteRecord {
|
|
|
45
63
|
viewports: ViewportName[];
|
|
46
64
|
issueCount: number;
|
|
47
65
|
}
|
|
66
|
+
export interface ScreenshotRecord {
|
|
67
|
+
route: string;
|
|
68
|
+
viewport: ViewportName;
|
|
69
|
+
colorScheme: ColorScheme;
|
|
70
|
+
/** Data URI (`data:image/jpeg;base64,…`) so reports stay self-contained. */
|
|
71
|
+
dataUri: string;
|
|
72
|
+
}
|
|
48
73
|
export interface GhostQAOptions {
|
|
49
74
|
maxDepth: number;
|
|
50
75
|
maxRoutes: number;
|
|
@@ -56,8 +81,28 @@ export interface GhostQAOptions {
|
|
|
56
81
|
interactions: boolean;
|
|
57
82
|
/** Fail (exit 1) when an issue at or above this severity is found. */
|
|
58
83
|
failOn: IssueSeverity;
|
|
84
|
+
/** Colour schemes to emulate (Theme Trap runs under each). */
|
|
85
|
+
colorSchemes?: ColorScheme[];
|
|
86
|
+
/** Capture a JPEG screenshot per route/viewport for the HTML dashboard. */
|
|
87
|
+
screenshots?: boolean;
|
|
59
88
|
}
|
|
60
89
|
export declare const DEFAULT_OPTIONS: GhostQAOptions;
|
|
90
|
+
export interface TrapScore {
|
|
91
|
+
trap: TrapName;
|
|
92
|
+
label: string;
|
|
93
|
+
/** 0–100, deterministic from that trap's findings. */
|
|
94
|
+
score: number;
|
|
95
|
+
grade: string;
|
|
96
|
+
issues: number;
|
|
97
|
+
bySeverity: Record<IssueSeverity, number>;
|
|
98
|
+
}
|
|
99
|
+
export interface HealthScore {
|
|
100
|
+
/** Overall QA Health Score, 0–100 (mean of the eight trap scores). */
|
|
101
|
+
score: number;
|
|
102
|
+
/** Letter grade A+ … F. */
|
|
103
|
+
grade: string;
|
|
104
|
+
byTrap: TrapScore[];
|
|
105
|
+
}
|
|
61
106
|
export interface GhostQAReport {
|
|
62
107
|
tool: 'ghost-qa';
|
|
63
108
|
version: string;
|
|
@@ -70,6 +115,10 @@ export interface GhostQAReport {
|
|
|
70
115
|
routes: RouteRecord[];
|
|
71
116
|
interactions: InteractionAction[];
|
|
72
117
|
issues: QAIssue[];
|
|
118
|
+
/** QA Health Score (0–100, grade A+…F) with per-trap sub-scores. */
|
|
119
|
+
health?: HealthScore;
|
|
120
|
+
/** Screenshots captured when `options.screenshots` was enabled. */
|
|
121
|
+
screenshots?: ScreenshotRecord[];
|
|
73
122
|
summary: {
|
|
74
123
|
routesCrawled: number;
|
|
75
124
|
interactionsTested: number;
|
|
@@ -78,6 +127,9 @@ export interface GhostQAReport {
|
|
|
78
127
|
totalIssues: number;
|
|
79
128
|
bySeverity: Record<IssueSeverity, number>;
|
|
80
129
|
byCategory: Record<IssueCategory, number>;
|
|
130
|
+
/** Present on v2 reports: mirrors `health.score` / `health.grade`. */
|
|
131
|
+
healthScore?: number;
|
|
132
|
+
grade?: string;
|
|
81
133
|
};
|
|
82
134
|
}
|
|
83
135
|
export declare const SEVERITY_ORDER: Record<IssueSeverity, number>;
|
package/dist/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/src/types.js
CHANGED
|
@@ -5,6 +5,29 @@
|
|
|
5
5
|
* rendered for a human (terminal), serialised for CI (JSON), or fed back to a
|
|
6
6
|
* coding agent (Markdown diagnostic) without any translation layer.
|
|
7
7
|
*/
|
|
8
|
+
export const TRAP_LABELS = {
|
|
9
|
+
console: 'Console & Framework Runtime',
|
|
10
|
+
network: 'Network & Asset Integrity',
|
|
11
|
+
layout: 'Layout, Responsive Geometry & Collision',
|
|
12
|
+
accessibility: 'Accessibility (WCAG 2.1 AA) & Keyboard Focus',
|
|
13
|
+
performance: 'Performance & Core Web Vitals',
|
|
14
|
+
links: 'Safe Interactive & Link Integrity',
|
|
15
|
+
seo: 'SEO, Metadata & Social Graph',
|
|
16
|
+
theme: 'Dark / Light Theme Emulation',
|
|
17
|
+
};
|
|
18
|
+
/** Which trap owns a category (interaction/navigation fold into Links/UX). */
|
|
19
|
+
export const CATEGORY_TO_TRAP = {
|
|
20
|
+
console: 'console',
|
|
21
|
+
network: 'network',
|
|
22
|
+
layout: 'layout',
|
|
23
|
+
accessibility: 'accessibility',
|
|
24
|
+
performance: 'performance',
|
|
25
|
+
interaction: 'links',
|
|
26
|
+
navigation: 'links',
|
|
27
|
+
links: 'links',
|
|
28
|
+
seo: 'seo',
|
|
29
|
+
theme: 'theme',
|
|
30
|
+
};
|
|
8
31
|
export const DEFAULT_OPTIONS = {
|
|
9
32
|
maxDepth: 2,
|
|
10
33
|
maxRoutes: 25,
|
|
@@ -12,9 +35,11 @@ export const DEFAULT_OPTIONS = {
|
|
|
12
35
|
timeoutMs: 15_000,
|
|
13
36
|
settleMs: 600,
|
|
14
37
|
headless: true,
|
|
15
|
-
viewports: ['desktop', 'mobile'],
|
|
38
|
+
viewports: ['desktop', 'tablet', 'mobile'],
|
|
16
39
|
interactions: true,
|
|
17
40
|
failOn: 'major',
|
|
41
|
+
colorSchemes: ['light', 'dark'],
|
|
42
|
+
screenshots: false,
|
|
18
43
|
};
|
|
19
44
|
export const SEVERITY_ORDER = {
|
|
20
45
|
critical: 3,
|
|
@@ -35,7 +60,7 @@ export function fingerprint(input) {
|
|
|
35
60
|
}
|
|
36
61
|
export function makeIssue(input) {
|
|
37
62
|
const detailString = input.details ? JSON.stringify(input.details) : '';
|
|
38
|
-
const id = fingerprint([input.category, input.type, input.route, input.viewport, input.message, detailString].join('|'));
|
|
63
|
+
const id = fingerprint([input.category, input.type, input.route, input.viewport, input.colorScheme ?? '', input.message, detailString].join('|'));
|
|
39
64
|
return {
|
|
40
65
|
...input,
|
|
41
66
|
id,
|
|
@@ -61,6 +86,9 @@ export function summarise(routes, interactions, issues, failOn) {
|
|
|
61
86
|
performance: 0,
|
|
62
87
|
interaction: 0,
|
|
63
88
|
navigation: 0,
|
|
89
|
+
links: 0,
|
|
90
|
+
seo: 0,
|
|
91
|
+
theme: 0,
|
|
64
92
|
};
|
|
65
93
|
for (const issue of issues) {
|
|
66
94
|
bySeverity[issue.severity] += 1;
|
package/dist/src/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghost-qa",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Autonomous headless browser QA & self-healing verification loop for coding agents.
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Autonomous headless browser QA & self-healing verification loop for coding agents. 8-trap deep inspection (console, network, layout, WCAG a11y + keyboard focus, Core Web Vitals, links, SEO, theme parity) across desktop/tablet/mobile in light + dark, QA Health Score (A+ to F), interactive HTML dashboard, and regression diffing (ghost_qa_compare).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"ghost-qa": "dist/bin/ghost-qa.js"
|
|
@@ -65,7 +65,15 @@
|
|
|
65
65
|
"agent-tools",
|
|
66
66
|
"llm-agents",
|
|
67
67
|
"frontend-qa",
|
|
68
|
-
"mobile-testing"
|
|
68
|
+
"mobile-testing",
|
|
69
|
+
"tablet-testing",
|
|
70
|
+
"dark-mode-testing",
|
|
71
|
+
"core-web-vitals",
|
|
72
|
+
"regression-testing",
|
|
73
|
+
"lighthouse-alternative",
|
|
74
|
+
"seo-audit",
|
|
75
|
+
"link-checker",
|
|
76
|
+
"health-score"
|
|
69
77
|
],
|
|
70
78
|
"author": "VibeFoundry",
|
|
71
79
|
"license": "MIT",
|