accessibility-server-mcp 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 +762 -0
- package/config/wcag-rules.json +252 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/dist/test-manual.d.ts +6 -0
- package/dist/test-manual.d.ts.map +1 -0
- package/dist/test-manual.js +66 -0
- package/dist/test-manual.js.map +1 -0
- package/dist/tools/accessibility-tester.d.ts +56 -0
- package/dist/tools/accessibility-tester.d.ts.map +1 -0
- package/dist/tools/accessibility-tester.js +317 -0
- package/dist/tools/accessibility-tester.js.map +1 -0
- package/dist/tools/color-contrast.d.ts +49 -0
- package/dist/tools/color-contrast.d.ts.map +1 -0
- package/dist/tools/color-contrast.js +237 -0
- package/dist/tools/color-contrast.js.map +1 -0
- package/dist/tools/wcag-validator.d.ts +43 -0
- package/dist/tools/wcag-validator.d.ts.map +1 -0
- package/dist/tools/wcag-validator.js +249 -0
- package/dist/tools/wcag-validator.js.map +1 -0
- package/dist/tools/website-accessibility-tester.d.ts +103 -0
- package/dist/tools/website-accessibility-tester.d.ts.map +1 -0
- package/dist/tools/website-accessibility-tester.js +228 -0
- package/dist/tools/website-accessibility-tester.js.map +1 -0
- package/dist/types/accessibility.d.ts +172 -0
- package/dist/types/accessibility.d.ts.map +1 -0
- package/dist/types/accessibility.js +5 -0
- package/dist/types/accessibility.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mcp.d.ts +70 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +5 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/utils/browser-manager.d.ts +83 -0
- package/dist/utils/browser-manager.d.ts.map +1 -0
- package/dist/utils/browser-manager.js +292 -0
- package/dist/utils/browser-manager.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +107 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/report-generator.d.ts +31 -0
- package/dist/utils/report-generator.d.ts.map +1 -0
- package/dist/utils/report-generator.js +252 -0
- package/dist/utils/report-generator.js.map +1 -0
- package/dist/utils/website-crawler.d.ts +66 -0
- package/dist/utils/website-crawler.d.ts.map +1 -0
- package/dist/utils/website-crawler.js +306 -0
- package/dist/utils/website-crawler.js.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types and interfaces for accessibility testing
|
|
3
|
+
*/
|
|
4
|
+
export type WCAGLevel = 'A' | 'AA' | 'AAA';
|
|
5
|
+
export type WCAGVersion = '2.0' | '2.1' | '2.2';
|
|
6
|
+
export type ViolationSeverity = 'minor' | 'moderate' | 'serious' | 'critical';
|
|
7
|
+
export type AccessibilityCategory = 'color' | 'keyboard' | 'images' | 'headings' | 'landmarks' | 'forms' | 'links' | 'tables' | 'aria' | 'structure';
|
|
8
|
+
/**
|
|
9
|
+
* Represents an accessibility violation found during testing
|
|
10
|
+
*/
|
|
11
|
+
export interface AccessibilityViolation {
|
|
12
|
+
/** Unique identifier for the violation rule */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Impact level of the violation */
|
|
15
|
+
impact: ViolationSeverity;
|
|
16
|
+
/** Human-readable description of the violation */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Help text explaining how to fix the violation */
|
|
19
|
+
help: string;
|
|
20
|
+
/** URL to detailed help documentation */
|
|
21
|
+
helpUrl: string;
|
|
22
|
+
/** DOM nodes where the violation was found */
|
|
23
|
+
nodes: ViolationNode[];
|
|
24
|
+
/** Tags categorizing this violation */
|
|
25
|
+
tags: string[];
|
|
26
|
+
/** WCAG conformance level this violation affects */
|
|
27
|
+
wcagLevel: WCAGLevel;
|
|
28
|
+
/** WCAG versions where this rule applies */
|
|
29
|
+
wcagVersions: WCAGVersion[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Represents a DOM node where a violation occurred
|
|
33
|
+
*/
|
|
34
|
+
export interface ViolationNode {
|
|
35
|
+
/** HTML source of the failing element */
|
|
36
|
+
html: string;
|
|
37
|
+
/** CSS selector path to the element */
|
|
38
|
+
target: string[];
|
|
39
|
+
/** XPath to the element (if available) */
|
|
40
|
+
xpath?: string;
|
|
41
|
+
/** Ancestry chain of the element */
|
|
42
|
+
ancestry?: string[];
|
|
43
|
+
/** Summary of why this element failed */
|
|
44
|
+
failureSummary?: string;
|
|
45
|
+
/** Additional data about the failure */
|
|
46
|
+
any?: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
data: Record<string, unknown>;
|
|
49
|
+
relatedNodes?: Array<{
|
|
50
|
+
html: string;
|
|
51
|
+
target: string[];
|
|
52
|
+
}>;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Complete results of an accessibility test
|
|
57
|
+
*/
|
|
58
|
+
export interface AccessibilityTestResult {
|
|
59
|
+
/** URL that was tested (if applicable) */
|
|
60
|
+
url?: string;
|
|
61
|
+
/** Timestamp when the test was performed */
|
|
62
|
+
timestamp: string;
|
|
63
|
+
/** Duration of the test in milliseconds */
|
|
64
|
+
testDuration: number;
|
|
65
|
+
/** Violations found during testing */
|
|
66
|
+
violations: AccessibilityViolation[];
|
|
67
|
+
/** Rules that passed */
|
|
68
|
+
passes: AccessibilityRule[];
|
|
69
|
+
/** Summary statistics of the test */
|
|
70
|
+
summary: TestSummary;
|
|
71
|
+
/** Metadata about the test environment */
|
|
72
|
+
metadata: TestMetadata;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Represents an accessibility rule that was tested
|
|
76
|
+
*/
|
|
77
|
+
export interface AccessibilityRule {
|
|
78
|
+
/** Unique identifier for the rule */
|
|
79
|
+
id: string;
|
|
80
|
+
/** Human-readable description of the rule */
|
|
81
|
+
description: string;
|
|
82
|
+
/** Impact level if this rule fails */
|
|
83
|
+
impact?: ViolationSeverity;
|
|
84
|
+
/** Tags categorizing this rule */
|
|
85
|
+
tags: string[];
|
|
86
|
+
/** Nodes that were tested for this rule */
|
|
87
|
+
nodes: ViolationNode[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Summary statistics of test results
|
|
91
|
+
*/
|
|
92
|
+
export interface TestSummary {
|
|
93
|
+
/** Total number of violations found */
|
|
94
|
+
totalViolations: number;
|
|
95
|
+
/** Violations grouped by severity */
|
|
96
|
+
violationsBySeverity: Record<ViolationSeverity, number>;
|
|
97
|
+
/** Number of rules that passed */
|
|
98
|
+
passedRules: number;
|
|
99
|
+
/** Overall accessibility score (0-100) */
|
|
100
|
+
score: number;
|
|
101
|
+
/** Compliance status for each WCAG level */
|
|
102
|
+
compliance: Record<WCAGLevel, boolean>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Metadata about the test environment and configuration
|
|
106
|
+
*/
|
|
107
|
+
export interface TestMetadata {
|
|
108
|
+
/** User agent string used for testing */
|
|
109
|
+
userAgent: string;
|
|
110
|
+
/** Viewport dimensions */
|
|
111
|
+
viewport: {
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
};
|
|
115
|
+
/** WCAG level tested */
|
|
116
|
+
wcagLevel: WCAGLevel;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Color contrast analysis result
|
|
120
|
+
*/
|
|
121
|
+
export interface ColorContrastResult {
|
|
122
|
+
/** Foreground color value */
|
|
123
|
+
foreground: string;
|
|
124
|
+
/** Background color value */
|
|
125
|
+
background: string;
|
|
126
|
+
/** Calculated contrast ratio */
|
|
127
|
+
ratio: number;
|
|
128
|
+
/** WCAG AA compliance status */
|
|
129
|
+
wcagAA: {
|
|
130
|
+
/** Compliance for normal text */
|
|
131
|
+
normal: boolean;
|
|
132
|
+
/** Compliance for large text */
|
|
133
|
+
large: boolean;
|
|
134
|
+
};
|
|
135
|
+
/** WCAG AAA compliance status */
|
|
136
|
+
wcagAAA: {
|
|
137
|
+
/** Compliance for normal text */
|
|
138
|
+
normal: boolean;
|
|
139
|
+
/** Compliance for large text */
|
|
140
|
+
large: boolean;
|
|
141
|
+
};
|
|
142
|
+
/** Accessibility score for this color combination */
|
|
143
|
+
score: number;
|
|
144
|
+
/** Recommendations for improvement */
|
|
145
|
+
recommendations?: string[];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* WCAG rule information
|
|
149
|
+
*/
|
|
150
|
+
export interface WCAGRule {
|
|
151
|
+
/** Unique rule identifier */
|
|
152
|
+
id: string;
|
|
153
|
+
/** Human-readable title */
|
|
154
|
+
title: string;
|
|
155
|
+
/** Detailed description */
|
|
156
|
+
description: string;
|
|
157
|
+
/** WCAG level this rule applies to */
|
|
158
|
+
level: WCAGLevel;
|
|
159
|
+
/** WCAG versions where this rule exists */
|
|
160
|
+
versions: WCAGVersion[];
|
|
161
|
+
/** Categories this rule belongs to */
|
|
162
|
+
categories: AccessibilityCategory[];
|
|
163
|
+
/** Tags for filtering */
|
|
164
|
+
tags: string[];
|
|
165
|
+
/** Link to official WCAG documentation */
|
|
166
|
+
wcagUrl: string;
|
|
167
|
+
/** Whether this rule is automated or manual */
|
|
168
|
+
testType: 'automated' | 'manual' | 'semi-automated';
|
|
169
|
+
/** Success criteria reference */
|
|
170
|
+
successCriteria: string;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=accessibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/types/accessibility.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/types/accessibility.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,oBAAoB,CAAC;AAGnC,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,sBAAsB;AACtB,cAAc,oBAAoB,CAAC;AAEnC,YAAY;AACZ,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for MCP server inputs and outputs
|
|
3
|
+
*/
|
|
4
|
+
import { WCAGLevel } from './accessibility.js';
|
|
5
|
+
/**
|
|
6
|
+
* Input parameters for accessibility testing (unified URL/HTML)
|
|
7
|
+
*/
|
|
8
|
+
export interface TestAccessibilityInput {
|
|
9
|
+
/** Target to test - URL or HTML content */
|
|
10
|
+
target: string;
|
|
11
|
+
/** Type of target */
|
|
12
|
+
type: 'url' | 'html';
|
|
13
|
+
/** Testing depth level */
|
|
14
|
+
level: 'basic' | 'full';
|
|
15
|
+
/** WCAG compliance level to test against */
|
|
16
|
+
wcagLevel: WCAGLevel;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Input parameters for color contrast checking
|
|
20
|
+
*/
|
|
21
|
+
export interface ColorContrastInput {
|
|
22
|
+
/** Foreground color (hex, rgb, hsl, or named color) */
|
|
23
|
+
foreground: string;
|
|
24
|
+
/** Background color (hex, rgb, hsl, or named color) */
|
|
25
|
+
background: string;
|
|
26
|
+
/** Font size in pixels */
|
|
27
|
+
fontSize: number;
|
|
28
|
+
/** Whether the text is bold */
|
|
29
|
+
isBold: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Input parameters for getting WCAG rules information
|
|
33
|
+
*/
|
|
34
|
+
export interface GetWCAGRulesInput {
|
|
35
|
+
/** Filter by WCAG level */
|
|
36
|
+
wcagLevel?: WCAGLevel;
|
|
37
|
+
/** Filter by category */
|
|
38
|
+
category?: string;
|
|
39
|
+
/** Search term for rule titles/descriptions */
|
|
40
|
+
search?: string;
|
|
41
|
+
/** Maximum number of rules to return */
|
|
42
|
+
limit?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Standard error response structure
|
|
46
|
+
*/
|
|
47
|
+
export interface ErrorResponse {
|
|
48
|
+
/** Error type */
|
|
49
|
+
type: 'validation' | 'network' | 'timeout' | 'parsing' | 'internal';
|
|
50
|
+
/** Error message */
|
|
51
|
+
message: string;
|
|
52
|
+
/** Additional error details */
|
|
53
|
+
details?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Base response wrapper for all tool outputs
|
|
57
|
+
*/
|
|
58
|
+
export interface ToolResponse<T> {
|
|
59
|
+
/** Whether the operation was successful */
|
|
60
|
+
success: boolean;
|
|
61
|
+
/** Result data (if successful) */
|
|
62
|
+
data?: T;
|
|
63
|
+
/** Error information (if failed) */
|
|
64
|
+
error?: ErrorResponse;
|
|
65
|
+
/** Processing time in milliseconds */
|
|
66
|
+
processingTime: number;
|
|
67
|
+
/** Timestamp of the response */
|
|
68
|
+
timestamp: string;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/types/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,0BAA0B;IAC1B,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,4CAA4C;IAC5C,SAAS,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,2BAA2B;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iBAAiB;IACjB,IAAI,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACpE,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,2CAA2C;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,kCAAkC;IAClC,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,oCAAoC;IACpC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/types/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Browser, Page, PuppeteerLaunchOptions } from 'puppeteer';
|
|
2
|
+
/**
|
|
3
|
+
* Browser manager for handling Puppeteer browser instances
|
|
4
|
+
* Provides connection pooling, error handling, and resource cleanup
|
|
5
|
+
*/
|
|
6
|
+
export declare class BrowserManager {
|
|
7
|
+
private readonly logger;
|
|
8
|
+
private readonly browserPool;
|
|
9
|
+
private readonly maxBrowsers;
|
|
10
|
+
private isLaunching;
|
|
11
|
+
private readonly launchOptions;
|
|
12
|
+
constructor(options?: Partial<PuppeteerLaunchOptions & {
|
|
13
|
+
maxBrowsers?: number;
|
|
14
|
+
}>);
|
|
15
|
+
/**
|
|
16
|
+
* Get or create a browser instance from the pool
|
|
17
|
+
*/
|
|
18
|
+
getBrowser(): Promise<Browser>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new browser and add it to the pool
|
|
21
|
+
*/
|
|
22
|
+
private createNewBrowser;
|
|
23
|
+
/**
|
|
24
|
+
* Wait for browser launch to complete
|
|
25
|
+
*/
|
|
26
|
+
private waitForLaunch;
|
|
27
|
+
/**
|
|
28
|
+
* Wait for an available browser in the pool
|
|
29
|
+
*/
|
|
30
|
+
private waitForAvailableBrowser;
|
|
31
|
+
/**
|
|
32
|
+
* Remove disconnected browsers from pool
|
|
33
|
+
*/
|
|
34
|
+
private cleanupDisconnectedBrowsers;
|
|
35
|
+
/**
|
|
36
|
+
* Remove a specific browser from the pool
|
|
37
|
+
*/
|
|
38
|
+
private removeBrowserFromPool;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new page with standard configuration
|
|
41
|
+
*/
|
|
42
|
+
createPage(options?: {
|
|
43
|
+
viewport?: {
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
};
|
|
47
|
+
userAgent?: string;
|
|
48
|
+
timeout?: number;
|
|
49
|
+
}): Promise<Page>;
|
|
50
|
+
/**
|
|
51
|
+
* Navigate to a URL with retry logic
|
|
52
|
+
*/
|
|
53
|
+
navigateToURL(page: Page, url: string, options?: {
|
|
54
|
+
waitUntil?: 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
|
|
55
|
+
timeout?: number;
|
|
56
|
+
retries?: number;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Close a page safely
|
|
60
|
+
*/
|
|
61
|
+
closePage(page: Page): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Clean up all browser resources
|
|
64
|
+
*/
|
|
65
|
+
cleanup(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Get browser pool status
|
|
68
|
+
*/
|
|
69
|
+
getPoolStatus(): {
|
|
70
|
+
total: number;
|
|
71
|
+
connected: number;
|
|
72
|
+
available: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Get browser information
|
|
76
|
+
*/
|
|
77
|
+
getBrowserInfo(): Promise<{
|
|
78
|
+
version: string;
|
|
79
|
+
userAgent: string;
|
|
80
|
+
poolStatus: any;
|
|
81
|
+
} | null>;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=browser-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-manager.d.ts","sourceRoot":"","sources":["../../src/utils/browser-manager.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,OAAO,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAG7E;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;gBAE3C,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,GAAG;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAuBhF;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAwBpC;;OAEG;YACW,gBAAgB;IA8B9B;;OAEG;YACW,aAAa;IAe3B;;OAEG;YACW,uBAAuB;IAqBrC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAcnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAQ7B;;OAEG;IACG,UAAU,CAAC,OAAO,CAAC,EAAE;QACzB,QAAQ,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDjB;;OAEG;IACG,aAAa,CACjB,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,cAAc,GAAG,cAAc,CAAC;QAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GACA,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB9B;;OAEG;IACH,aAAa,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAWxE;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,GAAG,CAAA;KAAE,GAAG,IAAI,CAAC;CAkBhG"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer';
|
|
2
|
+
import { Logger } from './logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Browser manager for handling Puppeteer browser instances
|
|
5
|
+
* Provides connection pooling, error handling, and resource cleanup
|
|
6
|
+
*/
|
|
7
|
+
export class BrowserManager {
|
|
8
|
+
logger;
|
|
9
|
+
browserPool = [];
|
|
10
|
+
maxBrowsers = 3;
|
|
11
|
+
isLaunching = false;
|
|
12
|
+
launchOptions;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.logger = new Logger().child({ component: 'BrowserManager' });
|
|
15
|
+
this.maxBrowsers = options?.maxBrowsers ?? 3;
|
|
16
|
+
this.launchOptions = {
|
|
17
|
+
headless: 'new',
|
|
18
|
+
args: [
|
|
19
|
+
'--no-sandbox',
|
|
20
|
+
'--disable-setuid-sandbox',
|
|
21
|
+
'--disable-dev-shm-usage',
|
|
22
|
+
'--disable-gpu',
|
|
23
|
+
'--no-first-run',
|
|
24
|
+
'--disable-extensions',
|
|
25
|
+
'--disable-default-apps',
|
|
26
|
+
'--disable-background-timer-throttling',
|
|
27
|
+
'--disable-backgrounding-occluded-windows',
|
|
28
|
+
'--disable-renderer-backgrounding'
|
|
29
|
+
],
|
|
30
|
+
timeout: 30000,
|
|
31
|
+
...options
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get or create a browser instance from the pool
|
|
36
|
+
*/
|
|
37
|
+
async getBrowser() {
|
|
38
|
+
// Try to find an available browser in the pool
|
|
39
|
+
const availableBrowser = this.browserPool.find(browser => browser.connected);
|
|
40
|
+
if (availableBrowser) {
|
|
41
|
+
this.logger.debug('Reusing browser from pool');
|
|
42
|
+
return availableBrowser;
|
|
43
|
+
}
|
|
44
|
+
// Remove disconnected browsers from pool
|
|
45
|
+
this.cleanupDisconnectedBrowsers();
|
|
46
|
+
// Create new browser if pool is not full
|
|
47
|
+
if (this.browserPool.length < this.maxBrowsers) {
|
|
48
|
+
return this.createNewBrowser();
|
|
49
|
+
}
|
|
50
|
+
// Wait for a browser to become available if pool is full
|
|
51
|
+
this.logger.warn('Browser pool is full, waiting for available browser');
|
|
52
|
+
return this.waitForAvailableBrowser();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a new browser and add it to the pool
|
|
56
|
+
*/
|
|
57
|
+
async createNewBrowser() {
|
|
58
|
+
// Prevent multiple launch attempts
|
|
59
|
+
if (this.isLaunching) {
|
|
60
|
+
await this.waitForLaunch();
|
|
61
|
+
return this.getBrowser();
|
|
62
|
+
}
|
|
63
|
+
this.isLaunching = true;
|
|
64
|
+
try {
|
|
65
|
+
this.logger.info('Creating new browser instance');
|
|
66
|
+
const browser = await puppeteer.launch(this.launchOptions);
|
|
67
|
+
// Set up error handlers
|
|
68
|
+
browser.on('disconnected', () => {
|
|
69
|
+
this.logger.warn('Browser disconnected');
|
|
70
|
+
this.removeBrowserFromPool(browser);
|
|
71
|
+
});
|
|
72
|
+
this.browserPool.push(browser);
|
|
73
|
+
this.logger.info(`Browser created successfully. Pool size: ${this.browserPool.length}`);
|
|
74
|
+
return browser;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.logger.error('Failed to launch browser', error instanceof Error ? error : new Error(String(error)));
|
|
78
|
+
throw new Error(`Browser launch failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.isLaunching = false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait for browser launch to complete
|
|
86
|
+
*/
|
|
87
|
+
async waitForLaunch() {
|
|
88
|
+
const maxWait = 30000; // 30 seconds
|
|
89
|
+
const checkInterval = 100; // 100ms
|
|
90
|
+
let waited = 0;
|
|
91
|
+
while (this.isLaunching && waited < maxWait) {
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
93
|
+
waited += checkInterval;
|
|
94
|
+
}
|
|
95
|
+
if (waited >= maxWait) {
|
|
96
|
+
throw new Error('Timeout waiting for browser to launch');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Wait for an available browser in the pool
|
|
101
|
+
*/
|
|
102
|
+
async waitForAvailableBrowser() {
|
|
103
|
+
const maxWait = 10000; // 10 seconds
|
|
104
|
+
const checkInterval = 500; // 500ms
|
|
105
|
+
let waited = 0;
|
|
106
|
+
while (waited < maxWait) {
|
|
107
|
+
const availableBrowser = this.browserPool.find(browser => browser.connected);
|
|
108
|
+
if (availableBrowser) {
|
|
109
|
+
return availableBrowser;
|
|
110
|
+
}
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
|
112
|
+
waited += checkInterval;
|
|
113
|
+
}
|
|
114
|
+
throw new Error('Timeout waiting for available browser in pool');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Remove disconnected browsers from pool
|
|
118
|
+
*/
|
|
119
|
+
cleanupDisconnectedBrowsers() {
|
|
120
|
+
const initialSize = this.browserPool.length;
|
|
121
|
+
for (let i = this.browserPool.length - 1; i >= 0; i--) {
|
|
122
|
+
const browser = this.browserPool[i];
|
|
123
|
+
if (browser && !browser.connected) {
|
|
124
|
+
this.browserPool.splice(i, 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (this.browserPool.length !== initialSize) {
|
|
128
|
+
this.logger.debug(`Cleaned up ${initialSize - this.browserPool.length} disconnected browsers`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove a specific browser from the pool
|
|
133
|
+
*/
|
|
134
|
+
removeBrowserFromPool(browser) {
|
|
135
|
+
const index = this.browserPool.indexOf(browser);
|
|
136
|
+
if (index > -1) {
|
|
137
|
+
this.browserPool.splice(index, 1);
|
|
138
|
+
this.logger.debug(`Removed browser from pool. Pool size: ${this.browserPool.length}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a new page with standard configuration
|
|
143
|
+
*/
|
|
144
|
+
async createPage(options) {
|
|
145
|
+
const browser = await this.getBrowser();
|
|
146
|
+
const page = await browser.newPage();
|
|
147
|
+
try {
|
|
148
|
+
// Set viewport
|
|
149
|
+
if (options?.viewport) {
|
|
150
|
+
await page.setViewport(options.viewport);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
await page.setViewport({ width: 1920, height: 1080 });
|
|
154
|
+
}
|
|
155
|
+
// Set user agent if provided
|
|
156
|
+
if (options?.userAgent) {
|
|
157
|
+
await page.setUserAgent(options.userAgent);
|
|
158
|
+
}
|
|
159
|
+
// Set timeouts
|
|
160
|
+
const timeout = options?.timeout ?? 30000;
|
|
161
|
+
page.setDefaultTimeout(timeout);
|
|
162
|
+
page.setDefaultNavigationTimeout(timeout);
|
|
163
|
+
// Block only heavy media to speed up loading but keep essential resources
|
|
164
|
+
await page.setRequestInterception(true);
|
|
165
|
+
page.on('request', (req) => {
|
|
166
|
+
const resourceType = req.resourceType();
|
|
167
|
+
// Only block heavy media files, keep CSS and fonts for proper accessibility testing
|
|
168
|
+
if (['image', 'media'].includes(resourceType)) {
|
|
169
|
+
req.abort();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
req.continue();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Add error handling
|
|
176
|
+
page.on('error', (error) => {
|
|
177
|
+
this.logger.error('Page error occurred', error);
|
|
178
|
+
});
|
|
179
|
+
page.on('pageerror', (error) => {
|
|
180
|
+
this.logger.error('Page script error', error);
|
|
181
|
+
});
|
|
182
|
+
this.logger.debug('Created new page');
|
|
183
|
+
return page;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
await page.close();
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Navigate to a URL with retry logic
|
|
192
|
+
*/
|
|
193
|
+
async navigateToURL(page, url, options) {
|
|
194
|
+
const maxRetries = options?.retries ?? 3;
|
|
195
|
+
const waitUntil = options?.waitUntil ?? 'networkidle2';
|
|
196
|
+
const timeout = options?.timeout ?? 30000;
|
|
197
|
+
let lastError = null;
|
|
198
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
199
|
+
try {
|
|
200
|
+
this.logger.debug(`Navigating to URL (attempt ${attempt}/${maxRetries})`, { url });
|
|
201
|
+
await page.goto(url, {
|
|
202
|
+
waitUntil,
|
|
203
|
+
timeout
|
|
204
|
+
});
|
|
205
|
+
this.logger.debug('Successfully navigated to URL', { url });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
210
|
+
this.logger.warn(`Navigation attempt ${attempt} failed`, {
|
|
211
|
+
url,
|
|
212
|
+
error: lastError.message,
|
|
213
|
+
attempt,
|
|
214
|
+
maxRetries
|
|
215
|
+
});
|
|
216
|
+
if (attempt < maxRetries) {
|
|
217
|
+
// Wait before retrying
|
|
218
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
throw new Error(`Failed to navigate to ${url} after ${maxRetries} attempts: ${lastError?.message ?? 'Unknown error'}`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Close a page safely
|
|
226
|
+
*/
|
|
227
|
+
async closePage(page) {
|
|
228
|
+
try {
|
|
229
|
+
if (!page.isClosed()) {
|
|
230
|
+
await page.close();
|
|
231
|
+
this.logger.debug('Page closed successfully');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
this.logger.warn('Error closing page', { error: String(error) });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Clean up all browser resources
|
|
240
|
+
*/
|
|
241
|
+
async cleanup() {
|
|
242
|
+
this.logger.info(`Cleaning up ${this.browserPool.length} browsers`);
|
|
243
|
+
const cleanupPromises = this.browserPool.map(async (browser, index) => {
|
|
244
|
+
try {
|
|
245
|
+
if (browser.connected) {
|
|
246
|
+
await browser.close();
|
|
247
|
+
this.logger.debug(`Browser ${index} closed successfully`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.logger.warn(`Error closing browser ${index}`, { error: String(error) });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
await Promise.allSettled(cleanupPromises);
|
|
255
|
+
this.browserPool.length = 0; // Clear the pool
|
|
256
|
+
this.logger.info('All browsers cleaned up');
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Get browser pool status
|
|
260
|
+
*/
|
|
261
|
+
getPoolStatus() {
|
|
262
|
+
const total = this.browserPool.length;
|
|
263
|
+
const connected = this.browserPool.filter(browser => browser.connected).length;
|
|
264
|
+
return {
|
|
265
|
+
total,
|
|
266
|
+
connected,
|
|
267
|
+
available: this.maxBrowsers - total
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get browser information
|
|
272
|
+
*/
|
|
273
|
+
async getBrowserInfo() {
|
|
274
|
+
try {
|
|
275
|
+
const browser = await this.getBrowser();
|
|
276
|
+
const version = await browser.version();
|
|
277
|
+
const page = await this.createPage();
|
|
278
|
+
const userAgent = await page.evaluate(() => navigator.userAgent);
|
|
279
|
+
await this.closePage(page);
|
|
280
|
+
return {
|
|
281
|
+
version,
|
|
282
|
+
userAgent,
|
|
283
|
+
poolStatus: this.getPoolStatus()
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
this.logger.error('Failed to get browser info', error instanceof Error ? error : new Error(String(error)));
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=browser-manager.js.map
|