org.sakuli.actions.screenbased.ScreenshotActions.java Source code

Java tutorial

Introduction

Here is the source code for org.sakuli.actions.screenbased.ScreenshotActions.java

Source

/*
 * Sakuli - Testing and Monitoring-Tool for Websites and common UIs.
 *
 * Copyright 2013 - 2015 the original author or authors.
 *
 * 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.sakuli.actions.screenbased;

import org.apache.commons.lang.StringUtils;
import org.sakuli.datamodel.TestCase;
import org.sakuli.datamodel.TestSuite;
import org.sakuli.datamodel.actions.Screen;
import org.sakuli.datamodel.properties.ActionProperties;
import org.sakuli.exceptions.SakuliException;
import org.sakuli.loader.BaseActionLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import static org.apache.commons.lang.StringUtils.isEmpty;

@Component
public class ScreenshotActions {
    private static final int MAX_FILENAME_LENGTH = 50;
    protected static final Logger LOGGER = LoggerFactory.getLogger(ScreenshotActions.class);
    private final static List<String> allowedScreenshotFormats = Collections
            .unmodifiableList(Arrays.asList("jpg", "png"));
    private BaseActionLoader baseActionLoader;
    private String defaultScreenshotFormat;
    private Screen screen;

    @Autowired
    public ScreenshotActions(Screen screen, ActionProperties props,
            @Qualifier("baseLoader") BaseActionLoader baseActionLoader) {
        this.screen = screen;
        this.defaultScreenshotFormat = props.getScreenshotformat();
        this.baseActionLoader = baseActionLoader;
    }

    /**
     * Transfers a {@link BufferedImage} to an picture and saves it
     *
     * @param message    Prefixed filename of the picture (timestamp will be added)
     * @param folderPath Folder of the place, where the picture should be stored
     * @return {@link Path} ot the screenshot.
     */
    static Path createPictureFromBufferedImage(String message, Path folderPath, String screenShotFormat,
            BufferedImage bufferedImage) throws IOException {
        LOGGER.info("create screen shot for error \"" + message + "\"");
        //generate a valid file name
        String fileName = TestSuite.GUID_DATE_FORMATE.format(new Date()) + "_" + replaceSpecialCharacters(message)
                + "." + screenShotFormat;
        if (fileName.length() > MAX_FILENAME_LENGTH) {
            fileName = fileName.substring(0, MAX_FILENAME_LENGTH) + "." + screenShotFormat;
        }

        //create file
        Path pictureFile = folderPath.resolve(fileName);
        return createPictureFromBufferedImage(pictureFile, screenShotFormat, bufferedImage);
    }

    /**
     * Transfers a {@link BufferedImage} to an picture and saves it
     *
     * @param pictureFile Path to the final image
     * @return {@link Path} ot the screenshot.
     */
    static Path createPictureFromBufferedImage(Path pictureFile, String screenshotFormat,
            BufferedImage bufferedImage) throws IOException {
        Path absPath = pictureFile.normalize().toAbsolutePath();
        //check Folder
        if (!Files.exists(absPath.getParent())) {
            Files.createDirectory(absPath.getParent());
        }

        if (Files.exists(absPath)) {
            LOGGER.info("overwrite screenshot '{}'", absPath);
            Files.delete(absPath);
        }

        Files.createFile(pictureFile);
        OutputStream outputStream = Files.newOutputStream(pictureFile);

        if (!allowedScreenshotFormats.contains(screenshotFormat)) {
            throw new IOException("Format '" + screenshotFormat + "' is not supported! Use "
                    + allowedScreenshotFormats.toString());
        }
        //write image
        ImageIO.write(bufferedImage, screenshotFormat, outputStream);
        LOGGER.info("screen shot saved to file \"" + pictureFile.toFile().getAbsolutePath() + "\"");
        return pictureFile;
    }

    private static String replaceSpecialCharacters(String string) {
        return string.replaceAll("[^a-zA-Z0-9]", "_").replaceAll("_+", "_");
    }

    /**
     * serrated method to generate the buffered image, to mock the screen usage in tests
     */
    BufferedImage createBufferedImage(Rectangle rectangle) {
        return rectangle == null ? screen.capture().getImage() : screen.capture(rectangle).getImage();
    }

    /**
     * Takes a screenshot of the hole screen area and save in the given format;
     *
     * @param picturePath Path to the final image
     * @return {@link Path} ot the screenshot.
     */
    public Path takeScreenshot(Path picturePath) {
        return takeScreenshot(picturePath, null);
    }

    /**
     * Takes a screenshot of the assigned area and save in the given format;
     *
     * @param filename  filename to resolve
     * @param rectangle if not null, the screenshot only shows the defined rectangle
     * @return {@link Path} ot the screenshot.
     */
    public Path takeScreenshot(String filename, Rectangle rectangle) {
        return takeScreenshot(resolveTakeScreenshotPath(filename), rectangle);
    }

    /**
     * Takes a screenshot of the assigned area and save in the given format;
     *
     * @param picturePath Path to the final image
     * @param rectangle   if not null, the screenshot only shows the defined rectangle
     * @return {@link Path} ot the screenshot.
     */
    public Path takeScreenshot(Path picturePath, Rectangle rectangle) {
        try {
            if (Files.isDirectory(picturePath)) {
                throw new IOException("Path to a file in format [folder-path]" + File.separator
                        + "screenshotname[.png|jpg] expected!");
            }
            String filename = picturePath.getFileName().toString();
            String format = defaultScreenshotFormat;
            if (filename.contains(".")) {
                format = filename.substring(filename.lastIndexOf(".") + 1);
            } else {
                //add default screenshot format
                picturePath = picturePath.getParent().resolve(filename + "." + format);
            }
            return createPictureFromBufferedImage(picturePath, format, createBufferedImage(rectangle));
        } catch (Exception e) {
            baseActionLoader.getExceptionHandler().handleException(
                    new SakuliException(e, "Can't create Screenshot for path '" + picturePath + "'"));
            return null;
        }

    }

    /**
     * Create a screenshot with timestamp in the filename.
     * Resolves the optFolderPath to a valid path!
     *
     * @return the path of the screenshot file
     */
    public Path takeScreenshotWithTimestamp(final String filenamePostfix, final String optFolderPath,
            final String optFormat, Rectangle optRectangle) {
        Path folderPath = isEmpty(optFolderPath) ? resolveTakeScreenshotPath(filenamePostfix).getParent()
                : Paths.get(optFolderPath);
        return takeScreenshotWithTimestamp(filenamePostfix, folderPath, optFormat, optRectangle);
    }

    /**
     * takes a Screenshot with timestamp for a specific format from the whole screen
     */
    public Path takeScreenshotWithTimestamp(String message, Path folderPath) {
        return takeScreenshotWithTimestamp(message, folderPath, null, null);
    }

    /**
     * takes a Screenshot with timestamp for a specific format
     */
    public Path takeScreenshotWithTimestamp(String message, Path folderPath, String format, Rectangle rectangle) {
        try {
            return takeScreenshotWithTimestampThrowIOException(message, folderPath, format, rectangle);
        } catch (IOException e) {
            baseActionLoader.getExceptionHandler()
                    .handleException(new SakuliException(e, "Can't execute 'takeScreenshotWithTimestamp()' for '"
                            + message + ", " + folderPath + ", " + format + "'"));
            return null;
        }
    }

    /**
     * takes a Screenshot with timestamp for a specific format
     */
    public Path takeScreenshotWithTimestampThrowIOException(String message, Path folderPath, String format,
            Rectangle rectangle) throws IOException {
        return createPictureFromBufferedImage(message, folderPath,
                StringUtils.isEmpty(format) ? this.defaultScreenshotFormat : format,
                createBufferedImage(rectangle));
    }

    /**
     * Resolves a given String to absolute Path or if not absolute:
     * a) the current testcase folder
     * b) the current testsuite folder
     */
    public Path resolveTakeScreenshotPath(String filename) {
        Path path = Paths.get(filename);
        if (path.isAbsolute()) {
            return path;
        }
        TestCase currentTestCase = baseActionLoader.getCurrentTestCase();
        Path folderPath = currentTestCase != null ? currentTestCase.getTcFile().getParent() : null;
        if (folderPath == null || !Files.exists(folderPath)) {
            LOGGER.warn("The test case folder could not be found => Fallback: Use test suite folder!");
            folderPath = baseActionLoader.getTestSuite().getTestSuiteFolder();
        }
        return folderPath.resolve(filename);
    }

}