upstream V3 development https://www.gnusocial.rocks/v3
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

143 lines
4.6 KiB

  1. <?php
  2. declare(strict_types = 1);
  3. /**
  4. * A helper class for Codeception (http://codeception.com/) that allows automated accessility checks
  5. * (WCAG 2.0, Section508) using the pa11y (http://pa11y.org/) command line tool
  6. * during acceptance testing.
  7. * It uses local binaries and can therefore be run offline.
  8. *
  9. * Requirements:
  10. * =============
  11. *
  12. * - Codeception with WebDriver or PhpBrowser set up
  13. * - pa11y is installed locally (e.g. using "npm insgall -g pa11y")
  14. *
  15. *
  16. * Installation:
  17. * =============
  18. *
  19. * - Copy this file to _support/Helper/ in the codeception directory
  20. * - Merge the following configuration to acceptance.suite.yml:
  21. *
  22. * modules:
  23. * enabled:
  24. * - \Helper\AccessibilityValidator
  25. * config:
  26. * \Helper\AccessibilityValidator:
  27. * pa11yPath: /usr/local/bin/pa11y
  28. *
  29. *
  30. * Usage:
  31. * ======
  32. *
  33. * Validate the current site against WCAG 2.0 (AAA):
  34. * $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2AAA);
  35. *
  36. * Validate the current site against WCAG 2.0 (AA):
  37. * $I->validatePa11y(); // or:
  38. * $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A);
  39. *
  40. * Validate the current site against WCAG 2.0 (A):
  41. * $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A);
  42. *
  43. * Validate the current site against Section 508:
  44. * $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_SECTION508);
  45. *
  46. * Validate against WCAG 2.0 (AA), but ignore errors containing the string "Ignoreme":
  47. * $I->validatePa11y(\Helper\AccessibilityValidator::STANDARD_WCAG2A, ["Ignoreme"]);
  48. *
  49. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  50. * @author Tobias Hößl <tobias@hoessl.eu>
  51. */
  52. namespace Helper;
  53. use Exception;
  54. use PHPUnit\Framework\Assert;
  55. class AccessibilityValidator extends \Codeception\Module
  56. {
  57. public static $SUPPORTED_STANDARDS = [
  58. 'WCAG2AAA',
  59. 'WCAG2AA',
  60. 'WCAG2A',
  61. 'Section508',
  62. ];
  63. public const STANDARD_WCAG2AAA = 'WCAG2AAA';
  64. public const STANDARD_WCAG2AA = 'WCAG2AA';
  65. public const STANDARD_WCAG2A = 'WCAG2A';
  66. public const STANDARD_SECTION508 = 'Section508';
  67. private function getPageUrl(): string
  68. {
  69. if ($this->hasModule('WebDriver')) {
  70. /** @var \Codeception\Module\WebDriver $webdriver */
  71. $webdriver = $this->getModule('WebDriver');
  72. return $webdriver->webDriver->getCurrentURL();
  73. } else {
  74. /** @var \Codeception\Module\PhpBrowser $phpBrowser */
  75. $phpBrowser = $this->getModule('PhpBrowser');
  76. return trim($phpBrowser->_getUrl(), '/') . $phpBrowser->_getCurrentUri();
  77. }
  78. }
  79. /**
  80. * @throws Exception
  81. */
  82. private function validateByPa11y(string $url, string $standard): array
  83. {
  84. if (!\in_array($standard, static::$SUPPORTED_STANDARDS)) {
  85. throw new Exception('Unknown standard: ' . $standard);
  86. }
  87. exec('sshpass -p pa11y ssh -o StrictHostKeyChecking=no pa11y 2>/dev/null pa11y -c /pa11y/config.json' . ' -s ' . $standard . " -r json '" . addslashes($url) . "'", $output);
  88. if (!empty($output)) {
  89. $data = json_decode($output[0], true);
  90. if (!$data) {
  91. throw new Exception('Invalid data returned from validation service: ' . implode("\n", $output));
  92. }
  93. return $data;
  94. }
  95. return [];
  96. }
  97. /**
  98. * @param string[] $ignoreMessages
  99. */
  100. public function validatePa11y(string $standard = 'WCAG2AA', array $ignoreMessages = []): void
  101. {
  102. try {
  103. $url = $this->getPageUrl();
  104. $messages = $this->validateByPa11y($url, $standard);
  105. } catch (Exception $e) {
  106. $this->fail($e->getMessage());
  107. return;
  108. }
  109. $failMessages = [];
  110. foreach ($messages as $message) {
  111. if ($message['type'] == 'error') {
  112. $string = $message['code'] . "\n" . $message['selector'] . ': ';
  113. $string .= $message['context'] . "\n";
  114. $string .= $message['message'];
  115. $ignoring = false;
  116. foreach ($ignoreMessages as $ignoreMessage) {
  117. if (mb_stripos($string, $ignoreMessage) !== false) {
  118. $ignoring = true;
  119. }
  120. }
  121. if (!$ignoring) {
  122. $failMessages[] = $string;
  123. }
  124. }
  125. }
  126. if (\count($failMessages) > 0) {
  127. $failStr = 'Failed ' . $standard . ' check: ' . "\n";
  128. $failStr .= implode("\n\n", $failMessages);
  129. Assert::fail($failStr);
  130. }
  131. }
  132. }