diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d89fa9b..a533100 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -109,7 +109,7 @@ jobs:
# Run tests
- name: Run Tests
- run: ./gradlew check
+ run: ./gradlew check -x uiTest
# Collect Tests Result of failed tests
- name: Collect Tests Result
diff --git a/.github/workflows/run-ui-tests.yml b/.github/workflows/run-ui-tests.yml
index 4793385..96bbc9b 100644
--- a/.github/workflows/run-ui-tests.yml
+++ b/.github/workflows/run-ui-tests.yml
@@ -1,6 +1,6 @@
-# GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps:
-# - Prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with the UI.
-# - Wait for IDE to start.
+# GitHub Actions Workflow for launching UI tests on Linux and Mac in the following steps:
+# - Prepare and launch IDE with your plugin and robot-server plugin, which is necessary to interact with the UI.
+# - Wait for IDE to start
# - Run UI tests with a separate Gradle task.
#
# Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform.
@@ -12,30 +12,29 @@ on:
workflow_dispatch
jobs:
-
testUI:
- runs-on: ${{ matrix.os }}
+ name: Run UI Test on ${{ matrix.osName }}
+ runs-on: ${{ matrix.runner }}
+ permissions:
+ contents: write
+ checks: write
+ pull-requests: write
strategy:
fail-fast: false
matrix:
include:
- - os: ubuntu-latest
- runIde: |
- export DISPLAY=:99.0
- Xvfb -ac :99 -screen 0 1920x1080x16 &
- gradle runIdeForUiTests &
- - os: windows-latest
- runIde: start gradlew.bat runIdeForUiTests
- - os: macos-latest
- runIde: ./gradlew runIdeForUiTests &
+ - runner: ubuntu-latest
+ osName: Linux
+ - runner: macos-latest
+ osName: macOS
+# - runner: windows-latest
+# osName: Windows
steps:
-
- # Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v6
- # Set up Java environment for the next steps
+ # Set up the Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v5
with:
@@ -48,18 +47,94 @@ jobs:
with:
cache-read-only: true
- # Run IDEA prepared for UI testing
- - name: Run IDE
- run: ${{ matrix.runIde }}
+ # Set up Xvfb for Linux headless mode
+ - name: Setup Xvfb for Linux
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt-get update -y
+ sudo apt-get install -y xvfb x11-utils libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0
+ export DISPLAY=:99.0
+ Xvfb -ac :99 -screen 0 1920x1080x16 &
+ sleep 3
+ echo "DISPLAY=:99.0" >> $GITHUB_ENV
+
+ # Run UI test with IDE Starter framework
+ - name: Run UI Test
+ env:
+ UI_BRANCH_NAME: "${{ matrix.osName }}-${{ github.ref_name }}"
+ DISPLAY: ${{ runner.os == 'Linux' && ':99.0' || '' }}
+ run: |
+ echo "UI on ${{ matrix.osName }} run #: ${{ github.run_number }} and id: ${{ github.run_id }}"
+ ./gradlew uiTest
+ timeout-minutes: 30
+
+ # List build directory for debugging
+ - name: List build directory structure
+ if: always()
+ shell: bash
+ run: |
+ echo "=== Build directory structure ==="
+ if [ -d "build" ]; then
+ find build -type d -name "log" -o -name "screenshots" -o -name "ui-hierarchy" 2>/dev/null | head -20 || true
+ echo "=== Looking for IDE test output ==="
+ find build -type d -path "*/ide-tests/*" 2>/dev/null | head -20 || true
+ else
+ echo "Build directory not found"
+ fi
- # Wait for IDEA to be started
- - name: Health Check
- uses: jtalk/url-health-check-action@v4
+ - name: Collect Test Results
+ if: always()
+ uses: actions/upload-artifact@v6
with:
- url: http://127.0.0.1:8082
- max-attempts: 15
- retry-delay: 30s
+ name: tests-result-${{ matrix.osName }}
+ path: |
+ build/reports/
+ build/test-results/
+ build/out/
+ build/idea-sandbox/
+ build/**/*.log
+ build/**/screenshots/
+ build/**/ui-hierarchy/
+ if-no-files-found: warn
+ continue-on-error: true
- # Run tests
- - name: Tests
- run: ./gradlew test
+ - name: Collect Test Report XML
+ if: always()
+ uses: actions/upload-artifact@v6
+ with:
+ name: test-report-${{ matrix.osName }}
+ path: ${{ github.workspace }}/build/test-results/uiTest/*.xml
+
+ # Test report job using dorny/test-reporter
+ test-report:
+ name: UI Test Report
+ needs: testUI
+ runs-on: ubuntu-latest
+ if: always()
+ permissions:
+ contents: write
+ checks: write
+ pull-requests: write
+ actions: read
+
+ steps:
+ - name: Fetch Sources
+ uses: actions/checkout@v6
+ with:
+ token: '${{ secrets.GITHUB_TOKEN }}'
+
+ - name: Download all test reports
+ uses: actions/download-artifact@v7
+ with:
+ pattern: test-report-*
+ path: test-reports
+ merge-multiple: true
+
+ - name: Generate Test Report
+ uses: dorny/test-reporter@v2.5.0
+ if: success() || failure()
+ with:
+ name: UI Test Report Dashboard
+ reporter: java-junit
+ fail-on-error: 'true'
+ path: 'test-reports/*.xml'
diff --git a/.gitignore b/.gitignore
index bc5e801..60a9844 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,10 @@
.idea
.intellijPlatform
.qodana
+allure-results
build
idea-flex.skeleton
java_pid*.hprof
jflex-*.jar
+out
src/main/gen
diff --git a/.run/Run Tests.run.xml b/.run/Run Tests.run.xml
index f281bdc..58674a1 100644
--- a/.run/Run Tests.run.xml
+++ b/.run/Run Tests.run.xml
@@ -5,7 +5,7 @@
-
+
@@ -18,8 +18,11 @@
true
true
+ false
false
true
+ false
+ false
-
+
\ No newline at end of file
diff --git a/.run/Run UI Tests.run.xml b/.run/Run UI Tests.run.xml
new file mode 100644
index 0000000..91151fc
--- /dev/null
+++ b/.run/Run UI Tests.run.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+ false
+ false
+ false
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c335868..f3f0751 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,9 +4,16 @@
## [Unreleased]
+### Added
+
+- Simple UI test to verify the plugin is installed and a Gradle task to run it
+- Codecov YAML file that was missed from the template
+
### Fixed
- Broken links in CHANGELOG.md caused by missing "v" in tag creation build by adding `versionPrefix` default
+- Run UI tests GitHub Action workflow to use the added UI test
+- Exclude the UI test from build GitHub Action workflow
### Changed
diff --git a/build.gradle.kts b/build.gradle.kts
index c5dfd20..081f7a6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,3 +1,4 @@
+import org.gradle.kotlin.dsl.register
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
@@ -10,6 +11,7 @@ plugins {
alias(libs.plugins.qodana) // Gradle Qodana Plugin
alias(libs.plugins.kover) // Gradle Kover Plugin
alias(libs.plugins.grammarkit) // Gradle Grammar-Kit Plugin
+ idea // IntelliJ IDEA support
}
group = providers.gradleProperty("pluginGroup").get()
@@ -22,16 +24,41 @@ sourceSets {
srcDirs("src/main/gen")
}
}
+ create("uiTest",Action{
+ compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output
+ runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output
+ })
+}
+
+idea {
+ module {
+ generatedSourceDirs.add(file("src/main/gen"))
+ testSources.from(sourceSets.getByName("uiTest").kotlin.srcDirs)
+ testResources.from(sourceSets.getByName("uiTest").resources.srcDirs)
+ }
+}
+
+val uiTestImplementation: Configuration by configurations.getting {
+ extendsFrom(configurations.testImplementation.get())
+}
+
+val uiTestRuntimeOnly: Configuration by configurations.getting {
+ extendsFrom(configurations.testRuntimeOnly.get())
}
// Set the JVM language level used to build the project.
kotlin {
- jvmToolchain(21)
+ jvmToolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ @Suppress("UnstableApiUsage")
+ vendor = JvmVendorSpec.JETBRAINS
+ }
}
// Configure project's dependencies
repositories {
mavenCentral()
+ maven("https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-ide-starter")
// IntelliJ Platform Gradle Plugin Repositories Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-repositories-extension.html
intellijPlatform {
@@ -59,7 +86,16 @@ dependencies {
testFramework(TestFrameworkType.Platform)
testBundledPlugin("org.jetbrains.kotlin")
+
+ // IntelliJ UI Tests framework - read more: https://github.com/JetBrains/intellij-ide-starter
+ testFramework(TestFrameworkType.Starter, configurationName = "uiTestImplementation")
+ testFramework(TestFrameworkType.JUnit5, configurationName = "uiTestImplementation")
}
+
+ uiTestImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
+ uiTestImplementation("org.kodein.di:kodein-di-jvm:7.20.2")
+ uiTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1")
+ uiTestRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
@@ -169,28 +205,40 @@ tasks {
gradleVersion = providers.gradleProperty("gradleVersion").get()
}
+ runIde {
+ systemProperties(
+ "ide.native.launcher" to true,
+ "ide.show.tips.on.startup.default.value" to false,
+ "jb.consents.confirmation.enabled" to false
+ )
+ }
+
publishPlugin {
dependsOn(patchChangelog)
}
-}
-intellijPlatformTesting {
-// runIde {
-// register("runIdeForUiTests") {
-// task {
-// jvmArgumentProviders += CommandLineArgumentProvider {
-// listOf(
-// "-Drobot-server.port=8082",
-// "-Dide.mac.message.dialogs.as.sheets=false",
-// "-Djb.privacy.policy.text=",
-// "-Djb.consents.confirmation.enabled=false",
-// )
-// }
-// }
-//
-// plugins {
-// robotServerPlugin()
-// }
-// }
-// }
+ register("uiTest") {
+ description = "Runs only the UI tests that start the IDE"
+ group = "verification"
+ val uiTestSourceSet = sourceSets["uiTest"]
+ testClassesDirs = uiTestSourceSet.output.classesDirs
+ classpath = uiTestSourceSet.runtimeClasspath
+ systemProperty("path.to.build.plugin", buildPlugin.get().archiveFile.get().asFile.absolutePath)
+ systemProperty("idea.home.path", prepareTestSandbox.get().getDestinationDir().parentFile.absolutePath)
+ systemProperty("platform.version", providers.gradleProperty("platformVersion").get())
+ systemProperty(
+ "allure.results.directory", project.layout.buildDirectory.get().asFile.absolutePath + "/allure-results"
+ )
+ useJUnitPlatform()
+
+ // Add required JVM arguments
+ jvmArgumentProviders += CommandLineArgumentProvider {
+ mutableListOf(
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ "--add-opens=java.desktop/javax.swing=ALL-UNNAMED"
+ )
+ }
+
+ dependsOn(buildPlugin)
+ }
}
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..c40fcc2
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,15 @@
+coverage:
+ status:
+ project:
+ default:
+ informational: true
+ threshold: 0%
+ base: auto
+ patch:
+ default:
+ informational: true
+
+
+ignore:
+ - src/test/
+ - src/uiTest/
diff --git a/src/uiTest/kotlin/com/github/jodiew/sortlines/ui/PluginTest.kt b/src/uiTest/kotlin/com/github/jodiew/sortlines/ui/PluginTest.kt
new file mode 100644
index 0000000..1d2b25e
--- /dev/null
+++ b/src/uiTest/kotlin/com/github/jodiew/sortlines/ui/PluginTest.kt
@@ -0,0 +1,83 @@
+package com.github.jodiew.sortlines.ui
+
+import com.intellij.driver.sdk.ui.components.common.welcomeScreen
+import com.intellij.driver.sdk.ui.shouldBe
+import com.intellij.ide.starter.driver.engine.runIdeWithDriver
+import com.intellij.ide.starter.ide.IdeProductProvider
+import com.intellij.ide.starter.models.TestCase
+import com.intellij.ide.starter.plugins.PluginConfigurator
+import com.intellij.ide.starter.project.NoProject
+import com.intellij.ide.starter.runner.Starter
+import com.intellij.openapi.util.SystemInfo
+import org.junit.jupiter.api.Test
+import kotlin.io.path.Path
+
+class PluginTest {
+
+ /**
+ * Test to verify that the plugin is installed in the IDE.
+ */
+ @Test
+ fun simpleTest() {
+ Starter.newContext("testInstalled", TestCase(IdeProductProvider.IU, NoProject).withVersion(System.getProperty("platform.version"))).apply {
+ val pathToPlugin = System.getProperty("path.to.build.plugin")
+ PluginConfigurator(this).installPluginFromPath(Path(pathToPlugin))
+ }.applyVMOptionsPatch{
+ // Required JVM arguments for module access
+ addSystemProperty("--add-opens", "java.base/java.lang=ALL-UNNAMED")
+ addSystemProperty("--add-opens", "java.desktop/javax.swing=ALL-UNNAMED")
+
+ // Core IDE configuration
+ addSystemProperty("idea.trust.all.projects", true) // Trust all projects automatically
+ addSystemProperty("jb.consents.confirmation.enabled", false) // Disable consent dialogs
+ addSystemProperty("jb.privacy.policy.text", "") // Skip privacy policy
+ addSystemProperty("ide.show.tips.on.startup.default.value", false) // No tips on startup
+
+ // Test framework configuration
+ addSystemProperty("junit.jupiter.extensions.autodetection.enabled", true)
+ addSystemProperty("shared.indexes.download.auto.consent", true)
+
+ // UI testing specific
+ addSystemProperty("expose.ui.hierarchy.url", true) // Enable UI hierarchy inspection
+
+ when {
+ SystemInfo.isMac -> {
+ // macOS specific settings
+ addSystemProperty("ide.mac.file.chooser.native", false) // Use Java file chooser
+ addSystemProperty("ide.mac.message.dialogs.as.sheets", false) // Use regular dialogs
+ addSystemProperty("jbScreenMenuBar.enabled", false) // Disable native menu bar
+ addSystemProperty("ide.native.launcher", true) // Use native launcher
+ }
+
+ SystemInfo.isWindows -> {
+ // Windows specific settings
+
+ }
+
+ SystemInfo.isLinux -> {
+ // Linux specific settings
+ addSystemProperty("ide.browser.jcef.enabled", true)
+ addSystemProperty("ide.native.launcher", false) // Avoid launcher issues on Linux
+
+ // X11/Wayland compatibility
+ addSystemProperty("sun.java2d.uiScale.enabled", false)
+ addSystemProperty("sun.java2d.xrender", false)
+ }
+ }
+ }.runIdeWithDriver().useDriverAndCloseIde {
+ welcomeScreen {
+ clickPlugins()
+ x { byAccessibleName("Installed") }.click()
+ keyboard {
+ typeText("Sort Lines", 50)
+ }
+ shouldBe("Plugin is installed") {
+ x {
+ byVisibleText("Sort Lines with Comment")
+ }.present()
+ }
+
+ }
+ }
+ }
+}
\ No newline at end of file