diff --git a/.github/workflows/runUiRdTests.yml b/.github/workflows/runUiRdTests.yml new file mode 100644 index 000000000..bae314c9a --- /dev/null +++ b/.github/workflows/runUiRdTests.yml @@ -0,0 +1,49 @@ +name: Run UI Rider Tests +on: + workflow_dispatch: + schedule: + - cron: '0 12 * * *' +jobs: + build-for-ui-test-mac-os: + if: github.repository == 'JetBrains/ideavim' + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + - name: Setup FFmpeg + run: brew install ffmpeg +# - name: Setup Gradle +# uses: gradle/gradle-build-action@v2.4.2 + - name: Build Plugin + run: gradle :buildPlugin + - name: Run Idea + run: | + mkdir -p build/reports + gradle --no-configuration-cache :runIdeForUiTests -PideaType=RD > build/reports/idea.log & + - name: Wait for Idea started + uses: jtalk/url-health-check-action@v3 + with: + url: http://127.0.0.1:8082 + max-attempts: 20 + retry-delay: 10s + - name: Tests + run: gradle :tests:ui-rd-tests:testUi + - name: Move video + if: always() + run: mv tests/ui-rd-tests/video build/reports + - name: Move sandbox logs + if: always() + run: mv build/idea-sandbox/RD-*/log_runIdeForUiTests idea-sandbox-log + - name: Save report + if: always() + uses: actions/upload-artifact@v4 + with: + name: ui-test-fails-report-mac + path: | + build/reports + tests/ui-rd-tests/build/reports + idea-sandbox-log diff --git a/.idea/gradle.xml b/.idea/gradle.xml index d775e4b3b..0ba89bdd9 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -17,6 +17,7 @@ <option value="$PROJECT_DIR$/tests/ui-fixtures" /> <option value="$PROJECT_DIR$/tests/ui-ij-tests" /> <option value="$PROJECT_DIR$/tests/ui-py-tests" /> + <option value="$PROJECT_DIR$/tests/ui-rd-tests" /> <option value="$PROJECT_DIR$/vim-engine" /> </set> </option> diff --git a/settings.gradle.kts b/settings.gradle.kts index 4c6176463..acce38486 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,3 +19,4 @@ include("tests:long-running-tests") include("tests:ui-ij-tests") include("tests:ui-py-tests") include("tests:ui-fixtures") +include("tests:ui-rd-tests") diff --git a/tests/ui-rd-tests/build.gradle.kts b/tests/ui-rd-tests/build.gradle.kts new file mode 100644 index 000000000..a40343159 --- /dev/null +++ b/tests/ui-rd-tests/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + java + kotlin("jvm") +} + +repositories { + mavenCentral() + maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") } +} + +val kotlinVersion: String by project +val ideaVersion: String by project +val javaVersion: String by project +val remoteRobotVersion: String by project + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter") + compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") + testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") + testImplementation(testFixtures(project(":"))) // The root project + testImplementation(testFixtures(project(":tests:ui-fixtures"))) + + testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion") + testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion") + testImplementation("com.intellij.remoterobot:ide-launcher:$remoteRobotVersion") + testImplementation("com.automation-remarks:video-recorder-junit5:2.0") +} + +tasks { + // This task is disabled because it should be excluded from `gradle test` run (because it's slow) + // I didn't find a better way to exclude except disabling and defining a new task with a different name + test { + useJUnitPlatform() + enabled = false + } + + register<Test>("testUi") { + group = "verification" + useJUnitPlatform() + + // This is needed for the robot to access the message of the exception + // Usually these opens are provided by the intellij gradle plugin + // https://github.com/JetBrains/gradle-intellij-plugin/blob/b21e3f382e9885948a6427001d5e64234c602613/src/main/kotlin/org/jetbrains/intellij/utils/OpenedPackages.kt#L26 + jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") + } +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(javaVersion)) + } +} + +kotlin { + jvmToolchain { + languageVersion.set(JavaLanguageVersion.of(javaVersion)) + } +} diff --git a/tests/ui-rd-tests/src/test/kotlin/RiderUiTest.kt b/tests/ui-rd-tests/src/test/kotlin/RiderUiTest.kt new file mode 100644 index 000000000..204f08bf6 --- /dev/null +++ b/tests/ui-rd-tests/src/test/kotlin/RiderUiTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2003-2025 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +import com.automation.remarks.junit5.Video +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.steps.CommonSteps +import com.intellij.remoterobot.stepsProcessing.step +import com.intellij.remoterobot.utils.keyboard +import com.intellij.remoterobot.utils.waitFor +import org.assertj.swing.core.MouseButton +import org.junit.jupiter.api.Test +import ui.pages.Editor +import ui.pages.IdeaFrame +import ui.pages.actionMenu +import ui.pages.actionMenuItem +import ui.pages.editor +import ui.pages.idea +import ui.pages.welcomeFrame +import ui.utils.StepsLogger +import ui.utils.uiTest +import java.time.Duration +import kotlin.test.assertEquals + +class RiderUiTest { + init { + StepsLogger.init() + } + + private lateinit var commonSteps: CommonSteps + + @Test + @Video + fun run() = uiTest("ideaVimTest") { + commonSteps = CommonSteps(this) + + startNewProject() + Thread.sleep(1000) + + idea { + waitSmartMode() + + createFile("1.txt", this@uiTest) + val editor = editor("1.txt") { + step("Write a text") { + injectText( + """ + |One Two + |Three Four + """.trimMargin() + ) + } + } + waitFor(Duration.ofMinutes(1)) { editor.findAllText("One").isNotEmpty() } + + testEnterWorksInNormalMode(editor) + } + } + + private fun IdeaFrame.testEnterWorksInNormalMode(editor: Editor) { + editor.findText("Two").click() + keyboard { + enter() + } + + assertEquals( + """ + |One Two + |Three Four + """.trimMargin(), editor.text + ) + + assertEquals(8, editor.caretOffset) + } + + private fun RemoteRobot.startNewProject() { + welcomeFrame { + createNewProjectLink.click() + button("Create").click() + } + } +} + +private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) { + step("Create $fileName file") { + with(projectViewTree) { + setExpandTimeout(30_000) + expand(projectName, "src") + findText("src").click(MouseButton.RIGHT_BUTTON) + } + remoteRobot.actionMenu("New").click() + remoteRobot.actionMenuItem("File").click() + keyboard { enterText(fileName); enter() } + } +} +