Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Steps definitions to open and close action menus.
19
 *
20
 * @package    core
21
 * @category   test
22
 * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
use Behat\Mink\Exception\{DriverException, ExpectationException};
27
 
28
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
29
 
30
require_once(__DIR__ . '/../../behat/behat_base.php');
31
 
32
/**
33
 * Steps definitions to assist with accessibility testing.
34
 *
35
 * @package    core
36
 * @category   test
37
 * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class behat_accessibility extends behat_base {
41
 
42
    /**
43
     * Run the axe-core accessibility tests.
44
     *
45
     * There are standard tags to ensure WCAG 2.1 A, WCAG 2.1 AA, and Section 508 compliance.
46
     * It is also possible to specify any desired optional tags.
47
     *
48
     * The list of available tags can be found at
49
     * https://github.com/dequelabs/axe-core/blob/v4.8.4/doc/rule-descriptions.md.
50
     *
51
     * @Then the page should meet accessibility standards
52
     * @Then the page should meet accessibility standards with :extratags extra tests
53
     * @Then the page should meet :standardtags accessibility standards
54
     * @param   string $standardtags Comma-separated list of standard tags to run
55
     * @param   string $extratags Comma-separated list of tags to run in addition to the standard tags
56
     */
57
    public function run_axe_validation_for_tags(string $standardtags = '', string $extratags = ''): void {
58
        $this->run_axe_for_tags(
59
            // Turn the comma-separated string into an array of trimmed values, filtering out empty values.
60
            array_filter(array_map('trim', explode(',', $standardtags))),
61
            array_filter(array_map('trim', explode(',', $extratags)))
62
        );
63
    }
64
 
65
    /**
66
     * Run the Axe tests.
67
     *
68
     * See https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md for details of the supported
69
     * tags.
70
     *
71
     * @param   array $standardtags The list of standard tags to run
72
     * @param   array $extratags The list of tags, in addition to the standard tags, to run
73
     */
74
    protected function run_axe_for_tags(array $standardtags = [], array $extratags = []): void {
75
        if (!behat_config_manager::get_behat_run_config_value('axe')) {
76
            return;
77
        }
78
 
79
        if (!$this->has_tag('accessibility')) {
80
            throw new DriverException(
81
                'Accessibility tests using Axe must have the @accessibility tag on either the scenario or feature.'
82
            );
83
        }
84
 
85
        $this->require_javascript();
86
 
87
        $axeurl = (new \moodle_url('/lib/behat/axe/axe.min.js'))->out(false);
88
        $axeconfig = $this->get_axe_config_for_tags($standardtags, $extratags);
89
        $runaxe = <<<EOF
90
(axeurl => {
91
    const runTests = () => {
92
        const axeTag = document.querySelector('script[data-purpose="axe"]');
93
        axeTag.dataset.results = null;
94
 
95
        axe.run({$axeconfig})
96
        .then(results => {
97
            axeTag.dataset.results = JSON.stringify({
98
                violations: results.violations,
99
                exception: null,
100
            });
101
        })
102
        .catch(exception => {
103
            axeTag.dataset.results = JSON.stringify({
104
                violations: [],
105
                exception: exception,
106
            });
107
        });
108
    };
109
 
110
    if (document.querySelector('script[data-purpose="axe"]')) {
111
        runTests();
112
    } else {
113
        // Inject the axe content.
114
        const axeTag = document.createElement('script');
115
        axeTag.src = axeurl,
116
        axeTag.dataset.purpose = 'axe';
117
 
118
        axeTag.onload = () => runTests();
119
        document.head.append(axeTag);
120
    }
121
})('{$axeurl}');
122
EOF;
123
 
124
        $this->execute_script($runaxe);
125
 
126
        $getresults = <<<EOF
127
return (() => {
128
    const axeTag = document.querySelector('script[data-purpose="axe"]');
129
    return axeTag.dataset.results;
130
})()
131
EOF;
132
 
133
        for ($i = 0; $i < self::get_extended_timeout() * 10; $i++) {
134
            $results = json_decode($this->evaluate_script($getresults) ?? '');
135
            if ($results) {
136
                break;
137
            }
138
        }
139
 
140
        if (empty($results)) {
141
            throw new \Exception('No data');
142
        }
143
 
144
        if ($results->exception !== null) {
145
            throw new ExpectationException($results->exception, $this->getSession());
146
        }
147
 
148
        $violations = $results->violations;
149
        if (!count($violations)) {
150
            return;
151
        }
152
 
153
        $violationdata = "Accessibility violations found:\n";
154
        foreach ($violations as $violation) {
155
            $nodedata = '';
156
            foreach ($violation->nodes as $node) {
157
                $failedchecks = [];
158
                foreach (array_merge($node->any, $node->all, $node->none) as $check) {
159
                    $failedchecks[$check->id] = $check->message;
160
                }
161
 
162
                $nodedata .= sprintf(
163
                    "    - %s:\n      %s\n\n",
164
                    implode(', ', $failedchecks),
165
                    implode("\n      ", $node->target)
166
                );
167
            }
168
 
169
            $violationdata .= sprintf(
170
                "  %.03d violations of '%s' (severity: %s)\n%s\n",
171
                count($violation->nodes),
172
                $violation->description,
173
                $violation->impact,
174
                $nodedata
175
            );
176
        }
177
 
178
        throw new ExpectationException($violationdata, $this->getSession());
179
    }
180
 
181
    /**
182
     * Get the configuration to use with Axe.
183
     *
184
     * See https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md for details of the rules.
185
     *
186
     * @param   array|null $standardtags The list of standard tags to run
187
     * @param   array|null $extratags The list of tags, in addition to the standard tags, to run
188
     * @return  string The JSON-encoded configuration.
189
     */
190
    protected function get_axe_config_for_tags(?array $standardtags = null, ?array $extratags = null): string {
191
        if (empty($standardtags)) {
192
            $standardtags = [
11 efrain 193
                // Meet WCAG 2.2 Level A success criteria.
194
                'wcag22a',
1 efrain 195
 
11 efrain 196
                // Meet WCAG 2.2 Level AA success criteria.
197
                'wcag22aa',
1 efrain 198
 
199
                // Meet Section 508 requirements.
200
                // See https://www.epa.gov/accessibility/what-section-508 for detail.
201
                'section508',
202
 
203
                // Ensure that ARIA attributes are correctly defined.
204
                'cat.aria',
205
 
206
                // Requirements for sensory and visual cues.
207
                // These largely related to viewport scale and zoom functionality.
208
                'cat.sensory-and-visual-cues',
209
 
210
                // Meet WCAG 1.3.4 requirements for orientation.
211
                // See https://www.w3.org/WAI/WCAG21/Understanding/orientation.html for detail.
212
                'wcag134',
213
            ];
214
        }
215
 
216
        return json_encode([
217
            'runOnly' => [
218
                'type' > 'tag',
219
                'values' => array_merge($standardtags, $extratags),
220
            ],
221
        ]);
222
    }
223
}