diff --git a/src/main/java/org/finra/jtaf/ewd/ExtWebDriver.java b/src/main/java/org/finra/jtaf/ewd/ExtWebDriver.java index 96124de..040d2ca 100644 --- a/src/main/java/org/finra/jtaf/ewd/ExtWebDriver.java +++ b/src/main/java/org/finra/jtaf/ewd/ExtWebDriver.java @@ -17,6 +17,8 @@ package org.finra.jtaf.ewd; +import java.io.File; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -418,5 +420,16 @@ public List getCurrentWindowIds() { * Selects the previously selected frame */ public void selectLastFrame(); + + /*** + * Take a screenshot of the browser content. Note that while using RemoteWebDriver sessions (ie: with + * Sauce Labs), the screenshot will be a full page of content--not only the visible content where the + * page is scrolled (as when using a non-RemoteWebDriver session). + * + * @param toSaveAs - name of the file to save the picture in (Note: must be PNG) + * @throws IOException + * if problems saving file + */ + public void takeScreenshotOfPage(File toSaveAs) throws IOException; } \ No newline at end of file diff --git a/src/main/java/org/finra/jtaf/ewd/impl/DefaultExtWebDriver.java b/src/main/java/org/finra/jtaf/ewd/impl/DefaultExtWebDriver.java index 635ce5c..4a938c1 100644 --- a/src/main/java/org/finra/jtaf/ewd/impl/DefaultExtWebDriver.java +++ b/src/main/java/org/finra/jtaf/ewd/impl/DefaultExtWebDriver.java @@ -17,6 +17,8 @@ package org.finra.jtaf.ewd.impl; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.HashSet; @@ -35,18 +37,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.commons.io.FileUtils; import org.ccil.cowan.tagsoup.Parser; import org.finra.jtaf.ewd.ExtWebDriver; import org.finra.jtaf.ewd.HighlightProvider; import org.finra.jtaf.ewd.TimeOutException; +import org.finra.jtaf.ewd.session.SessionManager; import org.finra.jtaf.ewd.widget.IElement; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.Augmenter; +import org.openqa.selenium.remote.RemoteWebDriver; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; @@ -884,4 +892,17 @@ public void setSessionId(String id) { public String getSessionId() { return this.sessionId; } + + @Override + public void takeScreenshotOfPage(File toSaveAs) throws IOException { + File screenshot; + if(!(wd instanceof RemoteWebDriver)) { + screenshot = ((TakesScreenshot) wd).getScreenshotAs(OutputType.FILE); + } + else { + Augmenter augmenter = new Augmenter(); + screenshot = ((TakesScreenshot) augmenter.augment(wd)).getScreenshotAs(OutputType.FILE); + } + FileUtils.copyFile(screenshot, toSaveAs); + } } diff --git a/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotComparator.java b/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotComparator.java new file mode 100644 index 0000000..0b8d0a7 --- /dev/null +++ b/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotComparator.java @@ -0,0 +1,130 @@ +/* + * (C) Copyright 2013 Java Test Automation Framework Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.finra.jtaf.ewd.utils; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.finra.jtaf.ewd.widget.IElement; +import org.finra.jtaf.ewd.widget.WidgetException; + +/*** + * To compare screenshots of elements + * + * @author Pulitand + * + */ +public class ScreenshotComparator { + + private static Logger log = LoggerFactory.getLogger(ScreenshotComparator.class); + + /*** + * Prereq: The page on which you are taking the screenshot is fully loaded + * + * Take a screenshot of the element identified by element and save the file as toSaveAs + * (Note that this file should be saved as a png). + * Test that the control picture, controlPicture, is both the same size as, + * and, has a similarity value greater than or equal to the threshold. + * + * @param element - the element to be tested + * @param controlPicture - the file of the picture that will serve as the control + * @param toSaveTestPictureAs - for example, save the file at "testData/textFieldWidget/screenshot.png" + * @param threshold - you are asserting that the similarity between the two pictures + * is a double greater than or equal to this double (between 0.0 and 1.0) + * @throws Exception + */ + public static boolean isElementSimilarToScreenshot(IElement element, File controlPicture, File + toSaveTestPictureAs, double threshold) throws IOException, WidgetException { + + element.captureElementScreenshot(toSaveTestPictureAs); + + log.info("Screenshot was successful. Comparing against control..."); + + BufferedImage var = ImageIO.read(toSaveTestPictureAs); + BufferedImage cont = ImageIO.read(controlPicture); + + return isSimilar(var, cont, threshold); + } + + /*** + * Prereq: The page on which you are taking the screenshot is fully loaded + * + * Take a screenshot of the element identified by element and save the file as toSaveAs + * (Note that this file should be saved as a png). + * Test that the control picture, controlPicture, is both the same size as, + * and, has a similarity value greater than or equal to the default threshold of .85. + * + * @param element - the element to be tested + * @param controlPicture - the file of the picture that will serve as the control + * @param toSaveTestPictureAs - for example, save the file at "testData/textFieldWidget/screenshot.png" + * @throws WidgetException + * @throws IOException + * @throws Exception + */ + public static boolean isElementSimilarToScreenshot(IElement element, File controlPicture, + File toSaveTestPictureAs) throws IOException, WidgetException { + return isElementSimilarToScreenshot(element, controlPicture, toSaveTestPictureAs, .85); + } + + private static boolean isSimilar(BufferedImage var, BufferedImage cont, double threshold) { + return similarity(var, cont) >= threshold; + } + + //Returns a double between 0 and 1.0 + private static double similarity(BufferedImage var, BufferedImage cont) { + + double[] varArr = new double[var.getWidth()*var.getHeight()*3]; + double[] contArr = new double[cont.getWidth()*cont.getHeight()*3]; + + if (varArr.length!=contArr.length) + throw new IllegalStateException("The pictures are different sizes!"); + + //unroll pixels + for(int i = 0; i < var.getHeight(); i++) { + for(int j = 0; j < var.getWidth(); j++) { + varArr[i*var.getWidth() + j + 0] = new Color(var.getRGB(j, i)).getRed(); + contArr[i*cont.getWidth() + j + 0] = new Color(cont.getRGB(j, i)).getRed(); + varArr[i*var.getWidth() + j + 1] = new Color(var.getRGB(j, i)).getGreen(); + contArr[i*cont.getWidth() + j + 1] = new Color(cont.getRGB(j, i)).getGreen(); + varArr[i*var.getWidth() + j + 2] = new Color(var.getRGB(j, i)).getBlue(); + contArr[i*cont.getWidth() + j + 2] = new Color(cont.getRGB(j, i)).getBlue(); + } + } + + double mins=0; + double maxs=0; + for(int i=0;i!=varArr.length;i++){ + if (varArr[i]>contArr[i]){ + mins+=contArr[i]; + maxs+=varArr[i]; + } else { + mins+=varArr[i]; + maxs+=contArr[i]; + } + } + return mins/maxs; + } + + +} diff --git a/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotUtils.java b/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotUtils.java deleted file mode 100644 index 97e9527..0000000 --- a/src/main/java/org/finra/jtaf/ewd/utils/ScreenshotUtils.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * (C) Copyright 2013 Java Test Automation Framework Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.finra.jtaf.ewd.utils; - -import java.awt.Color; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; - -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.finra.jtaf.ewd.session.SessionManager; -import org.finra.jtaf.ewd.timer.WidgetTimeoutException; -import org.finra.jtaf.ewd.widget.IElement; -import org.finra.jtaf.ewd.widget.WidgetException; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.Point; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.Locatable; -import org.openqa.selenium.remote.Augmenter; -import org.openqa.selenium.remote.RemoteWebDriver; - -/*** - * Utilities to take and compare screenshots of elements - * - * @author Pulitand - * - */ -public class ScreenshotUtils { - - private static Logger log = LoggerFactory.getLogger(ScreenshotUtils.class); - - /*** - * You can use this method to take your control pictures. Note that the file should be a png. - * - * @param element - * @param toSaveAs - * @throws IOException - * @throws WidgetException - * @throws WidgetTimeoutException - * @throws Exception - */ - public static void takeScreenshotOfElement(IElement element, File toSaveAs) throws IOException, WidgetException { - - for(int i = 0; i < 10; i++) { //Loop up to 10x to ensure a clean screenshot was taken - log.info("Taking screen shot of locator " + element.getByLocator() + " ... attempt #" + (i+1)); - - //Scroll to element - element.scrollTo(); - - //Take picture of the page - WebDriver wd = SessionManager.getInstance().getCurrentSession().getWrappedDriver(); - File screenshot; - boolean isRemote = false; - if(!(wd instanceof RemoteWebDriver)) { - screenshot = ((TakesScreenshot) wd).getScreenshotAs(OutputType.FILE); - } - else { - Augmenter augmenter = new Augmenter(); - screenshot = ((TakesScreenshot) augmenter.augment(wd)).getScreenshotAs(OutputType.FILE); - isRemote = true; - } - BufferedImage fullImage = ImageIO.read(screenshot); - WebElement ele = element.getWebElement(); - - //Parse out the picture of the element - Point point = ele.getLocation(); - int eleWidth = ele.getSize().getWidth(); - int eleHeight = ele.getSize().getHeight(); - int x; - int y; - if(isRemote) { - x = ((Locatable)ele).getCoordinates().inViewPort().getX(); - y = ((Locatable)ele).getCoordinates().inViewPort().getY(); - } - else { - x = point.getX(); - y = point.getY(); - } - log.debug("Screenshot coordinates x: "+x+", y: "+y); - BufferedImage eleScreenshot = fullImage.getSubimage(x, y, eleWidth, eleHeight); - ImageIO.write(eleScreenshot, "png", screenshot); - - //Ensure clean snapshot (sometimes WebDriver takes bad pictures and they turn out all black) - if(!isBlack(ImageIO.read(screenshot))) { - FileUtils.copyFile(screenshot, toSaveAs); - break; - } - } - } - - /*** - * Prereq: The page on which you are taking the screenshot is fully loaded - * - * Take a screenshot of the element identified by element and save the file as toSaveAs - * (Note that this file should be saved as a png). - * Test that the control picture, controlPicture, is both the same size as, - * and, has a similarity value greater than or equal to the threshold. - * - * @param element - the element to be tested - * @param controlPicture - the file of the picture that will serve as the control - * @param toSaveAs - for example, save the file at "testData/textFieldWidget/screenshot.png" - * @param threshold - you are asserting that the similarity between the two pictures - * is a double greater than or equal to this double (between 0.0 and 1.0) - * @throws Exception - */ - public static boolean isSimilarToScreenshot(IElement element, File controlPicture, File - toSaveAs, double threshold) throws IOException, WidgetException { - - takeScreenshotOfElement(element, toSaveAs); - - log.info("Screenshot was successful. Comparing against control..."); - - BufferedImage var = ImageIO.read(toSaveAs); - BufferedImage cont = ImageIO.read(controlPicture); - - return isSimilar(var, cont, threshold); - } - - /*** - * Prereq: The page on which you are taking the screenshot is fully loaded - * - * Take a screenshot of the element identified by element and save the file as toSaveAs - * (Note that this file should be saved as a png). - * Test that the control picture, controlPicture, is both the same size as, - * and, has a similarity value greater than or equal to the default threshold of .85. - * - * @param element - the element to be tested - * @param controlPicture - the file of the picture that will serve as the control - * @param toSaveAs - for example, save the file at "testData/textFieldWidget/screenshot.png" - * @throws WidgetException - * @throws IOException - * @throws Exception - */ - public static boolean isSimilarToScreenshot(IElement element, File controlPicture, - File toSaveAs) throws IOException, WidgetException { - return isSimilarToScreenshot(element, controlPicture, toSaveAs, .85); - } - - private static boolean isBlack(BufferedImage var) { - double[] varArr = new double[var.getWidth()*var.getHeight()*3]; - //unroll pixels - for(int i = 0; i < var.getHeight(); i++) { - for(int j = 0; j < var.getWidth(); j++) { - varArr[i*var.getWidth() + j + 0] = new Color(var.getRGB(j, i)).getRed(); - varArr[i*var.getWidth() + j + 1] = new Color(var.getRGB(j, i)).getGreen(); - varArr[i*var.getWidth() + j + 2] = new Color(var.getRGB(j, i)).getBlue(); - } - } - - //test if all are black - for(int i=0;i!=varArr.length;i++){ - if(varArr[i] != 0) - return false; - } - return true; - } - - private static boolean isSimilar(BufferedImage var, BufferedImage cont, double threshold) { - return similarity(var, cont) >= threshold; - } - - //Returns a double between 0 and 1.0 - private static double similarity(BufferedImage var, BufferedImage cont) { - - double[] varArr = new double[var.getWidth()*var.getHeight()*3]; - double[] contArr = new double[cont.getWidth()*cont.getHeight()*3]; - - if (varArr.length!=contArr.length) - throw new IllegalStateException("The pictures are different sizes!"); - - //unroll pixels - for(int i = 0; i < var.getHeight(); i++) { - for(int j = 0; j < var.getWidth(); j++) { - varArr[i*var.getWidth() + j + 0] = new Color(var.getRGB(j, i)).getRed(); - contArr[i*cont.getWidth() + j + 0] = new Color(cont.getRGB(j, i)).getRed(); - varArr[i*var.getWidth() + j + 1] = new Color(var.getRGB(j, i)).getGreen(); - contArr[i*cont.getWidth() + j + 1] = new Color(cont.getRGB(j, i)).getGreen(); - varArr[i*var.getWidth() + j + 2] = new Color(var.getRGB(j, i)).getBlue(); - contArr[i*cont.getWidth() + j + 2] = new Color(cont.getRGB(j, i)).getBlue(); - } - } - - double mins=0; - double maxs=0; - for(int i=0;i!=varArr.length;i++){ - if (varArr[i]>contArr[i]){ - mins+=contArr[i]; - maxs+=varArr[i]; - } else { - mins+=varArr[i]; - maxs+=contArr[i]; - } - } - return mins/maxs; - } - - -} diff --git a/src/main/java/org/finra/jtaf/ewd/widget/IElement.java b/src/main/java/org/finra/jtaf/ewd/widget/IElement.java index e77d3c2..061ee60 100644 --- a/src/main/java/org/finra/jtaf/ewd/widget/IElement.java +++ b/src/main/java/org/finra/jtaf/ewd/widget/IElement.java @@ -16,6 +16,9 @@ */ package org.finra.jtaf.ewd.widget; +import java.io.File; +import java.io.IOException; + import org.finra.jtaf.ewd.timer.WidgetTimeoutException; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -513,5 +516,15 @@ public void waitForNotAttribute(String attributeName, String pattern, long timeo * @throws WidgetException */ public void focusOn() throws WidgetException; + + + /*** + * Implementing this method would allow for the element to have a screenshot taken for it + * + * @param toSaveAs + * @throws IOException + * @throws WidgetException + */ + public void captureElementScreenshot(File toSaveAs) throws IOException, WidgetException; } diff --git a/src/main/java/org/finra/jtaf/ewd/widget/element/Element.java b/src/main/java/org/finra/jtaf/ewd/widget/element/Element.java index 47f5ade..5cadf04 100644 --- a/src/main/java/org/finra/jtaf/ewd/widget/element/Element.java +++ b/src/main/java/org/finra/jtaf/ewd/widget/element/Element.java @@ -16,10 +16,15 @@ */ package org.finra.jtaf.ewd.widget.element; +import java.awt.Color; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.util.List; +import javax.imageio.ImageIO; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; @@ -28,6 +33,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; +import org.apache.commons.io.FileUtils; import org.ccil.cowan.tagsoup.Parser; import org.finra.jtaf.ewd.ExtWebDriver; import org.finra.jtaf.ewd.HighlightProvider; @@ -42,11 +48,15 @@ import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.NoSuchElementException; +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.WebElement; import org.openqa.selenium.internal.Locatable; +import org.openqa.selenium.remote.Augmenter; +import org.openqa.selenium.remote.RemoteWebDriver; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -1256,7 +1266,81 @@ public void focusOn() throws WidgetException { jse.executeScript("arguments[0].focus();", findElement()); } - + /*** + * You can use this method to take screenshots of elements / control pictures to compare with. + * Note that the file should be a png. + * + * @param toSaveAs + * @throws IOException + * @throws WidgetTimeoutException + */ + public void captureElementScreenshot(File toSaveAs) throws IOException, WidgetException { + + for(int i = 0; i < 10; i++) { //Loop up to 10x to ensure a clean screenshot was taken + //log.info("Taking screen shot of locator " + element.getByLocator() + " ... attempt #" + (i+1)); + + //Scroll to element + this.scrollTo(); + + //Take picture of the page + WebDriver wd = getGUIDriver().getWrappedDriver(); + File screenshot; + boolean isRemote = false; + if(!(wd instanceof RemoteWebDriver)) { + screenshot = ((TakesScreenshot) wd).getScreenshotAs(OutputType.FILE); + } + else { + Augmenter augmenter = new Augmenter(); + screenshot = ((TakesScreenshot) augmenter.augment(wd)).getScreenshotAs(OutputType.FILE); + isRemote = true; + } + BufferedImage fullImage = ImageIO.read(screenshot); + WebElement ele = this.getWebElement(); + + //Parse out the picture of the element + Point point = ele.getLocation(); + int eleWidth = ele.getSize().getWidth(); + int eleHeight = ele.getSize().getHeight(); + int x; + int y; + if(isRemote) { + x = ((Locatable)ele).getCoordinates().inViewPort().getX(); + y = ((Locatable)ele).getCoordinates().inViewPort().getY(); + } + else { + x = point.getX(); + y = point.getY(); + } + //log.debug("Screenshot coordinates x: "+x+", y: "+y); + BufferedImage eleScreenshot = fullImage.getSubimage(x, y, eleWidth, eleHeight); + ImageIO.write(eleScreenshot, "png", screenshot); + + //Ensure clean snapshot (sometimes WebDriver takes bad pictures and they turn out all black) + if(!isScreenshotBlack(ImageIO.read(screenshot))) { + FileUtils.copyFile(screenshot, toSaveAs); + break; + } + } + } + + private boolean isScreenshotBlack(BufferedImage var) { + double[] varArr = new double[var.getWidth()*var.getHeight()*3]; + //unroll pixels + for(int i = 0; i < var.getHeight(); i++) { + for(int j = 0; j < var.getWidth(); j++) { + varArr[i*var.getWidth() + j + 0] = new Color(var.getRGB(j, i)).getRed(); + varArr[i*var.getWidth() + j + 1] = new Color(var.getRGB(j, i)).getGreen(); + varArr[i*var.getWidth() + j + 2] = new Color(var.getRGB(j, i)).getBlue(); + } + } + + //test if all are black + for(int i=0;i!=varArr.length;i++){ + if(varArr[i] != 0) + return false; + } + return true; + } private static class EByFirstMatching extends StringLocatorAwareBy { diff --git a/src/test/java/org/finra/jtaf/ewd/widget/element/ElementBrowserOnlyTest.java b/src/test/java/org/finra/jtaf/ewd/widget/element/ElementBrowserOnlyTest.java index 481f707..89044ab 100644 --- a/src/test/java/org/finra/jtaf/ewd/widget/element/ElementBrowserOnlyTest.java +++ b/src/test/java/org/finra/jtaf/ewd/widget/element/ElementBrowserOnlyTest.java @@ -20,7 +20,7 @@ import org.finra.jtaf.ewd.ExtWebDriver; import org.finra.jtaf.ewd.session.SessionManager; -import org.finra.jtaf.ewd.utils.ScreenshotUtils; +import org.finra.jtaf.ewd.utils.ScreenshotComparator; import org.finra.jtaf.ewd.widget.IElement; import org.finra.jtaf.ewd.widget.WidgetException; import org.finra.jtaf.ewd.widget.element.html.Image; @@ -72,7 +72,7 @@ public void testScreenshotCompare() throws Exception { Image im = new Image("//img[@src=\"finralogo.jpg\"]"); im.waitForVisible(); - Assert.assertTrue("The screenshots are similar", ScreenshotUtils.isSimilarToScreenshot( + Assert.assertTrue("The screenshots are similar", ScreenshotComparator.isElementSimilarToScreenshot( im, new File("src/test/resources/webapp/finralogo.jpg"), new File("testScreenshots/screenshot.png"))); }