Skip to content

Commit

Permalink
Make screenshot of SelenideElement/WebElement which is inside iframe (#…
Browse files Browse the repository at this point in the history
…705)

* Adding possibility to make screenshot of SelenideElement of WebElement which is inside iframe
* Added tests
* Added same behavior as for existing screenshoting functionality in case if element doesn't exist inside iframe
* Now taking into account vertical scroll bar for partially visible elements
  • Loading branch information
andrejska authored and rosolko committed Apr 18, 2018
1 parent c3777b7 commit 536e41d
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 94 deletions.
18 changes: 18 additions & 0 deletions src/main/java/com/codeborne/selenide/Screenshots.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ public static File takeScreenShot(WebElement element) {
return screenshots.takeScreenshot(element);
}

/**
* Take screenshot of WebElement/SelenideElement in iframe
* for partially visible WebElement/Selenide horizontal scroll bar will be present
* @return a temporary file, not guaranteed to be stored after tests complete.
*/
public static File takeScreenShot(WebElement iframe, WebElement element) {
return screenshots.takeScreenshot(iframe, element);
}

/**
* Take screenshot of WebElement/SelenideElement in iframe
* for partially visible WebElement/Selenide horizontal scroll bar will be present
* @return buffered image
*/
public static BufferedImage takeScreenShotAsImage(WebElement iframe, WebElement element) {
return screenshots.takeScreenshotAsImage(iframe, element);
}

/**
* Take screenshot of the WebElement/SelenideElement
* @return buffered image
Expand Down
244 changes: 150 additions & 94 deletions src/main/java/com/codeborne/selenide/impl/ScreenShotLaboratory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import com.codeborne.selenide.Configuration;
import com.codeborne.selenide.WebDriverRunner;

import org.openqa.selenium.Alert;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.UnhandledAlertException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.UnreachableBrowserException;

import javax.imageio.ImageIO;
Expand All @@ -33,62 +33,39 @@
import java.util.logging.Logger;

import static com.codeborne.selenide.Configuration.reportsFolder;
import static com.codeborne.selenide.Selenide.switchTo;
import static com.codeborne.selenide.WebDriverRunner.getWebDriver;
import static java.io.File.separatorChar;
import static java.util.logging.Level.SEVERE;
import static org.openqa.selenium.OutputType.FILE;

public class ScreenShotLaboratory {
private static final Logger log = Logger.getLogger(ScreenShotLaboratory.class.getName());

protected final List<File> allScreenshots = new ArrayList<>();
protected AtomicLong screenshotCounter = new AtomicLong();
protected ThreadLocal<String> currentContext = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
protected ThreadLocal<String> currentContext = ThreadLocal.withInitial(() -> "");
protected ThreadLocal<List<File>> currentContextScreenshots = new ThreadLocal<>();
protected final List<File> allScreenshots = new ArrayList<>();

protected Set<String> printedErrors = new ConcurrentSkipListSet<>();

protected synchronized void printOnce(String action, Throwable error) {
if (!printedErrors.contains(action)) {
log.log(SEVERE, error.getMessage(), error);
printedErrors.add(action);
}
else {
log.severe("Failed to " + action + ": " + error);
}
}

public String takeScreenShot(String className, String methodName) {
return takeScreenShot(getScreenshotFileName(className, methodName));
}

protected String getScreenshotFileName(String className, String methodName) {
return className.replace('.', separatorChar) + separatorChar +
methodName + '.' + timestamp();
}

protected long timestamp() {
return System.currentTimeMillis();
methodName + '.' + timestamp();
}

public String takeScreenShot() {
return takeScreenShot(generateScreenshotFileName());
}

protected String generateScreenshotFileName() {
return currentContext.get() + timestamp() + "." + screenshotCounter.getAndIncrement();
}

/**
* Takes screenshot of current browser window.
* Stores 2 files: html of page (if "savePageSource" option is enabled), and (if possible) image in PNG format.
*
* @param fileName name of file (without extension) to store screenshot to.
*
* @return the name of last saved screenshot or null if failed to create screenshot
*/
public String takeScreenShot(String fileName) {
Expand Down Expand Up @@ -117,8 +94,7 @@ public File takeScreenshot(WebElement element) {
ensureFolderExists(screenshotOfElement);
ImageIO.write(dest, "png", screenshotOfElement);
return screenshotOfElement;
}
catch (IOException e) {
} catch (IOException e) {
printOnce("takeScreenshot", e);
return null;
}
Expand Down Expand Up @@ -150,15 +126,120 @@ public BufferedImage takeScreenshotAsImage(WebElement element) {
elementHeight = img.getHeight() - elementLocation.getY();
}
return img.getSubimage(elementLocation.getX(), elementLocation.getY(), elementWidth, elementHeight);
} catch (IOException e) {
printOnce("takeScreenshotImage", e);
return null;
} catch (RasterFormatException e) {
log.warning("Cannot take screenshot because element is not displayed on current screen position");
return null;
}
}

protected String generateScreenshotFileName() {
return currentContext.get() + timestamp() + "." + screenshotCounter.getAndIncrement();
}

protected File ensureFolderExists(File targetFile) {
File folder = targetFile.getParentFile();
if (!folder.exists()) {
log.info("Creating folder: " + folder);
if (!folder.mkdirs()) {
log.severe("Failed to create " + folder);
}
}
return targetFile;
}

protected synchronized void printOnce(String action, Throwable error) {
if (!printedErrors.contains(action)) {
log.log(SEVERE, error.getMessage(), error);
printedErrors.add(action);
} else {
log.severe("Failed to " + action + ": " + error);
}
}

protected long timestamp() {
return System.currentTimeMillis();
}

public File takeScreenshot(WebElement iframe, WebElement element) {
try {
BufferedImage dest = takeScreenshotAsImage(iframe, element);
if (dest == null) {
return null;
}
File screenshotOfElement = new File(reportsFolder, generateScreenshotFileName() + ".png");
ensureFolderExists(screenshotOfElement);
ImageIO.write(dest, "png", screenshotOfElement);
return screenshotOfElement;
} catch (IOException e) {
printOnce("takeScreenshot", e);
return null;
}
catch (IOException e) {
}

public BufferedImage takeScreenshotAsImage(WebElement iframe, WebElement element) {
WebDriver webdriver = checkIfFullyValidDriver();
if (webdriver == null) {
return null;
}
byte[] screen = ((TakesScreenshot) webdriver).getScreenshotAs(OutputType.BYTES);
Point iframeLocation = iframe.getLocation();
BufferedImage img;
try {
img = ImageIO.read(new ByteArrayInputStream(screen));
} catch (IOException e) {
printOnce("takeScreenshotImage", e);
return null;
} catch (RasterFormatException ex) {
log.warning("Cannot take screenshot because iframe is not displayed");
return null;
}
catch (RasterFormatException e) {
log.warning("Cannot take screenshot because element is not displayed on current screen position");
int iframeHeight = iframe.getSize().getHeight();
switchTo().frame(iframe);
int iframeWidth = ((Long) ((JavascriptExecutor) webdriver).executeScript("return document.body.clientWidth")).intValue();
if (iframeHeight > img.getHeight()) {
iframeHeight = img.getHeight() - iframeLocation.getY();
}
if (iframeWidth > img.getWidth()) {
iframeWidth = img.getWidth() - iframeLocation.getX();
}
Point elementLocation = element.getLocation();
int elementWidth = element.getSize().getWidth();
int elementHeight = element.getSize().getHeight();
if (elementWidth > iframeWidth) {
elementWidth = iframeWidth - elementLocation.getX();
}
if (elementHeight > iframeHeight) {
elementHeight = iframeHeight - elementLocation.getY();
}
switchTo().defaultContent();
try {
img = img.getSubimage(iframeLocation.getX() + elementLocation.getX(), iframeLocation.getY() + elementLocation.getY(),
elementWidth, elementHeight);
} catch (RasterFormatException ex) {
log.warning("Cannot take screenshot because element is not displayed in iframe");
return null;
}
return img;
}

private WebDriver checkIfFullyValidDriver() {
if (!WebDriverRunner.hasWebDriverStarted()) {
log.warning("Cannot take screenshot because browser is not started");
return null;
}

WebDriver webdriver = getWebDriver();
if (!(webdriver instanceof TakesScreenshot)) {
log.warning("Cannot take screenshot because browser does not support screenshots");
return null;
} else if (!(webdriver instanceof JavascriptExecutor)) {
log.warning("Cannot take screenshot as driver is not supporting javascript execution");
return null;
}
return webdriver;
}

public File takeScreenShotAsFile() {
Expand All @@ -174,32 +255,41 @@ public File takeScreenShotAsFile() {
return scrFile;
}

protected File savePageImageToFile(String fileName, WebDriver webdriver) {
File imageFile = null;
if (webdriver instanceof TakesScreenshot) {
imageFile = takeScreenshotImage((TakesScreenshot) webdriver, fileName);
} else if (webdriver instanceof RemoteWebDriver) { // TODO Remove this obsolete branch
WebDriver remoteDriver = new Augmenter().augment(webdriver);
if (remoteDriver instanceof TakesScreenshot) {
imageFile = takeScreenshotImage((TakesScreenshot) remoteDriver, fileName);
}
}
return imageFile;
}

protected File getPageImage(WebDriver webdriver) {
File scrFile = null;
if (webdriver instanceof TakesScreenshot) {
scrFile = takeScreenshotInMemory((TakesScreenshot) webdriver);
} else if (webdriver instanceof RemoteWebDriver) { // TODO Remove this obsolete branch
WebDriver remoteDriver = new Augmenter().augment(webdriver);
if (remoteDriver instanceof TakesScreenshot) {
scrFile = takeScreenshotInMemory((TakesScreenshot) remoteDriver);
}
}
return scrFile;
}

protected File addToHistory(File screenshot) {
if (currentContextScreenshots.get() != null) {
currentContextScreenshots.get().add(screenshot);
}
synchronized (allScreenshots) {
allScreenshots.add(screenshot);
}
return screenshot;
}

protected File takeScreenshotInMemory(TakesScreenshot driver) {
try {
return driver.getScreenshotAs(FILE);
} catch (Exception e) {
printOnce("takeScreenshotAsFile", e);
return null;
}
}

protected File savePageImageToFile(String fileName, WebDriver webdriver) {
File imageFile = null;
if (webdriver instanceof TakesScreenshot) {
imageFile = takeScreenshotImage((TakesScreenshot) webdriver, fileName);
}
return imageFile;
}

protected File savePageSourceToFile(String fileName, WebDriver webdriver) {
return savePageSourceToFile(fileName, webdriver, true);
}
Expand All @@ -216,36 +306,22 @@ protected File savePageSourceToFile(String fileName, WebDriver webdriver, boolea
log.severe(e + ": " + alert.getText());
alert.accept();
savePageSourceToFile(fileName, webdriver, false);
}
catch (Exception unableToCloseAlert) {
} catch (Exception unableToCloseAlert) {
log.severe("Failed to close alert: " + unableToCloseAlert);
}
}
else {
} else {
printOnce("savePageSourceToFile", e);
}
}
catch (UnreachableBrowserException e) {
} catch (UnreachableBrowserException e) {
writeToFile(e.toString(), pageSource);
return pageSource;
}
catch (Exception e) {
} catch (Exception e) {
writeToFile(e.toString(), pageSource);
printOnce("savePageSourceToFile", e);
}
return pageSource;
}

protected File addToHistory(File screenshot) {
if (currentContextScreenshots.get() != null) {
currentContextScreenshots.get().add(screenshot);
}
synchronized (allScreenshots) {
allScreenshots.add(screenshot);
}
return screenshot;
}

protected File takeScreenshotImage(TakesScreenshot driver, String fileName) {
try {
File scrFile = driver.getScreenshotAs(FILE);
Expand All @@ -258,15 +334,6 @@ protected File takeScreenshotImage(TakesScreenshot driver, String fileName) {
}
}

protected File takeScreenshotInMemory(TakesScreenshot driver) {
try {
return driver.getScreenshotAs(FILE);
} catch (Exception e) {
printOnce("takeScreenshotAsFile", e);
return null;
}
}

protected void copyFile(File sourceFile, File targetFile) throws IOException {
try (FileInputStream in = new FileInputStream(sourceFile)) {
copyFile(in, targetFile);
Expand All @@ -288,23 +355,11 @@ protected void copyFile(InputStream in, File targetFile) throws IOException {
protected void writeToFile(String content, File targetFile) {
try (ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"))) {
copyFile(in, targetFile);
}
catch (IOException e) {
} catch (IOException e) {
log.log(SEVERE, "Failed to write file " + targetFile.getAbsolutePath(), e);
}
}

protected File ensureFolderExists(File targetFile) {
File folder = targetFile.getParentFile();
if (!folder.exists()) {
log.info("Creating folder: " + folder);
if (!folder.mkdirs()) {
log.severe("Failed to create " + folder);
}
}
return targetFile;
}

public void startContext(String className, String methodName) {
String context = className.replace('.', separatorChar) + separatorChar + methodName + separatorChar;
startContext(context);
Expand Down Expand Up @@ -350,8 +405,9 @@ public String formatScreenShotPath() {
String screenshotUrl = Configuration.reportsUrl + screenshotRelativePath.replace('\\', '/');
try {
screenshotUrl = new URL(screenshotUrl).toExternalForm();
} catch (MalformedURLException ignore) {
// ignored exception
}
catch (MalformedURLException ignore) { }
log.config("Replaced screenshot file path '" + screenshot + "' by public CI URL '" + screenshotUrl + "'");
return screenshotUrl;
}
Expand Down
Loading

0 comments on commit 536e41d

Please sign in to comment.