package com.codeborne.selenide.impl; import com.codeborne.selenide.Configuration; import com.codeborne.selenide.WebDriverRunner; import org.openqa.selenium.*; import org.openqa.selenium.remote.Augmenter; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.UnreachableBrowserException; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.image.RasterFormatException; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; import static com.codeborne.selenide.Configuration.reportsFolder; 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 AtomicLong screenshotCounter = new AtomicLong(); protected String currentContext = ""; protected List<File> currentContextScreenshots; protected 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(); } public String takeScreenShot() { return takeScreenShot(generateScreenshotFileName()); } protected String generateScreenshotFileName() { return currentContext + 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) { if (!WebDriverRunner.hasWebDriverStarted()) { log.warning("Cannot take screenshot because browser is not started"); return null; } WebDriver webdriver = getWebDriver(); if (Configuration.savePageSource) { savePageSourceToFile(fileName, webdriver); } File imageFile = savePageImageToFile(fileName, webdriver); if (imageFile == null) { return null; } return addToHistory(imageFile).getAbsolutePath(); } public File takeScreenshot(WebElement element) { try { BufferedImage dest = takeScreenshotAsImage(element); File screenshotOfElement = new File(reportsFolder, generateScreenshotFileName() + ".png"); ensureFolderExists(screenshotOfElement); ImageIO.write(dest, "png", screenshotOfElement); return screenshotOfElement; } catch (IOException e) { printOnce("takeScreenshot", e); return null; } } public BufferedImage takeScreenshotAsImage(WebElement element) { 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; } byte[] screen = ((TakesScreenshot) webdriver).getScreenshotAs(OutputType.BYTES); Point elementLocation = element.getLocation(); try { BufferedImage img = ImageIO.read(new ByteArrayInputStream(screen)); int elementWidth = element.getSize().getWidth(); int elementHeight = element.getSize().getHeight(); if (elementWidth > img.getWidth()) { elementWidth = img.getWidth() - elementLocation.getX(); } if (elementHeight > img.getHeight()) { 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; } } public File takeScreenShotAsFile() { if (!WebDriverRunner.hasWebDriverStarted()) { log.warning("Cannot take screenshot because browser is not started"); return null; } WebDriver webdriver = getWebDriver(); //File pageSource = savePageSourceToFile(fileName, webdriver); - temporary not available File scrFile = getPageImage(webdriver); addToHistory(scrFile); 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 savePageSourceToFile(String fileName, WebDriver webdriver) { return savePageSourceToFile(fileName, webdriver, true); } protected File savePageSourceToFile(String fileName, WebDriver webdriver, boolean retryIfAlert) { File pageSource = new File(reportsFolder, fileName + ".html"); try { writeToFile(webdriver.getPageSource(), pageSource); } catch (UnhandledAlertException e) { if (retryIfAlert) { try { Alert alert = webdriver.switchTo().alert(); log.severe(e + ": " + alert.getText()); alert.accept(); savePageSourceToFile(fileName, webdriver, false); } catch (Exception unableToCloseAlert) { log.severe("Failed to close alert: " + unableToCloseAlert); } } else { printOnce("savePageSourceToFile", e); } } catch (UnreachableBrowserException e) { writeToFile(e.toString(), pageSource); return pageSource; } catch (Exception e) { writeToFile(e.toString(), pageSource); printOnce("savePageSourceToFile", e); } return pageSource; } protected File addToHistory(File screenshot) { if (currentContextScreenshots != null) { currentContextScreenshots.add(screenshot); } allScreenshots.add(screenshot); return screenshot; } protected File takeScreenshotImage(TakesScreenshot driver, String fileName) { try { File scrFile = driver.getScreenshotAs(FILE); File imageFile = new File(reportsFolder, fileName + ".png"); copyFile(scrFile, imageFile); return imageFile; } catch (Exception e) { printOnce("takeScreenshotImage", e); return null; } } 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); } } protected void copyFile(InputStream in, File targetFile) throws IOException { ensureFolderExists(targetFile); try (FileOutputStream out = new FileOutputStream(targetFile)) { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } } } protected void writeToFile(String content, File targetFile) { try (ByteArrayInputStream in = new ByteArrayInputStream(content.getBytes("UTF-8"))) { copyFile(in, targetFile); } 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); } public void startContext(String context) { this.currentContext = context; currentContextScreenshots = new ArrayList<>(); } public List<File> finishContext() { List<File> result = currentContextScreenshots; this.currentContext = ""; currentContextScreenshots = null; return result; } public List<File> getScreenshots() { return allScreenshots; } public File getLastScreenshot() { return allScreenshots.isEmpty() ? null : allScreenshots.get(allScreenshots.size() - 1); } public String formatScreenShotPath() { if (!Configuration.screenshots) { log.config("Automatic screenshots are disabled."); return ""; } String screenshot = takeScreenShot(); if (screenshot == null) { return ""; } if (Configuration.reportsUrl != null) { String screenshotRelativePath = screenshot.substring(System.getProperty("user.dir").length() + 1); String screenshotUrl = Configuration.reportsUrl + screenshotRelativePath.replace('\\', '/'); try { screenshotUrl = new URL(screenshotUrl).toExternalForm(); } catch (MalformedURLException ignore) { } log.config("Replaced screenshot file path '" + screenshot + "' by public CI URL '" + screenshotUrl + "'"); return screenshotUrl; } log.config("reportsUrl is not configured. Returning screenshot file name '" + screenshot + "'"); try { return new File(screenshot).toURI().toURL().toExternalForm(); } catch (MalformedURLException e) { return "file://" + screenshot; } } }