[UI Tests] Fixed bug where tests could not click menus nested in more than one level.

Change-Id: Ib983dbed2549caf7794a7ec197196ae72c165589
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java
index 1d536ea..7f00216 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/EditorFixture.java
@@ -917,8 +917,7 @@
       }
     });
     if (visible == null || !visible) {
-      // This doesn't work; second nested menu item isn't clicked
-      //myFrame.invokeMenuPath("View", "Tool Windows", "Preview");
+      myFrame.invokeMenuPath("View", "Tool Windows", "Preview");
     }
 
     pause(new Condition("Preview window is visible") {
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeFrameFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeFrameFixture.java
index 39468ee..96fd398 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeFrameFixture.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeFrameFixture.java
@@ -74,7 +74,6 @@
 import javax.swing.*;
 import java.awt.*;
 import java.io.File;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -438,8 +437,7 @@
    * @param path the series of menu names, e.g. {@link invokeActionByMenuPath("Build", "Make Project")}
    */
   public void invokeMenuPath(@NotNull String... path) {
-    JMenuItem menuItem = findActionMenuItem(path);
-    robot().click(menuItem);
+    getMenuFixture().invokeMenuPath(path);
   }
 
   /**
@@ -450,37 +448,12 @@
    * @param path the series of menu name regular expressions, e.g. {@link invokeActionByMenuPath("Build", "Make( Project)?")}
    */
   public void invokeMenuPathRegex(@NotNull String... path) {
-    JMenuItem menuItem = findActionMenuItem(true, path);
-    robot().click(menuItem);
+    getMenuFixture().invokeMenuPathRegex(path);
   }
 
   @NotNull
-  private JMenuItem findActionMenuItem(@NotNull String... path) {
-    return findActionMenuItem(false, path);
-  }
-
-  @NotNull
-  private JMenuItem findActionMenuItem(final boolean pathIsRegex, @NotNull String... path) {
-    assertThat(path).isNotEmpty();
-    int segmentCount = path.length;
-    Container root = target();
-    for (int i = 0; i < segmentCount; i++) {
-      final String segment = path[i];
-      assertNotNull(root);
-      JMenuItem found = robot().finder().find(root, new GenericTypeMatcher<JMenuItem>(JMenuItem.class) {
-        @Override
-        protected boolean isMatching(@NotNull JMenuItem menuItem) {
-          return pathIsRegex ? menuItem.getText().matches(segment) : segment.equals(menuItem.getText());
-        }
-      });
-      if (i < segmentCount - 1) {
-        robot().click(found);
-        root = robot().findActivePopupMenu();
-        continue;
-      }
-      return found;
-    }
-    throw new AssertionError("Menu item with path " + Arrays.toString(path) + " should have been found already");
+  private MenuFixture getMenuFixture() {
+    return new MenuFixture(robot(), target());
   }
 
   @NotNull
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuFixture.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuFixture.java
new file mode 100644
index 0000000..6114e18
--- /dev/null
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuFixture.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.tests.gui.framework.fixture;
+
+import com.google.common.collect.Lists;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.wm.impl.IdeFrameImpl;
+import org.fest.swing.core.GenericTypeMatcher;
+import org.fest.swing.core.Robot;
+import org.fest.swing.timing.Condition;
+import org.fest.swing.timing.Pause;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.util.Lists.newArrayList;
+import static org.junit.Assert.assertNotNull;
+
+class MenuFixture {
+  @NotNull private final Robot myRobot;
+  @NotNull private final IdeFrameImpl myContainer;
+
+  MenuFixture(@NotNull Robot robot, @NotNull IdeFrameImpl container) {
+    myRobot = robot;
+    myContainer = container;
+  }
+
+  /**
+   * Invokes an action by menu path
+   *
+   * @param path the series of menu names, e.g. {@link invokeActionByMenuPath("Build", "Make Project ")}
+   */
+  void invokeMenuPath(@NotNull String... path) {
+    JMenuItem menuItem = findActionMenuItem(false, path);
+    myRobot.click(menuItem);
+  }
+
+  /**
+   * Invokes an action by menu path (where each segment is a regular expression). This is particularly
+   * useful when the menu items can change dynamically, such as the labels of Undo actions, Run actions,
+   * etc.
+   *
+   * @param path the series of menu name regular expressions, e.g. {@link invokeActionByMenuPath("Build", "Make( Project)?")}
+   */
+  void invokeMenuPathRegex(@NotNull String... path) {
+    JMenuItem menuItem = findActionMenuItem(true, path);
+    myRobot.click(menuItem);
+  }
+
+  @NotNull
+  private JMenuItem findActionMenuItem(final boolean pathIsRegex, @NotNull String... path) {
+    assertThat(path).isNotEmpty();
+    int segmentCount = path.length;
+
+    // We keep the list of previously found pop-up menus, so we don't look for menu items in the same pop-up more than once.
+    List<JPopupMenu> previouslyFoundPopups = Lists.newArrayList();
+
+    Container root = myContainer;
+    for (int i = 0; i < segmentCount; i++) {
+      final String segment = path[i];
+      assertNotNull(root);
+      JMenuItem found = myRobot.finder().find(root, new GenericTypeMatcher<JMenuItem>(JMenuItem.class) {
+        @Override
+        protected boolean isMatching(@NotNull JMenuItem menuItem) {
+          return pathIsRegex ? menuItem.getText().matches(segment) : segment.equals(menuItem.getText());
+        }
+      });
+      if (root instanceof JPopupMenu) {
+        previouslyFoundPopups.add((JPopupMenu)root);
+      }
+      if (i < segmentCount - 1) {
+        myRobot.click(found);
+        List<JPopupMenu> showingPopupMenus = findShowingPopupMenus(i + 1);
+        showingPopupMenus.removeAll(previouslyFoundPopups);
+        assertThat(showingPopupMenus).hasSize(1);
+        root = showingPopupMenus.get(0);
+        continue;
+      }
+      return found;
+    }
+    throw new AssertionError("Menu item with path " + Arrays.toString(path) + " should have been found already");
+  }
+
+  @NotNull
+  private List<JPopupMenu> findShowingPopupMenus(final int expectedCount) {
+    final Ref<List<JPopupMenu>> ref = new Ref<List<JPopupMenu>>();
+    Pause.pause(new Condition("waiting for " + expectedCount + " JPopupMenus to show up") {
+      @Override
+      public boolean test() {
+        List<JPopupMenu> popupMenus = newArrayList(myRobot.finder().findAll(new GenericTypeMatcher<JPopupMenu>(JPopupMenu.class) {
+          @Override
+          protected boolean isMatching(@NotNull JPopupMenu popupMenu) {
+            return popupMenu.isShowing();
+          }
+        }));
+        boolean allFound = popupMenus.size() == expectedCount;
+        if (allFound) {
+          ref.set(popupMenus);
+        }
+        return allFound;
+      }
+    });
+    List<JPopupMenu> popupMenus = ref.get();
+    assertThat(popupMenus).isNotNull().hasSize(expectedCount);
+    return popupMenus;
+  }
+}