Skip to content

Commit

Permalink
DQA-9426: Create toolkit command to run AXE Scanner (#774)
Browse files Browse the repository at this point in the history
Co-authored-by: J. João Santos <[email protected]>
  • Loading branch information
joaocsilva and jonhy81 authored May 23, 2024
1 parent 47f456d commit 5d244e3
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ docs/*
!docs/guide/
docs/guide/*
!docs/guide/*.rst

# Exclude axe-scan related files.
/axe*
12 changes: 12 additions & 0 deletions config/runner/toolkit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,15 @@ toolkit:
extensions: [ 'php', 'module', 'inc', 'theme', 'install' ]
exclude: [ '${toolkit.build.dist.root}/', '.cache/', 'vendor/', 'web/' ]
options: ''
axe-scan:
config: axe-scan.config.json
urls:
- '/'
file-path: axe-scan-urls.txt
result-types: [ 'incomplete', 'violations' ]
core-tags: [ 'wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa' ]
locale: en
result-file: axe-scan-results.csv
allow-list: axe-scan-allowlist.csv
run-summary: true
summary-result-file: axe-scan-summary.csv
168 changes: 168 additions & 0 deletions src/TaskRunner/Commands/AxeCommands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php

declare(strict_types=1);

namespace EcEuropa\Toolkit\TaskRunner\Commands;

use EcEuropa\Toolkit\TaskRunner\AbstractCommands;

/**
* Commands to interact with the axe-scan.
*
* @see https://github.com/ttsukagoshi/axe-scan
* @see https://github.com/puppeteer/puppeteer
* @see https://www.deque.com/axe/core-documentation/api-documentation/
*/
class AxeCommands extends AbstractCommands
{

/**
* A list of dependencies for axe-scan.
*
* @var array|string[]
*/
private array $dependencies = [
'libnss3-tools',
'libatk1.0-0',
'libatk-bridge2.0-0',
'libdrm2',
'libxcomposite1',
'libxdamage1',
'libxfixes3',
'libxrandr2',
'libgbm1',
'libxkbcommon-x11-0',
'libpangocairo-1.0-0',
'libasound2',
'fonts-liberation',
'libgcc1',
'libxss1',
'libgtk-3-0',
'libx11-xcb1',
'libxcursor1',
'xdg-utils',
];

/**
* Run the axe-scan.
*
* @command toolkit:run-axe-scan
*
* @aliases tk-axe
*/
public function toolkitRunAxeScan()
{
$tasks = [];

$tasks[] = $this->taskExec($this->getBin('run'))->arg('toolkit:setup-axe-scan');

$config = $this->getConfigValue('toolkit.axe-scan');
$exec = $this->taskExec($this->getNodeBinPath('axe-scan'))
->arg('run')
->option('file', $config['file-path']);

if (!empty($config['allow-list']) && file_exists($config['allow-list'])) {
$exec->option('allowlist', $config['allow-list']);
}
if (!empty($config['result-file'])) {
$exec->rawArg('> ' . $config['result-file']);
}
$tasks[] = $exec;

if (!empty($config['run-summary'])) {
$tasks[] = $this->taskExec($this->getBin('run'))->arg('toolkit:run-axe-scan-summary');
}

return $this->collectionBuilder()->addTaskList($tasks);
}

/**
* Run the axe-scan summary.
*
* @command toolkit:run-axe-scan-summary
*
* @aliases tk-axe-sum
*/
public function toolkitRunAxeScanSummary()
{
$config = $this->getConfigValue('toolkit.axe-scan');

$exec = $this->taskExec($this->getNodeBinPath('axe-scan'))
->arg('summary');

if (!empty($config['allow-list']) && file_exists($config['allow-list'])) {
$exec->option('allowlist', $config['allow-list']);
}
if (!empty($config['summary-result-file'])) {
$exec->rawArg('> ' . $config['summary-result-file']);
}
return $this->collectionBuilder()->addTask($exec);
}

/**
* Make sure axe-scan is installed and properly configured.
*
* @command toolkit:setup-axe-scan
*/
public function toolkitSetupAxeScan()
{
$tasks = [];

// Install dependencies if the bin is not present.
if (!file_exists($this->getNodeBinPath('axe-scan'))) {
$tasks[] = $this->taskExecStack()
->exec('npm -v || npm i npm')
->exec('[ -f package.json ] || npm init -y --scope')
->exec('npm list axe-scan && npm update axe-scan || npm install axe-scan -y');
}

// Install linux dependencies.
$tasks[] = $this->taskExec($this->getBin('run'))
->arg('toolkit:install-dependencies')
->option('packages', implode(',', $this->dependencies));

$config = $this->getConfigValue('toolkit.axe-scan');

// Generate the URLs file.
$baseUrl = $this->getConfigValue('drupal.base_url');
$urls = array_map(function ($url) use ($baseUrl) {
return rtrim($baseUrl, '/') . '/' . ltrim($url, '/');
}, $config['urls']);
$tasks[] = $this->taskWriteToFile($config['file-path'])
->text(implode(PHP_EOL, $urls));

// Generate the config file.
$data = [
'axeCoreTags' => $config['core-tags'],
'resultTypes' => $config['result-types'],
'filePath' => $config['file-path'],
'locale' => $config['locale'],
];
$tasks[] = $this->taskWriteToFile($config['config'])
->text(json_encode($data, JSON_PRETTY_PRINT) . PHP_EOL);

// Apply temporary patch to axe-scan when starting puppeteer to have the
// option --no-sandbox, this avoids the error: Running as root without
// --no-sandbox is not supported.
$files = [
'node_modules/axe-scan/build/src/commands/run.js',
'node_modules/axe-scan/build/src/commands/summary.js',
];
$from = 'const browser = await puppeteer.launch();';
$args = '["--no-sandbox", "--disable-setuid-sandbox", "--single-process", "--disable-impl-side-painting", "--disable-gpu-sandbox", "--disable-accelerated-2d-canvas", "--disable-accelerated-jpeg-decoding", "--disable-dev-shm-usage"]';
$to = 'const browser = await puppeteer.launch({args: ' . $args . '});';
foreach ($files as $file) {
if (file_exists($file)) {
$tasks[] = $this->taskReplaceInFile($file)->from($from)->to($to);
}
}

// Make sure puppeteer is installed.
if (file_exists('node_modules/puppeteer/install.mjs')) {
$tasks[] = $this->taskExec('node node_modules/puppeteer/install.mjs');
}

return $this->collectionBuilder()->addTaskList($tasks);
}

}
25 changes: 16 additions & 9 deletions src/TaskRunner/Commands/ToolCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -594,30 +594,37 @@ public static function formatBytes($bytes, $precision = 2)
}

/**
* Install packages present in the opts.yml file under extra_pkgs section.
* Install packages present in the .opts.yml file under extra_pkgs section.
*
* @command toolkit:install-dependencies
*
* @option print Shows output from apt commands.
* @option packages Specify a list of packages to install instead of read from .opts.yml.
* @option print Shows output from apt commands.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function toolkitInstallDependencies(ConsoleIO $io, array $options = [
'packages' => InputOption::VALUE_REQUIRED,
'print' => InputOption::VALUE_NONE,
])
{
$return = 0;
if (!$this->getConfig()->get('toolkit.install_dependencies')) {
return $return;
}
if (!file_exists('.opts.yml')) {
return $return;
}
$opts = Yaml::parseFile('.opts.yml');
$packages = $opts['extra_pkgs'] ?? [];
if (empty($packages)) {
return $return;

if (empty($options['packages'])) {
if (!($opts = self::parseOptsYml())) {
return $return;
}
$packages = $opts['extra_pkgs'] ?? [];
if (empty($packages)) {
return $return;
}
} else {
Toolkit::ensureArray($options['packages']);
$packages = $options['packages'];
}

$io->title('Installing dependencies');
Expand Down

0 comments on commit 5d244e3

Please sign in to comment.