@surea11y/webdriverio 1.0.0 → 1.0.1
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 +35 -40
- package/package.json +1 -1
- package/src/A11yCoreBuilder.d.ts +1 -1
- package/src/A11yCoreBuilder.js +14 -14
- package/src/formatFailures.js +3 -4
package/README.md
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
# surea11y
|
|
1
|
+
# @surea11y/webdriverio
|
|
2
2
|
|
|
3
|
-
A WebdriverIO binding for [
|
|
3
|
+
A WebdriverIO binding for [`@surea11y/core`](https://github.com/rumoroso/surea11y-core) — scans a real, already-rendered page for accessibility issues using surea11y's DOM-rules engine.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Install (local development)
|
|
8
|
-
|
|
9
|
-
`surea11y` isn't published to npm yet, so this package depends on it via a relative `file:` path (see `package.json`):
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
"dependencies": { "surea11y": "file:../core" }
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
That means this project must stay a sibling of `surea11y` (or you update the path) for `npm install` to resolve it.
|
|
5
|
+
## Install
|
|
16
6
|
|
|
17
7
|
```bash
|
|
18
|
-
npm install
|
|
19
|
-
npm test
|
|
8
|
+
npm install @surea11y/webdriverio webdriverio
|
|
20
9
|
```
|
|
21
10
|
|
|
22
|
-
`webdriverio`
|
|
11
|
+
`webdriverio` v9 auto-manages a local `chromedriver` for you the first time it launches Chrome (no separate Selenium server, and no extra browser-download step needed) — so a fresh `npm install` is enough for `npm test` to launch a real headless Chrome on its own.
|
|
23
12
|
|
|
24
13
|
## Usage
|
|
25
14
|
|
|
26
15
|
```js
|
|
27
16
|
const { remote } = require('webdriverio');
|
|
28
|
-
const { A11yCoreBuilder } = require('surea11y
|
|
17
|
+
const { A11yCoreBuilder } = require('@surea11y/webdriverio');
|
|
29
18
|
|
|
30
19
|
const browser = await remote({
|
|
31
20
|
capabilities: {
|
|
@@ -50,17 +39,17 @@ await browser.deleteSession();
|
|
|
50
39
|
|
|
51
40
|
The builder takes `{ browser }` — the object WebdriverIO's `remote()` returns (standalone mode), or the global `browser` the WDIO testrunner injects (see the E2E section). It must already be navigated to and settled at the URL to scan; the class does not navigate for you.
|
|
52
41
|
|
|
53
|
-
`results` is surea11y's own native result shape — see [
|
|
42
|
+
`results` is `@surea11y/core`'s own native result shape — see its [`OUTPUT_SCHEMA.md`](https://github.com/rumoroso/surea11y-core/blob/main/docs/OUTPUT_SCHEMA.md) — not the `violations`/`passes`/`incomplete`/`inapplicable` shape used by other popular accessibility testing tools. The builder's *method names* are modeled on common conventions in this space for migration familiarity; the richer result schema is kept as-is.
|
|
54
43
|
|
|
55
44
|
Also see `examples/basic-scan.js` for a runnable script (`npm run example -- <url>`).
|
|
56
45
|
|
|
57
46
|
### A note on the protocol: prefer classic WebDriver for this workload
|
|
58
47
|
|
|
59
|
-
WebdriverIO v9 defaults to the WebDriver **BiDi** protocol. A long-lived session that issues many `browser.execute()` calls — exactly what an accessibility scan does — degraded badly over BiDi in testing here: per-scan latency climbed from ~2s into the *minutes* and eventually the session started dropping commands (`Couldn't resolve command with id N`). Forcing the **classic** WebDriver protocol with `'wdio:enforceWebDriverClassic': true` in the capabilities gave a steady ~150ms per scan with no degradation, and every operation this binding needs (`execute`, `$`, `$$`, `switchFrame`) is fully supported on it. This project's own tests and examples all set that flag, and it's recommended for yours. The binding itself works under either protocol (it never depends on a BiDi-only feature)
|
|
48
|
+
WebdriverIO v9 defaults to the WebDriver **BiDi** protocol. A long-lived session that issues many `browser.execute()` calls — exactly what an accessibility scan does — degraded badly over BiDi in testing here: per-scan latency climbed from ~2s into the *minutes* and eventually the session started dropping commands (`Couldn't resolve command with id N`). Forcing the **classic** WebDriver protocol with `'wdio:enforceWebDriverClassic': true` in the capabilities gave a steady ~150ms per scan with no degradation, and every operation this binding needs (`execute`, `$`, `$$`, `switchFrame`) is fully supported on it. This project's own tests and examples all set that flag, and it's recommended for yours. The binding itself works under either protocol (it never depends on a BiDi-only feature).
|
|
60
49
|
|
|
61
50
|
### Composing the rule filters
|
|
62
51
|
|
|
63
|
-
`withTags()`/`disableRules()` above have counterparts: `.withRules([...])` (only run these specific rule IDs) and `.disableTags([...])` (never run rules carrying any of these tags). All four compose the same way similar allow/deny-list options do in other accessibility testing tools, with one non-obvious rule worth knowing: a "disable" always wins over a "with" on the same ID/tag (e.g. `.withRules(['a']).disableRules(['a'])` drops `'a'` entirely), and combining `.withRules()` **and** `.withTags()` together requires a rule to satisfy *both* (surea11y's default `includeMode: 'and'`
|
|
52
|
+
`withTags()`/`disableRules()` above have counterparts: `.withRules([...])` (only run these specific rule IDs) and `.disableTags([...])` (never run rules carrying any of these tags). All four compose the same way similar allow/deny-list options do in other accessibility testing tools, with one non-obvious rule worth knowing: a "disable" always wins over a "with" on the same ID/tag (e.g. `.withRules(['a']).disableRules(['a'])` drops `'a'` entirely), and combining `.withRules()` **and** `.withTags()` together requires a rule to satisfy *both* (`@surea11y/core`'s default `includeMode: 'and'`), not either one.
|
|
64
53
|
|
|
65
54
|
`.exclude(selector)` above excludes globally. Pass a second argument to scope it to specific rule IDs instead: `.exclude('.mat-select', { rules: ['aria-required-children'] })` skips `.mat-select` for that rule only — every other rule still sees it. Global and rule-scoped `.exclude()` calls compose freely.
|
|
66
55
|
|
|
@@ -74,7 +63,7 @@ The pattern above works unchanged inside a real WebdriverIO testrunner spec, whi
|
|
|
74
63
|
|
|
75
64
|
```js
|
|
76
65
|
const assert = require('node:assert');
|
|
77
|
-
const { A11yCoreBuilder, formatFailures } = require('surea11y
|
|
66
|
+
const { A11yCoreBuilder, formatFailures } = require('@surea11y/webdriverio');
|
|
78
67
|
|
|
79
68
|
describe('accessibility gate', () => {
|
|
80
69
|
it('has no accessibility violations', async () => {
|
|
@@ -87,7 +76,7 @@ describe('accessibility gate', () => {
|
|
|
87
76
|
});
|
|
88
77
|
```
|
|
89
78
|
|
|
90
|
-
See `examples/e2e-test-example.test.js` for a fuller, runnable version (`npm run example:e2e`), driven by `examples/wdio.conf.js` — one test proving real violations get caught (unlabeled button, missing `alt`), one proving a well-formed page passes cleanly. It uses the **WebdriverIO testrunner** (`@wdio/cli` + mocha), which is how real WebdriverIO suites are written
|
|
79
|
+
See `examples/e2e-test-example.test.js` for a fuller, runnable version (`npm run example:e2e`), driven by `examples/wdio.conf.js` — one test proving real violations get caught (unlabeled button, missing `alt`), one proving a well-formed page passes cleanly. It uses the **WebdriverIO testrunner** (`@wdio/cli` + mocha), which is how real WebdriverIO suites are written. This project's own *unit* suite (`tests/*.test.js`) uses `node:test` + `remote()`, the right tool for exercising the builder API in isolation.
|
|
91
80
|
|
|
92
81
|
Note the gate above uses `node:assert`, not WebdriverIO's bundled `expect`: WebdriverIO's `expect` takes **at most one argument** — there's no `expect(value, message)` custom-failure-message overload the way Playwright's `expect` has — so `node:assert`'s message parameter is what carries `formatFailures()`' readable block onto a failed assertion.
|
|
93
82
|
|
|
@@ -104,7 +93,7 @@ A bare length/equality assertion alone gets you a *working* gate, but the failur
|
|
|
104
93
|
Add an alt attribute (use alt="" only for decorative images).
|
|
105
94
|
```
|
|
106
95
|
|
|
107
|
-
Deliberately a plain function, not a custom matcher — no dependency on any particular assertion library, so it works the same with `node:assert`, WebdriverIO's `expect`, Jest, Vitest, or a hand-rolled `if`/`throw`. Defaults to `fail`/`cantTell` outcomes (the only two that ever carry occurrences); pass `{ outcomes: [...] }` to narrow further. A thrown rule (`occurrences: []`, `error` set
|
|
96
|
+
Deliberately a plain function, not a custom matcher — no dependency on any particular assertion library, so it works the same with `node:assert`, WebdriverIO's `expect`, Jest, Vitest, or a hand-rolled `if`/`throw`. Defaults to `fail`/`cantTell` outcomes (the only two that ever carry occurrences); pass `{ outcomes: [...] }` to narrow further. A thrown rule (`occurrences: []`, `error` set) is still surfaced using its `error` message rather than silently dropped.
|
|
108
97
|
|
|
109
98
|
### Scanning every frame, including cross-origin and nested iframes
|
|
110
99
|
|
|
@@ -121,11 +110,11 @@ for (const frame of results.frames) {
|
|
|
121
110
|
|
|
122
111
|
The mechanics here are genuinely different from those bindings, though, because WebdriverIO's frame model is different. Puppeteer/Playwright expose `page.frames()` — an array of independent `Frame` objects, each with its own `.evaluate()`/`.$()` — so a binding can iterate them freely. WebdriverIO instead has a single **stateful current context**: `browser.switchFrame(iframeElement)` changes which document `browser.execute()`/`browser.$()` run against, and `browser.switchFrame(null)` returns to the top-level frame. There is no array of frame objects. So `.frames(true)` enumerates each context's direct-child `<iframe>` elements with `browser.$$('iframe')`, switches into each, scans the now-current document, and recurses into that frame's own children (re-navigating from the top each time, since element references and BiDi context ids don't reliably survive a context switch). Cross-origin iframes are reached with no extra work — WebdriverIO's automation layer switches into them the same as same-origin ones, exactly like Puppeteer/Playwright's CDP does.
|
|
123
112
|
|
|
124
|
-
Verified against a real cross-origin page (`https://example.org/` embedded in an unrelated origin) and against a real nested-iframe page (top → child → grandchild) — see `
|
|
113
|
+
Verified against a real cross-origin page (`https://example.org/` embedded in an unrelated origin) and against a real nested-iframe page (top → child → grandchild) — see `tests/builder.test.js`. Default off, so plain `.analyze()` is unaffected unless you opt in. Unlike script-injection-based accessibility tools (which need a `postMessage`-based protocol to reach cross-origin iframes, since they're injected as a plain `<script>` subject to the same-origin policy), this needs no extra `@surea11y/core` engine support at all.
|
|
125
114
|
|
|
126
115
|
### Trimming the result to just violations
|
|
127
116
|
|
|
128
|
-
By default `analyze()` returns every rule's outcome, including `pass`/`notApplicable` — surea11y's own deliberate "not a violations-only list" design
|
|
117
|
+
By default `analyze()` returns every rule's outcome, including `pass`/`notApplicable` — `@surea11y/core`'s own deliberate "not a violations-only list" design. Use `.reportOnly()` to post-filter down to only the outcomes you care about:
|
|
129
118
|
|
|
130
119
|
```js
|
|
131
120
|
const results = await new A11yCoreBuilder({ browser })
|
|
@@ -135,7 +124,7 @@ const results = await new A11yCoreBuilder({ browser })
|
|
|
135
124
|
console.log(results.checksResults); // only fail/cantTell entries, pass/notApplicable dropped
|
|
136
125
|
```
|
|
137
126
|
|
|
138
|
-
Valid outcome values are `'pass'`, `'fail'`, `'cantTell'`, `'notApplicable'`. This is pure binding-layer filtering —
|
|
127
|
+
Valid outcome values are `'pass'`, `'fail'`, `'cantTell'`, `'notApplicable'`. This is pure binding-layer filtering — the engine itself still computes every rule; nothing about the scan itself changes. Combines with `.frames(true)`: the filter is applied to `results.topFrame` and each entry of `results.frames` independently.
|
|
139
128
|
|
|
140
129
|
### Getting a live element, not just a selector string
|
|
141
130
|
|
|
@@ -154,13 +143,13 @@ This resolves `occurrence.selector` to a `WebdriverIO.Element` (via `browser.$()
|
|
|
154
143
|
Two WebdriverIO-specific differences from the sibling bindings' `.elementRef(true)`, both real and worth knowing:
|
|
155
144
|
|
|
156
145
|
- **The field is `occurrence.element`, not `occurrence.elementHandle`.** WebdriverIO has no "handle" concept — `browser.$()` returns a `WebdriverIO.Element`. Its API differs too: read a property with `.getProperty('id')` (not Puppeteer's `.evaluate(el => el.id)`), screenshot with `.saveScreenshot(path)` (not `.screenshot({ path })`). See the [WebdriverIO element API](https://webdriver.io/docs/api/element).
|
|
157
|
-
- **A sub-frame element is only usable while switched into its frame.** WebdriverIO element references are bound to whichever frame was the current context when they were resolved. When you combine `.frames(true).elementRef(true)`, a *sub-frame* occurrence's `element` is resolved against that frame's own document (correct), but after `analyze()` returns the browser is back at the top-level frame — so to act on a sub-frame element you must `browser.switchFrame()` back into its frame first (`browser.switchFrame((await browser.$$('iframe'))[i])`). Top-frame occurrences have no such caveat. This is a direct consequence of WebdriverIO's stateful context model and has no analogue in Puppeteer/Playwright, whose handles are bound to an execution context, not a mutable "current frame."
|
|
146
|
+
- **A sub-frame element is only usable while switched into its frame.** WebdriverIO element references are bound to whichever frame was the current context when they were resolved. When you combine `.frames(true).elementRef(true)`, a *sub-frame* occurrence's `element` is resolved against that frame's own document (correct), but after `analyze()` returns the browser is back at the top-level frame — so to act on a sub-frame element you must `browser.switchFrame()` back into its frame first (`browser.switchFrame((await browser.$$('iframe'))[i])`). Top-frame occurrences have no such caveat. This is a direct consequence of WebdriverIO's stateful context model and has no analogue in Puppeteer/Playwright, whose handles are bound to an execution context, not a mutable "current frame."
|
|
158
147
|
|
|
159
148
|
Not every occurrence has one target element — a page-wide finding (some `manual`/`cantTell` rules) can carry `selector: ""`, in which case `occurrence.element` is `null` rather than an element. (WebdriverIO's `browser.$('')` actually *throws* `invalid selector: No selector specified`, unlike Puppeteer/Playwright's `.$('')` which resolves to `null` — so the empty-selector guard that produces this `null` matters even more here; verified with a real run.)
|
|
160
149
|
|
|
161
150
|
### Registering a custom rule at runtime
|
|
162
151
|
|
|
163
|
-
|
|
152
|
+
`@surea11y/core` supports registering additional rules per-scan via `engineOptions.customRules`. Use `.withCustomRules()` to register one:
|
|
164
153
|
|
|
165
154
|
```js
|
|
166
155
|
const results = await new A11yCoreBuilder({ browser })
|
|
@@ -177,7 +166,7 @@ const results = await new A11yCoreBuilder({ browser })
|
|
|
177
166
|
.analyze();
|
|
178
167
|
```
|
|
179
168
|
|
|
180
|
-
A custom rule descriptor is the same shape as one of surea11y's own internal rule modules (`{ id, meta, runInPage, applicability?, data? }`) — see
|
|
169
|
+
A custom rule descriptor is the same shape as one of `@surea11y/core`'s own internal rule modules (`{ id, meta, runInPage, applicability?, data? }`) — see its [`ENGINE_OPTIONS.md`](https://github.com/rumoroso/surea11y-core/blob/main/docs/ENGINE_OPTIONS.md) for the full contract. Results appear in `checksResults` exactly like a built-in rule's, including automatic `selector`/`html`/`structuralPath` fill-in. Registered per-scan only (nothing persists between calls or shows up in any catalog listing), and a custom rule whose `id` collides with a built-in one overrides it for that scan.
|
|
181
170
|
|
|
182
171
|
Pass an array to register several at once, or call `.withCustomRules()` again to add more — like `.withRules()`/`.withTags()`, it accumulates rather than replacing what was already registered:
|
|
183
172
|
|
|
@@ -188,31 +177,37 @@ const results = await new A11yCoreBuilder({ browser })
|
|
|
188
177
|
.analyze();
|
|
189
178
|
```
|
|
190
179
|
|
|
191
|
-
**Why `.withCustomRules()` instead of the raw `.options({ customRules })` passthrough** (still supported, and composes with this method if you use both): `runInPage`/`applicability` must reach the page as a function-source *string*, not a live `Function` — a WebdriverIO `browser.execute()` argument crosses a serialization boundary that can't carry a live function reference, only a string surea11y can reconstruct with `new Function` on the page side. Passing a raw live function via `.options()` directly would silently fail to serialize; `.withCustomRules()` calls `.toString()` on a live function for you (patching the ES6 method-shorthand `.toString()` quirk automatically
|
|
180
|
+
**Why `.withCustomRules()` instead of the raw `.options({ customRules })` passthrough** (still supported, and composes with this method if you use both): `runInPage`/`applicability` must reach the page as a function-source *string*, not a live `Function` — a WebdriverIO `browser.execute()` argument crosses a serialization boundary that can't carry a live function reference, only a string `@surea11y/core` can reconstruct with `new Function` on the page side. Passing a raw live function via `.options()` directly would silently fail to serialize; `.withCustomRules()` calls `.toString()` on a live function for you (patching the ES6 method-shorthand `.toString()` quirk automatically), so you can write a normal function and not have to remember that constraint yourself. A string is still accepted as-is if you already have one.
|
|
192
181
|
|
|
193
|
-
Invalid input (a missing/empty `id`, or a `runInPage`/`applicability` that's neither a function nor a non-empty string) throws immediately from `.withCustomRules()` itself, rather than surfacing later as a silently-skipped rule deep inside the page — easier to catch during development. (Note: a *raw* `.options({ customRules })` call bypasses this check entirely and defers to surea11y's own engine-side behavior, which silently skips an invalid descriptor rather than throwing
|
|
182
|
+
Invalid input (a missing/empty `id`, or a `runInPage`/`applicability` that's neither a function nor a non-empty string) throws immediately from `.withCustomRules()` itself, rather than surfacing later as a silently-skipped rule deep inside the page — easier to catch during development. (Note: a *raw* `.options({ customRules })` call bypasses this check entirely and defers to `@surea11y/core`'s own engine-side behavior, which silently skips an invalid descriptor rather than throwing.)
|
|
194
183
|
|
|
195
184
|
### Element addressing beyond a CSS selector
|
|
196
185
|
|
|
197
|
-
Every occurrence already carries `selector` and (with `.elementRef(true)`, above) a live `element`. It also carries `structuralPath` — a sibling-index path from the document root down to the flagged element (e.g. `[1, 0, 2]`) — a more robust identity than a selector string alone, since it survives some DOM changes a selector wouldn't (an id/class rename, for instance). No opt-in needed; it's already on every `fail`/`cantTell` occurrence today. See
|
|
186
|
+
Every occurrence already carries `selector` and (with `.elementRef(true)`, above) a live `element`. It also carries `structuralPath` — a sibling-index path from the document root down to the flagged element (e.g. `[1, 0, 2]`) — a more robust identity than a selector string alone, since it survives some DOM changes a selector wouldn't (an id/class rename, for instance). No opt-in needed; it's already on every `fail`/`cantTell` occurrence today. See [`OUTPUT_SCHEMA.md`](https://github.com/rumoroso/surea11y-core/blob/main/docs/OUTPUT_SCHEMA.md) for the full field description.
|
|
198
187
|
|
|
199
188
|
## TypeScript
|
|
200
189
|
|
|
201
|
-
`src/A11yCoreBuilder.d.ts` (re-exported from `src/index.d.ts`, wired up via `package.json`'s `types` field) ships hand-written types for the whole builder API plus surea11y's native result shapes (`A11yCoreResult`, `CheckResult`, `Occurrence`, `CompositeResult`, etc.)
|
|
190
|
+
`src/A11yCoreBuilder.d.ts` (re-exported from `src/index.d.ts`, wired up via `package.json`'s `types` field) ships hand-written types for the whole builder API plus `@surea11y/core`'s native result shapes (`A11yCoreResult`, `CheckResult`, `Occurrence`, `CompositeResult`, etc.). `analyze()` is typed `Promise<A11yCoreResult | A11yCoreMultiFrameResult>` — narrow on `'topFrame' in results` (or cast, if you already know which mode you called) to get the specific shape back, since a fluent builder can't statically track that `.frames(true)` was called earlier in the chain. `occurrence.element` is typed `WebdriverIO.Element | null`. `webdriverio` is a `peerDependencies` entry (not just `devDependencies`) since the class's `browser` argument and `Occurrence#element` both come from its global `WebdriverIO` type namespace — consumers need their own `webdriverio` install for the types to resolve, same as they already do to construct a `browser` in the first place.
|
|
202
191
|
|
|
203
|
-
## Relationship to
|
|
192
|
+
## Relationship to `@surea11y/playwright` and `@surea11y/puppeteer`
|
|
204
193
|
|
|
205
|
-
This binding's builder API is deliberately the same shape as [
|
|
194
|
+
This binding's builder API is deliberately the same shape as [`@surea11y/playwright`](https://github.com/rumoroso/surea11y-core-playwright)'s and `@surea11y/puppeteer`'s — same method names, same mutability contract, same result shapes — so the accessibility-gate logic reads almost identically across all three. The state-accumulation half of the builder (`include`/`exclude`/`withTags`/`disableTags`/`withRules`/`disableRules`/`options`/`withCustomRules`/`reportOnly`) is not copied across bindings — `A11yCoreBuilder` here extends `A11yCoreBuilderBase` from [`@surea11y/binding-base`](https://github.com/rumoroso/surea11y-core-binding-base), the single shared implementation every sibling binding depends on.
|
|
206
195
|
|
|
207
196
|
Where it genuinely diverges is WebdriverIO's driver model, and those differences are real, not cosmetic:
|
|
208
197
|
|
|
209
198
|
- **`analyze()`'s injection call** is variadic like Puppeteer's (`browser.execute(runa11yCoreInPage, url, ctx, opts, runOnly)`) — no single-arg wrapper/`eval()` trick the Playwright binding needs.
|
|
210
|
-
- **`.frames(true)`** can't iterate a `page.frames()` array (there isn't one) — it drives WebdriverIO's stateful `switchFrame()` context model instead, recursing to reach nested frames. See above
|
|
199
|
+
- **`.frames(true)`** can't iterate a `page.frames()` array (there isn't one) — it drives WebdriverIO's stateful `switchFrame()` context model instead, recursing to reach nested frames. See above.
|
|
211
200
|
- **`.elementRef(true)`** attaches `occurrence.element` (a `WebdriverIO.Element`), not `occurrence.elementHandle`, with the sub-frame-context caveat described above.
|
|
212
|
-
- **Protocol:** classic WebDriver is strongly preferred over WebdriverIO v9's default BiDi for this execute()-heavy workload (see above
|
|
201
|
+
- **Protocol:** classic WebDriver is strongly preferred over WebdriverIO v9's default BiDi for this execute()-heavy workload (see above).
|
|
202
|
+
|
|
203
|
+
## Building another framework binding?
|
|
204
|
+
|
|
205
|
+
See `@surea11y/core`'s [`BINDING_AUTHORS_GUIDE.md`](https://github.com/rumoroso/surea11y-core/blob/main/docs/BINDING_AUTHORS_GUIDE.md) — a reference for building a new binding, covering which parity features are engine-level (work through a generic `.options()`/`runOnly` passthrough with zero binding code, including WCAG-version tag filtering) vs. binding-layer (element refs, `reportOnly`-style verbosity filtering, the serialization-boundary caveat `.withCustomRules()` exists to paper over).
|
|
206
|
+
|
|
207
|
+
`A11yCoreBuilder` here extends `A11yCoreBuilderBase` from [`@surea11y/binding-base`](https://github.com/rumoroso/surea11y-core-binding-base), a small shared package holding the scaffolding common to every framework binding. A new binding should depend on that package from the start.
|
|
213
208
|
|
|
214
|
-
|
|
209
|
+
## License
|
|
215
210
|
|
|
216
|
-
|
|
211
|
+
MIT — see [`LICENSE`](./LICENSE).
|
|
217
212
|
|
|
218
|
-
|
|
213
|
+
This package depends on [`@surea11y/core`](https://github.com/rumoroso/surea11y-core), which is MPL-2.0. MPL-2.0's copyleft is file-level and applies only to `@surea11y/core`'s own source files; consuming it as a normal package dependency doesn't affect this package's license.
|
package/package.json
CHANGED
package/src/A11yCoreBuilder.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface Occurrence {
|
|
|
68
68
|
* `WebdriverIO.Element`), not `elementHandle` as in the Puppeteer/Playwright
|
|
69
69
|
* bindings -- WebdriverIO has no "handle" concept. A sub-frame occurrence's
|
|
70
70
|
* element is only usable while the browser is switched into that frame; see
|
|
71
|
-
* A11yCoreBuilder#elementRef
|
|
71
|
+
* A11yCoreBuilder#elementRef.
|
|
72
72
|
*/
|
|
73
73
|
element?: WebdriverIO.Element | null;
|
|
74
74
|
}
|
package/src/A11yCoreBuilder.js
CHANGED
|
@@ -14,8 +14,8 @@ const { A11yCoreBuilderBase } = require('@surea11y/binding-base');
|
|
|
14
14
|
* .options({ contrast: { mode: 'auditorAssist' } })
|
|
15
15
|
* .analyze();
|
|
16
16
|
*
|
|
17
|
-
* `results` is surea11y's own native result shape (checksResults /
|
|
18
|
-
* rulesResults -- see
|
|
17
|
+
* `results` is @surea11y/core's own native result shape (checksResults /
|
|
18
|
+
* rulesResults -- see ../core/docs/OUTPUT_SCHEMA.md), not the
|
|
19
19
|
* violations/passes/incomplete/inapplicable shape used by other tools in
|
|
20
20
|
* this space. Method names are modeled on common conventions in this space
|
|
21
21
|
* for migration ease, but the richer native schema (severity, confidence,
|
|
@@ -27,7 +27,7 @@ const { A11yCoreBuilderBase } = require('@surea11y/binding-base');
|
|
|
27
27
|
* be navigated to and settled at the URL to scan; this class does not
|
|
28
28
|
* navigate for you.
|
|
29
29
|
*
|
|
30
|
-
* Extends `A11yCoreBuilderBase` (from
|
|
30
|
+
* Extends `A11yCoreBuilderBase` (from `@surea11y/binding-base`), which owns
|
|
31
31
|
* every method with no driver-specific work at all -- `include()`/
|
|
32
32
|
* `exclude()`/`withTags()`/`disableTags()`/`withRules()`/`disableRules()`/
|
|
33
33
|
* `options()`/`reportOnly()`/`elementRef()`/`frames()`/`withCustomRules()`'s
|
|
@@ -36,7 +36,7 @@ const { A11yCoreBuilderBase } = require('@surea11y/binding-base');
|
|
|
36
36
|
* boundary), and `_buildEngineArgs()`. This class adds exactly the parts
|
|
37
37
|
* that are genuinely WebdriverIO-specific: `analyze()`'s injection
|
|
38
38
|
* mechanics, the stateful index-path frame-traversal design below, and
|
|
39
|
-
* `_attachElementRefs()`. See `../
|
|
39
|
+
* `_attachElementRefs()`. See `../binding-base/README.md` for
|
|
40
40
|
* what's shared and why.
|
|
41
41
|
*
|
|
42
42
|
* Opt in to scanning every frame on the page (including cross-origin
|
|
@@ -62,13 +62,13 @@ const { A11yCoreBuilderBase } = require('@surea11y/binding-base');
|
|
|
62
62
|
* way in). Cross-origin iframes are reached with no extra work -- the
|
|
63
63
|
* WebDriver/BiDi automation layer switches into them the same as same-origin
|
|
64
64
|
* ones, exactly like Puppeteer/Playwright's CDP does; verified against a
|
|
65
|
-
* real cross-origin frame (`https://example.org/`).
|
|
65
|
+
* real cross-origin frame (`https://example.org/`).
|
|
66
66
|
* Default off, so plain .analyze() keeps returning the single native result
|
|
67
67
|
* object it always has.
|
|
68
68
|
*
|
|
69
69
|
* By default `analyze()` returns every rule's outcome, including
|
|
70
|
-
* `pass`/`notApplicable` -- surea11y's own deliberate "not a
|
|
71
|
-
* violations-only list" design (see
|
|
70
|
+
* `pass`/`notApplicable` -- @surea11y/core's own deliberate "not a
|
|
71
|
+
* violations-only list" design (see ../core/docs/OUTPUT_SCHEMA.md).
|
|
72
72
|
* Opt in to a lighter payload with `.reportOnly(['fail', 'cantTell'])`,
|
|
73
73
|
* which post-filters `checksResults` by `outcome` (applied per-frame when
|
|
74
74
|
* combined with `.frames(true)`, since `checksResults` lives at
|
|
@@ -93,15 +93,15 @@ const { A11yCoreBuilderBase } = require('@surea11y/binding-base');
|
|
|
93
93
|
* Puppeteer/Playwright's `occurrence.elementHandle` -- WebdriverIO has no
|
|
94
94
|
* "handle" concept, and its element API differs (`.getProperty('id')` rather
|
|
95
95
|
* than `.evaluate(el => el.id)`, `.saveScreenshot(path)` rather than
|
|
96
|
-
* `.screenshot({ path })`). See
|
|
96
|
+
* `.screenshot({ path })`). See the README.
|
|
97
97
|
*
|
|
98
98
|
* Register your own rule(s) for just this scan with
|
|
99
|
-
* `.withCustomRules([...])` (surea11y's `engineOptions.customRules`
|
|
100
|
-
* escape hatch -- see
|
|
99
|
+
* `.withCustomRules([...])` (@surea11y/core's `engineOptions.customRules`
|
|
100
|
+
* escape hatch -- see ../core/docs/ENGINE_OPTIONS.md). Pass a real,
|
|
101
101
|
* live `runInPage`/
|
|
102
102
|
* `applicability` function -- unlike the raw `.options({ customRules })`
|
|
103
103
|
* passthrough, this method converts them to the function-source string
|
|
104
|
-
* surea11y needs on this side of the browser.execute() serialization
|
|
104
|
+
* @surea11y/core needs on this side of the browser.execute() serialization
|
|
105
105
|
* boundary for you, so you don't have to remember to call .toString()
|
|
106
106
|
* yourself:
|
|
107
107
|
*
|
|
@@ -142,8 +142,8 @@ class A11yCoreBuilder extends A11yCoreBuilderBase {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* Runs the scan and returns surea11y's native result object.
|
|
146
|
-
* @returns {Promise<object>} see
|
|
145
|
+
* Runs the scan and returns @surea11y/core's native result object.
|
|
146
|
+
* @returns {Promise<object>} see ../core/docs/OUTPUT_SCHEMA.md
|
|
147
147
|
*/
|
|
148
148
|
async analyze() {
|
|
149
149
|
const { contextSelector, engineOptions, runOnly } = this._buildEngineArgs();
|
|
@@ -205,7 +205,7 @@ class A11yCoreBuilder extends A11yCoreBuilderBase {
|
|
|
205
205
|
* That's what makes it protocol-agnostic: WebdriverIO's switchFrame()
|
|
206
206
|
* returns a reusable BiDi context id under the default BiDi protocol but
|
|
207
207
|
* `null` under the classic WebDriver protocol (`wdio:enforceWebDriverClassic`,
|
|
208
|
-
* which this project's own tests/examples use
|
|
208
|
+
* which this project's own tests/examples use),
|
|
209
209
|
* and `$$('iframe')` element references don't survive a context switch away
|
|
210
210
|
* and back either. The one thing that reliably re-addresses a nested context
|
|
211
211
|
* on both protocols is replaying `switchFrame(null)` then the chain of
|
package/src/formatFailures.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// Re-exported from surea11y
|
|
3
|
+
// Re-exported from @surea11y/binding-base, which now holds the single
|
|
4
4
|
// canonical copy shared across every surea11y binding -- see
|
|
5
|
-
// ../
|
|
6
|
-
// ../surea11y-binding-base/README.md for the full rationale. Kept as a
|
|
5
|
+
// ../binding-base/README.md for the full rationale. Kept as a
|
|
7
6
|
// real file here (not just re-exporting straight from src/index.js) so
|
|
8
|
-
// `require('surea11y
|
|
7
|
+
// `require('@surea11y/webdriverio/src/formatFailures')` keeps working for
|
|
9
8
|
// anyone importing the submodule path directly.
|
|
10
9
|
const { formatFailures } = require('@surea11y/binding-base');
|
|
11
10
|
|