Skip to main content

driver-ui-tests

Write IntelliJ UI tests with IDE Starter or UI Driver and TestOps cases.

Datos de origen

Repositorio
JetBrains/intellij-community
Última actividad en el origen
5 de agosto de 2026 a las 18:20
Idioma detectado de SKILL.md
inglés
Estrellas
20.445
Forks
6009

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
driver-ui-tests
description
Write IntelliJ UI tests with IDE Starter or UI Driver and TestOps cases.
<!-- Generated by community/.ai/render-guides.mjs; edit community/.agents/skills/driver-ui-tests/SKILL.md --> # Driver UI Tests Guide Guidelines for writing UI tests using IDE Starter and UI Driver frameworks. ## Common Imports ```kotlin // Driver core import com.intellij.driver.client.Driver import com.intellij.driver.sdk.waitForProjectOpen import com.intellij.driver.sdk.advancedSettings // Test utilities import com.intellij.driver.tests.utils.waitForIndicators import com.intellij.driver.tests.utils.Plugin import com.intellij.driver.tests.utils.PluginInstaller import com.intellij.driver.tests.utils.Plugins // IDE Starter framework import com.intellij.ide.starter.driver.runIdeTest import com.intellij.ide.starter.ide.IDETestContext import com.intellij.ide.starter.models.IDEStartResult import com.intellij.ide.starter.models.VMOptions import com.intellij.ide.starter.runner.IDERunContext import com.intellij.ide.starter.runner.Starter import com.intellij.ide.starter.utils.catchAll // Extended test infrastructure import com.intellij.ide.starter.extended.allure.AllureHelperExtended.step import com.intellij.ide.starter.extended.allure.Subsystems import com.intellij.ide.starter.extended.engine.newTestContainerExtended import com.intellij.ide.starter.extended.engine.TestContainerExtended import com.intellij.ide.starter.extended.license.StagingLicenseGenerator.licenseProductCode import com.intellij.ide.starter.extended.loadMetadataFromServer import com.intellij.ide.starter.extended.setupTestMetadataSchemeWithGroupsFromCode // Test framework import com.intellij.testFramework.TestApplicationManager ``` ## Test Structure - Tests use JUnit 5 with an IDE Starter framework and UI Driver framework (`community/platform/remote-driver`) - Test case projects are represented by the `com.intellij.ide.starter.models.TestCase` see `src/com/intellij/ide/starter/project` - Tests run against specific IDE (`community/tools/intellij.tools.ide.starter/src/com/intellij/ide/starter/ide/IdeProductProvider.kt`) ## Test project examples See `tests/intellij.ide.starter.extended/src/com/intellij/ide/starter/extended/data/cases` ## Page Object Pattern or UiComponent Page objects extend `UiComponent` with `ComponentData` constructor: ```kotlin class MyPageObject(data: ComponentData) : UiComponent(data) { val myButton = x { byAccessibleName("Button Name") } val myPanel = x { byClass("PanelClassName") } fun clickMyButton() { myButton.click() } } // Extension function on Finder to create the page object fun Finder.myPageObject(): MyPageObject = x( xQuery { byAccessibleName("Root Element Name") }, MyPageObject::class.java ) // Use specific ui component to specify the context of the search fun AnotherPageObject.myPageObject(): MyPageObject = x( xQuery { byAccessibleName("Root Element Name") }, MyPageObject::class.java ) ``` ## Element Selectors | Selector | Usage | |----------|-------| | `byAccessibleName("name")` | Find by accessible name attribute | | `byClass("ClassName")` | Find by Swing/AWT class name | | `byVisibleText("text")` | Find by visible text content | ## UI test examples See directory `tests/remote-driver-tests` ## Common UI Components See directory `community/platform/remote-driver/test-sdk/src/com/intellij/driver/sdk/ui` ## Scoping Element Searches When multiple elements match a selector, scope to a parent element: ```kotlin // BAD - will fail if multiple InstallButtons exist ui.x { byClass("InstallButton") }.click() // GOOD - scope to a parent element first 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 element.keyboard { typeText("search text") } ui.keyboard { key(KeyEvent.VK_ENTER) } ui.keyboard { hotKey(KeyEvent.VK_META, KeyEvent.VK_COMMA) } // Cmd+, ``` ## Writing Tests ### Required Annotations Every UI test **must** have the following annotations at the class level: | Annotation | Purpose | TestOps Custom Field | |------------|---------|---------------------| | `@Subsystems.*` | Categorizes the test by subsystem | `Subsystem` | | `@Features.*` | Specifies the feature being tested | `Feature` | | `@Components.*` | Identifies the component under test | `Component` | **For tests linked to TestOps test cases**, also add: - `@AllureId("test_case_id")` - Links the test to the TestOps test case ID The annotation values should match the corresponding TestOps custom fields (Subsystem, Feature, Component). **Available annotations:** See `tests/intellij.ide.starter.extended.allure/src/com/intellij/ide/starter/extended/allure/Annotations.kt` **Example with TestOps test case:** ```kotlin @Subsystems.Java @Features.Completion @Components.Editor class MyTestFromTestOps { @Test @AllureId("318541") // Required when test case exists in TestOps fun `my test from testops`(testInfo: TestInfo) { // ... } } ``` **Example for new test (not yet in TestOps):** ```kotlin @Subsystems.UI @Features.PluginManager @Components.Miscellaneous class MyNewTest { @Test fun `my new test`(testInfo: TestInfo) { // ... } } ``` ### Basic Test Structure ```kotlin @Subsystems.Java @Features.Completion @Components.Editor class MyTest { val testCase = TestCase(IdeProductProvider.IU, myProject) @Test @AllureId("123456") // Required if test case exists in TestOps fun `my test name`(testInfo: TestInfo) { val context = Starter.newContext(testName = "TestName", testCase = testCase) context.applyVMOptionsPatch { addSystemProperty("ide.ui.non.modal.settings.window", "true") } context.runIdeTest(testName = testInfo.displayName) { waitForIndicators(5.minutes) // Wait for indexing to complete step("Step description") { // Test actions here } } } } ``` ### Waiting for Project Import and Indexing Always wait for indicators at the start of your test: ```kotlin waitForIndicators() ``` This ensures the project is fully imported and indexed before interacting with the IDE. ### Opening Files Use `openFile` instead of UI-based file navigation: ```kotlin // GOOD - Direct and reliable openFile(relativePath = "src/Main.java") // AVOID - UI-based approach is slower and more fragile invokeAction("GotoFile", now = false) ui.keyboard { typeText("Main.java") } ui.keyboard { key(KeyEvent.VK_ENTER) } ``` ### invokeAction: `now` Parameter The `now` parameter controls whether the action completes before continuing: ```kotlin // now = true: Waits for action to complete (use when keyboard input follows) invokeAction("ToggleBookmarkWithMnemonic", now = true) ui.keyboard { key(KeyEvent.VK_1) } // This input goes to the bookmark dialog // now = false: Returns immediately (use when waiting for UI to appear) invokeAction("ShowSettings", now = false) ui.x { byClass("SettingsDialog") }.shouldBe { present() } ``` **Rule**: Use `now = true` when the next step is keyboard input to prevent input going to the wrong component. **Rule**: Use `now = false` when you expect to the UI dialog to appear. ### Custom Wait Conditions Use `waitFor` to wait for specific conditions: ```kotlin waitFor("description of what we're waiting for", 30.seconds) { ui.x { byClass("MyComponent") }.present() } waitFor("text to appear", 10.seconds) { ui.x { byClass("Tree") }.hasText("expected text") } ``` ## 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: ### Option 1: Using tests.cmd (Recommended) The `tests.cmd` script builds the IDE from sources and runs tests. **Recommended for dev server mode.** Example: ```bash ./tests.cmd \ --module intellij.driver.tests \ --test com.intellij.driver.tests.idea.java.FindAndGoToTest ``` **Key parameters:** - `--test` - fully qualified test class name (or pattern) - `--module intellij.driver.tests` - **required** for driver tests **Example with specific test:** ```bash ./tests.cmd \ --module intellij.driver.tests \ --test com.intellij.driver.tests.idea.ultimate.httpclient.BuiltInHttpClientBrotliCompressionUiTest ``` ## Debugging Test Failures ### Output Locations After test failure, check: - UI hierarchy: `out/ide-tests/tests/{IDE-version}/{TestName}/{test-method}/log/ui-hierarchy/ui.html` - IDE log: `out/ide-tests/tests/{IDE-version}/{TestName}/{test-method}/log/idea.log` - Screenshots: `out/ide-tests/tests/{IDE-version}/{TestName}/{test-method}/log/screenshots/` - Exceptions: `out/ide-tests/tests/{IDE-version}/{TestName}/{test-method}/error/` ### Inspect the LIVE UI hierarchy, not just the post-mortem file The `ui-hierarchy/ui.html` and `full-screen.png` written on failure are captured **after** `useDriverAndCloseIde` tears the IDE down — by then the session has ended and panels often revert to an empty/welcome state, so they can be misleading. Two better sources: - **Heartbeat screenshot** `log/screenshots/001_heartbeat/` — captured mid-run, shows the real state during the wait. - **Live UI hierarchy server** — while the IDE is up, the component tree is browsable at `http://localhost:<port>/api/remote-driver/` (the harness sets `-Dexpose.ui.hierarchy.url=true`; the port is logged at startup as `UI Hierarchy: http://localhost:<port>/api/remote-driver/`). To inspect interactively, **park the test** at the point of interest — temporarily raise a `waitFor` timeout (e.g. to `20.minutes`) — and `curl`/open that URL while the IDE stays alive. Each node exposes `class` (simple), `javaclass` (FQN, incl. `Outer$Inner` for inner classes), `visible_text`, and `accessiblename`; read these to build a reliable matcher instead of guessing from source. ### Common Issues 1. **Element Not Found**: Check UI hierarchy HTML for the correct accessible name or class 2. **Multiple Elements Match**: Scope search to parent element ## Critical Rules - **Never use `Thread.sleep()`** or `delay()` - Driver framework automatically waits for UI elements - **Wrap test logic in `step("description") { }`** for better logs - **Verify assertions actually fail** – Comment out the action being tested and confirm the test fails. If it still passes, your assertion is too weak. - **Use common UI components** – create new if necessary - **Always check UI hierarchy** to understand the UI state when a test fails
Ver en GitHub