mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-02 19:34:04 +02:00
Set up PyCharm UI test
This commit is contained in:
parent
d87965775a
commit
519d5eed06
55
.github/workflows/runUiPyTests.yml
vendored
Normal file
55
.github/workflows/runUiPyTests.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Run UI PyCharm 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: 17
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
# Not strictly necessary, but it may prevent rate limit
|
||||
# errors especially on GitHub-hosted macos machines.
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- 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 :tests:py-ui-tests:runIdeForUiTests > 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:py-ui-tests:testUi
|
||||
- name: Move video
|
||||
if: always()
|
||||
run: mv tests/py-ui-tests/video build/reports
|
||||
- name: Move sandbox logs
|
||||
if: always()
|
||||
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||
- name: Save report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ui-test-fails-report-mac
|
||||
path: |
|
||||
build/reports
|
||||
sandbox-idea-log
|
@ -16,3 +16,4 @@ include 'tests:java-tests'
|
||||
include 'tests:property-tests'
|
||||
include 'tests:long-running-tests'
|
||||
include 'tests:ij-ui-tests'
|
||||
include 'tests:py-ui-tests'
|
||||
|
@ -4,9 +4,6 @@ plugins {
|
||||
id("org.jetbrains.intellij")
|
||||
}
|
||||
|
||||
group = "org.example"
|
||||
version = "SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri("https://cache-redirector.jetbrains.com/intellij-dependencies") }
|
||||
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui
|
||||
|
||||
import com.automation.remarks.junit5.Video
|
||||
import com.intellij.remoterobot.steps.CommonSteps
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
class PyCharmTest {
|
||||
init {
|
||||
// StepsLogger.init()
|
||||
}
|
||||
|
||||
private lateinit var commonSteps: CommonSteps
|
||||
|
||||
companion object {
|
||||
private var ideaProcess: Process? = null
|
||||
private var tmpDir: Path = Files.createTempDirectory("launcher")
|
||||
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun beforeAll() {
|
||||
// val client = OkHttpClient()
|
||||
// val ideDownloader = IdeDownloader(client)
|
||||
// val downloadedIdePath = ideDownloader.downloadAndExtractLatestEap(Ide.PYCHARM, tmpDir)
|
||||
// // This hack doesn't work because it breaks .app. Waiting for the new version of robot with the fix.
|
||||
//// if (Os.hostOS() == Os.MAC) {
|
||||
//// // Hack for the problem of double vmoptions file
|
||||
//// // The ides now have two vmoptions files: one for ide itself and one for jetbrains client
|
||||
//// // Because of some reason, when ide detects more than one vmoptions file, it tries to find one that ends on
|
||||
//// // "64". And since it doesn't find one, the robot fails.
|
||||
//// val dataPath = downloadedIdePath.resolve("Contents/bin")
|
||||
//// Files.list(dataPath).filter {
|
||||
//// it.fileName.toString().endsWith("jetbrains_client.vmoptions")
|
||||
//// }.forEach { Files.delete(it) }
|
||||
//// }
|
||||
// ideaProcess = IdeLauncher.launchIde(
|
||||
// downloadedIdePath,
|
||||
// mapOf("robot-server.port" to 8083),
|
||||
// emptyList(),
|
||||
// listOf(ideDownloader.downloadRobotPlugin(tmpDir)),
|
||||
// tmpDir
|
||||
// )
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@JvmStatic
|
||||
fun cleanUp() {
|
||||
// ideaProcess?.destroy()
|
||||
// tmpDir.toFile().deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Video
|
||||
@Disabled("Waiting for the new version of the robot with fixes")
|
||||
fun run() {
|
||||
println("Hey")
|
||||
}
|
||||
}
|
70
tests/py-ui-tests/build.gradle.kts
Normal file
70
tests/py-ui-tests/build.gradle.kts
Normal file
@ -0,0 +1,70 @@
|
||||
plugins {
|
||||
java
|
||||
kotlin("jvm")
|
||||
id("org.jetbrains.intellij")
|
||||
}
|
||||
|
||||
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("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()
|
||||
}
|
||||
|
||||
downloadRobotServerPlugin {
|
||||
version.set(remoteRobotVersion)
|
||||
}
|
||||
|
||||
runIdeForUiTests {
|
||||
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.show.tips.on.startup.default.value", "false")
|
||||
}
|
||||
}
|
||||
|
||||
intellij {
|
||||
version.set(ideaVersion)
|
||||
type.set("PY")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(javaVersion))
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(javaVersion))
|
||||
}
|
||||
}
|
62
tests/py-ui-tests/src/test/kotlin/PyCharmTest.kt
Normal file
62
tests/py-ui-tests/src/test/kotlin/PyCharmTest.kt
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.utils.keyboard
|
||||
import com.intellij.remoterobot.utils.waitFor
|
||||
import org.junit.jupiter.api.Test
|
||||
import ui.pages.IdeaFrame
|
||||
import ui.pages.idea
|
||||
import ui.pages.welcomeFrame
|
||||
|
||||
class PyCharmTest {
|
||||
init {
|
||||
StepsLogger.init()
|
||||
}
|
||||
|
||||
private lateinit var commonSteps: CommonSteps
|
||||
|
||||
@Test
|
||||
@Video
|
||||
fun run() = uiTest("ideaVimTest") {
|
||||
commonSteps = CommonSteps(this)
|
||||
|
||||
startNewProject()
|
||||
Thread.sleep(1000)
|
||||
|
||||
idea {
|
||||
waitSmartMode()
|
||||
|
||||
testEnterWorksInPyConsole()
|
||||
}
|
||||
}
|
||||
|
||||
private fun IdeaFrame.testEnterWorksInPyConsole() {
|
||||
findText("Python Console").click()
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
keyboard {
|
||||
enterText("print(123 + 321)")
|
||||
enter()
|
||||
}
|
||||
|
||||
waitFor {
|
||||
findAllText("444").isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun RemoteRobot.startNewProject() {
|
||||
welcomeFrame {
|
||||
createNewProjectLink.click()
|
||||
button("Create").click()
|
||||
}
|
||||
}
|
||||
}
|
22
tests/py-ui-tests/src/test/kotlin/StepsLogger.kt
Normal file
22
tests/py-ui-tests/src/test/kotlin/StepsLogger.kt
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.intellij.remoterobot.stepsProcessing.StepLogger
|
||||
import com.intellij.remoterobot.stepsProcessing.StepWorker
|
||||
|
||||
object StepsLogger {
|
||||
private var initializaed = false
|
||||
|
||||
@JvmStatic
|
||||
fun init() = synchronized(initializaed) {
|
||||
if (initializaed.not()) {
|
||||
StepWorker.registerProcessor(StepLogger())
|
||||
initializaed = true
|
||||
}
|
||||
}
|
||||
}
|
65
tests/py-ui-tests/src/test/kotlin/UiTestWrapper.kt
Normal file
65
tests/py-ui-tests/src/test/kotlin/UiTestWrapper.kt
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.intellij.remoterobot.RemoteRobot
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
fun uiTest(testName: String = "test_${System.currentTimeMillis()}", url: String = "http://127.0.0.1:8082", test: RemoteRobot.() -> Unit) {
|
||||
val remoteRobot = RemoteRobot(url)
|
||||
try {
|
||||
remoteRobot.test()
|
||||
} catch (e: Throwable) {
|
||||
try {
|
||||
saveScreenshot(testName, remoteRobot)
|
||||
saveHierarchy(testName, url)
|
||||
} catch (another: Throwable) {
|
||||
another.initCause(e)
|
||||
throw another
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
private val client by lazy { OkHttpClient() }
|
||||
private fun BufferedImage.save(name: String) {
|
||||
val bytes = ByteArrayOutputStream().use { b ->
|
||||
ImageIO.write(this, "png", b)
|
||||
b.toByteArray()
|
||||
}
|
||||
File("build/reports").apply { mkdirs() }.resolve("$name.png").writeBytes(bytes)
|
||||
}
|
||||
|
||||
fun saveScreenshot(testName: String, remoteRobot: RemoteRobot) {
|
||||
fetchScreenShot(remoteRobot).save(testName)
|
||||
}
|
||||
|
||||
private fun fetchScreenShot(remoteRobot: RemoteRobot): BufferedImage {
|
||||
return remoteRobot.getScreenshot()
|
||||
}
|
||||
|
||||
private fun saveHierarchy(testName: String, url: String) {
|
||||
val hierarchySnapshot =
|
||||
saveFile(url, "build/reports", "hierarchy-$testName.html")
|
||||
if (File("build/reports/styles.css").exists().not()) {
|
||||
saveFile("$url/styles.css", "build/reports", "styles.css")
|
||||
}
|
||||
println("Hierarchy snapshot: ${hierarchySnapshot.absolutePath}")
|
||||
}
|
||||
|
||||
private fun saveFile(url: String, folder: String, name: String): File {
|
||||
val response = client.newCall(Request.Builder().url(url).build()).execute()
|
||||
return File(folder).apply {
|
||||
mkdirs()
|
||||
}.resolve(name).apply {
|
||||
writeText(response.body?.string() ?: "")
|
||||
}
|
||||
}
|
109
tests/py-ui-tests/src/test/kotlin/Utils.kt
Normal file
109
tests/py-ui-tests/src/test/kotlin/Utils.kt
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.fixtures.Fixture
|
||||
import com.intellij.remoterobot.fixtures.dataExtractor.RemoteText
|
||||
import org.assertj.swing.core.MouseButton
|
||||
import ui.pages.Editor
|
||||
import java.awt.Point
|
||||
|
||||
fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
|
||||
val updatedPoint = Point(this.point.x + shiftX, this.point.y)
|
||||
fixture.remoteRobot.execute(fixture) {
|
||||
robot.click(component, updatedPoint, button, 2)
|
||||
}
|
||||
}
|
||||
|
||||
fun RemoteText.tripleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
|
||||
val updatedPoint = Point(this.point.x + shiftX, this.point.y)
|
||||
fixture.remoteRobot.execute(fixture) {
|
||||
robot.click(component, updatedPoint, button, 3)
|
||||
}
|
||||
}
|
||||
|
||||
fun RemoteText.moveMouseTo(goal: RemoteText, editor: Editor): Boolean {
|
||||
this.moveMouse()
|
||||
editor.runJs("robot.pressMouse(MouseButton.LEFT_BUTTON)")
|
||||
goal.moveMouse()
|
||||
val caretDuringDragging = false/*editor.isBlockCursor*/
|
||||
editor.runJs("robot.releaseMouse(MouseButton.LEFT_BUTTON)")
|
||||
// waitFor { editor.isBlockCursor }
|
||||
return caretDuringDragging
|
||||
}
|
||||
|
||||
fun RemoteText.moveMouseWithDelayTo(goal: RemoteText, editor: Editor, delay: Long = 1000): Boolean {
|
||||
this.moveMouse()
|
||||
editor.runJs("robot.pressMouse(MouseButton.LEFT_BUTTON)")
|
||||
goal.moveMouse()
|
||||
Thread.sleep(delay)
|
||||
val caretDuringDragging = false/*editor.isBlockCursor*/
|
||||
editor.runJs("robot.releaseMouse(MouseButton.LEFT_BUTTON)")
|
||||
// waitFor { editor.isBlockCursor }
|
||||
return caretDuringDragging
|
||||
}
|
||||
|
||||
fun RemoteText.moveMouseInGutterTo(goal: RemoteText, fixture: Fixture) {
|
||||
this.moveMouse()
|
||||
val goalPoint = goal.point
|
||||
|
||||
fixture.runJs(
|
||||
"""
|
||||
const point = new java.awt.Point(${goalPoint.x}, ${goalPoint.y});
|
||||
robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
|
||||
robot.moveMouse(component, point)
|
||||
})
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
fun Point.moveMouseTo(point: Point, fixture: Fixture) {
|
||||
val _point = this
|
||||
fixture.execute { robot.moveMouse(component, _point) }
|
||||
|
||||
fixture.runJs(
|
||||
"""
|
||||
const point = new java.awt.Point(${point.x}, ${point.y});
|
||||
robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
|
||||
robot.moveMouse(component, point)
|
||||
})
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
fun RemoteText.moveMouseForthAndBack(middle: RemoteText, editor: Editor) {
|
||||
this.moveMouse()
|
||||
val initialPoint = this.point
|
||||
val middlePoint = middle.point
|
||||
|
||||
editor.runJs(
|
||||
"""
|
||||
const initialPoint = new Point(${initialPoint.x}, ${initialPoint.y});
|
||||
const point = new Point(${middlePoint.x}, ${middlePoint.y});
|
||||
robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
|
||||
robot.moveMouse(component, point)
|
||||
robot.moveMouse(component, initialPoint)
|
||||
})
|
||||
""",
|
||||
)
|
||||
// waitFor { editor.isBlockCursor }
|
||||
}
|
||||
|
||||
fun String.escape(): String = this.replace("\n", "\\n")
|
||||
|
||||
fun RemoteRobot.invokeActionJs(actionId: String) {
|
||||
runJs(
|
||||
"""
|
||||
const actionId = "$actionId";
|
||||
const actionManager = com.intellij.openapi.actionSystem.ActionManager.getInstance();
|
||||
const action = actionManager.getAction(actionId);
|
||||
actionManager.tryToExecute(action, com.intellij.openapi.ui.playback.commands.ActionCommand.getInputEvent(actionId), null, null, true);
|
||||
""",
|
||||
runInEdt = true,
|
||||
)
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2003-2023 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.
|
||||
*/
|
||||
|
||||
package pages;
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot;
|
||||
import com.intellij.remoterobot.data.RemoteComponent;
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture;
|
||||
import com.intellij.remoterobot.fixtures.FixtureName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@FixtureName(name = "Action Link")
|
||||
public class ActionLinkFixture extends ComponentFixture {
|
||||
public ActionLinkFixture(@NotNull RemoteRobot remoteRobot, @NotNull RemoteComponent remoteComponent) {
|
||||
super(remoteRobot, remoteComponent);
|
||||
}
|
||||
|
||||
public void click() {
|
||||
runJs("const offset = component.getHeight()/2;\n" +
|
||||
"robot.click(" +
|
||||
"component, " +
|
||||
"new Point(offset, offset), " +
|
||||
"MouseButton.LEFT_BUTTON, 1);");
|
||||
}
|
||||
}
|
44
tests/py-ui-tests/src/test/kotlin/pages/ActionMenuFixture.kt
Normal file
44
tests/py-ui-tests/src/test/kotlin/pages/ActionMenuFixture.kt
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import com.intellij.remoterobot.utils.waitFor
|
||||
|
||||
fun RemoteRobot.actionMenu(text: String): ActionMenuFixture {
|
||||
val xpath = byXpath("text '$text'", "//div[@class='ActionMenu' and @text='$text']")
|
||||
waitFor {
|
||||
findAll<ActionMenuFixture>(xpath).isNotEmpty()
|
||||
}
|
||||
return findAll<ActionMenuFixture>(xpath).first()
|
||||
}
|
||||
|
||||
fun RemoteRobot.actionMenuItem(text: String): ActionMenuItemFixture {
|
||||
val xpath = byXpath("text '$text'", "//div[@class='ActionMenuItem' and @text='$text']")
|
||||
waitFor {
|
||||
findAll<ActionMenuItemFixture>(xpath).isNotEmpty()
|
||||
}
|
||||
return findAll<ActionMenuItemFixture>(xpath).first()
|
||||
}
|
||||
|
||||
@FixtureName("ActionMenu")
|
||||
class ActionMenuFixture(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : ComponentFixture(remoteRobot, remoteComponent)
|
||||
|
||||
@FixtureName("ActionMenuItem")
|
||||
class ActionMenuItemFixture(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : ComponentFixture(remoteRobot, remoteComponent)
|
42
tests/py-ui-tests/src/test/kotlin/pages/DialogFixture.kt
Normal file
42
tests/py-ui-tests/src/test/kotlin/pages/DialogFixture.kt
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import com.intellij.remoterobot.stepsProcessing.step
|
||||
import java.time.Duration
|
||||
|
||||
fun ContainerFixture.dialog(
|
||||
title: String,
|
||||
timeout: Duration = Duration.ofSeconds(20),
|
||||
function: DialogFixture.() -> Unit = {},
|
||||
): DialogFixture = step("Search for dialog with title $title") {
|
||||
find<DialogFixture>(DialogFixture.byTitle(title), timeout).apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("Dialog")
|
||||
class DialogFixture(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun byTitle(title: String) = byXpath("title $title", "//div[@title='$title']")
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val title: String
|
||||
get() = callJs("component.getTitle();")
|
||||
}
|
100
tests/py-ui-tests/src/test/kotlin/pages/Editor.kt
Normal file
100
tests/py-ui-tests/src/test/kotlin/pages/Editor.kt
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import escape
|
||||
import java.awt.Point
|
||||
|
||||
@JvmOverloads
|
||||
fun ContainerFixture.editor(title: String, function: Editor.() -> Unit = {}): Editor {
|
||||
find<ComponentFixture>(
|
||||
byXpath("//div[@class='EditorTabs']//div[@accessiblename='$title' and @class='SimpleColoredComponent']"),
|
||||
).click()
|
||||
return find<Editor>(
|
||||
byXpath("title '$title'", "//div[@accessiblename='Editor for $title' and @class='EditorComponentImpl']"),
|
||||
)
|
||||
.apply { runJs("robot.moveMouse(component);") }
|
||||
.apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("Editor")
|
||||
class Editor(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent) {
|
||||
val text: String
|
||||
get() = callJs("component.getEditor().getDocument().getText()", true)
|
||||
|
||||
val selectedText: String
|
||||
get() = callJs("component.getEditor().getSelectionModel().getSelectedText()", true)
|
||||
|
||||
val caretOffset: Int
|
||||
get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
|
||||
|
||||
val caretCount: Int
|
||||
get() = callJs("component.getEditor().getCaretModel().getCaretCount()", runInEdt = true)
|
||||
|
||||
val isBlockCursor: Boolean
|
||||
// get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
|
||||
// Doesn't work at the moment because remote robot can't resolve classes from a plugin classloader
|
||||
get() = callJs("com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt.hasBlockOrUnderscoreCaret(component.getEditor())", true)
|
||||
|
||||
fun injectText(text: String) {
|
||||
runJs(
|
||||
"""
|
||||
const app = com.intellij.openapi.application.ApplicationManager.getApplication()
|
||||
|
||||
app.invokeLaterOnWriteThread(()=>{
|
||||
app['runWriteAction(com.intellij.openapi.util.Computable)'](()=>{
|
||||
component.getEditor().getDocument().setText('${text.escape()}')
|
||||
})
|
||||
})
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun findPointByOffset(offset: Int): Point {
|
||||
return callJs(
|
||||
"""
|
||||
const editor = component.getEditor()
|
||||
const visualPosition = editor.offsetToVisualPosition($offset)
|
||||
editor.visualPositionToXY(visualPosition)
|
||||
""",
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun moveToLine(lineNumber: Int) {
|
||||
val pointToClick = callJs<Point>(
|
||||
"""
|
||||
importClass(com.intellij.openapi.editor.ScrollType)
|
||||
const editor = component.getEditor()
|
||||
const document = editor.getDocument()
|
||||
const offset = document.getLineStartOffset($lineNumber - 1)
|
||||
editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(offset), ScrollType.CENTER)
|
||||
const visualPosition = editor.offsetToVisualPosition(offset)
|
||||
editor.visualPositionToXY(visualPosition)
|
||||
""",
|
||||
runInEdt = true,
|
||||
)
|
||||
// wait a bit for scroll completed
|
||||
Thread.sleep(500)
|
||||
|
||||
click(pointToClick)
|
||||
}
|
||||
}
|
31
tests/py-ui-tests/src/test/kotlin/pages/Gutter.kt
Normal file
31
tests/py-ui-tests/src/test/kotlin/pages/Gutter.kt
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
|
||||
@JvmOverloads
|
||||
fun ContainerFixture.gutter(function: Gutter.() -> Unit = {}): ContainerFixture {
|
||||
return find<Gutter>(
|
||||
byXpath("//div[@class='EditorGutterComponentImpl']"),
|
||||
)
|
||||
.apply { runJs("robot.moveMouse(component);") }
|
||||
.apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("Gutter")
|
||||
class Gutter(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent)
|
65
tests/py-ui-tests/src/test/kotlin/pages/IdeaFrame.kt
Normal file
65
tests/py-ui-tests/src/test/kotlin/pages/IdeaFrame.kt
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.DefaultXpath
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.fixtures.JTreeFixture
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import com.intellij.remoterobot.stepsProcessing.step
|
||||
import com.intellij.remoterobot.utils.waitFor
|
||||
import java.time.Duration
|
||||
|
||||
fun RemoteRobot.idea(function: IdeaFrame.() -> Unit) {
|
||||
find<IdeaFrame>().apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("Idea frame")
|
||||
@DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']")
|
||||
class IdeaFrame(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent) {
|
||||
|
||||
val projectViewTree
|
||||
get() = find<JTreeFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"), Duration.ofSeconds(10))
|
||||
|
||||
val projectName
|
||||
get() = step("Get project name") { return@step callJs<String>("component.getProject().getName()") }
|
||||
|
||||
@JvmOverloads
|
||||
fun dumbAware(timeout: Duration = Duration.ofMinutes(5), function: () -> Unit) {
|
||||
step("Wait for smart mode") {
|
||||
waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
|
||||
runCatching { isDumbMode().not() }.getOrDefault(false)
|
||||
}
|
||||
function()
|
||||
step("..wait for smart mode again") {
|
||||
waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
|
||||
isDumbMode().not()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun waitSmartMode(timeout: Duration = Duration.ofMinutes(5)) {
|
||||
step("Wait for smart mode") {
|
||||
waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
|
||||
isDumbMode().not()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDumbMode(): Boolean {
|
||||
return callJs("com.intellij.openapi. project.DumbService.isDumb(component.project);", true)
|
||||
}
|
||||
}
|
31
tests/py-ui-tests/src/test/kotlin/pages/SearchEverywhere.kt
Normal file
31
tests/py-ui-tests/src/test/kotlin/pages/SearchEverywhere.kt
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
|
||||
@JvmOverloads
|
||||
fun ContainerFixture.searchEverywhere(function: SearchEverywhere.() -> Unit = {}): SearchEverywhere {
|
||||
return find<SearchEverywhere>(
|
||||
byXpath("Search Everywhere", "//div[@accessiblename='Search everywhere' and @class='SearchEverywhereUI']"),
|
||||
)
|
||||
.apply { runJs("robot.moveMouse(component);") }
|
||||
.apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("SearchEverywhere")
|
||||
class SearchEverywhere(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent)
|
43
tests/py-ui-tests/src/test/kotlin/pages/WelcomeFrame.kt
Normal file
43
tests/py-ui-tests/src/test/kotlin/pages/WelcomeFrame.kt
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture
|
||||
import com.intellij.remoterobot.fixtures.DefaultXpath
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
import java.time.Duration
|
||||
|
||||
fun RemoteRobot.welcomeFrame(function: WelcomeFrame.() -> Unit) {
|
||||
find(WelcomeFrame::class.java, Duration.ofSeconds(10)).apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("Welcome Frame")
|
||||
@DefaultXpath("type", "//div[@class='FlatWelcomeFrame']")
|
||||
class WelcomeFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) :
|
||||
CommonContainerFixture(remoteRobot, remoteComponent) {
|
||||
val createNewProjectLink
|
||||
get() = actionLink(
|
||||
byXpath(
|
||||
"New Project",
|
||||
"//div[(@class='MainButton' and @text='New Project') or (@accessiblename='New Project' and @class='JButton')]",
|
||||
),
|
||||
)
|
||||
|
||||
@Suppress("unused")
|
||||
val moreActions
|
||||
get() = button(byXpath("More Action", "//div[@accessiblename='More Actions' and @class='ActionButton']"))
|
||||
|
||||
@Suppress("unused")
|
||||
val heavyWeightPopup
|
||||
get() = remoteRobot.find(ComponentFixture::class.java, byXpath("//div[@class='HeavyWeightWindow']"))
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2003-2024 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.
|
||||
*/
|
||||
|
||||
package pages;
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot;
|
||||
import com.intellij.remoterobot.data.RemoteComponent;
|
||||
import com.intellij.remoterobot.fixtures.ComponentFixture;
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture;
|
||||
import com.intellij.remoterobot.fixtures.DefaultXpath;
|
||||
import com.intellij.remoterobot.fixtures.FixtureName;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.remoterobot.search.locators.Locators.byXpath;
|
||||
|
||||
|
||||
@DefaultXpath(by = "FlatWelcomeFrame type", xpath = "//div[@class='FlatWelcomeFrame']")
|
||||
@FixtureName(name = "Welcome Frame")
|
||||
public class WelcomeFrameFixture extends ContainerFixture {
|
||||
public WelcomeFrameFixture(@NotNull RemoteRobot remoteRobot, @NotNull RemoteComponent remoteComponent) {
|
||||
super(remoteRobot, remoteComponent);
|
||||
}
|
||||
|
||||
public ActionLinkFixture createNewProjectLink() {
|
||||
return find(ActionLinkFixture.class, byXpath(
|
||||
"//div[(@accessiblename='New Project' and @class='MainButton') or (@accessiblename='New Project' and @class='JButton')]"));
|
||||
}
|
||||
|
||||
public ComponentFixture importProjectLink() {
|
||||
return find(ComponentFixture.class,
|
||||
byXpath("//div[@accessiblename='Get from Version Control...' and @class='JButton']"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user