@webability/core 1.1.3 → 1.2.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/dist/index.cjs +206 -63
- package/dist/index.d.cts +83 -1
- package/dist/index.d.ts +83 -1
- package/dist/index.js +206 -63
- package/package.json +15 -12
package/dist/index.d.cts
CHANGED
|
@@ -11,6 +11,18 @@ interface PageLike {
|
|
|
11
11
|
content: string;
|
|
12
12
|
}): Promise<unknown>;
|
|
13
13
|
url(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Real keyboard event dispatch (Playwright & Puppeteer both expose this).
|
|
16
|
+
* Optional so minimal PageLike shims still typecheck. The focusVisible
|
|
17
|
+
* detector uses `keyboard.press('Tab')` to put the browser into keyboard
|
|
18
|
+
* interaction modality before probing focus styles — otherwise
|
|
19
|
+
* `:focus-visible` / keyboard-detection-class patterns (focus-visible
|
|
20
|
+
* polyfill, `.user-is-tabbing` body class) never activate under a bare
|
|
21
|
+
* programmatic `.focus()`, producing mass false positives.
|
|
22
|
+
*/
|
|
23
|
+
keyboard?: {
|
|
24
|
+
press(key: string): Promise<void>;
|
|
25
|
+
};
|
|
14
26
|
}
|
|
15
27
|
/** All fix issue types the pipeline can detect and fix. */
|
|
16
28
|
type FixIssueType = 'weak_button_name' | 'weak_link_name' | 'weak_heading' | 'weak_iframe_title' | 'weak_page_title' | 'weak_svg_name' | 'weak_skip_link' | 'missing_button_role' | 'missing_semantic_role' | 'missing_landmark' | 'heading_skip' | 'svg_missing_name' | 'aria_hidden_focusable' | 'adjacent_redundant_link' | 'contrast_insufficient' | 'non_text_contrast_insufficient' | 'target_too_small' | 'focus_not_visible' | 'empty_link' | 'unlabeled_button' | 'missing_label' | 'decorative_icon' | 'skip_link' | 'aria_live' | 'missing_alt' | 'identify_input_purpose' | 'redundant_role' | 'duplicate_id' | 'new_window_link' | 'missing_fieldset_legend' | 'missing_table_header' | 'missing_table_caption' | 'missing_table_scope' | 'external_link_warning' | 'download_link_warning' | 'broken_anchor' | 'placeholder_link' | 'missing_aria_required' | 'missing_pattern_title' | 'missing_autocomplete' | 'missing_media_controls' | 'unsafe_autoplay' | 'invalid_aria_role' | 'zoom_restriction' | 'missing_button_type' | 'link_consistency' | 'semantic_emphasis' | 'keyboard_trap' | 'focus_order_disruption' | 'missing_captions' | 'color_only_link' | 'label_in_name' | 'onfocus_context_change' | 'focus_obscured_by_sticky' | 'text_spacing_blocked' | 'nested_landmark' | 'combobox_name_missing' | 'landmark_duplicate_unlabeled' | 'motion_uncontrollable' | 'auto_refresh' | 'motion_without_reduced_motion' | 'flashing_content' | 'animated_gif_no_pause' | 'missing_required_indicator' | 'background_image_unlabeled' | 'missing_h1' | 'multiple_h1' | 'empty_heading' | 'javascript_link' | 'broken_aria_reference' | 'main_content_hidden' | 'scrollable_not_focusable' | 'meaningful_sequence' | 'sensory_characteristic' | 'orientation_lock' | 'image_of_text' | 'reflow_overflow' | 'missing_audio_description' | 'missing_transcript' | 'character_key_shortcut' | 'on_input_change' | 'pointer_gesture' | 'pointer_cancellation' | 'motion_actuation' | 'error_prevention_missing' | 'insufficient_navigation' | 'inconsistent_navigation';
|
|
@@ -52,6 +64,11 @@ interface DetectedIssue {
|
|
|
52
64
|
interface FixDetector {
|
|
53
65
|
name: string;
|
|
54
66
|
detect(page: PageLike): Promise<DetectedIssue[]>;
|
|
67
|
+
/** Per-page emission cap, when the detector has one. When a run returns
|
|
68
|
+
* >= cap findings the results are TRUNCATED (DOM-order biased) and the
|
|
69
|
+
* scan reports it via ScanResult.truncated — silent truncation reads as
|
|
70
|
+
* "covered everything" when it didn't (loop iter8 / honesty infra). */
|
|
71
|
+
cap?: number;
|
|
55
72
|
}
|
|
56
73
|
/**
|
|
57
74
|
* Browser-runtime detector signature.
|
|
@@ -111,11 +128,50 @@ interface ScanIssue {
|
|
|
111
128
|
needsManualReview: boolean;
|
|
112
129
|
};
|
|
113
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Signal that the scan never reached the intended page — the target served a
|
|
133
|
+
* bot-challenge / interstitial (Cloudflare "Just a moment…", "Access Denied",
|
|
134
|
+
* a CAPTCHA wall) or the main document returned an HTTP error status. When
|
|
135
|
+
* present, `issues`/`incomplete` are empty and MUST NOT be read as "the page is
|
|
136
|
+
* clean": nothing real was scanned. Lets a caller distinguish "scanned and
|
|
137
|
+
* clean" from "was never actually scanned".
|
|
138
|
+
*/
|
|
139
|
+
interface BlockedInfo {
|
|
140
|
+
/** Human-readable explanation of why the page could not be scanned. */
|
|
141
|
+
reason: string;
|
|
142
|
+
/** Challenge vendor when identifiable (e.g. 'Cloudflare', 'PerimeterX/HUMAN'). */
|
|
143
|
+
vendor?: string;
|
|
144
|
+
/** HTTP status of the main document response, when known. */
|
|
145
|
+
status?: number;
|
|
146
|
+
/** The specific signal that matched (page title, marker selector, or status). */
|
|
147
|
+
signal?: string;
|
|
148
|
+
}
|
|
114
149
|
interface ScanResult {
|
|
115
150
|
url: string;
|
|
116
151
|
timestamp: string;
|
|
152
|
+
/**
|
|
153
|
+
* Set when the target served a bot-challenge/interstitial or an HTTP error
|
|
154
|
+
* instead of the real page. When present, treat `issues`/`incomplete` as
|
|
155
|
+
* "not scanned" (they are empty), NOT as "no problems found".
|
|
156
|
+
*/
|
|
157
|
+
blocked?: BlockedInfo;
|
|
117
158
|
/** High-confidence violations. Safe to surface as bugs. */
|
|
118
159
|
issues: ScanIssue[];
|
|
160
|
+
/** Detectors that hit their per-page cap this run — their counts are lower
|
|
161
|
+
* bounds, not a census. Additive field; absent = nothing truncated. */
|
|
162
|
+
truncated?: Array<{
|
|
163
|
+
detector: string;
|
|
164
|
+
cap: number;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Non-fatal engine subsystem failures that silently disable a category of
|
|
168
|
+
* checks. Consumers MUST surface these to the user — a result with
|
|
169
|
+
* `engineWarnings` is NOT a full scan. Currently: `'color:kernel-injection-failed'`
|
|
170
|
+
* disables all contrast checks for the page. Absent = all subsystems healthy.
|
|
171
|
+
*/
|
|
172
|
+
engineWarnings?: string[];
|
|
173
|
+
/** Engine (core package) version that produced this result — provenance */
|
|
174
|
+
engine?: string;
|
|
119
175
|
/**
|
|
120
176
|
* Findings the classifier or axe-core flagged as ambiguous. Show to the user
|
|
121
177
|
* for review — never auto-fix. Empty array (not omitted) when no incompletes,
|
|
@@ -160,11 +216,37 @@ interface ScanOptions {
|
|
|
160
216
|
enableScreenshots?: boolean;
|
|
161
217
|
/** Scan content inside iframes. Default: false. */
|
|
162
218
|
includeIframes?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Wait for entrance animations to finish before scanning (prevents false
|
|
221
|
+
* contrast failures on mid-fade text). `false` disables, a number sets the
|
|
222
|
+
* wait cap in ms. Default: enabled with a 4000ms cap.
|
|
223
|
+
*/
|
|
224
|
+
settle?: boolean | number;
|
|
225
|
+
/**
|
|
226
|
+
* HTTP status of the main-document response, for the Page-object call shape
|
|
227
|
+
* where the CALLER navigated (e.g. mcp's flow_scan does its own `page.goto`).
|
|
228
|
+
* The URL-string path captures this itself; on the Page path core cannot see
|
|
229
|
+
* the response, so a plain 4xx/5xx error page with no challenge DOM markers
|
|
230
|
+
* would otherwise be scanned as a clean page. Pass the caller's
|
|
231
|
+
* `response.status()` here so bot-challenge/error-page detection works
|
|
232
|
+
* uniformly across both entrypoints. Ignored on the URL-string path.
|
|
233
|
+
*/
|
|
234
|
+
mainStatus?: number;
|
|
163
235
|
}
|
|
164
236
|
|
|
165
237
|
declare function scan(target: string | PageLike, options?: ScanOptions): Promise<ScanResult>;
|
|
166
238
|
|
|
167
|
-
|
|
239
|
+
type TruncationEntry = {
|
|
240
|
+
detector: string;
|
|
241
|
+
cap: number;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* `outTruncated` collects which detectors hit their per-page cap THIS call.
|
|
245
|
+
* `outWarnings` collects non-fatal engine subsystem failures (e.g. kernel
|
|
246
|
+
* injection blocked by CSP) that silently disable a category of checks.
|
|
247
|
+
* Both arrays are per-invocation (caller-owned), never module state.
|
|
248
|
+
*/
|
|
249
|
+
declare function runDetectors(page: PageLike, outTruncated?: TruncationEntry[], outWarnings?: string[]): Promise<DetectedIssue[]>;
|
|
168
250
|
declare function extractSiteTheme(page: PageLike): Promise<SiteTheme>;
|
|
169
251
|
|
|
170
252
|
interface AxeViolation {
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ interface PageLike {
|
|
|
11
11
|
content: string;
|
|
12
12
|
}): Promise<unknown>;
|
|
13
13
|
url(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Real keyboard event dispatch (Playwright & Puppeteer both expose this).
|
|
16
|
+
* Optional so minimal PageLike shims still typecheck. The focusVisible
|
|
17
|
+
* detector uses `keyboard.press('Tab')` to put the browser into keyboard
|
|
18
|
+
* interaction modality before probing focus styles — otherwise
|
|
19
|
+
* `:focus-visible` / keyboard-detection-class patterns (focus-visible
|
|
20
|
+
* polyfill, `.user-is-tabbing` body class) never activate under a bare
|
|
21
|
+
* programmatic `.focus()`, producing mass false positives.
|
|
22
|
+
*/
|
|
23
|
+
keyboard?: {
|
|
24
|
+
press(key: string): Promise<void>;
|
|
25
|
+
};
|
|
14
26
|
}
|
|
15
27
|
/** All fix issue types the pipeline can detect and fix. */
|
|
16
28
|
type FixIssueType = 'weak_button_name' | 'weak_link_name' | 'weak_heading' | 'weak_iframe_title' | 'weak_page_title' | 'weak_svg_name' | 'weak_skip_link' | 'missing_button_role' | 'missing_semantic_role' | 'missing_landmark' | 'heading_skip' | 'svg_missing_name' | 'aria_hidden_focusable' | 'adjacent_redundant_link' | 'contrast_insufficient' | 'non_text_contrast_insufficient' | 'target_too_small' | 'focus_not_visible' | 'empty_link' | 'unlabeled_button' | 'missing_label' | 'decorative_icon' | 'skip_link' | 'aria_live' | 'missing_alt' | 'identify_input_purpose' | 'redundant_role' | 'duplicate_id' | 'new_window_link' | 'missing_fieldset_legend' | 'missing_table_header' | 'missing_table_caption' | 'missing_table_scope' | 'external_link_warning' | 'download_link_warning' | 'broken_anchor' | 'placeholder_link' | 'missing_aria_required' | 'missing_pattern_title' | 'missing_autocomplete' | 'missing_media_controls' | 'unsafe_autoplay' | 'invalid_aria_role' | 'zoom_restriction' | 'missing_button_type' | 'link_consistency' | 'semantic_emphasis' | 'keyboard_trap' | 'focus_order_disruption' | 'missing_captions' | 'color_only_link' | 'label_in_name' | 'onfocus_context_change' | 'focus_obscured_by_sticky' | 'text_spacing_blocked' | 'nested_landmark' | 'combobox_name_missing' | 'landmark_duplicate_unlabeled' | 'motion_uncontrollable' | 'auto_refresh' | 'motion_without_reduced_motion' | 'flashing_content' | 'animated_gif_no_pause' | 'missing_required_indicator' | 'background_image_unlabeled' | 'missing_h1' | 'multiple_h1' | 'empty_heading' | 'javascript_link' | 'broken_aria_reference' | 'main_content_hidden' | 'scrollable_not_focusable' | 'meaningful_sequence' | 'sensory_characteristic' | 'orientation_lock' | 'image_of_text' | 'reflow_overflow' | 'missing_audio_description' | 'missing_transcript' | 'character_key_shortcut' | 'on_input_change' | 'pointer_gesture' | 'pointer_cancellation' | 'motion_actuation' | 'error_prevention_missing' | 'insufficient_navigation' | 'inconsistent_navigation';
|
|
@@ -52,6 +64,11 @@ interface DetectedIssue {
|
|
|
52
64
|
interface FixDetector {
|
|
53
65
|
name: string;
|
|
54
66
|
detect(page: PageLike): Promise<DetectedIssue[]>;
|
|
67
|
+
/** Per-page emission cap, when the detector has one. When a run returns
|
|
68
|
+
* >= cap findings the results are TRUNCATED (DOM-order biased) and the
|
|
69
|
+
* scan reports it via ScanResult.truncated — silent truncation reads as
|
|
70
|
+
* "covered everything" when it didn't (loop iter8 / honesty infra). */
|
|
71
|
+
cap?: number;
|
|
55
72
|
}
|
|
56
73
|
/**
|
|
57
74
|
* Browser-runtime detector signature.
|
|
@@ -111,11 +128,50 @@ interface ScanIssue {
|
|
|
111
128
|
needsManualReview: boolean;
|
|
112
129
|
};
|
|
113
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Signal that the scan never reached the intended page — the target served a
|
|
133
|
+
* bot-challenge / interstitial (Cloudflare "Just a moment…", "Access Denied",
|
|
134
|
+
* a CAPTCHA wall) or the main document returned an HTTP error status. When
|
|
135
|
+
* present, `issues`/`incomplete` are empty and MUST NOT be read as "the page is
|
|
136
|
+
* clean": nothing real was scanned. Lets a caller distinguish "scanned and
|
|
137
|
+
* clean" from "was never actually scanned".
|
|
138
|
+
*/
|
|
139
|
+
interface BlockedInfo {
|
|
140
|
+
/** Human-readable explanation of why the page could not be scanned. */
|
|
141
|
+
reason: string;
|
|
142
|
+
/** Challenge vendor when identifiable (e.g. 'Cloudflare', 'PerimeterX/HUMAN'). */
|
|
143
|
+
vendor?: string;
|
|
144
|
+
/** HTTP status of the main document response, when known. */
|
|
145
|
+
status?: number;
|
|
146
|
+
/** The specific signal that matched (page title, marker selector, or status). */
|
|
147
|
+
signal?: string;
|
|
148
|
+
}
|
|
114
149
|
interface ScanResult {
|
|
115
150
|
url: string;
|
|
116
151
|
timestamp: string;
|
|
152
|
+
/**
|
|
153
|
+
* Set when the target served a bot-challenge/interstitial or an HTTP error
|
|
154
|
+
* instead of the real page. When present, treat `issues`/`incomplete` as
|
|
155
|
+
* "not scanned" (they are empty), NOT as "no problems found".
|
|
156
|
+
*/
|
|
157
|
+
blocked?: BlockedInfo;
|
|
117
158
|
/** High-confidence violations. Safe to surface as bugs. */
|
|
118
159
|
issues: ScanIssue[];
|
|
160
|
+
/** Detectors that hit their per-page cap this run — their counts are lower
|
|
161
|
+
* bounds, not a census. Additive field; absent = nothing truncated. */
|
|
162
|
+
truncated?: Array<{
|
|
163
|
+
detector: string;
|
|
164
|
+
cap: number;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Non-fatal engine subsystem failures that silently disable a category of
|
|
168
|
+
* checks. Consumers MUST surface these to the user — a result with
|
|
169
|
+
* `engineWarnings` is NOT a full scan. Currently: `'color:kernel-injection-failed'`
|
|
170
|
+
* disables all contrast checks for the page. Absent = all subsystems healthy.
|
|
171
|
+
*/
|
|
172
|
+
engineWarnings?: string[];
|
|
173
|
+
/** Engine (core package) version that produced this result — provenance */
|
|
174
|
+
engine?: string;
|
|
119
175
|
/**
|
|
120
176
|
* Findings the classifier or axe-core flagged as ambiguous. Show to the user
|
|
121
177
|
* for review — never auto-fix. Empty array (not omitted) when no incompletes,
|
|
@@ -160,11 +216,37 @@ interface ScanOptions {
|
|
|
160
216
|
enableScreenshots?: boolean;
|
|
161
217
|
/** Scan content inside iframes. Default: false. */
|
|
162
218
|
includeIframes?: boolean;
|
|
219
|
+
/**
|
|
220
|
+
* Wait for entrance animations to finish before scanning (prevents false
|
|
221
|
+
* contrast failures on mid-fade text). `false` disables, a number sets the
|
|
222
|
+
* wait cap in ms. Default: enabled with a 4000ms cap.
|
|
223
|
+
*/
|
|
224
|
+
settle?: boolean | number;
|
|
225
|
+
/**
|
|
226
|
+
* HTTP status of the main-document response, for the Page-object call shape
|
|
227
|
+
* where the CALLER navigated (e.g. mcp's flow_scan does its own `page.goto`).
|
|
228
|
+
* The URL-string path captures this itself; on the Page path core cannot see
|
|
229
|
+
* the response, so a plain 4xx/5xx error page with no challenge DOM markers
|
|
230
|
+
* would otherwise be scanned as a clean page. Pass the caller's
|
|
231
|
+
* `response.status()` here so bot-challenge/error-page detection works
|
|
232
|
+
* uniformly across both entrypoints. Ignored on the URL-string path.
|
|
233
|
+
*/
|
|
234
|
+
mainStatus?: number;
|
|
163
235
|
}
|
|
164
236
|
|
|
165
237
|
declare function scan(target: string | PageLike, options?: ScanOptions): Promise<ScanResult>;
|
|
166
238
|
|
|
167
|
-
|
|
239
|
+
type TruncationEntry = {
|
|
240
|
+
detector: string;
|
|
241
|
+
cap: number;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* `outTruncated` collects which detectors hit their per-page cap THIS call.
|
|
245
|
+
* `outWarnings` collects non-fatal engine subsystem failures (e.g. kernel
|
|
246
|
+
* injection blocked by CSP) that silently disable a category of checks.
|
|
247
|
+
* Both arrays are per-invocation (caller-owned), never module state.
|
|
248
|
+
*/
|
|
249
|
+
declare function runDetectors(page: PageLike, outTruncated?: TruncationEntry[], outWarnings?: string[]): Promise<DetectedIssue[]>;
|
|
168
250
|
declare function extractSiteTheme(page: PageLike): Promise<SiteTheme>;
|
|
169
251
|
|
|
170
252
|
interface AxeViolation {
|