WEB-78041 document remote-driver UI-test gotchas in driver-ui-tests skill

Captures lessons from building the Figma connection UI test that the skill did not cover: reading IDE-side state via @Remote (the plugin= classloader field for content-module classes and the declared-class method-dispatch pitfall), matching text-bearing toolbar/title actions that render as ActionButtonWithText rather than ActionButton, seeding a registry flag at startup via a -D system property, and driving a real browser with Playwright from the test JVM.

Edited the community source and re-rendered the generated skill copies.

(cherry picked from commit b3fd5d34005c7f53b15a72fa22d1de4318bed0c2)

GitOrigin-RevId: b4407dc0dc580ada86e64fd9a99296432c0b42ac
This commit is contained in:
Ilya Muradyan
2026-07-07 18:42:07 +00:00
committed by intellij-monorepo-bot
parent f2c673e845
commit 3096a84009
2 changed files with 88 additions and 0 deletions
+44
View File
@@ -108,6 +108,18 @@ val detailPane = ui.x { byClass("PluginDetailsPageComponent") }
detailPane.x { byClass("InstallButton") }.click()
```
## Finding toolbar / title-bar actions
Toolbar and tool-window title actions that show their text (`presentation.putClientProperty(ActionUtil.SHOW_TEXT_IN_TOOLBAR, true)`) render as **`ActionButtonWithText`**, not `ActionButton`. The SDK `actionButton(text)` helper searches `@class='ActionButton'` only, so it silently never matches them. Match by visible text across both variants:
```kotlin
// Matches both icon-only and text-bearing action buttons
fun Finder.statusButton(text: String) =
x("//div[(@class='ActionButtonWithText' or @class='ActionButton') and @visible_text='$text']")
```
The visible text is itself a reliable assertion signal — you usually do not need to read the backing service/state.
## Keyboard Interactions
```kotlin
@@ -250,6 +262,38 @@ waitFor("text to appear", 10.seconds) {
}
```
## Reading IDE state via `@Remote`
To read state from a service or model in the IDE under test, declare a `@Remote` interface and call it via `driver.service(...)` / `driver.utility(...)`.
- **Plugin classes need the `plugin` field.** Without it the class resolves against the platform/core classloader → `DriverIllegalStateException: No such class '<fqn>' in plugin null`.
- Class in a plugin **content module**: `@Remote("<fqn>", plugin = "<plugin.id>/<content.module>")` (e.g. `com.intellij.figma/intellij.figma.core`).
- Class in the **main / embedded** plugin module: `@Remote("<fqn>", plugin = "<plugin.id>")`.
- **Method dispatch resolves against the DECLARED `@Remote` class, not the runtime object.** A method declared on a sealed/abstract supertype ref is "not found" at runtime — declare it on the concrete subtype, or expose it via a top-level type. (The `jvm-class-name` injection also cannot resolve a nested `Foo$Bar` name → a cosmetic "Cannot resolve class" inspection error; prefer top-level types.)
- Add the plugin module as a TEST dependency so the FQNs resolve for code-insight.
```kotlin
@Remote("com.example.MyAppService", plugin = "com.example.myplugin/com.example.myplugin.core")
interface MyAppServiceRef {
fun getConfig(): MyConfigRef
}
// driver.service(MyAppServiceRef::class).getConfig()...
```
## Enabling a registry flag at startup
Seed a registry key before the IDE starts with a `-D` VM option — `RegistryValue` falls back to `System.getProperty`. Required when a startup `ProjectActivity` or `ToolWindowFactory.shouldBeAvailable` reads the flag (setting it via the driver after start is too late):
```kotlin
context.applyVMOptionsPatch {
addSystemProperty("my.feature.enabled", "true")
}
```
## Driving a real browser (Playwright)
Playwright runs in the **test JVM**, alongside the driver-driven IDE (both on localhost) — useful when the IDE's client is a web app/plugin. `page.onConsoleMessage { ... }` captures the page **and its iframes** (a strong diagnostic). Put custom screenshots/files under `context.paths.testHome.resolve("log")` so they are collected as test artifacts. See `plugins/figma/integrationTests` for a full example.
## Running Tests from Terminal
Driver tests require a fully built IDE. There are several ways to run them:
+44
View File
@@ -109,6 +109,18 @@ val detailPane = ui.x { byClass("PluginDetailsPageComponent") }
detailPane.x { byClass("InstallButton") }.click()
```
## Finding toolbar / title-bar actions
Toolbar and tool-window title actions that show their text (`presentation.putClientProperty(ActionUtil.SHOW_TEXT_IN_TOOLBAR, true)`) render as **`ActionButtonWithText`**, not `ActionButton`. The SDK `actionButton(text)` helper searches `@class='ActionButton'` only, so it silently never matches them. Match by visible text across both variants:
```kotlin
// Matches both icon-only and text-bearing action buttons
fun Finder.statusButton(text: String) =
x("//div[(@class='ActionButtonWithText' or @class='ActionButton') and @visible_text='$text']")
```
The visible text is itself a reliable assertion signal — you usually do not need to read the backing service/state.
## Keyboard Interactions
```kotlin
@@ -251,6 +263,38 @@ waitFor("text to appear", 10.seconds) {
}
```
## Reading IDE state via `@Remote`
To read state from a service or model in the IDE under test, declare a `@Remote` interface and call it via `driver.service(...)` / `driver.utility(...)`.
- **Plugin classes need the `plugin` field.** Without it the class resolves against the platform/core classloader → `DriverIllegalStateException: No such class '<fqn>' in plugin null`.
- Class in a plugin **content module**: `@Remote("<fqn>", plugin = "<plugin.id>/<content.module>")` (e.g. `com.intellij.figma/intellij.figma.core`).
- Class in the **main / embedded** plugin module: `@Remote("<fqn>", plugin = "<plugin.id>")`.
- **Method dispatch resolves against the DECLARED `@Remote` class, not the runtime object.** A method declared on a sealed/abstract supertype ref is "not found" at runtime — declare it on the concrete subtype, or expose it via a top-level type. (The `jvm-class-name` injection also cannot resolve a nested `Foo$Bar` name → a cosmetic "Cannot resolve class" inspection error; prefer top-level types.)
- Add the plugin module as a TEST dependency so the FQNs resolve for code-insight.
```kotlin
@Remote("com.example.MyAppService", plugin = "com.example.myplugin/com.example.myplugin.core")
interface MyAppServiceRef {
fun getConfig(): MyConfigRef
}
// driver.service(MyAppServiceRef::class).getConfig()...
```
## Enabling a registry flag at startup
Seed a registry key before the IDE starts with a `-D` VM option — `RegistryValue` falls back to `System.getProperty`. Required when a startup `ProjectActivity` or `ToolWindowFactory.shouldBeAvailable` reads the flag (setting it via the driver after start is too late):
```kotlin
context.applyVMOptionsPatch {
addSystemProperty("my.feature.enabled", "true")
}
```
## Driving a real browser (Playwright)
Playwright runs in the **test JVM**, alongside the driver-driven IDE (both on localhost) — useful when the IDE's client is a web app/plugin. `page.onConsoleMessage { ... }` captures the page **and its iframes** (a strong diagnostic). Put custom screenshots/files under `context.paths.testHome.resolve("log")` so they are collected as test artifacts. See `plugins/figma/integrationTests` for a full example.
## Running Tests from Terminal
Driver tests require a fully built IDE. There are several ways to run them: