2022-05-24 19:42:36 +03:00
|
|
|
const expect = require("expect");
|
2023-06-01 00:39:07 +03:00
|
|
|
const fs = require("fs");
|
2022-05-24 19:42:36 +03:00
|
|
|
const puppeteer = require("puppeteer");
|
|
|
|
const httpServer = require("http-server");
|
|
|
|
const percySnapshot = require("@percy/puppeteer");
|
2022-01-22 03:49:18 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const platform = require("os").platform();
|
2022-01-22 03:49:18 +03:00
|
|
|
// We need to change the args passed to puppeteer based on the platform they're using
|
2022-05-24 19:42:36 +03:00
|
|
|
const puppeteerArgs = /^win/.test(platform) ? [] : ["--single-process"];
|
2022-01-22 03:49:18 +03:00
|
|
|
const PORT = process.env.PORT_NUMBER || 8000;
|
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const { AxePuppeteer } = require("@axe-core/puppeteer");
|
|
|
|
const assert = require("assert");
|
2022-04-14 20:15:15 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
describe("UI tests", function () {
|
2022-01-22 03:49:18 +03:00
|
|
|
let page;
|
|
|
|
let server;
|
|
|
|
let browser;
|
|
|
|
|
|
|
|
before(async () => {
|
2023-06-01 01:20:55 +03:00
|
|
|
let root = process.env.ROOT || `${__dirname}/../public`;
|
2023-06-01 00:39:07 +03:00
|
|
|
|
|
|
|
if (!fs.existsSync(root)) {
|
2023-06-01 01:20:55 +03:00
|
|
|
assert.fail(
|
|
|
|
`Root was specified as ${root}, but that path does not exist.`
|
|
|
|
);
|
2023-06-01 00:39:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!fs.existsSync(`${root}/index.html`)) {
|
2023-06-01 01:20:55 +03:00
|
|
|
assert.fail(
|
|
|
|
`Root was specified as ${root}, but does not contain an index.html.`
|
|
|
|
);
|
2023-06-01 00:39:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
server = httpServer.createServer({ root });
|
2022-01-22 03:49:18 +03:00
|
|
|
server.listen(PORT);
|
|
|
|
|
|
|
|
browser = await puppeteer.launch({
|
|
|
|
headless: true,
|
|
|
|
timeout: 10000,
|
2022-05-24 19:42:36 +03:00
|
|
|
args: puppeteerArgs,
|
2022-01-22 03:49:18 +03:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
after(() => {
|
|
|
|
server.close();
|
|
|
|
});
|
2019-11-15 14:16:10 +03:00
|
|
|
|
2023-06-01 00:14:11 +03:00
|
|
|
const handlePageErrors = function (page) {
|
2023-06-01 01:20:55 +03:00
|
|
|
page.on("pageerror", (err) => {
|
2023-06-01 00:14:11 +03:00
|
|
|
console.log("Error from page:", err.toString());
|
2023-06-01 01:20:55 +03:00
|
|
|
});
|
|
|
|
};
|
2023-06-01 00:14:11 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const handleAxeResults = function (name, results) {
|
2022-04-14 20:33:22 +03:00
|
|
|
const violations = results["violations"];
|
|
|
|
if (violations.length > 0) {
|
2022-05-24 19:42:36 +03:00
|
|
|
violations.map(function (violation) {
|
|
|
|
console.log("\n\n", violation["id"], ":", violation["description"]);
|
|
|
|
console.log(violation["help"]);
|
|
|
|
console.log(violation["helpUrl"]);
|
2022-04-14 20:33:22 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
console.table(violation["nodes"], ["html"]);
|
2022-04-14 20:33:22 +03:00
|
|
|
});
|
2022-05-24 19:42:36 +03:00
|
|
|
assert.fail(
|
|
|
|
`Expected no axe violations in ${name} but got ${violations.length} violations`
|
|
|
|
);
|
2022-04-14 20:33:22 +03:00
|
|
|
}
|
2022-05-24 19:42:36 +03:00
|
|
|
};
|
2022-04-14 20:33:22 +03:00
|
|
|
|
2022-06-27 21:00:54 +03:00
|
|
|
const goTo = async (name, location) => {
|
2022-06-29 03:42:12 +03:00
|
|
|
await page.goto(location, { waitUntil: "load" });
|
2022-07-20 20:08:59 +03:00
|
|
|
await page.waitForXPath(
|
|
|
|
`//h1[contains(., 'Nri.Ui.${name}') and @aria-current='page']`,
|
|
|
|
200
|
|
|
|
);
|
2022-06-27 21:00:54 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
const defaultProcessing = async (name, location) => {
|
|
|
|
await goTo(name, location);
|
2022-05-24 19:42:36 +03:00
|
|
|
await percySnapshot(page, name);
|
2022-04-14 20:33:22 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const results = await new AxePuppeteer(page)
|
|
|
|
.disableRules(skippedRules[name] || [])
|
|
|
|
.analyze();
|
2022-04-14 20:38:10 +03:00
|
|
|
handleAxeResults(name, results);
|
2022-05-24 19:42:36 +03:00
|
|
|
};
|
2022-01-22 03:49:18 +03:00
|
|
|
|
2022-07-15 01:36:09 +03:00
|
|
|
const forAllOptions = async (labelName, callback) => {
|
|
|
|
await page.waitForXPath(
|
|
|
|
`//label[contains(., '${labelName}')]//select`,
|
|
|
|
200
|
|
|
|
);
|
|
|
|
const [select] = await page.$x(
|
|
|
|
`//label[contains(., '${labelName}')]//select`
|
|
|
|
);
|
2022-07-15 02:05:37 +03:00
|
|
|
const options = await select.$x(
|
2022-07-15 01:36:09 +03:00
|
|
|
`//label[contains(., '${labelName}')]//option`
|
|
|
|
);
|
2022-07-21 17:53:12 +03:00
|
|
|
// Actually doing the select was super flakey.
|
|
|
|
// Temporarily just using the first element to get
|
|
|
|
// CI consistent again. We can cover all the cases separately...
|
|
|
|
const optionEl = options[0];
|
|
|
|
const option = await page.evaluate((el) => el.innerText, optionEl);
|
|
|
|
await page.select("select", option);
|
|
|
|
await callback(option);
|
2022-07-15 01:36:09 +03:00
|
|
|
};
|
|
|
|
|
2022-06-18 02:17:09 +03:00
|
|
|
const messageProcessing = async (name, location) => {
|
2022-06-27 21:00:54 +03:00
|
|
|
await goTo(name, location);
|
2022-06-18 02:17:09 +03:00
|
|
|
await percySnapshot(page, name);
|
|
|
|
|
|
|
|
var axe = await new AxePuppeteer(page)
|
|
|
|
.disableRules(skippedRules[name] || [])
|
|
|
|
.analyze();
|
|
|
|
handleAxeResults(name, axe);
|
|
|
|
|
|
|
|
const [theme] = await page.$x("//label[contains(., 'theme')]");
|
|
|
|
await theme.click();
|
|
|
|
|
2022-07-15 01:36:09 +03:00
|
|
|
await forAllOptions("theme", async (option) => {
|
2022-06-18 02:29:42 +03:00
|
|
|
await percySnapshot(page, `${name} - ${option}`);
|
2022-06-18 02:17:09 +03:00
|
|
|
axe = await new AxePuppeteer(page)
|
|
|
|
.withRules(["color-contrast"])
|
|
|
|
.analyze();
|
2022-06-18 02:29:42 +03:00
|
|
|
handleAxeResults(`${name} - ${option}`, axe);
|
2022-07-15 01:36:09 +03:00
|
|
|
});
|
2022-06-18 02:17:09 +03:00
|
|
|
};
|
|
|
|
|
2022-07-15 00:17:39 +03:00
|
|
|
const modalProcessing = async (name, location) => {
|
|
|
|
await goTo(name, location);
|
|
|
|
|
2022-07-28 03:31:22 +03:00
|
|
|
await page.click("#launch-modal");
|
|
|
|
await page.waitForSelector('[role="dialog"]');
|
|
|
|
await percySnapshot(page, `${name} - info`);
|
2022-07-15 01:36:09 +03:00
|
|
|
|
2022-07-28 03:31:22 +03:00
|
|
|
axe = await new AxePuppeteer(page).analyze();
|
2022-07-15 01:36:09 +03:00
|
|
|
|
2022-07-28 03:31:22 +03:00
|
|
|
await page.click('[aria-label="Close modal"]');
|
2022-07-15 00:17:39 +03:00
|
|
|
|
2022-07-28 03:31:22 +03:00
|
|
|
handleAxeResults(`${name} - info`, axe);
|
2022-07-15 01:08:32 +03:00
|
|
|
};
|
2022-07-15 00:17:39 +03:00
|
|
|
|
|
|
|
const pageProcessing = async (name, location) => {
|
|
|
|
await goTo(name, location);
|
|
|
|
|
|
|
|
var axe = await new AxePuppeteer(page)
|
|
|
|
.disableRules(skippedRules[name] || [])
|
|
|
|
.analyze();
|
|
|
|
handleAxeResults(name, axe);
|
|
|
|
|
2022-07-21 17:53:12 +03:00
|
|
|
await percySnapshot(page, name, {
|
|
|
|
scope: "[data-page-container='']",
|
2022-07-15 01:36:09 +03:00
|
|
|
});
|
2022-07-15 01:08:32 +03:00
|
|
|
};
|
2022-07-15 00:17:39 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const iconProcessing = async (name, location) => {
|
|
|
|
await page.goto(location);
|
2022-06-27 20:56:15 +03:00
|
|
|
await page.waitForSelector(`#${name}`);
|
2022-05-24 19:42:36 +03:00
|
|
|
await percySnapshot(page, name);
|
2022-03-22 01:06:15 +03:00
|
|
|
|
|
|
|
// visible icon names snapshot
|
|
|
|
await page.click("label");
|
2023-04-20 18:39:43 +03:00
|
|
|
await page.waitForSelector("[aria-checked=true]");
|
2022-05-24 19:42:36 +03:00
|
|
|
await percySnapshot(page, `${name} - display icon names`);
|
2022-03-22 01:06:15 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const results = await new AxePuppeteer(page)
|
|
|
|
.disableRules(skippedRules[name] || [])
|
|
|
|
.analyze();
|
2022-04-14 20:38:10 +03:00
|
|
|
handleAxeResults(name, results);
|
2022-05-24 19:42:36 +03:00
|
|
|
};
|
2022-03-22 01:06:15 +03:00
|
|
|
|
2022-04-14 20:33:22 +03:00
|
|
|
const skippedRules = {
|
2022-12-06 03:22:34 +03:00
|
|
|
// See https://github.com/dequelabs/axe-core/issues/3649 -- we may be able to remove the Highlighter, Mark, and Block skipped rule
|
2022-09-08 01:13:52 +03:00
|
|
|
Highlighter: ["aria-roledescription"],
|
2022-11-10 02:54:13 +03:00
|
|
|
Block: ["aria-roledescription"],
|
2022-12-06 03:22:34 +03:00
|
|
|
QuestionBox: ["aria-roledescription"],
|
2022-04-19 19:40:48 +03:00
|
|
|
// Loading's color contrast check seems to change behavior depending on whether Percy snapshots are taken or not
|
2022-05-24 19:42:36 +03:00
|
|
|
Loading: ["color-contrast"],
|
|
|
|
RadioButton: ["duplicate-id"],
|
|
|
|
};
|
2022-04-14 20:33:22 +03:00
|
|
|
|
2019-11-27 19:01:30 +03:00
|
|
|
const specialProcessing = {
|
2022-06-18 02:17:09 +03:00
|
|
|
Message: messageProcessing,
|
2022-07-15 00:17:39 +03:00
|
|
|
Modal: modalProcessing,
|
|
|
|
Page: pageProcessing,
|
2022-05-24 19:42:36 +03:00
|
|
|
AssignmentIcon: iconProcessing,
|
|
|
|
UiIcon: iconProcessing,
|
|
|
|
Logo: iconProcessing,
|
|
|
|
Pennant: iconProcessing,
|
|
|
|
};
|
2022-01-22 03:49:18 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
it("All", async function () {
|
2022-01-22 03:49:18 +03:00
|
|
|
page = await browser.newPage();
|
2023-06-01 00:14:11 +03:00
|
|
|
handlePageErrors(page);
|
2023-06-01 01:20:55 +03:00
|
|
|
await page.goto(`http://localhost:${PORT}`, { waitUntil: "load" });
|
2022-05-24 19:42:36 +03:00
|
|
|
await page.$("#maincontent");
|
2022-01-22 03:49:18 +03:00
|
|
|
await percySnapshot(page, this.test.fullTitle());
|
2022-04-14 20:15:15 +03:00
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
const results = await new AxePuppeteer(page)
|
|
|
|
.disableRules([
|
2022-04-14 20:33:22 +03:00
|
|
|
"aria-hidden-focus",
|
|
|
|
"color-contrast",
|
|
|
|
"duplicate-id-aria",
|
|
|
|
"duplicate-id",
|
2022-05-24 19:42:36 +03:00
|
|
|
])
|
|
|
|
.analyze();
|
2022-04-14 20:15:15 +03:00
|
|
|
|
2022-01-22 03:49:18 +03:00
|
|
|
page.close();
|
2022-04-14 20:15:15 +03:00
|
|
|
|
2022-04-14 20:38:10 +03:00
|
|
|
handleAxeResults("index view", results);
|
2020-09-09 22:46:24 +03:00
|
|
|
});
|
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
it("Doodads", async function () {
|
2022-01-22 03:49:18 +03:00
|
|
|
page = await browser.newPage();
|
2023-06-01 00:14:11 +03:00
|
|
|
handlePageErrors(page);
|
2022-01-22 03:49:18 +03:00
|
|
|
await page.goto(`http://localhost:${PORT}`);
|
|
|
|
|
2022-05-24 19:42:36 +03:00
|
|
|
await page.$("#maincontent");
|
2022-01-22 03:49:18 +03:00
|
|
|
let links = await page.evaluate(() => {
|
2022-05-24 19:42:36 +03:00
|
|
|
let nodes = Array.from(
|
|
|
|
document.querySelectorAll("[data-nri-description='doodad-link']")
|
|
|
|
);
|
|
|
|
return nodes.map((node) => [node.text, node.href]);
|
|
|
|
});
|
2022-01-22 03:49:18 +03:00
|
|
|
|
|
|
|
await links.reduce((acc, [name, location]) => {
|
|
|
|
return acc.then(() => {
|
2022-05-24 19:42:36 +03:00
|
|
|
if (
|
|
|
|
process.env.ONLYDOODAD == "default" ||
|
|
|
|
process.env.ONLYDOODAD == name
|
|
|
|
) {
|
|
|
|
console.log(`Testing ${name}`);
|
2022-04-14 23:23:17 +03:00
|
|
|
let handler = specialProcessing[name] || defaultProcessing;
|
|
|
|
return handler(name, location);
|
2022-01-22 03:49:18 +03:00
|
|
|
}
|
2022-05-24 19:42:36 +03:00
|
|
|
});
|
|
|
|
}, Promise.resolve());
|
2022-04-14 20:38:10 +03:00
|
|
|
|
|
|
|
page.close();
|
2022-05-24 19:42:36 +03:00
|
|
|
});
|
|
|
|
});
|