@sdods/core 0.14.0 → 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.
@@ -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.length, `${results.violations.length} WCAG A/AA violation(s) on ${page.url()}:\n${describeViolations(results.violations)}`).toBe(0);
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.length, `${results.violations.length} WCAG A/AA violation(s) within "${sel}" on ${page.url()}:\n${describeViolations(results.violations)}`).toBe(0);
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.length, `${blocking.length} violation(s) at ${IMPACT_ORDER[floor]}+ on ${page.url()} (of ${results.violations.length} total):\n${describeViolations(blocking)}`).toBe(0);
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.length, `${blocking.length} violation(s) at ${IMPACT_ORDER[floor]}+ within "${sel}" on ${page.url()}:\n${describeViolations(blocking)}`).toBe(0);
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.length, `${violations.length} "${rule}" violation(s) on ${page.url()} (axe bucket: ${bucket}):\n${describeViolations(violations)}`).toBe(0);
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.length, `${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)}`).toBe(0);
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.length, `${results.violations.length} colour-contrast violation(s) within "${sel}" on ${page.url()} (axe bucket: ${bucket}):\n${describeViolations(results.violations)}`).toBe(0);
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.length, `expected exactly one level-1 heading, found ${level1.length} of ${gathered.items.length} heading(s): ${JSON.stringify(level1.map((h) => h.text))}`).toBe(1);
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.length, `skipped heading level(s) across ${headings.length} heading(s) on ${page.url()}: ${skips.join(', ')}`).toBe(0);
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.length, `${missing.length} of ${images.length} img element(s) have no alt attribute: ${missing.join(', ')}`).toBe(0);
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.length, `${missing.length} of ${images.length} img element(s) within "${sel}" have no alt attribute: ${missing.join(', ')}`).toBe(0);
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.length, `${bad.length} of ${iconOnly.length} icon-only control(s) within "${sel}" have a missing or unresolved accessible name:\n${bad.join('\n')}`).toBe(0);
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.length, `${bad.length} of ${focusables.length} focusable element(s) within "${sel}" show no computed style change when focused: ${bad.join(', ')}`).toBe(0);
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.14.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.14.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.14.0"
67
+ "@sdods/db": "0.15.0"
68
68
  },
69
69
  "homepage": "https://sdods.com",
70
70
  "bugs": {