148 lines
4.7 KiB
PHP
148 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types = 1);
|
|
|
|
/**
|
|
* A helper class for Codeception (http://codeception.com/) that allows automated accessility checks
|
|
* (WCAG 2.0, Section508) using the pa11y (http://pa11y.org/) command line tool
|
|
* during acceptance testing.
|
|
* It uses local binaries and can therefore be run offline.
|
|
*
|
|
* Requirements:
|
|
* =============
|
|
*
|
|
* - Codeception with WebDriver or PhpBrowser set up
|
|
* - pa11y is installed locally (e.g. using "npm insgall -g pa11y")
|
|
*
|
|
*
|
|
* Installation:
|
|
* =============
|
|
*
|
|
* - Copy this file to _support/Helper/ in the codeception directory
|
|
* - Merge the following configuration to acceptance.suite.yml:
|
|
*
|
|
* modules:
|
|
* enabled:
|
|
* - \Helper\AccessibilityValidator
|
|
* config:
|
|
* \Helper\AccessibilityValidator:
|
|
* pa11yPath: /usr/local/bin/pa11y
|
|
*
|
|
*
|
|
* Usage:
|
|
* ======
|
|
*
|
|
* Validate the current site against WCAG 2.0 (AAA):
|
|
* $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2AAA);
|
|
*
|
|
* Validate the current site against WCAG 2.0 (AA):
|
|
* $I->validatePa11y(); // or:
|
|
* $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A);
|
|
*
|
|
* Validate the current site against WCAG 2.0 (A):
|
|
* $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A);
|
|
*
|
|
* Validate the current site against Section 508:
|
|
* $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_SECTION508);
|
|
*
|
|
* Validate against WCAG 2.0 (AA), but ignore errors containing the string "Ignoreme":
|
|
* $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A, ["Ignoreme"]);
|
|
*
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
|
* @author Tobias Hößl <tobias@hoessl.eu>
|
|
*/
|
|
|
|
namespace Helper;
|
|
|
|
use Exception;
|
|
use PHPUnit\Framework\Assert;
|
|
|
|
class AccessibilityValidator extends \Codeception\Module
|
|
{
|
|
public static $SUPPORTED_STANDARDS = [
|
|
'WCAG2AAA',
|
|
'WCAG2AA',
|
|
'WCAG2A',
|
|
'Section508',
|
|
];
|
|
public const STANDARD_WCAG2AAA = 'WCAG2AAA';
|
|
public const STANDARD_WCAG2AA = 'WCAG2AA';
|
|
public const STANDARD_WCAG2A = 'WCAG2A';
|
|
public const STANDARD_SECTION508 = 'Section508';
|
|
|
|
private function getPageUrl(): string
|
|
{
|
|
if ($this->hasModule('WebDriver')) {
|
|
/** @var \Codeception\Module\WebDriver $webdriver */
|
|
$webdriver = $this->getModule('WebDriver');
|
|
return $webdriver->webDriver->getCurrentURL();
|
|
} else {
|
|
/** @var \Codeception\Module\PhpBrowser $phpBrowser */
|
|
$phpBrowser = $this->getModule('PhpBrowser');
|
|
return trim($phpBrowser->_getUrl(), '/') . $phpBrowser->_getCurrentUri();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
private function validateByPa11y(string $url, string $standard): array
|
|
{
|
|
if (!\in_array($standard, static::$SUPPORTED_STANDARDS)) {
|
|
throw new Exception('Unknown standard: ' . $standard);
|
|
}
|
|
|
|
$pa11yPath = $this->_getConfig('pa11yPath');
|
|
if (!$pa11yPath) {
|
|
$pa11yPath = 'pa11y';
|
|
}
|
|
if (!file_exists($pa11yPath)) {
|
|
throw new Exception('pa11y not found: ' . $pa11yPath);
|
|
}
|
|
|
|
exec($pa11yPath . ' -s ' . $standard . " -r json '" . addslashes($url) . "'", $return);
|
|
$data = json_decode($return[0], true);
|
|
if (!$data) {
|
|
$msg = 'Invalid data returned from validation service: ';
|
|
throw new Exception($msg . $return);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $ignoreMessages
|
|
*/
|
|
public function validatePa11y(string $standard = 'WCAG2AA', array $ignoreMessages = []): void
|
|
{
|
|
try {
|
|
$url = $this->getPageUrl();
|
|
$messages = $this->validateByPa11y($url, $standard);
|
|
} catch (Exception $e) {
|
|
$this->fail($e->getMessage());
|
|
return;
|
|
}
|
|
$failMessages = [];
|
|
foreach ($messages as $message) {
|
|
if ($message['type'] == 'error') {
|
|
$string = $message['code'] . "\n" . $message['selector'] . ': ';
|
|
$string .= $message['context'] . "\n";
|
|
$string .= $message['message'];
|
|
$ignoring = false;
|
|
foreach ($ignoreMessages as $ignoreMessage) {
|
|
if (mb_stripos($string, $ignoreMessage) !== false) {
|
|
$ignoring = true;
|
|
}
|
|
}
|
|
if (!$ignoring) {
|
|
$failMessages[] = $string;
|
|
}
|
|
}
|
|
}
|
|
if (\count($failMessages) > 0) {
|
|
$failStr = 'Failed ' . $standard . ' check: ' . "\n";
|
|
$failStr .= implode("\n\n", $failMessages);
|
|
Assert::fail($failStr);
|
|
}
|
|
}
|
|
}
|