Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run UI test ci #349

Merged
merged 2 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/runUiTests.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ Contributors:
[![icon][github]](https://github.com/MichalPlacek)
 
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
contact the maintainers.
Expand Down
20 changes: 12 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ val kotlinVersion: String by project
val ideaVersion: String by project
val downloadIdeaSources: String by project
val instrumentPluginCode: String by project
val remoteRobotVersion: String by project

val publishChannels: String by project
val publishToken: String by project
Expand All @@ -55,8 +56,8 @@ dependencies {
testImplementation("com.ensarsarajcic.neovim.java:neovim-api: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-fixtures:1.1.18")
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
}

// --- Compilation
Expand Down Expand Up @@ -116,7 +117,7 @@ intellij {

tasks {
downloadRobotServerPlugin {
version.set("0.10.0")
version.set(remoteRobotVersion)
}

publishPlugin {
Expand All @@ -126,6 +127,9 @@ tasks {

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

runPluginVerifier {
Expand Down Expand Up @@ -169,11 +173,11 @@ tasks.register<Test>("testUi") {
// --- Changelog

changelog {
groups = listOf("Features:", "Changes:", "Deprecations:", "Fixes:", "Merged PRs:")
itemPrefix = "*"
path = "${project.projectDir}/CHANGES.md"
unreleasedTerm = "To Be Released"
headerParserRegex = "0\\.\\d{2}(.\\d+)?".toRegex()
groups.set(listOf("Features:", "Changes:", "Deprecations:", "Fixes:", "Merged PRs:"))
itemPrefix.set("*")
path.set("${project.projectDir}/CHANGES.md")
unreleasedTerm.set("To Be Released")
headerParserRegex.set("0\\.\\d{2}(.\\d+)?".toRegex())
// header = { "${project.version}" }
// version = "0.60"
}
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ downloadIdeaSources=true
instrumentPluginCode=true
version=SNAPSHOT
javaVersion=1.8
remoteRobotVersion=0.11.6

# Please don't forget to update kotlin version in buildscript section
kotlinVersion=1.5.0
Expand Down
11 changes: 6 additions & 5 deletions test/ui/UiTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.search.locators.byXpath
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.Test
import ui.pages.Editor
Expand Down Expand Up @@ -53,7 +52,7 @@ class UiTests {
}

@Test
fun ideaVimTest() = uiTest {
fun ideaVimTest() = uiTest("ideaVimTest") {
val sharedSteps = JavaExampleSteps(this)

welcomeFrame {
Expand All @@ -69,12 +68,14 @@ class UiTests {
button("Finish").click()
}
}
sharedSteps.closeTipOfTheDay()
with(sharedSteps) {
closeIdeaVimDialog()
closeTipOfTheDay()
}
idea {
step("Create App file") {
with(projectViewTree) {
findText(projectName).doubleClick()
waitFor { hasText("src") }
expand(projectName, "src")
findText("src").click(MouseButton.RIGHT_BUTTON)
}
actionMenu("New").click()
Expand Down
13 changes: 12 additions & 1 deletion test/ui/pages/Editor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,19 @@ class Editor(
val caretOffset: Int
get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)

val isBlockCursor: Boolean
get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)

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")
Expand Down
3 changes: 2 additions & 1 deletion test/ui/pages/IdeaFrame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ContainerFixture
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
Expand All @@ -41,7 +42,7 @@ class IdeaFrame(
) : CommonContainerFixture(remoteRobot, remoteComponent) {

val projectViewTree
get() = find<ContainerFixture>(byXpath("ProjectViewTree", "//div[@class='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()") }
Expand Down
46 changes: 33 additions & 13 deletions test/ui/utils/JavaExampleSteps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,48 @@
package ui.utils

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.utils.Keyboard
import ui.pages.DialogFixture
import ui.pages.DialogFixture.Companion.byTitle
import ui.pages.IdeaFrame
import ui.pages.dialog
import ui.pages.idea

class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
@Suppress("unused")
private val keyboard: Keyboard = Keyboard(remoteRobot)

fun closeTipOfTheDay() {
step(
"Close Tip of the Day if it appears",
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()
} catch (ignore: Throwable) {
}
}
}
)
fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
remoteRobot.idea {
dialog("IdeaVim") { button("Yes").click() }
}
}


fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
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")
}
}
}
51 changes: 49 additions & 2 deletions test/ui/utils/UiTestWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,54 @@
package ui.utils

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) {
RemoteRobot(url).apply(test)
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) {
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() ?: "")
}
}
32 changes: 12 additions & 20 deletions test/ui/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.intellij.remoterobot.fixtures.Fixture
import com.intellij.remoterobot.fixtures.dataExtractor.RemoteText
import com.intellij.remoterobot.utils.waitFor
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) {
Expand All @@ -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()
val goalPoint = goal.point

val caretDuringDragging = fixture.callJs<Boolean>(
"""
const point = new java.awt.Point(${goalPoint.x}, ${goalPoint.y});
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()") }
editor.runJs("robot.pressMouse(MouseButton.LEFT_BUTTON)")
goal.moveMouse()
val caretDuringDragging = editor.isBlockCursor
editor.runJs("robot.releaseMouse(MouseButton.LEFT_BUTTON)")
waitFor { editor.isBlockCursor }
return caretDuringDragging
}

Expand All @@ -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()
val initialPoint = this.point
val middlePoint = middle.point

fixture.runJs(
editor.runJs(
"""
const initialPoint = new java.awt.Point(${initialPoint.x}, ${initialPoint.y});
const point = new java.awt.Point(${middlePoint.x}, ${middlePoint.y});
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 { fixture.callJs("component.getEditor().getSettings().isBlockCursor()") }
waitFor { editor.isBlockCursor }
}

fun String.escape(): String = this.replace("\n", "\\n")