Skip to content

[#642] Added 'AccessibilityTrait' for tool-agnostic WCAG testing.#643

Merged
AlexSkrypnyk merged 14 commits into
mainfrom
feature/642-a11y-trait
May 29, 2026
Merged

[#642] Added 'AccessibilityTrait' for tool-agnostic WCAG testing.#643
AlexSkrypnyk merged 14 commits into
mainfrom
feature/642-a11y-trait

Conversation

@AlexSkrypnyk
Copy link
Copy Markdown
Member

@AlexSkrypnyk AlexSkrypnyk commented May 28, 2026

Closes #642

Summary

Adds AccessibilityTrait to the behat-steps library: a tool-agnostic accessibility-testing trait for Behat scenarios. The trait owns scenario lifecycle, tag-driven gate semantics, and report writing; the actual engine (default: axe-core via CDN) is plugged in via two overridable methods - accessibilityRunEngine() runs the engine and returns raw results, accessibilityNormalizeResults() remaps the raw output into a canonical shape the rest of the trait expects. Reports land as one HTML and one JUnit XML per scenario, with one section per visited URL.

Changes

New trait - src/AccessibilityTrait.php

  • Step definitions: Then the current page should pass accessibility checks and Then the current page should pass accessibility checks for tags :rules.
  • Automatic mode: tag a scenario or feature with @accessibility, @accessibility-critical, @accessibility-serious, @accessibility-moderate, @accessibility-minor, @accessibility-any, @accessibility-warning, or @accessibility-strict for per-scenario gate configuration. The trait runs the engine after every step that changes the URL and aggregates findings.
  • Tool-agnostic shape: accessibilityRunEngine($rules) runs the engine and returns raw output; accessibilityNormalizeResults($raw) maps it into the canonical shape (violations[], incomplete[], passes[]). The default normalize uses a switch over the engine's impact string and assigns one of the four public IMPACT_* constants (IMPACT_CRITICAL, IMPACT_SERIOUS, IMPACT_MODERATE, IMPACT_MINOR) the gate logic compares against. Consumers wiring a different engine override both methods.
  • No protected constants for defaults: every default (CDN URL, report directory, auto tag, default rules, threshold, impact list, etc.) lives inline in an overridable accessibility* method.
  • HTML report split into two methods: accessibilityRenderHtmlPage($sections) for the page wrapper (override to brand the page) and accessibilityRenderHtmlSections() for the per-URL section logic.
  • Skip processing per scenario or feature with @behat-steps-skip:AccessibilityTrait.

Shared helper - src/HelperTrait.php

  • New helperSlug(string $value): string that lowercases, collapses non-alphanumeric runs to -, trims surrounding hyphens, and falls back to untitled. Used by AccessibilityTrait to build the per-scenario report filename and available to any other trait that needs filesystem-safe slugs.

Tests - tests/behat/features/accessibility.feature

  • 7 scenarios: clean page passes the explicit step (default rules and a specific tag set), auto mode passes across navigation between two clean pages, @accessibility-warning never fails on a broken page, plus three @trait:AccessibilityTrait subprocess scenarios that verify the broken page fails the explicit step, fails auto mode with the expected gate message, and that @accessibility-critical is honoured.

Fixture HTML pages

  • tests/behat/fixtures/accessibility_clean.html, accessibility_clean2.html, accessibility_violations.html - WCAG-clean pages plus a deliberately inaccessible one (missing alt, empty <button>, empty <a>).

Unit tests - tests/phpunit/src/HelperTraitTest.php

  • 13 data-provider rows for helperSlug() covering lowercase passthrough, whitespace collapse, mixed case, punctuation, digits, leading/trailing hyphen stripping, the untitled fallback for empty / whitespace-only / punctuation-only / unicode-only inputs, and the a11y digit-boundary case.

Tooling adjustments unrelated to the trait itself

  • phpstan.neon - new ignoreErrors entry for function.alreadyNarrowedType against ConfigOverrideTrait::$restHeaders. PHPStan 2.2.0 (released the same day as this PR) introduced a ConstantConditionInTraitCollector (PR phpstan/phpstan-src#5309) that emits the error from a different rule than the original, so the existing inline @phpstan-ignore comments at the call site no longer suppress it.
  • tests/behat/fixtures_drupal/d10/composer.json - pinned phpunit/php-code-coverage to ^9.2.31, the first 9.x with explicit ?self $parent = null in AbstractNode::__construct (required by PHP 8.4).
  • tests/behat/fixtures_drupal/d11/composer.json - pinned phpunit/php-code-coverage to ^11.0.6 for the same reason in the PHPUnit 11 line.
  • Both fixture composer.json files keep the audit block (abandoned: ignore, block-insecure: false, all severities ignored) so the disposable Drupal fixture builds are not blocked by unrelated upstream advisories on Drupal core releases.

Before / After

Before                                       After
------                                       -----
no accessibility coverage in steps           AccessibilityTrait

  scenario:                                    scenario:
    visit page                                   visit page
    [no a11y assertion]                          Then the current page should pass accessibility checks

                                               @accessibility scenario:
                                                 visit page A   -> engine runs, normalize -> canonical
                                                 visit page B   -> engine runs, normalize -> canonical
                                                 (after scenario) -> HTML + JUnit report
                                                                   one section per URL

Engine plug points:

   accessibilityRunEngine($rules)        accessibilityNormalizeResults($raw)
        |                                       |
        v                                       v
  +-------------+                       +------------------+
  | engine JS   |  --- raw output --->  | canonical shape  | ----> gate / reports
  | (axe-core   |                       | violations[],    |
  |  default)   |                       | incomplete[],    |
  +-------------+                       | passes[]         |
                                        +------------------+

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds A11yTrait to run axe-core accessibility checks in Behat scenarios (explicit steps and automatic per-step mode), per-scenario HTML and JUnit reporting, tag-driven threshold/gate configuration, tests, fixtures, documentation, and supporting build/test config adjustments.

Changes

A11yTrait accessibility feature

Layer / File(s) Summary
Documentation: README and STEPS reference
README.md, STEPS.md
Index table row added to README; new STEPS.md section documents explicit vs automatic modes, tag-driven configuration (threshold, incomplete-fail, auto enable), per-scenario reporting structure, and two step definition examples.
Trait lifecycle: setup, state, and initialization
src/A11yTrait.php
Trait declares configuration constants, per-scenario state fields for automatic mode, collected results, titles, and last-checked URL; a11ySetupScenario hook initializes state, captures feature/scenario metadata, checks skip tag, merges feature/scenario tags, and resolves automatic mode and threshold/incomplete-fail policy from tag variants.
Explicit and automatic assessment entry points
src/A11yTrait.php
a11yAssertCurrentPage and a11yAssertCurrentPageForTags run axe on demand, filter by impact and optional rule tags, compute gate failure, and throw ExpectationException; a11yAutoAssess hook runs axe after each step when session is active and URL changes, collecting results per URL; a11yFinalizeScenario writes HTML and JUnit reports and enforces scenario gate on collected results.
Configuration, CDN caching, and violation filtering
src/A11yTrait.php
Methods fetch axe-core JS from CDN with per-process caching, resolve tag variants into automatic mode and threshold/incomplete-fail state, filter violations by impact severity, expose configuration defaults (rules, threshold, report directory), and provide helpers for computing effective threshold/incomplete policy.
Browser-side axe execution and result collection
src/A11yTrait.php
a11yRunAxe injects cached axe JS, invokes axe.run with optional tag-based rule filtering, waits for results, validates shape, records violations/passes/incomplete keyed by current URL and requested rules, updates last-checked URL, and outputs summary to STDOUT.
Report generation: formatting, HTML, and JUnit XML
src/A11yTrait.php
Helpers format gate failure messages with violation/incomplete details and node targets; HTML rendering builds per-URL sections with violations, incomplete items, and passes; JUnit XML rendering creates per-URL test suites with failure entries per violation and minimal passes; both handle filename slugification and proper HTML/XML escaping.
Test integration: FeatureContext trait composition
tests/behat/bootstrap/FeatureContext.php
Imports A11yTrait and includes it in FeatureContext via trait composition, enabling accessibility steps and hooks in Behat tests.
HTML test fixtures for accessibility checking
tests/behat/fixtures/a11y_clean.html, a11y_clean2.html, a11y_violations.html
Three static HTML fixtures: clean pages with accessible structure/links/images/alt text (WCAG 2.0/2.1 A/AA compliant) and a violations page with intentional missing alt, empty button names, missing link text, and landmark issues for failure testing.
Behat feature scenarios for A11yTrait validation
tests/behat/features/a11y.feature
Feature with @javascript scenarios covering explicit assertion pass/fail, automatic mode pass on clean pages and with advisory warning gate, threshold configuration (critical-only), failure detection with expected output fields, and navigation between clean pages in auto mode.
Build configuration: Composer audit policies
tests/behat/fixtures_drupal/d10/composer.json, d11/composer.json
Adds config.audit blocks configuring abandoned package ignoring, disabled insecure-package blocking, and explicit ignored-severity lists.
Supporting utility and analysis configuration
docs.php, phpstan.neon
camel_to_snake() refactored to build replacements map once and apply via str_replace(), removing intermediate regex step; PHPStan configuration adds ignoreErrors entry for trait-context property_exists check.
Test expectation updates for camel_to_snake()
tests/phpunit/src/DocsTest.php
Adjusted expected outputs to match revised camel_to_snake() behavior for numeric and API/number sequences.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A rabbit read the HTML and said,
"With axe in paw I'll check each head."
Pages pass or fail, the reports compile,
One hop, two hops—accessibility, with style. 🥕📋

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Minor out-of-scope changes detected: composer.json audit suppressions in Drupal fixtures and camel_to_snake() function refactoring are unrelated to #642's accessibility trait requirements. Remove composer.json audit configuration changes and camel_to_snake() modifications, as they are not part of the accessibility trait implementation scope.
Title check ❓ Inconclusive The PR title references 'AccessibilityTrait' but the actual implementation uses 'A11yTrait' (abbreviated form), and the title omits the key detail that it's specifically axe-core integration for WCAG testing. Clarify whether the trait name is 'AccessibilityTrait' or 'A11yTrait', and consider updating the title to explicitly mention 'axe-core' for better clarity about the testing approach.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully implements all core requirements from #642: explicit and automatic accessibility assessment modes [#642], configurable per-scenario gating via tags [#642], single HTML and JUnit reports per scenario [#642], extensibility via method overrides [#642], reuses existing Mink session [#642], includes no new consumer dependencies [#642], and provides sensible defaults [#642].
Docstring Coverage ✅ Passed Docstring coverage is 98.08% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/642-a11y-trait

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@composer.json`:
- Around line 66-70: The current "audit" block suppresses all severities
("ignore-severities": ["low","medium","high","critical"]) disabling Composer
security checks; update the "audit" configuration by removing the entire "audit"
block or at minimum change "ignore-severities" to only ["low"] (or delete that
key entirely) and keep "abandoned": "ignore" only if abandoned packages are the
sole concern so that medium/high/critical vulnerabilities are reported;
reference the "audit" object and its keys ("ignore-severities", "abandoned")
when making the change.

In `@src/AccessibilityTrait.php`:
- Around line 1-14: Add a proper import for the global RuntimeException in
AccessibilityTrait by adding "use RuntimeException;" near the other use
statements, then update the places where RuntimeException is thrown in this
trait (the throw statements currently using \RuntimeException and any
`@throws/doc` references) to use the imported symbol (remove the leading backslash
or switch to RuntimeException::class in throws annotations) so they follow
PSR-12 and consistent imports.
- Around line 249-255: The code that creates the report directory and writes
files (accessibilityGetReportDir(), mkdir, file_put_contents of
accessibilityRenderHtml() and accessibilityRenderJunit()) lacks error handling;
update the block so you verify mkdir succeeded (or directory exists after call),
check file_put_contents returns !== false for both writes, and surface failures
by throwing a clear exception or logging an error (including the $dir and $slug
from accessibilitySlug($this->accessibilityFeatureName) and
accessibilitySlug($this->accessibilityScenarioName)) so calling code won't
silently skip report generation.

In `@tests/behat/fixtures_drupal/d10/composer.json`:
- Around line 62-66: The composer.json test fixture currently disables Composer
security auditing by setting the "audit" block with "ignore-severities": ["low",
"medium", "high", "critical"]; update the "audit" configuration in this file so
security checks are not fully suppressed—either remove the entire "audit" block,
or change the "ignore-severities" value to only ["low"] (or remove
"critical"/higher severities) and keep "abandoned": "ignore" if you only want to
ignore abandoned packages; locate the "audit" object in composer.json and apply
one of these fixes to restore meaningful audit results.

In `@tests/behat/fixtures_drupal/d11/composer.json`:
- Around line 63-67: The composer.json test fixture currently disables Composer
security auditing via the "audit" block (keys "abandoned", "block-insecure", and
"ignore-severities"); update composer.json to re-enable meaningful audits by
either removing the entire "audit" object or changing "ignore-severities" from
["low","medium","high","critical"] to a minimal suppression such as ["low"] (or
keep only "abandoned": "ignore" if abandoned-packages are the only concern) and
ensure "block-insecure" remains false/appropriate so Composer will report
medium+ and critical vulnerabilities.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b6910d1e-7f5b-490b-be65-170c27b4473e

📥 Commits

Reviewing files that changed from the base of the PR and between a7613a0 and 08c7f1d.

📒 Files selected for processing (11)
  • README.md
  • STEPS.md
  • composer.json
  • src/AccessibilityTrait.php
  • tests/behat/bootstrap/FeatureContext.php
  • tests/behat/features/accessibility.feature
  • tests/behat/fixtures/accessibility_clean.html
  • tests/behat/fixtures/accessibility_clean2.html
  • tests/behat/fixtures/accessibility_violations.html
  • tests/behat/fixtures_drupal/d10/composer.json
  • tests/behat/fixtures_drupal/d11/composer.json

Comment thread composer.json Outdated
Comment on lines +1 to +14
<?php

declare(strict_types=1);

namespace DrevOps\BehatSteps;

use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\AfterStepScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Hook\AfterScenario;
use Behat\Hook\AfterStep;
use Behat\Hook\BeforeScenario;
use Behat\Mink\Exception\ExpectationException;
use Behat\Step\Then;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add import for RuntimeException.

RuntimeException is thrown on lines 349, 533, and 537 but not imported. Per PSR-12 standards, global classes should be imported.

📦 Add missing import
 use Behat\Mink\Exception\ExpectationException;
 use Behat\Step\Then;
+use RuntimeException;

Then update the throws to remove the \ prefix if you added it, or keep them as RuntimeException::class in the throws.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<?php
declare(strict_types=1);
namespace DrevOps\BehatSteps;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\AfterStepScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Hook\AfterScenario;
use Behat\Hook\AfterStep;
use Behat\Hook\BeforeScenario;
use Behat\Mink\Exception\ExpectationException;
use Behat\Step\Then;
<?php
declare(strict_types=1);
namespace DrevOps\BehatSteps;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\AfterStepScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use Behat\Hook\AfterScenario;
use Behat\Hook\AfterStep;
use Behat\Hook\BeforeScenario;
use Behat\Mink\Exception\ExpectationException;
use Behat\Step\Then;
use RuntimeException;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/AccessibilityTrait.php` around lines 1 - 14, Add a proper import for the
global RuntimeException in AccessibilityTrait by adding "use RuntimeException;"
near the other use statements, then update the places where RuntimeException is
thrown in this trait (the throw statements currently using \RuntimeException and
any `@throws/doc` references) to use the imported symbol (remove the leading
backslash or switch to RuntimeException::class in throws annotations) so they
follow PSR-12 and consistent imports.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not importing - against standards

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexSkrypnyk, understood — using \RuntimeException directly (without a use import) is the project's preferred convention for global PHP built-in classes. I'll keep that in mind for future reviews.


✏️ Learnings added
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 643
File: src/AccessibilityTrait.php:1-14
Timestamp: 2026-05-28T10:09:36.498Z
Learning: In drevops/behat-steps, global PHP built-in/native exception classes (e.g., `\RuntimeException`, `\InvalidArgumentException`) are intentionally referenced with the backslash FQCN prefix (e.g., `throw new \RuntimeException(...)`) rather than being imported via `use` statements. Do not raise "missing import" review comments for global PHP namespace classes in this repository.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 560
File: src/ResponsiveTrait.php:173-173
Timestamp: 2026-04-09T08:32:59.254Z
Learning: In this project (drevops/behat-steps), Behat hook methods annotated with attributes like #[BeforeStep], #[BeforeScenario] (and similar Behat hook attributes) must keep their required scope parameter in the method signature (e.g., `BeforeStepScope $scope`, `BeforeScenarioScope $scope`) as required by the Behat framework contract. Even if the parameter is not referenced inside the method body, do not rename it to an unused placeholder like `$_scope` or otherwise change its type/name—treat it as structurally required. PHPMD `UnusedFormalParameter` warnings on these specific hook-method scope parameters are expected false positives and should be ignored.

Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 623
File: STEPS.md:2647-2650
Timestamp: 2026-04-10T08:13:52.837Z
Learning: In drevops/behat-steps, docs generation (docs.php when converting docblock code/endcode blocks into STEPS.md) strips `/*` sequences from examples. When writing glob/wildcard path patterns inside trait docblocks, avoid a literal slash-star `/*`—use patterns with a trailing `*` but no preceding slash. For example, write `/news*` instead of `/news/*`, so the glob survives generation and renders correctly in STEPS.md.

Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 634
File: src/Drupal/EntityFixtureTrait.php:41-95
Timestamp: 2026-05-12T07:07:50.984Z
Learning: In drevops/behat-steps, PHPMD is not part of the enforced lint/static-analysis pipeline (the repo relies on PHPStan via phpstan.neon and custom rules in composer.json). During code review, do not raise PHPMD cyclomatic complexity or NPath complexity warnings as review concerns for PHP changes; only consider complexity issues if they are flagged by PHPStan or the repo’s enforced/custom checks.

Learnt from: AlexSkrypnyk
Repo: drevops/behat-steps PR: 634
File: src/Drupal/EntityFixtureTrait.php:41-95
Timestamp: 2026-05-12T07:07:50.984Z
Learning: In this repository (drevops/behat-steps), snake_case is an intentional naming convention for **method parameter names** and **local variables** in PHP (per CLAUDE.md, e.g. `$entity_type`, `$files_path`). During code review, do **not** flag these snake_case identifiers as violations of camelCase naming conventions. Flag naming issues only when they are inconsistent with this established convention.

Comment thread src/AccessibilityTrait.php
Comment thread tests/behat/fixtures_drupal/d10/composer.json
Comment thread tests/behat/fixtures_drupal/d11/composer.json
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 92.36111% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.54%. Comparing base (a7613a0) to head (55cbd86).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/AccessibilityTrait.php 92.25% 22 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #643      +/-   ##
==========================================
- Coverage   96.93%   96.54%   -0.40%     
==========================================
  Files          43       44       +1     
  Lines        3068     3355     +287     
==========================================
+ Hits         2974     3239     +265     
- Misses         94      116      +22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/A11yTrait.php`:
- Around line 85-88: The a11ySetupScenario hook currently checks
$scope->getScenario()->hasTag(...) while a11yAutoAssess and a11yFinalizeScenario
check $scope->getFeature()->hasTag(...), causing inconsistent skip behavior;
update a11ySetupScenario to check
$scope->getFeature()->hasTag('behat-steps-skip:A11yTrait') so all three hooks
(a11ySetupScenario, a11yAutoAssess, a11yFinalizeScenario) use the same
feature-level tag check and behave consistently per the guideline.

In `@STEPS.md`:
- Around line 62-65: Add detailed usage docs for A11yTrait: list available tags
(`@axe`, `@axe-warning`, `@axe-critical`, `@axe-serious`, `@axe-moderate`, `@axe-minor`,
`@axe-strict`), explain each tag's effect (automatic mode vs explicit assertion
and threshold configuration), document the skip tag format
(`@behat-steps-skip`:A11yTrait), state the report output path
(.logs/test_results/a11y), and include one or two small example scenarios
demonstrating automatic mode and per-scenario threshold tags; place this content
near the existing A11yTrait summary so users can find it without reading
source/tests.

In `@tests/behat/features/a_11y.feature`:
- Around line 31-35: The inner PyString blocks in the `@trait`:A11yTrait scenarios
use triple double quotes; change those inner blocks to use triple single quotes
instead (replace """ with ''' in the nested scenario step blocks such as the
block containing Given I visit "/sites/default/files/a11y_violations.html" /
Then the current page should pass accessibility checks and the other similar
blocks), ensuring all `@javascript-tagged` scenario inner PyStrings follow the
BehatCliContext convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a61f9e4e-72c9-403b-aae6-6dd9785af0fa

📥 Commits

Reviewing files that changed from the base of the PR and between 5cb9754 and 0c8bc7c.

📒 Files selected for processing (8)
  • README.md
  • STEPS.md
  • src/A11yTrait.php
  • tests/behat/bootstrap/FeatureContext.php
  • tests/behat/features/a_11y.feature
  • tests/behat/fixtures/a11y_clean.html
  • tests/behat/fixtures/a11y_clean2.html
  • tests/behat/fixtures/a11y_violations.html

Comment thread src/A11yTrait.php Outdated
Comment thread STEPS.md Outdated
Comment thread tests/behat/features/accessibility.feature
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/A11yTrait.php (1)

149-155: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding error handling for report file operations.

If mkdir or file_put_contents fails (permissions, disk full, path issues), reports are silently lost with no indication to the user. While the gate enforcement still works correctly, losing reports in CI could be confusing.

🛡️ Optional: Add basic error checking
     $dir = $this->getA11yReportDir();
     if (!is_dir($dir)) {
-      mkdir($dir, 0777, TRUE);
+      if (!`@mkdir`($dir, 0777, TRUE) && !is_dir($dir)) {
+        fwrite(STDERR, sprintf("[a11y] Warning: Could not create report directory %s\n", $dir));
+        return;
+      }
     }
     $slug = $this->a11ySlug($this->a11yFeatureName) . '__' . $this->a11ySlug($this->a11yScenarioName);
-    file_put_contents($dir . '/' . $slug . '.html', $this->a11yRenderHtml());
-    file_put_contents($dir . '/junit-' . $slug . '.xml', $this->a11yRenderJunit());
+    if (file_put_contents($dir . '/' . $slug . '.html', $this->a11yRenderHtml()) === FALSE) {
+      fwrite(STDERR, sprintf("[a11y] Warning: Could not write HTML report to %s\n", $dir));
+    }
+    if (file_put_contents($dir . '/junit-' . $slug . '.xml', $this->a11yRenderJunit()) === FALSE) {
+      fwrite(STDERR, sprintf("[a11y] Warning: Could not write JUnit report to %s\n", $dir));
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/A11yTrait.php` around lines 149 - 155, Add basic error handling around
directory creation and file writes in A11yTrait: check the return value of
mkdir($dir, 0777, TRUE) and each file_put_contents call (for
$dir.'/'.$slug.'.html' and $dir.'/junit-'.$slug.'.xml'); on failure, log a clear
error (or throw an exception) including the target path and errno/message, and
ensure you still surface the problem to the caller (e.g., return false or
rethrow) so CI doesn’t silently lose reports; use the existing methods
a11yRenderHtml(), a11yRenderJunit(), getA11yReportDir(), and a11ySlug(...) to
build the paths and include those identifiers in the log/exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/A11yTrait.php`:
- Around line 149-155: Add basic error handling around directory creation and
file writes in A11yTrait: check the return value of mkdir($dir, 0777, TRUE) and
each file_put_contents call (for $dir.'/'.$slug.'.html' and
$dir.'/junit-'.$slug.'.xml'); on failure, log a clear error (or throw an
exception) including the target path and errno/message, and ensure you still
surface the problem to the caller (e.g., return false or rethrow) so CI doesn’t
silently lose reports; use the existing methods a11yRenderHtml(),
a11yRenderJunit(), getA11yReportDir(), and a11ySlug(...) to build the paths and
include those identifiers in the log/exception.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5ed4cc42-5536-434e-b82e-326d5078a175

📥 Commits

Reviewing files that changed from the base of the PR and between 0c8bc7c and 9c1e24e.

📒 Files selected for processing (5)
  • STEPS.md
  • docs.php
  • phpstan.neon
  • src/A11yTrait.php
  • tests/behat/features/a11y.feature
💤 Files with no reviewable changes (1)
  • tests/behat/features/a11y.feature

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

@AlexSkrypnyk AlexSkrypnyk changed the title [#642] Added 'AccessibilityTrait' for axe-core WCAG accessibility testing. [#642] Added 'A11yTrait' for axe-core WCAG accessibility testing. May 28, 2026
@AlexSkrypnyk AlexSkrypnyk changed the title [#642] Added 'A11yTrait' for axe-core WCAG accessibility testing. [#642] Added 'AccessibilityTrait' for tool-agnostic WCAG testing. May 29, 2026
@AlexSkrypnyk AlexSkrypnyk merged commit 3cd24a6 into main May 29, 2026
14 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/642-a11y-trait branch May 29, 2026 00:37
@AlexSkrypnyk AlexSkrypnyk added this to the v3.9 milestone May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add accessibility testing trait

1 participant