diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java index 863d45d9c..d52ff9fbb 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java @@ -9,14 +9,16 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.openqa.selenium.By; import org.openqa.selenium.support.pagefactory.Annotations; import org.openqa.selenium.support.pagefactory.ByAll; import org.openqa.selenium.support.pagefactory.ByChained; -class AppiumAnnotations extends Annotations{ +class AppiumAnnotations extends Annotations { private final static List METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ = new ArrayList() { private static final long serialVersionUID = 1L; @@ -31,6 +33,7 @@ class AppiumAnnotations extends Annotations{ } }; private final static Class[] DEFAULT_ANNOTATION_METHOD_ARGUMENTS = new Class[] {}; + private static List getMethodNames(Method[] methods) { List names = new ArrayList(); for (Method m : methods) { @@ -89,13 +92,13 @@ By getBy(Annotation annotation) { return By.xpath(getValue(annotation, this)); } }, - BYLINKTEXT("linkText") { + BYLINKTEXT("linkText") { @Override By getBy(Annotation annotation) { return By.linkText(getValue(annotation, this)); } }, - BYPARTIALLINKTEXT("partialLinkText") { + BYPARTIALLINKTEXT("partialLinkText") { @Override By getBy(Annotation annotation) { return By.partialLinkText(getValue(annotation, this)); @@ -150,54 +153,51 @@ By getBy(Annotation annotation) { private final Field mobileField; private final String platform; private final String automation; - private final boolean isBrowser; - AppiumAnnotations(Field field, String platform, String automation, boolean isBrowser) { + AppiumAnnotations(Field field, String platform, String automation) { super(field); mobileField = field; - this.platform = String.valueOf(platform). - toUpperCase().trim(); - this.automation = String.valueOf(automation). - toUpperCase().trim(); - this.isBrowser = isBrowser; + this.platform = String.valueOf(platform).toUpperCase().trim(); + this.automation = String.valueOf(automation).toUpperCase().trim(); } private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) throws IllegalArgumentException { if (a1 != null && a2 != null) { - throw new IllegalArgumentException( - "If you use a '@" + a1.getClass().getSimpleName() + "' annotation, " - + "you must not also use a '@" + a2.getClass().getSimpleName() + "' annotation"); + throw new IllegalArgumentException("If you use a '@" + + a1.getClass().getSimpleName() + "' annotation, " + + "you must not also use a '@" + + a2.getClass().getSimpleName() + "' annotation"); } } - + private void assertValidAnnotations() { AndroidFindBy androidBy = mobileField .getAnnotation(AndroidFindBy.class); AndroidFindBys androidBys = mobileField .getAnnotation(AndroidFindBys.class); - AndroidFindAll androidFindAll = mobileField. - getAnnotation(AndroidFindAll.class); - - SelendroidFindBy selendroidBy = mobileField + AndroidFindAll androidFindAll = mobileField + .getAnnotation(AndroidFindAll.class); + + SelendroidFindBy selendroidBy = mobileField .getAnnotation(SelendroidFindBy.class); SelendroidFindBys selendroidBys = mobileField .getAnnotation(SelendroidFindBys.class); - SelendroidFindAll selendroidFindAll = mobileField. - getAnnotation(SelendroidFindAll.class); - + SelendroidFindAll selendroidFindAll = mobileField + .getAnnotation(SelendroidFindAll.class); + iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class); iOSFindBys iOSBys = mobileField.getAnnotation(iOSFindBys.class); iOSFindAll iOSFindAll = mobileField.getAnnotation(iOSFindAll.class); - + checkDisallowedAnnotationPairs(androidBy, androidBys); checkDisallowedAnnotationPairs(androidBy, androidFindAll); checkDisallowedAnnotationPairs(androidBys, androidFindAll); - checkDisallowedAnnotationPairs(selendroidBy, selendroidBys); + checkDisallowedAnnotationPairs(selendroidBy, selendroidBys); checkDisallowedAnnotationPairs(selendroidBy, selendroidFindAll); checkDisallowedAnnotationPairs(selendroidBys, selendroidFindAll); - + checkDisallowedAnnotationPairs(iOSBy, iOSBys); checkDisallowedAnnotationPairs(iOSBy, iOSFindAll); checkDisallowedAnnotationPairs(iOSBys, iOSFindAll); @@ -205,12 +205,16 @@ private void assertValidAnnotations() { private static Method[] prepareAnnotationMethods( Class annotation) { - List targeAnnotationMethodNamesList = getMethodNames(annotation.getDeclaredMethods()); - targeAnnotationMethodNamesList.removeAll(METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ); + List targeAnnotationMethodNamesList = getMethodNames(annotation + .getDeclaredMethods()); + targeAnnotationMethodNamesList + .removeAll(METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ); Method[] result = new Method[targeAnnotationMethodNamesList.size()]; - for (String methodName: targeAnnotationMethodNamesList){ + for (String methodName : targeAnnotationMethodNamesList) { try { - result[targeAnnotationMethodNamesList.indexOf(methodName)] = annotation.getMethod(methodName, DEFAULT_ANNOTATION_METHOD_ARGUMENTS); + result[targeAnnotationMethodNamesList.indexOf(methodName)] = annotation + .getMethod(methodName, + DEFAULT_ANNOTATION_METHOD_ARGUMENTS); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (SecurityException e) { @@ -256,7 +260,8 @@ private By getMobileBy(Annotation annotation, String valueName) { } @SuppressWarnings("unchecked") - private T getComplexMobileBy(Annotation[] annotations, Class requiredByClass) { + private T getComplexMobileBy(Annotation[] annotations, + Class requiredByClass) { ; By[] byArray = new By[annotations.length]; for (int i = 0; i < annotations.length; i++) { @@ -265,78 +270,104 @@ private T getComplexMobileBy(Annotation[] annotations, Class r } try { Constructor c = requiredByClass.getConstructor(By[].class); - Object[] values = new Object[]{byArray}; + Object[] values = new Object[] { byArray }; return (T) c.newInstance(values); } catch (Exception e) { throw new RuntimeException(e); } } + private ContentMappedBy setByForTheNativeContentAndReturn(By by, + Map contentMap) { + if (by != null) + contentMap.put(ContentType.NATIVE, by); + return new ContentMappedBy(contentMap); + } + @Override public By buildBy() { - - if (isBrowser){ - return super.buildBy(); - } - + + Map contentMap = new HashMap(); + + By defaultBy = super.buildBy(); + contentMap.put(ContentType.HTML, defaultBy); + contentMap.put(ContentType.NATIVE, defaultBy); + assertValidAnnotations(); - SelendroidFindBy selendroidBy = mobileField + SelendroidFindBy selendroidBy = mobileField .getAnnotation(SelendroidFindBy.class); - if (selendroidBy != null && ANDROID.toUpperCase().equals(platform) && - "Selendroid".toUpperCase().equals(automation)) { - return getMobileBy(selendroidBy, getFilledValue(selendroidBy)); + if (selendroidBy != null && ANDROID.toUpperCase().equals(platform) + && "Selendroid".toUpperCase().equals(automation)) { + return setByForTheNativeContentAndReturn( + getMobileBy(selendroidBy, getFilledValue(selendroidBy)), + contentMap); } - - SelendroidFindBys selendroidBys = mobileField + + SelendroidFindBys selendroidBys = mobileField .getAnnotation(SelendroidFindBys.class); - if (selendroidBys != null && ANDROID.toUpperCase().equals(platform) && - "Selendroid".toUpperCase().equals(automation)) { - return getComplexMobileBy(selendroidBys.value(), ByChained.class); + if (selendroidBys != null && ANDROID.toUpperCase().equals(platform) + && "Selendroid".toUpperCase().equals(automation)) { + return setByForTheNativeContentAndReturn( + getComplexMobileBy(selendroidBys.value(), ByChained.class), + contentMap); } - - SelendroidFindAll selendroidAll = mobileField + + SelendroidFindAll selendroidAll = mobileField .getAnnotation(SelendroidFindAll.class); - if (selendroidAll != null && ANDROID.toUpperCase().equals(platform) && - "Selendroid".toUpperCase().equals(automation)) { - return getComplexMobileBy(selendroidAll.value(), ByAll.class); + if (selendroidAll != null && ANDROID.toUpperCase().equals(platform) + && "Selendroid".toUpperCase().equals(automation)) { + return setByForTheNativeContentAndReturn( + getComplexMobileBy(selendroidAll.value(), ByAll.class), + contentMap); } - - + AndroidFindBy androidBy = mobileField .getAnnotation(AndroidFindBy.class); if (androidBy != null && ANDROID.toUpperCase().equals(platform)) { - return getMobileBy(androidBy, getFilledValue(androidBy)); + return setByForTheNativeContentAndReturn( + getMobileBy(androidBy, getFilledValue(androidBy)), + contentMap); } - + AndroidFindBys androidBys = mobileField .getAnnotation(AndroidFindBys.class); if (androidBys != null && ANDROID.toUpperCase().equals(platform)) { - return getComplexMobileBy(androidBys.value(), ByChained.class); + return setByForTheNativeContentAndReturn( + getComplexMobileBy(androidBys.value(), ByChained.class), + contentMap); } - - AndroidFindAll androidFindAll = mobileField.getAnnotation(AndroidFindAll.class); + + AndroidFindAll androidFindAll = mobileField + .getAnnotation(AndroidFindAll.class); if (androidFindAll != null && ANDROID.toUpperCase().equals(platform)) { - return getComplexMobileBy(androidFindAll.value(), ByAll.class); + return setByForTheNativeContentAndReturn( + getComplexMobileBy(androidFindAll.value(), ByAll.class), + contentMap); } - iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class); if (iOSBy != null && IOS.toUpperCase().equals(platform)) { - return getMobileBy(iOSBy, getFilledValue(iOSBy)); + return setByForTheNativeContentAndReturn( + getMobileBy(iOSBy, getFilledValue(iOSBy)), + contentMap); } iOSFindBys iOSBys = mobileField.getAnnotation(iOSFindBys.class); if (iOSBys != null && IOS.toUpperCase().equals(platform)) { - return getComplexMobileBy(iOSBys.value(), ByChained.class); + return setByForTheNativeContentAndReturn( + getComplexMobileBy(iOSBys.value(), ByChained.class), + contentMap); } - + iOSFindAll iOSFindAll = mobileField.getAnnotation(iOSFindAll.class); if (iOSFindAll != null && IOS.toUpperCase().equals(platform)) { - return getComplexMobileBy(iOSFindAll.value(), ByAll.class); - } + return setByForTheNativeContentAndReturn( + getComplexMobileBy(iOSFindAll.value(), ByAll.class), + contentMap); + } - return super.buildBy(); + return new ContentMappedBy(contentMap); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java index f2e0049f1..ae31ff484 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -14,18 +14,14 @@ import org.openqa.selenium.SearchContext; import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.TimeoutException; -import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.WrapsDriver; -import org.openqa.selenium.internal.WrapsElement; -import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.support.pagefactory.ElementLocator; import org.openqa.selenium.support.ui.FluentWait; import com.google.common.base.Function; class AppiumElementLocator implements ElementLocator { - + // This function waits for not empty element list using all defined by private static class WaitingFunction implements Function> { @@ -39,7 +35,8 @@ public List apply(By by) { List result = new ArrayList(); try { result.addAll(searchContext.findElements(by)); - } catch (StaleElementReferenceException ignored) {} + } catch (StaleElementReferenceException ignored) { + } if (result.size() > 0) { return result; } else { @@ -53,12 +50,13 @@ public List apply(By by) { private final By by; private WebElement cachedElement; private List cachedElementList; - + private final TimeOutContainer timeOutContainer; /** * Creates a new mobile element locator. It instantiates {@link WebElement} - * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation sets + * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation + * sets * * @param searchContext * The context to use when finding the element @@ -69,71 +67,48 @@ public List apply(By by) { TimeOutContainer timeOutContainer) { this.searchContext = searchContext; // All known webdrivers implement HasCapabilities - Capabilities capabilities = ((HasCapabilities) unpackWebDriverFromSearchContext()). - getCapabilities(); - - String platform = String - .valueOf(capabilities.getCapability( - MobileCapabilityType.PLATFORM_NAME)); - String automation = String - .valueOf(capabilities.getCapability( - MobileCapabilityType.AUTOMATION_NAME)); - - String browser = (String) capabilities.getCapability(CapabilityType.BROWSER_NAME); - String app = (String) capabilities.getCapability(MobileCapabilityType.APP); - - boolean isBrowser = ((app == null || "".equals(app.trim())) && - (browser != null && !"".equals(browser.trim()))); - - AppiumAnnotations annotations = new AppiumAnnotations(field, - platform, automation, isBrowser); + Capabilities capabilities = ((HasCapabilities) WebDriverUnpackUtility. + unpackWebDriverFromSearchContext(this.searchContext)) + .getCapabilities(); + + String platform = String.valueOf(capabilities + .getCapability(MobileCapabilityType.PLATFORM_NAME)); + String automation = String.valueOf(capabilities + .getCapability(MobileCapabilityType.AUTOMATION_NAME)); + + AppiumAnnotations annotations = new AppiumAnnotations(field, platform, + automation); this.timeOutContainer = timeOutContainer; shouldCache = annotations.isLookupCached(); by = annotations.buildBy(); } - - private WebDriver unpackWebDriverFromSearchContext(){ - WebDriver driver = null; - if (searchContext instanceof WebDriver){ - driver = (WebDriver) searchContext; - } - //Search context it is not only Webdriver. Webelement is search context too. - //RemoteWebElement and MobileElement implement WrapsDriver - if (searchContext instanceof WebElement){ - WebElement element = (WebElement) searchContext; //there can be something that - //implements WebElement interface and wraps original - while (element instanceof WrapsElement){ - element = ((WrapsElement) element).getWrappedElement(); - } - driver = ((WrapsDriver) element).getWrappedDriver(); - } - return driver; - } - - private void changeImplicitlyWaitTimeOut(long newTimeOut, TimeUnit newTimeUnit){ - unpackWebDriverFromSearchContext().manage().timeouts().implicitlyWait(newTimeOut, newTimeUnit); + + private void changeImplicitlyWaitTimeOut(long newTimeOut, + TimeUnit newTimeUnit) { + WebDriverUnpackUtility.unpackWebDriverFromSearchContext(searchContext) + .manage().timeouts().implicitlyWait(newTimeOut, newTimeUnit); } - - //This method waits for not empty element list using all defined by - private List waitFor(){ - //When we use complex By strategies (like ChainedBy or ByAll) - //there are some problems (StaleElementReferenceException, implicitly wait time out - //for each chain By section, etc) - try{ + + // This method waits for not empty element list using all defined by + private List waitFor() { + // When we use complex By strategies (like ChainedBy or ByAll) + // there are some problems (StaleElementReferenceException, implicitly + // wait time out + // for each chain By section, etc) + try { changeImplicitlyWaitTimeOut(0, TimeUnit.SECONDS); FluentWait wait = new FluentWait(by); - wait.withTimeout(timeOutContainer.getTimeValue(), timeOutContainer.getTimeUnitValue()); + wait.withTimeout(timeOutContainer.getTimeValue(), + timeOutContainer.getTimeUnitValue()); return wait.until(new WaitingFunction(searchContext)); - } - catch (TimeoutException e){ + } catch (TimeoutException e) { return new ArrayList(); - } - finally{ - changeImplicitlyWaitTimeOut(timeOutContainer.getTimeValue(), + } finally { + changeImplicitlyWaitTimeOut(timeOutContainer.getTimeValue(), timeOutContainer.getTimeUnitValue()); } } - + /** * Find the element. */ @@ -141,14 +116,15 @@ public WebElement findElement() { if (cachedElement != null && shouldCache) { return cachedElement; } - List result = waitFor(); - if (result.size() == 0){ - String message = "Can't locate an element by this strategy: " + by.toString(); - throw new NoSuchElementException(message); + List result = waitFor(); + if (result.size() == 0) { + String message = "Can't locate an element by this strategy: " + + by.toString(); + throw new NoSuchElementException(message); } if (shouldCache) { cachedElement = result.get(0); - } + } return result.get(0); } @@ -162,7 +138,7 @@ public List findElements() { List result = waitFor(); if (shouldCache) { cachedElementList = result; - } + } return result; } } diff --git a/src/main/java/io/appium/java_client/pagefactory/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/ContentMappedBy.java new file mode 100644 index 000000000..07c80662c --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/ContentMappedBy.java @@ -0,0 +1,38 @@ +package io.appium.java_client.pagefactory; + +import java.util.List; +import java.util.Map; + +import org.openqa.selenium.By; +import org.openqa.selenium.ContextAware; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +class ContentMappedBy extends By { + private final Map map; + private final static String NATIVE_APP_PATTERN = "NATIVE_APP"; + + public ContentMappedBy(Map map) { + this.map = map; + } + + private By returnRelevantBy(SearchContext context){ + WebDriver driver = WebDriverUnpackUtility.unpackWebDriverFromSearchContext(context); + if (!ContextAware.class.isAssignableFrom(driver.getClass())){ //it is desktop browser + return map.get(ContentType.HTML); + } + + ContextAware contextAware = ContextAware.class.cast(driver); + String currentContext = contextAware.getContext(); + if (currentContext.contains(NATIVE_APP_PATTERN)) + return map.get(ContentType.NATIVE); + return map.get(ContentType.HTML); + } + + @Override + public List findElements(SearchContext context) { + return context.findElements(returnRelevantBy(context)); + } + +} diff --git a/src/main/java/io/appium/java_client/pagefactory/ContentType.java b/src/main/java/io/appium/java_client/pagefactory/ContentType.java new file mode 100644 index 000000000..97f6fc018 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/ContentType.java @@ -0,0 +1,6 @@ +package io.appium.java_client.pagefactory; + +enum ContentType { + HTML, + NATIVE; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/WebDriverUnpackUtility.java new file mode 100644 index 000000000..fab364a3a --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WebDriverUnpackUtility.java @@ -0,0 +1,28 @@ +package io.appium.java_client.pagefactory; + +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.WrapsDriver; +import org.openqa.selenium.internal.WrapsElement; + +final class WebDriverUnpackUtility { + + static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext){ + WebDriver driver = null; + if (searchContext instanceof WebDriver){ + driver = (WebDriver) searchContext; + } + //Search context it is not only Webdriver. Webelement is search context too. + //RemoteWebElement and MobileElement implement WrapsDriver + if (searchContext instanceof WebElement){ + WebElement element = (WebElement) searchContext; //there can be something that + //implements WebElement interface and wraps original + while (element instanceof WrapsElement){ + element = ((WrapsElement) element).getWrappedElement(); + } + driver = ((WrapsDriver) element).getWrappedDriver(); + } + return driver; + } +} diff --git a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java index 2dc9c6daf..fe6bd00b5 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -21,6 +21,7 @@ public interface MobileCapabilityType extends CapabilityType { String APP_WAIT_ACTIVITY = "appWaitActivity"; String APP_WAIT_PACKAGE = "appWaitPackage"; String SELENDROID_PORT = "selendroidPort"; + String UDID = "udid"; //Sauce-specific String APPIUM_VERSION = "appiumVersion"; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/IOSfMobileBrowserCompatibilityTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/IOSfMobileBrowserCompatibilityTest.java new file mode 100644 index 000000000..1aa1938d7 --- /dev/null +++ b/src/test/java/io/appium/java_client/pagefactory_tests/IOSfMobileBrowserCompatibilityTest.java @@ -0,0 +1,75 @@ +package io.appium.java_client.pagefactory_tests; + +import io.appium.java_client.ios.IOSDriver; +import io.appium.java_client.pagefactory.AndroidFindBy; +import io.appium.java_client.pagefactory.AndroidFindBys; +import io.appium.java_client.pagefactory.AppiumFieldDecorator; +import io.appium.java_client.pagefactory.iOSFindBy; +import io.appium.java_client.remote.MobileBrowserType; +import io.appium.java_client.remote.MobileCapabilityType; + +import java.net.URL; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.RemoteWebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.FindBys; +import org.openqa.selenium.support.PageFactory; + +public class IOSfMobileBrowserCompatibilityTest { + + private WebDriver driver; + + @FindBy(name = "q") + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/someId\")") + @iOSFindBy(className = "someClass") + private WebElement searchTextField; + + @AndroidFindBys({ + @AndroidFindBy(className = "someClass"), + @AndroidFindBy(xpath = "//someTag")}) + @iOSFindBy(className = "someClass") + @FindBy(name="btnG") + private RemoteWebElement searchButton; + + @AndroidFindBy(className = "someClass") + @FindBys({@FindBy(className = "r"), @FindBy(tagName = "a")}) + @iOSFindBy(className = "someClass") + private List foundLinks; + + @Before + public void setUp() throws Exception { + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, MobileBrowserType.SAFARI); + capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator"); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + PageFactory.initElements(new AppiumFieldDecorator(driver, 5, TimeUnit.SECONDS), this); + } + + @After + public void tearDown() throws Exception { + driver.quit(); + } + + @Test + public void test() { + driver.get("https://www.google.com"); + + searchTextField.sendKeys("Hello"); + searchButton.click(); + Assert.assertNotEquals(0, foundLinks.size()); + searchTextField.clear(); + searchTextField.sendKeys("Hello, Appium!"); + searchButton.click(); + } + +}