/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.runtime.util;
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.windowtester.internal.debug.LogHandler;
/**
* Dumps a <em>png</em> of the current screen in the standard "wintest" output directory (as defined by {@link ScreenCapture#getOutputLocation()}).
* Since taking screen shots consumes considerable resources, heap limits may be exceeded during a capture. To address
* this, {@link OutOfMemoryError}s are caught in the default handler implementation and the capture retried after a short interval
* ({@link ScreenCapture#CAPTURE_RETRY_INTERVAL}). The number of retries is bounded by {@link ScreenCapture#MAX_CAPTURE_RETRIES}.
* <p>
* Screen capture behavior can be specialized:
*
* <p>
* <b>1. Output Location.</b> Capture output can be redirected by calling {@link ScreenCapture#setOutputLocation(String)}.
* <p>
* <b>2. Screen Capture Handling.</b> The default screen capture implementation can be overridden by calling {@link ScreenCapture#setHandler(IScreenCaptureHandler)}. For example, the
* following user-defined handler will override the default behavior by doing nothing, essentially disabling screenshots.
*
* <pre>
* ScreenCapture.setHandler(new IScreenCaptureHandler() {
* public File createScreenCapture(String name) {
* //no-op
* return null;
* }
* });
* </pre>
*
*
*/
public class ScreenCapture {
public static final int MAX_CAPTURE_RETRIES = 5;
public static final int CAPTURE_RETRY_INTERVAL = 3000;
private static final String WINDOW_TESTER_SCREEN_CAPTURE_PATHS_KEY = "WindowTesterScreenCapturePaths";
private static final String BASE_IMAGE_NAME = "screenshot";
private static final String IMAGE_EXT = "png"; // best format for ui graphics
private static final String PATH_DELIM = System.getProperty("file.separator");
//TODO: make this use configurable
private static String OUTPUT_DIR = "wintest";
// increment counter for unique screenshot file names
// on a per run basis
private static int _counter = 0;
// keep a static Robot around to do the screencapture
private static Robot _robot;
static {
try {
_robot = new Robot();
} catch ( AWTException e ) {
LogHandler.log(e);
}
}
/**
* A user-defined screen capture strategy.
*
*/
public static interface IScreenCaptureHandler {
/**
* Create a screenshot file with the given name.
* @param name - the name of the screenshot
* @return a screenshot file (note: may be <code>null</code>
*/
File createScreenCapture(String name);
}
private static class DefaultScreenCaptureHandler implements IScreenCaptureHandler {
/**
* Save the screen pixels as a PNG image file in the current directory.
* Existing screen cap files will be overwritten.
*
* The name parameter will be used as a prefix for the name of the
* produced image.
*
* TODO parameterize the image file location
*
* @return the file into which the image was stored,
* or <code>null</code> if the image could not be stored.
*/
public File createScreenCapture(String name) {
BufferedImage image = captureScreen();
if (image == null)
return null;
return createScreenCaptureFile(image, name);
}
public static BufferedImage captureScreen() {
// determine current screen size
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screenSize = toolkit.getScreenSize();
Rectangle screenRect = new Rectangle( screenSize );
for (int i=0; i < MAX_CAPTURE_RETRIES; ++i) {
try {
return _robot.createScreenCapture( screenRect );
} catch (OutOfMemoryError e) {
LogHandler.log("OutOfMemoryError caught in screen capture (attempt [" + i + "])");
try {
Thread.sleep(CAPTURE_RETRY_INTERVAL);
} catch (InterruptedException e1) {
//just continue
}
}
}
LogHandler.log("Screen Capture failed");
return null;
}
public static File createScreenCaptureFile(BufferedImage image, String name) {
File file;
try {
ensureOutputDirExists();
// save captured image to PNG file
String path = getOutputLocation() + name + "_" + BASE_IMAGE_NAME + "_" + _counter++ + "." + IMAGE_EXT;
file = new File(path);
/*boolean writerFound = */ ImageIO.write( image, IMAGE_EXT, file );
//System.out.println(writerFound);
}
catch ( IOException e) {
LogHandler.log(e);
return null;
}
// If there was a successful screencapture and the
// WindowTesterXMLJunitResultFormatter is in use then record the screencapture so
// that WindowTesterXMLJunitResultFormatter can report it as a test artifact
String paths = System.getProperty(WINDOW_TESTER_SCREEN_CAPTURE_PATHS_KEY);
if (paths != null) {
if (paths.length() > 0)
paths += ",";
paths += file.getAbsolutePath();
System.setProperty(WINDOW_TESTER_SCREEN_CAPTURE_PATHS_KEY, paths);
}
return file;
}
}
private static void ensureOutputDirExists() {
File dir = new File(OUTPUT_DIR);
if (!dir.exists())
dir.mkdir();
}
/**
* Get the output directory for screenshots.
*/
public static String getOutputLocation() {
return OUTPUT_DIR + PATH_DELIM;
}
private static IScreenCaptureHandler handler;
private static IScreenCaptureHandler getDefaultHandler() {
// return new SWTGCScreenCaptureHandler();
return new DefaultScreenCaptureHandler();
}
/**
* Override the default screen capture strategy with a user-defined one.
* @param handler - a user-defined screen capture strategy, or <code>null</code> to reset the default
*/
public static void setHandler(IScreenCaptureHandler handler) {
ScreenCapture.handler = handler;
}
/**
* purely static class, no instances allowed
*/
private ScreenCapture()
{
}
/**
* Save the screen pixels as a PNG image file in the current directory
* (see {@link #createScreenCapture(String)}).
*
* @return the file into which the image was stored,
* or <code>null</code> if the image could not be stored.
*/
public static File createScreenCapture() {
return createScreenCapture(TestMonitor.getInstance().getCurrentTestCaseID());
}
/**
* Save the screen pixels as a PNG image file in the current directory.
* Existing screen cap files will be overwritten.
*
* The name parameter will be used as a prefix for the name of the
* produced image.
*
* TODO parameterize the image file location
*
* @return the file into which the image was stored,
* or <code>null</code> if the image could not be stored.
*/
public static File createScreenCapture(String name) {
return getHandler().createScreenCapture(name);
}
private static final IScreenCaptureHandler getHandler() {
if (handler == null)
handler = getDefaultHandler();
return handler;
}
/**
* Set the output path relative to the eclipse base directory.
*/
public static void setOutputLocation(String path) {
if (path == null)
throw new IllegalArgumentException("Path must not be null");
OUTPUT_DIR = path;
}
}