diff --git a/build.gradle.kts b/build.gradle.kts index 7604cadde0..c44945d0a6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,8 @@ allprojects { repositories { mavenCentral() + maven("https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-ide-starter") + maven("https://mvnrepository.com/artifact/com.jetbrains.intellij.tools/ide-starter-driver") intellijPlatform { defaultRepositories() } @@ -41,6 +43,7 @@ plugins { id("org.jetbrains.intellij.platform") version "2.7.2" // IntelliJ Platform Gradle Plugin id("org.jetbrains.kotlin.jvm") version "2.2.0" // Kotlin support id("org.jetbrains.changelog") version "2.2.0" // Gradle Changelog Plugin + idea // IntelliJ IDEA support } // By default (e.g. when we call `runIde` during development), the plugin version is SNAPSHOT @@ -96,11 +99,18 @@ jvmVersion = when (javaVersion) { throw IllegalArgumentException("javaVersion must be defined in the product matrix as either \"17\" or \"21\", but is not for $ideaVersion") } } + kotlin { compilerOptions { apiVersion.set(KotlinVersion.KOTLIN_2_1) jvmTarget = jvmVersion } + // This is how you specify the specific JVM requirements, this may be a requirement for the Starter test framework +// jvmToolchain { +// languageVersion = JavaLanguageVersion.of(21) +// @Suppress("UnstableApiUsage") +// vendor = JvmVendorSpec.JETBRAINS +// } } var javaCompatibilityVersion: JavaVersion @@ -117,11 +127,70 @@ javaCompatibilityVersion = when (javaVersion) { throw IllegalArgumentException("javaVersion must be defined in the product matrix as either \"17\" or \"21\", but is not for $ideaVersion") } } + java { sourceCompatibility = javaCompatibilityVersion targetCompatibility = javaCompatibilityVersion } +sourceSets { + main { + java.srcDirs( + listOf( + "src", + "third_party/vmServiceDrivers" + ) + ) + // Add kotlin.srcDirs if we start using Kotlin in the main plugin. + resources.srcDirs( + listOf( + "src", + "resources" + ) + ) + } + test { + java.srcDirs( + listOf( + "src", + "testSrc/unit", + "third_party/vmServiceDrivers" + ) + ) + resources.srcDirs( + listOf( + "resources", + "testData", + "testSrc/unit" + ) + ) + } + + create("integration", Action { + java.srcDirs("testSrc/integration") + kotlin.srcDirs("testSrc/integration") + resources.srcDirs("testSrc/integration") + compileClasspath += sourceSets["main"].output + sourceSets["test"].output + runtimeClasspath += sourceSets["main"].output + sourceSets["test"].output + }) +} + +// Configure IntelliJ IDEA to recognize integration as test sources +idea { + module { + testSources.from(sourceSets["integration"].kotlin.srcDirs) + testResources.from(sourceSets["integration"].resources.srcDirs) + } +} + +val integrationImplementation: Configuration by configurations.getting { + extendsFrom(configurations.testImplementation.get()) +} + +val integrationRuntimeOnly: Configuration by configurations.getting { + extendsFrom(configurations.testRuntimeOnly.get()) +} + dependencies { intellijPlatform { // Documentation on the default target platform methods: @@ -129,6 +198,8 @@ dependencies { // Android Studio versions can be found at: https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html androidStudio(ideaVersion) testFramework(TestFrameworkType.Platform) + testFramework(TestFrameworkType.Starter, configurationName = "integrationImplementation") + testFramework(TestFrameworkType.JUnit5, configurationName = "integrationImplementation") // Plugin dependency documentation: // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#plugins @@ -172,6 +243,14 @@ dependencies { ) ) ) + + // UI Test dependencies + integrationImplementation("org.kodein.di:kodein-di-jvm:7.26.1") + integrationImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") + + // JUnit 5 is required for UI tests + integrationImplementation("org.junit.jupiter:junit-jupiter:5.11.4") + integrationRuntimeOnly("org.junit.platform:junit-platform-launcher") } intellijPlatform { @@ -219,37 +298,41 @@ intellijPlatform { } } -sourceSets { - main { - java.srcDirs( - listOf( - "src", - "third_party/vmServiceDrivers" - ) - ) - // Add kotlin.srcDirs if we start using Kotlin in the main plugin. - resources.srcDirs( - listOf( - "src", - "resources" - ) - ) - } - test { - java.srcDirs( - listOf( - "src", - "testSrc/unit", - "third_party/vmServiceDrivers" - ) +tasks { + register("integration") { + description = "Runs only the UI integration tests that start the IDE" + group = "verification" + testClassesDirs = sourceSets["integration"].output.classesDirs + classpath = sourceSets["integration"].runtimeClasspath + useJUnitPlatform { + includeTags("ui") + } + + // UI tests should run sequentially (not in parallel) to avoid conflicts + maxParallelForks = 1 + + // Increase memory for UI tests + minHeapSize = "1g" + maxHeapSize = "4g" + + systemProperty("path.to.build.plugin", buildPlugin.get().archiveFile.get().asFile.absolutePath) + systemProperty("idea.home.path", prepareTestSandbox.get().getDestinationDir().parentFile.absolutePath) + systemProperty( + "allure.results.directory", project.layout.buildDirectory.get().asFile.absolutePath + "/allure-results" ) - resources.srcDirs( - listOf( - "resources", - "testData", - "testSrc/unit" + + // Disable IntelliJ test listener that conflicts with standard JUnit + systemProperty("idea.test.cyclic.buffer.size", "0") + + // Add required JVM arguments + jvmArgumentProviders += CommandLineArgumentProvider { + mutableListOf( + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.desktop/javax.swing=ALL-UNNAMED" ) - ) + } + + dependsOn(buildPlugin) } } diff --git a/src/io/flutter/module/FlutterGeneratorPeer.java b/src/io/flutter/module/FlutterGeneratorPeer.java index f815696a7d..a80c6961c9 100644 --- a/src/io/flutter/module/FlutterGeneratorPeer.java +++ b/src/io/flutter/module/FlutterGeneratorPeer.java @@ -13,8 +13,8 @@ import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.TextComponentAccessor; import com.intellij.openapi.ui.ValidationInfo; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.ComboboxWithBrowseButton; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.components.JBLabel; @@ -23,6 +23,8 @@ import io.flutter.module.settings.SettingsHelpForm; import io.flutter.sdk.FlutterSdk; import io.flutter.sdk.FlutterSdkUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.ComboBoxEditor; import javax.swing.JComponent; @@ -33,9 +35,6 @@ import javax.swing.event.DocumentEvent; import javax.swing.text.JTextComponent; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - public class FlutterGeneratorPeer { private final WizardContext myContext; private JPanel myMainPanel; @@ -65,6 +64,16 @@ public FlutterGeneratorPeer(WizardContext context) { private void init() { mySdkPathComboWithBrowse.getComboBox().setEditable(true); FlutterSdkUtil.addKnownSDKPathsToCombo(mySdkPathComboWithBrowse.getComboBox()); + if (mySdkPathComboWithBrowse.getComboBox().getModel().getSize() == 0) { + // If no SDKs are found, try to use the one from the FLUTTER_SDK environment variable. + // This ensures the SDK path is pre-filled when the combo box is empty, not requiring + // a running Application which is the case for users and bots on the initial startup + // experience. + final String flutterSDKPath = System.getenv("FLUTTER_SDK"); + if (StringUtil.isNotEmpty(flutterSDKPath)) { + mySdkPathComboWithBrowse.getComboBox().setSelectedItem(flutterSDKPath); + } + } mySdkPathComboWithBrowse.addBrowseFolderListener(null, FileChooserDescriptorFactory.createSingleFolderDescriptor() .withTitle(FlutterBundle.message("flutter.sdk.browse.path.label")), TextComponentAccessor.STRING_COMBOBOX_WHOLE_TEXT); diff --git a/testSrc/integration/io/flutter/integrationTest/NewProjectUITest.kt b/testSrc/integration/io/flutter/integrationTest/NewProjectUITest.kt new file mode 100644 index 0000000000..719a42ce40 --- /dev/null +++ b/testSrc/integration/io/flutter/integrationTest/NewProjectUITest.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +package io.flutter.integrationTest + +import com.intellij.driver.sdk.ui.components.UiComponent.Companion.waitFound +import com.intellij.driver.sdk.ui.components.common.ideFrame +import com.intellij.driver.sdk.ui.components.common.toolwindows.projectView +import com.intellij.driver.sdk.waitForIndicators +import com.intellij.ide.starter.driver.engine.BackgroundRun +import com.intellij.ide.starter.driver.engine.runIdeWithDriver +import com.intellij.ide.starter.junit5.config.UseLatestDownloadedIdeBuild +import io.flutter.integrationTest.utils.newProjectWelcomeScreen +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith +import java.nio.file.Paths +import kotlin.time.Duration.Companion.minutes + +@Tag("ui") +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@ExtendWith(UseLatestDownloadedIdeBuild::class) +class MyProjectUITest { + + companion object { + var testProjectName = "" + + /** + * Cleanup method that runs after all tests in this class. + * Removes the test project folder created during testing to keep the system clean. + */ + @JvmStatic + @AfterAll + fun cleanUpTestFolder() { + val projectPath = Paths.get(System.getProperty("user.home"), "IdeaProjects", testProjectName) + val projectFile = projectPath.toFile() + if (projectFile.exists()) { + projectFile.deleteRecursively() + println("Successfully deleted test folder: $projectPath") + } else { + println("Test folder does not exist, skipping cleanup: $projectPath") + } + } + } + + /** + * The IDE instance running in the background. + * Initialized in @BeforeEach and closed in @AfterEach. + */ + private lateinit var run: BackgroundRun + + /** + * Empty for these tests as we want a different IDE. Setup is called in each test. + */ + @BeforeEach + fun initContext() { + // Generate a unique folder name for the test project to avoid conflicts + testProjectName = "my_test_project_${System.currentTimeMillis()}" + } + + /** + * Tears down the test environment after each test method. + * + * Safely closes the IDE instance if it was successfully started. + * The initialization check prevents errors if the setup failed. + */ + @AfterEach + fun closeIde() { + if (::run.isInitialized) { + println("Closing IDE") + run.closeIdeAndWait() + } else { + println("IDE was not started, skipping close") + } + } + + @Test + fun newProjectIC() { + println("Initializing IDE test context") + println("Test project will be created as: $testProjectName") + run = Setup.setupTestContextIC("MyProjectUITest").runIdeWithDriver() + + newProjectWelcomeScreen(run, testProjectName) + newProjectInProjectView() + } + + @Test + @Disabled("Need license configuration to test") + fun newProjectUE() { + println("Initializing IDE test context") + println("Test project will be created as: $testProjectName") + run = Setup.setupTestContextUE("MyProjectUITest").runIdeWithDriver() + + newProjectWelcomeScreen(run, testProjectName) + run.driver.withContext { + ideFrame { + // Wait for the ideFrame to appear before attempting to interact with it. + // This is a positive assertion that confirms the UI transition is complete. + waitFound() + } + } + } + + @Test + @Disabled("Need license configuration to test") + fun newProjectWS() { + println("Initializing IDE test context") + println("Test project will be created as: $testProjectName") + run = Setup.setupTestContextWS("MyProjectUITest").runIdeWithDriver() + + newProjectWelcomeScreen(run, testProjectName) + newProjectInProjectView() + } + + /** + * Verifies the successful creation of a new project by asserting the presence + * and structure of files in the project view. + *

+ * This function is designed to be called after the project has been created. It waits + * for the IDE to finish indexing and then confirms that all expected files and folders + * are correctly displayed in the Project View tool window. + */ + fun newProjectInProjectView() { + /* + * This function verifies the successful creation of a new project by asserting the + * presence and structure of files in the project view. + * + * It is designed to be called after the project creation process has been initiated + * and the main IDE window has opened. + */ + run.driver.withContext { + ideFrame { + // Wait for the main IDE window to appear before attempting to interact with it. + // This is a positive assertion that confirms the UI transition is complete. + waitFound() + + // This is a crucial step that waits for the IDE to finish all background tasks, + // such as indexing, downloading dependencies, and building the initial project. + // This prevents race conditions where the test tries to interact with files + // before they are fully loaded. + driver.waitForIndicators(5.minutes) + println("IDE is ready, accessing editor.") + + projectView { + // Asserting the top-level files + // Note: The `pathExists()` method is part of an older API. + // The recommended method is `findPath().shouldExist()`. + projectViewTree.pathExists(testProjectName, "README.md") + projectViewTree.pathExists(testProjectName, "pubspec.yaml") + + // Asserting files inside specific directories + projectViewTree.pathExists(testProjectName, "lib", "main.dart") + projectViewTree.pathExists(testProjectName, "test", "widget_test.dart") + + // Asserting the existence of the main project folders + projectViewTree.pathExists(testProjectName, ".dart_tool") + projectViewTree.pathExists(testProjectName, "lib") + projectViewTree.pathExists(testProjectName, "test") + projectViewTree.pathExists(testProjectName, "android") + projectViewTree.pathExists(testProjectName, "ios") + projectViewTree.pathExists(testProjectName, "linux") + projectViewTree.pathExists(testProjectName, "macos") + projectViewTree.pathExists(testProjectName, "web") + projectViewTree.pathExists(testProjectName, "windows") + // Double-click the main file to open it in the editor. + projectViewTree + .waitFound() + .doubleClickPath(testProjectName, "lib", "main.dart", fullMatch = false) + } + } + } + } +} diff --git a/testSrc/integration/io/flutter/integrationTest/Setup.kt b/testSrc/integration/io/flutter/integrationTest/Setup.kt new file mode 100644 index 0000000000..3af05d4b65 --- /dev/null +++ b/testSrc/integration/io/flutter/integrationTest/Setup.kt @@ -0,0 +1,167 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +package io.flutter.integrationTest + +import com.intellij.ide.starter.buildTool.GradleBuildTool +import com.intellij.ide.starter.community.model.BuildType +import com.intellij.ide.starter.di.di +import com.intellij.ide.starter.ide.IDETestContext +import com.intellij.ide.starter.ide.IdeProductProvider +import com.intellij.ide.starter.models.IdeInfo +import com.intellij.ide.starter.models.TestCase +import com.intellij.ide.starter.path.GlobalPaths +import com.intellij.ide.starter.plugins.PluginConfigurator +import com.intellij.ide.starter.project.NoProject +import com.intellij.ide.starter.project.ProjectInfoSpec +import com.intellij.ide.starter.runner.Starter +import com.intellij.ide.starter.utils.Git +import com.intellij.openapi.util.SystemInfo +import org.kodein.di.DI +import org.kodein.di.bindSingleton +import java.nio.file.Paths + +/** + * Setup class for configuring IntelliJ IDE UI tests using the IntelliJ IDE Starter framework. + * + * This class provides the necessary configuration to: + * - Initialize the test environment with proper paths and dependencies + * - Install the plugin under test into the IDE instance + * - Configure VM options for optimal test execution + * - Apply OS-specific settings for compatibility + * + * @see [IntelliJ IDE Starter Documentation](https://github.com/JetBrains/intellij-ide-starter) + */ +class Setup { + + /** + * Custom GlobalPaths implementation that points to the project's build directory. + * This ensures all test artifacts are stored within the project structure. + */ + class TemplatePaths : GlobalPaths(Git.getRepoRoot().resolve("build")) + + + companion object { + + init { + // Configure dependency injection to use our custom paths + di = DI.Companion { + extend(di) + bindSingleton(overrides = true) { TemplatePaths() } + } + } + + fun setupTestContextIC(hyphenateWithClass: String, projectInfoSpec: ProjectInfoSpec = NoProject): IDETestContext { + return setupTestContext( + "", IdeProductProvider.IC.copy( + // TODO(team) should the version be fetched from some setting, i.e. System.getProperty("uiPlatformBuildVersion") + buildNumber = "252.23892.409", + buildType = BuildType.RELEASE.type + ), projectInfoSpec + ) + } + + fun setupTestContextUE(hyphenateWithClass: String, projectInfoSpec: ProjectInfoSpec = NoProject): IDETestContext { + return setupTestContext( + "", IdeProductProvider.IU.copy( + // TODO(team) should the version be fetched from some setting, i.e. System.getProperty("uiPlatformBuildVersion") + buildNumber = "252.23892.409", + buildType = BuildType.RELEASE.type + ), projectInfoSpec + ) + } + + fun setupTestContextWS(hyphenateWithClass: String, projectInfoSpec: ProjectInfoSpec = NoProject): IDETestContext { + return setupTestContext( + "", IdeProductProvider.WS.copy( + // TODO(team) should the version be fetched from some setting, i.e. System.getProperty("uiPlatformBuildVersion") + buildNumber = "252.23892.411", + buildType = BuildType.RELEASE.type + ), projectInfoSpec + ) + } + + /** + * Sets up the test context for UI testing with IntelliJ IDE Starter. + * + * This method: + * 1. Creates a test case with the specified IDE version + * 2. Configures the test context with the built plugin + * 3. Applies VM options for proper IDE behavior during tests + * 4. Applies OS-specific configurations + * + * @param hyphenateWithClass The name of the test class (used for test identification) + * @return IDETestContext configured for running UI tests + * + * @throws IllegalStateException if required system properties are not set + */ + fun setupTestContext(hyphenateWithClass: String, ideInfo: IdeInfo, projectInfoSpec: ProjectInfoSpec = NoProject): IDETestContext { + + val testCase = TestCase(ideInfo, projectInfoSpec) + + return Starter.newContext(testName = hyphenateWithClass, testCase = testCase).apply { + // Install the plugin that was built by the buildPlugin task + val pluginPath = System.getProperty("path.to.build.plugin") + PluginConfigurator(this) + .installPluginFromPath(Paths.get(pluginPath)) + .installPluginFromPluginManager("Dart", "252.24322.5") + withBuildTool() + }.applyVMOptionsPatch { + + // === Common system properties for all operating systems === + + // Required JVM arguments for module access + addSystemProperty("--add-opens", "java.base/java.lang=ALL-UNNAMED") + addSystemProperty("--add-opens", "java.desktop/javax.swing=ALL-UNNAMED") + + // Core IDE configuration + addSystemProperty("idea.trust.all.projects", true) // Trust all projects automatically + addSystemProperty("jb.consents.confirmation.enabled", false) // Disable consent dialogs + addSystemProperty("jb.privacy.policy.text", "") // Skip privacy policy + addSystemProperty("ide.show.tips.on.startup.default.value", false) // No tips on startup + + // Test framework configuration + addSystemProperty("junit.jupiter.extensions.autodetection.enabled", true) + addSystemProperty("shared.indexes.download.auto.consent", true) + + // UI testing specific + addSystemProperty("expose.ui.hierarchy.url", true) // Enable UI hierarchy inspection + addSystemProperty("ide.experimental.ui", true) // Use new UI for testing + + // ensure it does not open any project on startup + addSystemProperty("ide.open.project.at.startup", false) + + // === OS-specific system properties === + + when { + SystemInfo.isMac -> { + // macOS specific settings + addSystemProperty("ide.mac.file.chooser.native", false) // Use Java file chooser + addSystemProperty("ide.mac.message.dialogs.as.sheets", false) // Use regular dialogs + addSystemProperty("jbScreenMenuBar.enabled", false) // Disable native menu bar + addSystemProperty("ide.native.launcher", true) // Use native launcher + } + + SystemInfo.isWindows -> { + // Windows specific settings + + } + + SystemInfo.isLinux -> { + // Linux specific settings + addSystemProperty("ide.browser.jcef.enabled", true) + addSystemProperty("ide.native.launcher", false) // Avoid launcher issues on Linux + + // X11/Wayland compatibility + addSystemProperty("sun.java2d.uiScale.enabled", false) + addSystemProperty("sun.java2d.xrender", false) + } + } + + }.addProjectToTrustedLocations() + } + } +} diff --git a/testSrc/integration/io/flutter/integrationTest/utils/NewProject.kt b/testSrc/integration/io/flutter/integrationTest/utils/NewProject.kt new file mode 100644 index 0000000000..1a857adf47 --- /dev/null +++ b/testSrc/integration/io/flutter/integrationTest/utils/NewProject.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +package io.flutter.integrationTest.utils + +import com.intellij.driver.sdk.ui.Finder +import com.intellij.driver.sdk.ui.components.ComponentData +import com.intellij.driver.sdk.ui.components.UiComponent +import com.intellij.driver.sdk.ui.components.common.welcomeScreen +import com.intellij.driver.sdk.wait +import com.intellij.ide.starter.driver.engine.BackgroundRun +import org.junit.jupiter.api.fail +import kotlin.time.Duration.Companion.seconds + +// A Kotlin extension function for the `Finder` class. +// This function adds a new method, `newProjectDialog`, that can be +// called on any `Finder` object. +fun Finder.newProjectDialog(action: NewProjectDialogUI.() -> Unit) { + // Locates the "New Project" dialog. + // - `x(...)` creates an XPath-like query to find the UI component. + // - The query targets a `div` with a `title` of "New Project". + // - `NewProjectDialogUI::class.java` specifies that the found component + // should be treated as an instance of the `NewProjectDialogUI` class, + // allowing access to its properties and functions. + // + // The found dialog component is then passed to the `action` lambda, + // which contains the test steps to perform within the dialog. + x("//div[@title='New Project']", NewProjectDialogUI::class.java).action() +} + +// A UI component representing the "New Project" dialog. +// This class provides a structured way to interact with the dialog's elements +// using the IntelliJ Driver SDK. +open class NewProjectDialogUI(data: ComponentData) : UiComponent(data) { + + // Clicks on the specified project type from the list. + // The function waits until the text is found before performing the click action. + fun chooseProjectType(projectType: String) { + projectTypeList.waitOneText(projectType).click() + } + + // Locates the list of project types within the dialog. + // The xQuery targets a component with the specific class "JBList". + private val projectTypeList = x("//div[@class='JBList']") + + // Locates the "Next" button in the dialog. + // The xQuery finds a button component with the visible text "Next". + val nextButton = x("//div[@text='Next']") + + // Locates the "Create" button in the dialog. + // The xQuery finds a button component with the visible text "Create". + val createButton = x("//div[@text='Create']") +} + +/** + * Automates the process of creating a new Flutter project from the welcome screen. + *

+ * This function navigates the "New Project" dialog, selects the Flutter project type, + * and enters a unique project name. It relies on the {@code FLUTTER_SDK} environment + * variable to be set for the test to proceed. + */ +fun newProjectWelcomeScreen(run: BackgroundRun, testProjectName: String) { + run.driver.withContext { + // Assert that the welcome screen is visible before interacting with it. + welcomeScreen { + assert(isVisible()) + println("Creating the new project from Welcome Screen") + createNewProjectButton.click() + + // The test expects the `FlutterGeneratorPeer` to automatically populate the + // Flutter SDK path. This behavior relies on the FLUTTER_SDK environment + // variable being set. + // + // If the FLUTTER_SDK variable is not present, the UI will not find the SDK, + // and the 'Next' button will remain disabled, causing the test to fail. + // A common reason for this failure is an unconfigured test environment. + + newProjectDialog { + // Wait for the dialog to fully load + wait(1.seconds) + + // Select project type - adjust based on your needs + chooseProjectType("Flutter") + wait(1.seconds) + + // Expect setup to take care of setting the correct Flutter SDK + if (!nextButton.isEnabled()) { + fail { "The FLUTTER_SDK environment variable was not set." } + } + nextButton.click() + wait(1.seconds) + + keyboard { + typeText(testProjectName) + } + createButton.click() + } + } + } +} diff --git a/tool/github.sh b/tool/github.sh index 68a98b3ef4..44137abb07 100755 --- a/tool/github.sh +++ b/tool/github.sh @@ -72,12 +72,16 @@ elif [ "UNIT_TEST_BOT" = "$BOT" ] ; then ./gradlew test elif [ "VERIFY_BOT" = "$BOT" ] ; then - # Run the verifier for this version + # Run the verifier ./gradlew verifyPluginProjectConfiguration ./gradlew verifyPluginStructure ./gradlew verifyPluginSignature ./gradlew verifyPlugin +elif [ "INTEGRATION_BOT" = "$BOT" ]; then + # Run the integration tests + ./gradlew integration --warning-mode all + else # Run the build. ./gradlew buildPlugin