1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-06-02 13:34:07 +02:00

fixed Ide Fatal Errors

added UI tests reporting
This commit is contained in:
eugene.nizienko 2021-07-23 01:32:52 +02:00 committed by Alex Pláte
parent 4158bf1663
commit a58ca80fc9
10 changed files with 189 additions and 45 deletions

66
.github/workflows/runUiTests.yml vendored Normal file
View File

@ -0,0 +1,66 @@
name: Run UI Tests
on:
workflow_dispatch
jobs:
build-for-ui-test-mac-os:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v2.1.0
with:
distribution: zulu
java-version: 11
- name: Build Plugin
run: gradle :buildPlugin
- name: Run Idea
run: |
mkdir -p build/reports
gradle :runIdeForUiTests > build/reports/idea.log &
- name: Wait for Idea started
uses: jtalk/url-health-check-action@1.5
with:
url: http://127.0.0.1:8082
max-attempts: 20
retry-delay: 10s
- name: Tests
run: gradle :testUi
- name: Save fails report
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: ui-test-fails-report-mac
path: |
build/reports
# build-for-ui-test-linux:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - name: Setup Java
# uses: actions/setup-java@v2.1.0
# with:
# distribution: zulu
# java-version: 11
# - name: Build Plugin
# run: gradle :buildPlugin
# - name: Run Idea
# run: |
# export DISPLAY=:99.0
# Xvfb -ac :99 -screen 0 1920x1080x16 &
# mkdir -p build/reports
# gradle :runIdeForUiTests #> build/reports/idea.log
# - name: Wait for Idea started
# uses: jtalk/url-health-check-action@1.5
# with:
# url: http://127.0.0.1:8082
# max-attempts: 15
# retry-delay: 30s
# - name: Tests
# run: gradle :testUi
# - name: Save fails report
# if: ${{ failure() }}
# uses: actions/upload-artifact@v2
# with:
# name: ui-test-fails-report-linux
# path: |
# ui-test-example/build/reports

View File

@ -360,6 +360,10 @@ Contributors:
[![icon][github]](https://github.com/MichalPlacek) [![icon][github]](https://github.com/MichalPlacek)
   
Michal Placek Michal Placek
* [![icon][mail]](mailto:eugene.nizienko@jetbrains.com)
[![icon][github]](https://github.com/nizienko)
 
eugene nizienko
If you are a contributor and your name is not listed here, feel free to If you are a contributor and your name is not listed here, feel free to
contact the maintainers. contact the maintainers.

View File

@ -36,6 +36,7 @@ val kotlinVersion: String by project
val ideaVersion: String by project val ideaVersion: String by project
val downloadIdeaSources: String by project val downloadIdeaSources: String by project
val instrumentPluginCode: String by project val instrumentPluginCode: String by project
val remoteRobotVersion: String by project
val publishChannels: String by project val publishChannels: String by project
val publishToken: String by project val publishToken: String by project
@ -55,8 +56,8 @@ dependencies {
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3") testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
testImplementation("com.ensarsarajcic.neovim.java:core-rpc:0.2.3") testImplementation("com.ensarsarajcic.neovim.java:core-rpc:0.2.3")
testImplementation("com.intellij.remoterobot:remote-robot:0.11.6") testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:1.1.18") testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
} }
// --- Compilation // --- Compilation
@ -116,7 +117,7 @@ intellij {
tasks { tasks {
downloadRobotServerPlugin { downloadRobotServerPlugin {
version.set("0.10.0") version.set(remoteRobotVersion)
} }
publishPlugin { publishPlugin {

View File

@ -5,6 +5,7 @@ downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=SNAPSHOT
javaVersion=1.8 javaVersion=1.8
remoteRobotVersion=0.11.6
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
kotlinVersion=1.5.0 kotlinVersion=1.5.0

View File

@ -23,7 +23,6 @@ import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.search.locators.byXpath import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.keyboard import com.intellij.remoterobot.utils.keyboard
import com.intellij.remoterobot.utils.waitFor
import org.assertj.swing.core.MouseButton import org.assertj.swing.core.MouseButton
import org.junit.Test import org.junit.Test
import ui.pages.Editor import ui.pages.Editor
@ -53,7 +52,7 @@ class UiTests {
} }
@Test @Test
fun ideaVimTest() = uiTest { fun ideaVimTest() = uiTest("ideaVimTest") {
val sharedSteps = JavaExampleSteps(this) val sharedSteps = JavaExampleSteps(this)
welcomeFrame { welcomeFrame {
@ -69,12 +68,14 @@ class UiTests {
button("Finish").click() button("Finish").click()
} }
} }
sharedSteps.closeTipOfTheDay() with(sharedSteps) {
closeIdeaVimDialog()
closeTipOfTheDay()
}
idea { idea {
step("Create App file") { step("Create App file") {
with(projectViewTree) { with(projectViewTree) {
findText(projectName).doubleClick() expand(projectName, "src")
waitFor { hasText("src") }
findText("src").click(MouseButton.RIGHT_BUTTON) findText("src").click(MouseButton.RIGHT_BUTTON)
} }
actionMenu("New").click() actionMenu("New").click()

View File

@ -54,8 +54,19 @@ class Editor(
val caretOffset: Int val caretOffset: Int
get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true) get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
val isBlockCursor: Boolean
get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
fun injectText(text: String) { fun injectText(text: String) {
runJs("component.getEditor().getDocument().setText('${text.escape()}')", runInEdt = true) 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") @Suppress("unused")

View File

@ -24,6 +24,7 @@ import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ContainerFixture import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.fixtures.DefaultXpath import com.intellij.remoterobot.fixtures.DefaultXpath
import com.intellij.remoterobot.fixtures.FixtureName import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.fixtures.JTreeFixture
import com.intellij.remoterobot.search.locators.byXpath import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.waitFor import com.intellij.remoterobot.utils.waitFor
@ -41,7 +42,7 @@ class IdeaFrame(
) : CommonContainerFixture(remoteRobot, remoteComponent) { ) : CommonContainerFixture(remoteRobot, remoteComponent) {
val projectViewTree val projectViewTree
get() = find<ContainerFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']")) get() = find<JTreeFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"), Duration.ofSeconds(10))
val projectName val projectName
get() = step("Get project name") { return@step callJs<String>("component.getProject().getName()") } get() = step("Get project name") { return@step callJs<String>("component.getProject().getName()") }

View File

@ -18,28 +18,48 @@
package ui.utils package ui.utils
import com.intellij.remoterobot.RemoteRobot import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.JButtonFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.Keyboard import com.intellij.remoterobot.utils.Keyboard
import ui.pages.DialogFixture import ui.pages.DialogFixture
import ui.pages.DialogFixture.Companion.byTitle import ui.pages.DialogFixture.Companion.byTitle
import ui.pages.IdeaFrame import ui.pages.IdeaFrame
import ui.pages.dialog
import ui.pages.idea
class JavaExampleSteps(private val remoteRobot: RemoteRobot) { class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
@Suppress("unused") @Suppress("unused")
private val keyboard: Keyboard = Keyboard(remoteRobot) private val keyboard: Keyboard = Keyboard(remoteRobot)
fun closeTipOfTheDay() { fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
step( remoteRobot.idea {
"Close Tip of the Day if it appears", dialog("IdeaVim") { button("Yes").click() }
Runnable { }
val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java) }
idea.dumbAware {
try {
idea.find(DialogFixture::class.java, byTitle("Tip of the Day")).button("Close").click() fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
} catch (ignore: Throwable) { val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
} idea.dumbAware {
} idea.find(DialogFixture::class.java, byTitle("Tip of the Day")).button("Close").click()
} }
) closeAllGotIt()
}
fun closeAllGotIt() = step("Close Got It") {
remoteRobot.findAll<JButtonFixture>(byXpath("//div[@accessiblename='Got It']")).forEach {
it.click()
}
}
private fun optionalStep(stepName: String, code: () -> Unit) = step(stepName) {
try {
code()
} catch (ignore: Throwable) {
println("$stepName ignored")
}
} }
} }

View File

@ -19,7 +19,54 @@
package ui.utils package ui.utils
import com.intellij.remoterobot.RemoteRobot 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(url: String = "http://127.0.0.1:8082", test: RemoteRobot.() -> Unit) { fun uiTest(testName: String = "test_${System.currentTimeMillis()}", url: String = "http://127.0.0.1:8082", test: RemoteRobot.() -> Unit) {
RemoteRobot(url).apply(test) val remoteRobot = RemoteRobot(url)
try {
remoteRobot.test()
} catch (e: Throwable) {
saveScreenshot(testName, remoteRobot)
saveHierarchy(testName, url)
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

@ -22,6 +22,7 @@ import com.intellij.remoterobot.fixtures.Fixture
import com.intellij.remoterobot.fixtures.dataExtractor.RemoteText import com.intellij.remoterobot.fixtures.dataExtractor.RemoteText
import com.intellij.remoterobot.utils.waitFor import com.intellij.remoterobot.utils.waitFor
import org.assertj.swing.core.MouseButton import org.assertj.swing.core.MouseButton
import ui.pages.Editor
import java.awt.Point import java.awt.Point
fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) { fun RemoteText.doubleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseButton = MouseButton.LEFT_BUTTON) {
@ -38,22 +39,13 @@ fun RemoteText.tripleClickOnRight(shiftX: Int, fixture: Fixture, button: MouseBu
} }
} }
fun RemoteText.moveMouseTo(goal: RemoteText, fixture: Fixture): Boolean { fun RemoteText.moveMouseTo(goal: RemoteText, editor: Editor): Boolean {
this.moveMouse() this.moveMouse()
val goalPoint = goal.point editor.runJs("robot.pressMouse(MouseButton.LEFT_BUTTON)")
goal.moveMouse()
val caretDuringDragging = fixture.callJs<Boolean>( val caretDuringDragging = editor.isBlockCursor
""" editor.runJs("robot.releaseMouse(MouseButton.LEFT_BUTTON)")
const point = new java.awt.Point(${goalPoint.x}, ${goalPoint.y}); waitFor { editor.isBlockCursor }
let isBlock = true;
robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
robot.moveMouse(component, point)
isBlock = component.getEditor().getSettings().isBlockCursor();
})
isBlock
"""
)
waitFor { fixture.callJs("component.getEditor().getSettings().isBlockCursor()") }
return caretDuringDragging return caretDuringDragging
} }
@ -71,22 +63,22 @@ fun RemoteText.moveMouseInGutterTo(goal: RemoteText, fixture: Fixture) {
) )
} }
fun RemoteText.moveMouseForthAndBack(middle: RemoteText, fixture: Fixture) { fun RemoteText.moveMouseForthAndBack(middle: RemoteText, editor: Editor) {
this.moveMouse() this.moveMouse()
val initialPoint = this.point val initialPoint = this.point
val middlePoint = middle.point val middlePoint = middle.point
fixture.runJs( editor.runJs(
""" """
const initialPoint = new java.awt.Point(${initialPoint.x}, ${initialPoint.y}); const initialPoint = new Point(${initialPoint.x}, ${initialPoint.y});
const point = new java.awt.Point(${middlePoint.x}, ${middlePoint.y}); const point = new Point(${middlePoint.x}, ${middlePoint.y});
robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => { robot.pressMouseWhileRunning(MouseButton.LEFT_BUTTON, () => {
robot.moveMouse(component, point) robot.moveMouse(component, point)
robot.moveMouse(component, initialPoint) robot.moveMouse(component, initialPoint)
}) })
""" """
) )
waitFor { fixture.callJs("component.getEditor().getSettings().isBlockCursor()") } waitFor { editor.isBlockCursor }
} }
fun String.escape(): String = this.replace("\n", "\\n") fun String.escape(): String = this.replace("\n", "\\n")