diff --git a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/AccessibilityUtilities.java b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/AccessibilityUtilities.java index 87631a80f..97e8e36f2 100644 --- a/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/AccessibilityUtilities.java +++ b/maqs-accessibility/src/main/java/com/cognizantsoftvision/maqs/accessibility/AccessibilityUtilities.java @@ -258,6 +258,8 @@ public static void createAccessibilityHtmlReport(ISeleniumTestObject testObject, results = getResults.get(); HtmlReporter.createAxeHtmlReport(testObject.getWebDriver(), results, report, requestedResults); + } catch (Exception e) { + throw new RuntimeException(e.getLocalizedMessage()); } finally { // Restore logging if we suspended it if (restoreLogging) { 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..473213d15 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 @@ -10,6 +10,8 @@ import com.deque.html.axecore.results.Rule; import com.deque.html.axecore.selenium.AxeBuilder; import com.deque.html.axecore.selenium.ResultType; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -17,21 +19,27 @@ import java.nio.file.Paths; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.Objects; import java.util.Set; +import javax.imageio.ImageIO; 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.jsoup.select.Elements; +import org.openqa.selenium.By; import org.openqa.selenium.OutputType; +import org.openqa.selenium.Point; import org.openqa.selenium.SearchContext; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.WrapsElement; @@ -50,6 +58,8 @@ public class HtmlReporter { */ private static final String RESOURCES_FILE = "../maqs-accessibility/src/main/resources/"; + private static final String DATA_SOURCE = "data:image/png;base64,"; + /** * Class constructor. */ @@ -161,6 +171,8 @@ private static void createAxeHtmlReportFile(SearchContext context, Results resul Document doc = Jsoup.parse(stringBuilder); + TakesScreenshot screenshot = getScreenShot(context); + doc.select("style").append(getCss(context)); Element contentArea = doc.select("content").first(); @@ -233,10 +245,12 @@ private static void createAxeHtmlReportFile(SearchContext context, Results resul if (violationCount > 0 && requestedResults.contains(ResultType.Violations)) { getReadableAxeResults(results.getViolations(), ResultType.Violations, resultsFlex); + setImages(ResultType.Violations, doc, context, screenshot); } if (incompleteCount > 0 && requestedResults.contains(ResultType.Incomplete)) { getReadableAxeResults(results.getIncomplete(), ResultType.Incomplete, resultsFlex); + setImages(ResultType.Incomplete, doc, context, screenshot); } if (passCount > 0 && requestedResults.contains(ResultType.Passes)) { @@ -286,10 +300,10 @@ private static void getReadableAxeResults(List results, ResultType type, E sectionButtonHeader.text(type.name() + ": " + getCount(results)); sectionButton.appendChild(sectionButtonHeader); - Element sectionButtonExpando = new Element("h2"); + Element sectionButtonExpander = new Element("h2"); sectionButtonExpando.addClass("buttonExpandoText"); - sectionButtonExpando.text("-"); - sectionButton.appendChild(sectionButtonExpando); + sectionButtonExpander.text("-"); + sectionButton.appendChild(sectionButtonExpander); Element section = new Element("div"); section.addClass("majorSection"); @@ -357,13 +371,21 @@ private static void getReadableAxeResults(List results, ResultType type, E htmlAndSelector.addClass("wrapTwo"); for (Object target : Collections.singletonList(item.getTarget())) { - String targetString = target.toString().replace("[", "").replace("]", ""); + String targetString = target.toString(); + targetString = targetString.contains("[[") && targetString.contains("]]") + ? targetString.substring(2, targetString.length() - 2) + : targetString.substring(1, targetString.length() - 1); + htmlAndSelector.text(targetString); htmlAndSelector.html(targetString); } htmlAndSelectorWrapper.appendChild(htmlAndSelector); + + htmlAndSelectorWrapper = new Element("div"); + elementNodes.appendChild(htmlAndSelectorWrapper); addFixes(item, type, htmlAndSelectorWrapper); + htmlAndSelectorWrapper.addClass("emFour"); } } } @@ -454,6 +476,84 @@ private static void fixAnyIssues(Element htmlAndSelectorWrapper, List any htmlAndSelectorWrapper.appendChild(htmlAndSelector); } + private static void setImages(ResultType resultType, Element doc, + SearchContext searchContext, TakesScreenshot screenshot) throws IOException { + if (!checkForNoWebDriver(searchContext) || screenshot == null) { + return; + } + + Element section = doc.getElementById(resultType.name() + "Section"); + Elements findings = section.getElementsByClass("findings"); + int count = 1; + + for (Element finding : findings) { + for (Element table : finding.getElementsByClass("htmlTable")) { + String elementName = resultType.name() + "Element" + count++; + + Element emThree = table.selectFirst("div.emThree"); + String selectorText = emThree.selectFirst("p.wrapTwo").text(); + By by = By.cssSelector(selectorText); + WebElement foundElement = searchContext.findElement(By.cssSelector(selectorText)); + + Point location = searchContext.findElement(by).getLocation(); + String imageString = getDataElementString(searchContext, by); + + Element image = new Element("img"); + image.attributes().put("src", imageString); + image.attributes().put("alt", elementName); + image.attributes().put("class", elementName); + + Element emFour = table.selectFirst("div.emFour"); + emFour.appendChild(image); + } + } + } + + private static boolean checkForNoWebDriver(SearchContext searchContext) { + if (searchContext instanceof WebDriver) { + try { + if (((WebDriver) searchContext).getCurrentUrl().contains("http")) { + return true; + } + } catch (Exception e) { + throw new WebDriverException("web driver is not open"); + } + } + return false; + } + + private static TakesScreenshot getScreenShot(SearchContext context) { + return (TakesScreenshot) context; + } + + private static String getDataElementString(SearchContext webDriver, By by) { + WebElement webElement = webDriver.findElement(by); + String base64bytes = Base64.getEncoder().encodeToString(webElement.getScreenshotAs(OutputType.BYTES)); + return DATA_SOURCE + base64bytes; + } + + private static String getDataElementString( + TakesScreenshot screenshot, SearchContext webDriver, By by) throws IOException { + // Get screenshot as a file + File screenshotFile = screenshot.getScreenshotAs(OutputType.FILE); + + // Convert the screenshot into BufferedImage + BufferedImage fullScreen = ImageIO.read(screenshotFile); + + // Find location of the web element on the page + WebElement element = webDriver.findElement(by); + Point location = webDriver.findElement(by).getLocation(); + + // cropping the full image to get only the element screenshot + BufferedImage bufferedImage = fullScreen.getSubimage(location.getX(), location.getY(), + element.getSize().getWidth(), element.getSize().getHeight()); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ImageIO.write(bufferedImage, "PNG", out); + String base64bytes = Base64.getEncoder().encodeToString(out.toByteArray()); + return DATA_SOURCE + base64bytes; + } + /** * Sets up the Context content and adds it to the element. * @param results the results to be used for the report @@ -543,7 +643,7 @@ private static String getCss(SearchContext context) throws IOException { */ private static String getDataImageString(SearchContext context) { TakesScreenshot newScreen = (TakesScreenshot) context; - return "data:image/png;base64," + newScreen.getScreenshotAs(OutputType.BASE64); + return DATA_SOURCE + newScreen.getScreenshotAs(OutputType.BASE64); } /** diff --git a/maqs-accessibility/src/main/resources/htmlReporter.css b/maqs-accessibility/src/main/resources/htmlReporter.css index 61ec0be1e..c5611f124 100644 --- a/maqs-accessibility/src/main/resources/htmlReporter.css +++ b/maqs-accessibility/src/main/resources/htmlReporter.css @@ -38,9 +38,25 @@ } .emThree { - margin-left: 3em; + width: 50%; + margin-left: 3em; + flex: 1; +} + +.emFour { + width: 50%; + align-self: center; + transition: transform .2s; } + .emFour img { + transition: all .5s ease; + } + + .emFour:hover img{ + transform: scale(1.5); + } + #modal { display: none; position: fixed; @@ -76,7 +92,8 @@ .htmlTable { border-top: double lightgray; width: 100%; - display: table; + display: flex; + flex-wrap: wrap; } .sectionbutton { diff --git a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java index bd6615663..f5d84c0a0 100644 --- a/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java +++ b/maqs-accessibility/src/test/java/com/cognizantsoftvision/maqs/accessibility/HTMLReporterUnitTest.java @@ -63,13 +63,13 @@ public class HTMLReporterUnitTest extends BaseSeleniumTest { /** * The file to be converted into a result type. */ - private static final File integrationTestJsonResultFile = new File( + private static final File integrationTestSampleResultFile = 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(); + private static final String integrationTestSampleResultUrl = integrationTestSampleResultFile.getAbsolutePath(); /** * String value of main element selector. @@ -193,7 +193,7 @@ public void reportRespectRules() throws IOException, ParseException { @Test(groups = TestCategories.ACCESSIBILITY) public void reportSampleResults() throws IOException, ParseException { String path = createReportPath(); - Results results = new ObjectMapper().readValue(new File(integrationTestJsonResultUrl), Results.class); + Results results = new ObjectMapper().readValue(new File(integrationTestSampleResultUrl), Results.class); HtmlReporter.createAxeHtmlReport(this.getWebDriver(), results, path); validateReport(path, 3, 5, 2, 4); @@ -202,6 +202,7 @@ public void reportSampleResults() throws IOException, ParseException { Document doc = Jsoup.parse(text); String errorMessage = Objects.requireNonNull(doc.selectFirst("#ErrorMessage")).text(); + //String errorMessage = doc.selectFirst("#ErrorMessage").text(); Assert.assertEquals(errorMessage, "java.lang.Exception: AutomationError"); String reportContext = Objects.requireNonNull(doc.selectFirst("#reportContext")).text();