aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Lu <andrewlu@openjdk.org>2024-02-23 09:06:07 +0000
committerVitaly Provodin <vitaly.provodin@jetbrains.com>2024-04-19 15:36:01 +0700
commitde90d67c850ae806335e9f87d71aadd231ca5ae8 (patch)
tree0a68f3ff9ec88e976346d4f6f9318fb4628b9bfc
parente7fa0e07bd57b9d1207c40d03d2e0c20245a80b2 (diff)
downloadJetBrainsRuntime-de90d67c850ae806335e9f87d71aadd231ca5ae8.tar.gz
8294535: Add screen capture functionality to PassFailJFrame
Backport-of: dbb788f34dbbe0aa5c8356fb4a5dc19b96787d25
-rw-r--r--test/jdk/java/awt/regtesthelpers/PassFailJFrame.java228
1 files changed, 223 insertions, 5 deletions
diff --git a/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java b/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
index a13d6ee2e08..92ff9371933 100644
--- a/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
+++ b/test/jdk/java/awt/regtesthelpers/PassFailJFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -21,31 +21,44 @@
* questions.
*/
+import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
+import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
+import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.awt.image.RenderedImage;
+import java.io.File;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.imageio.ImageIO;
import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
+import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
-
import static javax.swing.SwingUtilities.invokeAndWait;
import static javax.swing.SwingUtilities.isEventDispatchThread;
@@ -68,7 +81,9 @@ public class PassFailJFrame {
private static volatile boolean failed;
private static volatile boolean timeout;
private static volatile String testFailedReason;
+ private static final AtomicInteger imgCounter = new AtomicInteger(0);
private static JFrame frame;
+ private static Robot robot;
public enum Position {HORIZONTAL, VERTICAL, TOP_LEFT_CORNER}
@@ -114,16 +129,62 @@ public class PassFailJFrame {
public PassFailJFrame(String title, String instructions, long testTimeOut,
int rows, int columns) throws InterruptedException,
InvocationTargetException {
+ this(title, instructions, testTimeOut, rows, columns, false);
+ }
+
+ /**
+ * Constructs a JFrame with a given title & serves as test instructional
+ * frame where the user follows the specified test instruction in order
+ * to test the test case & mark the test pass or fail. If the expected
+ * result is seen then the user click on the 'Pass' button else click
+ * on the 'Fail' button and the reason for the failure should be
+ * specified in the JDialog JTextArea.
+ * <p>
+ * The test instruction frame also provides a way for the tester to take
+ * a screenshot (full screen or individual frame) if this feature
+ * is enabled by passing {@code true} as {@code enableScreenCapture}
+ * parameter.
+ *
+ * @param title title of the Frame.
+ * @param instructions the instruction for the tester on how to test
+ * and what is expected (pass) and what is not
+ * expected (fail).
+ * @param testTimeOut test timeout where time is specified in minutes.
+ * @param rows number of visible rows of the JTextArea where the
+ * instruction is show.
+ * @param columns Number of columns of the instructional
+ * JTextArea
+ * @param enableScreenCapture if set to true, 'Capture Screen' button & its
+ * associated UIs are added to test instruction
+ * frame
+ * @throws InterruptedException exception thrown when thread is
+ * interrupted
+ * @throws InvocationTargetException if an exception is thrown while
+ * creating the test instruction frame on
+ * EDT
+ */
+ public PassFailJFrame(String title, String instructions, long testTimeOut,
+ int rows, int columns,
+ boolean enableScreenCapture) throws InterruptedException,
+ InvocationTargetException {
if (isEventDispatchThread()) {
- createUI(title, instructions, testTimeOut, rows, columns);
+ createUI(title, instructions, testTimeOut, rows, columns,
+ enableScreenCapture);
} else {
invokeAndWait(() -> createUI(title, instructions, testTimeOut,
- rows, columns));
+ rows, columns, enableScreenCapture));
}
}
+ private PassFailJFrame(Builder builder) throws InterruptedException,
+ InvocationTargetException {
+ this(builder.title, builder.instructions, builder.testTimeOut,
+ builder.rows, builder.columns, builder.screenCapture);
+ }
+
private static void createUI(String title, String instructions,
- long testTimeOut, int rows, int columns) {
+ long testTimeOut, int rows, int columns,
+ boolean enableScreenCapture) {
frame = new JFrame(title);
frame.setLayout(new BorderLayout());
JTextArea instructionsText = new JTextArea(instructions, rows, columns);
@@ -167,6 +228,10 @@ public class PassFailJFrame {
buttonsPanel.add(btnPass);
buttonsPanel.add(btnFail);
+ if (enableScreenCapture) {
+ buttonsPanel.add(createCapturePanel());
+ }
+
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
@@ -184,6 +249,91 @@ public class PassFailJFrame {
windowList.add(frame);
}
+ private static JComponent createCapturePanel() {
+ JComboBox<CaptureType> screenShortType = new JComboBox<>(CaptureType.values());
+
+ JButton capture = new JButton("ScreenShot");
+ capture.addActionListener((e) ->
+ captureScreen((CaptureType) screenShortType.getSelectedItem()));
+
+ JPanel panel = new JPanel();
+ panel.add(screenShortType);
+ panel.add(capture);
+ return panel;
+ }
+
+ private enum CaptureType {
+ FULL_SCREEN("Capture Full Screen"),
+ WINDOWS("Capture Individual Frame");
+
+ private final String type;
+ CaptureType(String type) {
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return type;
+ }
+ }
+
+ private static Robot createRobot() {
+ if (robot == null) {
+ try {
+ robot = new Robot();
+ } catch (AWTException e) {
+ String errorMsg = "Failed to create an instance of Robot.";
+ JOptionPane.showMessageDialog(frame, errorMsg, "Failed",
+ JOptionPane.ERROR_MESSAGE);
+ forceFail(errorMsg + e.getMessage());
+ }
+ }
+ return robot;
+ }
+
+ private static void captureScreen(Rectangle bounds) {
+ Robot robot = createRobot();
+
+ List<Image> imageList = robot.createMultiResolutionScreenCapture(bounds)
+ .getResolutionVariants();
+ Image image = imageList.get(imageList.size() - 1);
+
+ File file = new File("CaptureScreen_"
+ + imgCounter.incrementAndGet() + ".png");
+ try {
+ ImageIO.write((RenderedImage) image, "png", file);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void captureScreen(CaptureType type) {
+ switch (type) {
+ case FULL_SCREEN:
+ Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment()
+ .getScreenDevices())
+ .map(GraphicsDevice::getDefaultConfiguration)
+ .map(GraphicsConfiguration::getBounds)
+ .forEach(PassFailJFrame::captureScreen);
+ break;
+
+ case WINDOWS:
+ windowList.stream()
+ .filter(Window::isShowing)
+ .map(Window::getBounds)
+ .forEach(PassFailJFrame::captureScreen);
+ break;
+
+ default:
+ throw new IllegalStateException("Unexpected value of capture type");
+ }
+
+ JOptionPane.showMessageDialog(frame,
+ "Screen Captured Successfully",
+ "Screen Capture",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+
private static String convertMillisToTimeStr(long millis) {
if (millis < 0) {
return "00:00:00";
@@ -421,4 +571,72 @@ public class PassFailJFrame {
testFailedReason = FAILURE_REASON + reason;
latch.countDown();
}
+
+ public static class Builder {
+ private String title;
+ private String instructions;
+ private long testTimeOut;
+ private int rows;
+ private int columns;
+ private boolean screenCapture = false;
+
+ public Builder title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public Builder instructions(String instructions) {
+ this.instructions = instructions;
+ return this;
+ }
+
+ public Builder testTimeOut(long testTimeOut) {
+ this.testTimeOut = testTimeOut;
+ return this;
+ }
+
+ public Builder rows(int rows) {
+ this.rows = rows;
+ return this;
+ }
+
+ public Builder columns(int columns) {
+ this.columns = columns;
+ return this;
+ }
+
+ public Builder screenCapture() {
+ this.screenCapture = true;
+ return this;
+ }
+
+ public PassFailJFrame build() throws InterruptedException,
+ InvocationTargetException {
+ validate();
+ return new PassFailJFrame(this);
+ }
+
+ private void validate() {
+ if (this.title == null) {
+ this.title = TITLE;
+ }
+
+ if (this.instructions == null || this.instructions.length() == 0) {
+ throw new RuntimeException("Please provide the test " +
+ "instruction for this manual test");
+ }
+
+ if (this.testTimeOut == 0L) {
+ this.testTimeOut = TEST_TIMEOUT;
+ }
+
+ if (this.rows == 0) {
+ this.rows = ROWS;
+ }
+
+ if (this.columns == 0) {
+ this.columns = COLUMNS;
+ }
+ }
+ }
}