[GUI-496] UI component testing plugin layout

known issues: `intellij.remoterobot.*` libraries transitive dependencies have been explicitly disabled

Co-authored-by: eugene.nizienko <eugene.nizienko@jetbrains.com>
Co-authored-by: Andrew Kozlov <andrew.kozlov@jetbrains.com>

GitOrigin-RevId: 2c04e57a6b6e3ba37f020b3558e10ee3d86ae841
This commit is contained in:
eugene.nizienko
2022-02-17 20:51:48 +01:00
committed by intellij-monorepo-bot
parent eae188855c
commit 35288f87bc
21 changed files with 742 additions and 3 deletions

1
.idea/modules.xml generated
View File

@@ -879,6 +879,7 @@
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/extensions/intellij.platform.testExtensions.iml" filepath="$PROJECT_DIR$/platform/testFramework/extensions/intellij.platform.testExtensions.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/intellij.platform.testFramework.iml" filepath="$PROJECT_DIR$/platform/testFramework/intellij.platform.testFramework.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/core/intellij.platform.testFramework.core.iml" filepath="$PROJECT_DIR$/platform/testFramework/core/intellij.platform.testFramework.core.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/testFramework/ui/intellij.platform.testFramework.ui.iml" filepath="$PROJECT_DIR$/platform/testFramework/ui/intellij.platform.testFramework.ui.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/testRunner/intellij.platform.testRunner.iml" filepath="$PROJECT_DIR$/platform/testRunner/intellij.platform.testRunner.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/platform-tests/intellij.platform.tests.iml" filepath="$PROJECT_DIR$/platform/platform-tests/intellij.platform.tests.iml" />
<module fileurl="file://$PROJECT_DIR$/platform/tracing-ide/intellij.platform.tracing.ide.iml" filepath="$PROJECT_DIR$/platform/tracing-ide/intellij.platform.tracing.ide.iml" />

View File

@@ -817,6 +817,10 @@ object CommunityLibraryLicenses {
jetbrainsLibrary("git-learning-project"),
jetbrainsLibrary("intellij-coverage"),
jetbrainsLibrary("intellij-test-discovery"),
jetbrainsLibrary("intellij.remoterobot.ide.launcher"),
jetbrainsLibrary("intellij.remoterobot.remote.fixtures"),
jetbrainsLibrary("intellij.remoterobot.remote.robot"),
jetbrainsLibrary("intellij.remoterobot.robot.server"),
jetbrainsLibrary("jetbrains.markdown.jvm"),
jetbrainsLibrary("jetbrains.research.refactorinsight.kotlin.impl"),
jetbrainsLibrary("jshell-frontend"),

View File

@@ -30,7 +30,7 @@ final class CommunityRepositoryModules {
plugin("intellij.laf.macos") {
bundlingRestrictions.supportedOs = persistentListOf(OsFamily.MACOS)
},
plugin("intellij.webp"){
plugin("intellij.webp") {
withResource("lib/libwebp/linux", "lib/libwebp/linux")
withResource("lib/libwebp/mac", "lib/libwebp/mac")
withResource("lib/libwebp/win", "lib/libwebp/win")
@@ -52,7 +52,7 @@ final class CommunityRepositoryModules {
plugin("intellij.vcs.git") {
withModule("intellij.vcs.git.rt", "git4idea-rt.jar")
},
plugin("intellij.vcs.svn"){
plugin("intellij.vcs.svn") {
withProjectLibrary("sqlite")
},
plugin("intellij.xpath") {
@@ -104,7 +104,7 @@ final class CommunityRepositoryModules {
"maven-dependency-tree-1.2.jar",
"mercury-artifact-1.0-alpha-6.jar",
"nexus-indexer-1.2.3.jar"
].each {withResource("maven2-server-impl/lib/$it", "lib/maven2-server-lib")}
].each { withResource("maven2-server-impl/lib/$it", "lib/maven2-server-lib") }
doNotCopyModuleLibrariesAutomatically([
"intellij.maven.server.m2.impl", "intellij.maven.server.m3.common", "intellij.maven.server.m36.impl", "intellij.maven.server.m3.impl", "intellij.maven.server.m30.impl",
"intellij.maven.server.m2.impl", "intellij.maven.server.m36.impl", "intellij.maven.server.m3.impl", "intellij.maven.server.m30.impl",
@@ -260,6 +260,13 @@ final class CommunityRepositoryModules {
simplePlugin("intellij.keymap.visualStudio"),
simplePlugin("intellij.keymap.netbeans"),
simplePlugin("kotlin.plugin-fir"),
plugin("intellij.platform.testFramework.ui") {
withModuleLibrary("intellij.remoterobot.ide.launcher", mainModule, "")
withModuleLibrary("intellij.remoterobot.remote.fixtures", mainModule, "")
withModuleLibrary("intellij.remoterobot.remote.robot", mainModule, "")
withModuleLibrary("intellij.remoterobot.robot.server", mainModule, "")
withProjectLibrary("okhttp")
},
)
public final static List<PluginLayout> CONTRIB_REPOSITORY_PLUGINS = List.of(

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.resources" />
<orderEntry type="library" scope="RUNTIME" name="okhttp" level="project" />
<orderEntry type="module-library">
<library name="intellij.remoterobot.robot.server" type="repository">
<properties maven-id="com.intellij.remoterobot:robot-server:0.11.14">
<exclude>
<dependency maven-id="org.jetbrains:annotations" />
<dependency maven-id="com.intellij.remoterobot:remote-robot" />
<dependency maven-id="org.apache.logging.log4j:log4j-api" />
<dependency maven-id="org.apache.logging.log4j:log4j-core" />
<dependency maven-id="com.squareup.retrofit2:retrofit" />
<dependency maven-id="com.squareup.okhttp3:okhttp" />
<dependency maven-id="com.squareup.okio:okio" />
<dependency maven-id="com.squareup.retrofit2:converter-gson" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-reflect" />
<dependency maven-id="org.bouncycastle:bcprov-jdk15on" />
<dependency maven-id="commons-io:commons-io" />
<dependency maven-id="org.assertj:assertj-swing-junit" />
<dependency maven-id="junit:junit" />
<dependency maven-id="org.hamcrest:hamcrest-core" />
<dependency maven-id="org.assertj:assertj-swing" />
<dependency maven-id="org.assertj:assertj-core" />
<dependency maven-id="org.easytesting:fest-util" />
<dependency maven-id="org.easytesting:fest-reflect" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/intellij/remoterobot/robot-server/0.11.14/robot-server-0.11.14.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.0/kotlin-stdlib-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.0/kotlin-stdlib-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.0/kotlin-stdlib-common-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.0/kotlin-stdlib-jdk7-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-core/1.4.1/ktor-server-core-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.3.9-native-mt-2/kotlinx-coroutines-jdk8-1.3.9-native-mt-2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.9-native-mt-2/kotlinx-coroutines-core-1.3.9-native-mt-2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-utils-jvm/1.4.1/ktor-utils-jvm-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.3.9-native-mt-2/kotlinx-coroutines-core-jvm-1.3.9-native-mt-2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-io-jvm/1.4.1/ktor-io-jvm-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-jvm/1.4.1/ktor-http-jvm-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/typesafe/config/1.3.1/config-1.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-netty/1.4.1/ktor-server-netty-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-host-common/1.4.1/ktor-server-host-common-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-cio-jvm/1.4.1/ktor-http-cio-jvm-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-network-jvm/1.4.1/ktor-network-jvm-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-codec-http2/4.1.51.Final/netty-codec-http2-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-common/4.1.51.Final/netty-common-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-buffer/4.1.51.Final/netty-buffer-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport/4.1.51.Final/netty-transport-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-resolver/4.1.51.Final/netty-resolver-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-codec/4.1.51.Final/netty-codec-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-handler/4.1.51.Final/netty-handler-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-codec-http/4.1.51.Final/netty-codec-http-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/alpn/alpn-api/1.1.3.v20160715/alpn-api-1.1.3.v20160715.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-kqueue/4.1.51.Final/netty-transport-native-kqueue-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-unix-common/4.1.51.Final/netty-transport-native-unix-common-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-epoll/4.1.51.Final/netty-transport-native-epoll-4.1.51.Final.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-gson/1.4.1/ktor-gson-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.32/slf4j-api-1.7.32.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/mozilla/rhino/1.7.14/rhino-1.7.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="intellij.remoterobot.remote.robot" type="repository">
<properties maven-id="com.intellij.remoterobot:remote-robot:0.11.14" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/intellij/remoterobot/remote-robot/0.11.14/remote-robot-0.11.14.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/assertj/assertj-swing-junit/3.17.1/assertj-swing-junit-3.17.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/assertj/assertj-swing/3.17.1/assertj-swing-3.17.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/assertj/assertj-core/3.17.2/assertj-core-3.17.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/easytesting/fest-util/1.2.5/fest-util-1.2.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/easytesting/fest-reflect/1.4.1/fest-reflect-1.4.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apache/logging/log4j/log4j-core/2.17.2/log4j-core-2.17.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/3.14.9/okhttp-3.14.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okio/okio/1.17.2/okio-1.17.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/retrofit2/converter-gson/2.9.0/converter-gson-2.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.5/gson-2.8.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/bouncycastle/bcprov-jdk15on/1.70/bcprov-jdk15on-1.70.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="intellij.remoterobot.remote.fixtures" type="repository">
<properties include-transitive-deps="false" maven-id="com.intellij.remoterobot:remote-fixtures:0.11.14" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/intellij/remoterobot/remote-fixtures/0.11.14/remote-fixtures-0.11.14.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="intellij.remoterobot.ide.launcher" type="repository">
<properties maven-id="com.intellij.remoterobot:ide-launcher:0.11.14">
<exclude>
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
<dependency maven-id="org.jetbrains:annotations" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/intellij/remoterobot/ide-launcher/0.11.14/ide-launcher-0.11.14.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/4.9.3/okhttp-4.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okio/okio/2.8.0/okio-2.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.9.0/gson-2.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>

View File

@@ -0,0 +1,16 @@
<idea-plugin package="com.intellij.uiTests">
<id>com.intellij.uiTests</id>
<name>UI Testing</name>
<vendor>JetBrains</vendor>
<category>Test Tools</category>
<description><![CDATA[
.............................................................................
Component UI testing via remote-robot
.............................................................................
]]></description>
<extensions defaultExtensionNs="com.intellij">
<applicationInitializedListener implementation="com.intellij.uiTests.robot.RobotServerListener"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,12 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting
import com.intellij.ui.components.JBLabel
import com.intellij.uiTests.componentTesting.canvas.ComponentToTest
import javax.swing.JComponent
internal class DemoComponentToTest: ComponentToTest {
override fun build(): JComponent {
return JBLabel("My test component")
}
}

View File

@@ -0,0 +1,15 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting
import com.intellij.ide.actions.ShowSettingsUtilImpl
import com.intellij.openapi.options.ConfigurableWithId
import com.intellij.openapi.options.ex.ConfigurableCardPanel
import com.intellij.uiTests.componentTesting.canvas.ComponentToTest
import javax.swing.JComponent
internal class SettingsComponentToTest(private val id: String): ComponentToTest {
override fun build(): JComponent {
val configurable = ShowSettingsUtilImpl.getConfigurables(null, true).filterIsInstance<ConfigurableWithId>().first { it.id == id }
return ConfigurableCardPanel.createConfigurableComponent(configurable)
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.canvas
import com.intellij.openapi.application.runInEdt
import javax.swing.JComponent
internal fun interface ComponentToTest {
fun build(): JComponent
}
internal object ComponentTesting {
private lateinit var frame: UiTestFrameWrapper
fun show(componentToTest: ComponentToTest) {
runInEdt {
frame = UiTestFrameWrapper()
frame.show(componentToTest)
}
}
fun close() {
runInEdt {
frame.close()
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.canvas
import java.awt.event.WindowEvent
import javax.swing.JFrame
internal class UiTestFrameWrapper {
private lateinit var frame : JFrame
fun show(component: ComponentToTest) {
val componentToAdd = component.build()
frame = JFrame().apply {
title = component::class.java.canonicalName
extendedState = JFrame.MAXIMIZED_BOTH;
add(componentToAdd)
isVisible = true
}
}
fun close() {
frame.dispatchEvent(WindowEvent(frame, WindowEvent.WINDOW_CLOSING))
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.settings
import com.intellij.ide.actions.ShowSettingsUtilImpl
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableWithId
internal object ConfigurableCollectorAction {
fun get(id: String): ConfigurableWithId {
return getAllConfigurables().first { it.id == id }
}
fun getAllConfigurableIds(): List<String> {
return getAllConfigurables().map { it.id }
}
fun getAllConfigurables(): List<ConfigurableWithId> {
val list = mutableListOf<ConfigurableWithId>()
ShowSettingsUtilImpl.getConfigurableGroups(null, true)[0]
.configurables.forEach {
collectConfigurables(list, it)
}
return list
}
private fun collectConfigurables(list: MutableList<ConfigurableWithId>, configurable: Configurable) {
if (configurable is ConfigurableWithId) {
list.add(configurable)
} else {
throw NotImplementedError("no id for ${configurable::class.java}")
}
if (configurable is Configurable.Composite) {
configurable.configurables.forEach { collectConfigurables(list, it) }
}
}
}

View File

@@ -0,0 +1,32 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e.fixtures
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
internal 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")
internal class DialogFixture(
remoteRobot: RemoteRobot,
remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) {
companion object {
@JvmStatic
fun byTitle(title: String) = byXpath("title $title", "//div[@title='$title' and @class='MyDialog']")
}
val title: String
get() = callJs("component.getTitle();")
}

View File

@@ -0,0 +1,57 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e.fixtures
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.*
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.waitFor
import java.time.Duration
internal fun RemoteRobot.idea(function: IdeaFrameFixture.() -> Unit) {
find<IdeaFrameFixture>(timeout = Duration.ofSeconds(10)).apply(function)
}
@FixtureName("Idea frame")
@DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']")
internal class IdeaFrameFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) {
val projectViewTree
get() = find<ContainerFixture>(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"))
val projectName
get() = step("Get project name") { return@step callJs<String>("component.getProject().getName()") }
val menuBar: JMenuBarFixture
get() = step("Menu...") {
return@step remoteRobot.find(JMenuBarFixture::class.java, JMenuBarFixture.byType())
}
@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 isDumbMode(): Boolean {
return callJs("""
const frameHelper = com.intellij.openapi.wm.impl.ProjectFrameHelper.getFrameHelper(component)
if (frameHelper) {
const project = frameHelper.getProject()
project ? com.intellij.openapi.project.DumbService.isDumb(project) : true
} else {
true
}
""", true)
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e.fixtures
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
internal fun RemoteRobot.welcomeFrame(function: WelcomeFrameFixture.() -> Unit) {
find(WelcomeFrameFixture::class.java, Duration.ofSeconds(10)).apply(function)
}
@FixtureName("Welcome Frame")
@DefaultXpath("type", "//div[@class='FlatWelcomeFrame']")
internal class WelcomeFrameFixture(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')]"))
val moreActions
get() = button(byXpath("More Action", "//div[@accessiblename='More Actions']"))
val heavyWeightPopup
get() = remoteRobot.find(ComponentFixture::class.java, byXpath("//div[@class='HeavyWeightWindow']"))
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e.steps
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.waitFor
import com.intellij.uiTests.e2e.fixtures.dialog
import com.intellij.uiTests.e2e.fixtures.idea
import com.intellij.uiTests.e2e.fixtures.welcomeFrame
import com.intellij.uiTests.robot.optional
import java.time.Duration
class CreateNewProjectSteps(private val robot: RemoteRobot) {
fun createCommandLineProject() = step("Create CommandLine Project") {
robot.welcomeFrame {
createNewProjectLink.click()
dialog("New Project") {
findText("Java").click()
checkBox("Add sample code").select()
button("Create").click()
}
}
robot.idea {
waitFor(Duration.ofMinutes(5)) { isDumbMode().not() }
}
tryCloseTipOfTheDay()
}
private fun tryCloseTipOfTheDay(): Unit = step("Try to close 'Tip of The Day'") {
robot.idea {
optional {
dialog("Tip of the Day", Duration.ofSeconds(5)) {
button("Close").click()
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.robot
import com.intellij.remoterobot.stepsProcessing.StepProcessor
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.Color
import com.intellij.remoterobot.utils.color
private const val OPTIONAL_STEP = "Optional step"
internal fun optional(action: () -> Unit) {
try {
step(OPTIONAL_STEP) {
action()
}
}
catch (ignore: Throwable) {
}
}
internal class StepPrinter : StepProcessor {
private var indent = ThreadLocal.withInitial { 0 }
private fun indents() = buildString {
repeat(indent.get()) { append(" ") }
}
override fun doBeforeStep(stepTitle: String) {
println(indents() + stepTitle)
indent.set(indent.get().plus(1))
}
override fun doOnSuccess(stepTitle: String) {
}
override fun doOnFail(stepTitle: String, e: Throwable) {
if (stepTitle == OPTIONAL_STEP) {
println("${indents()}$stepTitle".color(Color.BLUE))
} else {
println("${indents()}$stepTitle".color(Color.RED))
}
}
override fun doAfterStep(stepTitle: String) {
indent.set(indent.get().minus(1))
}
}

View File

@@ -0,0 +1,26 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.robot
import com.intellij.ide.ApplicationInitializedListener
import com.intellij.remoterobot.RobotServerImpl
import com.intellij.remoterobot.fixtures.dataExtractor.server.TextToKeyCache
import com.intellij.remoterobot.services.IdeRobot
import com.intellij.remoterobot.services.LambdaLoader
import com.intellij.remoterobot.services.js.RhinoJavaScriptExecutor
internal class RobotServerListener : ApplicationInitializedListener {
override fun componentsInitialized() {
TextToKeyCache.init(javaClass.classLoader)
RobotServerImpl(
serverHost = if (java.lang.Boolean.getBoolean("robot-server.host.public")) "0.0.0.0" else "127.0.0.1",
serverPort = Integer.getInteger("robot-server.port", 8580),
) {
IdeRobot(
textToKeyCache = TextToKeyCache,
jsExecutor = RhinoJavaScriptExecutor(),
lambdaLoader = LambdaLoader(),
)
}.startServer()
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.demo
import com.intellij.remoterobot.fixtures.JLabelFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.uiTests.componentTesting.DemoComponentToTest
import com.intellij.uiTests.componentTesting.wrapper.componentUiTest
import org.junit.Test
internal class DemoTest {
@Test
fun checkDemoComponent() = componentUiTest(DemoComponentToTest::class.java) {
val label = find<JLabelFixture>(byXpath("//div[@class='JBLabel']"))
assert(label.value == "My test component")
}
}

View File

@@ -0,0 +1,67 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.demo
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ComponentFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.uiTests.componentTesting.SettingsComponentToTest
import org.junit.Test
import java.nio.file.Path
class PaintSettingsTest {
// Example of collecting UI images
@Test
fun test() {
val remoteRobot = RemoteRobot("http://127.0.0.1:8580")
val list = remoteRobot.callJs<ArrayList<String>>("""
importClass(com.intellij.ide.actions.ShowSettingsUtilImpl)
const list = ShowSettingsUtilImpl.getConfigurables(null, true)
const result = new ArrayList()
for (var i = 0 ; i < list.length; i++) {
result.add(list[i].getId())
}
result
""")
list.forEach { id ->
remoteRobot.runJs("""
importPackage(com.intellij.uiTests.canvas)
ComponentTesting.INSTANCE.show(new ${SettingsComponentToTest::class.java.canonicalName}('$id'))
""".apply { println(this) })
try {
val frame = remoteRobot.find(CommonContainerFixture::class.java, byXpath("//div[@title='${SettingsComponentToTest::class.java.canonicalName}']"))
Thread.sleep(500)
frame.paintToFile(Path.of("${id.replace("/", "")}.png"))
} finally {
try {
remoteRobot.runJs("""
importPackage(com.intellij.uiTests.canvas)
ComponentTesting.INSTANCE.close()
""")
} catch (e: Throwable) {
println("Failed to close Frame")
}
}
}
}
}
fun ComponentFixture.paintToFile(path: Path) = callJs<ByteArray>(
"""
importPackage(java.io)
importPackage(javax.imageio)
importPackage(java.awt.image)
const screenShot = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB);
component.paint(screenShot.getGraphics())
let pictureBytes;
const baos = new ByteArrayOutputStream();
try {
ImageIO.write(screenShot, "png", baos);
pictureBytes = baos.toByteArray();
} finally {
baos.close();
}
pictureBytes;
""", true
).apply { path.toFile().writeBytes(this) }

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.componentTesting.wrapper
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.uiTests.componentTesting.canvas.ComponentToTest
internal fun <T : Class<out ComponentToTest>> componentUiTest(componentClass: T, test: CommonContainerFixture.() -> Unit) {
val componentCanonicalName = componentClass.canonicalName
with(RemoteRobot("http://127.0.0.1:8580")) {
showComponent(componentCanonicalName)
val frame = find(CommonContainerFixture::class.java, byXpath("//div[@title='$componentCanonicalName']"))
try {
frame.test()
}
finally {
close()
}
}
}
private fun RemoteRobot.showComponent(componentCanonicalName: String) = runJs("""
importPackage(com.intellij.uiTests.canvas)
ComponentTesting.INSTANCE.show(new ${componentCanonicalName}())
""")
private fun RemoteRobot.close() = runJs("""
importPackage(com.intellij.uiTests.canvas)
ComponentTesting.INSTANCE.close()
""")

View File

@@ -0,0 +1,42 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.ContainerFixture
import com.intellij.remoterobot.search.locators.byXpath
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.waitFor
import com.intellij.uiTests.e2e.fixtures.idea
import org.junit.Test
import java.time.Duration
import java.time.Duration.ofSeconds
internal class CreateNewProjectTest : UITest() {
@Test
fun createNewProject() {
newProjectSteps.createCommandLineProject()
robot.idea {
step("Launch application") {
textEditor().apply {
waitFor(ofSeconds(20)) { button(byXpath("//div[@class='TrafficLightButton']")).hasText("Analyzing...").not() }
menuBar.select("Build", "Build Project")
waitFor { gutter.getIcons().isNotEmpty() }
gutter.getIcons().first { it.description.contains("run.svg") }.click()
}
step("Run from gutter") {
find<CommonContainerFixture>(byXpath("//div[@class='HeavyWeightWindow']"), ofSeconds(4))
.button(byXpath("//div[@disabledicon='execute.svg']"))
.click()
}
}
}
val consoleLocator = byXpath("ConsoleViewImpl", "//div[@class='ConsoleViewImpl']")
step("Wait for Console appears") {
waitFor(Duration.ofMinutes(1)) { robot.findAll<ContainerFixture>(consoleLocator).isNotEmpty() }
}
step("Check the message") {
waitFor(Duration.ofMinutes(1)) { robot.find<ContainerFixture>(consoleLocator).hasText("Hello world!") }
}
}
}

View File

@@ -0,0 +1,68 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.uiTests.e2e
import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.stepsProcessing.StepWorker
import com.intellij.remoterobot.stepsProcessing.step
import com.intellij.remoterobot.utils.keyboard
import com.intellij.remoterobot.utils.waitFor
import com.intellij.uiTests.e2e.fixtures.idea
import com.intellij.uiTests.e2e.steps.CreateNewProjectSteps
import com.intellij.uiTests.robot.StepPrinter
import org.junit.After
import org.junit.BeforeClass
import java.awt.event.KeyEvent
import java.net.ConnectException
import java.time.Duration
internal abstract class UITest {
companion object {
@JvmStatic
protected val robot = RemoteRobot("http://127.0.0.1:8580").apply {
StepWorker.registerProcessor(StepPrinter())
}
@JvmStatic
protected val newProjectSteps = CreateNewProjectSteps(robot)
@JvmStatic
@BeforeClass
fun before() {
step("Wait for ide started") {
waitFor(Duration.ofSeconds(10), errorMessage = "Ide is not started") {
try {
val ideaVersion = getIdeaVersion()
println(ideaVersion)
true
}
catch (e: ConnectException) {
false
}
}
}
}
private fun getIdeaVersion(): String {
return robot.callJs<String>("""
importPackage(com.intellij.openapi.application)
const info = ApplicationInfo.getInstance()
info.getFullVersion() + ': ' + info.getBuild()
""")
}
}
@After
fun closeProject() {
robot.idea {
if (robot.isMac()) {
keyboard {
hotKey(KeyEvent.VK_SHIFT, KeyEvent.VK_META, KeyEvent.VK_A)
enterText("Close Project")
enter()
}
} else {
menuBar.select("File", "Close Project")
}
}
}
}