diff --git a/lib/behat/classes/behat_session_trait.php b/lib/behat/classes/behat_session_trait.php
index 830b7b46454..9374443c0e7 100644
--- a/lib/behat/classes/behat_session_trait.php
+++ b/lib/behat/classes/behat_session_trait.php
@@ -1744,4 +1744,19 @@ EOF;
return $result ?: null;
}
+
+ /**
+ * Prepare an xpath for insertion into Selenium JavaScript.
+ *
+ * @param string $xpath
+ * @return string
+ */
+ protected function prepare_xpath_for_javascript(string $xpath): string {
+ $newlines = [
+ "\r\n",
+ "\r",
+ "\n",
+ ];
+ return str_replace($newlines, ' ', $xpath);
+ }
}
diff --git a/lib/tests/behat/behat_accessibility.php b/lib/tests/behat/behat_accessibility.php
index 4f53755cb76..42e241b49cb 100644
--- a/lib/tests/behat/behat_accessibility.php
+++ b/lib/tests/behat/behat_accessibility.php
@@ -14,7 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see .
-use Behat\Mink\Exception\{DriverException, ExpectationException};
+use Behat\Mink\Exception\DriverException;
+use Behat\Mink\Exception\ExpectationException;
+use Behat\Mink\Element\NodeElement;
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
@@ -29,7 +31,6 @@ require_once(__DIR__ . '/../../behat/behat_base.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_accessibility extends behat_base {
-
/**
* Run the axe-core accessibility tests.
*
@@ -52,6 +53,37 @@ class behat_accessibility extends behat_base {
);
}
+ /**
+ * Run the axe-core accessibility tests for a page region.
+ *
+ * There are standard tags to ensure WCAG 2.1 A, WCAG 2.1 AA, and Section 508 compliance.
+ * It is also possible to specify any desired optional tags.
+ *
+ * See {@link https://github.com/dequelabs/axe-core/blob/v4.10.0/doc/rule-descriptions.md} for the list of available tags
+ *
+ * @Then the :element :selector should meet accessibility standards
+ * @Then the :element :selector should meet accessibility standards with :extratags extra tests
+ * @Then the :element :selector should meet :standardtags accessibility standards
+ * @param string $element The element to run the tests on
+ * @param string $selector The selector to use to find the element
+ * @param string $standardtags Comma-separated list of standard tags to run
+ * @param string $extratags Comma-separated list of tags to run in addition to the standard tags
+ */
+ public function run_axe_validation_for_tags_within_element(
+ string $element,
+ string $selector,
+ string $standardtags = '',
+ string $extratags = '',
+ ): void {
+ $node = $this->get_selected_node($selector, $element);
+ $this->run_axe_for_tags(
+ // Turn the comma-separated string into an array of trimmed values, filtering out empty values.
+ array_filter(array_map('trim', explode(',', $standardtags))),
+ array_filter(array_map('trim', explode(',', $extratags))),
+ $node,
+ );
+ }
+
/**
* Run the Axe tests.
*
@@ -60,8 +92,13 @@ class behat_accessibility extends behat_base {
*
* @param array $standardtags The list of standard tags to run
* @param array $extratags The list of tags, in addition to the standard tags, to run
+ * @param null|NodeElement $containerelement The element to run the tests on
*/
- protected function run_axe_for_tags(array $standardtags = [], array $extratags = []): void {
+ protected function run_axe_for_tags(
+ array $standardtags = [],
+ array $extratags = [],
+ ?NodeElement $containerelement = null,
+ ): void {
if (!behat_config_manager::get_behat_run_config_value('axe')) {
return;
}
@@ -76,13 +113,33 @@ class behat_accessibility extends behat_base {
$axeurl = (new \moodle_url('/lib/behat/axe/axe.min.js'))->out(false);
$axeconfig = $this->get_axe_config_for_tags($standardtags, $extratags);
+ $xpath = '';
+ if ($containerelement) {
+ $xpath = $this->prepare_xpath_for_javascript($containerelement->getXpath());
+ }
$runaxe = << {
const runTests = () => {
const axeTag = document.querySelector('script[data-purpose="axe"]');
axeTag.dataset.results = null;
- axe.run({$axeconfig})
+ const getRun = () => {
+ const xpath = "{$xpath}";
+ if (xpath.length) {
+ const targetElements = [];
+ const results = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
+ let targetElement = results.iterateNext();
+ while (targetElement) {
+ targetElements.push(targetElement);
+ targetElement = results.iterateNext();
+ }
+ return axe.run(targetElements, {$axeconfig});
+ }
+
+ return axe.run({$axeconfig});
+ };
+
+ getRun()
.then(results => {
axeTag.dataset.results = JSON.stringify({
violations: results.violations,
diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php
index f7e5e929d3d..303b01daa30 100644
--- a/lib/tests/behat/behat_general.php
+++ b/lib/tests/behat/behat_general.php
@@ -997,13 +997,12 @@ class behat_general extends behat_base {
[$preselector, $prelocator] = $this->transform_selector($preselectortype, $preelement);
[$postselector, $postlocator] = $this->transform_selector($postselectortype, $postelement);
- $newlines = [
- "\r\n",
- "\r",
- "\n",
- ];
- $prexpath = str_replace($newlines, ' ', $this->find($preselector, $prelocator, false, $containernode)->getXpath());
- $postxpath = str_replace($newlines, ' ', $this->find($postselector, $postlocator, false, $containernode)->getXpath());
+ $prexpath = $this->prepare_xpath_for_javascript(
+ $this->find($preselector, $prelocator, false, $containernode)->getXpath()
+ );
+ $postxpath = $this->prepare_xpath_for_javascript(
+ $this->find($postselector, $postlocator, false, $containernode)->getXpath()
+ );
if ($this->running_javascript()) {
// The xpath to do this was running really slowly on certain Chrome versions so we are using