Skip to content

Commit 3514768

Browse files
authored
Add deep links tool window integration test (#8855)
This opens a project and checks that the deep links panel can be opened (since it does not require a running app). I'm able to run on mac with `./gradlew integration --tests "io.flutter.integrationTest.DeepLinksToolWindowTest"`. Sometimes I've gotten weird errors when I don't run `./gradlew clean` first, but not always. Part of #7959
1 parent 86d28b0 commit 3514768

3 files changed

Lines changed: 144 additions & 3 deletions

File tree

.gemini/styleguide.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,8 @@ enforce standard modern Java/Kotlin coding conventions, but strictly police the
3030
as recommended by the modern SDK.
3131
- **Backward Compatibility:** Avoid using `@ApiStatus.Internal` or `@ApiStatus.ScheduledForRemoval` APIs unless strictly necessary.
3232
- **Logging:**
33-
- Reject any use of `System.out.println` or `System.err.println` for logging.
34-
- Enforce the use of the IntelliJ SDK's built-in logger: `com.intellij.openapi.diagnostic.Logger` or our own: `
35-
io.flutter.logging.PluginLogger.
33+
- Reject any use of `System.out.println` or `System.err.println` for logging in `src/` code (integration tests may use them for milestone logging).
34+
- Enforce the use of the IntelliJ SDK's built-in logger (`com.intellij.openapi.diagnostic.Logger`) or our own (`io.flutter.logging.PluginLogger`).
3635
- **Actions:**
3736
- Classes extending `AnAction` must be completely stateless. Flag any `AnAction` class that defines mutable instance variables (fields),
3837
as the platform instantiates a single instance of the action for the lifetime of the IDE.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2026 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
7+
package io.flutter.integrationTest
8+
9+
import com.intellij.driver.sdk.invokeAction
10+
import com.intellij.driver.sdk.ui.components.UiComponent.Companion.waitFound
11+
import com.intellij.driver.sdk.ui.components.common.ideFrame
12+
import com.intellij.driver.sdk.ui.components.common.toolWindow
13+
import com.intellij.driver.sdk.ui.components.common.toolwindows.projectView
14+
import com.intellij.driver.sdk.waitForIndicators
15+
import com.intellij.ide.starter.driver.engine.BackgroundRun
16+
import com.intellij.ide.starter.driver.engine.runIdeWithDriver
17+
import com.intellij.ide.starter.junit5.config.UseLatestDownloadedIdeBuild
18+
import com.intellij.ide.starter.project.LocalProjectInfo
19+
import org.junit.jupiter.api.AfterAll
20+
import org.junit.jupiter.api.AfterEach
21+
import org.junit.jupiter.api.BeforeAll
22+
import org.junit.jupiter.api.Tag
23+
import org.junit.jupiter.api.Test
24+
import org.junit.jupiter.api.TestInstance
25+
import org.junit.jupiter.api.extension.ExtendWith
26+
import java.nio.file.Paths
27+
import kotlin.time.Duration.Companion.minutes
28+
import io.flutter.integrationTest.utils.createFlutterProjectWithCli
29+
import io.flutter.integrationTest.utils.deleteFlutterProject
30+
31+
@Tag("ui")
32+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
33+
@ExtendWith(UseLatestDownloadedIdeBuild::class)
34+
class DeepLinksToolWindowTest {
35+
36+
private val testProjectName = "my_deep_links_test_project_${System.currentTimeMillis()}"
37+
private lateinit var run: BackgroundRun
38+
39+
@BeforeAll
40+
fun setup() {
41+
createFlutterProjectWithCli(testProjectName, projectName = "deep_links_test")
42+
}
43+
44+
@AfterEach
45+
fun closeIde() {
46+
if (::run.isInitialized) {
47+
println("Closing IDE")
48+
run.closeIdeAndWait()
49+
} else {
50+
println("IDE was not started, skipping close")
51+
}
52+
}
53+
54+
@AfterAll
55+
fun teardown() {
56+
deleteFlutterProject(testProjectName)
57+
}
58+
59+
@Test
60+
fun testDeepLinksToolWindow() {
61+
println("Initializing IDE test context")
62+
val projectPath = Paths.get(System.getProperty("java.io.tmpdir"), testProjectName)
63+
run = Setup.setupTestContextIC("DeepLinksToolWindowTest", LocalProjectInfo(projectPath)).runIdeWithDriver()
64+
65+
run.driver.withContext {
66+
ideFrame {
67+
waitFound()
68+
driver.waitForIndicators(5.minutes)
69+
println("IDE is ready, accessing editor.")
70+
71+
// Make sure indexing is finished and project is healthy
72+
projectView {
73+
projectViewTree.pathExists("deep_links_test", "pubspec.yaml")
74+
}
75+
76+
// Open the Deep Links Tool Window
77+
driver.invokeAction("ActivateFlutterDeepLinksToolWindow")
78+
79+
// Wait for the tool window to be focused or exist
80+
toolWindow(io.flutter.deeplinks.DeepLinksViewFactory.TOOL_WINDOW_ID) {
81+
waitFound(1.minutes)
82+
83+
// Verify a JCEF or JxBrowser component exists. We use 'Browser' in class name robustly.
84+
x("//div[contains(@class, 'Browser')]").waitFound(1.minutes)
85+
}
86+
}
87+
}
88+
}
89+
}

testSrc/integration/io/flutter/integrationTest/utils/NewProject.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.intellij.driver.sdk.ui.components.common.welcomeScreen
1313
import com.intellij.driver.sdk.wait
1414
import com.intellij.ide.starter.driver.engine.BackgroundRun
1515
import org.junit.jupiter.api.fail
16+
import java.nio.file.Paths
1617
import kotlin.time.Duration.Companion.seconds
1718

1819
// A Kotlin extension function for the `Finder` class.
@@ -101,3 +102,55 @@ fun newProjectWelcomeScreen(run: BackgroundRun, testProjectName: String) {
101102
}
102103
}
103104
}
105+
106+
/**
107+
* Creates a new Flutter project using the Flutter CLI.
108+
*
109+
* @param testProjectName The folder name for the new project.
110+
* @param projectName The Dart package name of the project.
111+
* @param directory The parent directory where the project folder will be created.
112+
*/
113+
fun createFlutterProjectWithCli(
114+
testProjectName: String,
115+
projectName: String = "test_project",
116+
directory: String = System.getProperty("java.io.tmpdir")
117+
) {
118+
val flutterSdk = System.getenv("FLUTTER_SDK")
119+
?: throw IllegalStateException("FLUTTER_SDK environment variable not set")
120+
val flutterExe = Paths.get(flutterSdk, "bin", "flutter").toString()
121+
122+
println("Creating project $testProjectName in $directory")
123+
val process = ProcessBuilder(flutterExe, "create", "--project-name", projectName, testProjectName)
124+
.directory(java.io.File(directory))
125+
.redirectErrorStream(true)
126+
.start()
127+
128+
val exitCode = process.waitFor()
129+
if (exitCode != 0) {
130+
val output = process.inputStream.bufferedReader().readText()
131+
throw IllegalStateException("flutter create failed: $output")
132+
}
133+
}
134+
135+
/**
136+
* Deletes a test project folder.
137+
*
138+
* @param testProjectName The folder name of the project.
139+
* @param directory The parent directory where the project folder is located. Defaults to the system tmp directory.
140+
*/
141+
fun deleteFlutterProject(
142+
testProjectName: String,
143+
directory: String = System.getProperty("java.io.tmpdir")
144+
) {
145+
if (testProjectName.isNotEmpty()) {
146+
val projectPath = Paths.get(directory, testProjectName)
147+
val projectFile = projectPath.toFile()
148+
if (projectFile.exists()) {
149+
projectFile.deleteRecursively()
150+
println("Successfully deleted test folder: $projectPath")
151+
} else {
152+
println("Test folder does not exist, skipping cleanup: $projectPath")
153+
}
154+
}
155+
}
156+

0 commit comments

Comments
 (0)