Skip to content

Commit

Permalink
#5: Basic UI test (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
redcatbear authored Sep 15, 2022
1 parent 7ff2487 commit a0df2e1
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
57 changes: 46 additions & 11 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
val remoteRobotVersion = "0.11.16"

plugins {
id("java")
id("jacoco")
Expand All @@ -10,17 +12,28 @@ version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
maven { url = uri("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") }
}

// Configure Gradle IntelliJ Plugin
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
version.set("2021.3.3")
type.set("IC") // Target IDE Platform

plugins.set(listOf(/* Plugin Dependencies */))
}

dependencies {
testImplementation("com.intellij.remoterobot:remote-robot:" + remoteRobotVersion)
testImplementation("com.intellij.remoterobot:remote-fixtures:" + remoteRobotVersion)
testImplementation("com.squareup.okhttp3:logging-interceptor:4.10.0")

val junitVersion = "5.9.0"
testImplementation("org.junit.jupiter:junit-jupiter-api:" + junitVersion)
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:" + junitVersion)
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.9.0")
}

tasks {
// Set the JVM compatibility versions
withType<JavaCompile> {
Expand All @@ -42,19 +55,41 @@ tasks {
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
}
}

tasks.test {
finalizedBy(tasks.jacocoTestReport)
}
tasks.jacocoTestReport {
dependsOn(tasks.test)
reports {
xml.required.set(true)
downloadRobotServerPlugin {
version.set(remoteRobotVersion)
}

test {
systemProperty("robot-server.port", "8082")
useJUnitPlatform()
finalizedBy(jacocoTestReport)
}

runIdeForUiTests {
// In case your Idea is launched on remote machine you can enable public port and enable encryption of JS calls
// systemProperty "robot-server.host.public", "true"
// systemProperty "robot.encryption.enabled", "true"
// systemProperty "robot.encryption.password", "my super secret"
systemProperty("robot-server.port", "8082")
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
systemProperty("jb.consents.confirmation.enabled", "false")
systemProperty("ide.mac.file.chooser.native", "false")
systemProperty("jbScreenMenuBar.enabled", "false")
systemProperty("apple.laf.useScreenMenuBar", "false")
systemProperty("idea.trust.all.projects", "true")
systemProperty("ide.show.tips.on.startup.default.value", "false")
}

jacocoTestReport {
dependsOn(test)
reports {
xml.required.set(true)
}
}
}

tasks.sonarqube {
dependsOn(tasks.jacocoTestReport)
dependsOn(tasks.jacocoTestReport)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.itallcode.openfasttrace.intelijplugin.remoterobot;

public final class RemoteRobotProperties {
private RemoteRobotProperties () {
// prevent class instantiation.
}

final public static int ROBOT_PORT = Integer.parseInt(System.getProperty("robot-server.port"));
final public static String ROBOT_HOST = System.getProperty("robot-server.host.public", "localhost");
final public static String ROBOT_BASE_URL = "http://" + ROBOT_HOST + ":" + ROBOT_PORT;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.itallcode.openfasttrace.intelijplugin.uitest;

import com.intellij.remoterobot.RemoteRobot;
import com.intellij.remoterobot.fixtures.ComponentFixture;

import static com.intellij.remoterobot.search.locators.Locators.byXpath;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assumptions.assumeFalse;

import com.intellij.remoterobot.fixtures.JMenuBarFixture;
import org.itallcode.openfasttrace.intelijplugin.uitest.pages.IdeFrameFixture;
import org.itallcode.openfasttrace.intelijplugin.uitest.pages.NewProjectDialogFixture;
import org.itallcode.openfasttrace.intelijplugin.uitest.pages.WelcomeFrameFixture;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;
import java.time.Duration;

import static org.itallcode.openfasttrace.intelijplugin.remoterobot.RemoteRobotProperties.*;

class PluginUiTest {
final static Duration WITH_PATIENCE = Duration.ofSeconds(10);
@TempDir
static Path projectTempDir;

@Test
void testOftMenuEntryExists() {
assumeNotRunningInCiBUild();
final RemoteRobot robot = new RemoteRobot(ROBOT_BASE_URL);
final WelcomeFrameFixture welcomeFrame = robot.find(WelcomeFrameFixture.class, WITH_PATIENCE);
welcomeFrame.createNewProjectLink().click();
final NewProjectDialogFixture project = robot.find(NewProjectDialogFixture.class, WITH_PATIENCE);
project.projectTypes().clickItem("Empty Project", true);
project.projectLocation().setText(projectTempDir.toAbsolutePath().toString());
project.finish().click();
final IdeFrameFixture ide = robot.find(IdeFrameFixture.class, WITH_PATIENCE);
waitUntilMenuBarIsReady();
final JMenuBarFixture menuBar = ide.menuBar();
menuBar.select("Help");
assertDoesNotThrow(() -> robot.find(ComponentFixture.class,
byXpath("//div[@class='ActionMenu']//div[@text='OpenFastTrace User Guide']")));
}

private static void assumeNotRunningInCiBUild() {
assumeFalse(Boolean.parseBoolean(System.getenv("CI")));
}

@SuppressWarnings("java:S2925")
private void waitUntilMenuBarIsReady() {
try {
Thread.sleep(5000);
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new RuntimeException(exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.itallcode.openfasttrace.intelijplugin.uitest.pages;

import com.intellij.remoterobot.RemoteRobot;
import com.intellij.remoterobot.data.RemoteComponent;
import com.intellij.remoterobot.fixtures.*;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;

import static com.intellij.remoterobot.search.locators.Locators.byXpath;

/**
* The IDE Frame is the main container window (or page if you prefer that) for the IntelliJ IDEA.
*/
@DefaultXpath(by = "IdeFrameImpl type", xpath = "//div[@class='IdeFrameImpl']")
@FixtureName(name = "IDE Frame")
public class IdeFrameFixture extends CommonContainerFixture {
public IdeFrameFixture(@NotNull RemoteRobot remoteRobot, @NotNull RemoteComponent remoteComponent) {
super(remoteRobot, remoteComponent);
}

public JMenuBarFixture menuBar() {
return find(JMenuBarFixture.class, byXpath("//div[@class='LinuxIdeMenuBar']"), Duration.ofSeconds(20));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.itallcode.openfasttrace.intelijplugin.uitest.pages;

import com.intellij.remoterobot.RemoteRobot;
import com.intellij.remoterobot.data.RemoteComponent;
import com.intellij.remoterobot.fixtures.*;
import org.jetbrains.annotations.NotNull;

import static com.intellij.remoterobot.search.locators.Locators.byXpath;

@DefaultXpath(by = "NewProjectDialog type", xpath = "//*[contains(@title.key, 'title.new.project')]")
@FixtureName(name = "New Project Dialog")
public class NewProjectDialogFixture extends CommonContainerFixture {
public NewProjectDialogFixture(@NotNull RemoteRobot remoteRobot, @NotNull RemoteComponent remoteComponent) {
super(remoteRobot, remoteComponent);
}

public JListFixture projectTypes() {
return find(JListFixture.class, byXpath("//div[@class='JBList']"));
}

public JTextFieldFixture projectLocation() {
return find(JTextFieldFixture.class, byXpath("//div[@class='FieldPanel']/div[1]"));
}

public JButtonFixture finish() {
return find(JButtonFixture.class, byXpath("//div[@text.key='button.finish']"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.itallcode.openfasttrace.intelijplugin.uitest.pages;

import com.intellij.remoterobot.RemoteRobot;
import com.intellij.remoterobot.data.RemoteComponent;
import com.intellij.remoterobot.fixtures.*;
import org.jetbrains.annotations.NotNull;

import static com.intellij.remoterobot.search.locators.Locators.byXpath;

/**
* The Welcome Frame is the container window (or page if you prefer that) for everything that happens before IntelliJ
* opens the actual IDE window.
* <p>
* Displaying a welcome message is the smaller part of that job. The main reason is that the IDE needs a project context
* to work with and that context does not exist with a fresh installation. That is also why the welcome screen contains
* the means of opening, importing or creating projects in a sub dialog.
* </p>
*/
@DefaultXpath(by = "FlatWelcomeFrame type", xpath = "//div[@class='FlatWelcomeFrame']")
@FixtureName(name = "Welcome Frame")
public class WelcomeFrameFixture extends CommonContainerFixture {
public WelcomeFrameFixture(@NotNull RemoteRobot remoteRobot, @NotNull RemoteComponent remoteComponent) {
super(remoteRobot, remoteComponent);
}

public JButtonFixture createNewProjectLink() {
// The button style changes from an icon to a text button if a project already exists. To make things worse,
// In case of the icon the button is followed by a label that has the same text on it as the link-style button.
// We need a bit of XPath complexity, to pick either of the clickable items.
return find(JButtonFixture.class, byXpath("(//div[@defaulticon='createNewProjectTab.svg']" +
"|//div[@visible_text='New Project'])[1]"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.itallcode.openfasttrace.intelijplugin.wait;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.TimeoutException;

import static org.itallcode.openfasttrace.intelijplugin.remoterobot.RemoteRobotProperties.*;

/**
* This strategy waits for the Remote Robot server to become available.
*/
public class RobotServerReadyWaitStrategy {
private static final long RETRY_DELAY_MILLIS = 1000;
private final Duration timeout;

/**
* Wait for the Remote Robot server to become available.
*
* @param timeout maximum time to wait for the server to become available
* @throws TimeoutException if the given timeout is reached without being able to connect to the robot server
*/
public static void wait(final Duration timeout) throws TimeoutException {
final RobotServerReadyWaitStrategy strategy = new RobotServerReadyWaitStrategy(timeout);
strategy.waitUntilReady();
}

private RobotServerReadyWaitStrategy(final Duration timeout) {
this.timeout = timeout;
}

private void waitUntilReady() throws TimeoutException {
final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(timeout)
.retryOnConnectionFailure(true)
.build();
poll(client, ROBOT_BASE_URL);
client.dispatcher().executorService().shutdown();
}

private void poll(final OkHttpClient client, final String url) throws TimeoutException {
final Request request = new Request.Builder().url(url).build();
final Instant until = Instant.now().plus(this.timeout);
do {
try (final Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
return;
}
} catch (IOException exception) {
// keep trying.
try {
delayPollingRetry();
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw new RuntimeException(interruptedException);
}
}
} while (Instant.now().isBefore(until));
throw new TimeoutException("Timed out waiting for Remote Robot server to become available");
}

@SuppressWarnings("java:S2925")
private static void delayPollingRetry() throws InterruptedException {
Thread.sleep(RETRY_DELAY_MILLIS);
}
}

0 comments on commit a0df2e1

Please sign in to comment.