aboutsummaryrefslogtreecommitdiffstats
path: root/client/src
diff options
context:
space:
mode:
authorHenri Sara <hesara@vaadin.com>2013-10-02 13:27:38 +0300
committerVaadin Code Review <review@vaadin.com>2013-10-16 12:33:47 +0000
commitc141df78b37ccb36dfbf098c09b14df5535b99cb (patch)
tree8abe534508e3d5c02358ee429101f11a4861f02a /client/src
parentdf9a1192efeb49285cb1c4fc100bb60b5a77ac14 (diff)
downloadvaadin-framework-c141df78b37ccb36dfbf098c09b14df5535b99cb.tar.gz
vaadin-framework-c141df78b37ccb36dfbf098c09b14df5535b99cb.zip
Generate correct selectors in debug window for TestBench 4 (#12694)
Selectors use IDs, captions, indices of the widget type in parent and then as a fallback domChild/XPath paths to support sub-part selection and highlighting as well as other elements without subpart information. Change-Id: I1ce30a9eb4a96ef0387635ae7464db7e9bd6f542
Diffstat (limited to 'client/src')
-rw-r--r--client/src/com/vaadin/client/debug/internal/Highlight.java43
-rw-r--r--client/src/com/vaadin/client/debug/internal/SelectorPath.java813
-rw-r--r--client/src/com/vaadin/client/debug/internal/TestBenchSection.java118
3 files changed, 910 insertions, 64 deletions
diff --git a/client/src/com/vaadin/client/debug/internal/Highlight.java b/client/src/com/vaadin/client/debug/internal/Highlight.java
index f2695f58ca..5ee3a25e2c 100644
--- a/client/src/com/vaadin/client/debug/internal/Highlight.java
+++ b/client/src/com/vaadin/client/debug/internal/Highlight.java
@@ -144,20 +144,55 @@ public class Highlight {
*/
static Element show(Widget widget, String color) {
if (widget != null) {
+ show(widget.getElement(), color);
+ }
+ return null;
+ }
+
+ /**
+ * Highlights the given {@link Element}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param element
+ * Element to highlight
+ * @return Highlight element
+ */
+ static Element show(Element element) {
+ return show(element, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlight the given {@link Element} using the given color.
+ * <p>
+ * Pass the returned highlight {@link Element} to {@link #hide(Element)} to
+ * remove this particular highlight.
+ * </p>
+ *
+ * @param element
+ * Element to highlight
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(Element element, String color) {
+ if (element != null) {
if (highlights == null) {
highlights = new HashSet<Element>();
}
Element highlight = DOM.createDiv();
Style style = highlight.getStyle();
- style.setTop(widget.getAbsoluteTop(), Unit.PX);
- style.setLeft(widget.getAbsoluteLeft(), Unit.PX);
- int width = widget.getOffsetWidth();
+ style.setTop(element.getAbsoluteTop(), Unit.PX);
+ style.setLeft(element.getAbsoluteLeft(), Unit.PX);
+ int width = element.getOffsetWidth();
if (width < MIN_WIDTH) {
width = MIN_WIDTH;
}
style.setWidth(width, Unit.PX);
- int height = widget.getOffsetHeight();
+ int height = element.getOffsetHeight();
if (height < MIN_HEIGHT) {
height = MIN_HEIGHT;
}
diff --git a/client/src/com/vaadin/client/debug/internal/SelectorPath.java b/client/src/com/vaadin/client/debug/internal/SelectorPath.java
new file mode 100644
index 0000000000..d4502daeda
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/SelectorPath.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * 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.vaadin.client.debug.internal;
+
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.componentlocator.ComponentLocator;
+import com.vaadin.client.componentlocator.VaadinFinderLocatorStrategy;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.client.ui.SubPartAware;
+
+/**
+ * A single segment of a selector path with optional parent.
+ * <p>
+ * The static method {@link #findTestBenchSelector(ServerConnector, Element)}
+ * permits looking up a selector chain for an element (a selector and its
+ * parents, each selector relative to its parent).
+ * <p>
+ * The method {@link #findElement()} can be used to locate the element
+ * referenced by a {@link SelectorPath}. {@link #getJUnitSelector(String)} can
+ * be used to obtain the string to add to a JUnit test to refer to the element
+ * identified by the path.
+ * <p>
+ * This class should be considered internal to the framework and may change at
+ * any time.
+ *
+ * @since 7.1.x
+ */
+public abstract class SelectorPath {
+ private final SelectorPath parent;
+ private final ComponentLocator locator;
+
+ private static final String SUBPART_SEPARATOR = VaadinFinderLocatorStrategy.SUBPART_SEPARATOR;
+
+ /**
+ * Creates a {@link SelectorPath} from the root of the UI (without a parent)
+ * to identify an element.
+ * <p>
+ * The {@link ComponentLocator} is used to locate the corresponding
+ * {@link Element} in the context of a UI. If there are multiple UIs on a
+ * single page, the locator should correspond to the correct
+ * {@link ApplicationConnection}.
+ *
+ * @param locator
+ * {@link ComponentLocator} to use
+ */
+ protected SelectorPath(ComponentLocator locator) {
+ this(null, locator);
+ }
+
+ /**
+ * Creates a {@link SelectorPath} which is relative to another
+ * {@link SelectorPath}. to identify an element.
+ * <p>
+ * The {@link ComponentLocator} is used to locate the corresponding
+ * {@link Element} in the context of a UI. If there are multiple UIs on a
+ * single page, the locator should correspond to the correct
+ * {@link ApplicationConnection}.
+ *
+ * @param parent
+ * parent {@link SelectorPath} or null for root paths
+ * @param locator
+ * {@link ComponentLocator} to use
+ */
+ protected SelectorPath(SelectorPath parent, ComponentLocator locator) {
+ this.parent = parent;
+ this.locator = locator;
+ }
+
+ /**
+ * Returns the parent {@link SelectorPath} to which this path is relative.
+ *
+ * @return parent path
+ */
+ public SelectorPath getParent() {
+ return parent;
+ }
+
+ @Override
+ public String toString() {
+ return "SelectorPath: " + getJUnitSelector("...");
+ }
+
+ /**
+ * Returns the JUnit test fragment which can be used to refer to the element
+ * in a test.
+ *
+ * @param context
+ * the context to use (usually "getDriver()" or a variable name)
+ * @return string to add in a JUnit test
+ */
+ public abstract String getJUnitSelector(String context);
+
+ /**
+ * Returns the {@link Element} that this {@link SelectorPath} points to in
+ * the context of the {@link ComponentLocator} of the {@link SelectorPath}.
+ *
+ * @return Element identified by the path in the current UI
+ */
+ public abstract Element findElement();
+
+ /**
+ * Returns the path to an element/connector, including separate intermediate
+ * paths and the final path segment.
+ *
+ * @param connector
+ * the connector to find
+ * @param element
+ * sub-element inside connector or null to use connector root
+ * element
+ * @return Vaadin locator path
+ */
+ public static SelectorPath findTestBenchSelector(ServerConnector connector,
+ Element element) {
+ // TODO there should be a better way to locate and refer to captions -
+ // now using domChild in layout
+ SelectorPath selectorPath = null;
+ ApplicationConnection connection = connector.getConnection();
+ if (connection != null) {
+ if (null == element) {
+ element = findConnectorRootElement(connector);
+ }
+ if (null != element) {
+ ComponentLocator locator = new ComponentLocator(connection);
+ String path = locator.getPathForElement(element);
+ SelectorPath parent = null;
+
+ if (!path.isEmpty()) {
+ selectorPath = extractIdSelectorPath(path, locator);
+ if (null == selectorPath) {
+ // parent paths first if not rooted on an ID
+ if (connector.getParent() != null) {
+ parent = findTestBenchSelector(
+ connector.getParent(), null);
+ }
+
+ if (parent != null) {
+ // update path to be relative to parent
+ Element parentElement = parent.findElement();
+ if (null != parentElement) {
+ String parentPath = locator
+ .getPathForElement(parentElement);
+ if (path.startsWith(parentPath)) {
+ // remove path of parent to look for the
+ // children
+ path = path.substring(parentPath.length());
+ }
+ }
+ }
+
+ selectorPath = extractVaadinSelectorPath(path, parent,
+ locator);
+ }
+ if (null == selectorPath) {
+ if (path.startsWith("/V")) {
+ // fall-back: Vaadin
+ // this branch is needed for /VTabsheetPanel etc.
+ selectorPath = SelectorPath.vaadinPath(path,
+ parent, locator);
+ } else {
+ // fall-back: XPath
+ selectorPath = SelectorPath.xpath(path, parent,
+ locator);
+ }
+ }
+ }
+ }
+ }
+ return selectorPath;
+ }
+
+ private static SelectorPath extractIdSelectorPath(String path,
+ ComponentLocator locator) {
+ SelectorPath selectorPath = null;
+ if (path.startsWith("PID_S")) {
+ // remove internal prefix
+ path = path.substring(5);
+
+ // no parent for an ID selector
+ String pid = path;
+ String rest = null;
+ // split at first slash that is not in the subpart (if any)
+ int slashPos = path.indexOf("/");
+ int subPartPos = path.indexOf(SUBPART_SEPARATOR);
+ if (subPartPos >= 0 && slashPos > subPartPos) {
+ // ignore slashes in subpart
+ slashPos = -1;
+ } else if (slashPos >= 0 && subPartPos > slashPos) {
+ // ignore subpart after slashes - handled as a part of rest
+ subPartPos = -1;
+ }
+ // split the ID part and any relative path after it
+ if (slashPos > 0) {
+ pid = path.substring(0, slashPos);
+ rest = path.substring(slashPos);
+ }
+
+ // if there is a subpart directly after the id, need to use a Vaadin
+ // selector
+ SelectorPath pidSelector = null;
+ if (subPartPos > 0) {
+ String id = pid.substring(0, subPartPos);
+ // include the subpart separator
+ String subPart = pid.substring(subPartPos);
+ Element element = locator.getElementByPath("PID_S" + pid);
+ ComponentConnector connector = Util.findPaintable(
+ locator.getClient(), element);
+ if (null != connector && null != connector.getWidget()) {
+ String type = connector.getWidget().getClass()
+ .getSimpleName();
+ pidSelector = SelectorPath.vaadinPath("//" + type
+ + "[id=\\\"" + id + "\\\"]" + subPart, null,
+ locator);
+ } else {
+ // no valid connector for the subpart
+ return null;
+ }
+ } else {
+ pidSelector = SelectorPath.id(pid, locator);
+ }
+ if (null != rest && !rest.isEmpty()) {
+ selectorPath = extractVaadinSelectorPath(path, pidSelector,
+ locator);
+ if (selectorPath == null) {
+ selectorPath = SelectorPath.xpath(rest, pidSelector,
+ locator);
+ }
+ } else {
+ selectorPath = pidSelector;
+ }
+ }
+ return selectorPath;
+ }
+
+ private static SelectorPath extractVaadinSelectorPath(String path,
+ SelectorPath parent, ComponentLocator locator) {
+ SelectorPath selectorPath = null;
+
+ String xpathPart = null;
+ int xpathPos = Math.min(path.indexOf("/div"), path.indexOf("/span"));
+ if (xpathPos >= 0) {
+ xpathPart = path.substring(xpathPos);
+ path = path.substring(0, xpathPos);
+ }
+
+ String subPartPart = null;
+ int subPartPos = path.indexOf("#");
+ if (subPartPos >= 0) {
+ subPartPart = path.substring(subPartPos + 1);
+ path = path.substring(0, subPartPos);
+ }
+
+ String domChildPart = null;
+ int domChildPos = path.indexOf("/domChild");
+ if (domChildPos >= 0) {
+ // include the slash
+ domChildPart = path.substring(domChildPos);
+ path = path.substring(0, domChildPos);
+ }
+
+ // is it something VaadinSelectorPath can handle?
+ String widgetClass = null;
+ // first cases in a layout slot
+ RegExp widgetInSlotMatcher = RegExp
+ .compile("^/(Slot\\[(\\d+)\\]/)([a-zA-Z]+)(\\[0\\])?$");
+ MatchResult matchResult = widgetInSlotMatcher.exec(path);
+ if (null != matchResult) {
+ if (matchResult.getGroupCount() >= 3) {
+ widgetClass = matchResult.getGroup(3);
+ }
+ }
+ // handle cases without intervening slot
+ if (null == widgetClass) {
+ RegExp widgetDirectlyMatcher = RegExp
+ .compile("^//?([a-zA-Z]+)(\\[(\\d+)\\])?$");
+ matchResult = widgetDirectlyMatcher.exec(path);
+ if (null != matchResult) {
+ if (matchResult.getGroupCount() >= 1) {
+ widgetClass = matchResult.getGroup(1);
+ }
+ }
+ }
+ if (null != widgetClass && !widgetClass.isEmpty()) {
+ selectorPath = findVaadinSelectorInParent(path, widgetClass,
+ parent, locator);
+ if (null != subPartPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ ((VaadinSelectorPath) selectorPath).setSubPart(subPartPart);
+ } else if (null != xpathPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ // try to find sub-part if supported
+ ComponentConnector connector = Util.findPaintable(
+ locator.getClient(), selectorPath.findElement());
+ if (connector != null
+ && connector.getWidget() instanceof SubPartAware) {
+ // for SubPartAware, skip the XPath fall-back path
+ Element element = locator.getElementByPathStartingAt(path,
+ selectorPath.findElement());
+ SubPartAware subPartAware = (SubPartAware) connector
+ .getWidget();
+ String subPart = subPartAware.getSubPartName(element);
+ if (null != subPart) {
+ // type checked above
+ ((VaadinSelectorPath) selectorPath).setSubPart(subPart);
+ }
+ } else {
+ // fall-back to XPath for the last part of the path
+ selectorPath = SelectorPath.xpath(xpathPart, selectorPath,
+ locator);
+ }
+ }
+
+ // the whole /domChild[i]/domChild[j]... part as a single selector
+ if (null != domChildPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ selectorPath = SelectorPath.vaadinPath(domChildPart,
+ selectorPath, locator);
+ }
+ } else if (null != domChildPart) {
+ // cases with domChild path only (parent contains rest)
+ selectorPath = SelectorPath.vaadinPath(domChildPart, parent,
+ locator);
+ }
+ return selectorPath;
+ }
+
+ /**
+ * Find the zero-based index of the widget of type widgetClass identified by
+ * path within its parent and returns the corresponding Vaadin path (if
+ * any). For instance, the second button in a layout has index 1 regardless
+ * of non-button components in the parent.
+ * <p>
+ * The approach used internally is to try to find the caption of the element
+ * inside its parent and check whether it is sufficient to identify the
+ * element correctly. If not, possible indices are looped through to see if
+ * the component of the specified type within the specified parent
+ * identifies the correct element. This is inefficient but more reliable
+ * than some alternative approaches, and does not require special cases for
+ * various layouts etc.
+ *
+ * @param path
+ * relative path for the widget of interest
+ * @param widgetClass
+ * type of the widget of interest
+ * @param parent
+ * parent component to which the path is relative
+ * @param locator
+ * ComponentLocator used to map paths to elements
+ * @return selector path for the element, null if none found
+ */
+ private static SelectorPath findVaadinSelectorInParent(String path,
+ String widgetClass, SelectorPath parent, ComponentLocator locator) {
+ if (null == parent) {
+ SelectorPath selectorPath = SelectorPath.vaadin(widgetClass, 0,
+ null, locator);
+ if (selectorPath.findElement() == locator.getElementByPath(path)) {
+ return selectorPath;
+ } else {
+ return null;
+ }
+ }
+ // This method uses an inefficient brute-force approach but its
+ // results should match what is used by the TestBench selectors.
+ Element parentElement = parent.findElement();
+ String parentPathString = locator.getPathForElement(parentElement);
+ if (null == parentPathString) {
+ parentPathString = "";
+ }
+ Element elementToFind = locator.getElementByPath(parentPathString
+ + path);
+ if (null == elementToFind) {
+ return null;
+ }
+ // if the connector has a caption, first try if the element can be
+ // located in parent with it; if that fails, use the index in parent
+ String caption = getCaptionForElement(elementToFind, locator);
+ if (null != caption) {
+ SelectorPath testPath = SelectorPath.vaadin(widgetClass, caption,
+ parent, locator);
+ Element testElement = testPath.findElement();
+ // TODO in theory could also iterate upwards into parents, using
+ // "//" before the caption to find the shortest matching path that
+ // identifies the correct element
+ if (testElement == elementToFind) {
+ return testPath;
+ }
+ }
+
+ // Assumes that the number of logical child elements is at most the
+ // number of direct children of the DOM element - e.g. layouts have a
+ // single component per slot.
+ for (int i = 0; i < parentElement.getChildCount(); ++i) {
+ SelectorPath testPath = SelectorPath.vaadin(widgetClass, i, parent,
+ locator);
+ Element testElement = testPath.findElement();
+ if (testElement == elementToFind) {
+ return testPath;
+ }
+ }
+ return null;
+ }
+
+ private static String getCaptionForElement(Element element,
+ ComponentLocator locator) {
+ String caption = null;
+ ComponentConnector connector = Util.findPaintable(locator.getClient(),
+ element);
+ if (null != connector) {
+ Property property = AbstractConnector.getStateType(connector)
+ .getProperty("caption");
+ try {
+ Object value = property.getValue(connector.getState());
+ if (null != value) {
+ caption = String.valueOf(value);
+ }
+ } catch (NoDataException e) {
+ // skip the caption based selection and use index below
+ }
+ }
+ return caption;
+ }
+
+ private static Element findConnectorRootElement(ServerConnector connector) {
+ Element element = null;
+ // try to find the root element of the connector
+ if (connector instanceof ComponentConnector) {
+ Widget widget = ((ComponentConnector) connector).getWidget();
+ if (widget != null) {
+ element = widget.getElement();
+ }
+ }
+ return element;
+ }
+
+ public ComponentLocator getLocator() {
+ return locator;
+ }
+
+ @Override
+ public int hashCode() {
+ return getJUnitSelector("context").hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SelectorPath other = (SelectorPath) obj;
+ if (parent == null) {
+ if (other.parent != null) {
+ return false;
+ }
+ } else if (!parent.equals(other.parent)) {
+ return false;
+ }
+ if (!other.getJUnitSelector("context").equals(
+ getJUnitSelector("context"))) {
+ return false;
+ }
+ return true;
+ }
+
+ protected static SelectorPath xpath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ return new XPathSelectorPath(path, parent, locator);
+ }
+
+ protected static SelectorPath id(String id, ComponentLocator locator) {
+ return new IdSelectorPath(id, locator);
+ }
+
+ protected static SelectorPath vaadin(String widgetClass,
+ String widgetCaption, SelectorPath parent, ComponentLocator locator) {
+ return new VaadinSelectorPath(widgetClass, widgetCaption, 0, parent,
+ locator);
+ }
+
+ protected static SelectorPath vaadin(String widgetClass, int widgetIndex,
+ SelectorPath parent, ComponentLocator locator) {
+ return new VaadinSelectorPath(widgetClass, null, widgetIndex, parent,
+ locator);
+ }
+
+ protected static SelectorPath vaadinPath(String vaadinPath,
+ SelectorPath parent, ComponentLocator locator) {
+ return new ByVaadinSelectorPath(vaadinPath, parent, locator);
+ }
+
+ /**
+ * Selector path for finding an {@link Element} based on an XPath (relative
+ * to the parent {@link SelectorPath}).
+ */
+ private static class XPathSelectorPath extends SelectorPath {
+ // path segment relative to parent
+ private final String path;
+
+ /**
+ * Creates a relative XPath based component selector path.
+ *
+ * @param path
+ * XPath
+ * @param parent
+ * {@link SelectorPath} to which the XPath is relative, null
+ * if from the root
+ * @param locator
+ * ComponentLocator to use to find the element
+ */
+ public XPathSelectorPath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ this.path = path;
+ }
+
+ /**
+ * Returns the XPath relative to the parent element.
+ *
+ * @return relative path string
+ */
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ return context + ".findElement(By.xpath(\"" + getPath() + "\"))";
+ }
+
+ @Override
+ public Element findElement() {
+ if (null != getParent()) {
+ Element parentElement = getParent().findElement();
+ if (null == parentElement) {
+ // broken path - possibly removed parent
+ return null;
+ }
+ Element element = getLocator().getElementByPathStartingAt(
+ getPath(), parentElement);
+ return element;
+ } else {
+ Element element = getLocator().getElementByPath(getPath());
+ return element;
+ }
+ }
+ }
+
+ /**
+ * Element identifier based locator path.
+ * <p>
+ * Identifier paths never have a parent and the identifiers should be unique
+ * within the context of the {@link ComponentLocator}/page.
+ */
+ private static class IdSelectorPath extends SelectorPath {
+ private final String id;
+
+ /**
+ * Creates an identifier based {@link SelectorPath}. The identifier
+ * should not contain the old "PID_S" prefix.
+ *
+ * @param id
+ * @param locator
+ */
+ public IdSelectorPath(String id, ComponentLocator locator) {
+ super(locator);
+ this.id = id;
+ }
+
+ /**
+ * Returns the ID in the DOM used to identify the element.
+ *
+ * @return Vaadin debug ID or equivalent
+ */
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ return context + ".findElement(By.id(\"" + getId() + "\"))";
+ }
+
+ @Override
+ public Element findElement() {
+ // this also works for IDs
+ return getLocator().getElementByPath("PID_S" + getId());
+ }
+ }
+
+ /**
+ * Common base class for Vaadin selector paths (By.vaadin(...)).
+ */
+ private static abstract class AbstractVaadinSelectorPath extends
+ SelectorPath {
+
+ protected AbstractVaadinSelectorPath(SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ }
+
+ /**
+ * Returns the {@link ComponentLocator} path of the element relative to
+ * the parent path.
+ *
+ * @return path of the element for By.vaadin(...)
+ */
+ protected abstract String getPath();
+
+ @Override
+ public String getJUnitSelector(String context) {
+ return context + ".findElement(By.vaadin(\"" + getPath() + "\"))";
+ }
+
+ @Override
+ public Element findElement() {
+ if (null != getParent()) {
+ Element parentElement = getParent().findElement();
+ Element element = getLocator().getElementByPathStartingAt(
+ getPath(), parentElement);
+ return element;
+ } else {
+ return getLocator().getElementByPath(getPath());
+ }
+ }
+
+ }
+
+ /**
+ * TestBench selector path for Vaadin widgets. These selectors are based on
+ * the widget class and either the index among the widgets of that type in
+ * the parent or the widget caption.
+ */
+ private static class VaadinSelectorPath extends AbstractVaadinSelectorPath {
+ private final String widgetClass;
+ private final String widgetCaption;
+ // negative for no index
+ private final int widgetIndex;
+ private String subPart;
+
+ /**
+ * Creates a Vaadin {@link SelectorPath}. The path identifies an element
+ * of a given type under its parent based on either its caption or its
+ * index (if both are given, only the caption is used). See also
+ * {@link ComponentLocator} for more details.
+ *
+ * @param widgetClass
+ * client-side widget class
+ * @param widgetCaption
+ * caption of the widget - null to use the index instead
+ * @param widgetIndex
+ * index of the widget of the type within its parent, used
+ * only if the caption is not given
+ * @param parent
+ * parent {@link SelectorPath} or null
+ * @param locator
+ * component locator to use to find the corresponding
+ * {@link Element}
+ */
+ public VaadinSelectorPath(String widgetClass, String widgetCaption,
+ int widgetIndex, SelectorPath parent, ComponentLocator locator) {
+ super(parent, locator);
+ this.widgetClass = widgetClass;
+ this.widgetCaption = widgetCaption;
+ this.widgetIndex = widgetIndex;
+ }
+
+ /**
+ * Returns the widget type used to identify the element.
+ *
+ * @return Vaadin widget class
+ */
+ public String getWidgetClass() {
+ return widgetClass;
+ }
+
+ /**
+ * Returns the widget caption to look for or null if index is used
+ * instead.
+ *
+ * @return widget caption to match
+ */
+ public String getWidgetCaption() {
+ return widgetCaption;
+ }
+
+ /**
+ * Returns the index of the widget of that type within its parent - only
+ * used if caption is null.
+ *
+ * @return widget index
+ */
+ public int getWidgetIndex() {
+ return widgetIndex;
+ }
+
+ /**
+ * Returns the sub-part string (e.g. row and column identifiers within a
+ * table) used to identify a part of a component. See
+ * {@link ComponentLocator} and especially Vaadin selectors for more
+ * information.
+ *
+ * @return sub-part string or null if none
+ */
+ public String getSubPart() {
+ return subPart;
+ }
+
+ /**
+ * Sets the sub-part string (e.g. row and column identifiers within a
+ * table) used to identify a part of a component. See
+ * {@link ComponentLocator} and especially Vaadin selectors for more
+ * information.
+ *
+ * @param subPart
+ * sub-part string to use or null for none
+ */
+ public void setSubPart(String subPart) {
+ this.subPart = subPart;
+ }
+
+ @Override
+ protected String getPath() {
+ return "/" + getWidgetClass() + getIndexString(false)
+ + getSubPartPostfix();
+ }
+
+ private String getIndexString(boolean escapeQuotes) {
+ if (null != getWidgetCaption()) {
+ if (escapeQuotes) {
+ return "[caption=\\\"" + widgetCaption + "\\\"]";
+ } else {
+ return "[caption=\"" + widgetCaption + "\"]";
+ }
+ } else if (widgetIndex >= 0) {
+ return "[" + getWidgetIndex() + "]";
+ } else {
+ return "";
+ }
+ }
+
+ private String getSubPartPostfix() {
+ String subPartString = "";
+ if (null != getSubPart()) {
+ subPartString = SUBPART_SEPARATOR + getSubPart();
+ }
+ return subPartString;
+ }
+ }
+
+ /**
+ * TestBench selector path for Vaadin widgets, always using a
+ * By.vaadin(path) rather than other convenience methods.
+ */
+ private static class ByVaadinSelectorPath extends
+ AbstractVaadinSelectorPath {
+ private final String path;
+
+ /**
+ * Vaadin selector path for an exact path (including any preceding
+ * slash).
+ *
+ * @param path
+ * path of the element (normally with a leading slash), not
+ * null
+ * @param parent
+ * parent selector path or null if none
+ * @param locator
+ * ComponentLocator to use to find the corresponding element
+ */
+ public ByVaadinSelectorPath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ this.path = path;
+ }
+
+ /**
+ * Returns the By.vaadin(...) path relative to the parent element.
+ *
+ * @return relative path string
+ */
+ @Override
+ public String getPath() {
+ return path;
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
index a283b18912..efab5ac11f 100644
--- a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
+++ b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
@@ -15,6 +15,9 @@
*/
package com.vaadin.client.debug.internal;
+import java.util.HashMap;
+import java.util.Map;
+
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -38,12 +41,11 @@ import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.Util;
import com.vaadin.client.ValueMap;
-import com.vaadin.client.componentlocator.ComponentLocator;
/**
* Provides functionality for picking selectors for Vaadin TestBench.
*
- * @since 7.1.4
+ * @since 7.1.x
* @author Vaadin Ltd
*/
public class TestBenchSection implements Section {
@@ -51,47 +53,46 @@ public class TestBenchSection implements Section {
/**
* Selector widget showing a selector in a program-usable form.
*/
- private static class SelectorWidget extends HTML {
- private static int selectorIndex = 1;
- final private String path;
+ private static class SelectorWidget extends HTML implements
+ MouseOverHandler, MouseOutHandler {
+ private static int selectorCounter = 1;
+
+ final private SelectorPath path;
+ final private SelectorWidget parent;
+ final private int selectorIndex = selectorCounter++;
- public SelectorWidget(final String path) {
+ public SelectorWidget(final SelectorPath path,
+ final SelectorWidget parent) {
this.path = path;
+ this.parent = parent;
+
+ String parentString = (parent != null) ? ("element" + parent.selectorIndex)
+ : "getDriver()";
String html = "<div class=\""
+ VDebugWindow.STYLENAME
+ "-selector\"><span class=\"tb-selector\">"
- + Util.escapeHTML("WebElement element" + (selectorIndex++)
- + " = getDriver().findElement(By.vaadin(\"" + path
- + "\"));") + "</span></div>";
+ + Util.escapeHTML("WebElement element" + selectorIndex
+ + " = " + path.getJUnitSelector(parentString) + ";")
+ + "</span></div>";
setHTML(html);
- addMouseOverHandler(new MouseOverHandler() {
- @Override
- public void onMouseOver(MouseOverEvent event) {
- for (ApplicationConnection a : ApplicationConfiguration
- .getRunningApplications()) {
- Element element = new ComponentLocator(a)
- .getElementByPath(SelectorWidget.this.path);
- ComponentConnector connector = Util
- .getConnectorForElement(a, a.getUIConnector()
- .getWidget(), element);
- if (connector == null) {
- connector = Util.getConnectorForElement(a,
- RootPanel.get(), element);
- }
- if (connector != null) {
- Highlight.showOnly(connector);
- break;
- }
- }
- }
- });
- addMouseOutHandler(new MouseOutHandler() {
- @Override
- public void onMouseOut(MouseOutEvent event) {
- Highlight.hideAll();
- }
- });
+ addMouseOverHandler(this);
+ addMouseOutHandler(this);
+ }
+
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ ApplicationConnection a = path.getLocator().getClient();
+ Element element = path.findElement();
+ if (null != element) {
+ Highlight.hideAll();
+ Highlight.show(element);
+ }
+ }
+
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
}
}
@@ -101,7 +102,10 @@ public class TestBenchSection implements Section {
private final FlowPanel content = new FlowPanel();
private final HierarchyPanel hierarchyPanel = new HierarchyPanel();
+
private final FlowPanel selectorPanel = new FlowPanel();
+ // map from full path to SelectorWidget to enable reuse of old selectors
+ private Map<SelectorPath, SelectorWidget> selectorWidgets = new HashMap<SelectorPath, SelectorWidget>();
private final FlowPanel controls = new FlowPanel();
@@ -208,34 +212,28 @@ public class TestBenchSection implements Section {
}
private void pickSelector(ServerConnector connector, Element element) {
- String path = findTestBenchSelector(connector, element);
+ SelectorPath path = SelectorPath.findTestBenchSelector(connector,
+ element);
- if (null != path && !path.isEmpty()) {
- selectorPanel.add(new SelectorWidget(path));
- }
+ addSelectorWidgets(path);
}
- private String findTestBenchSelector(ServerConnector connector,
- Element element) {
- String path = null;
- ApplicationConnection connection = connector.getConnection();
- if (connection != null) {
- if (null == element) {
- // try to find the root element of the connector
- if (connector instanceof ComponentConnector) {
- Widget widget = ((ComponentConnector) connector)
- .getWidget();
- if (widget != null) {
- element = widget.getElement();
- }
- }
- }
- if (null != element) {
- path = new ComponentLocator(connection)
- .getPathForElement(element);
- }
+ private SelectorWidget addSelectorWidgets(SelectorPath path) {
+ // add selector widgets recursively from root towards children, reusing
+ // old ones
+ SelectorPath parent = path.getParent();
+ SelectorWidget parentWidget = null;
+ if (null != parent) {
+ parentWidget = addSelectorWidgets(parent);
+ }
+ SelectorWidget widget = selectorWidgets.get(path);
+ if (null == widget) {
+ // the parent has already been added above
+ widget = new SelectorWidget(path, parentWidget);
+ selectorWidgets.put(path, widget);
+ selectorPanel.add(widget);
}
- return path;
+ return widget;
}
private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() {