diff options
author | Andrew Lu <andrewlu@openjdk.org> | 2024-02-23 09:06:07 +0000 |
---|---|---|
committer | Vitaly Provodin <vitaly.provodin@jetbrains.com> | 2024-04-19 15:36:01 +0700 |
commit | de90d67c850ae806335e9f87d71aadd231ca5ae8 (patch) | |
tree | 0a68f3ff9ec88e976346d4f6f9318fb4628b9bfc | |
parent | e7fa0e07bd57b9d1207c40d03d2e0c20245a80b2 (diff) | |
download | JetBrainsRuntime-de90d67c850ae806335e9f87d71aadd231ca5ae8.tar.gz |
8294535: Add screen capture functionality to PassFailJFrame
Backport-of: dbb788f34dbbe0aa5c8356fb4a5dc19b96787d25
-rw-r--r-- | test/jdk/java/awt/regtesthelpers/PassFailJFrame.java | 228 |
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; + } + } + } } |