1
0
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:
Alex Plate 2024-02-09 16:03:31 +02:00
parent d87965775a
commit 519d5eed06
No known key found for this signature in database
GPG Key ID: 0B97153C8FFEC09F
18 changed files with 809 additions and 74 deletions

55
.github/workflows/runUiPyTests.yml vendored Normal file
View 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

View File

@ -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'

View File

@ -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") }

View File

@ -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")
}
}

View 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))
}
}

View 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()
}
}
}

View 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
}
}
}

View 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() ?: "")
}
}

View 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,
)
}

View File

@ -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);");
}
}

View 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)

View 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();")
}

View 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)
}
}

View 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)

View 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)
}
}

View 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)

View 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']"))
}

View File

@ -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']"));
}
}