0001: package abbot.tester;
0002:
0003: import java.awt.AWTEvent;
0004: import java.awt.Component;
0005: import java.awt.Frame;
0006: import java.awt.Point;
0007: import java.awt.event.*;
0008: import java.lang.reflect.Method;
0009: import java.util.ArrayList;
0010: import java.util.Arrays;
0011: import java.util.HashMap;
0012: import java.util.HashSet;
0013: import java.util.Set;
0014:
0015: import javax.accessibility.AccessibleContext;
0016: import javax.accessibility.AccessibleIcon;
0017:
0018: import abbot.BugReport;
0019: import abbot.Log;
0020: import abbot.WaitTimedOutError;
0021: import abbot.finder.AWTHierarchy;
0022: import abbot.finder.BasicFinder;
0023: import abbot.finder.ComponentFinder;
0024: import abbot.finder.ComponentNotFoundException;
0025: import abbot.finder.ComponentSearchException;
0026: import abbot.finder.Hierarchy;
0027: import abbot.finder.MultipleComponentsFoundException;
0028: import abbot.finder.matchers.WindowMatcher;
0029: import abbot.i18n.Strings;
0030: import abbot.script.ComponentReference;
0031: import abbot.util.*;
0032:
0033: /** Provides basic programmatic operation of a {@link Component} and related
0034: UI objects such as windows, menus and menu bars throuh action methods.
0035: Also provides some useful assertions about properties of a {@link
0036: Component}.
0037: <p>
0038: There are two sets of event-generating methods. The internal, protected
0039: methods inherited from {@link abbot.tester.Robot abbot.tester.Robot} are
0040: for normal programmatic use within derived Tester classes. No event queue
0041: synchronization should be performed except when modifying a component for
0042: which results are required for the action itself.
0043: <p>
0044: The public <code>actionXXX</code> functions are meant to be invoked from a
0045: script or directly from a hand-written test. These actions are
0046: distinguished by name, number of arguments, and by argument type. The
0047: actionX methods will be synchronized with the event dispatch thread when
0048: invoked, so you should only do synchronization with waitForIdle when you
0049: depend on the results of a particular event prior to sending the next one
0050: (e.g. scrolling a table cell into view before selecting it).
0051: All public action methods should ensure that the actions they
0052: trigger are finished on return, or will be finished before any subsequent
0053: actions are requested.
0054: <p>
0055: <i>Action</i> methods generally represent user-driven actions such
0056: as menu selection, table selection, popup menus, etc. All actions should
0057: have the following signature:
0058: <blockquote>
0059: <code>public void actionSpinMeRoundLikeARecord(Component c, ...);</code>
0060: <code>public void actionPinchMe(Component c, ComponentLocation loc);</code>
0061: </blockquote>
0062: <p>
0063: It is essential that the argument is of type {@link Component}; if you use
0064: a more-derived class, then the actual invocation becomes ambiguous since
0065: method parsing doesn't attempt to determine which identically-named method
0066: is the most-derived.
0067: <p>
0068: The {@link ComponentLocation} abstraction allows all derived tester
0069: classes to inherit click, popup menu, and drag variants without having to
0070: explicitly define new methods for component-specific substructures. The
0071: new class need only define the {@link #parseLocation(String)} method,
0072: which should return a location specific to the component in question.
0073: <p>
0074: <i>Assertions</i> are either independent of any component (and should be
0075: implemented in this class), or take a component as the first argument, and
0076: perform some check on that component. All assertions should have one of
0077: the following signatures:
0078: <blockquote>
0079: <code>public boolean assertMyself(...);</code><br>
0080: <code>public boolean assertBorderIsAtrociouslyUgly(Component c, ...);</code>
0081: </blockquote>
0082: Note that these assertions do not throw exceptions but rather return a
0083: <code>boolean</code> value indicating success or failure. Normally these
0084: assertions will be wrapped by an abbot.script.Assert step if you want to
0085: cause a test failure, or you can manually throw the proper failure
0086: exception if the method returns false.
0087: <p>
0088: <i>Property checks</i> may also be implemented in cases where the component
0089: "property" might not be readily available or easily comparable, e.g.
0090: see {@link abbot.tester.JPopupMenuTester#getMenuLabels(Component)}.
0091: <blockquote>
0092: <code>public Object getHairpiece(Component c);</code><br>
0093: <code>public boolean isRighteouslyIndignant(Component c);</code>
0094: </blockquote>
0095: Any non-property methods with the property signature, should be added to
0096: the {@link #IGNORED_METHODS} set, since property-like methods are scanned
0097: dynamically to populate the {@link abbot.editor.ScriptEditor editor}'s
0098: action menus.
0099: <p>
0100: <a name=Customization>
0101: <h2>Extending ComponentTester</h2>
0102: Following are the steps required to implement a Tester object for a custom
0103: class.
0104: <ul>
0105: <li><h3>Create the Tester Class</h3>
0106: Derive from this class to implement actions and assertions specific to
0107: a given component class. Testers for any classes found in the JRE
0108: (i.e. in the {@link java.awt} or {@link javax.swing} packages) should be
0109: in the {@link abbot.tester abbot.tester} package. Extensions (testers for
0110: any <code>Component</code> subclasses not found in the JRE) must be in the
0111: <code>abbot.tester.extensions</code> package and be
0112: named the name of the <code>Component</code> subclass followed by "Tester".
0113: For example, the {@link javax.swing.JButton javax.swing.JButton} tester
0114: class is {@link JButtonTester abbot.tester.JButtonTester}, and a tester
0115: for <code>org.me.PR0NViewer</code> would be
0116: <code>abbot.tester.extensions.PR0NViewerTester</code>.
0117: <li><h3>Add Action Methods</h3>
0118: Add <code>action</code> methods which effect user actions.
0119: See the section on <a href=Naming>naming conventions</a> below.
0120: <li><h3>Add Assertion Methods</h3>
0121: Add <code>assert</code> methods to access attributes not readily available
0122: as properties<br>
0123: <li><h3>Add Substructure Support</h3>
0124: Add a corresponding <code>ComponentLocation</code> implementation to
0125: handle any substructure present in your <code>Component</code>.
0126: See {@link ComponentLocation} for details.
0127: <li><h3>Add Substructure-handling Methods</h3>
0128: <ul>
0129: <li>{@link #parseLocation(String)}<br>
0130: Convert the given <code>String</code> into an instance of
0131: {@link ComponentLocation} specific to your Tester. Here is an example
0132: implementation from {@link JListTester}:<br>
0133: <code><pre>
0134: public ComponentLocation parseLocation(String encoded) {
0135: return new JListLocation().parse(encoded);
0136: }
0137: </pre></code>
0138: <li>{@link #getLocation(Component,Point)}<br>
0139: Use the given <code>Point</code> to create a {@link ComponentLocation}
0140: instance appropriate for the <code>Component</code> substructure
0141: available at that location. For example, on a {@link javax.swing.JList},
0142: you would return a {@link JListLocation} based on the
0143: stringified value at that location, the row/index at that
0144: location, or the raw <code>Point</code>,
0145: in order of preference. If a stringified value is unavailable,
0146: fall back to an indexed position; if that is not possible (if, for
0147: instance, the location is outside the list contents), return a
0148: {@link ComponentLocation} based on the raw <code>Point</code>.
0149: </ul>
0150: <li><h3>Set Editor Properties</h3>
0151: Add-on tester classes should set the following system properties so that
0152: the actions provided by their tester can be properly displayed in the
0153: script editor. For an action <code>actionWiggle</code> provided by class
0154: <code>abbot.tester.extensions.PR0NViewerTester</code>, the following
0155: properties should be defined:<br>
0156: <ul>
0157: <li><code>actionWiggle.menu</code> short name for Insert menu
0158: <li><code>actionWiggle.desc</code> short description (optional)
0159: <li><code>actionWiggle.icon</code> icon for the action (optional)
0160: <li><code>PR0NViewerTester.actionWiggle.args</code> javadoc-style
0161: description of method, displayed when asking the user for its arguments
0162: </ul>
0163: Since these properties are global, if your Tester class defines a method
0164: which is also defined by another Tester class, you must supply the class
0165: name as a prefix to the property.
0166: </ul>
0167: <p>
0168: <a name=MethodNaming>
0169: <h2>Method Naming Conventions</h2>
0170: Action methods should be named according to the human-centric action that
0171: is being performed if at all possible. For example, use
0172: <code>actionSelectRow</code> in a List rather than
0173: <code>actionSelectIndex</code>. If there's no common usage, or if the
0174: usage is too vague or diverse, use the specific terms used in code.
0175: For example, {@link JScrollBarTester} uses
0176: {@link JScrollBarTester#actionScrollUnitUp(Component) actionScrollUnitUp()}
0177: and
0178: {@link JScrollBarTester#actionScrollBlockUp(Component) actionScrollBlockUp()};
0179: since there is no common language usage for these
0180: concepts (line and page exist, but are not appropriate if what is scrolled
0181: is not text).
0182: <p>
0183: When naming a selection method, include the logical substructure target of
0184: the selection in the name (e.g.
0185: {@link JTableTester#actionSelectCell(Component,JTableLocation)}
0186: JTableTester.actionSelectCell()),
0187: since some components may have more than one type of selectable item
0188: within them.
0189: */
0190: public class ComponentTester extends Robot {
0191:
0192: /** Add any method names here which should <em>not</em> show up in a
0193: dynamically generated list of property methods.
0194: */
0195: protected static Set IGNORED_METHODS = new HashSet();
0196:
0197: static {
0198: // Omit from method lookup deprecated methods or others we want to
0199: // ignore
0200: IGNORED_METHODS.addAll(Arrays.asList(new String[] {
0201: "actionSelectAWTMenuItemByLabel",
0202: "actionSelectAWTPopupMenuItemByLabel", "getTag",
0203: "getTester", "isOnPopup", "getLocation", }));
0204: }
0205:
0206: /** Maps class names to their corresponding Tester object. */
0207: private static HashMap testers = new HashMap();
0208:
0209: /** Establish the given ComponentTester as the one to use for the given
0210: * class. This may be used to override the default tester for a given
0211: * core class. Note that this will only work with components loaded by
0212: * the framework class loader, not those loaded by the class loader for
0213: * the code under test.
0214: */
0215: public static void setTester(Class forClass, ComponentTester tester) {
0216: testers.put(forClass.getName(), tester);
0217: }
0218:
0219: /** Return a shared instance of the appropriate
0220: <code>ComponentTester</code> for the given {@link Component}.<p>
0221: This method is primarily used by the scripting system, since the
0222: appropriate <code>ComponentTester</code> for a given {@link Component}
0223: is not known <i>a priori</i>. Coded unit tests should generally just
0224: create an instance of the desired type of <code>ComponentTester</code>
0225: for local use.
0226: */
0227: public static ComponentTester getTester(Component comp) {
0228: return comp != null ? getTester(comp.getClass())
0229: : getTester(Component.class);
0230: }
0231:
0232: /** Return a shared instance of the corresponding
0233: <code>ComponentTester</code> object for the given {@link Component}
0234: class, chaining up the inheritance tree if no specific tester is found
0235: for that class.<p>
0236: The abbot tester package is searched first, followed by the tester
0237: extensions package.<p>
0238: This method is primarily used by the scripting system, since the
0239: appropriate <code>ComponentTester</code> for a given Component is not
0240: known <i>a priori</i>. Coded unit tests should generally just create
0241: an instance of the desired type of <code>ComponentTester</code> for
0242: local use.
0243: */
0244: public static ComponentTester getTester(Class componentClass) {
0245: String className = componentClass.getName();
0246: ComponentTester tester = (ComponentTester) testers
0247: .get(className);
0248: if (tester == null) {
0249: if (!Component.class.isAssignableFrom(componentClass)) {
0250: String msg = "Class " + className
0251: + " is not derived from java.awt.Component";
0252: throw new IllegalArgumentException(msg);
0253: }
0254: Log.debug("Looking up tester for " + componentClass);
0255: String testerName = simpleClassName(componentClass)
0256: + "Tester";
0257: Package pkg = ComponentTester.class.getPackage();
0258: String pkgName = "";
0259: if (pkg == null) {
0260: Log.warn("ComponentTester.class has null package; "
0261: + "the class loader is likely flawed: "
0262: + ComponentTester.class.getClassLoader()
0263: + ", "
0264: + Thread.currentThread()
0265: .getContextClassLoader(),
0266: Log.FULL_STACK);
0267: pkgName = "abbot.tester";
0268: } else {
0269: pkgName = pkg.getName();
0270: }
0271: if (className.startsWith("javax.swing.")
0272: || className.startsWith("java.awt.")) {
0273: tester = findTester(pkgName + "." + testerName,
0274: componentClass);
0275: }
0276: if (tester == null) {
0277: tester = findTester(pkgName + ".extensions."
0278: + testerName, componentClass);
0279: if (tester == null) {
0280: tester = getTester(componentClass.getSuperclass());
0281: }
0282: }
0283: if (tester != null && !tester.isExtension()) {
0284: // Only cache it if it's part of the standard framework,
0285: // but cache it for every level that we looked up, so we
0286: // don't repeat the effort.
0287: testers.put(componentClass.getName(), tester);
0288: }
0289: }
0290:
0291: return tester;
0292: }
0293:
0294: /** Return whether this tester is an extension. */
0295: public final boolean isExtension() {
0296: return getClass().getName().startsWith(
0297: "abbot.tester.extensions");
0298: }
0299:
0300: /** Look up the given class, using special class loading rules to maintain
0301: framework consistency. */
0302: private static Class resolveClass(String testerName,
0303: Class componentClass) throws ClassNotFoundException {
0304: // Extension testers must be loaded in the context of the code under
0305: // test.
0306: Class cls;
0307: if (testerName.startsWith("abbot.tester.extensions")) {
0308: cls = Class.forName(testerName, true, componentClass
0309: .getClassLoader());
0310: } else {
0311: cls = Class.forName(testerName);
0312: }
0313: Log.debug("Loaded class " + testerName + " with "
0314: + cls.getClassLoader());
0315: return cls;
0316: }
0317:
0318: /** Look up the given class with a specific class loader. */
0319: private static ComponentTester findTester(String testerName,
0320: Class componentClass) {
0321: ComponentTester tester = null;
0322: Class testerClass = null;
0323: try {
0324: testerClass = resolveClass(testerName, componentClass);
0325: tester = (ComponentTester) testerClass.newInstance();
0326: } catch (InstantiationException ie) {
0327: Log.warn(ie);
0328: } catch (IllegalAccessException iae) {
0329: Log.warn(iae);
0330: } catch (ClassNotFoundException cnf) {
0331: //Log.debug("Class " + testerName + " not found");
0332: } catch (ClassCastException cce) {
0333: throw new BugReport("Class loader conflict: environment "
0334: + ComponentTester.class.getClassLoader() + " vs. "
0335: + testerClass.getClassLoader());
0336: }
0337:
0338: return tester;
0339: }
0340:
0341: /** Derive a tag from the given accessible context if possible, or return
0342: * null.
0343: */
0344: protected String deriveAccessibleTag(AccessibleContext context) {
0345: String tag = null;
0346: if (context != null) {
0347: if (context.getAccessibleName() != null) {
0348: tag = context.getAccessibleName();
0349: }
0350: if ((tag == null || "".equals(tag))
0351: && context.getAccessibleIcon() != null
0352: && context.getAccessibleIcon().length > 0) {
0353: AccessibleIcon[] icons = context.getAccessibleIcon();
0354: tag = icons[0].getAccessibleIconDescription();
0355: if (tag != null) {
0356: tag = tag.substring(tag.lastIndexOf("/") + 1);
0357: tag = tag.substring(tag.lastIndexOf("\\") + 1);
0358: }
0359: }
0360: }
0361: return tag;
0362: }
0363:
0364: /** Default methods to use to derive a component-specific tag. These are
0365: * things that will probably be useful in a custom component if the method
0366: * is supported.
0367: */
0368: private static final String[] tagMethods = { "getLabel",
0369: "getTitle", "getText", };
0370:
0371: /** Return a reasonable identifier for the given component.
0372: Note that this should not duplicate information provided by existing
0373: attributes such as title, label, or text.
0374: This functionality is provided to assist in identification of
0375: non-standard GUI components only.
0376: */
0377: public static String getTag(Component comp) {
0378: return getTester(comp.getClass()).deriveTag(comp);
0379: }
0380:
0381: /** Returns whether the given class is not a core JRE class. */
0382: protected boolean isCustom(Class c) {
0383: return !(c.getName().startsWith("javax.swing.") || c.getName()
0384: .startsWith("java.awt."));
0385: }
0386:
0387: /** Determine a component-specific identifier not already defined in other
0388: * component attributes. This method should be defined to something
0389: * unique for custom UI components.
0390: */
0391: public String deriveTag(Component comp) {
0392: // If the component class is custom, don't provide a tag
0393: if (isCustom(comp.getClass()))
0394: return null;
0395:
0396: Method m;
0397: String tag = null;
0398: // Try a few default methods
0399: for (int i = 0; i < tagMethods.length; i++) {
0400: // Don't use getText on text components
0401: if (((comp instanceof javax.swing.text.JTextComponent) || (comp instanceof java.awt.TextComponent))
0402: && "getText".equals(tagMethods[i])) {
0403: continue;
0404: }
0405: try {
0406: m = comp.getClass().getMethod(tagMethods[i], null);
0407: String tmp = (String) m.invoke(comp, null);
0408: // Don't ever use empty strings for tags
0409: if (tmp != null && !"".equals(tmp)) {
0410: tag = tmp;
0411: break;
0412: }
0413: } catch (Exception e) {
0414: }
0415: }
0416: // In the absence of any other tag, try to derive one from something
0417: // recognizable on one of its ancestors.
0418: if (tag == null || "".equals(tag)) {
0419: Component parent = comp.getParent();
0420: if (parent != null) {
0421: String ptag = getTag(parent);
0422: if (ptag != null && !"".equals(tag)) {
0423: // Don't use the tag if it's simply the window title; that
0424: // doesn't provide any extra information.
0425: if (!ptag.endsWith(" Root Pane")) {
0426: StringBuffer buf = new StringBuffer(ptag);
0427: int under = ptag.indexOf(" under ");
0428: if (under != -1)
0429: buf = buf.delete(0, under + 7);
0430: buf.insert(0, " under ");
0431: buf.insert(0, simpleClassName(comp.getClass()));
0432: tag = buf.toString();
0433: }
0434: }
0435: }
0436: }
0437:
0438: return tag;
0439: }
0440:
0441: /**
0442: * Wait for an idle AWT event queue. Will return when there are no more
0443: * events on the event queue.
0444: */
0445: public void actionWaitForIdle() {
0446: waitForIdle();
0447: }
0448:
0449: /** Delay the given number of ms. */
0450: public void actionDelay(int ms) {
0451: delay(ms);
0452: }
0453:
0454: /** @deprecated Renamed to {@link #actionSelectAWTMenuItem(Frame,String)}.
0455: */
0456: public void actionSelectAWTMenuItemByLabel(Frame menuFrame,
0457: String path) {
0458: actionSelectAWTMenuItem(menuFrame, path);
0459: }
0460:
0461: /** Selects an AWT menu item ({@link java.awt.MenuItem}) and returns when
0462: the invocation has triggered (though not necessarily completed).
0463: @param menuFrame
0464: @param path either a unique label or the menu path.
0465: @see Robot#selectAWTMenuItem(Frame,String)
0466: */
0467: public void actionSelectAWTMenuItem(Frame menuFrame, String path) {
0468: AWTMenuListener listener = new AWTMenuListener();
0469: new WeakAWTEventListener(listener,
0470: ActionEvent.ACTION_EVENT_MASK);
0471: selectAWTMenuItem(menuFrame, path);
0472: long start = System.currentTimeMillis();
0473: while (!listener.eventFired) {
0474: if (System.currentTimeMillis() - start > defaultDelay) {
0475: throw new ActionFailedException("Menu item '" + path
0476: + "' failed to fire");
0477: }
0478: sleep();
0479: }
0480: waitForIdle();
0481: }
0482:
0483: /** @deprecated Renamed to
0484: {@link #actionSelectAWTPopupMenuItem(Component,String)}.
0485: */
0486: public void actionSelectAWTPopupMenuItemByLabel(Component invoker,
0487: String path) {
0488: actionSelectAWTPopupMenuItem(invoker, path);
0489: }
0490:
0491: /** Selects an AWT menu item and returns when the invocation has triggered
0492: (though not necessarily completed).
0493: @see Robot#selectAWTPopupMenuItem(Component,String)
0494: */
0495: public void actionSelectAWTPopupMenuItem(Component invoker,
0496: String path) {
0497: AWTMenuListener listener = new AWTMenuListener();
0498: new WeakAWTEventListener(listener,
0499: ActionEvent.ACTION_EVENT_MASK);
0500: selectAWTPopupMenuItem(invoker, path);
0501: long start = System.currentTimeMillis();
0502: while (!listener.eventFired) {
0503: if (System.currentTimeMillis() - start > defaultDelay) {
0504: throw new ActionFailedException("Menu item '" + path
0505: + "' failed to fire");
0506: }
0507: sleep();
0508: }
0509: waitForIdle();
0510: }
0511:
0512: /** Select the given menu item. */
0513: public void actionSelectMenuItem(Component item) {
0514: Log.debug("Attempting to select menu item " + toString(item));
0515: selectMenuItem(item);
0516: waitForIdle();
0517: }
0518:
0519: /** Select the given menu item, given its path and a component on the same
0520: window.
0521: */
0522: public void actionSelectMenuItem(Component sameWindow, String path) {
0523: Log.debug("Attempting to select menu item '" + path + "'");
0524: selectMenuItem(sameWindow, path);
0525: waitForIdle();
0526: }
0527:
0528: /** Pop up a menu at the center of the given component and
0529: select the given item.
0530: */
0531: public void actionSelectPopupMenuItem(Component invoker, String path) {
0532: actionSelectPopupMenuItem(invoker, invoker.getWidth() / 2,
0533: invoker.getHeight() / 2, path);
0534: }
0535:
0536: /** Pop up a menu at the given location on the given component and
0537: select the given item.
0538: */
0539: public void actionSelectPopupMenuItem(Component invoker,
0540: ComponentLocation loc, String path) {
0541: selectPopupMenuItem(invoker, loc, path);
0542: waitForIdle();
0543: }
0544:
0545: /** Pop up a menu at the given coordinates on the given component and
0546: select the given item.
0547: */
0548: public void actionSelectPopupMenuItem(Component invoker, int x,
0549: int y, String path) {
0550: actionSelectPopupMenuItem(invoker, new ComponentLocation(
0551: new Point(x, y)), path);
0552: }
0553:
0554: /** Pop up a menu in the center of the given component. */
0555: public void actionShowPopupMenu(Component invoker) {
0556: actionShowPopupMenu(invoker, new ComponentLocation());
0557: }
0558:
0559: /** Pop up a menu in the center of the given component. */
0560: public void actionShowPopupMenu(Component invoker,
0561: ComponentLocation loc) {
0562: Point where = loc.getPoint(invoker);
0563: showPopupMenu(invoker, where.x, where.y);
0564: }
0565:
0566: /** Pop up a menu at the given location on the given component.
0567: */
0568: public void actionShowPopupMenu(Component invoker, int x, int y) {
0569: showPopupMenu(invoker, x, y);
0570: }
0571:
0572: /** Click on the center of the component. */
0573: public void actionClick(Component comp) {
0574: actionClick(comp, new ComponentLocation());
0575: }
0576:
0577: /** Click on the component at the given location. */
0578: public void actionClick(Component c, ComponentLocation loc) {
0579: actionClick(c, loc, InputEvent.BUTTON1_MASK);
0580: }
0581:
0582: /** Click on the component at the given location with the given
0583: modifiers.
0584: */
0585: public void actionClick(Component c, ComponentLocation loc,
0586: int buttons) {
0587: actionClick(c, loc, buttons, 1);
0588: }
0589:
0590: /** Click on the component at the given location, with the given
0591: * modifiers and click count.
0592: */
0593: public void actionClick(Component c, ComponentLocation loc,
0594: int buttons, int count) {
0595: Point where = loc.getPoint(c);
0596: click(c, where.x, where.y, buttons, count);
0597: waitForIdle();
0598: }
0599:
0600: /** Click on the component at the given location (mouse button 1).
0601: */
0602: public void actionClick(Component comp, int x, int y) {
0603: actionClick(comp, new ComponentLocation(new Point(x, y)));
0604: }
0605:
0606: /** Click on the component at the given location. The buttons mask
0607: * should be the InputEvent masks for the desired button, e.g.
0608: * {@link InputEvent#BUTTON1_MASK}. ComponentTester also defines
0609: * {@link AWTConstants#POPUP_MASK} and {@link AWTConstants#TERTIARY_MASK} for
0610: * platform-independent masks for those buttons. Key modifiers other
0611: * than {@link InputEvent#ALT_MASK} and {@link InputEvent#META_MASK} may
0612: * also be used as a convenience.
0613: */
0614: public void actionClick(Component comp, int x, int y, int buttons) {
0615: actionClick(comp, x, y, buttons, 1);
0616: }
0617:
0618: /** Click on the component at the given location, specifying buttons and
0619: * the number of clicks. The buttons mask should be the InputEvent mask
0620: * for the desired button, e.g.
0621: * {@link InputEvent#BUTTON1_MASK}. ComponentTester also defines
0622: * {@link AWTConstants#POPUP_MASK} and {@link AWTConstants#TERTIARY_MASK} for
0623: * platform-independent masks for those buttons. Key modifiers other
0624: * than {@link InputEvent#ALT_MASK} and {@link InputEvent#META_MASK} may
0625: * also be used as a convenience.
0626: */
0627: public void actionClick(Component comp, int x, int y, int buttons,
0628: int count) {
0629: actionClick(comp, new ComponentLocation(new Point(x, y)),
0630: buttons, count);
0631: }
0632:
0633: /** Send a key press event for the given virtual key code to the given
0634: Component.
0635: */
0636: public void actionKeyPress(Component comp, int keyCode) {
0637: actionFocus(comp);
0638: actionKeyPress(keyCode);
0639: }
0640:
0641: /** Generate a key press event for the given virtual key code. */
0642: public void actionKeyPress(int keyCode) {
0643: keyPress(keyCode);
0644: waitForIdle();
0645: }
0646:
0647: /** Generate a key release event sent to the given component. */
0648: public void actionKeyRelease(Component comp, int keyCode) {
0649: actionFocus(comp);
0650: actionKeyRelease(keyCode);
0651: }
0652:
0653: /** Generate a key release event. */
0654: public void actionKeyRelease(int keyCode) {
0655: keyRelease(keyCode);
0656: waitForIdle();
0657: }
0658:
0659: /** Set the state of the given modifier keys. */
0660: public void actionSetModifiers(int mask, boolean pressed) {
0661: setModifiers(mask, pressed);
0662: waitForIdle();
0663: }
0664:
0665: /**
0666: * Send the given keystroke, which must be the KeyEvent field name of a
0667: * KeyEvent VK_ constant to the program. Sends a key down/up, with no
0668: * modifiers. The focus is changed to the target component.
0669: */
0670: public void actionKeyStroke(Component c, int keyCode) {
0671: actionFocus(c);
0672: actionKeyStroke(keyCode, 0);
0673: }
0674:
0675: /**
0676: * Send the given keystroke, which must be the KeyEvent field name of a
0677: * KeyEvent VK_ constant. Sends a key press and key release, with no
0678: * modifiers. Note that this method does not affect the current focus.
0679: */
0680: public void actionKeyStroke(int keyCode) {
0681: actionKeyStroke(keyCode, 0);
0682: }
0683:
0684: /**
0685: * Send the given keystroke, which must be the KeyEvent field name of a
0686: * KeyEvent VK_ constant to the program. Sends a key down/up, with the
0687: * given modifiers, which should be the InputEvent field name modifier
0688: * masks optionally ORed together with "|". The focus is changed to the
0689: * target component.<p>
0690: */
0691: public void actionKeyStroke(Component c, int keyCode, int modifiers) {
0692: actionFocus(c);
0693: actionKeyStroke(keyCode, modifiers);
0694: }
0695:
0696: /**
0697: * Send the given keystroke, which must be the KeyEvent field name of a
0698: * KeyEvent VK_ constant to the program. Sends a key down/up, with the
0699: * given modifiers, which should be the InputEvent field name modifier
0700: * masks optionally ORed together with "|". Note that this does not
0701: * affect the current focus.<p>
0702: */
0703: public void actionKeyStroke(int keyCode, int modifiers) {
0704: if (Bugs.hasKeyStrokeGenerationBug()) {
0705: int oldDelay = getAutoDelay();
0706: setAutoDelay(50);
0707: key(keyCode, modifiers);
0708: setAutoDelay(oldDelay);
0709: delay(100);
0710: } else {
0711: key(keyCode, modifiers);
0712: }
0713: waitForIdle();
0714: }
0715:
0716: /** Send events required to generate the given string on the given
0717: * component. This version is preferred over
0718: * {@link #actionKeyString(String)}.
0719: */
0720: public void actionKeyString(Component c, String string) {
0721: actionFocus(c);
0722: actionKeyString(string);
0723: }
0724:
0725: /** Send events required to generate the given string. */
0726: public void actionKeyString(String string) {
0727: keyString(string);
0728: // FIXME waitForIdle isn't always sufficient on OSX with key events
0729: if (Bugs.hasKeyStrokeGenerationBug()) {
0730: delay(100);
0731: }
0732: waitForIdle();
0733: }
0734:
0735: /** Set the focus on to the given component. */
0736: public void actionFocus(Component comp) {
0737: focus(comp, true);
0738: }
0739:
0740: /** Move the mouse/pointer to the given location. */
0741: public void actionMouseMove(Component comp, ComponentLocation loc) {
0742: Point where = loc.getPoint(comp);
0743: mouseMove(comp, where.x, where.y);
0744: waitForIdle();
0745: }
0746:
0747: /** Press mouse button 1 at the given location. */
0748: public void actionMousePress(Component comp, ComponentLocation loc) {
0749: actionMousePress(comp, loc, InputEvent.BUTTON1_MASK);
0750: }
0751:
0752: /** Press mouse buttons corresponding to the given mask at the given
0753: location.
0754: */
0755: public void actionMousePress(Component comp, ComponentLocation loc,
0756: int mask) {
0757: Point where = loc.getPoint(comp);
0758: mousePress(comp, where.x, where.y, mask);
0759: waitForIdle();
0760: }
0761:
0762: /** Release any currently held mouse buttons. */
0763: public void actionMouseRelease() {
0764: mouseRelease();
0765: waitForIdle();
0766: }
0767:
0768: /** Perform a drag action. Derived classes may provide more specific
0769: * identifiers for what is being dragged simply by deriving a new class
0770: * from ComponentLocation which identifies the appropriate substructure.
0771: * For instance, a {@link JTreeLocation} might encapsulate the expansion
0772: * control location for the path [root, node A, child B].
0773: */
0774: public void actionDrag(Component dragSource, ComponentLocation loc) {
0775: actionDrag(dragSource, loc, "BUTTON1_MASK");
0776: }
0777:
0778: /** Perform a drag action. Grabs the center of the given component. */
0779: public void actionDrag(Component dragSource) {
0780: actionDrag(dragSource, new ComponentLocation());
0781: }
0782:
0783: /** Perform a drag action with the given modifiers.
0784: * @param dragSource source of the drag
0785: * @param loc identifies where on the given {@link Component} to begin the
0786: * drag.
0787: * @param buttons a <code>String</code> representation of key modifiers,
0788: * e.g. "ALT|SHIFT", based on the {@link InputEvent#ALT_MASK InputEvent
0789: * <code>_MASK</code> fields}.
0790: * @deprecated Use the
0791: * {@link #actionDrag(Component,ComponentLocation,int) integer modifier mask}
0792: * version instead.
0793: */
0794: public void actionDrag(Component dragSource, ComponentLocation loc,
0795: String buttons) {
0796: actionDrag(dragSource, loc, AWT.getModifiers(buttons));
0797: }
0798:
0799: /** Perform a drag action with the given modifiers.
0800: * @param dragSource source of the drag
0801: * @param loc identifies where on the given {@link Component} to begin the
0802: * drag.
0803: * @param buttons one or more of the
0804: * {@link InputEvent#BUTTON1_MASK InputEvent <code>BUTTONN_MASK</code>
0805: * fields}.
0806: */
0807: public void actionDrag(Component dragSource, ComponentLocation loc,
0808: int buttons) {
0809: Point where = loc.getPoint(dragSource);
0810: drag(dragSource, where.x, where.y, buttons);
0811: waitForIdle();
0812: }
0813:
0814: /** Perform a drag action. Grabs at the given explicit location. */
0815: public void actionDrag(Component dragSource, int sx, int sy) {
0816: actionDrag(dragSource, new ComponentLocation(new Point(sx, sy)));
0817: }
0818:
0819: /** Perform a drag action. Grabs at the given location with the given
0820: * modifiers.
0821: * @param dragSource source of the drag
0822: * @param sx X coordinate
0823: * @param sy Y coordinate
0824: * @param buttons a <code>String</code> representation of key modifiers,
0825: * e.g. "ALT|SHIFT", based on the {@link InputEvent#ALT_MASK InputEvent
0826: * <code>_MASK</code> fields}.
0827: * @deprecated Use the
0828: * {@link #actionDrag(Component,ComponentLocation,int) ComponentLocation/
0829: * integer modifier mask} version instead.
0830: */
0831: public void actionDrag(Component dragSource, int sx, int sy,
0832: String buttons) {
0833: actionDrag(dragSource,
0834: new ComponentLocation(new Point(sx, sy)), buttons);
0835: }
0836:
0837: /** Drag the current dragged object over the given component. */
0838: public void actionDragOver(Component target) {
0839: actionDragOver(target, new ComponentLocation());
0840: }
0841:
0842: /** Drag the current dragged object over the given target/location. */
0843: public void actionDragOver(Component target, ComponentLocation where) {
0844: Point loc = where.getPoint(target);
0845: dragOver(target, loc.x, loc.y);
0846: waitForIdle();
0847: }
0848:
0849: /** Perform a basic drop action (implicitly causing preceding mouse
0850: * drag motion). Drops in the center of the component.
0851: */
0852: public void actionDrop(Component dropTarget) {
0853: actionDrop(dropTarget, new ComponentLocation());
0854: }
0855:
0856: /** Perform a basic drop action (implicitly causing preceding mouse
0857: * drag motion).
0858: */
0859: public void actionDrop(Component dropTarget, ComponentLocation loc) {
0860: Point where = loc.getPoint(dropTarget);
0861: drop(dropTarget, where.x, where.y);
0862: waitForIdle();
0863: }
0864:
0865: /** Perform a basic drop action (implicitly causing preceding mouse
0866: * drag motion). The modifiers represents the set of active modifiers
0867: * when the drop is made.
0868: */
0869: public void actionDrop(Component dropTarget, int x, int y) {
0870: drop(dropTarget, x, y);
0871: waitForIdle();
0872: }
0873:
0874: /** Return whether the component's contents matches the given image. */
0875: public boolean assertImage(Component comp, java.io.File fileImage,
0876: boolean ignoreBorder) {
0877: java.awt.image.BufferedImage img = capture(comp, ignoreBorder);
0878: return new ImageComparator().compare(img, fileImage) == 0;
0879: }
0880:
0881: /** Returns whether a Window corresponding to the given String is
0882: * showing. The string may be a plain String or regular expression and
0883: * may match either the window title (for Frames or Dialogs) or the
0884: * value of {@link Component#getName}.
0885: */
0886: public boolean assertFrameShowing(Hierarchy h, String id) {
0887: try {
0888: ComponentFinder finder = new BasicFinder(h);
0889: return finder.find(new WindowMatcher(id, true)) != null;
0890: } catch (ComponentNotFoundException e) {
0891: return false;
0892: } catch (MultipleComponentsFoundException m) {
0893: // Might not be the one you want, but that's what the docs say
0894: return true;
0895: }
0896: }
0897:
0898: /** Returns whether a Window corresponding to the given String is
0899: * showing. The string may be a plain String or regular expression and
0900: * may match either the window title (for Frames or Dialogs) or the value
0901: * of {@link Component#getName}.
0902: * @deprecated This method does not specify the proper context for the
0903: * lookup. Use {@link #assertFrameShowing(Hierarchy,String)} instead.
0904: */
0905: public boolean assertFrameShowing(String id) {
0906: return assertFrameShowing(AWTHierarchy.getDefault(), id);
0907: }
0908:
0909: /** Convenience wait for a window to be displayed. The given string may
0910: * be a plain String or regular expression and may match either the window
0911: * title (for Frames and Dialogs) or the value of {@link Component#getName}.
0912: * This method is provided as a convenience for hand-coded tests, since
0913: * scripts will use a wait step instead.<p>
0914: * The property <code>abbot.robot.component_delay</code> affects the
0915: * default timeout.
0916: */
0917: public void waitForFrameShowing(final Hierarchy h,
0918: final String identifier) {
0919: wait(new Condition() {
0920: public boolean test() {
0921: return assertFrameShowing(h, identifier);
0922: }
0923:
0924: public String toString() {
0925: return Strings.get("tester.Component.show_wait",
0926: new Object[] { identifier });
0927: }
0928: }, componentDelay);
0929: }
0930:
0931: /** Convenience wait for a window to be displayed. The given string may
0932: * be a plain String or regular expression and may match either the window
0933: * title (for Frames and Dialogs) or the value of {@link Component#getName}.
0934: * This method is provided as a convenience for hand-coded tests, since
0935: * scripts will use a wait step instead.<p>
0936: * The property abbot.robot.component_delay affects the default timeout.
0937: * @deprecated This method does not provide sufficient context to reliably
0938: * find a component. Use {@link #waitForFrameShowing(Hierarchy,String)}
0939: * instead.
0940: */
0941: public void waitForFrameShowing(final String identifier) {
0942: waitForFrameShowing(AWTHierarchy.getDefault(), identifier);
0943: }
0944:
0945: /** Return whether the Component represented by the given
0946: ComponentReference is available.
0947: */
0948: public boolean assertComponentShowing(ComponentReference ref) {
0949: try {
0950: Component c = ref.getComponent();
0951: return isReadyForInput(c);
0952: } catch (ComponentSearchException e) {
0953: return false;
0954: }
0955: }
0956:
0957: /** Wait for the Component represented by the given ComponentReference to
0958: become available. The timeout is affected by
0959: abbot.robot.component_delay, which defaults to 30s.
0960: */
0961: public void waitForComponentShowing(final ComponentReference ref) {
0962: wait(new Condition() {
0963: public boolean test() {
0964: return assertComponentShowing(ref);
0965: }
0966:
0967: public String toString() {
0968: return Strings.get("tester.Component.show_wait",
0969: new Object[] { ref });
0970: }
0971: }, componentDelay);
0972: }
0973:
0974: private Method[] cachedMethods = null;
0975:
0976: /** Look up methods with the given prefix. Facilitates auto-scanning by
0977: * scripts and the script editor.
0978: */
0979: private Method[] getMethods(String prefix, Class returnType,
0980: boolean componentArgument) {
0981: if (cachedMethods == null) {
0982: cachedMethods = getClass().getMethods();
0983: }
0984: ArrayList methods = new ArrayList();
0985: // Only save one Method for each unique name
0986: HashSet names = new HashSet(IGNORED_METHODS);
0987:
0988: Method[] mlist = cachedMethods;
0989: for (int i = 0; i < mlist.length; i++) {
0990: String name = mlist[i].getName();
0991: if (names.contains(name) || !name.startsWith(prefix))
0992: continue;
0993: Class[] params = mlist[i].getParameterTypes();
0994: if ((returnType == null || returnType.equals(mlist[i]
0995: .getReturnType()))
0996: && ((params.length == 0 && !componentArgument) || (params.length > 0 && (Component.class
0997: .isAssignableFrom(params[0]) == componentArgument)))) {
0998: methods.add(mlist[i]);
0999: names.add(name);
1000: }
1001: }
1002: return (Method[]) methods.toArray(new Method[methods.size()]);
1003: }
1004:
1005: private Method[] cachedActions = null;
1006:
1007: /** Return a list of all actions defined by this class that don't depend
1008: * on a component argument.
1009: */
1010: public Method[] getActions() {
1011: if (cachedActions == null) {
1012: cachedActions = getMethods("action", void.class, false);
1013: }
1014: return cachedActions;
1015: }
1016:
1017: private Method[] cachedComponentActions = null;
1018:
1019: /** Return a list of all actions defined by this class that require
1020: * a component argument.
1021: */
1022: public Method[] getComponentActions() {
1023: if (cachedComponentActions == null) {
1024: cachedComponentActions = getMethods("action", void.class,
1025: true);
1026: }
1027: return cachedComponentActions;
1028: }
1029:
1030: private Method[] cachedPropertyMethods = null;
1031:
1032: /** Return an array of all property check methods defined by this class.
1033: * The first argument <b>must</b> be a Component.
1034: */
1035: public Method[] getPropertyMethods() {
1036: if (cachedPropertyMethods == null) {
1037: ArrayList all = new ArrayList();
1038: all.addAll(Arrays.asList(getMethods("is", boolean.class,
1039: true)));
1040: all.addAll(Arrays.asList(getMethods("has", boolean.class,
1041: true)));
1042: all.addAll(Arrays.asList(getMethods("get", null, true)));
1043: cachedPropertyMethods = (Method[]) all
1044: .toArray(new Method[all.size()]);
1045: }
1046: return cachedPropertyMethods;
1047: }
1048:
1049: private Method[] cachedAssertMethods = null;
1050:
1051: /** Return a list of all assertions defined by this class that don't
1052: * depend on a component argument.
1053: */
1054: public Method[] getAssertMethods() {
1055: if (cachedAssertMethods == null) {
1056: cachedAssertMethods = getMethods("assert", boolean.class,
1057: false);
1058: }
1059: return cachedAssertMethods;
1060: }
1061:
1062: private Method[] cachedComponentAssertMethods = null;
1063:
1064: /** Return a list of all assertions defined by this class that require a
1065: * component argument.
1066: */
1067: public Method[] getComponentAssertMethods() {
1068: if (cachedComponentAssertMethods == null) {
1069: cachedComponentAssertMethods = getMethods("assert",
1070: boolean.class, true);
1071: }
1072: return cachedComponentAssertMethods;
1073: }
1074:
1075: /** Quick and dirty strip raw text from html, for getting the basic text
1076: from html-formatted labels and buttons. Behavior is undefined for
1077: badly formatted html.
1078: */
1079: public static String stripHTML(String str) {
1080: if (str != null
1081: && (str.startsWith("<html>") || str
1082: .startsWith("<HTML>"))) {
1083: while (str.startsWith("<")) {
1084: int right = str.indexOf(">");
1085: if (right == -1)
1086: break;
1087: str = str.substring(right + 1);
1088: }
1089: while (str.endsWith(">")) {
1090: int right = str.lastIndexOf("<");
1091: if (right == -1)
1092: break;
1093: str = str.substring(0, right);
1094: }
1095: }
1096: return str;
1097: }
1098:
1099: /** Wait for the given condition, throwing an ActionFailedException if it
1100: * times out.
1101: */
1102: protected void waitAction(String desc, Condition cond)
1103: throws ActionFailedException {
1104: try {
1105: wait(cond);
1106: } catch (WaitTimedOutError wto) {
1107: throw new ActionFailedException(desc);
1108: }
1109: }
1110:
1111: /** Return the Component class that corresponds to this
1112: * <tt>ComponentTester</tt>
1113: * class. For example,
1114: * <tt>JComponentTester.getTestedClass(JLabel.class)</tt>
1115: * would return <tt>JComponent.class</tt>.
1116: */
1117: public Class getTestedClass(Class cls) {
1118: while (getTester(cls.getSuperclass()) == this ) {
1119: cls = cls.getSuperclass();
1120: }
1121: return cls;
1122: }
1123:
1124: /** Parse the String representation of a <tt>ComponentLocation</tt> into
1125: * the actual <tt>ComponentLocation</tt> object.
1126: */
1127: public ComponentLocation parseLocation(String encoded) {
1128: return new ComponentLocation().parse(encoded);
1129: }
1130:
1131: /** Return a ComponentLocation for the given Point. Derived classes may
1132: * provide something more suitable than an (x, y) coordinate. A
1133: * {@link javax.swing.JTree}, for example, might provide a
1134: * {@link JTreeLocation}
1135: * indicating a path or row in the tree.
1136: */
1137: public ComponentLocation getLocation(Component c, Point where) {
1138: return new ComponentLocation(where);
1139: }
1140:
1141: private class AWTMenuListener implements AWTEventListener {
1142: public volatile boolean eventFired;
1143:
1144: public void eventDispatched(AWTEvent e) {
1145: if (e.getID() == ActionEvent.ACTION_PERFORMED) {
1146: eventFired = true;
1147: }
1148: }
1149: }
1150: }
|