com.mgmtp.jfunk.core.util.ScreenCapturer.java Source code

Java tutorial

Introduction

Here is the source code for com.mgmtp.jfunk.core.util.ScreenCapturer.java

Source

/*
 * Copyright (c) 2015 mgm technology partners GmbH
 *
 * 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 com.mgmtp.jfunk.core.util;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.io.Files.createParentDirs;

import java.awt.AWTException;
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 java.util.Set;

import javax.imageio.ImageIO;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.mgmtp.jfunk.common.exception.JFunkException;
import com.mgmtp.jfunk.core.config.ModuleArchiveDir;
import com.mgmtp.jfunk.core.event.AbstractBaseEvent;
import com.mgmtp.jfunk.core.event.AfterModuleEvent;

/**
 * <p>
 * Class for creating screenshots.
 * </p>
 * <p>
 * <b>Note:</b><br />
 * When jFunk is running multiple threads, i. e. multiple browser windows are open, there is no
 * guarantee that a screenshot captures the correct browser window because the current thread's
 * browser window may not be the top-level one.
 * </p>
 * <p>
 * The static methods of the class may be used independently. However, the class may also be
 * registered with jFunk's {@link EventBus} using {@link ScreenCapturerModule} in order to configure
 * automatic screenshots.
 * </p>
 * <p>
 * In order to make jFunk create automatic screenshots whenever an {@link AbstractBaseEvent} is
 * posted on the {@link EventBus}, the Guice module {@link ScreenCapturerModule} must be installed
 * and the actual {@link AbstractBaseEvent} descendants eligible for capturing the screen must be
 * configured as a comma-separated list using the property {@code screenshot.event.classes}. In the
 * case of an error, a screenshot is always taken on {@link AfterModuleEvent} if the property
 * {@code screenshot.on.error} is set to {@code true}.
 * </p>
 * <p>
 * <b>Example:</b>
 * 
 * <pre>
 * screenshot.event.classes = com.mgmtp.jfunk.core.event.AfterStepEvent,com.mgmtp.jfunk.core.event.AfterModuleEvent
 * screenshot.on.error = true
 * </pre>
 * 
 * </p>
 * 
 * @author rnaegele
 * @since 3.1.0
 */
@Singleton
public class ScreenCapturer {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScreenCapturer.class);
    private static final String SCREENSHOT_PATH_FORMAT = "screenshots/%04d_%s.png";

    private final Provider<File> moduleArchiveDirProvider;
    private final Set<Class<? extends AbstractBaseEvent>> screenCaptureEvents;
    private final Provider<MutableInt> counterProvider;
    private final boolean captureScreenOnError;

    @Inject
    public ScreenCapturer(@ModuleArchiveDir final Provider<File> moduleArchiveDirProvider,
            final Set<Class<? extends AbstractBaseEvent>> screenCaptureEvents,
            final Provider<MutableInt> counterProvider, @CaptureScreenOnError final boolean captureScreenOnError) {
        this.moduleArchiveDirProvider = moduleArchiveDirProvider;
        this.screenCaptureEvents = screenCaptureEvents;
        this.counterProvider = counterProvider;
        this.captureScreenOnError = captureScreenOnError;
    }

    /**
     * Captures the whole screen and saves it as png to the specified file. The file must have the
     * extension {@code png}. Any necessary but non-existent parent directories of the specified
     * file are created.
     * 
     * @param file
     *            the file to save the screenshot to
     */
    public static void captureScreen(final File file) {
        Rectangle rectangle = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
        captureScreenRect(file, rectangle);
    }

    /**
     * Captures the specified rectangle and saves it as png to the specified file. The file must
     * have the extension {@code png}. Any necessary but non-existent parent directories of the
     * specified file are created.
     * 
     * @param file
     *            the file to save the screenshot to
     * @param rectangle
     *            the rectangle to capture in screen coordinates
     */
    public static void captureScreenRect(final File file, final Rectangle rectangle) {
        checkState(file.getName().endsWith("png"), "Screenshot file must have extension 'png': %s", file);
        LOGGER.trace("Creating screenshot: {}", file);

        try {
            createParentDirs(file);
            BufferedImage capture = new Robot().createScreenCapture(rectangle);
            ImageIO.write(capture, "png", file);
        } catch (AWTException ex) {
            throw new JFunkException("Error capturing screen", ex);
        } catch (IOException ex) {
            throw new JFunkException("Error saving screenshot", ex);
        }
    }

    /**
     * Event handler method used by the {@link EventBus}. If configured for the given event's class,
     * a screenshot is taken and stored in folder {@code screenshots} in the current module's
     * archive directory. The screenshot images are named by the event class' simple name, prefixed
     * with a left-padded four-digit integer counter (format: {@code %04d_%s.png}).
     * 
     * @param event
     *            the event
     */
    @Subscribe
    @AllowConcurrentEvents
    public void handleEvent(final AbstractBaseEvent event) {
        Class<? extends AbstractBaseEvent> clazz = event.getClass();

        boolean errorCapture = captureScreenOnError(event);
        boolean eventConfigured = screenCaptureEvents.contains(clazz);
        if (eventConfigured || errorCapture) {
            String screenshotName = clazz.getSimpleName();
            if (errorCapture) {
                screenshotName += "_error";
            }
            captureAndArchiveScreen(screenshotName);
        }
    }

    private boolean captureScreenOnError(final AbstractBaseEvent event) {
        if (captureScreenOnError && event instanceof AfterModuleEvent) {
            return !((AfterModuleEvent) event).isSuccess();
        }

        return false;
    }

    /**
     * Takes a screenshot and stores it in folder {@code screenshots} in the current module's
     * archive directory. The screenshot image is named by the specified screenshot name, prefixed
     * with a left-padded four-digit integer counter (format: {@code %04d_%s.png}).
     * 
     * @param screenshotName
     *            the name of the screenshot
     */
    public void captureAndArchiveScreen(final String screenshotName) {
        MutableInt counter = counterProvider.get();
        String relativePath = String.format(SCREENSHOT_PATH_FORMAT, counter.intValue(), screenshotName);
        captureScreen(new File(moduleArchiveDirProvider.get(), relativePath));
        counter.increment();
    }
}