From 445c5991784020e4f9228fcebabd39d4ab370a2b Mon Sep 17 00:00:00 2001 From: Steve Messick Date: Tue, 16 Apr 2019 11:17:59 -0700 Subject: [PATCH 1/3] Make the inspector easier to test --- .../io/flutter/tests/gui/InspectorTest.kt | 15 ++++- .../testSrc/io/flutter/tests/gui/TestSuite.kt | 2 +- .../gui/fixtures/FlutterInspectorFixture.kt | 67 ++++++++++++++++++- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt index afc057d778..7d9986badd 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt @@ -20,6 +20,7 @@ import org.fest.swing.edt.GuiActionRunner.execute import org.fest.swing.edt.GuiQuery import org.fest.swing.fixture.JButtonFixture import org.fest.swing.timing.Condition +import org.fest.swing.timing.Pause import org.fest.swing.timing.Pause.pause import org.junit.Assert.assertNotNull import org.junit.Test @@ -32,11 +33,21 @@ class InspectorTest : GuiTestCase() { @Test fun importSimpleProject() { ProjectCreator.importProject() + println("DEBUG 1") ideFrame { launchFlutterApp() + println("DEBUG 2") val inspector = flutterInspectorFixture(this) inspector.populate() + println("DEBUG 3 $inspector") val widgetTree = inspector.widgetTreeFixture() + println("DEBUG 4 $widgetTree") + val inspectorTree = widgetTree.inspectorTreeFixture() + println("DEBUG 5 $inspectorTree") + inspectorTree.selectRow(1) + println("DEBUG 6 ${inspectorTree.selection()}") + Pause.pause(2000) + println("DEBUG 7 ${inspectorTree.selection()}") runner().stop() } } @@ -70,8 +81,8 @@ class InspectorTest : GuiTestCase() { 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!!, "") - // comboBoxActionFixture.selectItem(devName) +// val comboBoxActionFixture = ComboBoxActionFixture.findComboBoxByText(robot(), actionToolbarContainer!!, "") +// 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("") val comboBoxButtonFixture = JButtonFixture(robot(), selector.target()) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt index da2d99a3d3..576374e793 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt @@ -13,5 +13,5 @@ import org.junit.runners.Suite //* gradle -Dtest.single=TestSuite clean test -Didea.gui.test.alternativeIdePath="" @RunWith(GuiTestSuiteRunner::class) -@Suite.SuiteClasses(SmokeTest::class, InspectorTest::class) +@Suite.SuiteClasses(/*SmokeTest::class,*/ InspectorTest::class) class TestSuite : GuiTestSuite() diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt index 38090c3d72..eba8283b95 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt @@ -12,20 +12,29 @@ import com.intellij.testGuiFramework.fixtures.IdeFrameFixture import com.intellij.testGuiFramework.fixtures.ToolWindowFixture import com.intellij.testGuiFramework.framework.Timeouts import com.intellij.testGuiFramework.matcher.ClassNameMatcher +import com.intellij.util.ui.EdtInvocationManager import io.flutter.inspector.InspectorService +import io.flutter.inspector.InspectorTree import io.flutter.view.InspectorPanel import junit.framework.Assert.assertNotNull +import org.fest.swing.core.ComponentFinder import org.fest.swing.core.Robot +import org.fest.swing.fixture.JTreeFixture import org.fest.swing.timing.Condition import org.fest.swing.timing.Pause import org.fest.swing.timing.Pause.pause +import java.awt.Component +import java.awt.event.MouseEvent import javax.swing.JPanel +import javax.swing.JTree +import javax.swing.tree.TreePath class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFrame: IdeFrameFixture) : ToolWindowFixture("Flutter Inspector", project, robot) { fun populate() { activate() + selectedContent Pause.pause(object : Condition("Initialize inspector") { override fun test(): Boolean { return contents[0].displayName != null @@ -41,6 +50,14 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra return inspectorPanel(InspectorService.FlutterTreeType.renderObject) } + private fun finder(): ComponentFinder { + return ideFrame.robot().finder() + } + + private fun classMatcher(name: String, base: Class): ClassNameMatcher { + return ClassNameMatcher.forClass(name, base) + } + private fun inspectorPanel(type: InspectorService.FlutterTreeType): InspectorPanelFixture { val inspectorPanelRef = Ref() @@ -58,12 +75,56 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra } private fun findInspectorPanel(type: InspectorService.FlutterTreeType): InspectorPanel? { - val panels = ideFrame.robot().finder().findAll(contents[0].component, - ClassNameMatcher.forClass("io.flutter.view.InspectorPanel", JPanel::class.java)) - return panels.firstOrNull { (it as InspectorPanel).treeType == type } as InspectorPanel + val panels = finder().findAll(contents[0].component, classMatcher("io.flutter.view.InspectorPanel", JPanel::class.java)) + return panels.firstOrNull { it is InspectorPanel && it.treeType == type && !it.isDetailsSubtree } as InspectorPanel } inner class InspectorPanelFixture(val inspectorPanel: InspectorPanel) { + fun inspectorTreeFixture(): InspectorTreeFixture { + val inspectorTreeRef = Ref() + + pause(object : Condition("Tree shows up") { + override fun test(): Boolean { + val inspectorTree = findInspectorTree() + inspectorTreeRef.set(inspectorTree) + return inspectorTree != null + } + }, Timeouts.seconds10) + + val inspectorTree = inspectorTreeRef.get() + assertNotNull(inspectorTree) + return InspectorTreeFixture(inspectorTree) + } + + fun findInspectorTree(): InspectorTree? { + val trees = finder().findAll(inspectorPanel, classMatcher("io.flutter.inspector.InspectorTree", JTree::class.java)) + val tree = trees.firstOrNull { it is InspectorTree && !it.detailsSubtree } + if (tree != null) return tree as InspectorTree else return null + } + } + + inner class InspectorTreeFixture(val inspectorTree: InspectorTree) { + + fun treeFixture() : JTreeFixture { + return JTreeFixture(ideFrame.robot(), inspectorTree) + } + + fun selectRow(number: Int) { + pause(object : Condition("Tree has content") { + override fun test(): Boolean { + return inspectorTree.rowCount > 0 + } + }, Timeouts.seconds05) +// val click = MouseEvent(inspectorTree, 0, 0L, 0, 100, 30, 1, false) +// EdtInvocationManager.getInstance().invokeAndWait() { +// inspectorTree.dispatchEvent(click) +// } + treeFixture().clickRow(number) + } + + fun selection(): TreePath? { + return inspectorTree.selectionPath + } } } From 7f863113b1cf341ec97085dd6057c8b4aa533674 Mon Sep 17 00:00:00 2001 From: Steve Messick Date: Tue, 16 Apr 2019 11:20:59 -0700 Subject: [PATCH 2/3] checkpoint --- .../io/flutter/tests/gui/InspectorTest.kt | 148 +++++++++++++++--- .../io/flutter/tests/gui/ProjectCreator.kt | 72 +++++---- .../testSrc/io/flutter/tests/gui/SmokeTest.kt | 11 +- .../testSrc/io/flutter/tests/gui/TestSuite.kt | 2 + .../gui/fixtures/FlutterInspectorFixture.kt | 85 +++++++--- .../FlutterMessagesToolWindowFixture.kt | 19 +-- 6 files changed, 246 insertions(+), 91 deletions(-) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt index 7d9986badd..ba49d6ebb2 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt @@ -6,6 +6,7 @@ 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 @@ -15,59 +16,149 @@ 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 com.intellij.testGuiFramework.util.step 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 org.fest.swing.fixture.JTreeRowFixture import org.fest.swing.timing.Condition -import org.fest.swing.timing.Pause 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 +import kotlin.test.fail @RunWithIde(CommunityIde::class) class InspectorTest : GuiTestCase() { - @Test - fun importSimpleProject() { + //@Test + fun widgetTree() { ProjectCreator.importProject() - println("DEBUG 1") ideFrame { launchFlutterApp() - println("DEBUG 2") val inspector = flutterInspectorFixture(this) inspector.populate() - println("DEBUG 3 $inspector") val widgetTree = inspector.widgetTreeFixture() - println("DEBUG 4 $widgetTree") val inspectorTree = widgetTree.inspectorTreeFixture() - println("DEBUG 5 $inspectorTree") - inspectorTree.selectRow(1) - println("DEBUG 6 ${inspectorTree.selection()}") - Pause.pause(2000) - println("DEBUG 7 ${inspectorTree.selection()}") + 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() } } + @Test + fun hotReload() { + ProjectCreator.importProject() + ideFrame { + launchFlutterApp() + val inspector = flutterInspectorFixture(this) + inspector.populate() + var widgetTree = inspector.widgetTreeFixture() + var inspectorTree = widgetTree.inspectorTreeFixture() + inspectorTree.selectRow(0) + var detailsTree = widgetTree.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) + // Work around https://github.com/flutter/flutter-intellij/issues/3370 + inspector.renderTreeFixture() // The refresh button is broken so force tree update by switching views. + inspector.populate() + widgetTree = inspector.widgetTreeFixture() // And back to the one we want + inspectorTree = widgetTree.inspectorTreeFixture() + inspectorTree.selectRow(6) + detailsTree = widgetTree.inspectorTreeFixture(isDetails = true) + 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() + } + } + + private fun findHotReloadButton(): ActionButtonFixture { + return findActionButtonByClassName("ReloadFlutterAppRetarget") + } + private fun IdeFrameFixture.runner(): ExecutionToolWindowFixture.ContentFixture { return runToolWindow.findContent("main.dart") } 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") + step("Select Simulator") { + selectDevice("Open iOS Simulator") + } } fun IdeFrameFixture.selectDevice(devName: String) { @@ -81,8 +172,8 @@ class InspectorTest : GuiTestCase() { 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!!, "") -// comboBoxActionFixture.selectItem(devName) + // val comboBoxActionFixture = ComboBoxActionFixture.findComboBoxByText(robot(), actionToolbarContainer!!, "") + // 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("") val comboBoxButtonFixture = JButtonFixture(robot(), selector.target()) @@ -95,10 +186,19 @@ class InspectorTest : GuiTestCase() { robot().waitForIdle() } - 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. + var button: ActionButtonFixture? = null + ideFrame { + button = ActionButtonFixture.fixtureByActionId(target().parent, robot(), actionId) + } + return button!! + } + + private fun findActionButtonByClassName(className: String): ActionButtonFixture { var button: ActionButtonFixture? = null ideFrame { - button = ActionButtonFixture.fixtureByActionId(target(), robot(), actionId) + button = ActionButtonFixture.fixtureByActionClassName(target(), robot(), className) } return button!! } diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt index a33177a2d8..b623c13626 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt @@ -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 @@ -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 { @@ -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) } } @@ -127,8 +135,10 @@ 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() + } } } } @@ -136,7 +146,9 @@ class ProjectCreator(guiTestCase: GuiTestCase) : TestUtilsClass(guiTestCase) { private fun GuiTestCase.openPubspecInProject() { ideFrame { projectView { - path(project.name, "pubspec.yaml").doubleClick() + step("Open pubspec.yaml") { + path(project.name, "pubspec.yaml").doubleClick() + } } } } diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt index 38a443bc43..e97f8659a1 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt @@ -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 @@ -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() + } } } } diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt index 576374e793..2e0780da00 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt @@ -12,6 +12,8 @@ import org.junit.runner.RunWith import org.junit.runners.Suite //* gradle -Dtest.single=TestSuite clean test -Didea.gui.test.alternativeIdePath="" +// 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() diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt index eba8283b95..c668d01923 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt @@ -6,15 +6,21 @@ package io.flutter.tests.gui.fixtures +import com.intellij.execution.ui.layout.impl.JBRunnerTabs import com.intellij.openapi.project.Project import com.intellij.openapi.util.Ref import com.intellij.testGuiFramework.fixtures.IdeFrameFixture +import com.intellij.testGuiFramework.fixtures.JComponentFixture import com.intellij.testGuiFramework.fixtures.ToolWindowFixture import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.testGuiFramework.impl.GuiRobotHolder.robot import com.intellij.testGuiFramework.matcher.ClassNameMatcher -import com.intellij.util.ui.EdtInvocationManager +import com.intellij.testGuiFramework.util.step +import com.intellij.ui.tabs.TabInfo +import com.intellij.ui.tabs.impl.TabLabel import io.flutter.inspector.InspectorService import io.flutter.inspector.InspectorTree +import io.flutter.view.FlutterView import io.flutter.view.InspectorPanel import junit.framework.Assert.assertNotNull import org.fest.swing.core.ComponentFinder @@ -24,7 +30,6 @@ import org.fest.swing.timing.Condition import org.fest.swing.timing.Pause import org.fest.swing.timing.Pause.pause import java.awt.Component -import java.awt.event.MouseEvent import javax.swing.JPanel import javax.swing.JTree import javax.swing.tree.TreePath @@ -33,23 +38,34 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra : ToolWindowFixture("Flutter Inspector", project, robot) { fun populate() { - activate() - selectedContent - Pause.pause(object : Condition("Initialize inspector") { - override fun test(): Boolean { - return contents[0].displayName != null - } - }, Timeouts.seconds30) + step("Populate inspector tree") { + activate() + selectedContent + Pause.pause(object : Condition("Initialize inspector") { + override fun test(): Boolean { + return contents[0].displayName != null + } + }, Timeouts.seconds30) + } } fun widgetTreeFixture(): InspectorPanelFixture { + showTab(0) return inspectorPanel(InspectorService.FlutterTreeType.widget) } fun renderTreeFixture(): InspectorPanelFixture { + showTab(1) return inspectorPanel(InspectorService.FlutterTreeType.renderObject) } + private fun showTab(index : Int) { + val tabs : JBRunnerTabs = contents[0].component.components[0] as JBRunnerTabs + val info : TabInfo = tabs.getTabAt(index) + val label = tabs.getTabLabel(info) + TabLabelFixture(robot, label).click() + } + private fun finder(): ComponentFinder { return ideFrame.robot().finder() } @@ -81,12 +97,12 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra inner class InspectorPanelFixture(val inspectorPanel: InspectorPanel) { - fun inspectorTreeFixture(): InspectorTreeFixture { + fun inspectorTreeFixture(isDetails: Boolean = false): InspectorTreeFixture { val inspectorTreeRef = Ref() pause(object : Condition("Tree shows up") { override fun test(): Boolean { - val inspectorTree = findInspectorTree() + val inspectorTree = findInspectorTree(isDetails) inspectorTreeRef.set(inspectorTree) return inspectorTree != null } @@ -97,34 +113,53 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra return InspectorTreeFixture(inspectorTree) } - fun findInspectorTree(): InspectorTree? { + fun findInspectorTree(isDetails: Boolean): InspectorTree? { val trees = finder().findAll(inspectorPanel, classMatcher("io.flutter.inspector.InspectorTree", JTree::class.java)) - val tree = trees.firstOrNull { it is InspectorTree && !it.detailsSubtree } + val tree = trees.firstOrNull { it is InspectorTree && isDetails == it.detailsSubtree } if (tree != null) return tree as InspectorTree else return null } } inner class InspectorTreeFixture(val inspectorTree: InspectorTree) { - fun treeFixture() : JTreeFixture { + fun treeFixture(): JTreeFixture { return JTreeFixture(ideFrame.robot(), inspectorTree) } - fun selectRow(number: Int) { - pause(object : Condition("Tree has content") { - override fun test(): Boolean { - return inspectorTree.rowCount > 0 - } - }, Timeouts.seconds05) -// val click = MouseEvent(inspectorTree, 0, 0L, 0, 100, 30, 1, false) -// EdtInvocationManager.getInstance().invokeAndWait() { -// inspectorTree.dispatchEvent(click) -// } - treeFixture().clickRow(number) + fun selectRow(number: Int, expand: Boolean = true) { + waitForContent() + treeFixture().clickRow(number) // This should not collapse the tree, but it does. + if (expand) { + treeFixture().expandRow(number) // TODO(messick) Remove when selection preserves tree expansion. + } } fun selection(): TreePath? { return inspectorTree.selectionPath } + + fun selectionSync(): TreePath { + waitForContent() + waitForCondition("Selection is set") { inspectorTree.selectionPath != null } + return selection()!! + } + + private fun waitForContent() { + waitForCondition("Tree has content") { inspectorTree.rowCount > 1 } + } + + private fun waitForCondition(description: String, condition: () -> Boolean): Boolean { + var result: Boolean = false + pause(object : Condition(description) { + override fun test(): Boolean { + result = condition.invoke() + return result + } + }, Timeouts.seconds05) + return result + } } } + +class TabLabelFixture(robot: Robot, target: TabLabel) + : JComponentFixture(TabLabelFixture::class.java, robot, target) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt index 7022ace910..63371ea010 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt @@ -43,15 +43,16 @@ class FlutterMessagesToolWindowFixture(project: Project, robot: Robot) : ToolWin } private fun doFindMessage(matcher: String, timeout: Timeout): ConsoleViewImpl { - return GuiTestUtil.waitUntilFound(robot(), myContent.component, object : GenericTypeMatcher(ConsoleViewImpl::class.java) { - override fun isMatching(panel: ConsoleViewImpl): Boolean { - if (panel.javaClass.name.startsWith(ConsoleViewImpl::class.java.name) && panel.isShowing) { - val doc = panel.editor.document - return (doc.text.contains(matcher)) - } - return false - } - }, timeout) + return GuiTestUtil.waitUntilFound(robot(), myContent.component, + object : GenericTypeMatcher(ConsoleViewImpl::class.java) { + override fun isMatching(panel: ConsoleViewImpl): Boolean { + if (panel.javaClass.name.startsWith(ConsoleViewImpl::class.java.name) && panel.isShowing) { + val doc = panel.editor.document + return (doc.text.contains(matcher)) + } + return false + } + }, timeout) } } From 90883c6589a0af14f1a517a42668b47c60692edc Mon Sep 17 00:00:00 2001 From: Steve Messick Date: Tue, 16 Apr 2019 13:46:37 -0700 Subject: [PATCH 3/3] checkpoint --- .../io/flutter/tests/gui/InspectorTest.kt | 80 +++++-------------- .../testSrc/io/flutter/tests/gui/TestSuite.kt | 2 +- .../gui/fixtures/FlutterInspectorFixture.kt | 33 ++++++-- .../FlutterMessagesToolWindowFixture.kt | 2 +- 4 files changed, 46 insertions(+), 71 deletions(-) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt index ba49d6ebb2..9d6dec5e32 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt @@ -13,35 +13,27 @@ 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 com.intellij.testGuiFramework.util.step -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 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 -import kotlin.test.fail @RunWithIde(CommunityIde::class) class InspectorTest : GuiTestCase() { - //@Test + @Test 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 } @@ -86,10 +78,10 @@ class InspectorTest : GuiTestCase() { launchFlutterApp() val inspector = flutterInspectorFixture(this) inspector.populate() - var widgetTree = inspector.widgetTreeFixture() - var inspectorTree = widgetTree.inspectorTreeFixture() - inspectorTree.selectRow(0) - var detailsTree = widgetTree.inspectorTreeFixture(isDetails = true) + 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 { @@ -114,13 +106,13 @@ class InspectorTest : GuiTestCase() { return reload.isEnabled } }, Timeouts.seconds05) - // Work around https://github.com/flutter/flutter-intellij/issues/3370 - inspector.renderTreeFixture() // The refresh button is broken so force tree update by switching views. - inspector.populate() - widgetTree = inspector.widgetTreeFixture() // And back to the one we want - inspectorTree = widgetTree.inspectorTreeFixture() - inspectorTree.selectRow(6) - detailsTree = widgetTree.inspectorTreeFixture(isDetails = true) + 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() @@ -135,14 +127,6 @@ class InspectorTest : GuiTestCase() { } } - private fun findHotReloadButton(): ActionButtonFixture { - return findActionButtonByClassName("ReloadFlutterAppRetarget") - } - - private fun IdeFrameFixture.runner(): ExecutionToolWindowFixture.ContentFixture { - return runToolWindow.findContent("main.dart") - } - fun IdeFrameFixture.launchFlutterApp() { step("Launch Flutter app") { findRunApplicationButton().click() @@ -155,35 +139,12 @@ class InspectorTest : GuiTestCase() { } } - fun IdeFrameFixture.selectSimulator() { - step("Select Simulator") { - selectDevice("Open iOS Simulator") - } + private fun findHotReloadButton(): ActionButtonFixture { + return findActionButtonByClassName("ReloadFlutterAppRetarget") } - fun IdeFrameFixture.selectDevice(devName: String) { - val runButton = findRunApplicationButton() - val actionToolbarContainer = execute(object : GuiQuery() { - @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!!, "") - // 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("") - 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 findActionButtonByActionId(actionId: String): ActionButtonFixture { @@ -196,6 +157,7 @@ class InspectorTest : GuiTestCase() { } 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) @@ -203,8 +165,4 @@ class InspectorTest : GuiTestCase() { return button!! } - fun IdeFrameFixture.flutterInspectorFixture(ideFrame: IdeFrameFixture): FlutterInspectorFixture { - return FlutterInspectorFixture(project, robot(), ideFrame) - } - } diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt index 2e0780da00..9b117b6555 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt @@ -15,5 +15,5 @@ import org.junit.runners.Suite // 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) +@Suite.SuiteClasses(SmokeTest::class, InspectorTest::class) class TestSuite : GuiTestSuite() diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt index c668d01923..1b2156c25d 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt @@ -20,7 +20,6 @@ import com.intellij.ui.tabs.TabInfo import com.intellij.ui.tabs.impl.TabLabel import io.flutter.inspector.InspectorService import io.flutter.inspector.InspectorTree -import io.flutter.view.FlutterView import io.flutter.view.InspectorPanel import junit.framework.Assert.assertNotNull import org.fest.swing.core.ComponentFinder @@ -34,6 +33,11 @@ import javax.swing.JPanel import javax.swing.JTree import javax.swing.tree.TreePath +fun IdeFrameFixture.flutterInspectorFixture(ideFrame: IdeFrameFixture): FlutterInspectorFixture { + return FlutterInspectorFixture(project, robot(), ideFrame) +} + +// A fixture for the Inspector top-level view. class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFrame: IdeFrameFixture) : ToolWindowFixture("Flutter Inspector", project, robot) { @@ -49,7 +53,7 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra } } - fun widgetTreeFixture(): InspectorPanelFixture { + fun widgetsFixture(): InspectorPanelFixture { showTab(0) return inspectorPanel(InspectorService.FlutterTreeType.widget) } @@ -59,9 +63,9 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra return inspectorPanel(InspectorService.FlutterTreeType.renderObject) } - private fun showTab(index : Int) { - val tabs : JBRunnerTabs = contents[0].component.components[0] as JBRunnerTabs - val info : TabInfo = tabs.getTabAt(index) + private fun showTab(index: Int) { + val tabs: JBRunnerTabs = contents[0].component.components[0] as JBRunnerTabs + val info: TabInfo = tabs.getTabAt(index) val label = tabs.getTabLabel(info) TabLabelFixture(robot, label).click() } @@ -87,7 +91,7 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra val notificationPanel = inspectorPanelRef.get() assertNotNull(notificationPanel) - return InspectorPanelFixture(notificationPanel) + return InspectorPanelFixture(notificationPanel, type) } private fun findInspectorPanel(type: InspectorService.FlutterTreeType): InspectorPanel? { @@ -95,7 +99,18 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra return panels.firstOrNull { it is InspectorPanel && it.treeType == type && !it.isDetailsSubtree } as InspectorPanel } - inner class InspectorPanelFixture(val inspectorPanel: InspectorPanel) { + // The InspectorPanel is a little tricky to work with. In order for the tree to have content its view must be made + // visible (either Widgets or Render Tree). However, we don't want to return a reference to an empty tree, because + // of timing issues. We use the fact that the tree has content as a signal to move on to the next step. + inner class InspectorPanelFixture(val inspectorPanel: InspectorPanel, val type: InspectorService.FlutterTreeType) { + + fun show() { + showTab(tabIndex()) + } + + private fun tabIndex(): Int { + return if (type == InspectorService.FlutterTreeType.widget) 0 else 1 + } fun inspectorTreeFixture(isDetails: Boolean = false): InspectorTreeFixture { val inspectorTreeRef = Ref() @@ -120,7 +135,8 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra } } - inner class InspectorTreeFixture(val inspectorTree: InspectorTree) { + // This fixture is used to access the tree for both Widgets and Render Objects. + inner class InspectorTreeFixture(private val inspectorTree: InspectorTree) { fun treeFixture(): JTreeFixture { return JTreeFixture(ideFrame.robot(), inspectorTree) @@ -161,5 +177,6 @@ class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFra } } +// A clickable fixture for the three tabs in the inspector: Widgets, Render Tree, and Performance. class TabLabelFixture(robot: Robot, target: TabLabel) : JComponentFixture(TabLabelFixture::class.java, robot, target) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt index 63371ea010..96d9b7a588 100644 --- a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt @@ -29,7 +29,7 @@ class FlutterMessagesToolWindowFixture(project: Project, robot: Robot) : ToolWin inner class FlutterContentFixture(val myContent: Content) { - fun findMessageContainingText(text: String, timeout: Timeout = Timeouts.seconds10): FlutterMessageFixture { + fun findMessageContainingText(text: String, timeout: Timeout = Timeouts.seconds30): FlutterMessageFixture { val element = doFindMessage(text, timeout) return createFixture(element) }