Skip to content

Commit

Permalink
chore: add UI test to toggle telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
kmruiz committed Jun 12, 2024
1 parent b37f0e0 commit d9196d2
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.mongodb.jbplugin.settings

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
import java.io.Serializable

/**
* The state component represents the persisting state. Don't use directly, this is only necessary
Expand All @@ -27,7 +28,7 @@ class PluginSettingsStateComponent : SimplePersistentStateComponent<PluginSettin
*
* @see useSettings
*/
class PluginSettings : BaseState() {
class PluginSettings : BaseState(), Serializable {
var isTelemetryEnabled by property(true)
var hasTelemetryOptOutputNotificationBeenShown by property(false)
}
Expand All @@ -39,6 +40,7 @@ class PluginSettings : BaseState() {
*
* @return
*/
fun useSettings(): PluginSettings = ApplicationManager.getApplication().getService(
PluginSettingsStateComponent::class.java
).state
fun useSettings(): PluginSettings =
ApplicationManager.getApplication().getService(
PluginSettingsStateComponent::class.java,
).state
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ class PluginSettingsConfigurable : Configurable {
}

override fun apply() {
val savedSettings = useSettings().apply {
isTelemetryEnabled = settingsComponent.isTelemetryEnabledCheckBox.isSelected
}
val savedSettings =
useSettings().apply {
isTelemetryEnabled = settingsComponent.isTelemetryEnabledCheckBox.isSelected
}
}

override fun reset() {
Expand All @@ -57,5 +58,8 @@ private class PluginSettingsComponent {
.addTooltip(TelemetryMessages.message("settings.telemetry-collection-tooltip"))
.addComponentFillVertically(JPanel(), 0)
.panel

root.accessibleContext.accessibleName = "MongoDB Settings"
isTelemetryEnabledCheckBox.accessibleContext.accessibleName = "MongoDB Enable Telemetry"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.mongodb.jbplugin.fixtures

import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.fixtures.Fixture
import com.intellij.remoterobot.search.locators.Locator
import com.intellij.remoterobot.utils.waitFor
import java.time.Duration

Expand All @@ -16,15 +17,41 @@ import java.time.Duration
* @param timeout
* @return
*/
inline fun <reified T : Fixture> RemoteRobot.findVisible(timeout: Duration = Duration.ofMinutes(1)) = run {
inline fun <reified T : Fixture> RemoteRobot.findVisible(timeout: Duration = Duration.ofMinutes(1)) =
run {
waitFor(
timeout,
Duration.ofMillis(100),
errorMessage = "Could not find component of class ${T::class.java.canonicalName}",
) {
runCatching {
find(T::class.java, Duration.ofMillis(100)).callJs<Boolean>("true")
}.getOrDefault(false)
}

find(T::class.java)
}

/**
* Returns a fixture by the locator.
*
* @param timeout
* @param locator
* @return
*/
inline fun <reified T : Fixture> RemoteRobot.findVisible(
locator: Locator,
timeout: Duration = Duration.ofMinutes(1),
) = run {
waitFor(
timeout, Duration.ofMillis(100),
errorMessage = "Could not find component of class ${T::class.java.canonicalName}"
timeout,
Duration.ofMillis(100),
errorMessage = "Could not find component of class ${T::class.java.canonicalName}",
) {
runCatching {
find(T::class.java, Duration.ofMillis(100)).callJs<Boolean>("true")
find(T::class.java, locator, Duration.ofMillis(100)).callJs<Boolean>("true")
}.getOrDefault(false)
}

find(T::class.java)
}
find(T::class.java, locator)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,32 +45,61 @@ annotation class RequiresProject(val value: String)
*
* @see UiTest
*/
private class UiTestExtension : BeforeAllCallback,
private class UiTestExtension :
BeforeAllCallback,
BeforeTestExecutionCallback,
AfterTestExecutionCallback,
ParameterResolver {
private val remoteRobotUrl: String = "http://localhost:8082"
private lateinit var remoteRobot: RemoteRobot

override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean =
parameterContext?.parameter?.type?.equals(RemoteRobot::class.java) ?: false
override fun supportsParameter(
parameterContext: ParameterContext?,
extensionContext: ExtensionContext?,
): Boolean = parameterContext?.parameter?.type?.equals(RemoteRobot::class.java) ?: false

override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any =
remoteRobot
override fun resolveParameter(
parameterContext: ParameterContext?,
extensionContext: ExtensionContext?,
): Any = remoteRobot

override fun beforeAll(context: ExtensionContext?) {
remoteRobot = RemoteRobot(remoteRobotUrl)
}

override fun beforeTestExecution(context: ExtensionContext?) {
val requiresProject = context?.requiredTestMethod?.annotations
?.find { annotation -> annotation.annotationClass == RequiresProject::class } as RequiresProject?
val requiresProject =
context?.requiredTestMethod?.annotations
?.find { annotation -> annotation.annotationClass == RequiresProject::class } as RequiresProject?

CommonSteps(remoteRobot).closeProject()

remoteRobot.runJs(
"""
importClass(com.intellij.openapi.application.ApplicationManager)
importClass(com.intellij.ide.plugins.PluginManager)
importClass(com.intellij.openapi.extensions.PluginId)
global.put('loadPlugin', function () {
const pluginManager = PluginManager.getInstance();
const pluginID = PluginId.findId("com.mongodb.jbplugin");
return pluginManager.findEnabledPlugin(pluginID);
});
global.put('loadPluginClass', function (className) {
return global.get('loadPlugin')().getPluginClassLoader().loadClass(className);
});
global.put('loadPluginService', function (className) {
return ApplicationManager.getApplication().getService(global.get("loadPluginClass")(className));
});
""".trimIndent(),
)

requiresProject?.let {
// If we have the @RequireProject annotation, load that project on startup
CommonSteps(remoteRobot).openProject(
Path("src/test/resources/project-fixtures/${requiresProject.value}").toAbsolutePath().toString()
Path("src/test/resources/project-fixtures/${requiresProject.value}").toAbsolutePath().toString(),
)
}
}
Expand Down Expand Up @@ -101,7 +130,7 @@ private class UiTestExtension : BeforeAllCallback,

private fun String.saveFile(
url: String,
name: String
name: String,
): File {
val response = client.newCall(Request.Builder().url(url).build()).execute()
return File(this).apply {
Expand All @@ -112,20 +141,22 @@ private class UiTestExtension : BeforeAllCallback,
}

private fun BufferedImage.save(name: String) {
val bytes = ByteArrayOutputStream().use { bos ->
ImageIO.write(this, "png", bos)
bos.toByteArray()
}
val bytes =
ByteArrayOutputStream().use { bos ->
ImageIO.write(this, "png", bos)
bos.toByteArray()
}

File("build/reports").apply { mkdirs() }.resolve("$name.png").writeBytes(bytes)
}

private fun saveIdeaFrames(testName: String) {
remoteRobot.findAll<ContainerFixture>(byXpath("//div[@class='IdeFrameImpl']"))
.forEachIndexed { index, frame ->
val pic = try {
frame.callJs<ByteArray>(
"""
val pic =
try {
frame.callJs<ByteArray>(
"""
importPackage(java.io)
importPackage(javax.imageio)
importPackage(java.awt.image)
Expand All @@ -144,20 +175,22 @@ private class UiTestExtension : BeforeAllCallback,
baos.close();
}
pictureBytes;
""", true
)
} catch (e: Throwable) {
e.printStackTrace()
throw e
}
""",
true,
)
} catch (e: Throwable) {
e.printStackTrace()
throw e
}
pic.inputStream().use {
ImageIO.read(it)
}.save(testName + "_" + index)
}
}

private fun fetchScreenShot(): BufferedImage = remoteRobot.callJs<ByteArray>(
"""
private fun fetchScreenShot(): BufferedImage =
remoteRobot.callJs<ByteArray>(
"""
importPackage(java.io)
importPackage(javax.imageio)
const screenShot = new java.awt.Robot().createScreenCapture(
Expand All @@ -172,17 +205,17 @@ private class UiTestExtension : BeforeAllCallback,
baos.close();
}
pictureBytes;
"""
)
.inputStream()
.use {
ImageIO.read(it)
}
""",
)
.inputStream()
.use {
ImageIO.read(it)
}
}

/**
* Removes all html tags and only keeps the plain text.
*
* @return
*/
fun String.stripHtml(): String = Jsoup.clean(this, Safelist.none())
fun String.stripHtml(): String = Jsoup.clean(this, Safelist.none())
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Fixture that represents the plugin settings page. If you add new settings,
* you should add new properties here.
*
* For usage in tests that work with settings, you can use openSettings, that will open the UI, or
* remoteRobot.useSetting(name), that will give you the value for that specific setting.
*/

package com.mongodb.jbplugin.fixtures.components

import com.intellij.remoterobot.RemoteRobot
import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.*
import com.mongodb.jbplugin.fixtures.findVisible

/**
* Component that represents the settings page.
*
* @param remoteRobot
* @param remoteComponent
*/
@DefaultXpath(by = "accessible name", xpath = "//div[@accessiblename='MongoDB Settings']")
@FixtureName("MongoDBSettings")
class MongoDbSettingsFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : ContainerFixture(remoteRobot,
remoteComponent) {
val enableTelemetry by lazy {
findAll<JCheckboxFixture>().find { it.text == "Enable telemetry" } ?: throw NoSuchElementException()
}
val ok by lazy {
remoteRobot.findAll<JButtonFixture>().find { it.text == "OK" } ?: throw NoSuchElementException()
}
}

/**
* Opens the settings dialog and returns a fixture, so it can be interacted.
*
* @return
*/
fun RemoteRobot.openSettings(): MongoDbSettingsFixture {
this.runJs(
"""
importClass(com.intellij.openapi.application.ApplicationManager)
const runAction = new Runnable({
run: function() {
com.intellij.openapi.options.ShowSettingsUtil.getInstance().showSettingsDialog(
null,
"MongoDB",
)
}
})
ApplicationManager.getApplication().invokeLater(runAction)
""".trimIndent(),
)

return findVisible()
}

/**
* Returns a specific setting value from the plugin settings.
*
* @see com.mongodb.jbplugin.settings.PluginSettings for the possible values.
*
* @param name
* @return
*/
inline fun <reified T> RemoteRobot.useSetting(name: String): T = callJs(
"""
global.get('loadPluginService')(
'com.mongodb.jbplugin.settings.PluginSettingsStateComponent'
).getState().$name()
""".trimIndent(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mongodb.jbplugin.settings

import com.intellij.remoterobot.RemoteRobot
import com.mongodb.jbplugin.fixtures.RequiresProject
import com.mongodb.jbplugin.fixtures.UiTest
import com.mongodb.jbplugin.fixtures.components.openSettings
import com.mongodb.jbplugin.fixtures.components.useSetting
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Test

@UiTest
class SettingsUiTest {
@Test
@RequiresProject("basic-java-project-with-mongodb")
fun `allows toggling the telemetry`(remoteRobot: RemoteRobot) {
val telemetryBeforeTest = remoteRobot.useSetting<Boolean>("isTelemetryEnabled")

val settings = remoteRobot.openSettings()
settings.enableTelemetry.click()
settings.ok.click()

val telemetryAfterTest = remoteRobot.useSetting<Boolean>("isTelemetryEnabled")
assertNotEquals(telemetryBeforeTest, telemetryAfterTest)
}
}

0 comments on commit d9196d2

Please sign in to comment.