From a013ef6a7d08152f2e0d33e1e5874bb45e6d2121 Mon Sep 17 00:00:00 2001 From: jredingcvs <97762469+jredingcvs@users.noreply.github.com> Date: Fri, 2 Sep 2022 09:13:36 -0500 Subject: [PATCH 1/4] started adding content for html report for Playwright --- maqs-accessibility/pom.xml | 11 +- .../maqs/accessibility/HtmlReporter.java | 4 +- .../accessibility/PlaywrightReporter.java | 542 ++++++++++++++++++ .../maqs/accessibility/SeleniumReporter.java | 541 +++++++++++++++++ .../HTMLReporterPlaywrightUnitTest.java | 341 +++++++++++ .../HTMLReporterSeleniumUnitTest.java} | 25 +- maqs-playwright/pom.xml | 2 +- 7 files changed, 1450 insertions(+), 16 deletions(-) create mode 100644 maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java create mode 100644 maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java create mode 100644 maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java rename maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/{HTMLReporterUnitTest.java => htmlReporter/HTMLReporterSeleniumUnitTest.java} (92%) diff --git a/maqs-accessibility/pom.xml b/maqs-accessibility/pom.xml index 78a45cf89..5121edd95 100644 --- a/maqs-accessibility/pom.xml +++ b/maqs-accessibility/pom.xml @@ -18,7 +18,7 @@ 4.4.1 1.9 - 1.14.3 + 1.15.2 @@ -26,11 +26,20 @@ com.cognizantsoftvision.maqs.selenium maqs-selenium + + com.cognizantsoftvision.maqs.playwright + maqs-playwright + com.deque.html.axe-core selenium ${deque.axe.version} + + com.deque.html.axe-core + playwright + ${deque.axe.version} + io.github.bonigarcia webdrivermanager diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/HtmlReporter.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/HtmlReporter.java index c8ebb7ba4..48a026d72 100644 --- a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/HtmlReporter.java +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/HtmlReporter.java @@ -552,7 +552,7 @@ private static String getDataImageString(SearchContext context) { * @return The timestamp as a specified date formatted string * @throws ParseException If parse exception occurs */ - private static String getDateFormat(String timestamp) throws ParseException { + static String getDateFormat(String timestamp) throws ParseException { Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(timestamp); return new SimpleDateFormat("dd-MMM-yy HH:mm:ss Z").format(date); } @@ -562,7 +562,7 @@ private static String getDateFormat(String timestamp) throws ParseException { * @return the javascript file script as a string * @throws IOException if an exception is thrown */ - private static String getJavascriptFileToString() throws IOException { + public static String getJavascriptFileToString() throws IOException { return String.valueOf(Files.readAllLines(Paths.get(RESOURCES_FILE + "htmlReporterElements.js"))); } } \ No newline at end of file diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java new file mode 100644 index 000000000..afd49dac9 --- /dev/null +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java @@ -0,0 +1,542 @@ +/* + * Copyright 2022 (C) Cognizant SoftVision, All rights Reserved + */ + +package com.cognizantsoftvision.maqs.accessibility; + + +import com.deque.html.axecore.playwright.AxeBuilder; +import com.deque.html.axecore.selenium.ResultType; +import com.deque.html.axecore.utilities.axeresults.AxeResults; +import com.deque.html.axecore.utilities.axeresults.Check; +import com.deque.html.axecore.utilities.axeresults.CheckedNode; +import com.deque.html.axecore.utilities.axeresults.Rule; +import com.microsoft.playwright.Page; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.Base64; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.openqa.selenium.WebElement; + +/** + * The HTML reporter class. + */ +public class PlaywrightReporter extends HtmlReporter { + + /** + * Placeholder for wrap one tag string type. + */ + private static final String WRAP_ONE = "wrapOne"; + + /** + * File path to resources java resources folder. + */ + private static final String RESOURCES_FILE = "../maqs-accessibility/src/main/resources/"; + + /** + * Class constructor. + */ + protected PlaywrightReporter() { + } + + /** + * Create an HTML accessibility report for an entire web page. + * @param page the web driver used in the scan + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, String destination) + throws IOException, ParseException { + createAxeHtmlReport(page, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for an entire web page with specific Result types. + * @param page the web driver used in the scan + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, String destination, Set requestedResults) + throws IOException, ParseException { + createAxeHtmlReport(page, new AxeBuilder(page).analyze(), destination, requestedResults); + } + + /** + * Create an HTML accessibility report for a specific element. + * @param page the web driver used in the scan + * @param element the element to be scanned + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, WebElement element, String destination) + throws IOException, ParseException { + createAxeHtmlReport(page, element, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for a specific element and specified result types. + * @param page the web driver used in the scan + * @param element the element to be scanned + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, WebElement element, String destination, + Set requestedResults) throws IOException, ParseException { +// createAxeHtmlReport(page, new AxeBuilder(page).analyze(element), destination, requestedResults); + } + + /** + * Create an HTML accessibility report for an entire web page with already scanned results. + * @param page the web driver used in the scan + * @param results the results type variable used after scanning the web page + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, AxeResults results, String destination) + throws IOException, ParseException { + createAxeHtmlReport(page, results, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for an entire web page with specified Result types + * and inputted already scanned results. + * @param page the web driver used in the scan + * @param results the results object created after scanning the web page + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(Page page, AxeResults results, String destination, + Set requestedResults) throws IOException, ParseException { + createAxeHtmlReportFile(page, results, destination, requestedResults); + } + + /** + * Creates an HTML accessibility report. + * @param context the Search Context to be used in the scan + * @param results the results object created after scanning the web page + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + private static void createAxeHtmlReportFile(Page context, AxeResults results, String destination, + Set requestedResults) throws IOException, ParseException { + final int violationCount = getCount(results.getViolations()); + final int incompleteCount = getCount(results.getIncomplete()); + final int passCount = getCount(results.getPasses()); + final int inapplicableCount = getCount(results.getInapplicable()); + + String stringBuilder = String.valueOf(Files.readString(Paths.get(RESOURCES_FILE + "htmlReporterTags.html"))); + stringBuilder = stringBuilder.replace(System.lineSeparator(), ""); + + Document doc = Jsoup.parse(stringBuilder); + + doc.select("style").append(getCss(context)); + + Element contentArea = doc.select("content").first(); + + Element reportTitle = new Element("h1"); + reportTitle.text("Accessibility Check"); + Objects.requireNonNull(contentArea).appendChild(reportTitle); + + Element metaFlex = new Element("div"); + metaFlex.attributes().put("id", "metadata"); + contentArea.appendChild(metaFlex); + + Element contextGroup = new Element("div"); + contextGroup.attributes().put("id", "context"); + metaFlex.appendChild(contextGroup); + + Element contextHeader = new Element("h3"); + contextHeader.text("Context:"); + contextGroup.appendChild(contextHeader); + + Element contextContent = new Element("div"); + contextContent.addClass("emOne"); + contextContent.attributes().put("id", "reportContext"); + getContextContent(results, contextContent); + contextGroup.appendChild(contextContent); + + Element imgGroup = new Element("div"); + imgGroup.attributes().put("id", "image"); + metaFlex.appendChild(imgGroup); + + Element imageHeader = new Element("h3"); + imageHeader.appendText("Image:"); + imgGroup.appendChild(imageHeader); + + Element imageContent = new Element("img"); + imageContent.addClass("thumbnail"); + imageContent.attributes().put("id", "screenshotThumbnail"); + imageContent.attributes().put("alt", "A Screenshot of the page"); + imageContent.attributes().put("width", "33%"); + imageContent.attributes().put("height", "auto"); + imgGroup.appendChild(imageContent); + + Element countsGroup = new Element("div"); + countsGroup.attributes().put("id", "counts"); + metaFlex.appendChild(countsGroup); + + Element countsHeader = new Element("h3"); + countsHeader.appendText("Counts:"); + countsGroup.appendChild(countsHeader); + + Element countsContent = new Element("div"); + countsContent.addClass("emOne"); + getCountContent(violationCount, incompleteCount, passCount, inapplicableCount, requestedResults, countsContent); + countsGroup.appendChild(countsContent); + + Element resultsFlex = new Element("div"); + resultsFlex.attributes().put("id", "results"); + contentArea.appendChild(resultsFlex); + + if (results.isErrored()) { + Element errorHeader = new Element("h2"); + errorHeader.appendText("SCAN ERRORS:"); + contentArea.appendChild(errorHeader); + + Element errorContent = new Element("div"); + errorContent.attributes().put("id", "ErrorMessage"); + errorContent.appendText(results.getErrorMessage()); + contentArea.appendChild(errorContent); + } + + if (violationCount > 0 && requestedResults.contains(ResultType.Violations)) { + getReadableAxeResults(results.getViolations(), ResultType.Violations, resultsFlex); + } + + if (incompleteCount > 0 && requestedResults.contains(ResultType.Incomplete)) { + getReadableAxeResults(results.getIncomplete(), ResultType.Incomplete, resultsFlex); + } + + if (passCount > 0 && requestedResults.contains(ResultType.Passes)) { + getReadableAxeResults(results.getPasses(), ResultType.Passes, resultsFlex); + } + + if (inapplicableCount > 0 && requestedResults.contains(ResultType.Inapplicable)) { + getReadableAxeResults(results.getInapplicable(), ResultType.Inapplicable, resultsFlex); + } + + Element modal = new Element("div"); + modal.attributes().put("id", "modal"); + contentArea.appendChild(modal); + + Element modalClose = new Element("div"); + modalClose.text("X"); + modalClose.attributes().put("id", "modalclose"); + modal.appendChild(modalClose); + + Element modalImage = new Element("img"); + modalImage.attributes().put("id", "modalimage"); + modal.appendChild(modalImage); + + Element script = doc.select("script").first(); + Objects.requireNonNull(script).appendChild(new DataNode(HtmlReporter.getJavascriptFileToString())); + + FileUtils.writeStringToFile(new File(destination), doc.outerHtml(), StandardCharsets.UTF_8); + } + + /** + * Sets up the results into html elements for the report. + * @param results A list of the Rule results found + * @param type The result type that is being created + * @param body The main html page element body + */ + private static void getReadableAxeResults(List results, ResultType type, Element body) { + Element resultWrapper = new Element("div"); + resultWrapper.addClass("resultWrapper"); + body.appendChild(resultWrapper); + + Element sectionButton = new Element("button"); + sectionButton.addClass("sectionbutton active"); + resultWrapper.appendChild(sectionButton); + + Element sectionButtonHeader = new Element("h2"); + sectionButtonHeader.addClass("buttonInfoText"); + sectionButtonHeader.text(type.name() + ": " + getCount(results)); + sectionButton.appendChild(sectionButtonHeader); + + Element sectionButtonExpando = new Element("h2"); + sectionButtonExpando.addClass("buttonExpandoText"); + sectionButtonExpando.text("-"); + sectionButton.appendChild(sectionButtonExpando); + + Element section = new Element("div"); + section.addClass("majorSection"); + section.attributes().put("id", type.name() + "Section"); + resultWrapper.appendChild(section); + + int loops = 1; + + for (Rule element : results) { + Element childEl = new Element("div"); + childEl.addClass("findings"); + childEl.appendText(loops++ + ": " + element.getHelp()); + section.appendChild(childEl); + + Element content = new Element("div"); + content.addClass("emTwo"); + content.text("Description: " + element.getDescription()); + content.appendChild(new Element("br")); + content.appendText("Help: " + element.getHelp()); + content.appendChild(new Element("br")); + content.appendText("Help URL: "); + + Element link = new Element("a"); + link.attributes().put("href", element.getHelpUrl()); + link.text(element.getHelpUrl()); + + content.appendChild(link); + content.appendChild(new Element("br")); + + if (!element.getImpact().isEmpty()) { + content.appendText("Impact: " + element.getImpact()); + content.appendChild(new Element("br")); + } + + content.appendText("Tags: ").append(String.join(", ", element.getTags())); + content.appendChild(new Element("br")); + + if (!element.getNodes().isEmpty()) { + content.appendText("Element(s):"); + } + + Element childEl2 = new Element("div"); + childEl2.addClass("emTwo"); + childEl.appendChild(content); + + for (CheckedNode item : element.getNodes()) { + Element elementNodes = new Element("div"); + elementNodes.addClass("htmlTable"); + childEl.appendChild(elementNodes); + + Element htmlAndSelectorWrapper = new Element("div"); + htmlAndSelectorWrapper.addClass("emThree"); + htmlAndSelectorWrapper.text("Html:"); + htmlAndSelectorWrapper.appendChild(new Element("br")); + elementNodes.appendChild(htmlAndSelectorWrapper); + + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.html(item.getHtml()); + htmlAndSelector.text(item.getHtml()); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + htmlAndSelectorWrapper.appendText("Selector:"); + + htmlAndSelector = new Element("p"); + htmlAndSelector.addClass("wrapTwo"); + + for (Object target : Collections.singletonList(item.getTarget())) { + String targetString = target.toString().replace("[", "").replace("]", ""); + htmlAndSelector.text(targetString); + htmlAndSelector.html(targetString); + } + + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + addFixes(item, type, htmlAndSelectorWrapper); + } + } + } + + /** + * Add the fixes for the specified result type. + * @param resultsNode The fixes from the results in this specific result type + * @param type The result type fixes + * @param htmlAndSelectorWrapper The element that the fixes will be appended to + */ + private static void addFixes(CheckedNode resultsNode, ResultType type, Element htmlAndSelectorWrapper) { + Element htmlAndSelector = new Element("div"); + + List anyCheckResults = resultsNode.getAny(); + List allCheckResults = resultsNode.getAll(); + List noneCheckResults = resultsNode.getNone(); + + int checkResultsCount = anyCheckResults.size() + allCheckResults.size() + noneCheckResults.size(); + + // Add fixes if this is for violations + if (ResultType.Violations.equals(type) && checkResultsCount > 0) { + htmlAndSelector.text("To solve:"); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + + htmlAndSelector = new Element("p"); + htmlAndSelector.addClass("wrapTwo"); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + + if (!allCheckResults.isEmpty() || !noneCheckResults.isEmpty()) { + fixAllIssues(htmlAndSelectorWrapper, allCheckResults, noneCheckResults); + } + + if (!anyCheckResults.isEmpty()) { + fixAnyIssues(htmlAndSelectorWrapper, anyCheckResults); + } + } + } + + /** + * Adds the issues in the all category in the list of Checks. + * @param htmlAndSelectorWrapper The element that all the content will be appended to + * @param allCheckResults A list of the all check results + * @param noneCheckResults A list of the none check results + */ + private static void fixAllIssues(Element htmlAndSelectorWrapper, + List allCheckResults, List noneCheckResults) { + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.text("Fix at least one of the following issues:"); + + Element htmlSet = new Element("ul"); + + for (var checkResult : allCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + for (var checkResult : noneCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + htmlAndSelector.appendChild(htmlSet); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + } + + /** + * Adds the issues in the Any category in the list of Checks. + * @param htmlAndSelectorWrapper The element that all the content will be appended to + * @param anyCheckResults A list of the Any check results + */ + private static void fixAnyIssues(Element htmlAndSelectorWrapper, List anyCheckResults) { + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.text("Fix at least one of the following issues:"); + + Element htmlSet = new Element("ul"); + + for (var checkResult : anyCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + htmlAndSelector.appendChild(htmlSet); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + } + + /** + * Sets up the Context content and adds it to the element. + * @param results the results to be used for the report + * @param element the element that the content will be added to + * @throws ParseException if an exception is thrown + */ + private static void getContextContent(AxeResults results, Element element) throws ParseException { + element.text("Url: " + results.getUrl()); + element.appendChild(new Element("br")); + element.appendText("Orientation: " + results.getTestEnvironment().getOrientationType()); + element.appendChild(new Element("br")); + element.appendText("Size: " + results.getTestEnvironment().getwindowWidth() + " x " + + results.getTestEnvironment().getWindowHeight()); + element.appendChild(new Element("br")); + element.appendText("Time: " + HtmlReporter.getDateFormat(results.getTimestamp())); + element.appendChild(new Element("br")); + element.appendText("User agent: " + results.getTestEnvironment().getUserAgent()); + element.appendChild(new Element("br")); + element.appendText("Using: " + results.getTestEngine().getName() + " (" + + results.getTestEngine().getVersion() + ")"); + } + + /** + * Gets the count of the number of rules that came up in the scan. + * @param results The list of rules to be looped through + * @return The count of all the rules + */ + private static int getCount(List results) { + int count = 0; + for (Rule item : results) { + count += item.getNodes().size(); + + // Still add one if no targets are included + if (item.getNodes().isEmpty()) { + count++; + } + } + return count; + } + + /** + * Sets up the count content for the html report. + * @param violationCount The count for the violations in the scan + * @param incompleteCount The count for incomplete in the scan + * @param passCount The count for passes in the scan + * @param inapplicableCount The count for inapplicable in the scan + * @param requestedResults The result types that will be included on the html report + * @param element The element that all the content will be appended to + */ + private static void getCountContent(int violationCount, int incompleteCount, int passCount, + int inapplicableCount, Set requestedResults, Element element) { + if (requestedResults.contains(ResultType.Violations)) { + element.text(" Violations: " + violationCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Incomplete)) { + element.appendText(" Incomplete: " + incompleteCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Passes)) { + element.appendText(" Passes: " + passCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Inapplicable)) { + element.appendText(" Inapplicable: " + inapplicableCount); + } + } + + /** + * Gets the CSS file into a string format. + * @return the CSS file script as a string + * @throws IOException if an exception is thrown + */ + private static String getCss(Page context) throws IOException { + String css = String.valueOf(Files.readAllLines( + Paths.get(RESOURCES_FILE + "htmlReporter.css"))); + return css.replace("url('", "url('" + getDataImageString(context)); + } + + /** + * Gets the data image as a base 64 string. + * @param context The web driver or element to take a screenshot of + * @return the base 64 data image as a string + */ + private static String getDataImageString(Page context) { + //TakesScreenshot newScreen = (TakesScreenshot) context; + //return "data:image/png;base64," + newScreen.getScreenshotAs(OutputType.BASE64); + byte[] bytes = context.screenshot(); + return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes); + } +} \ No newline at end of file diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java new file mode 100644 index 000000000..a2bbfe8b0 --- /dev/null +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java @@ -0,0 +1,541 @@ +/* + * Copyright 2022 (C) Cognizant SoftVision, All rights Reserved + */ + +package com.cognizantsoftvision.maqs.accessibility; + +import com.deque.html.axecore.results.Check; +import com.deque.html.axecore.results.CheckedNode; +import com.deque.html.axecore.results.Results; +import com.deque.html.axecore.results.Rule; +import com.deque.html.axecore.selenium.AxeBuilder; +import com.deque.html.axecore.selenium.ResultType; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.jsoup.Jsoup; +import org.jsoup.nodes.DataNode; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.openqa.selenium.*; + +/** + * The HTML reporter class. + */ +public class SeleniumReporter extends HtmlReporter { + + /** + * Placeholder for wrap one tag string type. + */ + private static final String WRAP_ONE = "wrapOne"; + + /** + * File path to resources java resources folder. + */ + private static final String RESOURCES_FILE = "../maqs-accessibility/src/main/resources/"; + + /** + * Class constructor. + */ + protected SeleniumReporter() { + } + + /** + * Create an HTML accessibility report for an entire web page. + * @param webDriver the web driver used in the scan + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, String destination) + throws IOException, ParseException { + createAxeHtmlReport(webDriver, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for an entire web page with specific Result types. + * @param webDriver the web driver used in the scan + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, String destination, Set requestedResults) + throws IOException, ParseException { + createAxeHtmlReport(webDriver, new AxeBuilder().analyze(webDriver), destination, requestedResults); + } + + /** + * Create an HTML accessibility report for a specific element. + * @param webDriver the web driver used in the scan + * @param element the element to be scanned + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, WebElement element, String destination) + throws IOException, ParseException { + createAxeHtmlReport(webDriver, element, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for a specific element and specified result types. + * @param webDriver the web driver used in the scan + * @param element the element to be scanned + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, WebElement element, String destination, + Set requestedResults) throws IOException, ParseException { + createAxeHtmlReport(webDriver, new AxeBuilder().analyze(webDriver, element), destination, requestedResults); + } + + /** + * Create an HTML accessibility report for an entire web page with already scanned results. + * @param webDriver the web driver used in the scan + * @param results the results type variable used after scanning the web page + * @param destination the destination file the html report will go to + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, Results results, String destination) + throws IOException, ParseException { + createAxeHtmlReport(webDriver, results, destination, EnumSet.allOf(ResultType.class)); + } + + /** + * Create an HTML accessibility report for an entire web page with specified Result types + * and inputted already scanned results. + * @param webDriver the web driver used in the scan + * @param results the results object created after scanning the web page + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + public static void createAxeHtmlReport(WebDriver webDriver, Results results, String destination, + Set requestedResults) throws IOException, ParseException { + createAxeHtmlReportFile(webDriver, results, destination, requestedResults); + } + + /** + * Creates an HTML accessibility report. + * @param context the Search Context to be used in the scan + * @param results the results object created after scanning the web page + * @param destination the destination file the html report will go to + * @param requestedResults the specified result types to include in the report + * @throws IOException if an IO exception is thrown + * @throws ParseException if a parse exception is thrown + */ + private static void createAxeHtmlReportFile(SearchContext context, Results results, String destination, + Set requestedResults) throws IOException, ParseException { + // Get the unwrapped element if we are using a wrapped element + context = (context instanceof WrapsElement) + ? ((WrapsElement) context).getWrappedElement() : context; + + final int violationCount = getCount(results.getViolations()); + final int incompleteCount = getCount(results.getIncomplete()); + final int passCount = getCount(results.getPasses()); + final int inapplicableCount = getCount(results.getInapplicable()); + + String stringBuilder = String.valueOf(Files.readString(Paths.get(RESOURCES_FILE + "htmlReporterTags.html"))); + stringBuilder = stringBuilder.replace(System.lineSeparator(), ""); + + Document doc = Jsoup.parse(stringBuilder); + + doc.select("style").append(getCss(context)); + + Element contentArea = doc.select("content").first(); + + Element reportTitle = new Element("h1"); + reportTitle.text("Accessibility Check"); + Objects.requireNonNull(contentArea).appendChild(reportTitle); + + Element metaFlex = new Element("div"); + metaFlex.attributes().put("id", "metadata"); + contentArea.appendChild(metaFlex); + + Element contextGroup = new Element("div"); + contextGroup.attributes().put("id", "context"); + metaFlex.appendChild(contextGroup); + + Element contextHeader = new Element("h3"); + contextHeader.text("Context:"); + contextGroup.appendChild(contextHeader); + + Element contextContent = new Element("div"); + contextContent.addClass("emOne"); + contextContent.attributes().put("id", "reportContext"); + getContextContent(results, contextContent); + contextGroup.appendChild(contextContent); + + Element imgGroup = new Element("div"); + imgGroup.attributes().put("id", "image"); + metaFlex.appendChild(imgGroup); + + Element imageHeader = new Element("h3"); + imageHeader.appendText("Image:"); + imgGroup.appendChild(imageHeader); + + Element imageContent = new Element("img"); + imageContent.addClass("thumbnail"); + imageContent.attributes().put("id", "screenshotThumbnail"); + imageContent.attributes().put("alt", "A Screenshot of the page"); + imageContent.attributes().put("width", "33%"); + imageContent.attributes().put("height", "auto"); + imgGroup.appendChild(imageContent); + + Element countsGroup = new Element("div"); + countsGroup.attributes().put("id", "counts"); + metaFlex.appendChild(countsGroup); + + Element countsHeader = new Element("h3"); + countsHeader.appendText("Counts:"); + countsGroup.appendChild(countsHeader); + + Element countsContent = new Element("div"); + countsContent.addClass("emOne"); + getCountContent(violationCount, incompleteCount, passCount, inapplicableCount, requestedResults, countsContent); + countsGroup.appendChild(countsContent); + + Element resultsFlex = new Element("div"); + resultsFlex.attributes().put("id", "results"); + contentArea.appendChild(resultsFlex); + + if (results.isErrored()) { + Element errorHeader = new Element("h2"); + errorHeader.appendText("SCAN ERRORS:"); + contentArea.appendChild(errorHeader); + + Element errorContent = new Element("div"); + errorContent.attributes().put("id", "ErrorMessage"); + errorContent.appendText(results.getErrorMessage()); + contentArea.appendChild(errorContent); + } + + if (violationCount > 0 && requestedResults.contains(ResultType.Violations)) { + getReadableAxeResults(results.getViolations(), ResultType.Violations, resultsFlex); + } + + if (incompleteCount > 0 && requestedResults.contains(ResultType.Incomplete)) { + getReadableAxeResults(results.getIncomplete(), ResultType.Incomplete, resultsFlex); + } + + if (passCount > 0 && requestedResults.contains(ResultType.Passes)) { + getReadableAxeResults(results.getPasses(), ResultType.Passes, resultsFlex); + } + + if (inapplicableCount > 0 && requestedResults.contains(ResultType.Inapplicable)) { + getReadableAxeResults(results.getInapplicable(), ResultType.Inapplicable, resultsFlex); + } + + Element modal = new Element("div"); + modal.attributes().put("id", "modal"); + contentArea.appendChild(modal); + + Element modalClose = new Element("div"); + modalClose.text("X"); + modalClose.attributes().put("id", "modalclose"); + modal.appendChild(modalClose); + + Element modalImage = new Element("img"); + modalImage.attributes().put("id", "modalimage"); + modal.appendChild(modalImage); + + Element script = doc.select("script").first(); + Objects.requireNonNull(script).appendChild(new DataNode(HtmlReporter.getJavascriptFileToString())); + + FileUtils.writeStringToFile(new File(destination), doc.outerHtml(), StandardCharsets.UTF_8); + } + + /** + * Sets up the results into html elements for the report. + * @param results A list of the Rule results found + * @param type The result type that is being created + * @param body The main html page element body + */ + private static void getReadableAxeResults(List results, ResultType type, Element body) { + Element resultWrapper = new Element("div"); + resultWrapper.addClass("resultWrapper"); + body.appendChild(resultWrapper); + + Element sectionButton = new Element("button"); + sectionButton.addClass("sectionbutton active"); + resultWrapper.appendChild(sectionButton); + + Element sectionButtonHeader = new Element("h2"); + sectionButtonHeader.addClass("buttonInfoText"); + sectionButtonHeader.text(type.name() + ": " + getCount(results)); + sectionButton.appendChild(sectionButtonHeader); + + Element sectionButtonExpando = new Element("h2"); + sectionButtonExpando.addClass("buttonExpandoText"); + sectionButtonExpando.text("-"); + sectionButton.appendChild(sectionButtonExpando); + + Element section = new Element("div"); + section.addClass("majorSection"); + section.attributes().put("id", type.name() + "Section"); + resultWrapper.appendChild(section); + + int loops = 1; + + for (Rule element : results) { + Element childEl = new Element("div"); + childEl.addClass("findings"); + childEl.appendText(loops++ + ": " + element.getHelp()); + section.appendChild(childEl); + + Element content = new Element("div"); + content.addClass("emTwo"); + content.text("Description: " + element.getDescription()); + content.appendChild(new Element("br")); + content.appendText("Help: " + element.getHelp()); + content.appendChild(new Element("br")); + content.appendText("Help URL: "); + + Element link = new Element("a"); + link.attributes().put("href", element.getHelpUrl()); + link.text(element.getHelpUrl()); + + content.appendChild(link); + content.appendChild(new Element("br")); + + if (!element.getImpact().isEmpty()) { + content.appendText("Impact: " + element.getImpact()); + content.appendChild(new Element("br")); + } + + content.appendText("Tags: ").append(String.join(", ", element.getTags())); + content.appendChild(new Element("br")); + + if (!element.getNodes().isEmpty()) { + content.appendText("Element(s):"); + } + + Element childEl2 = new Element("div"); + childEl2.addClass("emTwo"); + childEl.appendChild(content); + + for (CheckedNode item : element.getNodes()) { + Element elementNodes = new Element("div"); + elementNodes.addClass("htmlTable"); + childEl.appendChild(elementNodes); + + Element htmlAndSelectorWrapper = new Element("div"); + htmlAndSelectorWrapper.addClass("emThree"); + htmlAndSelectorWrapper.text("Html:"); + htmlAndSelectorWrapper.appendChild(new Element("br")); + elementNodes.appendChild(htmlAndSelectorWrapper); + + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.html(item.getHtml()); + htmlAndSelector.text(item.getHtml()); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + htmlAndSelectorWrapper.appendText("Selector:"); + + htmlAndSelector = new Element("p"); + htmlAndSelector.addClass("wrapTwo"); + + for (Object target : Collections.singletonList(item.getTarget())) { + String targetString = target.toString().replace("[", "").replace("]", ""); + htmlAndSelector.text(targetString); + htmlAndSelector.html(targetString); + } + + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + addFixes(item, type, htmlAndSelectorWrapper); + } + } + } + + /** + * Add the fixes for the specified result type. + * @param resultsNode The fixes from the results in this specific result type + * @param type The result type fixes + * @param htmlAndSelectorWrapper The element that the fixes will be appended to + */ + private static void addFixes(CheckedNode resultsNode, ResultType type, Element htmlAndSelectorWrapper) { + Element htmlAndSelector = new Element("div"); + + List anyCheckResults = resultsNode.getAny(); + List allCheckResults = resultsNode.getAll(); + List noneCheckResults = resultsNode.getNone(); + + int checkResultsCount = anyCheckResults.size() + allCheckResults.size() + noneCheckResults.size(); + + // Add fixes if this is for violations + if (ResultType.Violations.equals(type) && checkResultsCount > 0) { + htmlAndSelector.text("To solve:"); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + + htmlAndSelector = new Element("p"); + htmlAndSelector.addClass("wrapTwo"); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + + if (!allCheckResults.isEmpty() || !noneCheckResults.isEmpty()) { + fixAllIssues(htmlAndSelectorWrapper, allCheckResults, noneCheckResults); + } + + if (!anyCheckResults.isEmpty()) { + fixAnyIssues(htmlAndSelectorWrapper, anyCheckResults); + } + } + } + + /** + * Adds the issues in the all category in the list of Checks. + * @param htmlAndSelectorWrapper The element that all the content will be appended to + * @param allCheckResults A list of the all check results + * @param noneCheckResults A list of the none check results + */ + private static void fixAllIssues(Element htmlAndSelectorWrapper, + List allCheckResults, List noneCheckResults) { + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.text("Fix at least one of the following issues:"); + + Element htmlSet = new Element("ul"); + + for (var checkResult : allCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + for (var checkResult : noneCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + htmlAndSelector.appendChild(htmlSet); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + } + + /** + * Adds the issues in the Any category in the list of Checks. + * @param htmlAndSelectorWrapper The element that all the content will be appended to + * @param anyCheckResults A list of the Any check results + */ + private static void fixAnyIssues(Element htmlAndSelectorWrapper, List anyCheckResults) { + Element htmlAndSelector = new Element("p"); + htmlAndSelector.addClass(WRAP_ONE); + htmlAndSelector.text("Fix at least one of the following issues:"); + + Element htmlSet = new Element("ul"); + + for (var checkResult : anyCheckResults) { + Element bulletPoints = new Element("li"); + bulletPoints.text(checkResult.getImpact().toUpperCase() + ": " + checkResult.getMessage()); + htmlSet.appendChild(bulletPoints); + } + + htmlAndSelector.appendChild(htmlSet); + htmlAndSelectorWrapper.appendChild(htmlAndSelector); + } + + /** + * Sets up the Context content and adds it to the element. + * @param results the results to be used for the report + * @param element the element that the content will be added to + * @throws ParseException if an exception is thrown + */ + private static void getContextContent(Results results, Element element) throws ParseException { + element.text("Url: " + results.getUrl()); + element.appendChild(new Element("br")); + element.appendText("Orientation: " + results.getTestEnvironment().getOrientationType()); + element.appendChild(new Element("br")); + element.appendText("Size: " + results.getTestEnvironment().getwindowWidth() + " x " + + results.getTestEnvironment().getWindowHeight()); + element.appendChild(new Element("br")); + element.appendText("Time: " + HtmlReporter.getDateFormat(results.getTimestamp())); + element.appendChild(new Element("br")); + element.appendText("User agent: " + results.getTestEnvironment().getUserAgent()); + element.appendChild(new Element("br")); + element.appendText("Using: " + results.getTestEngine().getName() + " (" + + results.getTestEngine().getVersion() + ")"); + } + + /** + * Gets the count of the number of rules that came up in the scan. + * @param results The list of rules to be looped through + * @return The count of all the rules + */ + private static int getCount(List results) { + int count = 0; + for (Rule item : results) { + count += item.getNodes().size(); + + // Still add one if no targets are included + if (item.getNodes().isEmpty()) { + count++; + } + } + return count; + } + + /** + * Sets up the count content for the html report. + * @param violationCount The count for the violations in the scan + * @param incompleteCount The count for incomplete in the scan + * @param passCount The count for passes in the scan + * @param inapplicableCount The count for inapplicable in the scan + * @param requestedResults The result types that will be included on the html report + * @param element The element that all the content will be appended to + */ + private static void getCountContent(int violationCount, int incompleteCount, int passCount, + int inapplicableCount, Set requestedResults, Element element) { + if (requestedResults.contains(ResultType.Violations)) { + element.text(" Violations: " + violationCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Incomplete)) { + element.appendText(" Incomplete: " + incompleteCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Passes)) { + element.appendText(" Passes: " + passCount); + element.appendChild(new Element("br")); + } + + if (requestedResults.contains(ResultType.Inapplicable)) { + element.appendText(" Inapplicable: " + inapplicableCount); + } + } + + /** + * Gets the CSS file into a string format. + * @return the CSS file script as a string + * @throws IOException if an exception is thrown + */ + private static String getCss(SearchContext context) throws IOException { + String css = String.valueOf(Files.readAllLines( + Paths.get(RESOURCES_FILE + "htmlReporter.css"))); + return css.replace("url('", "url('" + getDataImageString(context)); + } + + /** + * Gets the data image as a base 64 string. + * @param context The web driver or element to take a screenshot of + * @return the base 64 data image as a string + */ + private static String getDataImageString(SearchContext context) { + TakesScreenshot newScreen = (TakesScreenshot) context; + return "data:image/png;base64," + newScreen.getScreenshotAs(OutputType.BASE64); + } +} \ No newline at end of file diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java new file mode 100644 index 000000000..4991d0c57 --- /dev/null +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java @@ -0,0 +1,341 @@ +package com.cognizantsoftvision.maqs.accessibility.htmlReporter; + +import com.cognizantsoftvision.maqs.accessibility.PlaywrightReporter; +import com.cognizantsoftvision.maqs.playwright.BasePlaywrightTest; +import com.cognizantsoftvision.maqs.utilities.helper.TestCategories; +import com.deque.html.axecore.playwright.AxeBuilder; +import com.deque.html.axecore.selenium.ResultType; +import com.deque.html.axecore.utilities.axeresults.AxeResults; +import com.deque.html.axecore.utilities.axeresults.Check; +import com.deque.html.axecore.utilities.axeresults.CheckedNode; +import com.deque.html.axecore.utilities.axeresults.Rule; +import com.deque.html.axecore.utilities.axerunoptions.AxeRunOptions; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.playwright.options.LoadState; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Objects; +import java.util.UUID; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; +import org.testng.Assert; +import org.testng.annotations.Ignore; +import org.testng.annotations.Test; + +/** + * Accessibility HTML Playwright Reporter unit tests. + */ +public class HTMLReporterPlaywrightUnitTest extends BasePlaywrightTest { + /** + * The file to be opened in the browser. + */ + private static final File integrationTestTargetSimpleFile = new File( + "src/test/resources/testFiles/integration-test-target.html"); + + /** + * The url to be opened in the browser. + */ + private static final String integrationTestTargetSimpleUrl = integrationTestTargetSimpleFile.getAbsolutePath(); + + /** + * The file to be opened in the browser. + */ + private static final File integrationTestTargetComplexFile = new File( + "src/test/resources/testFiles/integration-test-target-complex.html"); + + /** + * The url to be opened in the browser. + */ + private static final String integrationTestTargetComplexUrl = integrationTestTargetComplexFile.getAbsolutePath(); + + /** + * The file to be converted into a result type. + */ + private static final File integrationTestJsonResultFile = new File( + "src/test/resources/testFiles/sampleResults.json"); + + /** + * The path to the file converted into a result type. + */ + private static final String integrationTestJsonResultUrl = integrationTestJsonResultFile.getAbsolutePath(); + + /** + * String value of main element selector. + */ + private static final String mainElementSelector = "main"; + + private void loadTestPage(String testPage) { + this.getPage().getAsyncPage().navigate("file:///" + new File(testPage).getAbsolutePath()); + this.getPage().getAsyncPage().waitForLoadState(LoadState.DOMCONTENTLOADED); + this.getPage().getAsyncPage().isVisible(mainElementSelector); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void runScanOnPage() { + loadTestPage(integrationTestTargetSimpleUrl); + + //var timeBeforeScan = DateTime.Now; + + AxeRunOptions axeRunOptions = new AxeRunOptions(); + axeRunOptions.setXPath(true); + + AxeBuilder builder = new AxeBuilder(this.getPage().getAsyncPage()); + builder.options(axeRunOptions); + builder.withTags(Arrays.asList("wcag2a", "wcag2aa")); + builder.disableRules(Collections.singletonList("color-contrast")); + // builder.withOutputFile("./raw-axe-results.json"); + + AxeResults results = builder.analyze(); + + Assert.assertEquals(results.getViolations().size(), 2); + + for (Rule violations : results.getViolations()) { + Assert.assertFalse(violations.getId().contains("color-contrast")); + + // results.Violations.FirstOrDefault(v => !v.Tags.Contains("wcag2a") && !v.Tags.Contains("wcag2aa")).Should().BeNull(); + Assert.assertTrue(violations.getTags().contains("wcag2a")); + // Assert.assertTrue(violations.getTags().contains("wcag2aa")); + } + + Assert.assertNotNull(results.getViolations().get(0).getNodes().get(0)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void runScanOnGivenElement() + throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + // HtmlPlaywrightReporter.createAxeHtmlReport(this.getPage().getAsyncPage(), + // this.getPageDriver().waitForSelector(mainElementSelector), path); + validateReport(path, 3, 14, 0, 75); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportFullPage() throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), path); + validateReport(path, 4, 26, 0, 69); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportFullPageViolationsOnly() + throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), path, EnumSet.of( + ResultType.Violations)); + + // Check violations + validateReport(path, 4, 0, 0, 0); + validateResultNotWritten(path, + EnumSet.of(ResultType.Passes, ResultType.Inapplicable, ResultType.Incomplete)); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportFullPagePassesInapplicableViolationsOnly() + throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), path, + EnumSet.of(ResultType.Passes, ResultType.Inapplicable, ResultType.Violations)); + + // Check Passes + validateReport(path, 4, 26, 0, 69); + validateResultNotWritten(path, EnumSet.of(ResultType.Incomplete)); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + @Ignore + public void reportOnElement() throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + + var mainElement = this.getPageDriver().getAsyncPage().waitForSelector(mainElementSelector); + // HtmlPlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), mainElement, path); + + validateReport(path, 3, 14, 0, 75); + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportRespectRules() throws IOException, ParseException { + loadTestPage(integrationTestTargetSimpleUrl); + String path = createReportPath(); + + var builder = new AxeBuilder(this.getPageDriver().getAsyncPage()).disableRules(Collections.singletonList("color-contrast")); + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), builder.analyze(), path); + + validateReport(path, 3, 21, 0, 69); + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportSampleResults() throws IOException, ParseException { + String path = createReportPath(); + AxeResults results = new ObjectMapper().readValue(new File(integrationTestJsonResultUrl), AxeResults.class); + + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), results, path); + validateReport(path, 3, 5, 2, 4); + + String text = new String(Files.readAllBytes(Paths.get(path))); + Document doc = Jsoup.parse(text); + + String errorMessage = Objects.requireNonNull(doc.selectFirst("#ErrorMessage")).text(); + Assert.assertEquals(errorMessage, "java.lang.Exception: AutomationError"); + + String reportContext = Objects.requireNonNull(doc.selectFirst("#reportContext")).text(); + Assert.assertTrue(reportContext.contains("Url: https://www.google.com/"), "URL is not in the document"); + Assert.assertTrue(reportContext.contains("Orientation: landscape-primary"), "Orientation is not in the document"); + Assert.assertTrue(reportContext.contains("Size: 1200 x 646"), "Size is not in the document"); + Assert.assertTrue(reportContext.contains("Time: 14-Apr-20 01:33:59"), "Time is not in the document: " + reportContext); + Assert.assertTrue(reportContext.contains("User agent: AutoAgent"), "User Agent is not in the document"); + Assert.assertTrue(reportContext.contains("Using: axe-core (3.4.1)"), "Using is not in the document"); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportRespectsIframeImplicitTrue() throws IOException, ParseException { + loadTestPage(integrationTestTargetComplexUrl); + String path = createReportPath(); + + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), path); + validateReport(path, 4, 43, 0, 64); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void ReportRespectsIframeTrue() throws IOException, ParseException { + loadTestPage(integrationTestTargetComplexUrl); + String path = createReportPath(); + + AxeRunOptions runOptions = new AxeRunOptions(); + runOptions.setIFrames(true); + + var builder = new AxeBuilder(getPageDriver().getAsyncPage()).options(runOptions); + + PlaywrightReporter.createAxeHtmlReport(this.getPageDriver().getAsyncPage(), builder.analyze(), path); + validateReport(path, 4, 43, 0, 64); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void reportRespectsIframeFalse() throws IOException, ParseException { + loadTestPage(integrationTestTargetComplexUrl); + String path = createReportPath(); + + AxeRunOptions runOptions = new AxeRunOptions(); + runOptions.setIFrames(false); + + var builder = new AxeBuilder(getPageDriver().getAsyncPage()).options(runOptions); + PlaywrightReporter.createAxeHtmlReport(getPageDriver().getAsyncPage(), builder.analyze(), path); + validateReport(path, 4, 43, 0, 64); + + deleteFile(new File(path)); + } + + @Test(groups = TestCategories.ACCESSIBILITY) + public void runSiteThatReturnsMultipleTargets() { + loadTestPage(integrationTestTargetComplexUrl); + + AxeResults axeResult = new AxeBuilder(getPageDriver().getAsyncPage()).analyze(); + // .withOutputFile("./raw-axe-results.json").analyze(); + + Rule colorContrast = null; + + for (Rule rule : axeResult.getViolations()) { + if (rule.getId().equals("color-contrast")) { + colorContrast = rule; + break; + } + } + + Assert.assertNotNull(colorContrast); + + for (CheckedNode checkedNode : colorContrast.getNodes()) { + for (Check check : checkedNode.getAny()) { + if (check.getId().equals("color-contrast")) { + Assert.assertNotNull(checkedNode.getAny()); + Assert.assertEquals(checkedNode.getAny().size(), 1); + break; + } + } + } + } + + private String createReportPath() { + return FileSystems.getDefault().getPath("target" + File.separator + "logs") + + File.separator + UUID.randomUUID() + ".html"; + } + + private void validateReport(String path, int violationCount, int passCount, int incompleteCount, int inapplicableCount) + throws IOException { + String text = String.valueOf(Files.readString(Paths.get(path))); + Document doc = Jsoup.parse(text); + + // Check the Element count for each result type + validateElementCount(doc, violationCount, ResultType.Violations); + validateElementCount(doc, passCount, ResultType.Passes); + validateElementCount(doc, inapplicableCount, ResultType.Inapplicable); + validateElementCount(doc, incompleteCount, ResultType.Incomplete); + + // Check header data + Assert.assertTrue(text.contains("Using: axe-core"), "Expected to find 'Using: axe-core'"); + + // Check the result count for each result type + validateResultCount(text, violationCount, ResultType.Violations); + validateResultCount(text, incompleteCount, ResultType.Incomplete); + validateResultCount(text, passCount, ResultType.Passes); + validateResultCount(text, inapplicableCount, ResultType.Inapplicable); + } + + private void validateElementCount(Document doc, int count, ResultType resultType) { + String ending = resultType.equals(ResultType.Inapplicable) ? "div.findings" : "div > div.htmlTable"; + String xpath = "#" + resultType + "Section > " + ending; + Elements liNodes = !doc.select(xpath).isEmpty() ? doc.select(xpath) : new Elements(); + Assert.assertEquals(liNodes.size(), count, "Expected " + count + " " + resultType); + } + + private void validateResultCount(String text, int count, ResultType resultType) { + if (count != 0) { + Assert.assertTrue(text.contains(resultType + ": " + count), + "Expected to find '" + resultType + ": " + count); + } + } + + private void validateResultNotWritten(String path, EnumSet resultTypeArray) throws IOException { + loadTestPage(integrationTestTargetSimpleUrl); + String text = String.valueOf(Files.readAllLines(Paths.get(path))); + + for (ResultType resultType : resultTypeArray) { + Assert.assertFalse(text.contains(resultType + ": "), + "Expected to not find '" + resultType + ": '"); + } + } + + private void deleteFile(File file) { + if (file.exists()) { + Assert.assertTrue(file.delete(), "File was not deleted"); + Assert.assertFalse(file.exists(), "file still exists"); + } + } +} diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java similarity index 92% rename from maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java rename to maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java index bd6615663..1c8eafa4b 100644 --- a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java @@ -2,8 +2,9 @@ * Copyright 2022 (C) Cognizant SoftVision, All rights Reserved */ -package com.cognizantsoftvision.maqs.accessibility; +package com.cognizantsoftvision.maqs.accessibility.htmlReporter; +import com.cognizantsoftvision.maqs.accessibility.SeleniumReporter; import com.deque.html.axecore.axeargs.AxeRunOptions; import com.deque.html.axecore.results.Check; import com.deque.html.axecore.results.CheckedNode; @@ -36,7 +37,7 @@ /** * Accessibility HTML Reporter unit tests. */ -public class HTMLReporterUnitTest extends BaseSeleniumTest { +public class HTMLReporterSeleniumUnitTest extends BaseSeleniumTest { /** * The file to be opened in the browser. @@ -119,7 +120,7 @@ public void runScanOnGivenElement() throws IOException, ParseException { loadTestPage(integrationTestTargetSimpleUrl); String path = createReportPath(); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), this.getWebDriver().findElement(By.cssSelector(mainElementSelector)), path); validateReport(path, 3, 14, 0, 75); @@ -130,7 +131,7 @@ public void runScanOnGivenElement() public void reportFullPage() throws IOException, ParseException { loadTestPage(integrationTestTargetSimpleUrl); String path = createReportPath(); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), path); validateReport(path, 4, 26, 0, 69); deleteFile(new File(path)); @@ -141,7 +142,7 @@ public void reportFullPageViolationsOnly() throws IOException, ParseException { loadTestPage(integrationTestTargetSimpleUrl); String path = createReportPath(); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), path, EnumSet.of(ResultType.Violations)); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), path, EnumSet.of(ResultType.Violations)); // Check violations validateReport(path, 4, 0, 0, 0); @@ -156,7 +157,7 @@ public void reportFullPagePassesInapplicableViolationsOnly() throws IOException, ParseException { loadTestPage(integrationTestTargetSimpleUrl); String path = createReportPath(); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), path, + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), path, EnumSet.of(ResultType.Passes, ResultType.Inapplicable, ResultType.Violations)); // Check Passes @@ -172,7 +173,7 @@ public void reportOnElement() throws IOException, ParseException { String path = createReportPath(); var mainElement = this.getWebDriver().findElement(By.cssSelector(mainElementSelector)); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), mainElement, path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), mainElement, path); validateReport(path, 3, 14, 0, 75); deleteFile(new File(path)); @@ -184,7 +185,7 @@ public void reportRespectRules() throws IOException, ParseException { String path = createReportPath(); var builder = new AxeBuilder().disableRules(Collections.singletonList("color-contrast")); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); validateReport(path, 3, 21, 0, 69); deleteFile(new File(path)); @@ -195,7 +196,7 @@ public void reportSampleResults() throws IOException, ParseException { String path = createReportPath(); Results results = new ObjectMapper().readValue(new File(integrationTestJsonResultUrl), Results.class); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), results, path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), results, path); validateReport(path, 3, 5, 2, 4); String text = new String(Files.readAllBytes(Paths.get(path))); @@ -220,7 +221,7 @@ public void reportRespectsIframeImplicitTrue() throws IOException, ParseExceptio loadTestPage(integrationTestTargetComplexUrl); String path = createReportPath(); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), path); validateReport(path, 4, 43, 0, 64); deleteFile(new File(path)); @@ -236,7 +237,7 @@ public void ReportRespectsIframeTrue() throws IOException, ParseException { var builder = new AxeBuilder().withOptions(runOptions); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); validateReport(path, 4, 43, 0, 64); deleteFile(new File(path)); @@ -251,7 +252,7 @@ public void reportRespectsIframeFalse() throws IOException, ParseException { runOptions.setIFrames(false); var builder = new AxeBuilder().withOptions(runOptions); - HtmlReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); + SeleniumReporter.createAxeHtmlReport(this.getWebDriver(), builder.analyze(this.getWebDriver()), path); validateReport(path, 4, 43, 0, 64); deleteFile(new File(path)); diff --git a/maqs-playwright/pom.xml b/maqs-playwright/pom.xml index 5ec8c6aa8..53a9175f0 100644 --- a/maqs-playwright/pom.xml +++ b/maqs-playwright/pom.xml @@ -16,7 +16,7 @@ ${revision} - 1.21.0 + 1.25.0 From bec8c9debc07396bec6d52a684bc6d8959e96dac Mon Sep 17 00:00:00 2001 From: jredingcvs <97762469+jredingcvs@users.noreply.github.com> Date: Tue, 6 Sep 2022 09:32:50 -0500 Subject: [PATCH 2/4] spacing --- .../htmlReporter/HTMLReporterPlaywrightUnitTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java index 4991d0c57..4bc1d70b2 100644 --- a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java @@ -34,6 +34,7 @@ * Accessibility HTML Playwright Reporter unit tests. */ public class HTMLReporterPlaywrightUnitTest extends BasePlaywrightTest { + /** * The file to be opened in the browser. */ @@ -108,6 +109,7 @@ public void runScanOnPage() { Assert.assertNotNull(results.getViolations().get(0).getNodes().get(0)); } + @Ignore @Test(groups = TestCategories.ACCESSIBILITY) public void runScanOnGivenElement() throws IOException, ParseException { From 193734c02e224f34cdc9c6086a1a36275d932921 Mon Sep 17 00:00:00 2001 From: jredingcvs <97762469+jredingcvs@users.noreply.github.com> Date: Tue, 6 Sep 2022 11:38:38 -0500 Subject: [PATCH 3/4] checkstyle fixes --- .../maqs/accessibility/PlaywrightReporter.java | 2 +- .../maqs/accessibility/SeleniumReporter.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java index afd49dac9..ef0b311b2 100644 --- a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/PlaywrightReporter.java @@ -101,7 +101,7 @@ public static void createAxeHtmlReport(Page page, WebElement element, String des */ public static void createAxeHtmlReport(Page page, WebElement element, String destination, Set requestedResults) throws IOException, ParseException { -// createAxeHtmlReport(page, new AxeBuilder(page).analyze(element), destination, requestedResults); + // createAxeHtmlReport(page, new AxeBuilder(page).analyze(element), destination, requestedResults); } /** diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java index a2bbfe8b0..129be71ab 100644 --- a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/SeleniumReporter.java @@ -26,7 +26,12 @@ import org.jsoup.nodes.DataNode; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import org.openqa.selenium.*; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.WrapsElement; /** * The HTML reporter class. From 7a9017ab9e605bbf43a94c52f05cd2719497c327 Mon Sep 17 00:00:00 2001 From: jredingcvs <97762469+jredingcvs@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:01:35 -0500 Subject: [PATCH 4/4] update unit test file names --- ...LReporterPlaywrightUnitTest.java => PlaywrightUnitTest.java} | 2 +- ...{HTMLReporterSeleniumUnitTest.java => SeleniumUnitTest.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/{HTMLReporterPlaywrightUnitTest.java => PlaywrightUnitTest.java} (99%) rename maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/{HTMLReporterSeleniumUnitTest.java => SeleniumUnitTest.java} (99%) diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/PlaywrightUnitTest.java similarity index 99% rename from maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java rename to maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/PlaywrightUnitTest.java index 4bc1d70b2..2ff40a602 100644 --- a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterPlaywrightUnitTest.java +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/PlaywrightUnitTest.java @@ -33,7 +33,7 @@ /** * Accessibility HTML Playwright Reporter unit tests. */ -public class HTMLReporterPlaywrightUnitTest extends BasePlaywrightTest { +public class PlaywrightUnitTest extends BasePlaywrightTest { /** * The file to be opened in the browser. diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/SeleniumUnitTest.java similarity index 99% rename from maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java rename to maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/SeleniumUnitTest.java index 1c8eafa4b..aff209343 100644 --- a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/HTMLReporterSeleniumUnitTest.java +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/htmlReporter/SeleniumUnitTest.java @@ -37,7 +37,7 @@ /** * Accessibility HTML Reporter unit tests. */ -public class HTMLReporterSeleniumUnitTest extends BaseSeleniumTest { +public class SeleniumUnitTest extends BaseSeleniumTest { /** * The file to be opened in the browser.