/** * */ package com.google.gdt.eclipse.swtbot; import com.google.gdt.eclipse.swtbot.conditions.TreeCollapsedCondition; import com.google.gdt.eclipse.swtbot.conditions.TreeExpandedCondition; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.results.WidgetResult; import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; public class SwtBotTreeActions { /** * Given a tree that contains an entry with <code>itemName</code> and a direct child with a name * matching <code>subchildName</code>, return its tree item. * * This method is useful when there is the possibility of a tree having two similarly-named * top-level nodes. * * @param mainTree the tree * @param itemName the name of a top-level node in the tree * @param subchildName the name of a direct child of the top-level node (used to uniquely select * the appropriate tree item for the given top-level node name) * @return the tree item corresponding to the top-level node with <code>itemName</code>that has a * direct child with <code>subchildName</code>. If there are multiple tree items that * satisfy this criteria, then the first one (in the UI) will be returned * * @throws IllegalStateException if no such node can be found */ public static SWTBotTreeItem getUniqueTreeItem(final SWTBot bot, final SWTBotTree mainTree, String itemName, String subchildName) { for (SWTBotTreeItem item : mainTree.getAllItems()) { if (itemName.equals(item.getText())) { try { item.expand(); SwtBotTreeActions.waitUntilTreeHasText(bot, item); if (item.getNode(subchildName) != null) { return item; } } catch (WidgetNotFoundException e) { // Ignore } } } throw new IllegalStateException("The '" + itemName + "' node with a child of '" + subchildName + "' must exist in the tree."); } /** * Given a tree that contains an entry with one of <code>itemNames</code> and a direct child with * a name matching <code>subchildName</code>, return its tree item. * * This method is useful when the top-level names are ambiguous and/or variable. * * @param mainTree the tree * @param itemNames possible names of a top-level node in the tree * @param subchildName the name of a direct child of the top-level node (used to uniquely select * the appropriate tree item for the given top-level node name) * @return the tree item corresponding to the top-level node with one of <code>itemNames</code> * that has a direct child with <code>subchildName</code>. If there are multiple tree * items that satisfy this criteria, then the first one (in the UI) will be returned * * @throws IllegalStateException if no such node can be found */ public static SWTBotTreeItem getUniqueTreeItem(final SWTBot bot, final SWTBotTree mainTree, String[] itemNames, String subchildName) { for (String itemName : itemNames) { try { return getUniqueTreeItem(bot, mainTree, itemName, subchildName); } catch (IllegalStateException e) { // Ignore } } throw new IllegalStateException("One of the '" + itemNames + "' nodes with a child of '" + subchildName + "' must exist in the tree."); } /** * Gets the item matching the given name from a tree item. * * @param widget the tree item to search * @param nodeText the text on the node. * @return the child tree item with the specified text. */ public static TreeItem getTreeItem(final TreeItem widget, final String nodeText) { return UIThreadRunnable.syncExec(new WidgetResult<TreeItem>() { @Override public TreeItem run() { TreeItem[] items = widget.getItems(); for (TreeItem item : items) { if (item.getText().equals(nodeText)) { return item; } } return null; } }); } private static boolean waitUntilTreeHasItemImpl(SWTBot bot, final TreeItem tree, final String nodeText) { try { bot.waitUntil(new DefaultCondition() { @Override public String getFailureMessage() { return "Could not find node with text " + nodeText; } @Override public boolean test() throws Exception { return getTreeItem(tree, nodeText) != null; } }); } catch (TimeoutException e) { return false; } return true; } private static boolean waitUntilTreeHasTextImpl(SWTBot bot, final TreeItem tree) { try { bot.waitUntil(new DefaultCondition() { @Override public String getFailureMessage() { return "Not all of the nodes in the tree have text."; } @Override public boolean test() throws Exception { return doesTreeItemHaveText(tree); } }); } catch (TimeoutException e) { return false; } return true; } /** * Blocks the caller until the tree item has the given item text. * * @param tree the tree item to search * @param nodeText the item text to look for * @throws org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException if the item could not * be found within the timeout period */ private static void waitUntilTreeItemHasItem(SWTBot bot, final SWTBotTreeItem tree, final String nodeText) { // Attempt #1 if (!waitUntilTreeHasItemImpl(bot, tree.widget, nodeText)) { // Attempt #2: Something went wrong, try to cautiously reopen it. bot.sleep(1000); // There isn't a method to collapse, so double-click instead tree.doubleClick(); bot.waitUntil(new TreeCollapsedCondition(tree.widget)); bot.sleep(1000); tree.expand(); bot.waitUntil(new TreeExpandedCondition(tree.widget)); if (!waitUntilTreeHasItemImpl(bot, tree.widget, nodeText)) { printTree(tree.widget); throw new TimeoutException( String.format("Timed out waiting for %s, giving up...", nodeText)); } } } private static boolean doesTreeItemHaveText(final TreeItem widget) { return UIThreadRunnable.syncExec(new Result<Boolean>() { @Override public Boolean run() { TreeItem[] items = widget.getItems(); for (TreeItem item : items) { if (item.getText() == null || item.getText().length() == 0) { return false; } } return true; } }); } /** * Helper method to check whether a given tree is expanded which can be called from any thread. */ public static boolean isTreeExpanded(final TreeItem tree) { return UIThreadRunnable.syncExec(new Result<Boolean>() { @Override public Boolean run() { return tree.getExpanded(); } }); } private static void printTree(final TreeItem tree) { UIThreadRunnable.syncExec(new VoidResult() { @Override public void run() { System.err.println(String.format("%s has %d items:", tree.getText(), tree.getItemCount())); for (TreeItem item : tree.getItems()) { System.err.println(String.format(" - %s", item.getText())); } } }); } /** * Blocks the caller until all of the direct children of the tree have text. The assumption is * that the tree does not have any "empty" children. * * TODO: Refactor some of this logic; it follows the same general pattern as * {@link #waitUntilTreeItemHasItem(SWTBot, SWTBotTreeItem, String)}. * * @param tree the tree to search * @throws TimeoutException if all of the direct children of the tree do not have text within the * timeout period */ public static void waitUntilTreeHasText(SWTBot bot, final SWTBotTreeItem tree) throws TimeoutException { // Attempt #1 if (!waitUntilTreeHasTextImpl(bot, tree.widget)) { // Attempt #2: Something went wrong, try to cautiously reopen it. bot.sleep(1000); // There isn't a method to collapse, so double-click instead tree.doubleClick(); bot.waitUntil(new TreeCollapsedCondition(tree.widget)); bot.sleep(1000); tree.expand(); bot.waitUntil(new TreeExpandedCondition(tree.widget)); if (!waitUntilTreeHasTextImpl(bot, tree.widget)) { printTree(tree.widget); throw new TimeoutException( "Timed out waiting for text of the tree's children, giving up..."); } } } public static SWTBotTreeItem selectTreeItem(SWTBot bot, SWTBotTreeItem tree, String itemText) { waitUntilTreeItemHasItem(bot, tree, itemText); SWTBotTreeItem item = tree.select(itemText); return item; } }