Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 117 additions & 48 deletions flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,94 +6,163 @@

package io.flutter.tests.gui

import com.intellij.openapi.util.SystemInfo.isMac
import com.intellij.testGuiFramework.fixtures.ActionButtonFixture
import com.intellij.testGuiFramework.fixtures.ExecutionToolWindowFixture
import com.intellij.testGuiFramework.fixtures.IdeFrameFixture
import com.intellij.testGuiFramework.framework.RunWithIde
import com.intellij.testGuiFramework.framework.Timeouts
import com.intellij.testGuiFramework.impl.GuiTestCase
import com.intellij.testGuiFramework.impl.GuiTestUtilKt
import com.intellij.testGuiFramework.impl.button
import com.intellij.testGuiFramework.launcher.ide.CommunityIde
import io.flutter.tests.gui.fixtures.FlutterInspectorFixture
import org.fest.swing.edt.GuiActionRunner.execute
import org.fest.swing.edt.GuiQuery
import org.fest.swing.fixture.JButtonFixture
import com.intellij.testGuiFramework.util.step
import io.flutter.tests.gui.fixtures.flutterInspectorFixture
import org.fest.swing.fixture.JTreeRowFixture
import org.fest.swing.timing.Condition
import org.fest.swing.timing.Pause.pause
import org.junit.Assert.assertNotNull
import org.junit.Test
import java.awt.Container
import java.awt.event.KeyEvent
import kotlin.test.expect

@RunWithIde(CommunityIde::class)
class InspectorTest : GuiTestCase() {

@Test
fun importSimpleProject() {
fun widgetTree() {
ProjectCreator.importProject()
ideFrame {
launchFlutterApp()
val inspector = flutterInspectorFixture(this)
inspector.populate()
val widgetTree = inspector.widgetTreeFixture()
val widgetTree = inspector.widgetsFixture()
val inspectorTree = widgetTree.inspectorTreeFixture()
val detailsTree = widgetTree.inspectorTreeFixture(isDetails = true)
expect(true) { detailsTree.selection() == null }

step("Details selection synced with main tree") {
inspectorTree.selectRow(2, expand = true)
expect("[[root], MyApp, MaterialApp, MyHomePage]") { inspectorTree.selectionSync().toString() }
expect("[MyHomePage]") { detailsTree.selectionSync().toString() }
inspectorTree.selectRow(10, expand = true)
expect("[[root], MyApp, MaterialApp, MyHomePage, Scaffold, FloatingActionButton]") {
inspectorTree.selectionSync().toString()
}
val string = detailsTree.selectionSync().toString()
expect(true) {
string.startsWith("[MyHomePage,") && string.endsWith("FloatingActionButton]")
}
}

// This is disabled due to an issue in the test framework. The #selectRow call causes
// the widget tree to change its selection, which is absolutely not what we want.
// step("Details selection leaves main tree unchanged") {
// val string = detailsTree.selectionSync().toString()
// detailsTree.selectRow(1, expand = false)
// pause(object : Condition("Details tree changes") {
// override fun test(): Boolean {
// return string != detailsTree.selectionSync().toString()
// }
// }, Timeouts.seconds05)
// expect("[[root], MyApp, MaterialApp, MyHomePage, Scaffold, FloatingActionButton]") {
// inspectorTree.selectionSync().toString()
// }
// }

runner().stop()
}
}

private fun IdeFrameFixture.runner(): ExecutionToolWindowFixture.ContentFixture {
return runToolWindow.findContent("main.dart")
@Test
fun hotReload() {
ProjectCreator.importProject()
ideFrame {
launchFlutterApp()
val inspector = flutterInspectorFixture(this)
inspector.populate()
val widgets = inspector.widgetsFixture()
val widgetsTree = widgets.inspectorTreeFixture(isDetails = false)
widgetsTree.selectRow(0)
val detailsTree = widgets.inspectorTreeFixture(isDetails = true)
val initialDetails = detailsTree.selectionSync().toString()

editor {
// Wait until current file has appeared in current editor and set focus to editor.
moveTo(0)
val editorCode = getCurrentFileContents(false)!!
val original = "pushed the button this"
val index = editorCode.indexOf(original) + original.length
moveTo(index)
val key = if (isMac) KeyEvent.VK_BACK_SPACE else KeyEvent.VK_DELETE
for (n in 1..4) typeKey(key)
typeText("that")
//typeKey(KeyEvent.VK_ESCAPE) // Dismiss completion popup -- not needed with "that" but is needed with "so"
}

step("Trigger Hot Reload and wait for it to finish") {
val reload = findHotReloadButton()
reload.click()
editor.clickCenter() // Need to cycle the event loop to get button enabled on Mac.
pause(object : Condition("Hot Reload finishes") {
override fun test(): Boolean {
return reload.isEnabled
}
}, Timeouts.seconds05)
step("Work around #3370") {
// https://github.com/flutter/flutter-intellij/issues/3370
inspector.renderTreeFixture().show() // The refresh button is broken so force tree update by switching views.
inspector.populate()
widgets.show() // And back to the one we want
}
widgetsTree.selectRow(6) // Text widget
pause(object : Condition("Details tree changes") {
override fun test(): Boolean {
return initialDetails != detailsTree.selectionSync().toString()
}
}, Timeouts.seconds05)
val row: JTreeRowFixture = detailsTree.treeFixture().node(1)
val expected = "\"You have pushed the button that many times:\""
expect(expected) { row.value() }
}

runner().stop()
}
}

fun IdeFrameFixture.launchFlutterApp() {
findRunApplicationButton().click()
val runner = runner()
pause(object : Condition("Start app") {
override fun test(): Boolean {
return runner.isExecutionInProgress
}
}, Timeouts.seconds10)
step("Launch Flutter app") {
findRunApplicationButton().click()
val runner = runner()
pause(object : Condition("Start app") {
override fun test(): Boolean {
return runner.isExecutionInProgress
}
}, Timeouts.seconds30)
}
}

fun IdeFrameFixture.selectSimulator() {
selectDevice("Open iOS Simulator")
private fun findHotReloadButton(): ActionButtonFixture {
return findActionButtonByClassName("ReloadFlutterAppRetarget")
}

fun IdeFrameFixture.selectDevice(devName: String) {
val runButton = findRunApplicationButton()
val actionToolbarContainer = execute<Container>(object : GuiQuery<Container>() {
@Throws(Throwable::class)
override fun executeInEDT(): Container? {
return runButton.target().parent
}
})
assertNotNull(actionToolbarContainer)

// These next two lines look like the right way to select the simulator, but it does not work.
// val comboBoxActionFixture = ComboBoxActionFixture.findComboBoxByText(robot(), actionToolbarContainer!!, "<no devices>")
// comboBoxActionFixture.selectItem(devName)
// Need to get focus on the combo box but the ComboBoxActionFixture.click() method is private, so it is inlined here.
val selector = button("<no devices>")
val comboBoxButtonFixture = JButtonFixture(robot(), selector.target())
GuiTestUtilKt.waitUntil("ComboBoxButton will be enabled", Timeouts.seconds10) {
GuiTestUtilKt.computeOnEdt { comboBoxButtonFixture.target().isEnabled } ?: false
}
comboBoxButtonFixture.click()
robot().pressAndReleaseKey(KeyEvent.VK_DOWN)
robot().pressAndReleaseKey(KeyEvent.VK_ENTER)
robot().waitForIdle()
private fun IdeFrameFixture.runner(): ExecutionToolWindowFixture.ContentFixture {
return runToolWindow.findContent("main.dart")
}

private fun IdeFrameFixture.findActionButtonByActionId(actionId: String): ActionButtonFixture {
private fun findActionButtonByActionId(actionId: String): ActionButtonFixture {
// This seems to be broken, but finding by simple class name works.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bummer. But I guess since we own all the classes under test, not a big deal?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the problem is. I haven't even ruled out pilot error (hmm, that may not be a great idiom these days). It might be a bug in the framework. I keep this around to remind myself not to use it.

var button: ActionButtonFixture? = null
ideFrame {
button = ActionButtonFixture.fixtureByActionId(target(), robot(), actionId)
button = ActionButtonFixture.fixtureByActionId(target().parent, robot(), actionId)
}
return button!!
}

fun IdeFrameFixture.flutterInspectorFixture(ideFrame: IdeFrameFixture): FlutterInspectorFixture {
return FlutterInspectorFixture(project, robot(), ideFrame)
private fun findActionButtonByClassName(className: String): ActionButtonFixture {
// This works when the button is enabled but fails if it is disabled (implementation detail).
var button: ActionButtonFixture? = null
ideFrame {
button = ActionButtonFixture.fixtureByActionClassName(target(), robot(), className)
}
return button!!
}

}
72 changes: 42 additions & 30 deletions flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.intellij.platform.templates.github.ZipUtil
import com.intellij.testGuiFramework.fixtures.IdeFrameFixture
import com.intellij.testGuiFramework.framework.Timeouts
import com.intellij.testGuiFramework.impl.*
import com.intellij.testGuiFramework.util.step
import com.intellij.testGuiFramework.utils.TestUtilsClass
import com.intellij.testGuiFramework.utils.TestUtilsClassCompanion
import com.intellij.util.UriUtil
Expand All @@ -37,25 +38,30 @@ class ProjectCreator(guiTestCase: GuiTestCase) : TestUtilsClass(guiTestCase) {
var flutterMessagesFixture: FlutterMessagesToolWindowFixture.FlutterContentFixture? = null

fun importProject(projectName: String = sampleProjectName): File {
val projectDirFile = extractProject(projectName)
val projectPath: File = guiTestCase.guiTestRule.importProject(projectDirFile)
with(guiTestCase) {
waitForFirstIndexing()
openPubspecInProject()
ideFrame {
editor {
val note = notificationPanel()
if (note?.getLabelText() == "Flutter commands") {
note.clickLink("Packages get")
flutterMessagesFixture = flutterMessagesToolWindowFixture().getFlutterContent(projectName)
flutterMessagesFixture!!.findMessageContainingText("Process finished")
return step("Import project $projectName") {
val projectDirFile = extractProject(projectName)
val projectPath: File = guiTestCase.guiTestRule.importProject(projectDirFile)
with(guiTestCase) {
waitForFirstIndexing()
step("Get packages") {
openPubspecInProject()
ideFrame {
editor {
val note = notificationPanel()
if (note?.getLabelText() == "Flutter commands") {
note.clickLink("Packages get")
flutterMessagesFixture = flutterMessagesToolWindowFixture().getFlutterContent(projectName)
flutterMessagesFixture!!.findMessageContainingText("Process finished")
}
// TODO(messick) Close pubspec.yaml once editor tab fixtures are working.
//closeTab("pubspec.yaml")
}
}
// TODO(messick) Close pubspec.yaml once editor tab fixtures are working.
}
openMainInProject()
}
openMainInProject()
projectPath
}
return projectPath
}

private fun extractProject(projectName: String): File {
Expand Down Expand Up @@ -93,20 +99,22 @@ class ProjectCreator(guiTestCase: GuiTestCase) : TestUtilsClass(guiTestCase) {

fun createProject(projectName: String = defaultProjectName, needToOpenMain: Boolean = true) {
with(guiTestCase) {
welcomeFrame {
this.actionLink(name = "Create New Project").click()
GuiTestUtilKt.waitProgressDialogUntilGone(robot = robot(), progressTitle = "Loading Templates",
timeoutToAppear = Timeouts.seconds02)
dialog("New Project") {
jList("Flutter").clickItem("Flutter")
button("Next").click()
typeText(projectName)
button("Finish").click()
GuiTestUtilKt.waitProgressDialogUntilGone(robot = robot(), progressTitle = "Creating Flutter Project",
timeoutToAppear = Timeouts.seconds03)
step("Create project $projectName") {
welcomeFrame {
this.actionLink(name = "Create New Project").click()
GuiTestUtilKt.waitProgressDialogUntilGone(
robot = robot(), progressTitle = "Loading Templates", timeoutToAppear = Timeouts.seconds02)
dialog("New Project") {
jList("Flutter").clickItem("Flutter")
button("Next").click()
typeText(projectName)
button("Finish").click()
GuiTestUtilKt.waitProgressDialogUntilGone(
robot = robot(), progressTitle = "Creating Flutter Project", timeoutToAppear = Timeouts.seconds03)
}
}
waitForFirstIndexing()
}
waitForFirstIndexing()
if (needToOpenMain) openMainInProject(wait = true)
}
}
Expand All @@ -127,16 +135,20 @@ class ProjectCreator(guiTestCase: GuiTestCase) : TestUtilsClass(guiTestCase) {
private fun GuiTestCase.openMainInProject(wait: Boolean = false) {
ideFrame {
projectView {
path(project.name, "lib", "main.dart").doubleClick()
if (wait) waitForBackgroundTasksToFinish()
step("Open lib/main.dart") {
path(project.name, "lib", "main.dart").doubleClick()
if (wait) waitForBackgroundTasksToFinish()
}
}
}
}

private fun GuiTestCase.openPubspecInProject() {
ideFrame {
projectView {
path(project.name, "pubspec.yaml").doubleClick()
step("Open pubspec.yaml") {
path(project.name, "pubspec.yaml").doubleClick()
}
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.testGuiFramework.framework.RunWithIde
import com.intellij.testGuiFramework.impl.GuiTestCase
import com.intellij.testGuiFramework.launcher.ide.CommunityIde
import com.intellij.testGuiFramework.util.step
import org.junit.Assert
import org.junit.Test

Expand All @@ -36,9 +37,13 @@ class SmokeTest : GuiTestCase() {
// Wait until current file has appeared in current editor and set focus to editor.
moveTo(1)
}
val editorCode = editor.getCurrentFileContents(false)
Assert.assertTrue(editorCode!!.isNotEmpty())
closeProjectAndWaitWelcomeFrame()
step("Verify open file has some content") {
val editorCode = editor.getCurrentFileContents(false)
Assert.assertTrue(editorCode!!.isNotEmpty())
}
step("Close project") {
closeProjectAndWaitWelcomeFrame()
}
}
}
}
2 changes: 2 additions & 0 deletions flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import org.junit.runner.RunWith
import org.junit.runners.Suite

//* gradle -Dtest.single=TestSuite clean test -Didea.gui.test.alternativeIdePath="<path_to_installed_IDE>"
// The log file is at flutter-gui-tests/build/idea-sandbox/system/log/idea.log
// The test report is at flutter-gui-tests/build/reports/tests/test/index.html
@RunWith(GuiTestSuiteRunner::class)
@Suite.SuiteClasses(SmokeTest::class, InspectorTest::class)
class TestSuite : GuiTestSuite()
Loading