From 7c5a221fe99409c051c7ff25c6d4bfbcc7d61cba Mon Sep 17 00:00:00 2001 From: Justin Hickman Date: Tue, 15 Feb 2022 17:33:49 -0500 Subject: [PATCH 1/5] gwt-driver-7 Add faster child/descendant Widget locator --- .../gwtdriver/client/SeleniumExporter.java | 69 ++++++++++++++++--- .../gwt/gwtdriver/invoke/ExportedMethods.java | 36 ++++++++++ .../gwtdriver/models/SimpleWidgetTest.java | 45 +++++++++++- .../models/client/SimpleWidgetsEP.java | 1 + 4 files changed, 142 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java index 99a066f..90e84c2 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java @@ -26,10 +26,13 @@ import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; @@ -84,23 +87,73 @@ public String getContainingWidgetClass(Element elt) { @Method("getContainingWidgetEltOfType") public Element getContainingWidgetEltOfType(Element elt, String type) { + Widget w = findContainingWidget(elt); + while (w != null && !isOfType(type, w)) { + w = w.getParent(); + } + return w == null ? null : w.getElement(); + } + + @Method("findDescendantWidgetElementsOfType") + public JsArray findDescendantWidgetElementsOfType(Element elt, String type) { + JsArray result = JsArray.createArray().cast(); + final Widget rootWidget = findContainingWidget(elt); + if (rootWidget == null) { + return result; + } + + Deque nodesToVisit = new ArrayDeque<>(); + nodesToVisit.push(rootWidget); + + // visit all breadth-first + while (!nodesToVisit.isEmpty()) { + Widget curr = nodesToVisit.removeFirst(); + if (curr instanceof HasWidgets) { + for (Widget child : ((HasWidgets) curr)) { + nodesToVisit.add(child); + } + } + // only push if it's of the right type + if (isOfType(type, curr)) { + result.push(curr.getElement()); + } + } + return result; + } + + @Method("findDescendantWidgetElements") + public JsArray findDescendantWidgetElements(Element elt) { + return findDescendantWidgetElementsOfType(elt, Widget.class.getName()); + } + + @Method("findFirstDescendantWidgetElementsOfType") + public Element findFirstDescendantWidgetElementsOfType(Element context, String className) { + JsArray widgetEls = findDescendantWidgetElementsOfType(context, className); + if (widgetEls.length() == 0) { + return null; + } + return widgetEls.shift(); + } + + private Widget findContainingWidget(Element elt) { EventListener listener = DOM.getEventListener(elt); - while (listener instanceof Widget == false) { + while (!(listener instanceof Widget)) { if (elt == null) { return null; } - elt = elt.getParentElement().cast(); + com.google.gwt.dom.client.Element parent = elt.getParentElement(); + if (parent == null) { + // parent is null and we didn't find it + return null; + } + elt = parent.cast(); if (elt == elt.getOwnerDocument().cast()) { return null; } listener = DOM.getEventListener(elt); } - //found a real widget - Widget w = (Widget) listener; - while (w != null && !isOfType(type, w)) { - w = w.getParent(); - } - return w == null ? null : w.getElement(); + // found real widget + return (Widget) listener; } } diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java index dc575c6..2e9cd99 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java @@ -19,6 +19,8 @@ import org.openqa.selenium.WebElement; +import java.util.List; + public interface ExportedMethods extends ClientMethods { boolean isWidget(WebElement elt); @@ -33,4 +35,38 @@ public interface ExportedMethods extends ClientMethods { // String getClass(Object obj); // String instanceOf(String type, Object instance); + + /** + * Finds all descendant Widget elements below the context element matching className type. + *

+ * This method will first find the nearest Widget from the context (going up the parent chain + * until it finds a Widget). From there, it will recursively traverse (breadth-first traversal) of + * all HasWidget types. + *

+ * Will return empty list if context is null or unable to find a Widget from context element. + *

+ * If nearest Widget to context element is not instanceof {@link com.google.gwt.user.client.ui.HasWidgets}, + * this will return a single item; the Widget element. + */ + List findDescendantWidgetElementsOfType(WebElement context, String className); + + /** + * Finds all descendant Widget elements below the context element. + *

+ * This method will first find the nearest Widget from the context (going up the parent chain + * until it finds a Widget). From there, it will recursively traverse (breadth-first traversal) of + * all HasWidget types. + *

+ * Will return empty list if context is null or unable to find a Widget from context element. + *

+ * If nearest Widget to context element is not instanceof {@link com.google.gwt.user.client.ui.HasWidgets}, + * this will return a single item; the Widget element. + */ + List findDescendantWidgetElements(WebElement context); + + /** + * Finds the first descendant Widget element below the context element matching className type; + * breadth-first traversal. + */ + WebElement findFirstDescendantWidgetElementsOfType(WebElement context, String className); } diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java index 8a7d852..8126ac1 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java @@ -17,8 +17,15 @@ */ package com.vertispan.webdriver.gwt.gwtdriver.models; +import static org.junit.jupiter.api.Assertions.*; + +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.TextBox; +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ClientMethodsFactory; +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; import com.vertispan.webdriver.gwt.gwtdriver.models.Dialog.DialogFinder; import org.eclipse.jetty.server.NetworkConnector; @@ -30,6 +37,7 @@ import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.interactions.Actions; @@ -104,7 +112,7 @@ public void testWithDriver() throws Exception { //*FlowPanel //**TextBox //**Button - assert children.size() == 4 : children.size(); + assert children.size() == 5 : children.size(); //find Label by iterating through sub-widgets and as'ing GwtLabel label = children.get(0).as(GwtLabel.class); @@ -149,4 +157,39 @@ public void testWithDriver() throws Exception { assert topDialog.getElement().getText().contains("fdsa"); } + + + @Test + void testFindDescedantWidgets() { + driver.get(url); + + WidgetContainer rootPanel = new GwtRootPanel(driver); + assert rootPanel.as(GwtRootPanel.class) != null; + + ExportedMethods exportedMethods = ClientMethodsFactory.create(ExportedMethods.class, driver); + + // find all widgets under a context + List allWidgetElements = exportedMethods + .findDescendantWidgetElements(rootPanel.getElement()); + + // should be 6 (including root panel) + assertEquals(6, allWidgetElements.size()); + exportedMethods.instanceofwidget(allWidgetElements.get(1), Label.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(2), Panel.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(3), TextBox.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(4), + com.google.gwt.user.client.ui.Button.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(5), Label.class.getName()); + + // find descendant of type + List buttons = exportedMethods.findDescendantWidgetElementsOfType( + rootPanel.getElement(), + com.google.gwt.user.client.ui.Button.class.getName()); + assertEquals(1, buttons.size()); + + // find first widget type + WebElement firstLabel = exportedMethods.findFirstDescendantWidgetElementsOfType( + rootPanel.getElement(), Label.class.getName()); + assertEquals("testing", new GwtLabel(driver, firstLabel).getText()); + } } diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java index 52aad11..8c05baa 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java @@ -51,6 +51,7 @@ public void onClick(ClickEvent event) { box.show(); } })); + panel.add(new Label("Another Label in the panel")); } } From a742d7b4e06871e03cf312a8e9805fc5f7d79ead Mon Sep 17 00:00:00 2001 From: Justin Hickman Date: Wed, 16 Feb 2022 09:16:23 -0500 Subject: [PATCH 2/5] gwt-driver-7 - Changed test to use UiBinder - Added new exported method "getChildren" - Modified the existing descendant methods to stop including root widget --- pom.xml | 1 + .../gwtdriver/client/SeleniumExporter.java | 20 +++- .../gwt/gwtdriver/invoke/ExportedMethods.java | 7 ++ .../gwtdriver/models/SimpleWidgetTest.java | 97 +++++++++++-------- .../models/client/SimpleWidgetsEP.java | 44 +++++---- .../models/client/SimpleWidgetsEP.ui.xml | 76 +++++++++++++++ 6 files changed, 184 insertions(+), 61 deletions(-) create mode 100644 src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml diff --git a/pom.xml b/pom.xml index c308fcc..e41d992 100644 --- a/pom.xml +++ b/pom.xml @@ -222,6 +222,7 @@ **/*.java **/*.gss **/*.gwt.xml + **/*.ui.xml **/*.html

LICENSE
diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java index 90e84c2..fecfe8b 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/client/SeleniumExporter.java @@ -94,6 +94,22 @@ public Element getContainingWidgetEltOfType(Element elt, String type) { return w == null ? null : w.getElement(); } + @Method("getChildren") + public JsArray getChildren(Element elt) { + Widget w = findContainingWidget(elt); + JsArray result = JsArray.createArray().cast(); + if (!(w instanceof HasWidgets)) { + // null or not subtype of HasWidgets; so no children + return result; + } + + for (Widget child : (HasWidgets) w) { + result.push(child.getElement()); + } + + return result; + } + @Method("findDescendantWidgetElementsOfType") public JsArray findDescendantWidgetElementsOfType(Element elt, String type) { JsArray result = JsArray.createArray().cast(); @@ -113,8 +129,8 @@ public JsArray findDescendantWidgetElementsOfType(Element elt, String t nodesToVisit.add(child); } } - // only push if it's of the right type - if (isOfType(type, curr)) { + // only push if it's of the right type ; skip rootWidget + if (curr != rootWidget && isOfType(type, curr)) { result.push(curr.getElement()); } } diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java index 2e9cd99..6724574 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/invoke/ExportedMethods.java @@ -36,6 +36,13 @@ public interface ExportedMethods extends ClientMethods { // String instanceOf(String type, Object instance); + /** + * Returns all children of the Widget associated with context. + * + * If the widget is null or does not implement HasWidgets, an empty list will be returned. + */ + List getChildren(WebElement context); + /** * Finds all descendant Widget elements below the context element matching className type. *

diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java index 8126ac1..7598887 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java @@ -19,8 +19,8 @@ import static org.junit.jupiter.api.Assertions.*; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; @@ -50,6 +50,10 @@ * Simple initial tests to make sure the basics pass a smoke test, so lots of manual setup */ public class SimpleWidgetTest { + + // a bit dumb as this will have to be updated every time the test UI changes + private static final int TOTAL_WIDGET_COUNT = 13; + public static class SmokeTestWidget { @Child(type = RootPanel.class) private GwtWidget widget; @@ -103,31 +107,37 @@ public void testSmokeTestWidget() { public void testWithDriver() throws Exception { driver.get(url); - WidgetContainer widget = new GwtRootPanel(driver); - assert widget.as(GwtRootPanel.class) != null; + WidgetContainer rootPanel = new GwtRootPanel(driver); + assert rootPanel.as(GwtRootPanel.class) != null; - List> children = widget.findWidgets(By.xpath(".//*")); + List> children = rootPanel.findWidgets(By.xpath(".//*")); //RootPanel //*Label - //*FlowPanel - //**TextBox - //**Button - assert children.size() == 5 : children.size(); + //*UIBINDER Internals + + // growing tests will be difficult to narrow down an exact count without having to continually + // update this value. + assertEquals(TOTAL_WIDGET_COUNT, children.size()); //find Label by iterating through sub-widgets and as'ing GwtLabel label = children.get(0).as(GwtLabel.class); - assert label != null; - assert label.getText().equals("testing") : label.getText(); + assertNotNull(label); + assertEquals("testing", label.getText()); //find label by finder - GwtLabel label2 = widget.find(GwtLabel.class).withText("testing").done(); - assert label2 != null; - assert label.getElement().equals(label2.getElement()); - assert label.getText().equals("testing"); + GwtLabel label2 = rootPanel.find(GwtLabel.class).withText("testing").done(); + assertNotNull(label2); + assertEquals(label.getElement(), label2.getElement()); + assertEquals("testing", label2.getText()); + + // find panel1 that contains form, button for dialog + WidgetContainer panel1 = rootPanel.findWidget(By.cssSelector(".panel1")) + .as(WidgetContainer.class); + List> panel1Children = panel1.findWidgets(By.xpath(".//*")); //find, as TextBox as input, verify text and enter new - Input textBox = children.get(2).as(Input.class); - assert "asdf".equals(textBox.getValue()); + Input textBox = panel1Children.get(0).as(Input.class); + assertEquals("asdf", textBox.getValue()); textBox.sendKeys("fdsa"); //find, click button @@ -135,14 +145,15 @@ public void testWithDriver() throws Exception { //find dialog by heading Dialog headingDialog = new DialogFinder().withHeading("Heading").withDriver(driver).done(); - assert headingDialog != null; - assert headingDialog.getHeadingText().equals("Heading Text For Dialog"); + assertNotNull(headingDialog); + assertEquals("Heading Text For Dialog", headingDialog.getHeadingText()); + //find dialog by top Dialog topDialog = new DialogFinder().atTop().withDriver(driver).done(); - assert topDialog != null; - assert topDialog.getHeadingText().equals("Heading Text For Dialog"); + assertNotNull(topDialog); + assertEquals("Heading Text For Dialog", topDialog.getHeadingText()); - assert headingDialog.getElement().equals(topDialog.getElement()); + assertEquals(topDialog.getElement(), headingDialog.getElement()); Point initialHeaderLoc = topDialog.getElement().getLocation(); @@ -151,11 +162,8 @@ public void testWithDriver() throws Exception { actions.build().perform(); Point movedHeaderLoc = topDialog.getElement().getLocation(); - assert !movedHeaderLoc.equals(initialHeaderLoc); - //this line is a little screwy in htmlunit -// assert movedHeaderLoc.equals(children.get(3).getElement().getLocation()); - - assert topDialog.getElement().getText().contains("fdsa"); + assertNotEquals(initialHeaderLoc, movedHeaderLoc); + assertTrue(topDialog.getElement().getText().contains("fdsa")); } @@ -164,32 +172,45 @@ void testFindDescedantWidgets() { driver.get(url); WidgetContainer rootPanel = new GwtRootPanel(driver); - assert rootPanel.as(GwtRootPanel.class) != null; + assertNotNull(rootPanel.as(GwtRootPanel.class)); ExportedMethods exportedMethods = ClientMethodsFactory.create(ExportedMethods.class, driver); + // let's get the panel1 and only look under it + WidgetContainer panel1 = rootPanel.findWidget(By.cssSelector(".panel1")) + .as(WidgetContainer.class); + // find all widgets under a context List allWidgetElements = exportedMethods - .findDescendantWidgetElements(rootPanel.getElement()); - - // should be 6 (including root panel) - assertEquals(6, allWidgetElements.size()); - exportedMethods.instanceofwidget(allWidgetElements.get(1), Label.class.getName()); - exportedMethods.instanceofwidget(allWidgetElements.get(2), Panel.class.getName()); - exportedMethods.instanceofwidget(allWidgetElements.get(3), TextBox.class.getName()); - exportedMethods.instanceofwidget(allWidgetElements.get(4), + .findDescendantWidgetElements(panel1.getElement()); + + // should be TOTAL_WIDGET_COUNT (excludes the parent panel) + assertEquals(3, allWidgetElements.size()); + exportedMethods.instanceofwidget(allWidgetElements.get(0), TextBox.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(1), com.google.gwt.user.client.ui.Button.class.getName()); - exportedMethods.instanceofwidget(allWidgetElements.get(5), Label.class.getName()); + exportedMethods.instanceofwidget(allWidgetElements.get(2), Label.class.getName()); // find descendant of type List buttons = exportedMethods.findDescendantWidgetElementsOfType( - rootPanel.getElement(), + panel1.getElement(), com.google.gwt.user.client.ui.Button.class.getName()); assertEquals(1, buttons.size()); - // find first widget type + // find first widget type; using root panel as the context as we know the first + // widget is the label we're expecting WebElement firstLabel = exportedMethods.findFirstDescendantWidgetElementsOfType( rootPanel.getElement(), Label.class.getName()); assertEquals("testing", new GwtLabel(driver, firstLabel).getText()); + + + // let's validate direct children fetch; only looking in panel2 + WidgetContainer panel2 = rootPanel.findWidget(By.cssSelector(".panel2")) + .as(WidgetContainer.class); + List panel2Children = exportedMethods.getChildren(panel2.getElement()); + assertEquals(3, panel2Children.size()); + exportedMethods.instanceofwidget(panel2Children.get(0), Label.class.getName()); + exportedMethods.instanceofwidget(panel2Children.get(1), Button.class.getName()); + exportedMethods.instanceofwidget(panel2Children.get(2), FlowPanel.class.getName()); } } diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java index 8c05baa..e1dd5bd 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.java @@ -18,40 +18,42 @@ package com.vertispan.webdriver.gwt.gwtdriver.models.client; import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.ui.Button; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.DialogBox; -import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; public class SimpleWidgetsEP implements EntryPoint { + interface MyUiBinder extends UiBinder { + } + + private MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + TextBox textBox; + @Override public void onModuleLoad() { - + // outside of UiBinder RootPanel.get().add(new Label("testing")); - FlowPanel panel = new FlowPanel(); - RootPanel.get().add(panel); - - final TextBox textBox = new TextBox(); - textBox.setValue("asdf"); - panel.add(textBox); - - panel.add(new Button("Open dialog", new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - DialogBox box = new DialogBox(); - box.setText("Heading Text For Dialog"); - box.add(new HTML(textBox.getValue())); - box.show(); - } - })); - panel.add(new Label("Another Label in the panel")); + // add UiBinder UI + RootPanel.get().add(uiBinder.createAndBindUi(this)); } + @UiHandler("openDialog") + void onOpenDialog(ClickEvent event) { + DialogBox box = new DialogBox(); + box.setText("Heading Text For Dialog"); + box.add(new HTML(textBox.getValue())); + box.show(); + } } diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml new file mode 100644 index 0000000..26367d6 --- /dev/null +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml @@ -0,0 +1,76 @@ + + + + + /* ensure it doesn't get obfuscated */ + @external panel1, panel2; + + .panel1, .panel2 { + } + + .container { + display: flex; + flex-direction: column; + gap: 20px; + } + + + + + + + Open dialog + + + + +

+ + + + + + + + + + + +
Column 1
+ +
+
+ + + + + + + + + + + + + + \ No newline at end of file From 4be52bd59bf5fccc6726d3588edbb5eaa9e17caf Mon Sep 17 00:00:00 2001 From: Justin Hickman Date: Wed, 16 Feb 2022 12:12:04 -0500 Subject: [PATCH 3/5] gwt-driver-7 - Created a base GwtBy removed the need to pass the driver in by getting it from the search context. pulled up common methods into base class. Created convenient static methods in the GwtBy --- .../gwt/gwtdriver/by/ByDescendantWidget.java | 155 ++++++++++++++++++ .../gwt/gwtdriver/by/ByNearestWidget.java | 42 ++++- .../webdriver/gwt/gwtdriver/by/ByWidget.java | 88 +++++++--- .../gwt/gwtdriver/by/CheatingByChained.java | 2 +- .../gwt/gwtdriver/by/FasterByChained.java | 2 +- .../webdriver/gwt/gwtdriver/by/GwtBy.java | 121 ++++++++++++++ .../gwtdriver/models/SimpleWidgetTest.java | 8 +- 7 files changed, 387 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java create mode 100644 src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java new file mode 100644 index 0000000..67dd7a5 --- /dev/null +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java @@ -0,0 +1,155 @@ +/* + * Copyright 2013 Colin Alworth + * Copyright 2012-2013 Sencha Labs + * Copyright 2022 Vertispan LLC + * + * 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.vertispan.webdriver.gwt.gwtdriver.by; + +import com.google.gwt.user.client.ui.Widget; + +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ClientMethodsFactory; +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; + +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; + +import java.util.List; + + +/** + * GWT specific {@code By} implementation that finds descendant widgets. + *

+ * As this {@code By} calls the GWT module itself, it will only locate descendants if the {@link + * com.google.gwt.user.client.ui.HasWidgets} hierarchy is setup correctly. For example, if a Widget + * takes the Element from another Widget, but does not implement HasWidgets, this {@code By} will + * not locate the other Widget. + */ +public class ByDescendantWidget extends GwtBy { + private final String type; + + /** + * Finds the descendant Widgets of any type - anything that extends Widget will be found. + */ + public ByDescendantWidget() { + this((WebDriver) null); + } + + /** + * Finds the descendant Widgets of any type - anything that extends Widget will be found. + * + * @param driver The driver to use to communicate with the browser. + */ + public ByDescendantWidget(WebDriver driver) { + this(driver, Widget.class); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param widgetType The type of widget to find + */ + public ByDescendantWidget(Class widgetType) { + this(widgetType.getName()); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetType The type of widget to find + */ + public ByDescendantWidget(WebDriver driver, Class widgetType) { + this(driver, widgetType.getName()); + } + + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param widgetClassName The type of widget to find + */ + public ByDescendantWidget(String widgetClassName) { + this(null, widgetClassName); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetClassName The type of widget to find + */ + public ByDescendantWidget(WebDriver driver, String widgetClassName) { + super(driver); + this.type = widgetClassName; + } + + + @Override + public List findElements(SearchContext context) { + Require.nonNull("Search Context", context); + final WebElement contextElem = toWebElement(context); + + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); + // could return empty list + return m.findDescendantWidgetElementsOfType(contextElem, type); + } + + @Override + public WebElement findElement(SearchContext context) { + Require.nonNull("Search Context", context); + final WebElement contextElem = toWebElement(context); + + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); + WebElement first = m.findFirstDescendantWidgetElementsOfType(contextElem, type); + if (first == null) { + throw new NoSuchElementException("Cannot find widget of type " + type); + } + return first; + } + + @Override + public String toString() { + return "ByDescendantWidget{" + + "type='" + type + '\'' + + '}'; + } +} diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByNearestWidget.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByNearestWidget.java index 34e550f..92c04b3 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByNearestWidget.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByNearestWidget.java @@ -31,6 +31,7 @@ import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; import java.util.Collections; import java.util.List; @@ -44,10 +45,16 @@ * descendent element that is a widget, use another By to find those elements along with a {@code * ByWidget} to confirm it is a widget. */ -public class ByNearestWidget extends By { - private final WebDriver driver; +public class ByNearestWidget extends GwtBy { private final String widgetClassName; + /** + * Finds the nearest containing widget of any type - anything that extends Widget will be found. + */ + public ByNearestWidget() { + this((WebDriver) null, Widget.class); + } + /** * Finds the nearest containing widget of any type - anything that extends Widget will be found. * @@ -57,6 +64,18 @@ public ByNearestWidget(WebDriver driver) { this(driver, Widget.class); } + /** + * Finds the nearest containing widget of the given type. This will find any subtype of that + * widget, allowing you to pass in {@link ValueBoxBase} and find any {@link TextBox}, {@link + * TextArea}, {@link IntegerBox}, etc, as these are all subclasses of {@code ValueBoxBase}. Note + * that interfaces cannot be used, only base classes, and those classes *must* extend Widget. + * + * @param type the type of widget to find + */ + public ByNearestWidget(Class type) { + this(type.getName()); + } + /** * Finds the nearest containing widget of the given type. This will find any subtype of that * widget, allowing you to pass in {@link ValueBoxBase} and find any {@link TextBox}, {@link @@ -70,6 +89,18 @@ public ByNearestWidget(WebDriver driver, Class type) { this(driver, type.getName()); } + /** + * Finds the nearest containing widget of the given type. This will find any subtype of that + * widget, allowing you to pass in {@link ValueBoxBase} and find any {@link TextBox}, {@link + * TextArea}, {@link IntegerBox}, etc, as these are all subclasses of {@code ValueBoxBase}. Note + * that interfaces cannot be used, only base classes, and those classes *must* extend Widget. + * + * @param widgetClassName the type of widget to find + */ + public ByNearestWidget(String widgetClassName) { + this(null, widgetClassName); + } + /** * Finds the nearest containing widget of the given type. This will find any subtype of that * widget, allowing you to pass in {@link ValueBoxBase} and find any {@link TextBox}, {@link @@ -80,12 +111,13 @@ public ByNearestWidget(WebDriver driver, Class type) { * @param widgetClassName the type of widget to find */ public ByNearestWidget(WebDriver driver, String widgetClassName) { - this.driver = driver; + super(driver); this.widgetClassName = widgetClassName; } @Override public List findElements(SearchContext context) { + Require.nonNull("Search Context", context); WebElement elt = tryFindElement(context); if (elt != null) { return Collections.singletonList(elt); @@ -95,6 +127,7 @@ public List findElements(SearchContext context) { @Override public WebElement findElement(SearchContext context) { + Require.nonNull("Search Context", context); WebElement potentialElement = tryFindElement(context); if (potentialElement == null) { throw new NoSuchElementException("Cannot find a " + widgetClassName + " in " + context); @@ -111,7 +144,7 @@ public WebElement findElement(SearchContext context) { */ private WebElement tryFindElement(SearchContext context) { WebElement elt = context.findElement(By.xpath(".")); - ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, driver); + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); WebElement potentialElement = m.getContainingWidgetEltOfType(elt, widgetClassName); return potentialElement; } @@ -121,5 +154,4 @@ public String toString() { return "ByNearestWidget" + (Widget.class.getName().equals(widgetClassName) ? "" : " " + widgetClassName); } - } diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidget.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidget.java index 1541f78..d86f1dd 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidget.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidget.java @@ -22,62 +22,104 @@ import com.vertispan.webdriver.gwt.gwtdriver.invoke.ClientMethodsFactory; import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; -import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** - * GWT-specific {@code By} implementation that looks for widgets that in the current search context. + * GWT-specific {@code By} implementation that identifies if the current search context is a Widget. * Use in conjunction with other {@code By} statements to look for widgets that match a certain * criteria. + *

+ * This implementation will only look at the current context and will not search surrounding + * elements. */ -public class ByWidget extends By { - private final WebDriver driver; +public class ByWidget extends GwtBy { private final String type; + /** + * Checks if context is a widget of any type - anything that extends Widget will be found. + */ + public ByWidget() { + this((WebDriver) null); + } + + /** + * Checks if context is a widget of any type - anything that extends Widget will be found. + * + * @param driver The driver to use to communicate with the browser. + */ public ByWidget(WebDriver driver) { this(driver, Widget.class); } + /** + * Checks if context is a widget of the given type. + * + * @param widgetType The type of widget to validate against + */ + public ByWidget(Class widgetType) { + this(widgetType.getName()); + } + + /** + * Checks if context is a widget of the given type. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetType The type of widget to validate against + */ public ByWidget(WebDriver driver, Class widgetType) { this(driver, widgetType.getName()); } - public ByWidget(WebDriver driver, String className) { - this.driver = driver; - this.type = className; + /** + * Checks if context is a widget of the given type. + * + * @param widgetClassName The type of widget to validate against + */ + public ByWidget(String widgetClassName) { + this(null, widgetClassName); + } + + /** + * Checks if context is a widget of the given type. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetClassName The type of widget to validate against + */ + public ByWidget(WebDriver driver, String widgetClassName) { + super(driver); + this.type = widgetClassName; } @Override public List findElements(SearchContext context) { - List elts = context.findElements(By.xpath(".")); - - List ret = new ArrayList(); - ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, driver); - for (WebElement elt : elts) { - if (m.instanceofwidget(elt, type)) { - ret.add(elt); - } + Require.nonNull("Search Context", context); + WebElement contextElem = toWebElement(context); + + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); + if (m.instanceofwidget(contextElem, type)) { + return Collections.singletonList(contextElem); } - return ret; + return Collections.emptyList(); } @Override public WebElement findElement(SearchContext context) { - List elts = context.findElements(By.xpath(".")); + Require.nonNull("Search Context", context); + final WebElement contextElement = toWebElement(context); - ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, driver); - for (WebElement elt : elts) { - if (m.instanceofwidget(elt, type)) { - return elt; - } + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); + if (m.instanceofwidget(contextElement, type)) { + return contextElement; } + throw new NoSuchElementException("Can't find widget of type " + type); } diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/CheatingByChained.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/CheatingByChained.java index e58ba2c..6c2b9e4 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/CheatingByChained.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/CheatingByChained.java @@ -33,7 +33,7 @@ * When running {@link SearchContext#findElements} to search for multiple items, uses ByChained * normal. */ -public class CheatingByChained extends By { +public class CheatingByChained extends GwtBy { private By[] bys; public CheatingByChained(By... bys) { diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/FasterByChained.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/FasterByChained.java index d87f128..8b3fa92 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/FasterByChained.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/FasterByChained.java @@ -35,7 +35,7 @@ * Using this class should be functionally eqivelent to using ByChained, except faster in some * cases. */ -public class FasterByChained extends By { +public class FasterByChained extends GwtBy { private By[] bys; public FasterByChained(By... bys) { diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java new file mode 100644 index 0000000..0d76072 --- /dev/null +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java @@ -0,0 +1,121 @@ +/* + * Copyright 2013 Colin Alworth + * Copyright 2012-2013 Sencha Labs + * Copyright 2022 Vertispan LLC + * + * 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.vertispan.webdriver.gwt.gwtdriver.by; + +import com.google.gwt.user.client.ui.Widget; + +import org.openqa.selenium.By; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.WrapsDriver; + +/** + * + */ +public abstract class GwtBy extends By { + + public static CheatingByChained cheatingChained(By... bys) { + return new CheatingByChained(bys); + } + + public static FasterByChained fasterChained(By... bys) { + return new FasterByChained(bys); + } + + // is widget + + public static ByWidget isWidget() { + return new ByWidget(); + } + + public static ByWidget isWidget(Class widgetType) { + return new ByWidget(widgetType); + } + + public static ByWidget isWidget(String widgetClassName) { + return new ByWidget(widgetClassName); + } + + // nearest widget + + public static ByNearestWidget nearestWidget() { + return new ByNearestWidget(); + } + + public static ByNearestWidget nearestWidget(Class widgetType) { + return new ByNearestWidget(widgetType); + } + + public static ByNearestWidget nearestWidget(String widgetClassName) { + return new ByNearestWidget(widgetClassName); + } + + // descendant widgets + public static ByDescendantWidget descendantWidget() { + return new ByDescendantWidget(); + } + + public static ByDescendantWidget descendantWidget(Class widgetType) { + return new ByDescendantWidget(widgetType); + } + + public static ByDescendantWidget descendantWidget(String widgetClassName) { + return new ByDescendantWidget(widgetClassName); + } + + + private final WebDriver driver; + + protected GwtBy() { + this(null); + } + + protected GwtBy(WebDriver driver) { + this.driver = driver; + } + + + protected WebElement toWebElement(SearchContext context) { + final WebElement contextElem; + if (context instanceof WebElement) { + contextElem = (WebElement) context; + } else { + // most likely the driver + contextElem = context.findElement(By.xpath("//body")); + } + return contextElem; + } + + protected WebDriver getDriver(SearchContext context) { + if (this.driver != null) { + return this.driver; + } + + if (context instanceof WebDriver) { + return (WebDriver) context; + } + + if (context instanceof WrapsDriver) { + return ((WrapsDriver) context).getWrappedDriver(); + } + + throw new IllegalArgumentException("Unable to get WebDriver from provided context. " + + "Maybe use the constructor that takes a driver."); + } +} diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java index 7598887..b975e68 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/SimpleWidgetTest.java @@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; +import com.vertispan.webdriver.gwt.gwtdriver.by.GwtBy; import com.vertispan.webdriver.gwt.gwtdriver.invoke.ClientMethodsFactory; import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; import com.vertispan.webdriver.gwt.gwtdriver.models.Dialog.DialogFinder; @@ -203,7 +204,6 @@ void testFindDescedantWidgets() { rootPanel.getElement(), Label.class.getName()); assertEquals("testing", new GwtLabel(driver, firstLabel).getText()); - // let's validate direct children fetch; only looking in panel2 WidgetContainer panel2 = rootPanel.findWidget(By.cssSelector(".panel2")) .as(WidgetContainer.class); @@ -212,5 +212,11 @@ void testFindDescedantWidgets() { exportedMethods.instanceofwidget(panel2Children.get(0), Label.class.getName()); exportedMethods.instanceofwidget(panel2Children.get(1), Button.class.getName()); exportedMethods.instanceofwidget(panel2Children.get(2), FlowPanel.class.getName()); + + // similar test as above, but using the ByDescendantWidget + List elements = driver.findElements(GwtBy.descendantWidget(Label.class)); + // all labels + assertEquals(5, elements.size()); + System.out.println(elements.size()); } } From 810e33ff1d6fd485f387afe0e2a923a3abaada8b Mon Sep 17 00:00:00 2001 From: Justin Hickman Date: Wed, 16 Feb 2022 13:16:56 -0500 Subject: [PATCH 4/5] gwt-driver-7 - Added new ByWidgetChildren "By" convenience to call the getChildren exported method. --- .../gwt/gwtdriver/by/ByDescendantWidget.java | 1 - .../gwt/gwtdriver/by/ByWidgetChildren.java | 132 ++++++++++++++++++ .../webdriver/gwt/gwtdriver/by/GwtBy.java | 78 +++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java index 67dd7a5..b83aa9a 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java @@ -89,7 +89,6 @@ public ByDescendantWidget(WebDriver driver, Class widgetType) this(driver, widgetType.getName()); } - /** * Finds the descendant Widgets of the given type. *

diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java new file mode 100644 index 0000000..51d5429 --- /dev/null +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013 Colin Alworth + * Copyright 2012-2013 Sencha Labs + * Copyright 2022 Vertispan LLC + * + * 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.vertispan.webdriver.gwt.gwtdriver.by; + +import com.google.gwt.user.client.ui.Widget; + +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ClientMethodsFactory; +import com.vertispan.webdriver.gwt.gwtdriver.invoke.ExportedMethods; + +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * GWT specific {@code By} implementation that gets the direct child Widgets. + *

+ * As this {@code By} calls the GWT module itself, it will only return children if the {@link + * com.google.gwt.user.client.ui.HasWidgets} interface is implemented by the Widget associated with + * the searchContext. + */ +public class ByWidgetChildren extends GwtBy { + private final String widgetClassName; + + /** + * Finds the child Widgets of any type - anything that extends Widget will be found. + */ + public ByWidgetChildren() { + this((WebDriver) null); + } + + /** + * Finds the descendant Widgets of any type - anything that extends Widget will be found. + * + * @param driver The driver to use to communicate with the browser. + */ + public ByWidgetChildren(WebDriver driver) { + this(driver, Widget.class); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param widgetType The type of widget to find + */ + public ByWidgetChildren(Class widgetType) { + this(widgetType.getName()); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetType The type of widget to find + */ + public ByWidgetChildren(WebDriver driver, Class widgetType) { + this(driver, widgetType.getName()); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param widgetClassName The type of widget to find + */ + public ByWidgetChildren(String widgetClassName) { + this(null, widgetClassName); + } + + /** + * Finds the descendant Widgets of the given type. + *

+ * This will find any subtype of that widget, allowing you to pass in {@link + * com.google.gwt.user.client.ui.ValueBoxBase} and find any {@link com.google.gwt.user.client.ui.TextBox}, + * {@link com.google.gwt.user.client.ui.TextArea}, {@link com.google.gwt.user.client.ui.IntegerBox}, + * etc, as these are all subclasses of {@code ValueBoxBase}. Note that interfaces cannot be used, + * only base classes, and those classes *must* extend Widget. + * + * @param driver The driver to use to communicate with the browser. + * @param widgetClassName The type of widget to find + */ + public ByWidgetChildren(WebDriver driver, String widgetClassName) { + super(driver); + this.widgetClassName = widgetClassName; + } + + @Override + public List findElements(SearchContext context) { + Require.nonNull("Search Context", context); + final WebElement contextElem = toWebElement(context); + ExportedMethods m = ClientMethodsFactory.create(ExportedMethods.class, getDriver(context)); + return m.getChildren(contextElem) + .stream() + .filter(el -> m.instanceofwidget(el, widgetClassName)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java index 0d76072..590b7e5 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java @@ -30,55 +30,133 @@ */ public abstract class GwtBy extends By { + /** + * When looking for only one item, cheats by only looking for one item at each level. + *

+ * Useful only if you want the first result of each {@link By} operation, otherwise use {@link + * #fasterChained(By...)} or {@link org.openqa.selenium.support.pagefactory.ByChained}. + */ public static CheatingByChained cheatingChained(By... bys) { return new CheatingByChained(bys); } + /** + * When looking for only one item, speeds up the last {@link By} in the chain by only running it + * until it finds something. + *

+ * When using {@link SearchContext#findElements(By)}, uses {@link + * org.openqa.selenium.support.pagefactory.ByChained} to find all possible elements as normal. + *

+ * Using this should be functionally equivalent to using {@link org.openqa.selenium.support.pagefactory.ByChained}, + * except faster in some cases. + */ public static FasterByChained fasterChained(By... bys) { return new FasterByChained(bys); } + // ---------- // is widget + // ---------- + /** + * Checks if context is a widget of any type - anything that extends Widget. + */ public static ByWidget isWidget() { return new ByWidget(); } + /** + * Checks if context is a widget of the specified widgetType. + */ public static ByWidget isWidget(Class widgetType) { return new ByWidget(widgetType); } + /** + * Checks if context is a widget of the specified widgetType. + */ public static ByWidget isWidget(String widgetClassName) { return new ByWidget(widgetClassName); } + // --------------- // nearest widget + // --------------- + /** + * Looks at the current search context and above for the nearest Widget object. + */ public static ByNearestWidget nearestWidget() { return new ByNearestWidget(); } + /** + * Looks at the current search context and above for the nearest Widget object of provided + * widgetType. + */ public static ByNearestWidget nearestWidget(Class widgetType) { return new ByNearestWidget(widgetType); } + /** + * Looks at the current search context and above for the nearest Widget object of provided + * widgetType. + */ public static ByNearestWidget nearestWidget(String widgetClassName) { return new ByNearestWidget(widgetClassName); } + // ------------------ // descendant widgets + // ------------------ + + + /** + * Finds the descendant Widgets of any type - anything that extends Widget will be found. + */ public static ByDescendantWidget descendantWidget() { return new ByDescendantWidget(); } + /** + * Finds the descendant Widgets of the given type. + */ public static ByDescendantWidget descendantWidget(Class widgetType) { return new ByDescendantWidget(widgetType); } + /** + * Finds the descendant Widgets of the given type. + */ public static ByDescendantWidget descendantWidget(String widgetClassName) { return new ByDescendantWidget(widgetClassName); } + // ------------------ + // get children + // ------------------ + + /** + * Gets the children Widgets of any type - any child that extends Widget will be found. + */ + public static ByWidgetChildren childrenWidgets() { + return new ByWidgetChildren(); + } + + /** + * Gets the children Widgets of the given type. + */ + public static ByWidgetChildren childrenWidgets(Class widgetType) { + return new ByWidgetChildren(widgetType); + } + + /** + * Gets the children Widgets of the given type. + */ + public static ByWidgetChildren childrenWidgets(String widgetClassName) { + return new ByWidgetChildren(widgetClassName); + } + private final WebDriver driver; From 786312bacadc19a8b7e4e74c2da30fa144e7eab2 Mon Sep 17 00:00:00 2001 From: Justin Hickman Date: Wed, 16 Feb 2022 16:37:15 -0500 Subject: [PATCH 5/5] gwt-driver-7 Fixed license changes on newly added files --- LICENSE_VERTISPAN | 13 +++++ pom.xml | 53 +++++++++++++++---- .../gwt/gwtdriver/by/ByDescendantWidget.java | 2 - .../gwt/gwtdriver/by/ByWidgetChildren.java | 2 - .../webdriver/gwt/gwtdriver/by/GwtBy.java | 2 - .../models/client/SimpleWidgetsEP.ui.xml | 2 - 6 files changed, 57 insertions(+), 17 deletions(-) create mode 100644 LICENSE_VERTISPAN diff --git a/LICENSE_VERTISPAN b/LICENSE_VERTISPAN new file mode 100644 index 0000000..45d6f00 --- /dev/null +++ b/LICENSE_VERTISPAN @@ -0,0 +1,13 @@ +Copyright 2022 Vertispan LLC + +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. diff --git a/pom.xml b/pom.xml index e41d992..08455ba 100644 --- a/pom.xml +++ b/pom.xml @@ -212,20 +212,55 @@ com.mycila license-maven-plugin - 3.0 + 4.2.rc2 JAVADOC_STYLE SLASHSTAR_STYLE - - **/*.java - **/*.gss - **/*.gwt.xml - **/*.ui.xml - **/*.html - -

LICENSE
+ + + +
LICENSE_VERTISPAN
+ + **/*.java + **/*.gss + **/*.gwt.xml + **/*.ui.xml + **/*.html + +
+ +
LICENSE
+ + + **/Button.java + **/ByNearestWidget.java + **/ByWidget.java + **/CheatingByChained.java + **/Child.java + **/ClientMethods.java + **/ClientMethodsFactory.java + **/Dialog.java + **/ExportedMethods.java + **/FasterByChained.java + **/GwtDriver.gwt.xml + **/GwtLabel.java + **/GwtRootPanel.java + **/GwtWidget.java + **/GwtWidgetFinder.java + **/Input.java + **/ModuleUtilities.java + **/SeExporterGenerator.java + **/SeleniumExporter.java + **/SimpleWidgetTest.java + **/SimpleWidgets.gwt.xml + **/SimpleWidgetsEP.java + **/WidgetContainer.java + **/index.html + +
+
diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java index b83aa9a..d7258f1 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByDescendantWidget.java @@ -1,6 +1,4 @@ /* - * Copyright 2013 Colin Alworth - * Copyright 2012-2013 Sencha Labs * Copyright 2022 Vertispan LLC * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java index 51d5429..0a2db0c 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/ByWidgetChildren.java @@ -1,6 +1,4 @@ /* - * Copyright 2013 Colin Alworth - * Copyright 2012-2013 Sencha Labs * Copyright 2022 Vertispan LLC * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java index 590b7e5..fa70ca0 100644 --- a/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java +++ b/src/main/java/com/vertispan/webdriver/gwt/gwtdriver/by/GwtBy.java @@ -1,6 +1,4 @@ /* - * Copyright 2013 Colin Alworth - * Copyright 2012-2013 Sencha Labs * Copyright 2022 Vertispan LLC * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml index 26367d6..239f97a 100644 --- a/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml +++ b/src/test/java/com/vertispan/webdriver/gwt/gwtdriver/models/client/SimpleWidgetsEP.ui.xml @@ -1,7 +1,5 @@