You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digitransit-ui/test/accessibility/run-test.js

294 lines
8.4 KiB
JavaScript

/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-console */
const parallel = require('async/parallel');
const AxeBuilder = require('@axe-core/webdriverjs');
const { Builder, By, Key, until } = require('selenium-webdriver');
const firefox = require('selenium-webdriver/firefox');
const args = process.argv.slice(2);
const onlyTestLocal = args.includes('local');
console.log(`Testing against benchmark: ${!onlyTestLocal}`);
const LOCAL = 'http://127.0.0.1:8080';
const BENCHMARK = 'https://reittiopas.hsl.fi';
const RERUN_COUNT = 3;
let CURRENT_RUN = 0;
const localResults = {
violations: [],
passes: [],
incomplete: [],
inapplicable: [],
};
const benchmarkResults = {
violations: [],
passes: [],
incomplete: [],
inapplicable: [],
};
const color = {
critical: '\x1b[1m\x1b[31m',
serious: '\x1b[31m',
moderate: '\x1b[33m',
minor: '\x1b[33m',
};
process.exitCode = 1;
const createDriver = () => {
return new Builder()
.forBrowser('firefox')
.setFirefoxOptions(new firefox.Options().headless())
.build();
};
const createBuilder = driver => {
return new AxeBuilder(driver)
.setLegacyMode()
.exclude('.map')
.disableRules('color-contrast'); // Color-contrast checks seem inconsistent, can be possibly enabled in newer axe versions
};
const createTestEnv = () => {
const driver = createDriver();
const builder = createBuilder(driver);
return [driver, builder];
};
const saveTestResults = (results, benchmark) => {
const location = benchmark ? benchmarkResults : localResults;
const { violations, passes, incomplete, inapplicable } = results;
location.violations = [...location.violations, ...violations];
location.passes = [...location.passes, ...passes];
location.incomplete = [...location.incomplete, ...incomplete];
location.inapplicable = [...location.inapplicable, ...inapplicable];
};
const printTestResults = (results, printResults, url) => {
const { violations } = results;
if (printResults) {
console.log(`RESULTS for ${url}: `);
console.log('Violations: ');
}
for (let j = 0; j < results.violations.length; j++) {
const v = violations[j];
v.url = url;
const firstTargetElement =
v.nodes.length > 0 ? `- on element: ${v.nodes[0].target[0]}` : '';
if (printResults) {
console.log(
color[v.impact],
`${v.impact} - ${v.id}: ${v.help} ${firstTargetElement}`,
'\x1b[0m',
);
}
}
};
async function analyzeWithAxe(builder, printResults, benchmark, path) {
return builder.analyze((err, results) => {
if (err) {
console.log(err);
} else {
saveTestResults(results, benchmark);
printTestResults(results, printResults, path);
}
});
}
async function frontPageTest(rootUrl, printResults, benchmark, path) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
async function routePageTest(rootUrl, printResults, benchmark, path) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
async function routePageTimetableTest(rootUrl, printResults, benchmark, path) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
async function terminalPageTimetableTest(
rootUrl,
printResults,
benchmark,
path,
) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
async function stopsNearYouTest(rootUrl, printResults, benchmark, path) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
async function ItineraryTest(rootUrl, printResults, benchmark, path) {
const [driver, builder] = createTestEnv();
const url = `${rootUrl}${path}`;
await driver.get(url);
// Open settings menu
const el = await driver.findElement(
By.className('open-advanced-settings-window-button'),
);
await driver.wait(until.elementIsVisible(el), 1000);
await el.sendKeys(Key.RETURN);
await analyzeWithAxe(builder, printResults, benchmark, path);
driver.quit();
}
const TEST_CASES = {
'/etusivu': frontPageTest,
'/linjat/HSL:3002P/pysakit/HSL:3002P:0:01': routePageTest,
'/linjat/HSL:3002P/aikataulu/HSL:3002P:0:01': routePageTimetableTest,
'/terminaalit/HSL%3A2000102/aikataulu': terminalPageTimetableTest,
'/lahellasi/BUS/Rautatientori%2C%20Helsinki::60.170384,24.939846':
stopsNearYouTest,
'/reitti/Otakaari%2024%2C%20Espoo%3A%3A60.1850004462205%2C24.832384918447488/L%C3%B6nnrotinkatu%2029%2C%20Helsinki%3A%3A60.164182342362864%2C24.932237237563104':
ItineraryTest,
};
async function runTestCases(
rootUrl,
isBenchmark,
callback,
printResults,
pathsToTest = undefined,
) {
for (const [path, test] of Object.entries(TEST_CASES)) {
if (pathsToTest) {
if (pathsToTest.includes(path)) {
await test(rootUrl, printResults, isBenchmark, path);
}
} else {
await test(rootUrl, printResults, isBenchmark, path);
}
}
callback(null);
}
const violationsAreEqual = (v1, v2) => {
return v1.url === v2.url && v1.id === v2.id;
};
const resetErrorsRelatedtoURL = urls => {
localResults.violations = localResults.violations.filter(
v => !urls.has(v.url),
);
localResults.passes = localResults.passes.filter(v => !urls.has(v.url));
benchmarkResults.violations = benchmarkResults.violations.filter(
v => !urls.has(v.url),
);
benchmarkResults.passes = benchmarkResults.passes.filter(
v => !urls.has(v.url),
);
};
const wrapup = () => {
console.timeEnd('Execution time');
if (CURRENT_RUN === 0) {
console.log('=== ACCESSIBILITY TESTS DONE ===');
}
console.log(
`violations in LOCAL: ${localResults.violations.length}, passes: ${localResults.passes.length}, incomplete: ${localResults.incomplete.length}, inapplicable: ${localResults.inapplicable.length}`,
);
if (!onlyTestLocal) {
console.log(
`violations in BENCHMARK: ${benchmarkResults.violations.length}, passes: ${benchmarkResults.passes.length}, incomplete: ${benchmarkResults.incomplete.length}, inapplicable: ${benchmarkResults.inapplicable.length}`,
);
const newViolations = localResults.violations.filter(v1 => {
return !benchmarkResults.violations.some(v2 => {
return violationsAreEqual(v1, v2);
});
});
if (newViolations.length > 0) {
if (CURRENT_RUN < RERUN_COUNT) {
console.log('Found new violation, re-running the test to verify...');
CURRENT_RUN += 1;
const urlsWithErrors = new Set();
newViolations.forEach(v => urlsWithErrors.add(v.url));
resetErrorsRelatedtoURL(urlsWithErrors);
// eslint-disable-next-line no-use-before-define
runTests(false, Array.from(urlsWithErrors));
return;
}
console.log('New Errors introduced: ');
let lastUrl = '';
for (let j = 0; j < newViolations.length; j++) {
const v = newViolations[j];
if (lastUrl !== v.url) {
lastUrl = v.url;
console.log(`On url: ${LOCAL}${v.url}`);
}
const firstTargetElement =
v.nodes.length > 0 ? `- on element: ${v.nodes[0].target[0]}` : '';
console.log(
color[v.impact],
`${v.impact} - ${v.id}: ${v.help} ${firstTargetElement}`,
'\x1b[0m',
);
}
} else {
process.exitCode = 0;
console.log('No new erros');
}
}
if (onlyTestLocal && localResults.violations.length === 0) {
process.exitCode = 0;
}
};
const runTests = (printResults, pathsToTest = undefined) => {
try {
console.time('Execution time');
parallel(
[
callback => {
runTestCases(LOCAL, false, callback, printResults, pathsToTest);
},
callback => {
if (!onlyTestLocal) {
runTestCases(BENCHMARK, true, callback, false, pathsToTest);
} else {
callback(null);
}
},
],
err => {
if (err) {
throw new Error(err);
}
wrapup();
},
);
} catch (e) {
throw new Error(e);
}
};
runTests(true);