Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
106fb12
#1702: Fixed button scaling issue by updating BorderPane in main-view…
laim2003 Mar 18, 2026
78f0458
#1702: updated CHANGELOG.adoc
laim2003 Mar 18, 2026
7934b5f
#1702: updated formatting to comply with coding conventions
laim2003 Mar 18, 2026
71ab07f
#1702: reverted accidental change
laim2003 Mar 18, 2026
6890fe9
added basic UI state test for the IDE selector buttons and workspace …
laim2003 Mar 18, 2026
4423f46
Added UI tests for the comboboxes in the main windows
laim2003 Mar 24, 2026
3c77430
removed duplicate dependency
laim2003 Mar 24, 2026
d576b66
Merge branch 'main' into GUI-testing-implementation
laim2003 Mar 24, 2026
c4f120b
updated FXML version
laim2003 Mar 24, 2026
7cb6651
set glass.platform to Headless
laim2003 Mar 24, 2026
4557f30
Merge branch 'main' into GUI-testing-implementation
laim2003 Mar 25, 2026
641f8ff
updated build files and AppBaseTest.java to match headless setup
laim2003 Mar 25, 2026
5ea6169
- Refactored the MainController.java to use Dependency Injection
laim2003 Mar 25, 2026
3dcf60c
- increased timeout limit to prevent issues with GitHub CI
laim2003 Mar 25, 2026
e8e0ede
Merge branch 'main' into GUI-testing-implementation
laim2003 Mar 26, 2026
cc5e19b
- #1768: Moved headless testing implementation into a reusable separa…
laim2003 Mar 26, 2026
989052f
Merge branch 'main' into GUI-testing-implementation
laim2003 Mar 26, 2026
dab08d7
- #1768: JavaFx version aligned with java version
laim2003 Mar 26, 2026
70e1f83
Merge remote-tracking branch 'origin/GUI-testing-implementation' into…
laim2003 Mar 26, 2026
19ba65e
Merge branch 'main' into GUI-testing-implementation
hohwille Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
java-version: '21'
- name: Build project with Maven
run: mvn -B -ntp -Dstyle.color=always install
env:
TESTFX_HEADLESS: true #required for running ide-gui tests in headless mode
- name: Coveralls GitHub Action
uses: coverallsapp/github-action@v2.2.3
with:
Expand Down
7 changes: 6 additions & 1 deletion gui/src/main/java/com/devonfw/ide/gui/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import javafx.stage.Screen;
import javafx.stage.Stage;

import com.devonfw.tools.ide.variable.IdeVariables;
import com.devonfw.tools.ide.version.IdeVersion;

/**
Expand All @@ -22,7 +23,11 @@ public class App extends Application {
@Override
public void start(Stage primaryStage) throws IOException {

root = FXMLLoader.load(App.class.getResource("main-view.fxml"));
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("main-view.fxml"));
fxmlLoader.setController(
new MainController(System.getenv(IdeVariables.IDE_ROOT.getName()))
);
root = fxmlLoader.load();

Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
Scene scene = new Scene(root, bounds.getWidth() / 2, bounds.getHeight() / 2);
Expand Down
13 changes: 10 additions & 3 deletions gui/src/main/java/com/devonfw/ide/gui/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeStartContextImpl;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.log.IdeLogListenerBuffer;
import com.devonfw.tools.ide.variable.IdeVariables;

/**
* Controller of the main screen of the dashboard GUI.
*/
public class MainController {

private static Logger LOG = LoggerFactory.getLogger(MainController.class);

@FXML
private ComboBox<String> selectedProject;

Expand All @@ -44,8 +48,9 @@ public class MainController {
/**
* Constructor
*/
public MainController() {
this.directoryPath = System.getenv(IdeVariables.IDE_ROOT.getName());
public MainController(String directoryPath) {
LOG.debug("IDE_ROOT path={}", directoryPath);
this.directoryPath = directoryPath;
}

@FXML
Expand Down Expand Up @@ -81,6 +86,8 @@ private void openVsCode() {

private void setProjectsComboBox() {

assert (directoryPath != null) : "directoryPath is null! Please check the setup of your environment variables (IDE_ROOT)";

selectedProject.getItems().clear();
Path directory = Path.of(directoryPath);

Expand Down
2 changes: 1 addition & 1 deletion gui/src/main/resources/com/devonfw/ide/gui/main-view.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<?import javafx.scene.layout.VBox?>
<?import java.lang.Double?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" prefHeight="515.0" prefWidth="817.0" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.devonfw.ide.gui.MainController">
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" prefHeight="515.0" prefWidth="817.0" xmlns="http://javafx.com/javafx/21.0.10" xmlns:fx="http://javafx.com/fxml/1">
<left>
<VBox prefHeight="428.0" prefWidth="181.0" spacing="4.0" stylesheets="@style/navigation.css" BorderPane.alignment="CENTER">
<children>
Expand Down
126 changes: 104 additions & 22 deletions gui/src/test/java/com/devonfw/ide/gui/AppBaseTest.java
Original file line number Diff line number Diff line change
@@ -1,55 +1,137 @@
package com.devonfw.ide.gui;

import static org.testfx.api.FxAssert.verifyThat;
import static org.testfx.assertions.api.Assertions.assertThat;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Arrays;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.stage.Stage;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;
import org.testfx.matcher.base.NodeMatchers;
import org.junit.jupiter.api.io.TempDir;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Base Test
* Basic UI Test
*/
public class AppBaseTest extends ApplicationTest {
public class AppBaseTest extends HeadlessApplicationTest {

private static Logger LOGGER = LoggerFactory.getLogger(AppBaseTest.class);

private Button androidStudioOpen, eclipseOpen, intellijOpen, vsCodeOpen;
private ComboBox<String> selectedProject, selectedWorkspace;

@TempDir
private static Path temporayProjectDirectoryPath;

@Override
public void start(Stage stage) throws IOException {

new App().start(stage);
URL mainViewUrl = getClass().getResource("main-view.fxml");
assertThat(mainViewUrl).as("Cannot resolve main UI FXML resource!").isNotNull();

FXMLLoader fxmlLoader = new FXMLLoader(mainViewUrl);
fxmlLoader.setController(new MainController(temporayProjectDirectoryPath.toString()));
Parent root = fxmlLoader.load();
stage.setScene(new Scene(root));
stage.requestFocus(); //sometimes needed for headless setup to work
stage.show();

androidStudioOpen = (Button) root.lookup("#androidStudioOpen");
eclipseOpen = (Button) root.lookup("#eclipseOpen");
intellijOpen = (Button) root.lookup("#intellijOpen");
vsCodeOpen = (Button) root.lookup("#vsCodeOpen");
selectedProject = (ComboBox<String>) root.lookup("#selectedProject");
selectedWorkspace = (ComboBox<String>) root.lookup("#selectedWorkspace");
}

/**
* Set up headless testing
*
* Generate a temporary project directories in order to be able to test on any device (including GitHub CI). This is required for the {@link MainController}
* to work in the test context. Generates a structure like this: /project-[0..6]/workspaces/main
*/
@BeforeAll
public static void setupHeadlessMode() {
public static void generateProjectFolderStructure() {

//Enable headless testing. Should be moved as a system property "-Dheadless=true" into workflows to only affect CI
System.setProperty("headless", "true");
LOGGER.debug("tempDir: {}", temporayProjectDirectoryPath);
for (int i = 0; i <= 5; i++) {
String projectFolderName = "project-" + i;
assertThat(temporayProjectDirectoryPath.resolve(projectFolderName).toFile().mkdir())
.as("Unable to create mock project directory for mock project " + i)
.isTrue();
assertThat(temporayProjectDirectoryPath.resolve(projectFolderName).resolve("workspaces").toFile().mkdir())
.as("Unable to create mock workspaces directory for mock project " + i)
.isTrue();
assertThat(temporayProjectDirectoryPath.resolve(projectFolderName).resolve("workspaces").resolve("main").toFile().mkdir())
.as(
"Unable to create mock main workspace directory for mock project " + i)
.isTrue();
}
LOGGER.debug("project folders: {}", Arrays.toString(temporayProjectDirectoryPath.toFile().list()));
}

if (Boolean.getBoolean("headless")) {
System.setProperty("testfx.robot", "glass");
System.setProperty("glass.platform", "Monocle");
System.setProperty("testfx.headless", "true");
System.setProperty("prism.order", "sw");
System.setProperty("java.awt.headless", "true");
/**
* This test ensures that all IDE open buttons are disabled when no project is selected.
*/
@Test
public void testIdeOpenButtonsDisabledWhenNoProjectSelected() {

// assert that no project is selected
assertThat(selectedProject.getValue()).isNull();

// assert all IDE open buttons are disabled
for (Button button : new Button[] { androidStudioOpen, eclipseOpen, intellijOpen, vsCodeOpen }) {
assertThat(button.isDisabled()).as(button.getId() + " button should be disabled when no project has been selected").isTrue();
}
}

/**
* This test ensures that all IDE open buttons are enabled when a project is selected.
*/
@Test
public void testIdeOpenButtonsEnabledWhenProjectSelected() {

// assert that a project is selected
interact(() -> selectedProject.getSelectionModel().select("project-1"));

// assert all IDE open buttons are disabled
for (Button button : new Button[] { androidStudioOpen, eclipseOpen, intellijOpen, vsCodeOpen }) {
assertThat(!button.isDisabled()).as(button.getId() + " button should be enabled when a project has been selected").isTrue();
}
}

/**
* Test if welcome message is shown when GUI is started
* Tests that the workspace {@link ComboBox} is disbaled when no project is selected.
*/
@Test
@Disabled
public void ensureHelloMessageIsShownOnStartUp() {
public void testWorkspaceComboBoxDisabledWhenNoProjectSelected() {

assertThat(selectedProject.getValue()).isNull();

verifyThat("#hellomessage", NodeMatchers.isNotNull());
assertThat(selectedWorkspace.isDisabled())
.as("selectedWorkspace ComboBox should be disabled when no project is selected")
.isTrue();
}

/**
* Tests that the workspace {@link ComboBox} is enabled when a project is selected.
*/
@Test
public void testWorkspaceComboboxEnabledEnabledWhenProjectSelected() {

// assert that a project is selected
interact(() -> selectedProject.getSelectionModel().select("project-1"));

// assert all IDE open buttons are disabled
assertThat(selectedWorkspace.isDisabled())
.as("selectedWorkspace ComboBox should be enabled when a project is selected")
.isFalse();
}
}
24 changes: 24 additions & 0 deletions gui/src/test/java/com/devonfw/ide/gui/HeadlessApplicationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.devonfw.ide.gui;

import org.testfx.framework.junit5.ApplicationTest;

/**
* Headless testing is often required for contexts, in which the device running the test does not have access to a physical display (e.g. GitHub CI).
*
* @see <a href="https://aqua-cloud.io/de/ui-tests-ein-umfassender-leitfaden/">UI-Tests: Ein umfassender Leitfaden</a>
* @see <a href="https://testgrid.io/blog/ui-testing/#best-practices-for-ui-testing">Best Practices for UI Testing</a>
*/
public abstract class HeadlessApplicationTest extends ApplicationTest {

//setting up for headless testing
static {

System.setProperty("testfx.robot", "glass");
System.setProperty("testfx.headless", "true");
System.setProperty("glass.platform", "Monocle");
System.setProperty("prism.order", "sw");
System.setProperty("prism.text", "t2k");
System.setProperty("testfx.setup.timeout", "10000"); // increased timeout for testing on server-side CIs
System.setProperty("java.awt.headless", "true");
}
}
Loading