@sdods/core 0.14.1 → 0.15.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/.tsbuildinfo +1 -1
- package/dist/steps/a11y.steps.js +13 -13
- package/package.json +3 -3
package/dist/steps/a11y.steps.js
CHANGED
|
@@ -339,7 +339,7 @@ Then('the page should have no accessibility violations', async ({ page, scenario
|
|
|
339
339
|
markPageAudited(scenario, page.url());
|
|
340
340
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
341
341
|
assertAxeChecked(results, `on ${page.url()}`);
|
|
342
|
-
expect(results.violations
|
|
342
|
+
expect(results.violations, `${results.violations.length} WCAG A/AA violation(s) on ${page.url()}:\n${describeViolations(results.violations)}`).toHaveLength(0);
|
|
343
343
|
});
|
|
344
344
|
/**
|
|
345
345
|
* PROVES one region of the page is clean, so a shared component can be audited without the rest
|
|
@@ -352,7 +352,7 @@ Then('the page should have no accessibility violations within {string}', async (
|
|
|
352
352
|
const results = await runAxe(page, { include: sel });
|
|
353
353
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
354
354
|
assertAxeChecked(results, `within "${sel}"`);
|
|
355
|
-
expect(results.violations
|
|
355
|
+
expect(results.violations, `${results.violations.length} WCAG A/AA violation(s) within "${sel}" on ${page.url()}:\n${describeViolations(results.violations)}`).toHaveLength(0);
|
|
356
356
|
});
|
|
357
357
|
/**
|
|
358
358
|
* PROVES nothing at or above a chosen severity is present, so a team can adopt a11y gating with a
|
|
@@ -366,7 +366,7 @@ Then('the page should have no accessibility violations of impact {string} or wor
|
|
|
366
366
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
367
367
|
assertAxeChecked(results, `on ${page.url()}`);
|
|
368
368
|
const blocking = violationsAtOrAbove(results.violations, floor);
|
|
369
|
-
expect(blocking
|
|
369
|
+
expect(blocking, `${blocking.length} violation(s) at ${IMPACT_ORDER[floor]}+ on ${page.url()} (of ${results.violations.length} total):\n${describeViolations(blocking)}`).toHaveLength(0);
|
|
370
370
|
});
|
|
371
371
|
/** PROVES a region carries no violation at or above a severity — the scoped form of the above. */
|
|
372
372
|
Then('the page should have no accessibility violations of impact {string} or worse within {string}', async ({ page, scenario, apiContext, env, $bddContext, $testInfo }, level, selector) => {
|
|
@@ -378,7 +378,7 @@ Then('the page should have no accessibility violations of impact {string} or wor
|
|
|
378
378
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
379
379
|
assertAxeChecked(results, `within "${sel}"`);
|
|
380
380
|
const blocking = violationsAtOrAbove(results.violations, floor);
|
|
381
|
-
expect(blocking
|
|
381
|
+
expect(blocking, `${blocking.length} violation(s) at ${IMPACT_ORDER[floor]}+ within "${sel}" on ${page.url()}:\n${describeViolations(blocking)}`).toHaveLength(0);
|
|
382
382
|
});
|
|
383
383
|
/**
|
|
384
384
|
* PROVES one named axe rule holds on this page. Isolating a rule keeps a targeted regression
|
|
@@ -393,7 +393,7 @@ Then('the page should have no accessibility violations of rule {string}', async
|
|
|
393
393
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
394
394
|
const bucket = assertRuleRan(results, rule, `on ${page.url()}`);
|
|
395
395
|
const violations = results.violations.filter((v) => v.id === rule);
|
|
396
|
-
expect(violations
|
|
396
|
+
expect(violations, `${violations.length} "${rule}" violation(s) on ${page.url()} (axe bucket: ${bucket}):\n${describeViolations(violations)}`).toHaveLength(0);
|
|
397
397
|
});
|
|
398
398
|
/**
|
|
399
399
|
* PROVES text on this page meets the WCAG AA contrast ratio. Kept separate from the full audit
|
|
@@ -405,7 +405,7 @@ Then('the page should have no colour-contrast violations', async ({ page, scenar
|
|
|
405
405
|
const results = await runAxe(page, { rules: ['color-contrast'] });
|
|
406
406
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
407
407
|
const bucket = assertRuleRan(results, 'color-contrast', `on ${page.url()}`);
|
|
408
|
-
expect(results.violations
|
|
408
|
+
expect(results.violations, `${results.violations.length} colour-contrast violation(s) on ${page.url()} (axe bucket: ${bucket}; "incomplete" means axe could not read the background, e.g. text over an image):\n${describeViolations(results.violations)}`).toHaveLength(0);
|
|
409
409
|
});
|
|
410
410
|
/** PROVES one region's text meets AA contrast — for auditing a themed component in isolation. */
|
|
411
411
|
Then('the page should have no colour-contrast violations within {string}', async ({ page, scenario, apiContext, env, $bddContext, $testInfo }, selector) => {
|
|
@@ -414,7 +414,7 @@ Then('the page should have no colour-contrast violations within {string}', async
|
|
|
414
414
|
const results = await runAxe(page, { include: sel, rules: ['color-contrast'] });
|
|
415
415
|
attachA11y({ scenario, testInfo: $testInfo, stepIndex: $bddContext.stepIndex }, results);
|
|
416
416
|
const bucket = assertRuleRan(results, 'color-contrast', `within "${sel}"`);
|
|
417
|
-
expect(results.violations
|
|
417
|
+
expect(results.violations, `${results.violations.length} colour-contrast violation(s) within "${sel}" on ${page.url()} (axe bucket: ${bucket}):\n${describeViolations(results.violations)}`).toHaveLength(0);
|
|
418
418
|
});
|
|
419
419
|
/* ── structure axe cannot judge ────────────────────────────────────────── */
|
|
420
420
|
/**
|
|
@@ -426,7 +426,7 @@ Then('the page should have no colour-contrast violations within {string}', async
|
|
|
426
426
|
Then('the page should have exactly one level-1 heading', async ({ page }) => {
|
|
427
427
|
const gathered = await page.evaluate(gatherHeadings, null);
|
|
428
428
|
const level1 = gathered.items.filter((h) => h.level === 1);
|
|
429
|
-
expect(level1
|
|
429
|
+
expect(level1, `expected exactly one level-1 heading, found ${level1.length} of ${gathered.items.length} heading(s): ${JSON.stringify(level1.map((h) => h.text))}`).toHaveLength(1);
|
|
430
430
|
});
|
|
431
431
|
/**
|
|
432
432
|
* PROVES the heading outline is navigable: no jump from h2 straight to h4, which is how a screen
|
|
@@ -440,7 +440,7 @@ Then('the heading levels should not skip a level', async ({ page }) => {
|
|
|
440
440
|
const gathered = await page.evaluate(gatherHeadings, null);
|
|
441
441
|
const headings = assertGathered(gathered.items, 'headings', `on ${page.url()}`);
|
|
442
442
|
const skips = headingSkips(headings);
|
|
443
|
-
expect(skips
|
|
443
|
+
expect(skips, `skipped heading level(s) across ${headings.length} heading(s) on ${page.url()}: ${skips.join(', ')}`).toHaveLength(0);
|
|
444
444
|
});
|
|
445
445
|
/**
|
|
446
446
|
* PROVES every image declares its alt text intent. Fails on a page with no images: the brief that
|
|
@@ -451,7 +451,7 @@ Then('every image on the page should carry an alt attribute', async ({ page }) =
|
|
|
451
451
|
const gathered = await page.evaluate(gatherImages, null);
|
|
452
452
|
const images = assertGathered(gathered.items, 'img elements', `on ${page.url()}`);
|
|
453
453
|
const missing = imagesWithoutAlt(images);
|
|
454
|
-
expect(missing
|
|
454
|
+
expect(missing, `${missing.length} of ${images.length} img element(s) have no alt attribute: ${missing.join(', ')}`).toHaveLength(0);
|
|
455
455
|
});
|
|
456
456
|
/** PROVES every image inside one region declares its alt intent — the scoped form of the above. */
|
|
457
457
|
Then('every image within {string} should carry an alt attribute', async ({ page, apiContext, env }, selector) => {
|
|
@@ -459,7 +459,7 @@ Then('every image within {string} should carry an alt attribute', async ({ page,
|
|
|
459
459
|
const gathered = await page.evaluate(gatherImages, sel);
|
|
460
460
|
const images = assertGathered(assertRegion(gathered, sel), 'img elements', `within "${sel}"`);
|
|
461
461
|
const missing = imagesWithoutAlt(images);
|
|
462
|
-
expect(missing
|
|
462
|
+
expect(missing, `${missing.length} of ${images.length} img element(s) within "${sel}" have no alt attribute: ${missing.join(', ')}`).toHaveLength(0);
|
|
463
463
|
});
|
|
464
464
|
/**
|
|
465
465
|
* PROVES every icon-only button or link in a region announces itself. An icon with no name is
|
|
@@ -475,7 +475,7 @@ Then('every icon-only control within {string} should expose an accessible name',
|
|
|
475
475
|
const controls = assertRegion(gathered, sel);
|
|
476
476
|
const iconOnly = assertGathered(iconOnlyControls(controls), 'icon-only controls', `within "${sel}" (${controls.length} control(s) there render text)`);
|
|
477
477
|
const bad = unnamedControls(iconOnly);
|
|
478
|
-
expect(bad
|
|
478
|
+
expect(bad, `${bad.length} of ${iconOnly.length} icon-only control(s) within "${sel}" have a missing or unresolved accessible name:\n${bad.join('\n')}`).toHaveLength(0);
|
|
479
479
|
});
|
|
480
480
|
/**
|
|
481
481
|
* PROVES a keyboard user can see where they are inside a region (WCAG 2.2 AA 2.4.11/2.4.13). A
|
|
@@ -492,6 +492,6 @@ Then('every focusable element within {string} should show a visible focus indica
|
|
|
492
492
|
const gathered = await page.evaluate(gatherFocusIndicators, sel);
|
|
493
493
|
const focusables = assertGathered(assertRegion(gathered, sel), 'focusable elements', `within "${sel}"`);
|
|
494
494
|
const bad = focusRingMissing(focusables);
|
|
495
|
-
expect(bad
|
|
495
|
+
expect(bad, `${bad.length} of ${focusables.length} focusable element(s) within "${sel}" show no computed style change when focused: ${bad.join(', ')}`).toHaveLength(0);
|
|
496
496
|
});
|
|
497
497
|
//# sourceMappingURL=a11y.steps.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdods/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "SDODS runtime: configuration, project registry, fixtures, step libraries, data providers, screenshot narratives and self-healing locators.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"funding": "https://sdods.com/sponsor/",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"playwright-bdd": ">=9"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@sdods/contracts": "0.
|
|
49
|
+
"@sdods/contracts": "0.15.0",
|
|
50
50
|
"@cucumber/cucumber-expressions": "19.0.0",
|
|
51
51
|
"@cucumber/gherkin": "^42.0.1",
|
|
52
52
|
"@cucumber/messages": "^34.2.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"tsx": "^4.23.15",
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"zod": "^4.6.5",
|
|
67
|
-
"@sdods/db": "0.
|
|
67
|
+
"@sdods/db": "0.15.0"
|
|
68
68
|
},
|
|
69
69
|
"homepage": "https://sdods.com",
|
|
70
70
|
"bugs": {
|