/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.springsource.ide.eclipse.commons.frameworks.test.util; import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.withRegex; import static org.eclipse.swtbot.swt.finder.waits.Conditions.waitForShell; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.List; import junit.framework.Assert; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.model.IProcess; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotPerspective; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.finders.ContextMenuFinder; import org.eclipse.swtbot.swt.finder.finders.MenuFinder; 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.utils.SWTBotPreferences; import org.eclipse.swtbot.swt.finder.utils.SWTUtils; import org.eclipse.swtbot.swt.finder.utils.TableCollection; import org.eclipse.swtbot.swt.finder.utils.TableRow; import org.eclipse.swtbot.swt.finder.waits.Conditions; import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; import org.eclipse.swtbot.swt.finder.waits.ICondition; import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition; import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot; import org.eclipse.swtbot.swt.finder.widgets.SWTBotList; import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; import org.eclipse.ui.PlatformUI; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; /** * Utility methods that are useful in writing SWTBot tests. * @author Kris De Volder * @author Nieraj Singh * @author Tomasz Zarna */ public class SWTBotUtils { public static final long PROCESS_COMPLETION_TIMEOUT = 60000; //Needs enough time to finish running grails command like compile -non-interactive public static final long WAIT_FOR_BUILD_TIMEOUT = 10000; /** * Open a particular perspective, or try to activate it if it can't be * opened (maybe it already open) */ public static void openPerspective(SWTWorkbenchBot bot, String perspectiveLabel) { SWTBotShell shell = null; try { menu(bot, "Window").menu("Open Perspective").menu("Other...") .click(); shell = bot.shell("Open Perspective"); // SWTBotUtils.screenshot("openPerspective_"+perspectiveLabel); assertTrue(shell.isOpen()); bot.waitUntil(SWTBotUtils.widgetMakeActive(shell)); shell.bot().table().select(perspectiveLabel); shell.bot().button("OK").click(); bot.waitUntil(Conditions.shellCloses(shell)); } catch (Exception e) { if (shell != null && shell.isOpen()) shell.close(); System.err.println("Couldn't open perspective '" + perspectiveLabel + "'\n" + "trying to activate already open perspective instead"); // maybe somehow the perspective is already opened (by another test // before us) SWTBotPerspective perspective = bot .perspectiveByLabel(perspectiveLabel); perspective.activate(); assertTrue(perspective.isActive()); } Assert.assertEquals(perspectiveLabel, bot.activePerspective() .getLabel()); } /** * @return */ public static Shell getMainShell() { Shell mainWidget = UIThreadRunnable.syncExec(new Result<Shell>() { public Shell run() { return PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getShell(); } }); return mainWidget; } /** * Create an SWTBot {@link ICondition} that tests whether a given resource * exists. * * @param pathToResource * Path starting from workspace root. For example * "kris/src/Foo.java" would refer to file "Foo.java" in the * "src" directory of project "kris". Path's ending with trailing * "/" will be considered to point to "IFolder" path without * trailing "/" will be treated as "IFile" references. Paths with * no "/" at all will be treated as references to a project. * @return */ public static ResourceExists resourceExists(String pathToResource) { return new ResourceExists(pathToResource); } /** * Wait for resource to be created. * * @param pathToResource * Path starting from workspace root. For example * "kris/src/Foo.java" would refer to file "Foo.java" in the * "src" directory of project "kris". Path's ending with trailing * "/" will be considered to point to "IFolder" path without * trailing "/" will be treated as "IFile" references. Paths with * on "/" at all will be treated as references to a project. */ public static void waitResourceCreated(SWTBot bot, String pathToResource, long timeOut) { bot.waitUntil(resourceExists(pathToResource), timeOut); } /** * Wait for resource to be created. * * @param pathToResource * Path starting from workspace root. For example * "kris/src/Foo.java" would refer to file "Foo.java" in the * "src" directory of project "kris". Path's ending with trailing * "/" will be considered to point to "IFolder" path without * trailing "/" will be treated as "IFile" references. Paths with * on "/" at all will be treated as references to a project. */ public static void waitResourceCreated(SWTBot bot, String pathToResource) { waitResourceCreated(bot, pathToResource, SWTBotPreferences.TIMEOUT); } /** * @return a List of {@link Shell} under a parent. * @param parent * the parent under which a shell will be found. */ public static List<Shell> shells(SWTBotShell parent) { Matcher<Shell> anyShell = widgetOfType(Shell.class); WaitForObjectCondition<Shell> waitForShell = waitForShell(anyShell, parent.widget); parent.bot().waitUntilWidgetAppears(waitForShell); List<Shell> allShells = waitForShell.getAllMatches(); System.err.println(">>> SWTBot Shells found:"); for (Shell shell : allShells) { SWTBotShell bot = new SWTBotShell(shell); System.err.println(" '" + bot.getText() + "'"); } System.err.println("<<< SWTBot Shells found:"); return allShells; } /** * @return the first shell that is present or appears under a given parent. * @param parent * the parent under which the shell will be found. */ public static SWTBotShell shell(SWTBotShell parentShell) { return new SWTBotShell(shells(parentShell).get(0)); } /** * Wait for a shell with the given regular expression. * * @param bot the SWTBot * @param regex the regular expression */ public static void waitForShellWithRegex(SWTBot bot, String regex) { Matcher<Shell> withRegex = withRegex(regex); WaitForObjectCondition<Shell> waitForShell = waitForShell(withRegex); bot.waitUntilWidgetAppears(waitForShell); } public static void doubleClick(SWTWorkbenchBot bot, final SWTBotTable table, int row, int col) { table.click(row, col); // Note: table.doubleClick() method doesn't work, table appears not to // be listening to the events that are being posted by this method. // Use this workaround instead: // http://dev.eclipse.org/mhonarc/newsLists/news.eclipse.swtbot/msg00309.html UIThreadRunnable.asyncExec(bot.getDisplay(), new VoidResult() { public void run() { table.widget.notifyListeners(SWT.DefaultSelection, new Event()); } }); } /** * A better version of the "menu" method from SWTBot. This one will retry to * get the active shell on each poll, rather than get the active shell at * the start and then get stuck using the wrong shell. */ public static SWTBotMenu menu(SWTWorkbenchBot bot, String name) { return new SWTBotMenu(SWTBotConditions.waitForShellMenuList(bot, name, true).get(0)); } /** * Given a view name containing a tree, this method will attempt to select a * node contained within a parent. * * @param bot * containing shell with a tree * @param shellName * of the shell containing tree * @param parentName * parent containing the node to be selected * @param childName * node to be selected * @return the selected node */ public static SWTBotTreeItem selectChildTreeElement(SWTBot bot, String shellName, String parentName, String childName) { SWTBotShell shell = bot.shell(shellName); shell.activate(); SWTBotTreeItem parentNode = SWTBotConditions.waitForNodeExpanded(bot, parentName); SWTBotTreeItem childNode = SWTBotConditions.waitChildNodeToAppear(bot, parentNode, childName); return childNode.select(); } /** * Gets a view by name. It assumes the view MUST be opened, although not * necessarily active. Open the view first if necessary. * * @return Active, focused explorer view */ public static SWTBotView getView(SWTWorkbenchBot bot, String name) { SWTBotView explorer = bot.viewByTitle(name); explorer.setFocus(); return explorer; } /** * Selects a project in the currently opened view. A view must be opened * prior to using this method. * * @param projectName * @return */ public static SWTBotTree selectProject(SWTWorkbenchBot bot, String projectName, String viewName) { SWTBotView explorer = getView(bot, viewName); SWTBotTree tree = explorer.bot().tree(); if (projectName != null) { tree.select(projectName); waitForSelection(bot, projectName, tree); return tree; } else { tree.select(new String[0]); waitForSelection(bot, "", tree); return tree; } } /** * Sometimes widgets respond slowly to selection and key presses. Use this * method to wait for a selection to become what you expect it to be. * Otherwise, trying to proceed will probably fail the test. */ public static void waitForSelection(SWTWorkbenchBot bot, final String expected, final SWTBotList list) { bot.waitUntil(new DefaultCondition() { public boolean test() throws Exception { String selection = getSelection(); return expected.equals(selection); } private String getSelection() { String[] selections = list.selection(); String selection = null; if (selections.length == 1) selection = selections[0]; return selection; } public String getFailureMessage() { return "Was expecting selection '" + expected + " but found '" + getSelection() + "'"; } }); } /** * Wait for a selection to become what you expect it to be. This method * assumes a SWTBotTree that only has a single column selection. */ public static void waitForSelection(SWTWorkbenchBot bot, final String expected, final SWTBotTree tree) { bot.waitUntil(new DefaultCondition() { public boolean test() throws Exception { String selection = getSelection(tree); return expected.equals(selection); } public String getFailureMessage() { return "Was expecting selection \n----\n" + expected + "\n----\nbut found \n----\n" + getSelection(tree) + "\n-----"; } }); } public static String getSelection(SWTBotTree tree) { TableCollection selections = tree.selection(); String selection = ""; for (int r = 0; r < selections.rowCount(); r++) { if (r > 0) selection += "\n"; TableRow row = selections.get(r); for (int c = 0; c < row.columnCount(); c++) { if (c > 0) selection += " >> "; selection += row.get(c); } } return selection; } private static int screenshotNumber = 0; /** * Call this method to create extra screenshots along the way. A message * will be printed to System.err when the screenshot is taken (look in error * log to see which screenshot file was taken when). Scrreenshots will * include a sequence number at the start of their name that makes it easy * to see in what order they were taken. * * @filename The screenshot will be save as "screenshots/filename_<number>" */ public static void screenshot(String fileName) { fileName = "screenshots/" + screenshotNumber() + fileName + "." + SWTBotPreferences.SCREENSHOT_FORMAT.toLowerCase(); System.err.println("Creating extra screenshot: " + fileName); new File("screenshots").mkdirs(); //$NON-NLS-1$ SWTUtils.captureScreenshot(fileName); } private static String screenshotNumber() { return String.format("%03d_", screenshotNumber++); } public static void waitForAllBuildJobs(SWTBot bot){ waitForAllProcessesToTerminate(bot); waitForJobCompletions(); bot.sleep(WAIT_FOR_BUILD_TIMEOUT); waitForJobCompletions(); } private static void waitForJobCompletions() { StsTestUtil.waitForAutoBuild(); StsTestUtil.waitForManualBuild(); StsTestUtil.waitForJobFamily(ResourcesPlugin.FAMILY_AUTO_REFRESH); StsTestUtil.waitForJobFamily(ResourcesPlugin.FAMILY_MANUAL_REFRESH); } public static void waitForAllProcessesToTerminate(SWTBot bot) { waitForAllProcessesToTerminate(bot, PROCESS_COMPLETION_TIMEOUT); } public static void waitForAllProcessesToTerminate(SWTBot bot, long timeout) { bot.waitUntil(new ICondition() { private IProcess activeProcess = null; public boolean test() throws Exception { IProcess[] processes = DebugPlugin.getDefault().getLaunchManager().getProcesses(); for (IProcess process : processes) { if (!process.isTerminated()) { print("Waiting for processes..."); print("active = "+process.getClass()+ " " + process.getLabel()); activeProcess = process; return false; } } print("Waiting for processes... DONE"); return true; } public void init(SWTBot bot) { } public String getFailureMessage() { return "A process is still active:" + "\n label = " + activeProcess.getLabel() + "\n type = " + activeProcess.getAttribute(IProcess.ATTR_PROCESS_TYPE) + "\n cmd = " + activeProcess.getAttribute(IProcess.ATTR_CMDLINE); } }, timeout); } /** * This is only for debugging purposes, in case having trouble finding the * menus... */ public static List<MenuItem> findAllContextMenus(Shell shell) { MenuFinder menuFinder = new ContextMenuFinder(shell); return menuFinder.findMenus(shell, anyMenuMatcher, true); } private static final Matcher<MenuItem> anyMenuMatcher = new BaseMatcher<MenuItem>() { public boolean matches(Object item) { boolean result = item instanceof MenuItem; if (result) { MenuItem menu = (MenuItem) item; System.out.println("Found menu with text : '" + menu.getText() + "'"); } return result; } public void describeTo(Description description) { description.appendText("Any menu"); } }; /** * @param explorerTree * @return */ public static ICondition widgetMakeActive( final AbstractSWTBot<? extends Widget> widget) { return new ICondition() { public boolean test() throws Exception { widget.setFocus(); return widget.isActive(); } public void init(SWTBot bot) { } public String getFailureMessage() { return "Widget not active: " + widget; } }; } private static boolean enablePrinting = true; public static void enablePrinting() { enablePrinting = true; } public static void disablePrinting() { enablePrinting = false; } public static void print(String message) { if (enablePrinting) { System.out.println(message); } } public static ICondition widgetIsDisposed( AbstractSWTBot<? extends Widget> widget) { return new WidgetIsDisposed(widget); } public static String getConsoleText(SWTWorkbenchBot bot) { SWTBotView console = bot.viewByTitle("Console"); SWTBotStyledText textWidget = console.bot().styledText(); return textWidget.getText(); } /** * Repeatedly try to find a submenu that contains a given String, * until the menu appears, or until timeout happens. */ public static SWTBotMenu subMenuContaining(SWTBot bot, SWTBotMenu parentMenu, String text) { Matcher<MenuItem> matcher = SWTBotConditions.withTextContaining(text); SubMenusMatching condition = new SubMenusMatching(bot, parentMenu, matcher); return new SWTBotMenu(condition.getMenus().get(0)); } }