From 03172d8583ba1ef7517d30f45e0a852e7f6fe4d1 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 16 Jan 2020 14:20:16 +0100 Subject: [PATCH 001/630] chore: Update web view detection algorithm for iOS tests (#1294) --- .../java_client/ios/BaseIOSWebViewTest.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index a608177ba..ce0b115a5 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -25,10 +25,14 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.time.Duration; public class BaseIOSWebViewTest extends BaseIOSTest { + private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(1); + private static final Duration WEB_VIEW_DETECT_DURATION = Duration.ofSeconds(15); - @BeforeClass public static void beforeClass() throws IOException { + @BeforeClass + public static void beforeClass() throws IOException { final String ip = startAppiumServer(); if (service == null || !service.isRunning()) { @@ -47,11 +51,17 @@ public class BaseIOSWebViewTest extends BaseIOSTest { } protected void findAndSwitchToWebView() throws InterruptedException { - Thread.sleep(10000); - driver.getContextHandles().forEach((handle) -> { - if (handle.contains("WEBVIEW")) { - driver.context(handle); + final long msStarted = System.currentTimeMillis(); + while (System.currentTimeMillis() - msStarted <= WEB_VIEW_DETECT_DURATION.toMillis()) { + for (String handle : driver.getContextHandles()) { + if (handle.contains("WEBVIEW")) { + driver.context(handle); + return; + } } - }); + Thread.sleep(WEB_VIEW_DETECT_INTERVAL.toMillis()); + } + throw new IllegalStateException(String.format("No web views have been detected within %sms timeout", + WEB_VIEW_DETECT_DURATION.toMillis())); } } From 3573e23eda46536b95aa1ad0b7c87c641961a694 Mon Sep 17 00:00:00 2001 From: Chanatan Charnkijtawarush Date: Fri, 17 Jan 2020 07:24:00 -0800 Subject: [PATCH 002/630] feat: Add support for viewmatcher (#1293) --- .../FindsByAndroidViewMatcher.java | 32 ++++++++ .../java/io/appium/java_client/MobileBy.java | 75 ++++++++++++++++++- .../io/appium/java_client/MobileSelector.java | 1 + .../java_client/android/AndroidDriver.java | 3 +- .../java_client/android/AndroidElement.java | 3 +- .../java_client/events/DefaultAspect.java | 2 + .../pagefactory/AndroidFindBy.java | 7 ++ .../pagefactory/bys/builder/Strategies.java | 6 ++ .../android/AndroidDataMatcherTest.java | 46 ++++++++++++ .../android/AndroidViewMatcherTest.java | 42 +++++++++++ .../java_client/android/BaseEspressoTest.java | 67 +++++++++++++++++ 11 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java create mode 100644 src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java create mode 100644 src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java create mode 100644 src/test/java/io/appium/java_client/android/BaseEspressoTest.java diff --git a/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java b/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java new file mode 100644 index 000000000..1370cf3ae --- /dev/null +++ b/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client; + +import org.openqa.selenium.WebElement; + +import java.util.List; + +public interface FindsByAndroidViewMatcher extends FindsByFluentSelector { + + default T findElementByAndroidViewMatcher(String using) { + return findElement(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using); + } + + default List findElementsByAndroidViewMatcher(String using) { + return findElements(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using); + } +} diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index c04af3ea1..c76542da9 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -97,8 +97,8 @@ public static By iOSClassChain(final String iOSClassChainString) { /** * This locator strategy is only available in Espresso Driver mode. - * @param dataMatcherString is a valid class chain locator string. - * See + * @param dataMatcherString is a valid json string detailing hamcrest matcher for Espresso onData(). + * See * the documentation for more details * @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidDataMatcher} */ @@ -106,6 +106,17 @@ public static By androidDataMatcher(final String dataMatcherString) { return new ByAndroidDataMatcher(dataMatcherString); } + /** + * This locator strategy is only available in Espresso Driver mode. + * @param viewMatcherString is a valid json string detailing hamcrest matcher for Espresso onView(). + * See + * the documentation for more details + * @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidViewMatcher} + */ + public static By androidViewMatcher(final String viewMatcherString) { + return new ByAndroidViewMatcher(viewMatcherString); + } + /** * This locator strategy is available in XCUITest Driver mode. * @param iOSNsPredicateString is an an iOS NsPredicate String @@ -407,6 +418,66 @@ protected ByAndroidDataMatcher(String locatorString) { } } + public static class ByAndroidViewMatcher extends MobileBy implements Serializable { + + protected ByAndroidViewMatcher(String locatorString) { + super(MobileSelector.ANDROID_VIEW_MATCHER, locatorString); + } + + /** + * {@inheritDoc} + * + * @throws WebDriverException when current session doesn't support the given selector or when + * value of the selector is not consistent. + * @throws IllegalArgumentException when it is impossible to find something on the given + * {@link SearchContext} instance + */ + @SuppressWarnings("unchecked") + @Override public List findElements(SearchContext context) { + Class contextClass = context.getClass(); + + if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) { + return FindsByAndroidViewMatcher.class.cast(context) + .findElementsByAndroidViewMatcher(getLocatorString()); + } + + if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { + return super.findElements(context); + } + + throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class, + FindsByFluentSelector.class); + } + + /** + * {@inheritDoc} + * + * @throws WebDriverException when current session doesn't support the given selector or when + * value of the selector is not consistent. + * @throws IllegalArgumentException when it is impossible to find something on the given + * {@link SearchContext} instance + */ + @Override public WebElement findElement(SearchContext context) { + Class contextClass = context.getClass(); + + if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) { + return FindsByAndroidViewMatcher.class.cast(context) + .findElementByAndroidViewMatcher(getLocatorString()); + } + + if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { + return super.findElement(context); + } + + throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class, + FindsByFluentSelector.class); + } + + @Override public String toString() { + return "By.FindsByAndroidViewMatcher: " + getLocatorString(); + } + } + public static class ByIosNsPredicate extends MobileBy implements Serializable { protected ByIosNsPredicate(String locatorString) { diff --git a/src/main/java/io/appium/java_client/MobileSelector.java b/src/main/java/io/appium/java_client/MobileSelector.java index 4b01d38a3..0fbe3284e 100644 --- a/src/main/java/io/appium/java_client/MobileSelector.java +++ b/src/main/java/io/appium/java_client/MobileSelector.java @@ -26,6 +26,7 @@ public enum MobileSelector { IMAGE("-image"), ANDROID_VIEWTAG("-android viewtag"), ANDROID_DATA_MATCHER("-android datamatcher"), + ANDROID_VIEW_MATCHER("-android viewmatcher"), CUSTOM("-custom"); private final String selector; diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index 926363602..9fc18bd51 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -26,6 +26,7 @@ import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.FindsByAndroidDataMatcher; +import io.appium.java_client.FindsByAndroidViewMatcher; import io.appium.java_client.FindsByAndroidUIAutomator; import io.appium.java_client.FindsByAndroidViewTag; import io.appium.java_client.HasOnScreenKeyboard; @@ -62,7 +63,7 @@ public class AndroidDriver extends AppiumDriver implements PressesKey, HasNetworkConnection, PushesFiles, StartsActivity, FindsByAndroidUIAutomator, FindsByAndroidViewTag, FindsByAndroidDataMatcher, - LocksDevice, HasAndroidSettings, HasAndroidDeviceDetails, + FindsByAndroidViewMatcher, LocksDevice, HasAndroidSettings, HasAndroidDeviceDetails, HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard, CanRecordScreen, SupportsSpecialEmulatorCommands, SupportsNetworkStateManagement, ListensToLogcatMessages, HasAndroidClipboard, diff --git a/src/main/java/io/appium/java_client/android/AndroidElement.java b/src/main/java/io/appium/java_client/android/AndroidElement.java index 0a68124e1..95899e4db 100644 --- a/src/main/java/io/appium/java_client/android/AndroidElement.java +++ b/src/main/java/io/appium/java_client/android/AndroidElement.java @@ -20,13 +20,14 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.FindsByAndroidDataMatcher; +import io.appium.java_client.FindsByAndroidViewMatcher; import io.appium.java_client.FindsByAndroidUIAutomator; import io.appium.java_client.FindsByAndroidViewTag; import io.appium.java_client.MobileElement; public class AndroidElement extends MobileElement implements FindsByAndroidUIAutomator, FindsByAndroidDataMatcher, - FindsByAndroidViewTag { + FindsByAndroidViewMatcher, FindsByAndroidViewTag { /** * This method replace current text value. * @param value a new value diff --git a/src/main/java/io/appium/java_client/events/DefaultAspect.java b/src/main/java/io/appium/java_client/events/DefaultAspect.java index 6e0aaf5c4..345ec77ee 100644 --- a/src/main/java/io/appium/java_client/events/DefaultAspect.java +++ b/src/main/java/io/appium/java_client/events/DefaultAspect.java @@ -99,6 +99,8 @@ class DefaultAspect { + "execution(* org.openqa.selenium.ContextAware.*(..)) || " + "execution(* io.appium.java_client.FindsByAccessibilityId.*(..)) || " + "execution(* io.appium.java_client.FindsByAndroidUIAutomator.*(..)) || " + + "execution(* io.appium.java_client.FindsByAndroidDataMatcher.*(..)) || " + + "execution(* io.appium.java_client.FindsByAndroidViewMatcher.*(..)) || " + "execution(* io.appium.java_client.FindsByWindowsAutomation.*(..)) || " + "execution(* io.appium.java_client.FindsByIosNSPredicate.*(..)) || " + "execution(* org.openqa.selenium.internal.FindsByClassName.*(..)) || " diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java index 9d84b324d..d789f20ec 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java @@ -81,6 +81,13 @@ */ String androidDataMatcher() default ""; + /** + * It is a desired view matcher expression. + * + * @return a desired view matcher expression + */ + String androidViewMatcher() default ""; + /** * It is a xpath to the target element. * diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java index 104448bb9..718cd403c 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java @@ -102,6 +102,12 @@ enum Strategies { .androidDataMatcher(getValue(annotation, this)); } }, + BY_VIEW_MATCHER("androidViewMatcher") { + @Override By getBy(Annotation annotation) { + return MobileBy + .androidViewMatcher(getValue(annotation, this)); + } + }, BY_NS_PREDICATE("iOSNsPredicate") { @Override By getBy(Annotation annotation) { return MobileBy diff --git a/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java b/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java new file mode 100644 index 000000000..5e75653e6 --- /dev/null +++ b/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java @@ -0,0 +1,46 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.MobileBy; +import org.junit.Test; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import static org.junit.Assert.assertNotNull; + +public class AndroidDataMatcherTest extends BaseEspressoTest { + + @Test + public void testFindByDataMatcher() { + final WebDriverWait wait = new WebDriverWait(driver, 10); + wait.until(ExpectedConditions + .elementToBeClickable(MobileBy.AccessibilityId("Graphics"))); + driver.findElement(MobileBy.AccessibilityId("Graphics")).click(); + + String selector = new Json().toJson(ImmutableMap.of( + "name", "hasEntry", + "args", ImmutableList.of("title", "Sweep") + )); + + assertNotNull(wait.until(ExpectedConditions + .presenceOfElementLocated(MobileBy.androidDataMatcher(selector)))); + } +} diff --git a/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java b/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java new file mode 100644 index 000000000..e8253074e --- /dev/null +++ b/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java @@ -0,0 +1,42 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.MobileBy; +import org.junit.Test; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import static org.junit.Assert.assertNotNull; + +public class AndroidViewMatcherTest extends BaseEspressoTest { + + @Test + public void testFindByViewMatcher() { + String selector = new Json().toJson(ImmutableMap.of( + "name", "withText", + "args", ImmutableList.of("Animation"), + "class", "androidx.test.espresso.matcher.ViewMatchers" + )); + final WebDriverWait wait = new WebDriverWait(driver, 10); + assertNotNull(wait.until(ExpectedConditions + .presenceOfElementLocated(MobileBy.androidViewMatcher(selector)))); + } +} diff --git a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java b/src/test/java/io/appium/java_client/android/BaseEspressoTest.java new file mode 100644 index 000000000..889aa238f --- /dev/null +++ b/src/test/java/io/appium/java_client/android/BaseEspressoTest.java @@ -0,0 +1,67 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android; + +import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openqa.selenium.remote.DesiredCapabilities; + +import java.io.File; + +public class BaseEspressoTest { + + private static AppiumDriverLocalService service; + protected static AndroidDriver driver; + + /** + * initialization. + */ + @BeforeClass public static void beforeClass() { + service = AppiumDriverLocalService.buildDefaultService(); + service.start(); + + if (service == null || !service.isRunning()) { + throw new AppiumServerHasNotBeenStartedLocallyException( + "An appium server node is not started!"); + } + + File appDir = new File("src/test/java/io/appium/java_client"); + File app = new File(appDir, "ApiDemos-debug.apk"); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ESPRESSO); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); + capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability("eventTimings", true); + driver = new AndroidDriver<>(service.getUrl(), capabilities); + } + + /** + * finishing. + */ + @AfterClass public static void afterClass() { + if (driver != null) { + driver.quit(); + } + if (service != null) { + service.stop(); + } + } +} From d9ed3a0d22ac8bb3c0827468fee22fcbcd21ca74 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 24 Jan 2020 13:29:07 +0300 Subject: [PATCH 003/630] chore: Add GitHub Action validating Gradle wrapper (#1296) --- .github/workflows/gradle-wrapper-validation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 000000000..405a2b306 --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 From 12f3b6d354cd7e0d661ad3c87004e9567a8cce85 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sat, 8 Feb 2020 17:33:04 +0300 Subject: [PATCH 004/630] chore: Upgrade to Gradle 6.1.1 (#1299) --- gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 35 ++++++++++------------- gradlew.bat | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13536770052936a92b204cc34e72284a03a6903c..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 50409 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 z

^YH-jI`)=dY;ZV9lv8V3D3|6zsibR~s4zY#pWp~2s-y;>x!8r>`fjbsO(F}-+S zv=SbO44pZh87YCN;7s>k2BsT>g^_tBd^*qYYyFf?y3kd}g#ZFx<`WfexS*AIC3q6o zOj?PRNQVU+M1RF&{HR}o6eG}2juaVFajj&HO_?rTdmBPIClvtx@8ES_jR9W9o?7+E zY2)|fI8DRUJrgxxM;}xLZ{{nTQ5=Xu3q_*iw^Bo4Djq#_Pw(U-l`Rzl~JJYGe6#-3Nzb%#c z$gr;FuR1p3=e=Bdt4s5p`<;JJNUnPPs7Nb~ik3EtvIjw{rL;{1#tcn2SNmU!~NN+Ryna3i7gGMKammmxMdib-FV$@5$B=zkNY0NywXg@s>Ij zgn_piF#tZVvtRHoTEvfGHUp|67~T!vYjJ^h<&xgpaVj`-X7|A42)t=;W+ zR*c)VGSUBvoIb4bbQAUlygh&`-|e-ykUVEJq~OU=86aCT-vyrAH_w1vz*Z18#eULX zPyzfqIP!>FUZLYm@IO7Xc?o!Q2XF~4EgPBvIRk#ubQU0QI+HSL`s}3^>HC5GDx2!o zkyJE5rlDbp&NG-y{PT$`mKOJ$4T!Jv7yHYzB6q~tb$$J5E|$iI>TBB8cgJ3I*-Y+{ z|F%;_cqd4U2`~}1rfgG}nH*;2`=bLnBPKy4T_*X|Zu;^KZg39xJA7G%+uNV#E zhSbq`TmYwebz_gExo9lfgG2+unhPHin_`}8Vsz201CKS7L$M>MmaH*XjG{GHtdj0R z`q0JL4N^Z8InhG!Ra&^WygV49PK>p<;}#evB)q`Wqn&T5m!^PO1_^OytxM_awA5RB?8mcwiRsbdAR>ZPHbr-blEx+T%;EBmpdniX!x9H4~t?g?*#;oD$1?fT>77Dz30cR8vrIGU#%V$w}Gr8|{{afHIH$3ak}pOvqec z<>nazmL(I{mb9mEF5c}%FuKDn)S}|4L%KV%M>nLR!9(<^!3Hen7Nh|A;Tc4RW`Qf( zgzBk(D1lMv6LRc(P}sh3!JTge1+Cf_HT>mKGQzo>Ni{C67eMS*qVQ|^ksdRbQSUU6$Mg+`qEH=yEYBU%fpmL%Sn+2>2A3w8??oRd75N?uzeb4eF>PX_&BPXf!1GX z)``F5D7LJg^K^Yq8ul}G!z%UVr8BXC-0beTNtry3ee^KDy|0V@1nqmzy}fi>m!s>v z^n&1C=L1Ll+l#0NGO=SJ%MJbVif|yt4Gb+zEPEK?(7 z5oaFTk9}Ob|Kw&_i}dAOlA?4I3FYO1o1YnpeUvV&+TukrxFl@trwGCc+3a#^6 zlPL*+LILs+?HK$2xkRM9+`$bMh=qYntUr+PgM$-k>&&A|x4qY^Wap%MH1OXuNr>%{ zxVV#2QdhKdmCr4{n5SE1_id%-0^PA{czl3=<(8aWssceg=oAWusqWhHj(4|3-=9ah zC|ni~)Bfp*PFwOOEWPK^oKblZCh`i^ zh2CjCAxu~Mn(7uD>FWj4M*2)OoQkN#2>>5C;7@vHBdZHWbwqh}xbJT&0wa!pL}bx? z^b62o7GUrTov~1f+F2u|B31&6J&cg-cJeus*@aoR$x0=Ttlq8v`Kj#)!>fyP`#`c= zY>5y&6RhUC`JQO)_khhChItzT|21rhVIDY6P!lZ?en>-aB590ID3*)@)4s1uI|S6e z8$*rz{EMn)$_lHsF0q|oGs*Tqg`pS1h|hr*^GMQFn9x6TzFI#Pf$9NY(B>zRZ{B}r z*ZOi#x>(;+q9feIG-)#Fnk^PABV{5~0~8AiM+#F(9sc|`Pn-E76;fdBsLdCXC74}u1g81&ScQL%!J$`S@hAFjLNxD z9!0tg-=hTk;4cL!c~wNuh~QzncOUamZ%b1|?jB$)Fjg?8e;^xp73I;?SFM;$ugK2xPcOfK2$C&_qHzYux| zstFXo!8{pX+7^DTiK6VpH8*$~NxpY&3V*i>0m!%QcmzT2L8bOv^$HU2x#5*hMTgdj zY1F9}Vw)??cJ(Zns^=s_bO3w3c`6jagl5PE_w!aESRt zRcw)v7TF)K*0P6#GoIRMV`bm{CNB5!Z4+deL|KrNJ88`!GTPzAY=8~V2$w&Sr^EtY z$N!4w(6z|ZG+?5g2Ht&O%6}uYPQ<6FA(AL$Uzfd^^gAzXHnjR)k ze-&qF4)P>n_%X2x$ZF~R!slKml) z{u@S7My8H3_6MVQ^E0vM`VYclMu7sLyeTs%gv6g!99wgTapMV2XoC(q%qgCLAxuR> z71U$DS>g~YyP=7#I{oQ)^d>sk6$&#c)1;9ePS{ROlzYNTbN!V6wEE2Mryc>Y_cN)a zC34y9sDahcpA6iJnvjOo$~({CPW zEeCh(w@kLZGzcxyC+gw|IIm-i?8hD+Ny?VE7)FcQS6w6sFdmTnbpwuK`dLNeFrU>h(E zhwqUH?+(`3|MAGhSGuj!9ZA z<4Gsg&-gE)$NwXFHJ`Y7asKeJ5`V}e6#w^;!6)y|LI6@V&H%W}==`R#INp2)g=^3p z9!l9lX40*hK=N7XDQhf|eU18#OVCCV3u3uqnaoXO9@etfiz3fywbo}MnjO&?#b|BC zlWkw}^u61>tZ8<{m@oOy*S|V`KqJ3BPd?wTICnstV7`-_1Xp;OVB`nX^^&D%?v##& zF^zAZAOMWH$i^}W^)tK}vL09nv#aD0?fsise0Gs9?IFQl8UvKK($IM(p~`%iBW2`# z@v~B6drfp(Xm{MXf{eycjJLOi5sVQpCRm0^PH2V*2ej26>I6R`B16kFToMDAgDg6K z+8D?1JACM!;pg%-K;#{K@oz{c>O%ZH8q1rdvEgF&4!H^D;ETArx%-O|#He@v*f~P;%Jd zDu9cOc{leJ>Jv3r>G4#x*4aZ%G}VKaUhMi+b`yRU)AZpKOB(k(&)jwX5zst>~5kpTAk-o3uLf%;+opyl=G{@x7$GcugM;Xu$NwIyh)Z zGt_B3RZc{M;6aZ=3X4o*@Qb>UACNV^XMm(uVk3s9nagfOL0vNgx2Xm3VI9gylT+#E zmimQzCr#}Zx{~tJmO`6#kT8C7*=gQV3&)bi&TooOBZtZ3bGuGHY9S)Rch3R^$Kz#q zK>^}Pc3}(*dvXAMmEB9|f@!7sK?hH_bydev7=M1}JPH+$NSMi~W)S8e@(!wg3qW-e zHp5S(Z%>~?_OWn#p5hjs1{7HkjR^UgjC@*tZ0rlzMlS;BBprbPCoV(NJ2?uVs2%$c z8XMJ1Y6LqQ{f-pd`a^3Z*-K`G=GdGsif-#_YmCJ|NYm_4IbU~s%4A_)27Ec?PIcuK z+h0Lgdi({vj^b7pyVvAQy^Cab3GmM7GxE|Bu`~8U?@zh^%yP`6h$)ceuQWtx_JNF! zUAL#m@)ffR{_y8=A|xYoDGGZ_BU+%hBwfQ#iSFr0EGa}aI88gp<2<~~`JLuxVEB~xSx46LoAo(y+2IM)XBrS)Ws6VgI z%l?A(oorlg#2oTo%#T^ueMizR;GCi2-wyDA+YRP|2PQ03$K7xKx~+9j@?T<=Mpz_h zQNo#>KljfB&kVl930c)!j-R8Nhl3*xAoWToTUQzml+Y^{1?0BI51}lYpjgYjr#mK-Cmv_riJ@PygEiin!S}DfXcdOdW%O|j zsejSxiH>p%R_*X?7H)Mv^AwjnosvB{@oZNI4ZNK@`j&2nD(g@~c%|9ux} z5xwW26^hf%ubYxFnhrl&bj|qV#dlr`O1MCYD3I~me?Z)786#Pb( zQ$x8DRsXB$d;{wm)t<{Q5t&6ytL>J!gV;%akz1pC0T?7-&6L9VefQHtUwuL9Kkywu zMmRou^Ngnlqm`kqla(EByAGQ1X-gwO=GKy|)cZ&9L3m@637X$G{Qg1ex{&GyyF&zP z4KA9IM)*@XFps-+r^|LumVgcbkdYHjc^=m8&bq{y@Y>x-Q0hpWVfS7xtg6UJq&=4> z+eleFjBpRwi)pg?d#gzZ^IcqQFAir~xZKMkUr@~+q)G13N^IaqBQ*~E%Pxi%l`bMl z7`8qizR}yHqP^zme@svdu|7cf-=P0f%@6{Zz8LsH!}bW37o&Q>jmbh`c8krg|)5FwUY2*=k=;+1LE^zf3-m^Shny#~#gi zOG02V9o76U-e~x`rhq3aZb9UJ{_E<0^4Aq`vYqw$z9aYrc1zL&n~$mtjtaa)gwuz$ zt;B>Kpon7%Oy#AjunkDsNC~nAXN9o8ePBBloVv3d-gdTYO;^Irp<7e;S?-57BgWM@{u$1W`3R}j` ziXF$>YD_^20)LS90~KD>URvPR&wmOs#+gYEpj>LA+Qo0vum>>fxnf}@nS1%Y}n`^I&SDt6m9OkXspb09@|Buj?OhDXG*tC zqr{Q9mo3mbv8Yk4)(@ac=)^yum2!_tg_pI!9ejk1+n;WP3Dv{lva)iQ?UKmV*QX^3 za^QAn%(2E9X8<0&S03pp7B0;kL^*R@;w&^*9zsNy3gjvnGIGQ27r@S=Bj`0K)+_1Q z6V}_BR{XBJ@g<9kVis$rObx;(Pz|`&#)8wB*Z;N`k*{Fy9V;g&)mH{gxCeH$C$h0q zTCxHj&h!NT`2@D7Hlg|i7ROP#`6`7~dI6V#XFjcDECCv+YfDHXQ~Ks@(4d?73#M6e z3QXWHXck!R5@|j$tahq=1T1+*!g^-cHU5;ya+7vfZi475Z?;~}Os6<_l+xR$_RMsL zc^FvI7twGS$rx9R?UHII)R4eR3M&l5+f}wlrbN;E(#^eJJ_lRU>fXcAyoe2#y~qup zw3qC|`T{EUX?<(uca&{mP<3?h)IVn9!8CUMKVTA+wS_;xVD~B3JZbGM+Bp`j672 zdlid728umsR2Wsl&^vs#_%iNykhb-H$IYQp9{@InUt&?bYs_y)i&-S3t=F+XDdr6p z@h7V2_=RIr+3y?g)?0HfllE?Vn$->x?JMOkEL1?&TCf=XINFlvN7_P6)S>Ul%nWA(+s+Yqc@HVDyn-X7`kC7&T3p? zhX5cKs=)$1FMmeomb-RhBeQ~_+Ojk!Jg2=fQl=_PIqyG*s@;HRYV%gsg%}%q@!* zAU{jr=#!NJ=;QtnS*pWwiOfFKoS<$-VjsgHIVHNLYuUV<;P&y2@eeiRXKX-7nlK$B zj`RPJyI{&f8hYl6*OdN}YM=)<4A}sG{x$xMBo8A&$lRjYkEISS5IPA1UdJEsofPyI z7hbOFbVn%1XH>enm1bxX=B(NI@xR1dmHZWm^*={JLCF8m)_3P1lVj#k0kO&Uim1X! zqm~5^8wWD7d0@UE=+=mP#V{}=&{FDh=%U~#I|=mJM^b;1I;$xBY2?W$dTv2q3SwA2 zVN=Yr(kWP*SpVgHUR^)V?ilt0=Yvf0{76`LjX{o+jD*C4C853K_V_ZuSro_k$eYUh zlOSfXupr|Iy%dU2x$J-U1C*{J0LN*zkSk9s5G$-(kL=f*aE;1}(Vg;DaqMl>&)p~e z#K9t-mHuP2|6m_ym_e_qTv6l;sm@~=d23OtnNAr~J+X!ini1=BYq@JbYhC}=cO!Pq z!#q3O{98eI#?y&5qvLY(g*ejLwAGQ1Cv3L6h#pl`u$$e8{fKUT2aH>L@F~zmWI1f~ zzm+6g%9y`iS{pjxFz8 zE;>J0cRlrgCaDw~6c?vEzlv*f>~%Z6mQ=^&lPzP;hg!vop(O}Cu72zBi0Zrp1XMwo8Kw89i*)S00Xw!}70<9dq`pF!8LtKKWq@Q5OW`IIQLBEx zsuPV4liG2_U48;l+}0%xd~4#pS&>NigI5my&~^E*iN67C{unRiqJS!#0zEb}@u694B%uuXq3&he@C&AIpbhsRN^~ z5QZ>M5MQEulmi-vPH03SBM#=%3`)022!U&i%4{JZ@&;z90h68}QYRk+WtUQcSQnbk zA|L_>WD4_Gza!DL0n}*1Dmgh&e(t( zsJ$GP?UG%%?V5ZHMXL00SC;J}BjTB5l#^fHUWAA@C((qvd(x%;Od*JOA82{L>6(h+ zw{Wv3-d-Fb@wXq!6o42_Lc~2(K0s2qV(2a2Y~Jx6D~kZ`z=>8M?9`VxxaR?E^**Ne zBe{VecSv3R+bruKEK8}kf9|;ASUaHHh_`3ru#Q^l043N!gDF@WT=Vu3$slE}zL%Gq zYi^5~ZEb6ZlZ9=LH3{of(G4CBK^E%y2ft!-g-y@9o!d-L%G;wm@S(t()z(t&CpC%Y z1|4e-g#VtTPY~i_VzRR9=U};t+~=<7%^z~_8%y|t#ZwrxJK=X;DxSJbXJ#-Kjkk_%i`UiR zDQe4WY{uKN6}+6v2`V$PZZ)yJV#{1f38|TPx!ol8$XQVZ?~pJG zRA80Qq`zct^f5GcQEP)}Q3kBA|1~Xbo+6%R@wy>s+^W^Pr#{;Qp`d}w$!l&|iZq}< z*{)hQHG}_E7xIX%^VaDwNDPglU-e8TzIv9D>&lN*PYLJZJDHQw&SrVWJ-zUZ>n&3~ z4-yQg9Tp-!4<8p$?a+n@@8&9WZ_zK4DxzLUB@TYiay%v`ySf0ffCp$~aU(XhISZkr z7m-Lq(O#v9_DGoDTG#9Mi1VC$35)q{)+KjMxN2YLQ;eIF0&XC}_x5cTSq+Pr@S~8I zbYx%G&-F$jap5gpW9;v#9`6m* zSp=in*a9J)QbAb-Rh$%5u9+0JCUqTEgZ$(*`@Odb{dyIdI0kP^dvPHNMr|sOoYZQ?uPcrd1d^1z}#OAF1}=IO)1FPES6+ zc<~S6VZu8~bilD3)~GTzK>1GdLw;mO{R8wnrM_-9CITQ9pUrX^IR@=F5$PpRRu;o7 zswf##T}RF75}afls#$QTwz8t|>qF&aF?rILi_18pEc>}?X!Ax`OHI2PW#y&E(R?e& z$HF~j6eqFSyP>3`w&27~rRnomXj>;wV87xJW3+cK3voP zq&*OQBAenpa>@(P=a?X^y%5v~8=M~LKwO~^W(zGa?wbQH%I1yr!ogxIU7;P3=320* zK;i(R7|knda3ZrNuXr}$gEG71Q@FKpo6zP(%Cg`Ga3MHOV7k`5>~A%JPzJ)TQJjT*VYwoUCNmi zLm|^Vr$LBOcxMmMO*#`z#-uM~zEDdfcl^0PaW)4M+m*TR;b|?A?+n~iM02oBs?yC! z7sO#d$m>xBITK8RVTX?1-$+lpeAmOUXx|}D#sN>V&QRf@vSnvKkq#P{Qqt92qLqLU z+24idf?z80{mIJYi@h3|MQ#&ye6c@`5L1Ehl ztHnDNsX-E!oRD_H59oQhd(=3w5tm1peFnY&&Lo3sd8madtF-ZJ&Upr*GSVTx>hf{W zeK-=Zz1vc3yB-8`jBU1P`tTE{=X!vaL(XvP#MC)C`kRs@ObW}7)+i`T+npc-25{QW zl1D+J+vtL@89omB{4k1wzB@~dY_E%kd zbd-+(>JLEz6Yya7&h<9p{pZBwLGEP#{?P%~-W!c51UUCoP29s)Cy4zW#6%1~X0()o zkU~N{tCrlKsR-R1-C;d=RC^UEXi`{JGdV17J}FOhl}(ax@F;``lpKK`J+7(gD8}WKB(RR#Gm1=V(N_v_8)QJ@)T8kLA zTxgTGWbKp)vwrAgT3FQ`~(g(=L?7DBzyEh-+TWXl)q#i4NLfgJ;(n)0U|)j#Pj6Ieb#tT zM0<2UWbQ)mWc(u!F?aiU=kRlN{c*QC{(S6aZSD&C z|Gt;}VvP+9(}ZFZ1@hmVtgFE-O2i_nq+XHa`UhBoylj z7H#g6OTVY>6W(`^`->e#0PrnGk1|utv$=1+5!655_97!-PjtF{0!qQds&|-$7peZy zu?Hgn^dx`!Fbtta=WQeip}>_$D`c=&U*zmdJ%wq`#gAP;bOaNwKgBv@8!uQ8Mc*_5 z7TAPYc9m*X%c4*=77Diw51c8u}(B52$;!L$)>4ISuwva=pLE{YhG^ zay?qP#aA23meQJuiOtoT`h8Ai;IT{@f(5D7JVloo!$V0LG7^gOXn&IAg^jFX7HXXV z?WO3GBm2VR%zBzaFfIQ|^}sJ%h5tqc2vf6@j3*zk1r;10a?vfL9!fM>Riew#TE@W{ zjQM2t$l?sd#e{c1LT|G60A$8dK1A$coq8W(qDdh6M z)sctUO8)-wLEd^L|0^f%?(3F`m1)_=>@4EGO`Y-#H)%`8Jopm*2E)EGHn`#i5a+dK zw#2G?)^^u*y{Ip*tP|s5`75gkfpKBrL}GX)z;~r z`TWXK(Ti)nd|jXF)@h!*WENipQ20S~H|{df>m^z`riT z#FA5Li0aYmh}zL8RciBXKVO!R$Z#Z~PqF+MjiVF}p+>4!DxrpKqWoh3hi~PdVp3Um z$HFQf4?Cw1MALt;1lxkHVNysRnCJ|7gC=hAF|3^f_qcuAijk?8bcJ{9i11zrK%Gh6 zy}!-Ub(+Q6cWJBCl6H;I7`P+Sr0=!#}NO8I0h&1 zljwawtU@pTD4AjxZK6(1ZN>g=9^>S(?F=;e>qS`2pGWQrdM( z#1N>iJ$aOj*z=0{fz9x{eb$U8<8Sd~c=o>>*Pf={%`b)HXMq0~c;Dm;MT|Wc<$*62 zaJH>DVVaIJup{hQA=Y-}E)s({;1-4}(28+u0>)Zg$_GkwAiFLq z6nUVMYdo?aP1N9T4+Vs*gm4(WFvq`EhhrENp3>U#)n&E$mDr_hi+CBUoW&;YWd@zh zkge1*(~^>c~!>`N6J&a*fvZ%A?CyU2lVp!$lmM-aNf-^LE~x z=~+8LHPOyM{Kpt9G0;h)$#RvtiF(^mG_!m87kqYPb$O^MgDx4|sBQT|Z=xN3a{bN413h|ikq($0=03sWKx+9iFoq~J| z?Mbz+f_s%3WaYRt6p(i>cl_cU1Whk4mFSKJIC1O?=0PUM7t}el8_Ns(ySP&9l3Y%< zyr{y(Ko>)2?z0QMP))^DT+jA2MqHaMtF9;?5`5CT@4%xupBXwG>#Jf0oytxLTcv!O zwRl`Izx4s@mU|JR4dr45; zZA%dEz{8AXN%_Ft`Vp;8=9jqbPRye`C5+`F2Yomb>;*6n;0?A^K?Ce$Hjkf}xcFDp z7P_1kq)~-Ai;qO7bX9*gtNN4PIQhH+dP|u(yQrxCosPcnUSNm)e7m-2tM-P;Dz**- z?bzfR1^L79==X&Q?3Pj8E%j}a>t8mw4iYM=uDxFkHU_J=eBbSDcNh8K%KbF;0(#)> zgh)g8E5BYy0jPKxN5^>R+5cuxKge@F6|S#FfmVa{J_9Mhxbvj>!cx3~`d&w^d?JkY zCD{bUWW6O7YKh5=eZFyY$o~#&z!*dKlCUJdK=%i-QvuU$|9U$CEd)>8Z-*t0+x!z% z0Lp(lqVd)ZTJ&&6Rw8ZFB^8_VV2p;`CH9>a!uWxK0|3FZ=0%v>F`j@qS(t$;2~O!m z@|sAlE=q7|q~UnxF-W=1(%&RcSZ(*j zlGID!pTJRJGV&_`p=wiG4{F%ur@C#!g5gshEIU z@)kz)8B35sSP6U=_s3!DSca*!?S{dGZRH>R0APG3Cw{Io9S_NY|2D?6iNuIcI7k8t zms|>wnhcIohR7h+M}JTxldK9P&TXXNP&j-c(28}|a5Uv&3|SDL$5w?;{(u$!l!!+i z$t4qqydKq|pC@A!<7wM7A@~`|uHyz=b^ko8r_jA40up~4Vj~`AOdHW z=M%`1cYRO*EifKO9TB#{1_e}5Q9+auS4z4IU`gkDdR{(u-T0 z7ib+;q6qE{M=`BiPjjd)aC?LlU!cmItKc!RpU@MterhHc6FuDK^cZ?!8tnoC3RcGq zFR{-9$lm@}un`%$#V^^PVBKEmpWYii*(nkwdFmG#fWMwzNeSsD0=_Rc(H`+AX&f~~{HqK- zSRvdNKzeIT+$Ah~nl@3+AeQW*hF*J;LS0@vEjGwxXxv9{S5D2FL}{BshRvSCfhI>Ui~s!q%D8a$@#pb0*kd$nMu=gL0k%A zr>m4&f`kzh21YxU%LPttpn$78!*mixQ!$`H9b9ZDEF-4egm=OI5R4@rY6L5gkttx0Dsfn_OyB2zhE% zspAfJp|j^{!MG8T_p8%rCY*WxMFLO+W#DN4c4yve$}f!eKc#=Tu=MY>(rm5_mUUyC zQ%v-5ZLTX?$At3>p~Zr^DZ1vr(zhyW&^}$GJ734^vLnDJyCA|}KR~GkT8VlCAdF+x zCUfFG;&&7MWm5zb?0ba1zO`KHyoJv#`MR;<1u{fTEF~U$ns4_6vi=0xepE_t?fDWF zU(6NPn6pAPvO})1gTA5*1=4LO2eYS?RHJZ$-4YY7c37Mc2M$N>!yh;!wgz2e1moiK z4_wzJ%Oa`i+tYZs!-@oX)UFe(3i3Bvk0%@HBqDV%>Zi&gz2_W(;Os7Lq_sGcRYRX1**eJRRQut+o*YSvYS7#3_SX$G9i6)fQP1MM{IJ z*1O4Fq#KW^b@}8(+l(V~3@VK$vsY1gFSpbHqAs))|Jbc9m-yv@hb`eta((HCB{q0H z-?50gj8~!XmBG<&KFaF~9Aq(QE2MP6cuwl^$RNf_117DR%x=0L>|YOKZ@fe*CPmg< z#*bpd57WQvAsv}w;3=Y+%O z#5y)Tv)ymG-Ht@i3 zfpPYSBjjO!16ZY75q{Zr*gK#7J6b*u#y7lY@oc*iH3(pVdC)68gD;U3dPI}fpZf{z zEeUUyEVgE|lxu~Yrt=pz`K<=u&Gtkh%$rvfT{CEeJCbh7z@9W^2qwzzXKVWFc5Yh>H8`Nwyk&Y)PgTrk{rZLx}ToUIvZh~WMF!QsXOU~+qJ{$>EiI_yB2T z0y6Ty^0{N8+@?xnYgADj$g|oDB9V4b|zZ zF5`1g_0HN&?gax5xxqgJPOb}vpYHIQU0a2}vo_2UR;ulD_EM1S*lf(VddkwO$RdyF z2-UJ0c-AMYgC-}EL3gss%@*4Hr=(!8-^zu zS0W0Qba0?)+HPu_)O;RrlZP)M#&f#AdQRtvb#;?pCC}SzN-Hx{<>njUo02Y4gBP5D)o_<+}Da6E&sfUCAHlmSm|1ATKKqkIeOKHR*Ec+Dz6`&K@C8)09suo{Vx6W z;z$N(aK~xCku0|U!t&)H4_0FxW9@hUHBE@t7+j&U6ca)4Je7mJM$aCO2r|yzYadMz zz$n|l_XE#*NjYEzC%0oB`gl>h1+upr2`&5U7p1(4~Jcc=djXo$Rf8)6eZB+`aGlI%Ek@~sgP*Y;Y%Vt5T} z_LmYG+(Hb|r@_Gd)^$DwPdIToinO`73P9jZ=l!xRlRVC=m86I(8>-({6+eGe9G4S**PQS z?QwYYUW`oOQt{V-cWNq-){z#tC{$>xMy8c8a1r87FZrJloV#egybtCzKlKIx{(SeA z%i@pHm@M(aD^CuSAS-uSql*bGxiEFfcj`7HE>x2x2@)qrm3kn51U>a-l%vm3Y(UTXr93;@%vF111zO7~ZRE*?9k zswIxKwvl0VNC@mrJV@XYDI+BUvCJyKkkTy7==|FNdi@q^%yGh_;6?Ku$KBFrsW*!E zkKva|O91Tk>Yvk%_K;_51M!F+qqG57B87kLWN(LbNlOT`P^yVYVtHK<^Wc`wkcD|O z`=-Y5P@<~;qbWh-_k3?qYEzjE zk0Oasw-UziVlE9SmzT?2iZ2d%F2FzE_4qyI5D6Im?u%wje=+j?{~CJ>s5+Jlen(mtF z+C5d(w?3pfVhw06RL1#50H5zZlN&%UsT0pky2ilR3%3xB{Y>imDF!U` zeFE|%d66W)QgT`mHYP4@CA=t{fa0)YbMRp$Kh*Y3$8=zYbZa*D*O`m!Uo7ouN z>(AU2P3Fxeqb0cpu0<#c_D?%~eN70rfZ;bGAAhqY_j!n`%eW8%&~u0fk*RNA2dJxB z-k?EUz+6c-Q=tm?wpyiKF8jo9&>uX{DZ%7&M-!q$tZL0&QcOI}ZlKamn;Qqm zh%$U{Q|r@G%WP1;VR`aP1ax%ek+4}amC0$3+2M0Imatb{jc?-->h-wpr`gC;^Y3j> zo16hCPG9`b*$JaQCo2wCUY$dI-{}mer9ORY1hcP_7)Dq*3*NFAkOt+zZH?T>I&p`` z*n7XC6}>~G7je+Ict-D@)+Z0i67vN0r(5N7qB1@STCt->0|OKKrF4N2gn%)vBhZ>1 z=1V*GSm6w~Yy!Q6o+oO;VC{ z+({U3cFZVP91YOh4x?LS!~pDp;BcYQ5{rxl5W4qCkw!HT)Qpn&iONyl-)B-bhfDh% zpeWeiEz+A1ob6=%Fk6j=!M*H)C4M(}3%?o|=IQySyTIoO*HFYDfTl*j8z}dX6b#Cw zpvct{uQE<+ud+>*ftx@xJ%xhxbxSX{b1&o^m?8>DVIEOvptNO5m(-jo;;1kY{ zzWHE$%wyG1uo6WxZAgcDO9jiwk5r~8zKC^6SOXS8L(UcuFFj1C>Ff$Of$_Ooj0F-Z zJ?bkzc#b;JyoyyrU7dA3cg5#IeTunhGhXJk5iEzfc2)vogSTY+BkSk}pIaGK1)RRv)dU-%4uFm#b}fOlqo$p_C;%nwg-MBX~XCob&b2v{3GRv+wGA^%$uV z;g?Kn%SuLTHnAb*twIkPAJovabVN+j>VYlZLHoOmbd~l^Jsf~o+AtM<@3K)@zG1x) zqRG?GqGhz=2D}_Oo<~5OQ&I-XHd5oTU>s<_A1?=bjwfaY1}QfB!z1hlI-c6&Bk)x{ zRt}_LcRE%ARfhHsMjx*o()oxm^&*nnPB2q5KAyR)%r`hjT+hkEGxwRn=UZw_1{T4m zE&-qR-ggGvvjiL&?}bl7lKNeQK~XjT&f@RRVsD2XUpJZ-PLBC)HPS~Kc<(N zCpM<(s4{yl*sWF!$kczhg#Xwm+x2mgsitmC?WT*&T{lIJXo}Evupu-iI9rKFy8&M$ z*$vn`ejeI2!_u(YuiX&_JU>N*4@(X5;%)Q=gCJjM&&`x1#h4!&P;{& zA@I&n-Bp@y2^qnvXZYJGUZ*w(R3Z`vyLt7-rJ3-7=;>FaK8zT$p5H8U6ys|BKD zwI6L6gg%XLa%ONucvp`@)l-9q_s+vqZrKzu@#!zJKOYCZ?c~Kj9FW5+_voD}i!8}( zoXNd*1EjmID>$R7DY`IsVZqM4-2swnqM6b>-P0AcbJNG>Q+gp5JxQVQ2i94hz;;jd zQ^e=XOQA-L(-?g>j*}v&L?8oy$1>bYtQNVzrq~vMUyFhh{-i~|-a8ju-+d5m?ZC8| zrKP%+rL};5-n*RQMKjOy=8mX|dGd@Yv0F=M3cxn#UAu`(|L`$sk|%Y}5dR5%YrUHF z2krO+Gk56l=uPIMHUEdcer5GQ*IvsrvTdznZDmlVs0U6W@9Xr=I2}h++ih`|$T&fS za^E9u1g{$S1)$x>BSNtuXoMplNPb9L9*<>6S?O%f(0VDh9I7~=Gi#*wfYRE8f$+hk zDS*nn{`9qYsahd$R;thm=583O62xOOtNI+NFszKbn4qZ7-028Dsx# z=3Wdvq!-5zOeE=YwP6%o_SHz@S>_5TOQH5Or?b-!GPN%;7Rre~agauap4L@I3Z7?f zDLOIIF=;2&Y%-H+!s!iRBW|eS{go`UzG{`%DhFP4iCAjgduPA`mwsq-It4*r=&tJu zqusJb?J;MD-mG;BD`e*oMLboc(*k;mdrn^BDNMYsNw|6z{~kvnqNdIml4{2`0e4nr z6<@}x!Opabmj~|kx=zyBp!0|;(-ds{NFAES=ShL6s%*8lEkh!9sf98uO;XBrpWS^j zaBP|J`_vT^S5yFYS!Baz;(PA9)IGTr$fWbFq%E(1eDxbfV~AZuN#LpY>V*@G6iBlBZ9OZbg zLgO_)th{o`WM_?IgT!LQZ3>rtb!H?OD-rtrIVIfqf%3@=kSgQ}iAU|#7-W2m+XIK6 zRkQj8J^f^5zf=sbI-KpH>(Yr;SxhGwD#v1BdU%>#c!w_}^B$klD!deKYKYZJQ95c8 zKtFMdI)vj#-Xi`!cvN&>`D(Qr%o`<3CNr;o>IWh_G!<)odjFQ+rf0zU$wejM*ae_{Pn zB&ynkEk>MZ#%Z}oyf~g`nofk}#%(ig-#Cx7rlpX-rYspK6%XS0JU<`Hq zL%kbong)|J>;7MBV`@;c!sQ?x?gi=af1J4IlvhCCd2B$X`ikAmuM}m+?Iy}3ZoOfu zLOS_1+2z-C?GAKU5?Fbp&=U0a1Orery;bwbA&OfF9$Vm)@ZoQLz(_(;<<}DFMVYZ! z*0a*;527v1K|0;>0;bg4&Wk=+HpxowzM$q6T{dW<7ZM8x4W!4EW1uY!H_;GP?s5$c z-r~#vtX3s6u9yy{5Gu3GLRWK*22Rz$D>gffEgzGNqKnUZTQxVQ5g#J!{m6|!G#hDX z6F@|{?z%AgehDvbI#;G#Cv&uD$@Q_=qyHn)6=D)$5D&ec*7R)_S{k$X!%%|--|a9m zPKVCY4sqXS*A?!*_d%^yGEE_07@gbG4)s;#0nZKZ6RFyK5qp$6zD0KKhrDY18d68Y z*xJGQoJlq+p-;VvRhYti8)`y=N*PP(-HD^`&@VM}QW8^plWPA1OJzhs7I#0LD#m5u z$f~TAEFvj%CUs^qP8HwoomN*{sIHb$O{p>52%>X9T1DH1T&+3W0z+fDU#fA<2i@{D zb3iIT@qu4^X@E`%9iQdxzT%>4=n$Z+GO!iVq{}%w!*;@P)Cr{}6As_!7{7Xsxa|_E z`a*K#al}-;rL1MPY-^QOZeeU}Y?6sssJ}f4qxHt{?A+11e!a-esmg|xIWcddNRnh4 z_0^saWLZ+C$5k*|9vMDZ4_@8dVAz@TX#kB|m&+{{nXePhmg2Maq;_OIqZ)T0<%n^( zk5%Lpp3Vg1Y1S+(+5t4;kI^VoSv9qs`@q34@ebO)t}7zJDe-Jt=56#oJZ6fW>yzB- z82aNJH0p9;oh6<(8)WS;W~w^uVrc{G6wP7x#rEYSEA|Xr>O)_OQB7uuTKdG-^50f_ zC*Q)06~tlE8^A8PO;hkneN?cbJoMy7&rgkIK>PkO#U>HXTggG`RG=TV_^&myRI1I< zR*+hkgVdVm*Kd>O6dZt>EvO_err+88lrleIHGGFZ;u?Gq`)kxlSmr54?JuUGy>Vuo z^@Y|lD`sdn{BiiBkX z8<;OFyv(LtXJM7wfEt_Bp%z-^{|7ae(u*I@Cm zUptlNP5z3^1d#=odZ)bKaR;&Jh4`rx8QU$^xJmb`W#rC4pw0s8BP*P56eM(!E#mvo zYJKmN_z>elp-!`@Y_S%~G2!HPrPGFW-%ZXUXZC6zp{bI&BV~G*TC>BJ0@+CB*Whr% zPNqnSMTw@A096hPX&*|e05Y#T2bv$D#?yPK{%EUYLmpB2n<#Hm$eY={BX{|N8&43+ z6c3yIn^aF6DiIE(om<`QuWXPXm$!;M%Z#f2A2-&5yo855k0<$VMg{=hH z!(dW#7X^uI5;@6Q+Ggen0K3MKkt$!awSHT3Righ zN{WBkQ&3Iqr_fQ{cJ!YpK$V7Wlu=L~W2aS_syNPUpVI)=1ai~Fc;q1zb#aPEDFgD zk!EhopzPtVOG{pgxQF*8tcGHF0P`)K=E(3558-UZ*+{G3_)avQ`Sf(Kh2u{#D~4Hv z);%EUKL(~QcBy4$CV~Nf(~jI_U+gQRWkbiL7#38;d61vsVBVa33wSe)CfR(=d$b~( zjFvK9Rr&S?#hBe{Swrht?V;*3=hz$5G}Dm*jrZHM-1#|!r5{R5(~w_@-Rb8j>!0Ek zifT!7h4p6->;CBL1#f`8R(v3wzoRb55$jRou;z_lMprnlWN@U&e{;zDUbnzyH8s5n zRrZIxX`EWeXjB`TrU8IvdKhdacaDgO4eJhI=myVcwA!t8%1JK9O1xuI4Yq z>qIcb=FS*2M>PAq%rS_tmk<}87Fxrucnr-|+h$c*A6NtqmT|?NBjyNtjy5ia2U!CT zruYpG%pxih`;~Tn|DD7~kkM*@1}Xw*fXIu8K;ldo zfZB@!=$+N`t=HbLW0DTk8)dYpPf{bwy*Z@#)^gFX)U>Z&bgMo%d9+)fAs57LW8AY) zLPSst`=1wrYKEZN0L6ggXQSgj*JGn+PY>5mH2!>oM-n1(S-qcPIU~iGidYMF@EMWn z`8hE|Xc5>xblYwTRBu&~B^^h>_V>jH;c@|E?XJWvV)l4*9wRZh(dub6aN@@Z z8}8ka0|kS5s@8n4)*4K^>Jzm#6QAit+sB{Er7zRN5-|1^`c=V?(lJDw3ql7(0M!@# zd(EjVF7%%OO#8QCnr&o9mQ54!kwN>lp)iOn6Kd}6er8JqdovZNW@P)gP8Ja+hdT|Y zZre0zwB6i<~db2OoP3*H+z5?vsuNt@FK6j;anMe%#RR z8xyuW?=XetAp@D(N*E>xkOr#J*A$R#i#5h(r!Vc^uqsTj$SZ2*xv8*NeZXQW=AyF= z%8h!v{K^VW2WC{9a`ecz)ZCH8Mllq5h!xA|n^$U}LCWj&GF<8(9vp}iO#sBfEa{$I z1xgi5#Tm<@h(ct>^o?9|Ak5+Sz*-Ve0(}DddzK|UCYZ_2ci#?k&u6=s`)DXjhy(gK zhk7ix8$9%iI$xPxRZ5ajQ=dsh`Ad9skV}VQb&LCGO^!zspD14#UHT}=;E{Y)4zZNU zOf0q}Koq;dzG~A#$n&P>&BwQ7_;$mg;rhrxX;YP^kTPy_wIe&JLke>YbRAKHVF<}R zA58okBE3Kbjyyx^8=OC_l!d5cT@pyAJ)r;gFOe4s0aGgoudU}cbe3{o{beZQgF)Nt{D`TK03ch(QWY(B<>jQO z$bQCM$DONrr;qmw+81&Smv1JLVazy4o*->j8y(t92=#21pP#MC&FhWVYt$D2rQ}?N z=)d|5*gn-o6z5v`oZS$6%lOi~=uFF@ca7h@gw?^c!&?>#~p!2|fIzd2g?zUN5N$Va8}G_Bsll zCie??hiOnSx?8}jBSoP#9n=)nC1rLR7!5ulq~~?o@#v#96e52Gyqkfy9eAW$_qsbF zPy;ziOr6HID^8!}G+RAA2mJ$*0;m|RJw|JmfF=@98godDJT>A!dXGEMFoaO+b_xTO z0j9Xqo!91CPZJ0(MOspfp$T7S-=!WuDNa&3GB)B7znKWNTQ;rc(8_f$2{QIKD1&DX zDYA?w2QpG*q_%?rq>u+tD`nDl;iq>Ah`{WOf*FBVyF z_?4wgs)q%~c<60eLclXR^!Gm?XK_v-01@PQKwJ9!H!%Z8_2AiGNEOLPxa6*dcbj9; zNxq;fN(83}$eV~ww}z~s4Kb~f#bGp4MQ<_Bjgp6X@0RTbDA2^n-(tP`TH)mt)sGi9 zpiR{##?7Vs2xS}VcW`L^A^}tj6VvGjIwo+{31TPdvse~GHtpto@s*qE2!ufs<_rr< z$m*A`Gf6s^0`J+Cm-c9hE2hg0uRZ@$d5z@#)S*Di+XQ;8!v1TD^;`r^uc{mN(@LO7 zc{)r4XaAL1J}fjIT!OiFyl854dAULJz@?sb=E)l7z4=u)$rll@*Es%=pKMK)>l4J8;s&^nJ>D4)hy}%aZ^^KD$qaI)4?xuu zMC*0Gp8%e1eK*}KM|rmia4w!k2<;ZZ9-g`6w2~|_kfv?Jp?6UiK}VyBIxlolU%)Xw zyc%(1Q8KZp=pA&=$~)&B1mo#XMPzf%taPzu!fKRLvNL0Bj{}~5|K6W z(p%Jre8Rrp;E(CZ)hE10PhV^e;X=iz+EI}6!O6&^S#4EYh&1V_W>|SkR}frRNO!J? zkWPCtD(iPo8Qox7`hBB&uXrl%aCwd8Q9T7Y^7DN(M1x>&O7wTe4Gl~FNOve-5*cy+U zi}ec&ZXdxlj1F1rCsa#!XrxWoKgrvRevu|Jvr(ATxjy0wxJXpZ-EZMFD^gizT^(@h zVZWDDFwn7{<^p(_P?h1+`3M%dXMVKUaC_lu10f_2=y4O$*ZUBK=}Y3fV`LY1{53j|hNNcYo7z{y zj+-RHyxQ`mYtnrZFOq>d?yu`#^XZXiFOhwALtO@lCuMqlDP@bgk8r#ESXJbaoJ>6( zwB>J|g-Pj)HWF<`kw!qK7{jz`SAn|_Z2wr9}GYgw`X3iL5RoLf$Er_BP2*F^ZgMrzA(v+xwE{Fo>;z|D6=p#$p z%1Ey-;rVTP8jqJ(gV|D@kde(6bWKCHbaq^uaUq#j!f$r63`HBr+SYfdRj#^^B>}L%fJbC3i)4 zbys!QxrI5!Y9jAjRDW&2l^QYsW$pWCO|H&`TI?Ll`ygZ%;i*fy=Thef4y2coEyeHm zNWu$wI|xLc_bNTcpt$e@AHuFFpOXQ$Il_ftOwFi!Dyae7p}F1ylQxg1ZPiY$xqB>$ z>{$K$A5?~8xaD|x=8_L)n63#Boze4!N&_xF-C1VQ)F!S;n;4w{%-Ro*<#^S+)TPw9 zP??3h*%j##Zxy4?lY9h0%f*|e)Iir+>00WN<5UJ!&b0C&PNHkGmRKD=dFAoR!Z!o& zyhPu+&Xy(P)6Ep>4LDI<*rx4%fcKK1pL?5ZSU$(hq3+zmT)&IRm0mfN=~Iz{-%_6D(~{zV9ziGElRox<8m=(Ms~%G6WK?xyTBudmD4o*w z_Y=;}xkBLSMtH z#aVzdapag!f~A&|pLZT`R;+0dWnDIVfDly{rzJj^zkRP?v8I#H+Yof~mju8#eE6x;xyXV;I*H+crsZ(<`621p2E9)*53CZ9U zOqUV_>K6`K!+(|vj5xz((o1M=`SOg1EV=yA7W`UM!sK-BwARRavBC~6tej{?BZR!T z^5jt>iH{!_S<0O|*e$IxwiRmlfo|(n~2A*ySEOf}ne5S682P4KpKLKF&8>#Tb5}%NkhWfIKM%frB zixeAt8+)q;sHjRVd1R{HM#RTLKIV7fG7N5?Q->y($WwI_CDkXcRU%RB1$tB#FDs00 z<0TacFaZZi+Z+^?(YC~LLP4bI(VTlqI5$+3lQ#J?LjAi zL2H16C!K0V6$I5BQf&vPGxlgrV29Wmfwz{JH3Trs5>{v^$q$6bK}JWLfXbxi5Dn#E zx4=e(QQw`thin(Yi=l*Q(xM$U*)IHv=FQ*LI!tU z$&{-&A+AR1BCh%qAgxwNZgic#DGgv6hwB$;ilJr-{S;}n>D{ELB#3!}oY?h=txz7Y z+#KXm8rrBg7QYZuP`o={4r@5F{i_2U9D0oXecq3`Q&Hp!y)#(EOgfU=@Ot*sEkHHTYj4|0E3 z-ezWHl3^fd;^iahNNYfdI527iuvTo*LJjX8yJ}&#Bvg<-&gzOI8Ckij*!V4DtYo)P zAJddHIc}uVR8_1|@P4ltQy&wNU@dm2R_0~dZCy(xM|lUk(P_`ZuA}%ZfI)&f#m2zr z`+n8Pad(n;U-4|I)7O+b74htNnxk1VlL)H2M6!rbAK?iCmEH)0>7EGpJNE{o`2|20 zB22L2g=7wgAS2N?QxZF23z1c5EW-m&cEXwopKl*>QrD?&;9>l`yz;dUgvkpOOxaE} zfadYqs66cT4xrE|FiSRPfNxh*JGW55TgS+%>8`#{6Z8c`e0hc6-neRQLaFDLe;sHQ z$!MRiUB7ew46Zfd4B-AZSucZ=DTV76%^B zInK9c6zgu96y+I}txWl>wO?ng(E3$*xjh7Ggrf84ndRid7T^NN)4d*3n#zUi*5qok z@stc3t@CQi9ENeYLD5$c$P98v{*RW0Z1^(7MgbCOoF*aDERFi2BUW~`2iS|vZE1Ld zr?r;u3!(ALC9FbU=RQDSS#X3Zyr&_re@)@}RohzFP6G!S0A0r_efTN6(=cI%Ip^Aup1&5`ABU!_{6hiZjb!L1we_Rz^JTM5 zjJ>*V7t~GN&N*7)7D4){`*&+JC%$c`C2)`7hb>!Sunmqu&+hHEpvrOLR7jNlZG~9G z3}W{!yIal6W?vM#q>7LeEL#KZ@1mCN7w3v_m)dd+i~#T*8*(Xs{L_Bos*RD_u&2B4 z13ZFQ1(T~hmhXi7JRTAjR#DoUPpo#okr_IG^@B%S9u5rd*TM7oE642l(rzMjV_bYQ z{$$kHRwWq{*x8+`V{i6lC91Y{uhze@rW9w#tss|VCj;wiaKEn;NeN^-uN991x!D)l zZJ;+>?K}YI;$|BYT~8rU23u79i*8K~4$#_%+YF5^5~~t>65?^Md2m}OSNHOKgNEU&G+j)V$RO-E}!%1J_RJqmtKu>x=d^VnXx8q@7BV#Tc9CI7yqd`cIn znN*^u@kF{mP5-RohP7~|EsJ(EpD<># z>TY9h#-dQYXfF{SSwf@Sbt@=mgf1t6aa@DU;3&Lu1N#oLVF%({E}rH)iWwse6I%}A zer$kX48rBYVx-{?d7JF;T@6xB0rbEWJ%!P*$EP7W%Mb5dr0Mwg#{ zhcGuo;Ec$J4;l3CDL?zY64>MWHaOCxw%*ILE3QWe_`-FCR|OQ=@!|_5aCS;fp}!^R zvpnAGL2(dSX=TL&C2J50MJvx|N{%6VC$|6=UGUvA0%0yuT_Q}_{NBV0zW+R?bK|@r zXrUIVNq0o%p<6ThU_hm7~{)P9wJ#d6SiD&kdZI(}Wzs3{~W&@!FSDcjrB#+9a}J(uO70wLf=6nkr-`1PPCh zcZFKFNB`JR|7IMUTRZ)VEV|l-wN#P}pen&KzUuxMR2QI?-K`4ov_-Qozg;$FYS`1n z8+XtaaL|!pQT#1SevBq>l5gz#enA%T8?BJ4Rtp{s6XOjB#=`|8lR|c$(98s$YpCiL z-?-8@tHarg%+P}+__a&uVj|`GA1Ef|X~Y}`nFUn1+y+w zOam32Q}?X_=|n=EK@6eI0@j0?H}RJw{|%(f2M6qs)1KXcz5K3`fxB0aAr>UPe5)b! zvkbK?6dQ@{Fgjar6+9q3!P`*|0Q>Y0Y08C2WeEjY8oTOWG<;E! zxD5Kk-dPcjPA9+9u56nObC-ZbOM*@ ztDy^fRBdF{+wz`S9_}T{ixhO;e5X{yjssHR(-Z74?kcT0J2=tnD6B{-VZ=Lg6j2tQs-Fthfnk^vVdJ=wCaBD9zu1YrQ zG}a05XoGmzght|;zqgG#gvCY|N7*qY-&dCVbm1a*=Ey&k(FadxHU==EV&rfS4jI_h z_r-O_Ts}U=s8h}M?Oz}8GZRV2V<#XGj2bo!sCL*ZuM@U;Tb;qWR+jO;z2rpH0J_KO zIuza&$6mQ$RpLi%XU~1J16WTvChw%5$oh7+z3P-ZEKBDe)01Lvy?ca3XAk;%gX|^C zr1S-voy80Xgkg_fQ_O?>>c^ z^FfR`L1Z=D${rhUu)nm54 z+!c5*K0V{2Y$rdpdhzRClcT*d>30*}8eB=%o}Hl*LKkHH+Z5~6B=t%!LPB?N*13vP zp(5K(Rr)a^Y$L)Ju>jHC?}?t?ysq#jQ^srb234^&SJG?rVJeNoWCt3}$rfc-OrBwu z1DlP^u@t4|{tCi?=rY4E-k1^^oE(%T*!P#`_b3PERp!;m3eCKCpk11}GCv6{jo2KF zn?5T(en43GS#${Rq7J4{24mkC3y0tkI!sEhS9U0rq#+|?? z=361+thTJC=+1%q&devQlTOhgiY3FL=Zur;tKExF0***W)n)*qBMl5XgUh|Af2}d$ zN9_p@f!0{TK3U%(%k1Km>Tuh8y|0P|!KQS=qCf#dn4%ngz6 zpj>0kn~5=+)Z&0mjLZ!5*_xMPQ1Yx4mc2AzFUgt9?$v}0x<`!L^0((;8-4i$v3(@_ ztkGP$5qIjhpPN|?aPD}5`wsfXS!Q>lnkZ8Y(ZLKgE|Xb1@I`StG~%jvK%F^0$S-wI_2mpyvr>}e9kImVSGVO4f#Jh%$ODM+K zKORg&B7eCkv?_v`PRSm|heTK!{Q_)JXI0tRpH9q{l{a_lr}NytpiLmSN>OHYYAB#r zBM~-PTK;sssHPUy@D^z>gkjD2mfQ+D5-D16Y=SNgKt4`qm*|&Qv2#k*3#A ztUgIx&B|*`kmAQ|jd(Xs4;!69C+;zEQ5+?EfcTZ}Qcl!mT5qzxz9kvWT2uSLVVojZ zbSw@2Q`_z+oTR8vYtppl%v!zsH8IOq|MpZmkHz`T84WJk*#?i=+}uk3*<>HO)gIzo ziaU2jB|xW1|2$5%NtO&P)`rF<5p<7%=OT9gp!y=WY|%RU`Ay-=yQxnxwR-QnM`0h@ zXU;xzs5vC4OVNf$W1jjiEd~38* zW=Sfq;4-kDlC@9Ac5Osg6cCaOY_er+R9Rj^s|HvWI&x%iVN-6rb2pinQ^X&UY*nalaHo z+898=>^(bRbN(zO%=w5aqH<}{&}}}i{`jUx@sa}?X6=#}^D8~2$s7xbl;%??dJFv* z)dS|5V6Mz=AiiI$dWT&@Dh|BQP->{*<_AImju3t^Yo#&b0p}yUrn=q?Yg|3PU(ANX zCrvq+TWOt%ls0}Z&n^eg#iM)7uSfLXdYy`iWbqF0eBXBtVwpTvhJI}--+Gljb%9(p zpQzhI*jnJmoSek&t74TA-B0qtgilrDllqO)kW#eFc$SqD!(B}oBwuqW@eXprr`Q?` zz=&!|GyKf=Pm9V$r8@P*p5d7*VLP8UKjSN+h#kmV%h2@aO7#dot&V_>~3YjAm9^y!lp1%mxdg+h$G+sffVe($4J}vZN$*(UepFmN)xtj9EQq=JY_% zBfITEm@}C2)K?|eFUct9JLyaY1|+)=lYn9_-L|%8)MHPPXwhMDe|BUMwfF1NtD%Wa zkQgvBN96t!~n8x>9i3Ijr(`Z&cb^z=P zX2&Nkvk8~>?-?G3QW%P-&DKum6m4S=vuQUON>5i30ZRzU2wPiy$b1%8g7~{x}RAx|$zB_l0p6B}!>3wTtXmyeDk={UA-K7YC1V=K5 zl%L*wcpY*1V}f8Wnvd#MkmdxzP{Lpav-s{{BDMPeA8i zfd2^oUmRZI$Sdfdfa9MVB{2S5lw_)z;$~1tARZ_rkmVnSU|@v-MBqShcr2h2I3_10 z8?%Xxv9t5PiLJmHKazl=bSOc+%Jc^@1>|4E`2W+QrSh|HJ_tMw`cV8q6axz)BKrSq z&C6)_J0GZ57D2Auum1guxG=y0?k6EE^!S9U7_v!P#A8&yc8%g zbOMd~x8eR{_q0EFFapsgv4P_XRR1IUAKQ2RL4%?L>{6orAA;cDw;=h0Akz4kP&AX| zzYo)YYxw`WbJER!;r~SO?7@*|*kFooImk0f?^`S-io5e5dlLq4Q(>(oUeL!aOZwmyBoccLbgU-O} zDc;|u{ckBzFff6CnEli-RW}e84>)E+`Oi~kGW3;hlLCC_yx z2u}yf8~Vds{*i_82mU_i7yfV_>v#TtgtY&Ghb;i%`TwUg{~Iy=?-775`qcn#CL*wN z;g3=KBO>*WM!-w|LzDBrGwUCbfPWBOlmoZ&IR8hoKf=NOAb3*=YJ~!*TS)vrME{PT z`J1jVz^?{GM9%(yx3hmNiTq6iIu`~kb^~*}$$_8@grCn=@zT#{Yi}5Ijrs3~F>(3l zHMnsg)w1YMOu)eJ&*s_$FlPDn??d#DqXpvsvsUrnM`1wC6wrEz>~FpxpGN+t>-G0h h7ytqC5&nNUVoI{mph*V|j2rZK44Si~emWOm{|AOYwcP*! delta 48113 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 Date: Tue, 11 Feb 2020 11:54:19 +0300 Subject: [PATCH 005/630] chore: Use Gradle 6 built-in support for javadoc and source jars (#1301) https://docs.gradle.org/6.0/release-notes.html#javadoc-sources-jar --- build.gradle | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index bc68f5789..6efcc121c 100644 --- a/build.gradle +++ b/build.gradle @@ -37,10 +37,14 @@ dependencies { lombok 'org.projectlombok:lombok:1.18.8' } -compileJava { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + withJavadocJar() + withSourcesJar() +} +compileJava { def ecjJar = configurations.ecj.singleFile def lombokjar = configurations.lombok.singleFile @@ -117,16 +121,6 @@ checkstyle { } checkstyleMain.excludes = ['**/org/openqa/selenium/**'] -task sourcesJar(type: Jar) { - from sourceSets.main.allJava - archiveClassifier = 'sources' -} - -task javadocJar(type: Jar) { - from javadoc - archiveClassifier = 'javadoc' -} - javadoc { options.addStringOption('encoding', 'UTF-8') } From 188b106489b4798b5b7ea75c99f04e30378b53c6 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 11 Feb 2020 11:54:38 +0300 Subject: [PATCH 006/630] chore: Upgrade to Checkstyle 8.29 (#1300) --- build.gradle | 2 +- google-style.xml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 6efcc121c..4b5d1e080 100644 --- a/build.gradle +++ b/build.gradle @@ -114,7 +114,7 @@ tasks.withType(JacocoReport) { jacocoTestReport.dependsOn test checkstyle { - toolVersion = '8.22' + toolVersion = '8.29' configFile = file("$projectDir/google-style.xml") showViolations = true ignoreFailures = false diff --git a/google-style.xml b/google-style.xml index 2fb4cc988..5762dbafb 100755 --- a/google-style.xml +++ b/google-style.xml @@ -41,10 +41,6 @@ - - - - @@ -187,11 +183,11 @@ - - - + + + @@ -207,4 +203,8 @@ + + + + From 6cd31e2b2eb979bea389918ac42f5b7ed6c40b60 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 11 Feb 2020 11:55:11 +0300 Subject: [PATCH 007/630] Bump dependencies (#1302) * Bump dependency-check-gradle from 5.1.0 to 5.3.0 * Bump Gradle shadow plugin from 5.1.0 to 5.2.0 * Bump ECJ from 3.18.0 to 3.20.0 * Bump Lombok from 1.18.8 to 1.18.12 * Bump Gson from 2.8.5 to 2.8.6 * Bump Apache HttpClient from 4.5.9 to 4.5.11 * Bump cglib from 3.2.12 to 3.3.0 * Bump Spring from 5.1.8.RELEASE to 5.2.3.RELEASE * Bump AspectJ from 1.9.4 to 1.9.5 * Bump SLF4J from 1.7.26 to 1.7.30 * Bump JUnit from 4.12 to 4.13 * Bump Hamcrest from 2.1 to 2.2 * Bump WebDriverManager from 3.6.1 to 3.8.1 --- build.gradle | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 4b5d1e080..92de4f553 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.owasp:dependency-check-gradle:5.1.0' - classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0' + classpath 'org.owasp:dependency-check-gradle:5.3.0' + classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' } } @@ -33,8 +33,8 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.18.0' - lombok 'org.projectlombok:lombok:1.18.8' + ecj 'org.eclipse.jdt:ecj:3.20.0' + lombok 'org.projectlombok:lombok:1.18.12' } java { @@ -56,8 +56,8 @@ compileJava { } dependencies { - compileOnly('org.projectlombok:lombok:1.18.8') - annotationProcessor('org.projectlombok:lombok:1.18.8') + compileOnly('org.projectlombok:lombok:1.18.12') + annotationProcessor('org.projectlombok:lombok:1.18.12') compile ("org.seleniumhq.selenium:selenium-java:${project.property('selenium.version')}") { force = true @@ -72,19 +72,19 @@ dependencies { compile ("org.seleniumhq.selenium:selenium-api:${project.property('selenium.version')}") { force = true } - compile 'com.google.code.gson:gson:2.8.5' - compile 'org.apache.httpcomponents:httpclient:4.5.9' - compile 'cglib:cglib:3.2.12' + compile 'com.google.code.gson:gson:2.8.6' + compile 'org.apache.httpcomponents:httpclient:4.5.11' + compile 'cglib:cglib:3.3.0' compile 'commons-validator:commons-validator:1.6' compile 'org.apache.commons:commons-lang3:3.9' compile 'commons-io:commons-io:2.6' - compile 'org.springframework:spring-context:5.1.8.RELEASE' - compile 'org.aspectj:aspectjweaver:1.9.4' - compile 'org.slf4j:slf4j-api:1.7.26' + compile 'org.springframework:spring-context:5.2.3.RELEASE' + compile 'org.aspectj:aspectjweaver:1.9.5' + compile 'org.slf4j:slf4j-api:1.7.30' - testCompile 'junit:junit:4.12' - testCompile 'org.hamcrest:hamcrest:2.1' - testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '3.6.1') { + testCompile 'junit:junit:4.13' + testCompile 'org.hamcrest:hamcrest:2.2' + testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '3.8.1') { exclude group: 'org.seleniumhq.selenium' } } From 568eeed2954edd3311ef6471dac8ae0b5c767801 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 16 Mar 2020 17:51:46 +0100 Subject: [PATCH 008/630] feat: Add wrappers for the Windows screen recorder (#1313) --- azure-pipelines.yml | 83 +++++----- .../AndroidStartScreenRecordingOptions.java | 9 ++ .../ios/IOSStartScreenRecordingOptions.java | 9 ++ .../BaseScreenRecordingOptions.java | 2 +- .../BaseStartScreenRecordingOptions.java | 35 ++-- .../screenrecording/CanRecordScreen.java | 4 - .../java_client/windows/WindowsDriver.java | 3 +- .../WindowsStartScreenRecordingOptions.java | 151 ++++++++++++++++++ .../WindowsStopScreenRecordingOptions.java | 28 ++++ .../generation/BaseElementGenerationTest.java | 12 +- .../android/AndroidElementGeneratingTest.java | 22 ++- .../ios/IOSElementGenerationTest.java | 34 +++- .../io/appium/java_client/ios/AppIOSTest.java | 9 +- .../java_client/ios/BaseIOSWebViewTest.java | 22 ++- 14 files changed, 331 insertions(+), 92 deletions(-) create mode 100644 src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java create mode 100644 src/main/java/io/appium/java_client/windows/WindowsStopScreenRecordingOptions.java diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e374442f0..ea502ec76 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,43 +15,46 @@ variables: IOS_PLATFORM_VERSION: 12.2 IOS_DEVICE_NAME: iPhone X -steps: -- task: NodeTool@0 - inputs: - versionSpec: '11.x' - -- script: | - echo "Configuring Environment" - echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' - echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "$(ANDROID_EMU_NAME)" -k 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' --force - echo $ANDROID_HOME/emulator/emulator -list-avds - - echo "Starting emulator" - nohup $ANDROID_HOME/emulator/emulator -avd "$(ANDROID_EMU_NAME)" -no-snapshot > /dev/null 2>&1 & - $ANDROID_HOME/platform-tools/adb wait-for-device - while [[ $? -ne 0 ]]; do sleep 1; $ANDROID_HOME/platform-tools/adb shell pm list packages; done; - $ANDROID_HOME/platform-tools/adb devices - echo "Emulator started" - - sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer - xcrun simctl list - - npm config delete prefix - npm config set prefix $NVM_DIR/versions/node/`node --version` - node --version - - npm install -g appium@beta - appium --version - - java -version - -- task: Gradle@2 - inputs: - gradleWrapperFile: 'gradlew' - gradleOptions: '-Xmx3072m' - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' - jdkArchitectureOption: 'x64' - publishJUnitResults: true - tasks: 'build' - options: 'xcuiTest uiAutomationTest -x checkstyleTest -x test -x signMavenJavaPublication' +jobs: +- job: E2E_Tests + timeoutInMinutes: 120 + steps: + - task: NodeTool@0 + inputs: + versionSpec: '11.x' + + - script: | + echo "Configuring Environment" + echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' + echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "$(ANDROID_EMU_NAME)" -k 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' --force + echo $ANDROID_HOME/emulator/emulator -list-avds + + echo "Starting emulator" + nohup $ANDROID_HOME/emulator/emulator -avd "$(ANDROID_EMU_NAME)" -no-snapshot > /dev/null 2>&1 & + $ANDROID_HOME/platform-tools/adb wait-for-device + while [[ $? -ne 0 ]]; do sleep 1; $ANDROID_HOME/platform-tools/adb shell pm list packages; done; + $ANDROID_HOME/platform-tools/adb devices + echo "Emulator started" + + sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer + xcrun simctl list + + npm config delete prefix + npm config set prefix $NVM_DIR/versions/node/`node --version` + node --version + + npm install -g appium@beta + appium --version + + java -version + + - task: Gradle@2 + inputs: + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + tasks: 'build' + options: 'xcuiTest uiAutomationTest -x checkstyleTest -x test -x signMavenJavaPublication' diff --git a/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java index f3c79a508..c8378a86b 100644 --- a/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; +import io.appium.java_client.screenrecording.ScreenRecordingUploadOptions; import java.time.Duration; import java.util.Map; @@ -49,6 +50,14 @@ public AndroidStartScreenRecordingOptions withBitRate(int bitRate) { this.bitRate = bitRate; return this; } + + /** + * {@inheritDoc} + */ + @Override + public AndroidStartScreenRecordingOptions withUploadOptions(ScreenRecordingUploadOptions uploadOptions) { + return (AndroidStartScreenRecordingOptions) super.withUploadOptions(uploadOptions); + } /** * The video size of the generated media file. The format is WIDTHxHEIGHT. diff --git a/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java index ff45f7e08..d9b918977 100644 --- a/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java @@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; +import io.appium.java_client.screenrecording.ScreenRecordingUploadOptions; import java.time.Duration; import java.util.Map; @@ -38,6 +39,14 @@ public static IOSStartScreenRecordingOptions startScreenRecordingOptions() { return new IOSStartScreenRecordingOptions(); } + /** + * {@inheritDoc} + */ + @Override + public IOSStartScreenRecordingOptions withUploadOptions(ScreenRecordingUploadOptions uploadOptions) { + return (IOSStartScreenRecordingOptions) super.withUploadOptions(uploadOptions); + } + /** * The video codec type used for encoding of the recorded screen capture. * Execute `ffmpeg -codecs` in the terminal to see the list of supported video codecs. diff --git a/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java b/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java index d194e6ab6..127cc29a9 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java @@ -33,7 +33,7 @@ public abstract class BaseScreenRecordingOptions build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); diff --git a/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java b/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java index 69c1498e4..551a68bec 100644 --- a/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java +++ b/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java @@ -31,8 +31,6 @@ public interface CanRecordScreen extends ExecutesMethod { * * @param options see the documentation on the {@link BaseStartScreenRecordingOptions} * descendant for the particular platform. - * @return Base-64 encoded content of the recorded media file or an empty string - * if the file has been successfully uploaded to a remote location (depends on the actual options). */ default String startRecordingScreen(T options) { return CommandExecutionHelper.execute(this, startRecordingScreenCommand(options)); @@ -40,8 +38,6 @@ default String startRecordingScreen( /** * Start asynchronous screen recording process with default options. - * - * @return Base-64 encoded content of the recorded media file. */ default String startRecordingScreen() { return this.execute(START_RECORDING_SCREEN).getValue().toString(); diff --git a/src/main/java/io/appium/java_client/windows/WindowsDriver.java b/src/main/java/io/appium/java_client/windows/WindowsDriver.java index af559f12a..3c8126ac1 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsDriver.java +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -21,6 +21,7 @@ import io.appium.java_client.AppiumDriver; import io.appium.java_client.FindsByWindowsAutomation; import io.appium.java_client.HidesKeyboardWithKeyName; +import io.appium.java_client.screenrecording.CanRecordScreen; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.openqa.selenium.Capabilities; @@ -32,7 +33,7 @@ public class WindowsDriver extends AppiumDriver implements PressesKeyCode, HidesKeyboardWithKeyName, - FindsByWindowsAutomation { + FindsByWindowsAutomation, CanRecordScreen { public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, updateDefaultPlatformName(capabilities, WINDOWS)); diff --git a/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java new file mode 100644 index 000000000..ff90a08f2 --- /dev/null +++ b/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java @@ -0,0 +1,151 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.windows; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; + +import java.time.Duration; +import java.util.Map; + +import static java.util.Optional.ofNullable; + +public class WindowsStartScreenRecordingOptions + extends BaseStartScreenRecordingOptions { + private Integer fps; + private String videoFilter; + private String preset; + private Boolean captureCursor; + private Boolean captureClicks; + private String audioInput; + + public static WindowsStartScreenRecordingOptions startScreenRecordingOptions() { + return new WindowsStartScreenRecordingOptions(); + } + + /** + * The count of frames per second in the resulting video. + * Increasing fps value also increases the size of the resulting + * video file and the CPU usage. + * + * @param fps The actual frames per second value. + * The default value is 15. + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions withFps(int fps) { + this.fps = fps; + return this; + } + + /** + * Whether to capture the mouse cursor while recording + * the screen. Disabled by default. + * + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions enableCursorCapture() { + this.captureCursor = true; + return this; + } + + /** + * Whether to capture the click gestures while recording + * the screen. Disabled by default. + * + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions enableClicksCapture() { + this.captureClicks = true; + return this; + } + + /** + * If provided then the given audio input will be used to record the computer audio + * along with the desktop video. The list of available devices could be retrieved using + * `ffmpeg -list_devices true -f dshow -i dummy` command. + * + * @param audioInput One of valid audio input names listed by ffmpeg + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions withAudioInput(String audioInput) { + this.audioInput = audioInput; + return this; + } + + /** + * The video filter spec to apply for ffmpeg. + * See https://trac.ffmpeg.org/wiki/FilteringGuide for more details on the possible values. + * Example: Set it to `scale=ifnot(gte(iw\,1024)\,iw\,1024):-2` in order to limit the video width + * to 1024px. The height will be adjusted automatically to match the actual screen aspect ratio. + * + * @param videoFilter Valid ffmpeg video filter spec string. + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions withVideoFilter(String videoFilter) { + this.videoFilter = videoFilter; + return this; + } + + /** + * A preset is a collection of options that will provide a certain encoding speed to compression ratio. + * A slower preset will provide better compression (compression is quality per filesize). + * This means that, for example, if you target a certain file size or constant bit rate, you will + * achieve better quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 + * for more details. + * + * @param preset One of the supported encoding presets. Possible values are: + * - ultrafast + * - superfast + * - veryfast (default) + * - faster + * - fast + * - medium + * - slow + * - slower + * - veryslow + * @return self instance for chaining. + */ + public WindowsStartScreenRecordingOptions withPreset(String preset) { + this.preset = preset; + return this; + } + + /** + * The maximum recording time. The default value is 600 seconds (10 minutes). + * The minimum time resolution unit is one second. + * + * @param timeLimit The actual time limit of the recorded video. + * @return self instance for chaining. + */ + @Override + public WindowsStartScreenRecordingOptions withTimeLimit(Duration timeLimit) { + return super.withTimeLimit(timeLimit); + } + + @Override + public Map build() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(super.build()); + ofNullable(fps).map(x -> builder.put("fps", x)); + ofNullable(preset).map(x -> builder.put("preset", x)); + ofNullable(videoFilter).map(x -> builder.put("videoFilter", x)); + ofNullable(captureClicks).map(x -> builder.put("captureClicks", x)); + ofNullable(captureCursor).map(x -> builder.put("captureCursor", x)); + ofNullable(audioInput).map(x -> builder.put("audioInput", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/windows/WindowsStopScreenRecordingOptions.java b/src/main/java/io/appium/java_client/windows/WindowsStopScreenRecordingOptions.java new file mode 100644 index 000000000..206e8c644 --- /dev/null +++ b/src/main/java/io/appium/java_client/windows/WindowsStopScreenRecordingOptions.java @@ -0,0 +1,28 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.windows; + +import io.appium.java_client.screenrecording.BaseStopScreenRecordingOptions; + +public class WindowsStopScreenRecordingOptions extends + BaseStopScreenRecordingOptions { + + public static WindowsStopScreenRecordingOptions stopScreenRecordingOptions() { + return new WindowsStopScreenRecordingOptions(); + } + +} diff --git a/src/test/java/io/appium/java_client/appium/element/generation/BaseElementGenerationTest.java b/src/test/java/io/appium/java_client/appium/element/generation/BaseElementGenerationTest.java index f6d8db2bd..83271f229 100644 --- a/src/test/java/io/appium/java_client/appium/element/generation/BaseElementGenerationTest.java +++ b/src/test/java/io/appium/java_client/appium/element/generation/BaseElementGenerationTest.java @@ -7,14 +7,16 @@ import org.junit.After; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; import java.util.function.BiPredicate; import java.util.function.Supplier; public class BaseElementGenerationTest { protected AppiumDriver driver; - private AppiumDriverLocalService service; + protected AppiumDriverLocalService service; protected final BiPredicate> commonPredicate = (by, aClass) -> { WebElement element = driver.findElement(by); @@ -37,12 +39,4 @@ public void tearDown() { } } - protected boolean check(Supplier capabilitiesSupplier, - BiPredicate> filter, - By by, Class clazz) { - service = AppiumDriverLocalService.buildDefaultService(); - driver = new AppiumDriver<>(service, capabilitiesSupplier.get()); - return filter.test(by, clazz); - } - } diff --git a/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java b/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java index 992cb43cf..9e139e2cd 100644 --- a/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java +++ b/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java @@ -5,16 +5,22 @@ import static org.openqa.selenium.By.name; import static org.openqa.selenium.By.tagName; +import io.appium.java_client.AppiumDriver; import io.appium.java_client.android.AndroidElement; import io.appium.java_client.appium.element.generation.BaseElementGenerationTest; import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileBrowserType; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; +import io.appium.java_client.service.local.AppiumDriverLocalService; import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import java.io.File; +import java.util.function.BiPredicate; import java.util.function.Supplier; public class AndroidElementGeneratingTest extends BaseElementGenerationTest { @@ -36,8 +42,8 @@ public void whenAndroidNativeAppIsLaunched() { clientCapabilities.setCapability(MobileCapabilityType.FULL_RESET, true); clientCapabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 60); return clientCapabilities; - }, commonPredicate, AndroidUIAutomator("new UiSelector().clickable(true)"), - AndroidElement.class)); + }, commonPredicate, AndroidUIAutomator("new UiSelector().clickable(true)") + )); } @Test @@ -50,7 +56,7 @@ public void whenAndroidHybridAppIsLaunched() { }, (by, aClass) -> { driver.context("WEBVIEW_io.appium.android.apis"); return commonPredicate.test(by, aClass); - }, tagName("a"), AndroidElement.class)); + }, tagName("a"))); } @Test @@ -64,8 +70,14 @@ public void whenAndroidBrowserIsLaunched() { }, (by, aClass) -> { driver.get("https://www.google.com"); return commonPredicate.test(by, aClass); - }, name("q"), AndroidElement.class)); + }, name("q"))); } - + private boolean check(Supplier capabilitiesSupplier, + BiPredicate> filter, + By by) { + service = AppiumDriverLocalService.buildDefaultService(); + driver = new AppiumDriver<>(service, capabilitiesSupplier.get()); + return filter.test(by, AndroidElement.class); + } } diff --git a/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java b/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java index 0b7572be3..19f1fd32b 100644 --- a/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java +++ b/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java @@ -6,6 +6,7 @@ import static org.openqa.selenium.By.name; import static org.openqa.selenium.By.partialLinkText; +import io.appium.java_client.AppiumDriver; import io.appium.java_client.appium.element.generation.BaseElementGenerationTest; import io.appium.java_client.ios.BaseIOSTest; import io.appium.java_client.ios.IOSElement; @@ -13,14 +14,19 @@ import io.appium.java_client.remote.MobileBrowserType; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; +import io.appium.java_client.service.local.AppiumDriverLocalService; import org.junit.Ignore; import org.junit.Test; +import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.SessionNotCreatedException; +import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import java.io.File; +import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Supplier; @@ -73,8 +79,8 @@ public void whenIOSNativeAppIsLaunched() { Capabilities caps = commonAppCapabilitiesSupplier.get(); return caps.merge(appFileSupplierFunction.apply(testApp).get()); }, commonPredicate, - AccessibilityId("Answer"), - IOSElement.class)); + AccessibilityId("Answer") + )); } @Ignore @@ -102,7 +108,7 @@ public void whenIOSHybridAppIsLaunched() { } }); return commonPredicate.test(by, aClass); - }, partialLinkText("login"), IOSElement.class)); + }, partialLinkText("login"))); } @Test @@ -113,7 +119,7 @@ public void whenIOSBrowserIsLaunched() { }, (by, aClass) -> { driver.get("https://www.google.com"); return commonPredicate.test(by, aClass); - }, name("q"), IOSElement.class)); + }, name("q"))); } @Test @@ -123,7 +129,7 @@ public void whenIOSNativeAppIsLaunched2() { serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, BaseIOSTest.PLATFORM_VERSION); serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.IOS); return serverCapabilities.merge(appFileSupplierFunction.apply(testApp).get()); - }, commonPredicate, id("IntegerA"), IOSElement.class)); + }, commonPredicate, id("IntegerA"))); } @Test @@ -136,6 +142,22 @@ public void whenIOSBrowserIsLaunched2() { }, (by, aClass) -> { driver.get("https://www.google.com"); return commonPredicate.test(by, aClass); - }, name("q"), IOSElement.class)); + }, name("q"))); + } + + private boolean check(Supplier capabilitiesSupplier, + BiPredicate> filter, + By by) { + service = AppiumDriverLocalService.buildDefaultService(); + Capabilities caps = capabilitiesSupplier.get(); + DesiredCapabilities fixedCaps = new DesiredCapabilities(caps); + fixedCaps.setCapability("commandTimeouts", "120000"); + try { + driver = new AppiumDriver<>(service, fixedCaps); + } catch (SessionNotCreatedException e) { + fixedCaps.setCapability("useNewWDA", true); + driver = new AppiumDriver<>(service, fixedCaps); + } + return filter.test(by, IOSElement.class); } } diff --git a/src/test/java/io/appium/java_client/ios/AppIOSTest.java b/src/test/java/io/appium/java_client/ios/AppIOSTest.java index b4b47247c..e41e325a6 100644 --- a/src/test/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/AppIOSTest.java @@ -5,6 +5,7 @@ import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.BeforeClass; +import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.remote.DesiredCapabilities; import java.io.File; @@ -30,7 +31,13 @@ public static void beforeClass() throws Exception { capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); + capabilities.setCapability("commandTimeouts", "120000"); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); + try { + driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); + } catch (SessionNotCreatedException e) { + capabilities.setCapability("useNewWDA", true); + driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); + } } } diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index ce0b115a5..647718603 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -20,12 +20,15 @@ import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.BeforeClass; +import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.remote.DesiredCapabilities; import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.function.Supplier; public class BaseIOSWebViewTest extends BaseIOSTest { private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(1); @@ -41,13 +44,28 @@ public static void beforeClass() throws IOException { File appDir = new File("src/test/java/io/appium/java_client"); File app = new File(appDir, "vodqa.zip"); - DesiredCapabilities capabilities = new DesiredCapabilities(); + final DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME); + capabilities.setCapability("commandTimeouts", "120000"); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); + Supplier> createDriver = () -> { + try { + return new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }; + try { + driver = createDriver.get(); + } catch (SessionNotCreatedException e) { + // Sometimes WDA session creation freezes unexpectedly on CI: + // https://dev.azure.com/srinivasansekar/java-client/_build/results?buildId=356&view=ms.vss-test-web.build-test-results-tab + capabilities.setCapability("useNewWDA", true); + driver = createDriver.get(); + } } protected void findAndSwitchToWebView() throws InterruptedException { From 820dbb76eb27af7083bc22b5b15750fa131e1654 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 17 Mar 2020 16:25:00 +0100 Subject: [PATCH 009/630] fix: Set appropriate fluent wait timeouts (#1316) --- azure-pipelines.yml | 2 +- .../appium/java_client/ios/IOSAlertTest.java | 52 +++++++++++++++---- .../appium/java_client/ios/IOSTouchTest.java | 2 +- .../pagefactory_tests/XCUITModeTest.java | 2 +- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea502ec76..39ffdeb6b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,7 @@ variables: jobs: - job: E2E_Tests - timeoutInMinutes: 120 + timeoutInMinutes: 60 steps: - task: NodeTool@0 inputs: diff --git a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java index 06df51bd7..cb810e4d0 100644 --- a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java @@ -22,9 +22,12 @@ import io.appium.java_client.MobileBy; import org.apache.commons.lang3.StringUtils; +import org.junit.After; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.support.ui.WebDriverWait; import java.util.function.Supplier; @@ -32,32 +35,61 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class IOSAlertTest extends AppIOSTest { - private WebDriverWait waiting = new WebDriverWait(driver, 10000); + private static final long ALERT_TIMEOUT_SECONDS = 5; + private static final int CLICK_RETRIES = 2; + + private WebDriverWait waiting = new WebDriverWait(driver, ALERT_TIMEOUT_SECONDS); private static final String iOSAutomationText = "show alert"; - @Test public void acceptAlertTest() { + private void ensureAlertPresence() { + int retry = 0; + // CI might not be performant enough, so we need to retry + while (true) { + try { + driver.findElement(MobileBy.AccessibilityId(iOSAutomationText)).click(); + waiting.until(alertIsPresent()); + return; + } catch (TimeoutException e) { + retry++; + if (retry >= CLICK_RETRIES) { + throw e; + } + } + } + } + + @After + public void afterEach() { + try { + driver.switchTo().alert().accept(); + } catch (WebDriverException e) { + // ignore + } + } + + @Test + public void acceptAlertTest() { Supplier acceptAlert = () -> { - driver.findElement(MobileBy.AccessibilityId(iOSAutomationText)).click(); - waiting.until(alertIsPresent()); + ensureAlertPresence(); driver.switchTo().alert().accept(); return true; }; assertTrue(acceptAlert.get()); } - @Test public void dismissAlertTest() { + @Test + public void dismissAlertTest() { Supplier dismissAlert = () -> { - driver.findElement(MobileBy.AccessibilityId(iOSAutomationText)).click(); - waiting.until(alertIsPresent()); + ensureAlertPresence(); driver.switchTo().alert().dismiss(); return true; }; assertTrue(dismissAlert.get()); } - @Test public void getAlertTextTest() { - driver.findElement(MobileBy.AccessibilityId(iOSAutomationText)).click(); - waiting.until(alertIsPresent()); + @Test + public void getAlertTextTest() { + ensureAlertPresence(); assertFalse(StringUtils.isBlank(driver.switchTo().alert().getText())); } } diff --git a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java index ea2f83d9d..247e167d1 100644 --- a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java @@ -82,7 +82,7 @@ public void touchWithPressureTest() { new MultiTouchAction(driver).add(tap1).add(tap2).perform(); - WebDriverWait waiting = new WebDriverWait(driver, 10000); + WebDriverWait waiting = new WebDriverWait(driver, 10); assertNotNull(waiting.until(alertIsPresent())); driver.switchTo().alert().accept(); } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java index c54bf9129..ee2324256 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java @@ -47,7 +47,7 @@ public class XCUITModeTest extends AppIOSTest { private boolean populated = false; - private WebDriverWait waiting = new WebDriverWait(driver, 10000); + private WebDriverWait waiting = new WebDriverWait(driver, 10); @HowToUseLocators(iOSXCUITAutomation = ALL_POSSIBLE) @iOSXCUITFindBy(iOSNsPredicate = "label contains 'Compute'") From c7cf1b1b10133a00c978d0e7f8dc035a94bf5d75 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 19 Mar 2020 18:05:31 +0100 Subject: [PATCH 010/630] feat: Make settings available for all AppiumDriver instances (#1318) * feat: Make settings available for all AppiumDriver instances * fix indent * Simplify the call * tune the tests --- .../io/appium/java_client/AppiumDriver.java | 2 +- .../io/appium/java_client/HasSettings.java | 20 +++++----- .../android/HasAndroidSettings.java | 39 +++++++------------ .../java_client/ios/HasIOSSettings.java | 27 +++++-------- .../appium/java_client/ios/IOSAlertTest.java | 8 +++- 5 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 398779025..049171912 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -68,7 +68,7 @@ @SuppressWarnings("unchecked") public class AppiumDriver extends DefaultGenericMobileDriver implements ComparesImages, FindsByImage, FindsByCustom, - ExecutesDriverScript, LogsEvents { + ExecutesDriverScript, LogsEvents, HasSettings { private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters diff --git a/src/main/java/io/appium/java_client/HasSettings.java b/src/main/java/io/appium/java_client/HasSettings.java index 2db441b57..2d6045846 100644 --- a/src/main/java/io/appium/java_client/HasSettings.java +++ b/src/main/java/io/appium/java_client/HasSettings.java @@ -19,13 +19,10 @@ import static io.appium.java_client.MobileCommand.getSettingsCommand; import static io.appium.java_client.MobileCommand.setSettingsCommand; -import com.google.common.collect.ImmutableMap; - import org.openqa.selenium.remote.Response; import java.util.Map; - public interface HasSettings extends ExecutesMethod { /** @@ -34,10 +31,11 @@ public interface HasSettings extends ExecutesMethod { * the method for the specific setting you want to change. * * @param setting Setting you wish to set. - * @param value value of the setting. + * @param value Value of the setting. + * @return Self instance for chaining. */ - default void setSetting(Setting setting, Object value) { - CommandExecutionHelper.execute(this, setSettingsCommand(setting.toString(), value)); + default HasSettings setSetting(Setting setting, Object value) { + return setSetting(setting.toString(), value); } /** @@ -45,11 +43,13 @@ default void setSetting(Setting setting, Object value) { * convenience function, rather than use this function directly. Try finding * the method for the specific setting you want to change. * - * @param setting Setting you wish to set. - * @param value value of the setting. + * @param settingName Setting name you wish to set. + * @param value Value of the setting. + * @return Self instance for chaining. */ - default void setSetting(String setting, Object value) { - CommandExecutionHelper.execute(this, setSettingsCommand(setting, value)); + default HasSettings setSetting(String settingName, Object value) { + CommandExecutionHelper.execute(this, setSettingsCommand(settingName, value)); + return this; } /** diff --git a/src/main/java/io/appium/java_client/android/HasAndroidSettings.java b/src/main/java/io/appium/java_client/android/HasAndroidSettings.java index 977377b42..0c0e44f34 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidSettings.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidSettings.java @@ -33,8 +33,7 @@ interface HasAndroidSettings extends HasSettings { * @return self instance for chaining */ default HasAndroidSettings ignoreUnimportantViews(Boolean compress) { - setSetting(Setting.IGNORE_UNIMPORTANT_VIEWS, compress); - return this; + return (HasAndroidSettings) setSetting(Setting.IGNORE_UNIMPORTANT_VIEWS, compress); } /** @@ -45,8 +44,7 @@ default HasAndroidSettings ignoreUnimportantViews(Boolean compress) { * @return self instance for chaining */ default HasAndroidSettings configuratorSetWaitForIdleTimeout(Duration timeout) { - setSetting(Setting.WAIT_FOR_IDLE_TIMEOUT, timeout.toMillis()); - return this; + return (HasAndroidSettings) setSetting(Setting.WAIT_FOR_IDLE_TIMEOUT, timeout.toMillis()); } /** @@ -57,8 +55,7 @@ default HasAndroidSettings configuratorSetWaitForIdleTimeout(Duration timeout) { * @return self instance for chaining */ default HasAndroidSettings configuratorSetWaitForSelectorTimeout(Duration timeout) { - setSetting(Setting.WAIT_FOR_SELECTOR_TIMEOUT, timeout.toMillis()); - return this; + return (HasAndroidSettings) setSetting(Setting.WAIT_FOR_SELECTOR_TIMEOUT, timeout.toMillis()); } /** @@ -69,8 +66,7 @@ default HasAndroidSettings configuratorSetWaitForSelectorTimeout(Duration timeou * @return self instance for chaining */ default HasAndroidSettings configuratorSetScrollAcknowledgmentTimeout(Duration timeout) { - setSetting(Setting.WAIT_SCROLL_ACKNOWLEDGMENT_TIMEOUT, timeout.toMillis()); - return this; + return (HasAndroidSettings) setSetting(Setting.WAIT_SCROLL_ACKNOWLEDGMENT_TIMEOUT, timeout.toMillis()); } /** @@ -81,8 +77,7 @@ default HasAndroidSettings configuratorSetScrollAcknowledgmentTimeout(Duration t * @return self instance for chaining */ default HasAndroidSettings configuratorSetKeyInjectionDelay(Duration delay) { - setSetting(Setting.KEY_INJECTION_DELAY, delay.toMillis()); - return this; + return (HasAndroidSettings) setSetting(Setting.KEY_INJECTION_DELAY, delay.toMillis()); } /** @@ -93,8 +88,7 @@ default HasAndroidSettings configuratorSetKeyInjectionDelay(Duration delay) { * @return self instance for chaining */ default HasAndroidSettings configuratorSetActionAcknowledgmentTimeout(Duration timeout) { - setSetting(Setting.WAIT_ACTION_ACKNOWLEDGMENT_TIMEOUT, timeout.toMillis()); - return this; + return (HasAndroidSettings) setSetting(Setting.WAIT_ACTION_ACKNOWLEDGMENT_TIMEOUT, timeout.toMillis()); } /** @@ -111,8 +105,7 @@ default HasAndroidSettings configuratorSetActionAcknowledgmentTimeout(Duration t * @return self instance for chaining */ default HasAndroidSettings normalizeTagNames(boolean enabled) { - setSetting(Setting.NORMALIZE_TAG_NAMES, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.NORMALIZE_TAG_NAMES, enabled); } /** @@ -124,8 +117,7 @@ default HasAndroidSettings normalizeTagNames(boolean enabled) { * @return self instance for chaining */ default HasAndroidSettings setShouldUseCompactResponses(boolean enabled) { - setSetting(Setting.SHOULD_USE_COMPACT_RESPONSES, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.SHOULD_USE_COMPACT_RESPONSES, enabled); } /** @@ -136,8 +128,7 @@ default HasAndroidSettings setShouldUseCompactResponses(boolean enabled) { * @return self instance for chaining */ default HasAndroidSettings setElementResponseAttributes(String attrNames) { - setSetting(Setting.ELEMENT_RESPONSE_ATTRIBUTES, attrNames); - return this; + return (HasAndroidSettings) setSetting(Setting.ELEMENT_RESPONSE_ATTRIBUTES, attrNames); } /** @@ -148,8 +139,7 @@ default HasAndroidSettings setElementResponseAttributes(String attrNames) { * @return self instance for chaining */ default HasAndroidSettings allowInvisibleElements(boolean enabled) { - setSetting(Setting.ALLOW_INVISIBLE_ELEMENTS, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.ALLOW_INVISIBLE_ELEMENTS, enabled); } /** @@ -162,8 +152,7 @@ default HasAndroidSettings allowInvisibleElements(boolean enabled) { * @return self instance for chaining */ default HasAndroidSettings enableNotificationListener(boolean enabled) { - setSetting(Setting.ENABLE_NOTIFICATION_LISTENER, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.ENABLE_NOTIFICATION_LISTENER, enabled); } /** @@ -174,8 +163,7 @@ default HasAndroidSettings enableNotificationListener(boolean enabled) { * @return self instance for chaining */ default HasAndroidSettings shutdownOnPowerDisconnect(boolean enabled) { - setSetting(Setting.SHUTDOWN_ON_POWER_DISCONNECT, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.SHUTDOWN_ON_POWER_DISCONNECT, enabled); } /** @@ -188,7 +176,6 @@ default HasAndroidSettings shutdownOnPowerDisconnect(boolean enabled) { * @return self instance for chaining */ default HasAndroidSettings setTrackScrollEvents(boolean enabled) { - setSetting(Setting.TRACK_SCROLL_EVENTS, enabled); - return this; + return (HasAndroidSettings) setSetting(Setting.TRACK_SCROLL_EVENTS, enabled); } } diff --git a/src/main/java/io/appium/java_client/ios/HasIOSSettings.java b/src/main/java/io/appium/java_client/ios/HasIOSSettings.java index 1eda46aa4..83a994a43 100644 --- a/src/main/java/io/appium/java_client/ios/HasIOSSettings.java +++ b/src/main/java/io/appium/java_client/ios/HasIOSSettings.java @@ -28,8 +28,7 @@ interface HasIOSSettings extends HasSettings { * @return self instance for chaining */ default HasIOSSettings nativeWebTap(Boolean enabled) { - setSetting(Setting.NATIVE_WEB_TAP, enabled); - return this; + return (HasIOSSettings) setSetting(Setting.NATIVE_WEB_TAP, enabled); } /** @@ -41,8 +40,7 @@ default HasIOSSettings nativeWebTap(Boolean enabled) { * @return self instance for chaining */ default HasIOSSettings setShouldUseCompactResponses(boolean enabled) { - setSetting(Setting.SHOULD_USE_COMPACT_RESPONSES, enabled); - return this; + return (HasIOSSettings) setSetting(Setting.SHOULD_USE_COMPACT_RESPONSES, enabled); } /** @@ -53,8 +51,7 @@ default HasIOSSettings setShouldUseCompactResponses(boolean enabled) { * @return self instance for chaining */ default HasIOSSettings setElementResponseAttributes(String attrNames) { - setSetting(Setting.ELEMENT_RESPONSE_ATTRIBUTES, attrNames); - return this; + return (HasIOSSettings) setSetting(Setting.ELEMENT_RESPONSE_ATTRIBUTES, attrNames); } /** @@ -66,8 +63,7 @@ default HasIOSSettings setElementResponseAttributes(String attrNames) { * @return self instance for chaining */ default HasIOSSettings setMjpegServerScreenshotQuality(int quality) { - setSetting(Setting.MJPEG_SERVER_SCREENSHOT_QUALITY, quality); - return this; + return (HasIOSSettings) setSetting(Setting.MJPEG_SERVER_SCREENSHOT_QUALITY, quality); } /** @@ -79,8 +75,7 @@ default HasIOSSettings setMjpegServerScreenshotQuality(int quality) { * @return self instance for chaining */ default HasIOSSettings setMjpegServerFramerate(int framerate) { - setSetting(Setting.MJPEG_SERVER_FRAMERATE, framerate); - return this; + return (HasIOSSettings) setSetting(Setting.MJPEG_SERVER_FRAMERATE, framerate); } /** @@ -92,8 +87,7 @@ default HasIOSSettings setMjpegServerFramerate(int framerate) { * @return self instance for chaining */ default HasIOSSettings setScreenshotQuality(int quality) { - setSetting(Setting.SCREENSHOT_QUALITY, quality); - return this; + return (HasIOSSettings) setSetting(Setting.SCREENSHOT_QUALITY, quality); } /** @@ -104,8 +98,7 @@ default HasIOSSettings setScreenshotQuality(int quality) { * @return self instance for chaining */ default HasIOSSettings setMjpegScalingFactor(int scale) { - setSetting(Setting.MJPEG_SCALING_FACTOR, scale); - return this; + return (HasIOSSettings) setSetting(Setting.MJPEG_SCALING_FACTOR, scale); } /** @@ -115,8 +108,7 @@ default HasIOSSettings setMjpegScalingFactor(int scale) { * @return self instance for chaining */ default HasIOSSettings setKeyboardAutocorrection(boolean enabled) { - setSetting(Setting.KEYBOARD_AUTOCORRECTION, enabled); - return this; + return (HasIOSSettings) setSetting(Setting.KEYBOARD_AUTOCORRECTION, enabled); } /** @@ -126,7 +118,6 @@ default HasIOSSettings setKeyboardAutocorrection(boolean enabled) { * @return self instance for chaining */ default HasIOSSettings setKeyboardPrediction(boolean enabled) { - setSetting(Setting.KEYBOARD_PREDICTION, enabled); - return this; + return (HasIOSSettings) setSetting(Setting.KEYBOARD_PREDICTION, enabled); } } diff --git a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java index cb810e4d0..e5a5033bb 100644 --- a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java @@ -38,7 +38,7 @@ public class IOSAlertTest extends AppIOSTest { private static final long ALERT_TIMEOUT_SECONDS = 5; private static final int CLICK_RETRIES = 2; - private WebDriverWait waiting = new WebDriverWait(driver, ALERT_TIMEOUT_SECONDS); + private final WebDriverWait waiter = new WebDriverWait(driver, ALERT_TIMEOUT_SECONDS); private static final String iOSAutomationText = "show alert"; private void ensureAlertPresence() { @@ -47,7 +47,11 @@ private void ensureAlertPresence() { while (true) { try { driver.findElement(MobileBy.AccessibilityId(iOSAutomationText)).click(); - waiting.until(alertIsPresent()); + } catch (WebDriverException e) { + // ignore + } + try { + waiter.until(alertIsPresent()); return; } catch (TimeoutException e) { retry++; From 5a899f948c28377272afce42ea9bafcf276ef507 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Sun, 29 Mar 2020 03:55:36 -0400 Subject: [PATCH 011/630] docs: FindBy doc update (#1311) --- docs/Advanced-By.md | 160 ++++++++++++++++++ .../java/io/appium/java_client/MobileBy.java | 5 +- .../java_client/pagefactory/AndroidBy.java | 5 +- .../pagefactory/AndroidFindBy.java | 5 +- .../java_client/pagefactory/iOSXCUITBy.java | 2 +- .../pagefactory/iOSXCUITFindBy.java | 2 +- 6 files changed, 168 insertions(+), 11 deletions(-) create mode 100644 docs/Advanced-By.md diff --git a/docs/Advanced-By.md b/docs/Advanced-By.md new file mode 100644 index 000000000..4226ed9f6 --- /dev/null +++ b/docs/Advanced-By.md @@ -0,0 +1,160 @@ +# Standard Selectors + +## AndroidFindBy / iOSXCUITFindBy / WindowsFindBy + +# Advanced Selectors + +## iOS's String Predicates + +You can specify a [predicate](https://developer.apple.com/documentation/foundation/nspredicate) +in your Class Chain to limit the number of matched items. There's +[a detailed guide to that](https://appium.io/docs/en/writing-running-appium/ios/ios-predicate/index.html) +on the Appium Docs website with some Appium-specific considerations. + +## iOS's Class Chain Queries + +Our XCUiTest integration has full support for the 'Class Chain' concept. This +can do much of what XPath does...but faster. Note that many Class Chains leverage +String Predicates too. + +### String Predicates in Class Chains + +There's a special array-style syntax for defining predicates in a Class Chain: + +``` +// Start with [ +// Followed by ` +// Followed by some text +// Followed by ` +// End with ] +[`label != 'a'`] +[`isWDVisible == 1`] +[`value < 0`] +``` + +#### Searching Descendents + +If you replace the backticks (`` ` ``) around a predicate with dollar signs (`$`), +then the Class Chain will match against the children, grandchildren, and other +descendants of each element. + +``` +// Find a cell element with the label 'here' +XCUIElementTypeCell[`label == 'here'`] +// Find a cell element which contains SOMETHING ELSE that has the label 'here' +XCUIElementTypeCell[$label == 'here'$] +``` + +#### Handling Quote Marks + +Most of the time, you can treat pairs of single quotes or double quotes +interchangably. If you're searching with a string that contains quote marks, +though, you [need to be careful](https://stackoverflow.com/q/14116217). + +```c +// Make sure to escape each quote mark that matches your delimiter +"text with \"some\" 'quote' marks" +// To NSPredicate, the line above and the line below are equivalent +'text with "some" \'quote\' marks' +``` +```java +// When defining a iOSXCUITFindBy annotation, you'll be constrained by the +// Java string-quoting rules too. +@iOSXCUITFindBy(iOSClassChain = "**/SomeElement[`'text with \"some\" \\\'quote\\\' marks'`]") +``` + +### External References + +Refer to [the official WebDriverAgent query docs](https://github.com/facebookarchive/WebDriverAgent/wiki/Class-Chain-Queries-Construction-Rules) +to learn more about the general concept. + +### Sample usage: + +```java +// Selector for image elements +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") + +// Selector for the first image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[1]") +// Selector for the second image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[2]") +// Selector for the last image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[-1]") +// Selector for the penultimate image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[-2]") + +// Selector for every cell with the name 'Foo' (single quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == 'Foo'`]") +// Selector for every cell with the name "Foo" (double quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == \"Foo\"`]") + +// Selector for every cell with a name that starts with 'Foo' (single quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH 'Foo'`]") +// Selector for every cell with a name that starts with "Foo" (double quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH \"Foo\"`]") + +// Selector for every cell with a name that starts with "it's not" +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH \"it's not\"`]") + +// Selector that'll match every top-level element on screen +@iOSXCUITFindBy(iOSClassChain = "*") +// Selector that'll match every leaf element on screen (watch out: this can be SLOW) +@iOSXCUITFindBy(iOSClassChain = "**/*") + +// You can place an index after a predicate: the following finds the last image element with name 'Foo' +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == 'Foo'`][-1]") +``` + +## Android's uiAutomator String + +Available when using [AndroidBy](AndroidBy) and [AndroidFindBy](AndroidFindBy) with +[appium-uiautomator2-server](https://github.com/appium/appium-uiautomator2-server). This +string will be used by the server to construct a UiSelector or UiScrollable object. + +### External References + +For an overview of what the backend is capable of, please check out the + +* [Main UI Automator Guide](https://developer.android.com/training/testing/ui-automator) +* [UiScrollable API docs](https://developer.android.com/reference/androidx/test/uiautomator/UiScrollable) +and +* [UiSelector API docs](https://developer.android.com/reference/androidx/test/uiautomator/UiSelector) + +### Sample Strings + +Here are some ways you could configure a UiSelector in your project: + +```java +// Create a selector that looks for the text "Hello World": +@AndroidFindBy(uiAutomator = "new UiSelector().text(\"Hello World\")") + +// Create a selector that tries to find an ImageView: +@AndroidFindBy(uiAutomator = "new UiSelector().className(\"android.widget.ImageView\")") + +// Create a selector that matches resource ids against a regular expression: +private static final String looksLikeAPage = "page_number_\d*"; +@AndroidFindBy(uiAutomator = "new UiSelector().resourceIdMatches(\"" + looksLikeAPage + "\")") + +// The agent also supports some abbreviated forms - all 3 of the below +// strings are equivalent. +@AndroidFindBy(uiAutomator = "new UiSelector().className(\"android.widget.EditText\")") +@AndroidFindBy(uiAutomator = "UiSelector().className(\"android.widget.EditText\")") +@AndroidFindBy(uiAutomator = ".className(\"android.widget.EditText\")") + +// You can connect up conditions to search for multiple things at once +@AndroidFindBy(uiAutomator = ".resourceId(\"android:id/list\").classNameMatches(\"\.*RecyclerView\").index(3)") +``` + +..and here are some that create UiScrollable objects: + +```java +private static final String ourImageSelector = ".className(\"android.widget.ImageView\")"; +private static final String ourListSelector = ".className(\"android.widget.ListView\")"; + +// Create a scrollable associated with a list (by itself, this doesn't do anything useful...) +@AndroidFindBy(uiAutomator = "new UiScrollable(" + ourListSelector + ")") + +// Create a scrollable that scrolls forward along a list until it finds an ImageView: +@AndroidFindBy(uiAutomator = "new UiScrollable(" + ourListSelector + ").scrollIntoView(" + ourImageSelector + ")") + +``` diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index c76542da9..b8fd3d5db 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -62,8 +62,7 @@ protected MobileBy(MobileSelector selector, String locatorString) { } /** - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * Refer to https://developer.android.com/training/testing/ui-automator * @param uiautomatorText is Android UIAutomator string * @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidUIAutomator} */ @@ -87,7 +86,7 @@ public static By AccessibilityId(final String accessibilityId) { /** * This locator strategy is available in XCUITest Driver mode. * @param iOSClassChainString is a valid class chain locator string. - * See + * See * the documentation for more details * @return an instance of {@link io.appium.java_client.MobileBy.ByIosClassChain} */ diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java index 298956fd7..b40acdd07 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java @@ -21,9 +21,8 @@ */ public @interface AndroidBy { /** - * It is an Android UIAutomator string. - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * A String that can build an Android UiSelector or UiScrollable object. + * Refer to https://developer.android.com/training/testing/ui-automator * * @return an Android UIAutomator string */ diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java index d789f20ec..25b37c0b6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java @@ -36,9 +36,8 @@ @Repeatable(AndroidFindBySet.class) public @interface AndroidFindBy { /** - * It is an Android UIAutomator string. - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * A String that can build an Android UiSelector or UiScrollable object. + * Refer to https://developer.android.com/training/testing/ui-automator * * @return an Android UIAutomator string */ diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java index 02eb4da5f..c59a1559d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java @@ -24,7 +24,7 @@ /** * The Class Chain locator is similar to xpath, but it's faster and can only * search direct children elements. See the - * + * * documentation for more details. * * @return iOS class chain diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java index cee419e39..5194e4094 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java @@ -31,7 +31,7 @@ /** * The Class Chain locator is similar to xpath, but it's faster and can only * search direct children elements. See the - * + * * documentation for more details. * * @return iOS class chain From 50e231831c7a0d0a4bfb47a2f74242af6de8cff2 Mon Sep 17 00:00:00 2001 From: Titus Date: Sun, 29 Mar 2020 13:36:45 -0500 Subject: [PATCH 012/630] feat: add constants for mobile capability settings (#1323) --- .../java_client/remote/MobileCapabilityType.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 1ce83cf0b..1f0b0ccf2 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -134,4 +134,16 @@ public interface MobileCapabilityType extends CapabilityType { * or Safari's (on iOS) performance logging (default {@code false}). */ String ENABLE_PERFORMANCE_LOGGING = "enablePerformanceLogging"; + + + /** + * App or list of apps (as a JSON array) to install prior to running tests. Note that it will not work with + * automationName of Espresso and iOS real devices. + */ + String OTHER_APPS = "otherApps"; + + /** + * When a find operation fails, print the current page source. Defaults to false. + */ + String PRINT_PAGE_SOURCE_ON_FIND_FAILURE = "printPageSourceOnFindFailure"; } From b914237a58054593612031a568bc5fc8e71cb79f Mon Sep 17 00:00:00 2001 From: Titus Date: Mon, 30 Mar 2020 10:51:45 -0500 Subject: [PATCH 013/630] chore: update android capability types (#1326) --- .../remote/AndroidMobileCapabilityType.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java index b97592e82..9ec293fa5 100644 --- a/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java @@ -448,4 +448,45 @@ public interface AndroidMobileCapabilityType extends CapabilityType { * @since 1.9.0 */ String DISABLE_WINDOW_ANIMATION = "disableWindowAnimation"; + + /** + * Specify the Android build-tools version to be something different than the default, which is to use the most + * recent version. It is helpful to use a non-default version if your environment uses alpha/beta build tools. + * @since 1.14.0 + */ + String BUILD_TOOLS_VERSION = "buildToolsVersion"; + + /** + * By default application installation is skipped if newer or the same version of this app is already present on + * the device under test. Setting this option to {@code true} will enforce Appium to always install the current + * application build independently of the currently installed version of it. Defaults to {@code false}. + * @since 1.16.0 + */ + String ENFORCE_APP_INSTALL = "enforceAppInstall"; + + /** + * Whether or not Appium should augment its webview detection with page detection, guaranteeing that any + * webview contexts which show up in the context list have active pages. This can prevent an error if a + * context is selected where Chromedriver cannot find any pages. Defaults to {@code false}. + * @since 1.15.0 + */ + String ENSURE_WEBVIEWS_HAVE_PAGES = "ensureWebviewsHavePages"; + + /** + * To support the `ensureWebviewsHavePages` feature, it is necessary to open a TCP port for communication with + * the webview on the device under test. This capability allows overriding of the default port of {@code 9222}, + * in case multiple sessions are running simultaneously (to avoid port clash), or in case the default port + * is not appropriate for your system. + * @since 1.15.0 + */ + String WEBVIEW_DEVTOOLS_PORT = "webviewDevtoolsPort"; + + /** + * Set the maximum number of remote cached apks which are pushed to the device-under-test's + * local storage. Caching apks remotely speeds up the execution of sequential test cases, when using the + * same set of apks, by avoiding the need to be push an apk to the remote file system every time a + * reinstall is needed. Set this capability to {@code 0} to disable caching. Defaults to {@code 10}. + * @since 1.14.0 + */ + String REMOTE_APPS_CACHE_LIMIT = "remoteAppsCacheLimit"; } From 988692ffdece24e6c187f49e0c4f675987be7140 Mon Sep 17 00:00:00 2001 From: Enrique Gonzalez Date: Tue, 31 Mar 2020 01:37:34 -0700 Subject: [PATCH 014/630] fix: remove appiumVersion from MobileCapabilityType (#1325) --- .../io/appium/java_client/remote/MobileCapabilityType.java | 4 ---- 1 file changed, 4 deletions(-) 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 1f0b0ccf2..b503b3762 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -65,10 +65,6 @@ public interface MobileCapabilityType extends CapabilityType { */ String UDID = "udid"; - /** - * Sauce-specific. - */ - String APPIUM_VERSION = "appiumVersion"; /** * Language to set for iOS (XCUITest driver only) and Android. From 5d6cf9de96a354e1efa1f77df2f20d25c7f4e0d8 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 1 Apr 2020 11:22:08 +0200 Subject: [PATCH 015/630] feat: Add idempotency key to session creation requests (#1327) --- .../remote/AppiumCommandExecutor.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java index 3f094ff53..061a291f2 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -62,8 +62,11 @@ import java.net.URL; import java.util.Map; import java.util.Optional; +import java.util.UUID; public class AppiumCommandExecutor extends HttpCommandExecutor { + // https://github.com/appium/appium-base-driver/pull/400 + private static final String IDEMPOTENCY_KEY_HEADER = "X-Idempotency-Key"; private final Optional serviceOptional; @@ -150,18 +153,23 @@ protected void setResponseCodec(ResponseCodec codec) { } protected HttpClient getClient() { - //noinspection unchecked return getPrivateFieldValue("client", HttpClient.class); } + protected HttpClient withRequestsPatchedByIdempotencyKey(HttpClient httpClient) { + return (request) -> { + request.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); + return httpClient.execute(request); + }; + } + private Response createSession(Command command) throws IOException { if (getCommandCodec() != null) { throw new SessionNotCreatedException("Session already exists"); } ProtocolHandshake handshake = new ProtocolHandshake() { - @SuppressWarnings("unchecked") - public Result createSession(HttpClient client, Command command) - throws IOException { + @SuppressWarnings({"unchecked", "UnstableApiUsage"}) + public Result createSession(HttpClient client, Command command) throws IOException { Capabilities desiredCapabilities = (Capabilities) command.getParameters().get("desiredCapabilities"); Capabilities desired = desiredCapabilities == null ? new ImmutableCapabilities() : desiredCapabilities; @@ -182,8 +190,8 @@ public Result createSession(HttpClient client, Command command) .getDeclaredMethod("createSession", HttpClient.class, InputStream.class, long.class); createSessionMethod.setAccessible(true); - Optional result = (Optional) createSessionMethod - .invoke(this, client, contentStream, counter.getCount()); + Optional result = (Optional) createSessionMethod.invoke(this, + withRequestsPatchedByIdempotencyKey(client), contentStream, counter.getCount()); return result.map(result1 -> { Result toReturn = result.get(); From cc4003f8217b2dc885b8c5c72f51712cf29f9793 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 4 Apr 2020 10:42:18 +0200 Subject: [PATCH 016/630] chore: Change getDeviceTime to call the `mobile` implementation (#1332) --- .../java/io/appium/java_client/HasDeviceTime.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/HasDeviceTime.java b/src/main/java/io/appium/java_client/HasDeviceTime.java index abb03b0f0..fa9df8997 100644 --- a/src/main/java/io/appium/java_client/HasDeviceTime.java +++ b/src/main/java/io/appium/java_client/HasDeviceTime.java @@ -18,10 +18,14 @@ import static io.appium.java_client.MobileCommand.GET_DEVICE_TIME; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.Response; +import java.util.Map; + public interface HasDeviceTime extends ExecutesMethod { /** @@ -31,10 +35,15 @@ public interface HasDeviceTime extends ExecutesMethod { * https://momentjs.com/docs/ to get the full list of supported * datetime format specifiers. The default format is * `YYYY-MM-DDTHH:mm:ssZ`, which complies to ISO-8601 + * @since Appium 1.18 * @return Device time string */ default String getDeviceTime(String format) { - Response response = execute(GET_DEVICE_TIME, ImmutableMap.of("format", format)); + Map params = ImmutableMap.of( + "script", "mobile: getDeviceTime", + "args", ImmutableList.of(ImmutableMap.of("format", format)) + ); + Response response = execute(DriverCommand.EXECUTE_SCRIPT, params); return response.getValue().toString(); } From 2c2141ec985bc30ef3d69102b115abf7195ffcde Mon Sep 17 00:00:00 2001 From: titusfortner Date: Thu, 2 Apr 2020 20:52:13 -0500 Subject: [PATCH 017/630] feat: create android and ios driver option classes --- .../java_client/android/AndroidOptions.java | 32 +++ .../io/appium/java_client/ios/IOSOptions.java | 32 +++ .../remote/MobileCapabilityType.java | 2 +- .../java_client/remote/MobileOptions.java | 223 ++++++++++++++++++ 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/android/AndroidOptions.java create mode 100644 src/main/java/io/appium/java_client/ios/IOSOptions.java create mode 100644 src/main/java/io/appium/java_client/remote/MobileOptions.java diff --git a/src/main/java/io/appium/java_client/android/AndroidOptions.java b/src/main/java/io/appium/java_client/android/AndroidOptions.java new file mode 100644 index 000000000..768bf75eb --- /dev/null +++ b/src/main/java/io/appium/java_client/android/AndroidOptions.java @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android; + +import io.appium.java_client.remote.MobileOptions; +import io.appium.java_client.remote.MobilePlatform; +import org.openqa.selenium.Capabilities; + +public class AndroidOptions extends MobileOptions { + public AndroidOptions() { + setPlatformName(MobilePlatform.ANDROID); + } + + public AndroidOptions(Capabilities source) { + this(); + merge(source); + } +} diff --git a/src/main/java/io/appium/java_client/ios/IOSOptions.java b/src/main/java/io/appium/java_client/ios/IOSOptions.java new file mode 100644 index 000000000..14af6aad6 --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/IOSOptions.java @@ -0,0 +1,32 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.ios; + +import io.appium.java_client.remote.MobileOptions; +import io.appium.java_client.remote.MobilePlatform; +import org.openqa.selenium.Capabilities; + +public class IOSOptions extends MobileOptions { + public IOSOptions() { + setPlatformName(MobilePlatform.IOS); + } + + public IOSOptions(Capabilities source) { + this(); + merge(source); + } +} 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 b503b3762..1f4eb00f1 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -120,7 +120,7 @@ public interface MobileCapabilityType extends CapabilityType { String EVENT_TIMINGS = "eventTimings"; /** - * This is the flag which forces server to switch to the mobile WSONWP. + * This is the flag which forces server to switch to the mobile JSONWP. * If {@code false} then it is switched to W3C mode. */ String FORCE_MJSONWP = "forceMjsonwp"; diff --git a/src/main/java/io/appium/java_client/remote/MobileOptions.java b/src/main/java/io/appium/java_client/remote/MobileOptions.java new file mode 100644 index 000000000..3fa20f019 --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/MobileOptions.java @@ -0,0 +1,223 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.remote; + +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.ScreenOrientation; +import org.openqa.selenium.remote.CapabilityType; + +import java.net.URL; +import java.time.Duration; + +public class MobileOptions> extends MutableCapabilities { + + public MobileOptions() { + } + + public MobileOptions(Capabilities source) { + merge(source); + } + + public T setPlatformName(String platform) { + return amend(CapabilityType.PLATFORM_NAME, platform); + } + + public String getPlatformName() { + return (String) getCapability(CapabilityType.PLATFORM_NAME); + } + + public T setApp(String path) { + return amend(MobileCapabilityType.APP, path); + } + + public T setApp(URL url) { + return setApp(url.toString()); + } + + public String getApp() { + return (String) getCapability(MobileCapabilityType.APP); + } + + public T setAutomationName(String name) { + return amend(MobileCapabilityType.AUTOMATION_NAME, name); + } + + public String getAutomationName() { + return (String) getCapability(MobileCapabilityType.AUTOMATION_NAME); + } + + public T setAutoWebview() { + return setAutoWebview(true); + } + + public T setAutoWebview(boolean bool) { + return amend(MobileCapabilityType.AUTO_WEBVIEW, bool); + } + + public boolean doesAutoWebview() { + return (boolean) getCapability(MobileCapabilityType.AUTO_WEBVIEW); + } + + public T setClearSystemFiles() { + return setClearSystemFiles(true); + } + + public T setClearSystemFiles(boolean bool) { + return amend(MobileCapabilityType.CLEAR_SYSTEM_FILES, bool); + } + + public boolean doesClearSystemFiles() { + return (boolean) getCapability(MobileCapabilityType.CLEAR_SYSTEM_FILES); + } + + public T setDeviceName(String deviceName) { + return amend(MobileCapabilityType.DEVICE_NAME, deviceName); + } + + public String getDeviceName() { + return (String) getCapability(MobileCapabilityType.DEVICE_NAME); + } + + public T setEnablePerformanceLogging() { + return setEnablePerformanceLogging(true); + } + + public T setEnablePerformanceLogging(boolean bool) { + return amend(MobileCapabilityType.ENABLE_PERFORMANCE_LOGGING, bool); + } + + public boolean isEnablePerformanceLogging() { + return (boolean) getCapability(MobileCapabilityType.ENABLE_PERFORMANCE_LOGGING); + } + + public T setEventTimings() { + return setEventTimings(true); + } + + public T setEventTimings(boolean bool) { + return amend(MobileCapabilityType.EVENT_TIMINGS, bool); + } + + public boolean doesEventTimings() { + return (boolean) getCapability(MobileCapabilityType.EVENT_TIMINGS); + } + + public T setFullReset() { + return setFullReset(true); + } + + public T setFullReset(boolean bool) { + return amend(MobileCapabilityType.FULL_RESET, bool); + } + + public boolean doesFullReset() { + return (boolean) getCapability(MobileCapabilityType.FULL_RESET); + } + + public T setLanguage(String language) { + return amend(MobileCapabilityType.LANGUAGE, language); + } + + public String getLanguage() { + return (String) getCapability(MobileCapabilityType.LANGUAGE); + } + + public T setLocale(String locale) { + return amend(MobileCapabilityType.LOCALE, locale); + } + + public String getLocale() { + return (String) getCapability(MobileCapabilityType.LOCALE); + } + + public T setNewCommandTimeout(Duration duration) { + return amend(MobileCapabilityType.NEW_COMMAND_TIMEOUT, duration.getSeconds()); + } + + public Duration getNewCommandTimeout() { + Object duration = getCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT); + return Duration.ofSeconds(Long.parseLong("" + duration)); + } + + public T setNoReset() { + return setNoReset(true); + } + + public T setNoReset(boolean bool) { + return amend(MobileCapabilityType.NO_RESET, bool); + } + + public boolean doesNoReset() { + return (boolean) getCapability(MobileCapabilityType.NO_RESET); + } + + public T setOrientation(ScreenOrientation orientation) { + return amend(MobileCapabilityType.ORIENTATION, orientation); + } + + public ScreenOrientation getOrientation() { + return (ScreenOrientation) getCapability(MobileCapabilityType.ORIENTATION); + } + + public T setOtherApps(String apps) { + return amend(MobileCapabilityType.OTHER_APPS, apps); + } + + public String getOtherApps() { + return (String) getCapability(MobileCapabilityType.OTHER_APPS); + } + + public T setPlatformVersion(String version) { + return amend(MobileCapabilityType.PLATFORM_VERSION, version); + } + + public String getPlatformVersion() { + return (String) getCapability(MobileCapabilityType.PLATFORM_VERSION); + } + + public T setPrintPageSourceOnFindFailure() { + return setPrintPageSourceOnFindFailure(true); + } + + public T setPrintPageSourceOnFindFailure(boolean bool) { + return amend(MobileCapabilityType.PRINT_PAGE_SOURCE_ON_FIND_FAILURE, bool); + } + + public boolean doesPrintPageSourceOnFindFailure() { + return (boolean) getCapability(MobileCapabilityType.PRINT_PAGE_SOURCE_ON_FIND_FAILURE); + } + + public T setUdid(String id) { + return amend(MobileCapabilityType.UDID, id); + } + + public String getUdid() { + return (String) getCapability(MobileCapabilityType.UDID); + } + + @Override + public T merge(Capabilities extraCapabilities) { + super.merge(extraCapabilities); + return (T) this; + } + + protected T amend(String optionName, Object value) { + setCapability(optionName, value); + return (T) this; + } +} From 6316f4f89750a1adfc734fa2f218b7763bd2e681 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Thu, 2 Apr 2020 20:53:00 -0500 Subject: [PATCH 018/630] test: implement tests for driver options --- .../io/appium/java_client/IntentExample.apk | Bin 39857 -> 50008 bytes .../android/AndroidOptionsTest.java | 77 +++++++++++++ .../java_client/ios/IOSOptionsTest.java | 77 +++++++++++++ .../java_client/remote/MobileOptionsTest.java | 109 ++++++++++++++++++ 4 files changed, 263 insertions(+) create mode 100644 src/test/java/io/appium/java_client/android/AndroidOptionsTest.java create mode 100644 src/test/java/io/appium/java_client/ios/IOSOptionsTest.java create mode 100644 src/test/java/io/appium/java_client/remote/MobileOptionsTest.java diff --git a/src/test/java/io/appium/java_client/IntentExample.apk b/src/test/java/io/appium/java_client/IntentExample.apk index b86d5e9b7857d5f3d2eeb75ff53d46116fded082..196ea90940af607535d0cf5c57e0c22c1d3e4af2 100644 GIT binary patch delta 7085 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 delta 2439 zcmaJ@2{aUJ7alt?SreZ!CF{^IG`2~$k7#C?kdQGkD3ZP*YKTeJ5@IY9hX1$A7L!IH z+bG)%lCn*-pt6j@H}-z&d|!Qk=l}0}&U?mt7NGm77p!Z+cP7kySw*OG%*XQCFsm6iwBqG zRVG(+)}6BedL>@8VZFQDnYqM_W{$(lhMNhWoa z*KC&{sYbob*ZEc^)DsnA(Xc9hYBOZT!5J)SYx32CJ5W_9x(pkaN^CA72xz~+%+)8` zf$lXb*jyB*tJ|)$+UREI|imng3!%e8~Yw)m$`>PYUL!yFogfj(i;2m!&s ztiptNE&yPb8vyuUy#)Ylcg_z!%`NO4Aoi9C$=|pn#;{QvqOS8X2hg8&hJ_u)3sg%? z2D2E=xAnPo^sirUdcv63^+_mdgd21cz8qr@PsCl%^R!wJ?}jSjZ04=zUPc{6A5h)! z--6VEz!A4SR;a8InR^;YeAE{+C%_HSOKs%()=j%vScABLK%0uiqk+U~*+{VbmM?9j zLdlBQPsXY4mF(-Q5wpK;U2H?LZH5*bVIMjcBD1?{_t6|u-2TaYv zqGYAE!+7hg@z{U31na)2&@_U{ymyU8OP1!QJr$+>4iJe!<;GY*1lz1u< zi*riQo7V7z8=I~D5Oc86t&DJre_s$sYJrG>jT(GTth$lfd8T~o0SCP%$CSX>Ub$&U z2nO@TFN&?Emm=~n%vy4=640n!$UOLKM0k+}kF`&EUCv$SfxxM$%Y0#+f-vv?lxS5p zAU#%3(5?2O_^Y?i!LK^K4r9eEZXVSGz7I2r66#pg1N^?sXhgj?L$P>r*wdTlMnt_3 zs(27{&AZ?jSZ(!jflBXurR(UmWEU>nUyZy%SjLsvWXxQTy;^+hiZ)`F~`K z|3bE%y{XQ559i*erGr!be^N4%DL$r>-Q1DN=>|wrQi?Q@%m>UQ6M&gAO^~zR9-bZ^ zQ1U$wT(j`$whrJ6kKMWv2C2~~vf7a$bn|sl`Wp%FD?JFcu3jWmN=Ju{6~r!s@zd{S zvC0V0S9l@qQ##ssFMVuD`V7rt zZk(ZchSDlMT5*1SioE1%T?dEZUuKttHF-=DVcM5a>%nkl=g9-^deaW1GSXBqjeQEf z6hL@mTh9Y!vQ&^>-n#T>F@o)OoOwBz_+n0Nb=8iR){+C+V{VoMkXxmiJ{ShLI%q}y^d(MtFu`%c4ooBJ~b%|zhx z9rVLGS5ku5;=)d2J#s6JB#o!$U)%soT3DXb4ZsTXvq@U3&4R_%cwJ~FNjGy>`4J#)I_OTQFJa+C)yKP-z|~jY#o0`#A--ef z9Vl@QntNeMU5T^xZoFf}#rUvZD6yhb{EB;-0&RG>5wGHQQ5|zHz`bH1t9SF+tZ31e zfs9*F=gKr6M7MNF2$f==AQDL53Mpn#nqA83rq9?8r%k>la@R^&fX}fQ3298XN2}%x zCjCRh>3MIOH=mzQIhZhXaPQ%cYg>bFW-@rU_7k4?k@^{9D58If990fN)Zd~`nhzxw zKRX8QL5e2bx;r}HYqVbT;zXHxQe>NmtNi7UEZ>Fkt6J_aEiSe&-1pK|l*huV=Qz`8 z)uOiR4=ep4S`?2F@owA@oy?E4vTn}CowW^?h@Mg`xE-76=A0=b`#73SXNA?6tvEew zC?-1luO4fY?^c}W(J8u13@$bNMHOTXp7$8c%Kc+Hx^eNGPo@FilL@i8ptOVe+Y zK9xYS#(g&%u*#;0sQ9(Ps*5vs^1B_&gM4VhoabuizBV0G{#S!pB2hhbF-tdpd~kv1 zz|nNz@Hy=+*_YXry`ZrIsBvSh-^g)j{Ig`cME9v-J!$pHyLGO?V~t)N=E!S<* zrB-RhzgC8yhtJN+@WW7dNE&T2L@sWr?Oo!p^q=KxXCnQu-%@^^PiVwHtG`42j&w)j eei%>ce^+k{9vZ>NFR_y&vVD$j3nrGl(); + + @Test + public void acceptsExistingCapabilities() { + MutableCapabilities capabilities = new MutableCapabilities(); + capabilities.setCapability("deviceName", "Pixel"); + capabilities.setCapability("platformVersion", "10"); + capabilities.setCapability("newCommandTimeout", 60); + + mobileOptions = new MobileOptions<>(capabilities); + + assertEquals("Pixel", mobileOptions.getDeviceName()); + assertEquals("10", mobileOptions.getPlatformVersion()); + assertEquals(Duration.ofSeconds(60), mobileOptions.getNewCommandTimeout()); + } + + @Test + public void acceptsMobileCapabilities() throws MalformedURLException { + mobileOptions.setApp(new URL("http://example.com/myapp.apk")) + .setAutomationName(AutomationName.ANDROID_UIAUTOMATOR2) + .setPlatformVersion("10") + .setDeviceName("Pixel") + .setOtherApps("/path/to/app.apk") + .setLocale("fr_CA") + .setUdid("1ae203187fc012g") + .setOrientation(ScreenOrientation.LANDSCAPE) + .setNewCommandTimeout(Duration.ofSeconds(60)) + .setLanguage("fr"); + + assertEquals("http://example.com/myapp.apk", mobileOptions.getApp()); + assertEquals(AutomationName.ANDROID_UIAUTOMATOR2, mobileOptions.getAutomationName()); + assertEquals("10", mobileOptions.getPlatformVersion()); + assertEquals("Pixel", mobileOptions.getDeviceName()); + assertEquals("/path/to/app.apk", mobileOptions.getOtherApps()); + assertEquals("fr_CA", mobileOptions.getLocale()); + assertEquals("1ae203187fc012g", mobileOptions.getUdid()); + assertEquals(ScreenOrientation.LANDSCAPE, mobileOptions.getOrientation()); + assertEquals(Duration.ofSeconds(60), mobileOptions.getNewCommandTimeout()); + assertEquals("fr", mobileOptions.getLanguage()); + } + + @Test + public void acceptsMobileBooleanCapabilityDefaults() { + mobileOptions.setClearSystemFiles() + .setAutoWebview() + .setEnablePerformanceLogging() + .setEventTimings() + .setAutoWebview() + .setFullReset() + .setPrintPageSourceOnFindFailure(); + + assertTrue(mobileOptions.doesClearSystemFiles()); + assertTrue(mobileOptions.doesAutoWebview()); + assertTrue(mobileOptions.isEnablePerformanceLogging()); + assertTrue(mobileOptions.doesEventTimings()); + assertTrue(mobileOptions.doesAutoWebview()); + assertTrue(mobileOptions.doesFullReset()); + assertTrue(mobileOptions.doesPrintPageSourceOnFindFailure()); + } + + @Test + public void setsMobileBooleanCapabilities() { + mobileOptions.setClearSystemFiles(false) + .setAutoWebview(false) + .setEnablePerformanceLogging(false) + .setEventTimings(false) + .setAutoWebview(false) + .setFullReset(false) + .setPrintPageSourceOnFindFailure(false); + + assertFalse(mobileOptions.doesClearSystemFiles()); + assertFalse(mobileOptions.doesAutoWebview()); + assertFalse(mobileOptions.isEnablePerformanceLogging()); + assertFalse(mobileOptions.doesEventTimings()); + assertFalse(mobileOptions.doesAutoWebview()); + assertFalse(mobileOptions.doesFullReset()); + assertFalse(mobileOptions.doesPrintPageSourceOnFindFailure()); + } +} From fac396ae9fdd7f0c4475435fe6d18da7a2724dc5 Mon Sep 17 00:00:00 2001 From: titusfortner Date: Thu, 2 Apr 2020 20:53:25 -0500 Subject: [PATCH 019/630] docs: clarify methods and link to respective capability type for driver options --- .../java_client/remote/MobileOptions.java | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) diff --git a/src/main/java/io/appium/java_client/remote/MobileOptions.java b/src/main/java/io/appium/java_client/remote/MobileOptions.java index 3fa20f019..6a7810f1a 100644 --- a/src/main/java/io/appium/java_client/remote/MobileOptions.java +++ b/src/main/java/io/appium/java_client/remote/MobileOptions.java @@ -26,186 +26,478 @@ public class MobileOptions> extends MutableCapabilities { + /** + * Creates new instance with no preset capabilities. + */ public MobileOptions() { } + /** + * Creates new instance with provided capabilities capabilities. + * + * @param source is Capabilities instance to merge into new instance + */ public MobileOptions(Capabilities source) { merge(source); } + /** + * Set the kind of mobile device or emulator to use. + * + * @param platform the kind of mobile device or emulator to use. + * @return this MobileOptions, for chaining. + * @see org.openqa.selenium.remote.CapabilityType#PLATFORM_NAME + */ public T setPlatformName(String platform) { return amend(CapabilityType.PLATFORM_NAME, platform); } + /** + * Get the kind of mobile device or emulator to use. + * + * @return String representing the kind of mobile device or emulator to use. + * @see org.openqa.selenium.remote.CapabilityType#PLATFORM_NAME + */ public String getPlatformName() { return (String) getCapability(CapabilityType.PLATFORM_NAME); } + /** + * Set the absolute local path for the location of the App. + * The or remote http URL to a {@code .ipa} file (IOS), + * + * @param path is a String representing the location of the App + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#APP + */ public T setApp(String path) { return amend(MobileCapabilityType.APP, path); } + /** + * Set the remote http URL for the location of the App. + * + * @param url is the URL representing the location of the App + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#APP + */ public T setApp(URL url) { return setApp(url.toString()); } + /** + * Get the app location. + * + * @return String representing app location + * @see MobileCapabilityType#APP + */ public String getApp() { return (String) getCapability(MobileCapabilityType.APP); } + /** + * Set the automation engine to use. + * + * @param name is the name of the automation engine + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#AUTOMATION_NAME + */ public T setAutomationName(String name) { return amend(MobileCapabilityType.AUTOMATION_NAME, name); } + /** + * Get the automation engine to use. + * + * @return String representing the name of the automation engine + * @see MobileCapabilityType#AUTOMATION_NAME + */ public String getAutomationName() { return (String) getCapability(MobileCapabilityType.AUTOMATION_NAME); } + /** + * Set the app to move directly into Webview context. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#AUTO_WEBVIEW + */ public T setAutoWebview() { return setAutoWebview(true); } + /** + * Set whether the app moves directly into Webview context. + * + * @param bool is whether the app moves directly into Webview context. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#AUTO_WEBVIEW + */ public T setAutoWebview(boolean bool) { return amend(MobileCapabilityType.AUTO_WEBVIEW, bool); } + /** + * Get whether the app moves directly into Webview context. + * + * @return true if app moves directly into Webview context. + * @see MobileCapabilityType#AUTO_WEBVIEW + */ public boolean doesAutoWebview() { return (boolean) getCapability(MobileCapabilityType.AUTO_WEBVIEW); } + /** + * Set the app to delete any generated files at the end of a session. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#CLEAR_SYSTEM_FILES + */ public T setClearSystemFiles() { return setClearSystemFiles(true); } + /** + * Set whether the app deletes generated files at the end of a session. + * + * @param bool is whether the app deletes generated files at the end of a session. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#CLEAR_SYSTEM_FILES + */ public T setClearSystemFiles(boolean bool) { return amend(MobileCapabilityType.CLEAR_SYSTEM_FILES, bool); } + /** + * Get whether the app deletes generated files at the end of a session. + * + * @return true if the app deletes generated files at the end of a session. + * @see MobileCapabilityType#CLEAR_SYSTEM_FILES + */ public boolean doesClearSystemFiles() { return (boolean) getCapability(MobileCapabilityType.CLEAR_SYSTEM_FILES); } + /** + * Set the name of the device. + * + * @param deviceName is the name of the device. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#DEVICE_NAME + */ public T setDeviceName(String deviceName) { return amend(MobileCapabilityType.DEVICE_NAME, deviceName); } + /** + * Get the name of the device. + * + * @return String representing the name of the device. + * @see MobileCapabilityType#DEVICE_NAME + */ public String getDeviceName() { return (String) getCapability(MobileCapabilityType.DEVICE_NAME); } + /** + * Set the app to enable performance logging. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#ENABLE_PERFORMANCE_LOGGING + */ public T setEnablePerformanceLogging() { return setEnablePerformanceLogging(true); } + /** + * Set whether the app logs performance. + * + * @param bool is whether the app logs performance. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#ENABLE_PERFORMANCE_LOGGING + */ public T setEnablePerformanceLogging(boolean bool) { return amend(MobileCapabilityType.ENABLE_PERFORMANCE_LOGGING, bool); } + /** + * Get the app logs performance. + * + * @return true if the app logs performance. + * @see MobileCapabilityType#ENABLE_PERFORMANCE_LOGGING + */ public boolean isEnablePerformanceLogging() { return (boolean) getCapability(MobileCapabilityType.ENABLE_PERFORMANCE_LOGGING); } + /** + * Set the app to report the timings for various Appium-internal events. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#EVENT_TIMINGS + */ public T setEventTimings() { return setEventTimings(true); } + /** + * Set whether the app reports the timings for various Appium-internal events. + * + * @param bool is whether the app enables event timings. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#EVENT_TIMINGS + */ public T setEventTimings(boolean bool) { return amend(MobileCapabilityType.EVENT_TIMINGS, bool); } + /** + * Get whether the app reports the timings for various Appium-internal events. + * + * @return true if the app reports event timings. + * @see MobileCapabilityType#EVENT_TIMINGS + */ public boolean doesEventTimings() { return (boolean) getCapability(MobileCapabilityType.EVENT_TIMINGS); } + /** + * Set the app to do a full reset. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#FULL_RESET + */ public T setFullReset() { return setFullReset(true); } + /** + * Set whether the app does a full reset. + * + * @param bool is whether the app does a full reset. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#FULL_RESET + */ public T setFullReset(boolean bool) { return amend(MobileCapabilityType.FULL_RESET, bool); } + /** + * Get whether the app does a full reset. + * + * @return true if the app does a full reset. + * @see MobileCapabilityType#FULL_RESET + */ public boolean doesFullReset() { return (boolean) getCapability(MobileCapabilityType.FULL_RESET); } + /** + * Set language abbreviation for use in session. + * + * @param language is the language abbreviation. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#LANGUAGE + */ public T setLanguage(String language) { return amend(MobileCapabilityType.LANGUAGE, language); } + /** + * Get language abbreviation for use in session. + * + * @return String representing the language abbreviation. + * @see MobileCapabilityType#LANGUAGE + */ public String getLanguage() { return (String) getCapability(MobileCapabilityType.LANGUAGE); } + /** + * Set locale abbreviation for use in session. + * + * @param locale is the locale abbreviation. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#LOCALE + */ public T setLocale(String locale) { return amend(MobileCapabilityType.LOCALE, locale); } + /** + * Get locale abbreviation for use in session. + * + * @return String representing the locale abbreviation. + * @see MobileCapabilityType#LOCALE + */ public String getLocale() { return (String) getCapability(MobileCapabilityType.LOCALE); } + /** + * Set the timeout for new commands. + * + * @param duration is the allowed time before seeing a new command. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#NEW_COMMAND_TIMEOUT + */ public T setNewCommandTimeout(Duration duration) { return amend(MobileCapabilityType.NEW_COMMAND_TIMEOUT, duration.getSeconds()); } + /** + * Get the timeout for new commands. + * + * @return allowed time before seeing a new command. + * @see MobileCapabilityType#NEW_COMMAND_TIMEOUT + */ public Duration getNewCommandTimeout() { Object duration = getCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT); return Duration.ofSeconds(Long.parseLong("" + duration)); } + /** + * Set the app not to do a reset. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#NO_RESET + */ public T setNoReset() { return setNoReset(true); } + /** + * Set whether the app does not do a reset. + * + * @param bool is whether the app does not do a reset. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#NO_RESET + */ public T setNoReset(boolean bool) { return amend(MobileCapabilityType.NO_RESET, bool); } + /** + * Get whether the app does not do a reset. + * + * @return true if the app does not do a reset. + * @see MobileCapabilityType#NO_RESET + */ public boolean doesNoReset() { return (boolean) getCapability(MobileCapabilityType.NO_RESET); } + /** + * Set the orientation of the screen. + * + * @param orientation is the screen orientation. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#ORIENTATION + */ public T setOrientation(ScreenOrientation orientation) { return amend(MobileCapabilityType.ORIENTATION, orientation); } + /** + * Get the orientation of the screen. + * + * @return ScreenOrientation of the app. + * @see MobileCapabilityType#ORIENTATION + */ public ScreenOrientation getOrientation() { return (ScreenOrientation) getCapability(MobileCapabilityType.ORIENTATION); } + /** + * Set the location of the app(s) to install before running a test. + * + * @param apps is the apps to install. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#OTHER_APPS + */ public T setOtherApps(String apps) { return amend(MobileCapabilityType.OTHER_APPS, apps); } + /** + * Get the list of apps to install before running a test. + * + * @return String of apps to install. + * @see MobileCapabilityType#OTHER_APPS + */ public String getOtherApps() { return (String) getCapability(MobileCapabilityType.OTHER_APPS); } + /** + * Set the version of the platform. + * + * @param version is the platform version. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#PLATFORM_VERSION + */ public T setPlatformVersion(String version) { return amend(MobileCapabilityType.PLATFORM_VERSION, version); } + /** + * Get the version of the platform. + * + * @return String representing the platform version. + * @see MobileCapabilityType#PLATFORM_VERSION + */ public String getPlatformVersion() { return (String) getCapability(MobileCapabilityType.PLATFORM_VERSION); } + /** + * Set the app to print page source when a find operation fails. + * + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#PRINT_PAGE_SOURCE_ON_FIND_FAILURE + */ public T setPrintPageSourceOnFindFailure() { return setPrintPageSourceOnFindFailure(true); } + /** + * Set whether the app to print page source when a find operation fails. + * + * @param bool is whether to print page source. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#PRINT_PAGE_SOURCE_ON_FIND_FAILURE + */ public T setPrintPageSourceOnFindFailure(boolean bool) { return amend(MobileCapabilityType.PRINT_PAGE_SOURCE_ON_FIND_FAILURE, bool); } + /** + * Get whether the app to print page source when a find operation fails. + * + * @return true if app prints page source. + * @see MobileCapabilityType#PRINT_PAGE_SOURCE_ON_FIND_FAILURE + */ public boolean doesPrintPageSourceOnFindFailure() { return (boolean) getCapability(MobileCapabilityType.PRINT_PAGE_SOURCE_ON_FIND_FAILURE); } + /** + * Set the id of the device. + * + * @param id is the unique device identifier. + * @return this MobileOptions, for chaining. + * @see MobileCapabilityType#UDID + */ public T setUdid(String id) { return amend(MobileCapabilityType.UDID, id); } + /** + * Get the id of the device. + * + * @return String representing the unique device identifier. + * @see MobileCapabilityType#UDID + */ public String getUdid() { return (String) getCapability(MobileCapabilityType.UDID); } From 85e806a921f85f064507176607b36745860c65ec Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 7 Apr 2020 20:16:03 +0200 Subject: [PATCH 020/630] refactor: Put test files into test resources (#1333) --- .../io/appium/java_client/TestResources.java | 31 ++++++++++++++++ .../java/io/appium/java_client/TestUtils.java | 21 +++++++++++ .../io/appium/java_client/WebViewApp.app.zip | Bin 52697 -> 0 bytes .../java_client/android/BaseAndroidTest.java | 6 +-- .../java_client/android/BaseEspressoTest.java | 6 +-- .../java_client/android/IntentTest.java | 6 +-- .../java_client/appium/AndroidTest.java | 6 +-- .../android/AndroidElementGeneratingTest.java | 6 +-- .../ios/IOSElementGenerationTest.java | 9 ++--- .../io/appium/java_client/ios/AppIOSTest.java | 7 ++-- .../java_client/ios/BaseIOSWebViewTest.java | 7 ++-- .../java_client/ios/UICatalogIOSTest.java | 7 ++-- .../DesktopBrowserCompatibilityTest.java | 9 ++--- .../service/local/ServerBuilderTest.java | 5 +-- .../service/local/StartingAppLocallyTest.java | 35 +++++------------- .../apps}/ApiDemos-debug.apk | Bin .../apps}/IntentExample.apk | Bin .../apps}/TestApp.app.zip | Bin .../apps}/UICatalog.app.zip | Bin .../java_client => resources/apps}/vodqa.zip | Bin .../html}/hello appium - saved page.htm | 0 21 files changed, 90 insertions(+), 71 deletions(-) create mode 100644 src/test/java/io/appium/java_client/TestResources.java delete mode 100644 src/test/java/io/appium/java_client/WebViewApp.app.zip rename src/test/{java/io/appium/java_client => resources/apps}/ApiDemos-debug.apk (100%) rename src/test/{java/io/appium/java_client => resources/apps}/IntentExample.apk (100%) rename src/test/{java/io/appium/java_client => resources/apps}/TestApp.app.zip (100%) rename src/test/{java/io/appium/java_client => resources/apps}/UICatalog.app.zip (100%) rename src/test/{java/io/appium/java_client => resources/apps}/vodqa.zip (100%) rename src/test/{java/io/appium/java_client => resources/html}/hello appium - saved page.htm (100%) diff --git a/src/test/java/io/appium/java_client/TestResources.java b/src/test/java/io/appium/java_client/TestResources.java new file mode 100644 index 000000000..9d188fb58 --- /dev/null +++ b/src/test/java/io/appium/java_client/TestResources.java @@ -0,0 +1,31 @@ +package io.appium.java_client; + +import java.nio.file.Path; + +import static io.appium.java_client.TestUtils.resourcePathToLocalPath; + +public class TestResources { + public static Path apiDemosApk() { + return resourcePathToLocalPath("apps/ApiDemos-debug.apk"); + } + + public static Path testAppZip() { + return resourcePathToLocalPath("apps/TestApp.app.zip"); + } + + public static Path uiCatalogAppZip() { + return resourcePathToLocalPath("apps/UICatalog.app.zip"); + } + + public static Path vodQaAppZip() { + return resourcePathToLocalPath("apps/vodqa.zip"); + } + + public static Path intentExampleApk() { + return resourcePathToLocalPath("apps/IntentExample.apk"); + } + + public static Path helloAppiumHtml() { + return resourcePathToLocalPath("html/hello appium - saved page.htm"); + } +} diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index cd6e3dd44..4195d4ef9 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -1,9 +1,14 @@ package io.appium.java_client; +import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; +import java.net.URL; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; public class TestUtils { public static String getLocalIp4Address() throws SocketException, UnknownHostException { @@ -13,4 +18,20 @@ public static String getLocalIp4Address() throws SocketException, UnknownHostExc return socket.getLocalAddress().getHostAddress(); } } + + public static Path resourcePathToLocalPath(String resourcePath) { + URL url = ClassLoader.getSystemResource(resourcePath); + if (url == null) { + throw new IllegalArgumentException(String.format("Cannot find the '%s' resource", resourcePath)); + } + return Paths.get(url.getPath()); + } + + public static String resourceAsString(String resourcePath) { + try { + return new String(Files.readAllBytes(resourcePathToLocalPath(resourcePath))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/test/java/io/appium/java_client/WebViewApp.app.zip b/src/test/java/io/appium/java_client/WebViewApp.app.zip deleted file mode 100644 index 2741b57c251de18d0abf7aaa780f3c92b061db43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52697 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 BSkeFh diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java index 022c8db50..1e12834d1 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -26,7 +26,7 @@ import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; +import static io.appium.java_client.TestResources.apiDemosApk; public class BaseAndroidTest { public static final String APP_ID = "io.appium.android.apis"; @@ -45,12 +45,10 @@ public class BaseAndroidTest { "An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); capabilities.setCapability("eventTimings", true); driver = new AndroidDriver<>(service.getUrl(), capabilities); } diff --git a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java b/src/test/java/io/appium/java_client/android/BaseEspressoTest.java index 889aa238f..cbea1e064 100644 --- a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java +++ b/src/test/java/io/appium/java_client/android/BaseEspressoTest.java @@ -24,7 +24,7 @@ import org.junit.BeforeClass; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; +import static io.appium.java_client.TestResources.apiDemosApk; public class BaseEspressoTest { @@ -43,12 +43,10 @@ public class BaseEspressoTest { "An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ESPRESSO); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); capabilities.setCapability("eventTimings", true); driver = new AndroidDriver<>(service.getUrl(), capabilities); } diff --git a/src/test/java/io/appium/java_client/android/IntentTest.java b/src/test/java/io/appium/java_client/android/IntentTest.java index ac5810c5d..d2fde8e25 100644 --- a/src/test/java/io/appium/java_client/android/IntentTest.java +++ b/src/test/java/io/appium/java_client/android/IntentTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import static io.appium.java_client.TestResources.intentExampleApk; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -10,7 +11,6 @@ import org.junit.Test; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; import java.util.function.Predicate; public class IntentTest { @@ -28,11 +28,9 @@ public class IntentTest { throw new RuntimeException("An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "IntentExample.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, intentExampleApk().toAbsolutePath().toString()); driver = new AndroidDriver<>(service.getUrl(), capabilities); } diff --git a/src/test/java/io/appium/java_client/appium/AndroidTest.java b/src/test/java/io/appium/java_client/appium/AndroidTest.java index fd4b7339c..aa3d2929e 100644 --- a/src/test/java/io/appium/java_client/appium/AndroidTest.java +++ b/src/test/java/io/appium/java_client/appium/AndroidTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.appium; +import static io.appium.java_client.TestResources.apiDemosApk; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -22,7 +23,6 @@ import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.Response; -import java.io.File; import java.util.Map; public class AndroidTest { @@ -44,12 +44,10 @@ public static void beforeClass() { "An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); driver = new AppiumDriver<>(service.getUrl(), capabilities); } diff --git a/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java b/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java index 9e139e2cd..1582c7b18 100644 --- a/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java +++ b/src/test/java/io/appium/java_client/appium/element/generation/android/AndroidElementGeneratingTest.java @@ -1,6 +1,7 @@ package io.appium.java_client.appium.element.generation.android; import static io.appium.java_client.MobileBy.AndroidUIAutomator; +import static io.appium.java_client.TestResources.apiDemosApk; import static org.junit.Assert.assertTrue; import static org.openqa.selenium.By.name; import static org.openqa.selenium.By.tagName; @@ -19,19 +20,16 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; import java.util.function.BiPredicate; import java.util.function.Supplier; public class AndroidElementGeneratingTest extends BaseElementGenerationTest { - private final File app = new File(new File("src/test/java/io/appium/java_client"), - "ApiDemos-debug.apk"); private final Supplier commonCapabilitiesSupplier = () -> { DesiredCapabilities serverCapabilities = new DesiredCapabilities(); serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); serverCapabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - serverCapabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + serverCapabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); return serverCapabilities; }; diff --git a/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java b/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java index 19f1fd32b..3115f8997 100644 --- a/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java +++ b/src/test/java/io/appium/java_client/appium/element/generation/ios/IOSElementGenerationTest.java @@ -1,6 +1,8 @@ package io.appium.java_client.appium.element.generation.ios; import static io.appium.java_client.MobileBy.AccessibilityId; +import static io.appium.java_client.TestResources.testAppZip; +import static io.appium.java_client.TestResources.vodQaAppZip; import static org.junit.Assert.assertTrue; import static org.openqa.selenium.By.id; import static org.openqa.selenium.By.name; @@ -32,12 +34,9 @@ public class IOSElementGenerationTest extends BaseElementGenerationTest { - private static final File testApp = new File(new File("src/test/java/io/appium/java_client"), - "TestApp.app.zip"); - - private static final File webViewApp = new File(new File("src/test/java/io/appium/java_client"), - "vodqa.zip"); + private static final File testApp = testAppZip().toFile(); + private static final File webViewApp = vodQaAppZip().toFile(); private Supplier commonAppCapabilitiesSupplier = () -> { DesiredCapabilities serverCapabilities = new DesiredCapabilities(); diff --git a/src/test/java/io/appium/java_client/ios/AppIOSTest.java b/src/test/java/io/appium/java_client/ios/AppIOSTest.java index e41e325a6..12426ebd9 100644 --- a/src/test/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/AppIOSTest.java @@ -8,9 +8,10 @@ import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; import java.net.URL; +import static io.appium.java_client.TestResources.testAppZip; + public class AppIOSTest extends BaseIOSTest { public static final String BUNDLE_ID = "io.appium.TestApp"; @@ -23,8 +24,6 @@ public static void beforeClass() throws Exception { throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "TestApp.app.zip"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME); @@ -32,7 +31,7 @@ public static void beforeClass() throws Exception { //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); capabilities.setCapability("commandTimeouts", "120000"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, testAppZip().toAbsolutePath().toString()); try { driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); } catch (SessionNotCreatedException e) { diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index 647718603..cc87a97ee 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -23,13 +23,14 @@ import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; import java.util.function.Supplier; +import static io.appium.java_client.TestResources.vodQaAppZip; + public class BaseIOSWebViewTest extends BaseIOSTest { private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(1); private static final Duration WEB_VIEW_DETECT_DURATION = Duration.ofSeconds(15); @@ -42,15 +43,13 @@ public static void beforeClass() throws IOException { throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "vodqa.zip"); final DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME); capabilities.setCapability("commandTimeouts", "120000"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, vodQaAppZip().toAbsolutePath().toString()); Supplier> createDriver = () -> { try { return new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); diff --git a/src/test/java/io/appium/java_client/ios/UICatalogIOSTest.java b/src/test/java/io/appium/java_client/ios/UICatalogIOSTest.java index ff6e4b5a5..95bbc5f6a 100644 --- a/src/test/java/io/appium/java_client/ios/UICatalogIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/UICatalogIOSTest.java @@ -6,10 +6,11 @@ import org.junit.BeforeClass; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; import java.io.IOException; import java.net.URL; +import static io.appium.java_client.TestResources.uiCatalogAppZip; + public class UICatalogIOSTest extends BaseIOSTest { @BeforeClass @@ -21,15 +22,13 @@ public static void beforeClass() throws IOException { "An appium server node is not started!"); } - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "UICatalog.app.zip"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "9.2"); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone 6"); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, uiCatalogAppZip().toAbsolutePath().toString()); driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); } } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/DesktopBrowserCompatibilityTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/DesktopBrowserCompatibilityTest.java index 93fa1225e..63ff333b5 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/DesktopBrowserCompatibilityTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/DesktopBrowserCompatibilityTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.pagefactory_tests; +import static io.appium.java_client.TestResources.helloAppiumHtml; import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static java.time.Duration.ofSeconds; @@ -36,7 +37,6 @@ import org.openqa.selenium.support.FindBys; import org.openqa.selenium.support.PageFactory; -import java.io.File; import java.util.List; public class DesktopBrowserCompatibilityTest { @@ -60,11 +60,8 @@ public class DesktopBrowserCompatibilityTest { @Test public void chromeTest() { WebDriver driver = new ChromeDriver(); try { - PageFactory - .initElements(new AppiumFieldDecorator(driver, ofSeconds(15)), - this); - driver.get(new File("src/test/java/io/appium/java_client/hello appium - saved page.htm") - .toURI().toString()); + PageFactory.initElements(new AppiumFieldDecorator(driver, ofSeconds(15)), this); + driver.get(helloAppiumHtml().toUri().toString()); assertNotEquals(0, foundLinks.size()); assertNotEquals(0, main.size()); assertNull(trap1); diff --git a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java index 8df6ac856..03fc62a80 100644 --- a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java +++ b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.service.local; +import static io.appium.java_client.TestResources.apiDemosApk; import static io.appium.java_client.TestUtils.getLocalIp4Address; import static io.appium.java_client.remote.AndroidMobileCapabilityType.APP_ACTIVITY; import static io.appium.java_client.remote.AndroidMobileCapabilityType.APP_PACKAGE; @@ -152,15 +153,13 @@ public void checkAbilityToStartServiceUsingFlags() { @Test public void checkAbilityToStartServiceUsingCapabilities() { - File app = ROOT_TEST_PATH.resolve("ApiDemos-debug.apk").toFile(); - DesiredCapabilities caps = new DesiredCapabilities(); caps.setCapability(PLATFORM_NAME, "Android"); caps.setCapability(FULL_RESET, true); caps.setCapability(NEW_COMMAND_TIMEOUT, 60); caps.setCapability(APP_PACKAGE, "io.appium.android.apis"); caps.setCapability(APP_ACTIVITY, ".view.WebView1"); - caps.setCapability(APP, app.getAbsolutePath()); + caps.setCapability(APP, apiDemosApk().toAbsolutePath().toString()); caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getBinaryPath()); service = new AppiumServiceBuilder().withCapabilities(caps).build(); diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java index 1ffecff14..8cc855440 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java @@ -16,6 +16,8 @@ package io.appium.java_client.service.local; +import static io.appium.java_client.TestResources.apiDemosApk; +import static io.appium.java_client.TestResources.uiCatalogAppZip; import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,16 +39,12 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; - public class StartingAppLocallyTest { @Test public void startingAndroidAppWithCapabilitiesOnlyTest() { - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM); AndroidDriver driver = new AndroidDriver<>(capabilities); @@ -56,18 +54,16 @@ public class StartingAppLocallyTest { assertEquals(AutomationName.APPIUM, caps.getCapability(MobileCapabilityType.AUTOMATION_NAME)); assertEquals(MobilePlatform.ANDROID, caps.getCapability(MobileCapabilityType.PLATFORM_NAME)); assertNotEquals(null, caps.getCapability(MobileCapabilityType.DEVICE_NAME)); - assertEquals(app.getAbsolutePath(), caps.getCapability(MobileCapabilityType.APP)); + assertEquals(apiDemosApk().toAbsolutePath().toString(), caps.getCapability(MobileCapabilityType.APP)); } finally { driver.quit(); } } @Test public void startingAndroidAppWithCapabilitiesAndServiceTest() { - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) @@ -85,15 +81,12 @@ public class StartingAppLocallyTest { } @Test public void startingAndroidAppWithCapabilitiesAndFlagsOnServerSideTest() { - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "ApiDemos-debug.apk"); - DesiredCapabilities serverCapabilities = new DesiredCapabilities(); serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android"); serverCapabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); serverCapabilities.setCapability(MobileCapabilityType.FULL_RESET, true); serverCapabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 60); - serverCapabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + serverCapabilities.setCapability(MobileCapabilityType.APP, apiDemosApk().toAbsolutePath().toString()); WebDriverManager chromeManager = chromedriver(); chromeManager.setup(); @@ -122,15 +115,12 @@ public class StartingAppLocallyTest { } @Test public void startingIOSAppWithCapabilitiesOnlyTest() { - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "UICatalog.app.zip"); - DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, BaseIOSTest.PLATFORM_VERSION); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, BaseIOSTest.DEVICE_NAME); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, uiCatalogAppZip().toAbsolutePath().toString()); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM); IOSDriver driver = new IOSDriver<>(capabilities); @@ -141,7 +131,7 @@ public class StartingAppLocallyTest { assertEquals(MobilePlatform.IOS, caps.getCapability(MobileCapabilityType.PLATFORM_NAME)); assertNotEquals(null, caps.getCapability(MobileCapabilityType.DEVICE_NAME)); assertEquals(BaseIOSTest.PLATFORM_VERSION, caps.getCapability(MobileCapabilityType.PLATFORM_VERSION)); - assertEquals(app.getAbsolutePath(), caps.getCapability(MobileCapabilityType.APP)); + assertEquals(uiCatalogAppZip().toAbsolutePath().toString(), caps.getCapability(MobileCapabilityType.APP)); } finally { driver.quit(); } @@ -149,13 +139,10 @@ public class StartingAppLocallyTest { @Test public void startingIOSAppWithCapabilitiesAndServiceTest() { - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "UICatalog.app.zip"); - DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, BaseIOSTest.DEVICE_NAME); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.APP, uiCatalogAppZip().toAbsolutePath().toString()); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, BaseIOSTest.PLATFORM_VERSION); //sometimes environment has performance problems capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); @@ -183,10 +170,8 @@ public class StartingAppLocallyTest { serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, BaseIOSTest.PLATFORM_VERSION); serverCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.IOS); - File appDir = new File("src/test/java/io/appium/java_client"); - File app = new File(appDir, "UICatalog.app.zip"); DesiredCapabilities clientCapabilities = new DesiredCapabilities(); - clientCapabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + clientCapabilities.setCapability(MobileCapabilityType.APP, uiCatalogAppZip().toAbsolutePath().toString()); AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) diff --git a/src/test/java/io/appium/java_client/ApiDemos-debug.apk b/src/test/resources/apps/ApiDemos-debug.apk similarity index 100% rename from src/test/java/io/appium/java_client/ApiDemos-debug.apk rename to src/test/resources/apps/ApiDemos-debug.apk diff --git a/src/test/java/io/appium/java_client/IntentExample.apk b/src/test/resources/apps/IntentExample.apk similarity index 100% rename from src/test/java/io/appium/java_client/IntentExample.apk rename to src/test/resources/apps/IntentExample.apk diff --git a/src/test/java/io/appium/java_client/TestApp.app.zip b/src/test/resources/apps/TestApp.app.zip similarity index 100% rename from src/test/java/io/appium/java_client/TestApp.app.zip rename to src/test/resources/apps/TestApp.app.zip diff --git a/src/test/java/io/appium/java_client/UICatalog.app.zip b/src/test/resources/apps/UICatalog.app.zip similarity index 100% rename from src/test/java/io/appium/java_client/UICatalog.app.zip rename to src/test/resources/apps/UICatalog.app.zip diff --git a/src/test/java/io/appium/java_client/vodqa.zip b/src/test/resources/apps/vodqa.zip similarity index 100% rename from src/test/java/io/appium/java_client/vodqa.zip rename to src/test/resources/apps/vodqa.zip diff --git a/src/test/java/io/appium/java_client/hello appium - saved page.htm b/src/test/resources/html/hello appium - saved page.htm similarity index 100% rename from src/test/java/io/appium/java_client/hello appium - saved page.htm rename to src/test/resources/html/hello appium - saved page.htm From 5af08a1b1d8d471d5d42e2bb002db658457b5a7b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 23 Apr 2020 08:49:10 +0200 Subject: [PATCH 021/630] fix: Properly translate desiredCapabilities into a command line argument (#1337) --- .../service/local/AppiumServiceBuilder.java | 92 +++---------------- .../service/local/ServerBuilderTest.java | 6 ++ 2 files changed, 18 insertions(+), 80 deletions(-) diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java index 10fdefe56..caef4d36a 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java @@ -23,8 +23,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.appium.java_client.remote.AndroidMobileCapabilityType; -import io.appium.java_client.remote.MobileCapabilityType; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.appium.java_client.service.local.flags.ServerArgument; import org.apache.commons.io.IOUtils; @@ -32,7 +32,6 @@ import org.apache.commons.lang3.SystemUtils; import org.apache.commons.validator.routines.InetAddressValidator; import org.openqa.selenium.Capabilities; -import org.openqa.selenium.Platform; import org.openqa.selenium.os.ExecutableFinder; import org.openqa.selenium.remote.BrowserType; import org.openqa.selenium.remote.DesiredCapabilities; @@ -54,7 +53,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Function; - public final class AppiumServiceBuilder extends DriverService.Builder { @@ -73,8 +71,6 @@ public final class AppiumServiceBuilder private static final String NODE_PATH = "NODE_BINARY_PATH"; public static final String BROADCAST_IP_ADDRESS = "0.0.0.0"; - private static final List PATH_CAPABILITIES = ImmutableList.of(AndroidMobileCapabilityType.KEYSTORE_PATH, - AndroidMobileCapabilityType.CHROMEDRIVER_EXECUTABLE, MobileCapabilityType.APP); private static final Path APPIUM_PATH_SUFFIX = Paths.get("appium", "build", "lib", "main.js"); public static final int DEFAULT_APPIUM_PORT = 4723; private final Map serverArguments = new HashMap<>(); @@ -300,79 +296,15 @@ private void loadPathToMainScript() { this.appiumJS = findMainScript(); } - private String parseCapabilitiesIfWindows() { - String result = StringUtils.EMPTY; - - if (capabilities != null) { - Map capabilitiesMap = capabilities.asMap(); - Set> entries = capabilitiesMap.entrySet(); - - for (Map.Entry entry : entries) { - Object value = entry.getValue(); - - if (value == null) { - continue; - } - - if (String.class.isAssignableFrom(value.getClass())) { - if (PATH_CAPABILITIES.contains(entry.getKey())) { - value = "\\\"" + String.valueOf(value).replace("\\", "/") + "\\\""; - } else { - value = "\\\"" + value + "\\\""; - } - } else { - value = String.valueOf(value); - } - - String key = "\\\"" + entry.getKey() + "\\\""; - if (StringUtils.isBlank(result)) { - result = key + ": " + value; - } else { - result = result + ", " + key + ": " + value; - } - } - } - - return "{" + result + "}"; - } - - private String parseCapabilitiesIfUNIX() { - String result = StringUtils.EMPTY; - - if (capabilities != null) { - Map capabilitiesMap = capabilities.asMap(); - Set> entries = capabilitiesMap.entrySet(); - - for (Map.Entry entry : entries) { - Object value = entry.getValue(); - - if (value == null) { - continue; - } - - if (String.class.isAssignableFrom(value.getClass())) { - value = "\"" + value + "\""; - } else { - value = String.valueOf(value); - } - - String key = "\"" + entry.getKey() + "\""; - if (StringUtils.isBlank(result)) { - result = key + ": " + value; - } else { - result = result + ", " + key + ": " + value; - } - } - } - - return "{" + result + "}"; - } - - private String parseCapabilities() { - if (Platform.getCurrent().is(Platform.WINDOWS)) { - return parseCapabilitiesIfWindows(); - } - return parseCapabilitiesIfUNIX(); + private String capabilitiesToCmdlineArg() { + Gson gson = new GsonBuilder() + .disableHtmlEscaping() + .serializeNulls() + .create(); + // Selenium internally uses org.apache.commons.exec.CommandLine + // which has the following known bug in its arguments parser: + // https://issues.apache.org/jira/browse/EXEC-54 + return gson.toJson(capabilities.asMap()); } @Override @@ -418,7 +350,7 @@ protected ImmutableList createArgs() { if (capabilities != null) { argList.add("--default-capabilities"); - argList.add(parseCapabilities()); + argList.add(capabilitiesToCmdlineArg()); } return new ImmutableList.Builder().addAll(argList).build(); diff --git a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java index 03fc62a80..05a9109f9 100644 --- a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java +++ b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import com.google.common.collect.ImmutableMap; import io.github.bonigarcia.wdm.WebDriverManager; import org.junit.After; import org.junit.BeforeClass; @@ -178,6 +179,11 @@ public void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { caps.setCapability(APP_PACKAGE, "io.appium.android.apis"); caps.setCapability(APP_ACTIVITY, ".view.WebView1"); caps.setCapability(APP, app.getAbsolutePath()); + caps.setCapability("winPath", "C:\\selenium\\app.apk"); + caps.setCapability("unixPath", "/selenium/app.apk"); + caps.setCapability("quotes", "\"'"); + caps.setCapability("goog:chromeOptions", + ImmutableMap.of("env", ImmutableMap.of("test", "value"), "val2", 0)); caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getBinaryPath()); service = new AppiumServiceBuilder() From dea6bb18422d591bf47e7538bd622a56b188f02b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 10 May 2020 09:20:00 +0200 Subject: [PATCH 022/630] feat: Add new upload options that were introduced recently (#1342) --- .../ScreenRecordingUploadOptions.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java b/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java index dcad861b4..424bfeddd 100644 --- a/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java @@ -28,6 +28,9 @@ public class ScreenRecordingUploadOptions { private String user; private String pass; private String method; + private String fileFieldName; + private Map headers; + private Map formFields; public static ScreenRecordingUploadOptions uploadOptions() { return new ScreenRecordingUploadOptions(); @@ -74,6 +77,45 @@ public ScreenRecordingUploadOptions withHttpMethod(RequestMethod method) { return this; } + /** + * Sets the form field name containing the binary payload in multipart/form-data + * requests. + * + * @since Appium 1.18.0 + * @param fileFieldName The name of the form field containing the binary payload. + * "file" by default. + * @return self instance for chaining. + */ + public ScreenRecordingUploadOptions withFileFieldName(String fileFieldName) { + this.fileFieldName = checkNotNull(fileFieldName); + return this; + } + + /** + * Sets additional form fields in multipart/form-data requests. + * + * @since Appium 1.18.0 + * @param formFields form fields mapping. If any entry has the same key as + * `fileFieldName` then it is going to be ignored. + * @return self instance for chaining. + */ + public ScreenRecordingUploadOptions withFormFields(Map formFields) { + this.formFields = checkNotNull(formFields); + return this; + } + + /** + * Sets additional headers in multipart/form-data requests. + * + * @since Appium 1.18.0 + * @param headers headers mapping. + * @return self instance for chaining. + */ + public ScreenRecordingUploadOptions withHeaders(Map headers) { + this.headers = checkNotNull(headers); + return this; + } + /** * Builds a map, which is ready to be passed to the subordinated * Appium API. @@ -86,6 +128,9 @@ public Map build() { ofNullable(user).map(x -> builder.put("user", x)); ofNullable(pass).map(x -> builder.put("pass", x)); ofNullable(method).map(x -> builder.put("method", x)); + ofNullable(fileFieldName).map(x -> builder.put("fileFieldName", x)); + ofNullable(formFields).map(x -> builder.put("formFields", x)); + ofNullable(headers).map(x -> builder.put("headers", x)); return builder.build(); } } From fb8fd729f016776598d4a0a3652b0a20973b7457 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Wed, 13 May 2020 23:23:45 +0300 Subject: [PATCH 023/630] chore: upgrade dependencies (#1344) --- build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 92de4f553..d8b4aab67 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.owasp:dependency-check-gradle:5.3.0' + classpath 'org.owasp:dependency-check-gradle:5.3.2.1' classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' } } @@ -33,7 +33,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.20.0' + ecj 'org.eclipse.jdt:ecj:3.21.0' lombok 'org.projectlombok:lombok:1.18.12' } @@ -73,18 +73,18 @@ dependencies { force = true } compile 'com.google.code.gson:gson:2.8.6' - compile 'org.apache.httpcomponents:httpclient:4.5.11' + compile 'org.apache.httpcomponents:httpclient:4.5.12' compile 'cglib:cglib:3.3.0' compile 'commons-validator:commons-validator:1.6' - compile 'org.apache.commons:commons-lang3:3.9' + compile 'org.apache.commons:commons-lang3:3.10' compile 'commons-io:commons-io:2.6' - compile 'org.springframework:spring-context:5.2.3.RELEASE' + compile 'org.springframework:spring-context:5.2.6.RELEASE' compile 'org.aspectj:aspectjweaver:1.9.5' compile 'org.slf4j:slf4j-api:1.7.30' testCompile 'junit:junit:4.13' testCompile 'org.hamcrest:hamcrest:2.2' - testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '3.8.1') { + testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.0.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -100,7 +100,7 @@ dependencyCheck { } jacoco { - toolVersion = '0.8.3' + toolVersion = '0.8.5' } tasks.withType(JacocoReport) { @@ -114,7 +114,7 @@ tasks.withType(JacocoReport) { jacocoTestReport.dependsOn test checkstyle { - toolVersion = '8.29' + toolVersion = '8.32' configFile = file("$projectDir/google-style.xml") showViolations = true ignoreFailures = false From 7915d343d2212bbe7c6cf8673c02c54f8d42e152 Mon Sep 17 00:00:00 2001 From: Andrei Solntsev Date: Thu, 14 May 2020 13:55:59 +0300 Subject: [PATCH 024/630] refactor: avoid casting to RemoteWebElement (#1345) --- .../io/appium/java_client/touch/offset/ElementOption.java | 8 ++++---- .../io/appium/java_client/touch/TouchOptionsTests.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/appium/java_client/touch/offset/ElementOption.java b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java index 8cf1c8c6a..ede90103c 100644 --- a/src/main/java/io/appium/java_client/touch/offset/ElementOption.java +++ b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java @@ -6,7 +6,7 @@ import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebElement; +import org.openqa.selenium.internal.HasIdentity; import java.util.HashMap; import java.util.Map; @@ -84,9 +84,9 @@ public ElementOption withCoordinates(int xOffset, int yOffset) { public ElementOption withElement(WebElement element) { checkNotNull(element); checkArgument(true, "Element should be an instance of the class which " - + "extends org.openqa.selenium.remote.RemoteWebElement", - (RemoteWebElement.class.isAssignableFrom(element.getClass()))); - elementId = RemoteWebElement.class.cast(element).getId(); + + "implements org.openqa.selenium.internal.HasIdentity", + element instanceof HasIdentity); + elementId = ((HasIdentity) element).getId(); return this; } diff --git a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java index 72ef8441f..4476ed33e 100644 --- a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java +++ b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java @@ -27,7 +27,7 @@ import java.util.Map; public class TouchOptionsTests { - private static final WebElement DUMMY_ELEMENT = new DummyElement(); + private static final RemoteWebElement DUMMY_ELEMENT = new DummyElement(); @Test(expected = IllegalArgumentException.class) public void invalidEmptyPointOptionsShouldFailOnBuild() { @@ -62,7 +62,7 @@ public void longPressOptionsShouldBuildProperly() { .withDuration(ofMillis(1)) .build(); final Map expectedOpts = new HashMap<>(); - expectedOpts.put("element", ((RemoteWebElement) DUMMY_ELEMENT).getId()); + expectedOpts.put("element", DUMMY_ELEMENT.getId()); expectedOpts.put("x", 0); expectedOpts.put("y", 0); expectedOpts.put("duration", 1L); From 80070fa34fae170e9797b3869506912ee62f8742 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 14 May 2020 15:29:50 +0200 Subject: [PATCH 025/630] chore: Mute all confusing warnings (#1347) --- build.gradle | 4 +++- .../io/appium/java_client/remote/AppiumCommandExecutor.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d8b4aab67..9bebd7def 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,9 @@ compileJava { options.fork = true options.fork executable: 'java', jvmArgs: [ '-javaagent:'+lombokjar.path+'=ECJ', '-jar', ecjJar.path, '-cp', lombokjar.path] options.define compilerArgs: [ - '-encoding', 'utf-8' + '-encoding', 'utf-8', + // https://www.ibm.com/support/knowledgecenter/SS8PJ7_9.7.0/org.eclipse.jdt.doc.user/tasks/task-using_batch_compiler.htm + '-warn:-unused,-unchecked,-raw,-serial,-suppress', ] } diff --git a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java index 061a291f2..ea0ade5f0 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -168,7 +168,7 @@ private Response createSession(Command command) throws IOException { throw new SessionNotCreatedException("Session already exists"); } ProtocolHandshake handshake = new ProtocolHandshake() { - @SuppressWarnings({"unchecked", "UnstableApiUsage"}) + @SuppressWarnings("unchecked") public Result createSession(HttpClient client, Command command) throws IOException { Capabilities desiredCapabilities = (Capabilities) command.getParameters().get("desiredCapabilities"); Capabilities desired = desiredCapabilities == null ? new ImmutableCapabilities() : desiredCapabilities; From cf33d48e5575f739722da99d27e5bb641ab77287 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 14 May 2020 17:35:19 +0200 Subject: [PATCH 026/630] chore: Add dependabot (#1346) --- .dependabot/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .dependabot/config.yml diff --git a/.dependabot/config.yml b/.dependabot/config.yml new file mode 100644 index 000000000..7abbcde03 --- /dev/null +++ b/.dependabot/config.yml @@ -0,0 +1,6 @@ +version: 1 +update_configs: + - package_manager: "java:gradle" + directory: "/" + update_schedule: "weekly" + From d1d0f783284132d6d8164f1fe20e319862621d63 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 27 May 2020 13:33:33 +0200 Subject: [PATCH 027/630] fix: Increase the timeout for graceful service termination (#1354) --- .../local/AppiumDriverLocalService.java | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java index c3f9e0389..3e82e0de4 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java @@ -37,8 +37,10 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @@ -56,6 +58,8 @@ public final class AppiumDriverLocalService extends DriverService { private static final Pattern LOG_MESSAGE_PATTERN = Pattern.compile("^(.*)\\R"); private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern.compile("^(\\[debug\\] )?\\[(.+?)\\]"); private static final String APPIUM_SERVICE_SLF4J_LOGGER_PREFIX = "appium.service"; + private static final Duration DESTROY_TIMEOUT = Duration.ofSeconds(60); + private final File nodeJSExec; private final ImmutableList nodeJSArgs; private final ImmutableMap nodeJSEnvironment; @@ -64,7 +68,7 @@ public final class AppiumDriverLocalService extends DriverService { private final ReentrantLock lock = new ReentrantLock(true); //uses "fair" thread ordering policy private final ListOutputStream stream = new ListOutputStream().add(System.out); private final URL url; - + private CommandLine process = null; AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort, @@ -187,10 +191,47 @@ public void stop() { } } - private void destroyProcess() { - if (process.isRunning()) { - process.destroy(); + /** + * Destroys the service if it is running. + * + * @param timeout The maximum time to wait before the process will be force-killed. + * @return The exit code of the process or zero if the process was not running. + */ + private int destroyProcess(Duration timeout) { + if (!process.isRunning()) { + return 0; } + + // This all magic is necessary, because Selenium does not publicly expose + // process killing timeouts. By default a process is killed forcibly if + // it does not exit after two seconds, which is in most cases not enough for + // Appium + try { + Field processField = process.getClass().getDeclaredField("process"); + processField.setAccessible(true); + Object osProcess = processField.get(process); + Field watchdogField = osProcess.getClass().getDeclaredField("executeWatchdog"); + watchdogField.setAccessible(true); + Object watchdog = watchdogField.get(osProcess); + Field nativeProcessField = watchdog.getClass().getDeclaredField("process"); + nativeProcessField.setAccessible(true); + Process nativeProcess = (Process) nativeProcessField.get(watchdog); + nativeProcess.destroy(); + nativeProcess.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOG.warn("No explicit timeout could be applied to the process termination", e); + } + + return process.destroy(); + } + + /** + * Destroys the service. + * This methods waits up to `DESTROY_TIMEOUT` seconds for the Appium service + * to exit gracefully. + */ + private void destroyProcess() { + destroyProcess(DESTROY_TIMEOUT); } /** From f53e4bc8bb52867f69fe06bafa85de010590e22b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 29 May 2020 02:52:09 +0200 Subject: [PATCH 028/630] docs: Address warnings printed by docs linter (#1355) --- src/main/java/io/appium/java_client/ComparesImages.java | 6 ++++++ .../java/io/appium/java_client/battery/BatteryInfo.java | 1 + .../java_client/imagecomparison/ComparisonResult.java | 1 + .../io/appium/java_client/internal/CapabilityHelpers.java | 1 + src/main/java/io/appium/java_client/internal/Config.java | 2 ++ .../appium/java_client/pagefactory/utils/ProxyFactory.java | 1 + .../appium/java_client/remote/NewAppiumSessionPayload.java | 5 +++++ .../java_client/screenrecording/CanRecordScreen.java | 7 +++++++ src/main/java/org/openqa/selenium/SearchContext.java | 6 ++++-- 9 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 060920bb1..3cb85036c 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -74,6 +74,7 @@ default FeaturesMatchingResult matchImagesFeatures(byte[] base64image1, byte[] b * @param image1 The location of the first image * @param image2 The location of the second image * @return The matching result. + * @throws IOException On file system I/O error. */ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2) throws IOException { return matchImagesFeatures(image1, image2, null); @@ -88,6 +89,7 @@ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2) thr * @param image2 The location of the second image * @param options comparison options * @return The matching result. The configuration of fields in the result depends on comparison options. + * @throws IOException On file system I/O error. */ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2, @Nullable FeaturesMatchingOptions options) throws IOException { @@ -137,6 +139,7 @@ default OccurrenceMatchingResult findImageOccurrence(byte[] fullImage, byte[] pa * @param fullImage The location of the full image * @param partialImage The location of the partial image * @return The matching result. The configuration of fields in the result depends on comparison options. + * @throws IOException On file system I/O error. */ default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partialImage) throws IOException { return findImageOccurrence(fullImage, partialImage, null); @@ -152,6 +155,7 @@ default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partia * @param partialImage The location of the partial image * @param options comparison options * @return The matching result. The configuration of fields in the result depends on comparison options. + * @throws IOException On file system I/O error. */ default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partialImage, @Nullable OccurrenceMatchingOptions options) @@ -202,6 +206,7 @@ default SimilarityMatchingResult getImagesSimilarity(byte[] base64image1, byte[] * @param image1 The location of the full image * @param image2 The location of the partial image * @return Matching result. The configuration of fields in the result depends on comparison options. + * @throws IOException On file system I/O error. */ default SimilarityMatchingResult getImagesSimilarity(File image1, File image2) throws IOException { return getImagesSimilarity(image1, image2, null); @@ -217,6 +222,7 @@ default SimilarityMatchingResult getImagesSimilarity(File image1, File image2) t * @param image2 The location of the partial image * @param options comparison options * @return Matching result. The configuration of fields in the result depends on comparison options. + * @throws IOException On file system I/O error. */ default SimilarityMatchingResult getImagesSimilarity(File image1, File image2, @Nullable SimilarityMatchingOptions options) diff --git a/src/main/java/io/appium/java_client/battery/BatteryInfo.java b/src/main/java/io/appium/java_client/battery/BatteryInfo.java index 1ce4ba060..1781db8a5 100644 --- a/src/main/java/io/appium/java_client/battery/BatteryInfo.java +++ b/src/main/java/io/appium/java_client/battery/BatteryInfo.java @@ -25,6 +25,7 @@ public double getLevel() { /** * Returns battery state. * + * @param The type of state data object for the corresponding platform. * @return Battery state value. */ public abstract T getState(); diff --git a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java index bd0cad7c5..1dcfa3e68 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -67,6 +67,7 @@ public byte[] getVisualization() { * Stores visualization image into the given file. * * @param destination file to save image. + * @throws IOException On file system I/O error. */ public void storeVisualization(File destination) throws IOException { final byte[] data = Base64.decodeBase64(getVisualization()); diff --git a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java index 0cb9ec96c..8688eef13 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -29,6 +29,7 @@ public class CapabilityHelpers { * Helper that is used for capability values retrieval. * Supports both prefixed W3C and "classic" capability names. * + * @param The corresponding capability type. * @param caps driver caps object * @param name capability name * @param expectedType the expected capability type diff --git a/src/main/java/io/appium/java_client/internal/Config.java b/src/main/java/io/appium/java_client/internal/Config.java index 0d0e3febd..fd9ef73c1 100644 --- a/src/main/java/io/appium/java_client/internal/Config.java +++ b/src/main/java/io/appium/java_client/internal/Config.java @@ -33,6 +33,7 @@ private Config(String configName) { /** * Retrieve a value from properties file. * + * @param the value type. * @param key the name of the corresponding key which value to retrieve * @param valueType the expected type of the value to be retrieved * @return the actual value @@ -49,6 +50,7 @@ public T getValue(String key, Class valueType) { /** * Retrieve a value from properties file. * + * @param the type of the resulting value. * @param key the name of the corresponding key which value to retrieve * @param valueType the expected type of the value to be retrieved * @return the actual value or {@link Optional#empty()} if the key is not present diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/ProxyFactory.java b/src/main/java/io/appium/java_client/pagefactory/utils/ProxyFactory.java index 2941e9408..390ebcd92 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/ProxyFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/ProxyFactory.java @@ -36,6 +36,7 @@ public static T getEnhancedProxy(Class requiredClazz, MethodInterceptor i /** * It returns some proxies created by CGLIB. * + * @param The proxy object class. * @param requiredClazz is a {@link java.lang.Class} whose instance should be created * @param params is an array of @link java.lang.Class}. It should be convenient to * parameter types of some declared constructor which belongs to desired diff --git a/src/main/java/io/appium/java_client/remote/NewAppiumSessionPayload.java b/src/main/java/io/appium/java_client/remote/NewAppiumSessionPayload.java index add478a87..8d51f914d 100644 --- a/src/main/java/io/appium/java_client/remote/NewAppiumSessionPayload.java +++ b/src/main/java/io/appium/java_client/remote/NewAppiumSessionPayload.java @@ -120,6 +120,7 @@ private static List getAppiumCapabilities(Class capabilityList) { * * @param caps capabilities to create a new session * @return instance of {@link NewAppiumSessionPayload} + * @throws IOException On file system I/O error. */ public static NewAppiumSessionPayload create(Capabilities caps) throws IOException { boolean forceMobileJSONWP = @@ -235,6 +236,7 @@ private void validate() throws IOException { * Writes json capabilities to some appendable object. * * @param appendable to write a json + * @throws IOException On file system I/O error. */ public void writeTo(Appendable appendable) throws IOException { try (JsonOutput json = new Json().newOutput(appendable)) { @@ -304,6 +306,9 @@ private void writeMetaData(JsonOutput out) throws IOException { * in the W3C WebDriver spec. The OSS {@link Capabilities} are listed first because converting the * OSS capabilities to the equivalent W3C capabilities isn't particularly easy, so it's hoped that * this approach gives us the most compatible implementation. + * + * @return The capabilities as a stream. + * @throws IOException On file system I/O error. */ public Stream stream() throws IOException { // OSS first diff --git a/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java b/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java index 551a68bec..cea74c04c 100644 --- a/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java +++ b/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java @@ -29,15 +29,20 @@ public interface CanRecordScreen extends ExecutesMethod { /** * Start asynchronous screen recording process. * + * @param The platform-specific {@link BaseStartScreenRecordingOptions} * @param options see the documentation on the {@link BaseStartScreenRecordingOptions} * descendant for the particular platform. + * @return `not used`. */ + @SuppressWarnings("rawtypes") default String startRecordingScreen(T options) { return CommandExecutionHelper.execute(this, startRecordingScreenCommand(options)); } /** * Start asynchronous screen recording process with default options. + * + * @return `not used`. */ default String startRecordingScreen() { return this.execute(START_RECORDING_SCREEN).getValue().toString(); @@ -46,11 +51,13 @@ default String startRecordingScreen() { /** * Gather the output from the previously started screen recording to a media file. * + * @param The platform-specific {@link BaseStopScreenRecordingOptions} * @param options see the documentation on the {@link BaseStopScreenRecordingOptions} * descendant for the particular platform. * @return Base-64 encoded content of the recorded media file or an empty string * if the file has been successfully uploaded to a remote location (depends on the actual options). */ + @SuppressWarnings("rawtypes") default String stopRecordingScreen(T options) { return CommandExecutionHelper.execute(this, stopRecordingScreenCommand(options)); } diff --git a/src/main/java/org/openqa/selenium/SearchContext.java b/src/main/java/org/openqa/selenium/SearchContext.java index 79501d6d8..d6498972e 100644 --- a/src/main/java/org/openqa/selenium/SearchContext.java +++ b/src/main/java/org/openqa/selenium/SearchContext.java @@ -23,19 +23,21 @@ public interface SearchContext { /** * Find all elements within the current context using the given mechanism. * + * @param The type of the resulting elements. * @param by The locating mechanism to use * @return A list of all {@link WebElement}s, or an empty list if nothing matches * @see org.openqa.selenium.By */ - List findElements(By by); + List findElements(By by); /** * Find the first {@link WebElement} using the given method. * + * @param The type of the resulting element. * @param by The locating mechanism * @return The first matching element on the current context * @throws NoSuchElementException If no matching elements are found */ - T findElement(By by); + T findElement(By by); } From d1f11293e8cb19a126b59f480174b9190826e4df Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 18:14:55 +0200 Subject: [PATCH 029/630] chore(deps): bump commons-io from 2.6 to 2.7 (#1357) Bumps commons-io from 2.6 to 2.7. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9bebd7def..a16b7b3e2 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ dependencies { compile 'cglib:cglib:3.3.0' compile 'commons-validator:commons-validator:1.6' compile 'org.apache.commons:commons-lang3:3.10' - compile 'commons-io:commons-io:2.6' + compile 'commons-io:commons-io:2.7' compile 'org.springframework:spring-context:5.2.6.RELEASE' compile 'org.aspectj:aspectjweaver:1.9.5' compile 'org.slf4j:slf4j-api:1.7.30' From ce942a6cf58bbd35804c196c5360c4ffa743541e Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 3 Jun 2020 12:14:01 +0200 Subject: [PATCH 030/630] docs: Update environment troubleshooting (#1358) --- docs/environment.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/environment.md b/docs/environment.md index fce3c0c53..1af2f306c 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -1,17 +1,35 @@ # Appium Environment Troubleshooting -Quite often there are questions about why Appium throws an error about missing environment variable, for example `ANDROID_HOME`, or about missing binaries, like `idevice_id`, `carthage` or `java`. This article explains what might be a cause of such problem and how to resolve it. +Quite often there are questions about why Appium throws an error about missing environment variable, for example `ANDROID_HOME`, or about missing binaries, like `idevice_id`, `carthage` or `java`. +This article explains what might be a cause of such problem and how to resolve it. ## Prerequisites -In order to understand this topic you should know concept of environment variables, how it works in your operating system and how you can change the system environment if needed. In particular, it is important to know the meaning of `PATH` environment variable. Read https://en.wikipedia.org/wiki/Environment_variable and https://en.wikipedia.org/wiki/PATH_(variable) for more details. +In order to understand this topic you should know concept of environment variables, how it works in your operating system and how you can change the system environment if needed. +In particular, it is important to know the meaning of `PATH` environment variable. Read https://en.wikipedia.org/wiki/Environment_variable and https://en.wikipedia.org/wiki/PATH_(variable) for more details. ## How To Verify What Is Missing -Appium itself is a NodeJS application and uses the same environment as its host `node` process. If you experience an error related to local environment setup then verify the actual process environment first. In Mac OS, for example, it is possible to do this via `ps eww ` command, where `PID` is the process identifier of the running Appium's host Node process. In Windows the [ProcessExplorer](https://docs.microsoft.com/sysinternals/downloads/process-explorer) utility can be used for such purpose. Then make sure the corresponding variable is there and it is set to a proper value, or, in case there is an error finding some binary, make sure the parent folder of this binary is present in `PATH` list, the binary itself on the local file system and can be executed manually. +Appium itself is a NodeJS application and uses the same environment as its host `node` process. If you experience an error related to local environment setup then verify the actual process environment first. +In Mac OS, for example, it is possible to do this via `ps eww ` command, where `PID` is the process identifier of the running Appium's host Node process. +In Windows the [ProcessExplorer](https://docs.microsoft.com/sysinternals/downloads/process-explorer) utility can be used for such purpose. +Then make sure the corresponding variable is there and it is set to a proper value, or, in case there is an error finding some binary, make sure the parent folder of this binary is present in `PATH` list, the binary itself on the local file system and can be executed manually. ## How To Fix Missing Environment Variables -Usually, if one starts Appium from the command line, then it inherits all the environment variables from the parent process (`bash`/`cmd`, etc.). This means that if you start Appium manually or with a script then make sure its parent process has all the necessary environemnt variables set to proper values. Also, it is possible to set variables on [per-process](https://stackoverflow.com/questions/10856129/setting-an-environment-variable-before-a-command-in-bash-not-working-for-second) basis. This might be handy if Appium is set up to start automatically with the operating system, because on early stages of system initialization it might be that the "usual" environment is not present yet. +Usually, if one starts Appium from the command line, then it inherits all the environment variables from the parent process (`bash`/`cmd`, etc.). +This means that if you start Appium manually or with a script then make sure its parent process has all the necessary environment variables set to proper values. +Use `env` command to check the currently defined environment variables of your *nix shell interpreter or `set` for cmd.exe shell in Windows. -In case the Appium process is started programatically, for example with java client's `AppiumDriverLocalService` helper class, then it might be necessary to setup the environment [in the client code](https://github.com/appium/java-client/pull/753), because prior to version 6.0 the client does not inherit it from the the parent process by default. +On *nix system you could add/edit environment variables of your shell either by using the `export` command (only works in scope of the current shell session) or by editing the appropriate shell config if it is necessary to keep the changes. +The path to this config depends on the currently selected interpreter. Use `echo $SHELL` to figure out what your current interpreter is. +Bash, for example, loads the config from `$HOME/.bashrc` or `$HOME/.bash_profile` and ZSH from `$HOME/.zshrc`. +Remember to reload the config after it has been changed either by sourcing it (e.g. `source ~/.zshrc`) or by restarting the terminal session. + +On Windows, you could use the `set` command to add/change environment variables in scope of the current shell session or edit them in [system settings](https://www.java.com/en/download/help/path.xml) to keep the changes. +Remember to reload the config after it has been changed by restarting the command prompt session. + +Also, it is possible to set variables on [per-process](https://stackoverflow.com/questions/10856129/setting-an-environment-variable-before-a-command-in-bash-not-working-for-second) basis. +This might be handy if Appium is set up to start automatically with the operating system, because on early stages of system initialization it is possible that the "usual" environment has not been loaded yet. + +In case the Appium process is started programatically, for example with java client's `AppiumDriverLocalService` helper class, then it might be necessary to setup the environment [in the client code](https://github.com/appium/java-client/pull/753), because prior to version 6.0 the client does not inherit it from the parent process by default. From b31a6d8da55d978ff9938cf89225d5cd6b0be079 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2020 19:47:18 +0530 Subject: [PATCH 031/630] chore(deps): bump spring-context from 5.2.6.RELEASE to 5.2.7.RELEASE (#1360) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a16b7b3e2..a26153854 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { compile 'commons-validator:commons-validator:1.6' compile 'org.apache.commons:commons-lang3:3.10' compile 'commons-io:commons-io:2.7' - compile 'org.springframework:spring-context:5.2.6.RELEASE' + compile 'org.springframework:spring-context:5.2.7.RELEASE' compile 'org.aspectj:aspectjweaver:1.9.5' compile 'org.slf4j:slf4j-api:1.7.30' From 9e59b56b119a3b9e9f42336b864de0db701b3b45 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 9 Jul 2020 13:10:15 +0200 Subject: [PATCH 032/630] fix: Parse platformName if it is passed as enum item (#1369) --- azure-pipelines.yml | 10 +++--- .../io/appium/java_client/AppiumDriver.java | 4 ++- .../internal/CapabilityHelpers.java | 10 ++++-- .../JsonToMobileElementConverter.java | 6 ++-- .../pagefactory/AppiumFieldDecorator.java | 32 ++++++++----------- .../appium/java_client/ios/IOSTouchTest.java | 25 ++------------- 6 files changed, 36 insertions(+), 51 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39ffdeb6b..f2d020e67 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,24 +4,24 @@ # https://docs.microsoft.com/azure/devops/pipelines/languages/java pool: - vmImage: 'macOS 10.14' + vmImage: 'macOS-10.15' variables: ANDROID_EMU_NAME: test ANDROID_EMU_ABI: x86 ANDROID_EMU_TARGET: android-27 ANDROID_EMU_TAG: google_apis - XCODE_VERSION: 10.2 - IOS_PLATFORM_VERSION: 12.2 + XCODE_VERSION: 11.5 + IOS_PLATFORM_VERSION: 13.5 IOS_DEVICE_NAME: iPhone X jobs: - job: E2E_Tests - timeoutInMinutes: 60 + timeoutInMinutes: '60' steps: - task: NodeTool@0 inputs: - versionSpec: '11.x' + versionSpec: '12.x' - script: | echo "Configuring Environment" diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 049171912..32785d1eb 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -40,6 +40,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.html5.Location; +import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.ErrorHandler; @@ -316,7 +317,8 @@ public URL getRemoteAddress() { @Override public boolean isBrowser() { - String browserName = CapabilityHelpers.getCapability(getCapabilities(), "browserName", String.class); + String browserName = CapabilityHelpers.getCapability(getCapabilities(), + CapabilityType.BROWSER_NAME, String.class); if (!isBlank(browserName)) { try { return (boolean) executeScript("return !!window.navigator;"); diff --git a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java index 8688eef13..b97da189c 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -43,8 +43,14 @@ public static T getCapability(Capabilities caps, String name, Class expec possibleNames.add(APPIUM_PREFIX + name); } for (String capName : possibleNames) { - if (caps.getCapability(capName) != null - && expectedType.isAssignableFrom(caps.getCapability(capName).getClass())) { + if (caps.getCapability(capName) == null) { + continue; + } + + if (expectedType == String.class) { + return expectedType.cast(String.valueOf(caps.getCapability(capName))); + } + if (expectedType.isAssignableFrom(caps.getCapability(capName).getClass())) { return expectedType.cast(caps.getCapability(capName)); } } diff --git a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java index 22cce7275..9f8674a90 100644 --- a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java +++ b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java @@ -18,8 +18,10 @@ import static io.appium.java_client.internal.ElementMap.getElementClass; +import io.appium.java_client.remote.MobileCapabilityType; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.remote.internal.JsonToWebElementConverter; @@ -46,8 +48,8 @@ public JsonToMobileElementConverter(RemoteWebDriver driver) { super(driver); this.driver = driver; Capabilities caps = driver.getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, "platformName", String.class); - this.automation = CapabilityHelpers.getCapability(caps, "automationName", String.class); + this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); + this.automation = CapabilityHelpers.getCapability(caps, MobileCapabilityType.AUTOMATION_NAME, String.class); } @Override diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java index 933795729..1d2b9caa0 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -29,13 +29,14 @@ import io.appium.java_client.ios.IOSElement; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.windows.WindowsElement; import org.openqa.selenium.Capabilities; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator; import org.openqa.selenium.support.pagefactory.ElementLocator; @@ -49,6 +50,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -88,8 +90,8 @@ public AppiumFieldDecorator(SearchContext context, Duration duration) { if (this.webDriver instanceof HasCapabilities) { Capabilities caps = ((HasCapabilities) this.webDriver).getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, "platformName", String.class); - this.automation = CapabilityHelpers.getCapability(caps, "automationName", String.class); + this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); + this.automation = CapabilityHelpers.getCapability(caps, MobileCapabilityType.AUTOMATION_NAME, String.class); } else { this.platform = null; this.automation = null; @@ -107,8 +109,7 @@ protected WebElement proxyForLocator(ClassLoader ignored, ElementLocator locator @Override @SuppressWarnings("unchecked") - protected List proxyForListLocator(ClassLoader ignored, - ElementLocator locator) { + protected List proxyForListLocator(ClassLoader ignored, ElementLocator locator) { ElementListInterceptor elementInterceptor = new ElementListInterceptor(locator); return getEnhancedProxy(ArrayList.class, elementInterceptor); } @@ -125,19 +126,12 @@ protected boolean isDecoratableList(Field field) { } Type listType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; + List bounds = (listType instanceof TypeVariable) + ? Arrays.asList(((TypeVariable) listType).getBounds()) + : Collections.emptyList(); - for (Class webElementClass : availableElementClasses) { - if (webElementClass.equals(listType)) { - return true; - } - } - - if ((listType instanceof TypeVariable) - && Arrays.asList(((TypeVariable) listType).getBounds()) - .stream().anyMatch(item -> availableElementClasses.contains(item))) { - return true; - } - return false; + return availableElementClasses.stream() + .anyMatch((webElClass) -> webElClass.equals(listType) || bounds.contains(webElClass)); } }; @@ -188,10 +182,10 @@ private Object decorateWidget(Field field) { } if (listType instanceof Class) { - if (!Widget.class.isAssignableFrom((Class) listType)) { + if (!Widget.class.isAssignableFrom((Class) listType)) { return null; } - widgetType = Class.class.cast(listType); + widgetType = (Class) listType; } else { return null; } diff --git a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java index 247e167d1..560fb2245 100644 --- a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java @@ -5,7 +5,6 @@ import static io.appium.java_client.touch.WaitOptions.waitOptions; import static io.appium.java_client.touch.offset.ElementOption.element; import static java.time.Duration.ofMillis; -import static java.time.Duration.ofSeconds; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; @@ -13,11 +12,9 @@ import io.appium.java_client.MobileElement; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; -import io.appium.java_client.touch.offset.ElementOption; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; -import org.openqa.selenium.Dimension; import org.openqa.selenium.support.ui.WebDriverWait; @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -33,7 +30,7 @@ public void tapTest() { intB.sendKeys("4"); MobileElement e = driver.findElementByAccessibilityId("ComputeSumButton"); - new TouchAction(driver).tap(tapOptions().withElement(element(e))).perform(); + new IOSTouchAction(driver).tap(tapOptions().withElement(element(e))).perform(); assertEquals(driver.findElementByXPath("//*[@name = \"Answer\"]").getText(), "6"); } @@ -57,28 +54,12 @@ public void touchWithPressureTest() { assertEquals(driver.findElementByXPath("//*[@name = \"Answer\"]").getText(), "6"); } - @Test public void swipeTest() { - WebDriverWait webDriverWait = new WebDriverWait(driver, 30); - IOSElement slider = webDriverWait.until(driver1 -> driver.findElementByClassName("XCUIElementTypeSlider")); - Dimension size = slider.getSize(); - - ElementOption press = element(slider, size.width / 2 + 2, size.height / 2); - ElementOption move = element(slider, 1, size.height / 2); - - TouchAction swipe = new TouchAction(driver).press(press) - .waitAction(waitOptions(ofSeconds(2))) - .moveTo(move).release(); - - swipe.perform(); - assertEquals("0%", slider.getAttribute("value")); - } - @Test public void multiTouchTest() { MobileElement e = driver.findElementByAccessibilityId("ComputeSumButton"); MobileElement e2 = driver.findElementByAccessibilityId("show alert"); - TouchAction tap1 = new TouchAction(driver).tap(tapOptions().withElement(element(e))); - TouchAction tap2 = new TouchAction(driver).tap(tapOptions().withElement(element(e2))); + IOSTouchAction tap1 = new IOSTouchAction(driver).tap(tapOptions().withElement(element(e))); + IOSTouchAction tap2 = new IOSTouchAction(driver).tap(tapOptions().withElement(element(e2))); new MultiTouchAction(driver).add(tap1).add(tap2).perform(); From 485cebe791e342876011a14619971c5f5f1861b4 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2020 21:55:19 +0530 Subject: [PATCH 033/630] chore(deps): bump webdrivermanager from 4.0.0 to 4.1.0 (#1374) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a26153854..20adf529b 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,7 @@ dependencies { testCompile 'junit:junit:4.13' testCompile 'org.hamcrest:hamcrest:2.2' - testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.0.0') { + testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.1.0') { exclude group: 'org.seleniumhq.selenium' } } From f27d29d6503780c4a8444764d59907b887ed04a9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2020 22:02:04 +0530 Subject: [PATCH 034/630] chore(deps): bump ecj from 3.21.0 to 3.22.0 (#1363) Bumps ecj from 3.21.0 to 3.22.0. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 20adf529b..d97816e3d 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.21.0' + ecj 'org.eclipse.jdt:ecj:3.22.0' lombok 'org.projectlombok:lombok:1.18.12' } From c33b577b0ede5bc6a956de5fabb27294268657ef Mon Sep 17 00:00:00 2001 From: jayandran-Sampath Date: Thu, 23 Jul 2020 21:22:13 +0530 Subject: [PATCH 035/630] feat: Support for Appium Chrome Dev Protocol Commands (#1375) * Client support for Appium : Chrome Dev Protocol * Review Changes - Client support for Appium : Chrome Dev Protocol * Review changes for documentation * CheckStyle Fix --- .../appium/java_client/ExecuteCDPCommand.java | 61 ++++++++++++ .../io/appium/java_client/MobileCommand.java | 3 + .../java_client/android/AndroidDriver.java | 3 +- .../android/ExecuteCDPCommandTest.java | 98 +++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/ExecuteCDPCommand.java create mode 100644 src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java diff --git a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java new file mode 100644 index 000000000..8cf09f357 --- /dev/null +++ b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java @@ -0,0 +1,61 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client; + +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.remote.Response; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.EXECUTE_GOOGLE_CDP_COMMAND; + +public interface ExecuteCDPCommand extends ExecutesMethod { + + /** + * Allows to execute ChromeDevProtocol commands against Android Chrome browser session. + * + * @param command Command to execute against the browser (For Ref : https://chromedevtools.github.io/devtools-protocol/) + * @param params additional parameters required to execute the command + * @return Value (Output of the command execution) + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the command + * @since Appium 1.18 + */ + default Map executeCdpCommand(String command, @Nullable Map params) { + Map data = new HashMap<>(); + data.put("cmd", checkNotNull(command)); + data.put("params", params == null ? Collections.emptyMap() : params); + Response response = execute(EXECUTE_GOOGLE_CDP_COMMAND, data); + //noinspection unchecked + return ImmutableMap.copyOf((Map) response.getValue()); + } + + /** + * Allows to execute ChromeDevProtocol commands against Android Chrome browser session without parameters. + * + * @param command Command to execute against the browser (For Ref : https://chromedevtools.github.io/devtools-protocol/) + * @return Value (Output of the command execution) + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the command + * @since Appium 1.18 + */ + default Map executeCdpCommand(String command) { + return executeCdpCommand(command, null); + } +} diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 58a3b6098..df50c962f 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -114,6 +114,7 @@ public class MobileCommand { protected static final String COMPARE_IMAGES; protected static final String EXECUTE_DRIVER_SCRIPT; protected static final String GET_ALLSESSION; + protected static final String EXECUTE_GOOGLE_CDP_COMMAND; public static final Map commandRepository; @@ -192,6 +193,7 @@ public class MobileCommand { COMPARE_IMAGES = "compareImages"; EXECUTE_DRIVER_SCRIPT = "executeDriverScript"; GET_ALLSESSION = "getAllSessions"; + EXECUTE_GOOGLE_CDP_COMMAND = "executeCdp"; commandRepository = new HashMap<>(); commandRepository.put(RESET, postC("/session/:sessionId/appium/app/reset")); @@ -282,6 +284,7 @@ public class MobileCommand { commandRepository.put(COMPARE_IMAGES, postC("/session/:sessionId/appium/compare_images")); commandRepository.put(EXECUTE_DRIVER_SCRIPT, postC("/session/:sessionId/appium/execute_driver")); commandRepository.put(GET_ALLSESSION, getC("/sessions")); + commandRepository.put(EXECUTE_GOOGLE_CDP_COMMAND, postC("/session/:sessionId/goog/cdp/execute")); } /** diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index 9fc18bd51..62016e814 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -25,6 +25,7 @@ import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecuteCDPCommand; import io.appium.java_client.FindsByAndroidDataMatcher; import io.appium.java_client.FindsByAndroidViewMatcher; import io.appium.java_client.FindsByAndroidUIAutomator; @@ -67,7 +68,7 @@ public class AndroidDriver HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard, CanRecordScreen, SupportsSpecialEmulatorCommands, SupportsNetworkStateManagement, ListensToLogcatMessages, HasAndroidClipboard, - HasBattery { + HasBattery, ExecuteCDPCommand { private static final String ANDROID_PLATFORM = MobilePlatform.ANDROID; diff --git a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java new file mode 100644 index 000000000..3637d0360 --- /dev/null +++ b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java @@ -0,0 +1,98 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android; + +import io.appium.java_client.pagefactory.AppiumFieldDecorator; +import io.appium.java_client.remote.MobileBrowserType; +import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import org.junit.After; +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.PageFactory; + +import java.util.HashMap; +import java.util.Map; + +import static java.time.Duration.ofSeconds; +import static org.junit.Assert.assertNotNull; + +public class ExecuteCDPCommandTest { + + private WebDriver driver; + + private AppiumDriverLocalService service; + + @FindBy(name = "q") + private WebElement searchTextField; + + + /** + * The setting up. + */ + @Before + public void setUp() { + service = AppiumDriverLocalService.buildDefaultService(); + service.start(); + + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); + capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, MobileBrowserType.CHROME); + capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2"); + driver = new AndroidDriver(service.getUrl(), capabilities); + //This time out is set because test can be run on slow Android SDK emulator + PageFactory.initElements(new AppiumFieldDecorator(driver, ofSeconds(5)), this); + } + + /** + * finishing. + */ + @After + public void tearDown() { + if (driver != null) { + driver.quit(); + } + + if (service != null) { + service.stop(); + } + } + + @Test + public void testExecuteCDPCommandWithoutParam() { + driver.get("https://www.google.com"); + searchTextField.sendKeys("Hello"); + Map cookies = ((AndroidDriver) driver).executeCdpCommand("Page.getCookies"); + assertNotNull(cookies); + } + + @Test + public void testExecuteCDPCommandWithParams() { + Map params = new HashMap(); + params.put("latitude", 13.0827); + params.put("longitude", 80.2707); + params.put("accuracy", 1); + ((AndroidDriver) driver).executeCdpCommand("Emulation.setGeolocationOverride", params); + driver.get("https://www.google.com"); + } + +} From 463cd695c09b6bd5960130d52d2ab31a4c64b6bf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2020 08:30:40 +0530 Subject: [PATCH 036/630] chore(deps): bump commons-lang3 from 3.10 to 3.11 (#1373) Bumps commons-lang3 from 3.10 to 3.11. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d97816e3d..17c31c855 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { compile 'org.apache.httpcomponents:httpclient:4.5.12' compile 'cglib:cglib:3.3.0' compile 'commons-validator:commons-validator:1.6' - compile 'org.apache.commons:commons-lang3:3.10' + compile 'org.apache.commons:commons-lang3:3.11' compile 'commons-io:commons-io:2.7' compile 'org.springframework:spring-context:5.2.7.RELEASE' compile 'org.aspectj:aspectjweaver:1.9.5' From 41fa9c742ca4e6944feef24f94754e403430f697 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2020 16:23:08 +0530 Subject: [PATCH 037/630] chore(deps): bump aspectjweaver from 1.9.5 to 1.9.6 (#1378) Bumps [aspectjweaver](https://github.com/eclipse/org.aspectj) from 1.9.5 to 1.9.6. - [Release notes](https://github.com/eclipse/org.aspectj/releases) - [Commits](https://github.com/eclipse/org.aspectj/commits) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 17c31c855..64fdbe8da 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { compile 'org.apache.commons:commons-lang3:3.11' compile 'commons-io:commons-io:2.7' compile 'org.springframework:spring-context:5.2.7.RELEASE' - compile 'org.aspectj:aspectjweaver:1.9.5' + compile 'org.aspectj:aspectjweaver:1.9.6' compile 'org.slf4j:slf4j-api:1.7.30' testCompile 'junit:junit:4.13' From b9be9aed1ffd77844736656c2a4024e0ed8c64c1 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2020 23:02:13 +0530 Subject: [PATCH 038/630] chore(deps): bump spring-context from 5.2.7.RELEASE to 5.2.8.RELEASE (#1377) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.2.7.RELEASE to 5.2.8.RELEASE. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.2.7.RELEASE...v5.2.8.RELEASE) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 64fdbe8da..163a04f37 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { compile 'commons-validator:commons-validator:1.6' compile 'org.apache.commons:commons-lang3:3.11' compile 'commons-io:commons-io:2.7' - compile 'org.springframework:spring-context:5.2.7.RELEASE' + compile 'org.springframework:spring-context:5.2.8.RELEASE' compile 'org.aspectj:aspectjweaver:1.9.6' compile 'org.slf4j:slf4j-api:1.7.30' From 19e3adb35d41b9c3701322b30609d81ae87a8ed9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2020 22:20:13 +0530 Subject: [PATCH 039/630] chore(deps): bump commons-validator from 1.6 to 1.7 (#1382) Bumps commons-validator from 1.6 to 1.7. Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 163a04f37..f053ad3a4 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ dependencies { compile 'com.google.code.gson:gson:2.8.6' compile 'org.apache.httpcomponents:httpclient:4.5.12' compile 'cglib:cglib:3.3.0' - compile 'commons-validator:commons-validator:1.6' + compile 'commons-validator:commons-validator:1.7' compile 'org.apache.commons:commons-lang3:3.11' compile 'commons-io:commons-io:2.7' compile 'org.springframework:spring-context:5.2.8.RELEASE' From 158f1835266cf007605bef9db30dfe9e51104791 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2020 16:05:52 +0530 Subject: [PATCH 040/630] chore(deps): bump webdrivermanager from 4.1.0 to 4.2.0 (#1384) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.1.0...webdrivermanager-4.2.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f053ad3a4..17c4aa11f 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,7 @@ dependencies { testCompile 'junit:junit:4.13' testCompile 'org.hamcrest:hamcrest:2.2' - testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.1.0') { + testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.0') { exclude group: 'org.seleniumhq.selenium' } } From b794c52de44c37e74f700d43c28a0f7c0f6c5410 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 6 Sep 2020 14:54:13 +0530 Subject: [PATCH 041/630] Update Dependabot config file (#1387) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- .dependabot/config.yml | 6 ------ .github/dependabot.yml | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 .dependabot/config.yml create mode 100644 .github/dependabot.yml diff --git a/.dependabot/config.yml b/.dependabot/config.yml deleted file mode 100644 index 7abbcde03..000000000 --- a/.dependabot/config.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 1 -update_configs: - - package_manager: "java:gradle" - directory: "/" - update_schedule: "weekly" - diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..bcf259eb9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: gradle + directory: "/" + schedule: + interval: weekly + time: "11:00" + open-pull-requests-limit: 10 From c8cee6ec6fca2872757c1c79ddb0fd65c34ad6ce Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 7 Sep 2020 17:56:16 +0530 Subject: [PATCH 042/630] build: update gradle and fix jitpack build failures (#1389) --- build.gradle | 82 +++++++++---------- .../service/local/ServerBuilderTest.java | 4 +- .../service/local/StartingAppLocallyTest.java | 2 +- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/build.gradle b/build.gradle index 17c4aa11f..f484b3cf4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,22 @@ -apply plugin: 'java' -apply plugin: 'idea' -apply plugin: 'maven-publish' -apply plugin: 'eclipse' -apply plugin: 'jacoco' -apply plugin: 'checkstyle' -apply plugin: 'signing' - import org.apache.tools.ant.filters.* +plugins { + id 'java' + id 'idea' + id 'maven-publish' + id 'eclipse' + id 'jacoco' + id 'checkstyle' + id 'signing' + id 'org.owasp.dependencycheck' version '5.3.2.1' + id 'com.github.johnrengelman.shadow' version '5.2.0' +} + repositories { jcenter() mavenCentral() } -buildscript { - repositories { - jcenter() - mavenCentral() - } - dependencies { - classpath 'org.owasp:dependency-check-gradle:5.3.2.1' - classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0' - } -} - -apply plugin: 'org.owasp.dependencycheck' -apply plugin: 'com.github.johnrengelman.shadow' - configurations { ecj lombok @@ -60,33 +50,39 @@ compileJava { dependencies { compileOnly('org.projectlombok:lombok:1.18.12') annotationProcessor('org.projectlombok:lombok:1.18.12') - compile ("org.seleniumhq.selenium:selenium-java:${project.property('selenium.version')}") { - force = true + implementation ("org.seleniumhq.selenium:selenium-java") { + version { + strictly "${project.property('selenium.version')}" + } exclude group: 'com.google.code.gson' exclude module: 'htmlunit-driver' exclude group: 'net.sourceforge.htmlunit' } - compile ("org.seleniumhq.selenium:selenium-support:${project.property('selenium.version')}") { - force = true + implementation ("org.seleniumhq.selenium:selenium-support") { + version { + strictly "${project.property('selenium.version')}" + } } - compile ("org.seleniumhq.selenium:selenium-api:${project.property('selenium.version')}") { - force = true + implementation ("org.seleniumhq.selenium:selenium-api") { + version { + strictly "${project.property('selenium.version')}" + } } - compile 'com.google.code.gson:gson:2.8.6' - compile 'org.apache.httpcomponents:httpclient:4.5.12' - compile 'cglib:cglib:3.3.0' - compile 'commons-validator:commons-validator:1.7' - compile 'org.apache.commons:commons-lang3:3.11' - compile 'commons-io:commons-io:2.7' - compile 'org.springframework:spring-context:5.2.8.RELEASE' - compile 'org.aspectj:aspectjweaver:1.9.6' - compile 'org.slf4j:slf4j-api:1.7.30' - - testCompile 'junit:junit:4.13' - testCompile 'org.hamcrest:hamcrest:2.2' - testCompile (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.0') { + implementation 'com.google.code.gson:gson:2.8.6' + implementation 'org.apache.httpcomponents:httpclient:4.5.12' + implementation 'cglib:cglib:3.3.0' + implementation 'commons-validator:commons-validator:1.7' + implementation 'org.apache.commons:commons-lang3:3.11' + implementation 'commons-io:commons-io:2.7' + implementation 'org.springframework:spring-context:5.2.8.RELEASE' + implementation 'org.aspectj:aspectjweaver:1.9.6' + implementation 'org.slf4j:slf4j-api:1.7.30' + + testImplementation 'junit:junit:4.13' + testImplementation 'org.hamcrest:hamcrest:2.2' + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -134,8 +130,6 @@ publishing { artifactId = 'java-client' version = '7.3.0' from components.java - artifact sourcesJar - artifact javadocJar pom { name = 'java-client' description = 'Java client for Appium Mobile Webdriver' @@ -199,7 +193,7 @@ signing { } wrapper { - gradleVersion = '5.4' + gradleVersion = '6.6.1' distributionType = Wrapper.DistributionType.ALL } diff --git a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java index 05a9109f9..48e4671b4 100644 --- a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java +++ b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java @@ -161,7 +161,7 @@ public void checkAbilityToStartServiceUsingCapabilities() { caps.setCapability(APP_PACKAGE, "io.appium.android.apis"); caps.setCapability(APP_ACTIVITY, ".view.WebView1"); caps.setCapability(APP, apiDemosApk().toAbsolutePath().toString()); - caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getBinaryPath()); + caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getDownloadedDriverPath()); service = new AppiumServiceBuilder().withCapabilities(caps).build(); service.start(); @@ -184,7 +184,7 @@ public void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { caps.setCapability("quotes", "\"'"); caps.setCapability("goog:chromeOptions", ImmutableMap.of("env", ImmutableMap.of("test", "value"), "val2", 0)); - caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getBinaryPath()); + caps.setCapability(CHROMEDRIVER_EXECUTABLE, chromeManager.getDownloadedDriverPath()); service = new AppiumServiceBuilder() .withArgument(CALLBACK_ADDRESS, testIP) diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java index 8cc855440..83dbeb3c7 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyTest.java @@ -91,7 +91,7 @@ public class StartingAppLocallyTest { WebDriverManager chromeManager = chromedriver(); chromeManager.setup(); serverCapabilities.setCapability(AndroidMobileCapabilityType.CHROMEDRIVER_EXECUTABLE, - chromeManager.getBinaryPath()); + chromeManager.getDownloadedDriverPath()); AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) From 32ea1a24cbe07fd942ffad637b5b09edbe918e7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Sep 2020 19:54:49 +0530 Subject: [PATCH 043/630] build(deps): bump org.owasp.dependencycheck from 5.3.2.1 to 6.0.1 (#1392) Bumps org.owasp.dependencycheck from 5.3.2.1 to 6.0.1. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f484b3cf4..a71860d02 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '5.3.2.1' + id 'org.owasp.dependencycheck' version '6.0.1' id 'com.github.johnrengelman.shadow' version '5.2.0' } From 8ff0352120ebcd5171037a40ddea190703d3fa49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2020 04:38:43 +0530 Subject: [PATCH 044/630] build(deps): bump com.github.johnrengelman.shadow from 5.2.0 to 6.0.0 (#1393) Bumps com.github.johnrengelman.shadow from 5.2.0 to 6.0.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a71860d02..89ef6a151 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'checkstyle' id 'signing' id 'org.owasp.dependencycheck' version '6.0.1' - id 'com.github.johnrengelman.shadow' version '5.2.0' + id 'com.github.johnrengelman.shadow' version '6.0.0' } repositories { From 84487d8f4adb31ce9d350c0db68082e36e21ed4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2020 18:38:35 +0530 Subject: [PATCH 045/630] build(deps): bump commons-io from 2.7 to 2.8.0 (#1391) Bumps commons-io from 2.7 to 2.8.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 89ef6a151..1ecddb3f9 100644 --- a/build.gradle +++ b/build.gradle @@ -75,7 +75,7 @@ dependencies { implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' - implementation 'commons-io:commons-io:2.7' + implementation 'commons-io:commons-io:2.8.0' implementation 'org.springframework:spring-context:5.2.8.RELEASE' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 1765a2d0d158b5399dc8f50b656d6dc0ed48fa89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Sep 2020 11:07:58 +0530 Subject: [PATCH 046/630] build(deps): bump spring-context from 5.2.8.RELEASE to 5.2.9.RELEASE (#1398) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1ecddb3f9..998a1385b 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.2.8.RELEASE' + implementation 'org.springframework:spring-context:5.2.9.RELEASE' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 625d61cd6d3adeee3e5367ca38eb3e00657821ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:43:41 +0530 Subject: [PATCH 047/630] build(deps): bump junit from 4.13 to 4.13.1 (#1405) Bumps [junit](https://github.com/junit-team/junit4) from 4.13 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.13...r4.13.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 998a1385b..98f361376 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.1' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.0') { exclude group: 'org.seleniumhq.selenium' From 30b73ee68da6c03349397c21a52f6d643faca329 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:44:10 +0530 Subject: [PATCH 048/630] build(deps): bump lombok from 1.18.12 to 1.18.14 (#1404) Bumps [lombok](https://github.com/rzwitserloot/lombok) from 1.18.12 to 1.18.14. - [Release notes](https://github.com/rzwitserloot/lombok/releases) - [Changelog](https://github.com/rzwitserloot/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/rzwitserloot/lombok/compare/v1.18.12...v1.18.14) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 98f361376..e39675be3 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ configurations { dependencies { ecj 'org.eclipse.jdt:ecj:3.22.0' - lombok 'org.projectlombok:lombok:1.18.12' + lombok 'org.projectlombok:lombok:1.18.14' } java { From 28d4d046be0db68c0f66c0dc7c9f143ed68d2844 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:44:37 +0530 Subject: [PATCH 049/630] build(deps): bump httpclient from 4.5.12 to 4.5.13 (#1403) Bumps httpclient from 4.5.12 to 4.5.13. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e39675be3..d030636fc 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ dependencies { } } implementation 'com.google.code.gson:gson:2.8.6' - implementation 'org.apache.httpcomponents:httpclient:4.5.12' + implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' From bcca20276191f98d9293974b6430cb97cb5e465c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:45:05 +0530 Subject: [PATCH 050/630] build(deps): bump com.github.johnrengelman.shadow from 6.0.0 to 6.1.0 (#1402) Bumps com.github.johnrengelman.shadow from 6.0.0 to 6.1.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d030636fc..c85687bd2 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'checkstyle' id 'signing' id 'org.owasp.dependencycheck' version '6.0.1' - id 'com.github.johnrengelman.shadow' version '6.0.0' + id 'com.github.johnrengelman.shadow' version '6.1.0' } repositories { From febf316166261ff39bd7d7183b3c57a767762a40 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 10:01:16 +0530 Subject: [PATCH 051/630] build(deps): bump org.owasp.dependencycheck from 6.0.1 to 6.0.2 (#1399) Bumps org.owasp.dependencycheck from 6.0.1 to 6.0.2. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c85687bd2..4a8b814bd 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.0.1' + id 'org.owasp.dependencycheck' version '6.0.2' id 'com.github.johnrengelman.shadow' version '6.1.0' } From cf0d5d8d1a5a83fa0840111fb8e4d2f822d93b7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 10:01:37 +0530 Subject: [PATCH 052/630] build(deps): bump ecj from 3.22.0 to 3.23.0 (#1397) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4a8b814bd..00408e324 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.22.0' + ecj 'org.eclipse.jdt:ecj:3.23.0' lombok 'org.projectlombok:lombok:1.18.14' } From 5999cebe19e564e4568a998aa16776762444d663 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2020 12:41:37 +0530 Subject: [PATCH 053/630] build(deps): bump spring-context from 5.2.9.RELEASE to 5.3.0 (#1407) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.2.9.RELEASE to 5.3.0. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.2.9.RELEASE...v5.3.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 00408e324..6b5ad223e 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.2.9.RELEASE' + implementation 'org.springframework:spring-context:5.3.0' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From c4060efb9d549f8ae755b9351166576e2d22d2e6 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 9 Nov 2020 20:10:23 +0300 Subject: [PATCH 054/630] feat: Add ability to set multiple settings (#1409) --- .../io/appium/java_client/HasSettings.java | 28 +++++++++++++++++++ .../io/appium/java_client/MobileCommand.java | 7 +++-- .../java_client/android/SettingTest.java | 21 ++++++++++++++ .../appium/java_client/ios/SettingTest.java | 27 +++++++++++++++--- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/appium/java_client/HasSettings.java b/src/main/java/io/appium/java_client/HasSettings.java index 2d6045846..8210123a7 100644 --- a/src/main/java/io/appium/java_client/HasSettings.java +++ b/src/main/java/io/appium/java_client/HasSettings.java @@ -21,7 +21,10 @@ import org.openqa.selenium.remote.Response; +import java.util.EnumMap; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; public interface HasSettings extends ExecutesMethod { @@ -52,6 +55,31 @@ default HasSettings setSetting(String settingName, Object value) { return this; } + /** + * Sets settings for this test session. + * + * @param settings a map with settings, where key is the setting name you wish to set and value is the value of + * the setting. + * @return Self instance for chaining. + */ + default HasSettings setSettings(EnumMap settings) { + Map convertedSettings = settings.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey().toString(), Entry::getValue)); + return setSettings(convertedSettings); + } + + /** + * Sets settings for this test session. + * + * @param settings a map with settings, where key is the setting name you wish to set and value is the value of + * the setting. + * @return Self instance for chaining. + */ + default HasSettings setSettings(Map settings) { + CommandExecutionHelper.execute(this, setSettingsCommand(settings)); + return this; + } + /** * Get settings stored for this test session It's probably better to use a * convenience function, rather than use this function directly. Try finding diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index df50c962f..f601aaab7 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -481,8 +481,11 @@ public static ImmutableMap prepareArguments(String[] params, } public static Map.Entry> setSettingsCommand(String setting, Object value) { - return new AbstractMap.SimpleEntry<>(SET_SETTINGS, prepareArguments("settings", - prepareArguments(setting, value))); + return setSettingsCommand(prepareArguments(setting, value)); + } + + public static Map.Entry> setSettingsCommand(Map settings) { + return new AbstractMap.SimpleEntry<>(SET_SETTINGS, prepareArguments("settings", settings)); } /** diff --git a/src/test/java/io/appium/java_client/android/SettingTest.java b/src/test/java/io/appium/java_client/android/SettingTest.java index 559c1ba69..52963e566 100644 --- a/src/test/java/io/appium/java_client/android/SettingTest.java +++ b/src/test/java/io/appium/java_client/android/SettingTest.java @@ -4,6 +4,9 @@ import org.junit.Test; import java.time.Duration; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; import static org.junit.Assert.assertEquals; @@ -103,6 +106,24 @@ public class SettingTest extends BaseAndroidTest { .get("shouldUseCompactResponses")); } + @Test public void setMultipleSettings() { + EnumMap enumSettings = new EnumMap<>(Setting.class); + enumSettings.put(Setting.IGNORE_UNIMPORTANT_VIEWS, true); + enumSettings.put(Setting.ELEMENT_RESPONSE_ATTRIBUTES, "type,label"); + driver.setSettings(enumSettings); + Map actual = driver.getSettings(); + assertEquals(true, actual.get(Setting.IGNORE_UNIMPORTANT_VIEWS.toString())); + assertEquals("type,label", actual.get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); + + Map mapSettings = new HashMap<>(); + mapSettings.put(Setting.IGNORE_UNIMPORTANT_VIEWS.toString(), false); + mapSettings.put(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString(), ""); + driver.setSettings(mapSettings); + actual = driver.getSettings(); + assertEquals(false, actual.get(Setting.IGNORE_UNIMPORTANT_VIEWS.toString())); + assertEquals("", actual.get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); + } + private void assertJSONElementContains(Setting setting, long value) { assertEquals(driver.getSettings().get(setting.toString()), value); } diff --git a/src/test/java/io/appium/java_client/ios/SettingTest.java b/src/test/java/io/appium/java_client/ios/SettingTest.java index d48d2c64d..9d0fbcf80 100644 --- a/src/test/java/io/appium/java_client/ios/SettingTest.java +++ b/src/test/java/io/appium/java_client/ios/SettingTest.java @@ -22,6 +22,9 @@ import static org.junit.Assert.assertEquals; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; public class SettingTest extends AppIOSTest { @@ -34,10 +37,10 @@ public class SettingTest extends AppIOSTest { } @Test public void testSetElementResponseAttributes() { - assertEquals("type,label", driver.getSettings() + assertEquals("", driver.getSettings() .get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); - driver.setElementResponseAttributes("name"); - assertEquals("name", driver.getSettings() + driver.setElementResponseAttributes("type,label"); + assertEquals("type,label", driver.getSettings() .get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); } @@ -94,5 +97,21 @@ public class SettingTest extends AppIOSTest { .get("shouldUseCompactResponses")); } - + @Test public void setMultipleSettings() { + EnumMap enumSettings = new EnumMap<>(Setting.class); + enumSettings.put(Setting.IGNORE_UNIMPORTANT_VIEWS, true); + enumSettings.put(Setting.ELEMENT_RESPONSE_ATTRIBUTES, "type,label"); + driver.setSettings(enumSettings); + Map actual = driver.getSettings(); + assertEquals(true, actual.get(Setting.IGNORE_UNIMPORTANT_VIEWS.toString())); + assertEquals("type,label", actual.get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); + + Map mapSettings = new HashMap<>(); + mapSettings.put(Setting.IGNORE_UNIMPORTANT_VIEWS.toString(), false); + mapSettings.put(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString(), ""); + driver.setSettings(mapSettings); + actual = driver.getSettings(); + assertEquals(false, actual.get(Setting.IGNORE_UNIMPORTANT_VIEWS.toString())); + assertEquals("", actual.get(Setting.ELEMENT_RESPONSE_ATTRIBUTES.toString())); + } } From 81e0f21754ee534ee7d94c1a72ee5f2307c030d4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 10 Nov 2020 10:50:47 +0100 Subject: [PATCH 055/630] chore: Update unstable tests (#1410) --- .azure-templates/bootstrap_steps.yml | 10 ++++ azure-pipelines.yml | 53 ++++++++++--------- build.gradle | 2 +- .../java/io/appium/java_client/TestUtils.java | 30 +++++++++++ .../java_client/android/BaseAndroidTest.java | 1 - .../appium/java_client/ios/IOSDriverTest.java | 43 ++++++--------- 6 files changed, 86 insertions(+), 53 deletions(-) create mode 100644 .azure-templates/bootstrap_steps.yml diff --git a/.azure-templates/bootstrap_steps.yml b/.azure-templates/bootstrap_steps.yml new file mode 100644 index 000000000..9f4e3032b --- /dev/null +++ b/.azure-templates/bootstrap_steps.yml @@ -0,0 +1,10 @@ +steps: + - task: NodeTool@0 + inputs: + versionSpec: "$(NODE_VERSION)" + - script: | + npm config delete prefix + npm config set prefix $NVM_DIR/versions/node/`node --version` + node --version + + npm install -g appium@beta diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f2d020e67..91256cf5c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,52 +9,55 @@ pool: variables: ANDROID_EMU_NAME: test ANDROID_EMU_ABI: x86 - ANDROID_EMU_TARGET: android-27 - ANDROID_EMU_TAG: google_apis + ANDROID_EMU_TARGET: android-28 + ANDROID_EMU_TAG: default XCODE_VERSION: 11.5 IOS_PLATFORM_VERSION: 13.5 IOS_DEVICE_NAME: iPhone X + NODE_VERSION: 12.x + JDK_VERSION: 1.8 jobs: -- job: E2E_Tests - timeoutInMinutes: '60' +- job: Android_E2E_Tests +# timeoutInMinutes: '90' steps: - - task: NodeTool@0 - inputs: - versionSpec: '12.x' - + - template: .azure-templates/bootstrap_steps.yml - script: | - echo "Configuring Environment" echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "$(ANDROID_EMU_NAME)" -k 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' --force echo $ANDROID_HOME/emulator/emulator -list-avds echo "Starting emulator" - nohup $ANDROID_HOME/emulator/emulator -avd "$(ANDROID_EMU_NAME)" -no-snapshot > /dev/null 2>&1 & + nohup $ANDROID_HOME/emulator/emulator -avd "$(ANDROID_EMU_NAME)" -no-snapshot -delay-adb > /dev/null 2>&1 & $ANDROID_HOME/platform-tools/adb wait-for-device - while [[ $? -ne 0 ]]; do sleep 1; $ANDROID_HOME/platform-tools/adb shell pm list packages; done; - $ANDROID_HOME/platform-tools/adb devices + $ANDROID_HOME/platform-tools/adb devices -l echo "Emulator started" - + displayName: Emulator configuration + - task: Gradle@2 + inputs: + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: "$(JDK_VERSION)" + jdkArchitectureOption: 'x64' + publishJUnitResults: true + tasks: 'build' + options: 'uiAutomationTest -x checkstyleTest -x test -x signMavenJavaPublication' +- job: iOS_E2E_Tests +# timeoutInMinutes: '90' + steps: + - template: .azure-templates/bootstrap_steps.yml + - script: | sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer xcrun simctl list - - npm config delete prefix - npm config set prefix $NVM_DIR/versions/node/`node --version` - node --version - - npm install -g appium@beta - appium --version - - java -version - + displayName: Simulator configuration - task: Gradle@2 inputs: gradleWrapperFile: 'gradlew' gradleOptions: '-Xmx3072m' javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.8' + jdkVersionOption: "$(JDK_VERSION)" jdkArchitectureOption: 'x64' publishJUnitResults: true tasks: 'build' - options: 'xcuiTest uiAutomationTest -x checkstyleTest -x test -x signMavenJavaPublication' + options: 'xcuiTest -x checkstyleTest -x test -x signMavenJavaPublication' diff --git a/build.gradle b/build.gradle index 6b5ad223e..7405b25a5 100644 --- a/build.gradle +++ b/build.gradle @@ -227,7 +227,7 @@ task uiAutomationTest( type: Test ) { testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' filter { - includeTestsMatching '*.SettingTest' + includeTestsMatching 'io.appium.java_client.android.SettingTest' includeTestsMatching 'io.appium.java_client.android.ClipboardTest' includeTestsMatching '*.AndroidAppStringsTest' } diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index 4195d4ef9..c0b55e5f0 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -1,5 +1,7 @@ package io.appium.java_client; +import org.openqa.selenium.TimeoutException; + import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; @@ -9,6 +11,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; +import java.util.function.Supplier; public class TestUtils { public static String getLocalIp4Address() throws SocketException, UnknownHostException { @@ -34,4 +38,30 @@ public static String resourceAsString(String resourcePath) { throw new RuntimeException(e); } } + + public static void waitUntilTrue(Supplier func, Duration timeout, Duration interval) { + long started = System.currentTimeMillis(); + RuntimeException lastError = null; + while (System.currentTimeMillis() - started < timeout.toMillis()) { + lastError = null; + try { + Boolean result = func.get(); + if (result != null && result) { + return; + } + //noinspection BusyWait + Thread.sleep(interval.toMillis()); + } catch (RuntimeException | InterruptedException e) { + if (e instanceof InterruptedException) { + throw new RuntimeException(e); + } else { + lastError = (RuntimeException) e; + } + } + } + if (lastError != null) { + throw lastError; + } + throw new TimeoutException(String.format("Condition unmet after %sms timeout", timeout.toMillis())); + } } diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java index 1e12834d1..3ebaa5dbf 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -39,7 +39,6 @@ public class BaseAndroidTest { @BeforeClass public static void beforeClass() { service = AppiumDriverLocalService.buildDefaultService(); service.start(); - if (service == null || !service.isRunning()) { throw new AppiumServerHasNotBeenStartedLocallyException( "An appium server node is not started!"); diff --git a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java index 995ac4c58..40ecb5a9b 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -16,22 +16,18 @@ package io.appium.java_client.ios; -import static org.hamcrest.Matchers.empty; +import static io.appium.java_client.TestUtils.waitUntilTrue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import io.appium.java_client.MobileElement; import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.remote.HideKeyboardStrategy; -import io.appium.java_client.remote.MobileCapabilityType; import org.junit.Ignore; import org.junit.Test; @@ -90,8 +86,8 @@ public void getDeviceTimeTest() { } @Test public void pullFileTest() { - byte[] data = driver.pullFile("@io.appium.TestApp/TestApp"); - assert (data.length > 0); + byte[] data = driver.pullFile(String.format("@%s/TestApp", BUNDLE_ID)); + assertThat(data.length, greaterThan(0)); } @Test public void keyboardTest() { @@ -106,31 +102,25 @@ public void getDeviceTimeTest() { assertThat(System.currentTimeMillis() - msStarted, greaterThan(3000L)); } - @Test public void applicationsManagementTest() throws InterruptedException { - // This only works since Xcode9 - try { - if (Double.parseDouble( - (String) driver.getCapabilities() - .getCapability(MobileCapabilityType.PLATFORM_VERSION)) < 11) { - return; - } - } catch (NumberFormatException | NullPointerException e) { - return; - } + @Test public void applicationsManagementTest() { assertThat(driver.queryAppState(BUNDLE_ID), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); - Thread.sleep(500); driver.runAppInBackground(Duration.ofSeconds(-1)); - assertThat(driver.queryAppState(BUNDLE_ID), lessThan(ApplicationState.RUNNING_IN_FOREGROUND)); - Thread.sleep(500); + waitUntilTrue( + () -> driver.queryAppState(BUNDLE_ID).ordinal() < ApplicationState.RUNNING_IN_FOREGROUND.ordinal(), + Duration.ofSeconds(10), Duration.ofSeconds(1)); driver.activateApp(BUNDLE_ID); - assertThat(driver.queryAppState(BUNDLE_ID), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); + waitUntilTrue( + () -> driver.queryAppState(BUNDLE_ID) == ApplicationState.RUNNING_IN_FOREGROUND, + Duration.ofSeconds(10), Duration.ofSeconds(1)); } @Test public void putAIntoBackgroundWithoutRestoreTest() { - assertThat(driver.findElementsById("IntegerA"), is(not(empty()))); + waitUntilTrue(() -> !driver.findElementsById("IntegerA").isEmpty(), + Duration.ofSeconds(10), Duration.ofSeconds(1)); driver.runAppInBackground(Duration.ofSeconds(-1)); - assertThat(driver.findElementsById("IntegerA"), is(empty())); - driver.launchApp(); + waitUntilTrue(() -> driver.findElementsById("IntegerA").isEmpty(), + Duration.ofSeconds(10), Duration.ofSeconds(1)); + driver.activateApp(BUNDLE_ID); } @Ignore @@ -138,6 +128,7 @@ public void getDeviceTimeTest() { driver.toggleTouchIDEnrollment(true); driver.performTouchID(true); driver.performTouchID(false); + //noinspection SimplifiableAssertion assertEquals(true, true); } } From ce44fc51d6c1418407f375df94a7a6d40d3805b0 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Tue, 17 Nov 2020 15:15:41 +0530 Subject: [PATCH 056/630] Release 7.4.0 and update release notes --- README.md | 32 ++++++++++++++++++++++++++++++++ build.gradle | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15900ab6e..c1bc98cd8 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,38 @@ dependencies { ``` ## Changelog +*7.4.0* +- **[ENHANCEMENTS]** + - Add ability to set multiple settings. [#1409](https://github.com/appium/java-client/pull/1409) + - Support to execute Chrome DevTools Protocol commands against Android Chrome browser session. [#1375](https://github.com/appium/java-client/pull/1375) + - Add new upload options i.e withHeaders, withFormFields and withFileFieldName. [#1342](https://github.com/appium/java-client/pull/1342) + - Add AndroidOptions and iOSOptions. [#1331](https://github.com/appium/java-client/pull/1331) + - Add idempotency key to session creation requests. [#1327](https://github.com/appium/java-client/pull/1327) + - Add support for Android capability types: `buildToolsVersion`, `enforceAppInstall`, `ensureWebviewsHavePages`, `webviewDevtoolsPort`, and `remoteAppsCacheLimit`. [#1326](https://github.com/appium/java-client/pull/1326) + - Added OTHER_APPS and PRINT_PAGE_SOURCE_ON_FIND_FAILURE Mobile Capability Types. [#1323](https://github.com/appium/java-client/pull/1323) + - Make settings available for all AppiumDriver instances. [#1318](https://github.com/appium/java-client/pull/1318) + - Add wrappers for the Windows screen recorder. [#1313](https://github.com/appium/java-client/pull/1313) + - Add GitHub Action validating Gradle wrapper. [#1296](https://github.com/appium/java-client/pull/1296) + - Add support for Android viewmatcher. [#1293](https://github.com/appium/java-client/pull/1293) + - Update web view detection algorithm for iOS tests. [#1294](https://github.com/appium/java-client/pull/1294) + - Add allow-insecure and deny-insecure server flags. [#1282](https://github.com/appium/java-client/pull/1282) +- **[BUG FIX]** + - Fix jitpack build failures. [#1389](https://github.com/appium/java-client/pull/1389) + - Fix parse platformName if it is passed as enum item. [#1369](https://github.com/appium/java-client/pull/1369) + - Increase the timeout for graceful AppiumDriverLocalService termination. [#1354](https://github.com/appium/java-client/pull/1354) + - Avoid casting to RemoteWebElement in ElementOptions. [#1345](https://github.com/appium/java-client/pull/1345) + - Properly translate desiredCapabilities into a command line argument. [#1337](https://github.com/appium/java-client/pull/1337) + - Change getDeviceTime to call the `mobile` implementation. [#1332](https://github.com/appium/java-client/pull/1332) + - Remove appiumVersion from MobileCapabilityType. [#1325](https://github.com/appium/java-client/pull/1325) + - Set appropriate fluent wait timeouts. [#1316](https://github.com/appium/java-client/pull/1316) +- **[DOCUMENTATION UPDATES]** + - Update Appium Environment Troubleshooting. [#1358](https://github.com/appium/java-client/pull/1358) + - Address warnings printed by docs linter. [#1355](https://github.com/appium/java-client/pull/1355) + - Add java docs for various Mobile Options. [#1331](https://github.com/appium/java-client/pull/1331) + - Add AndroidFindBy, iOSXCUITFindBy and WindowsFindBy docs. [#1311](https://github.com/appium/java-client/pull/1311) + - Renamed maim.js to main.js. [#1277](https://github.com/appium/java-client/pull/1277) + - Improve Readability of Issue Template. [#1260](https://github.com/appium/java-client/pull/1260) + *7.3.0* - **[ENHANCEMENTS]** - Add support for logging custom events on the Appium Server. [#1262](https://github.com/appium/java-client/pull/1262) diff --git a/build.gradle b/build.gradle index 7405b25a5..f0ef66b56 100644 --- a/build.gradle +++ b/build.gradle @@ -128,7 +128,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '7.3.0' + version = '7.4.0' from components.java pom { name = 'java-client' From 7d27bdf74098077ef63580478bd2091af541f295 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Nov 2020 12:25:36 +0530 Subject: [PATCH 057/630] build(deps): bump spring-context from 5.3.0 to 5.3.1 (#1413) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.0 to 5.3.1. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.0...v5.3.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f0ef66b56..df8bf50fb 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.0' + implementation 'org.springframework:spring-context:5.3.1' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 0c96287e721c412a515f435d4622387ab8018c09 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sun, 22 Nov 2020 09:56:03 +0300 Subject: [PATCH 058/630] chore: upgrade to Gradle 6.7.1 (#1414) --- build.gradle | 5 ++++- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 ++ gradlew.bat | 25 +++++++---------------- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/build.gradle b/build.gradle index df8bf50fb..34f890170 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ compileJava { // https://www.ibm.com/support/knowledgecenter/SS8PJ7_9.7.0/org.eclipse.jdt.doc.user/tasks/task-using_batch_compiler.htm '-warn:-unused,-unchecked,-raw,-serial,-suppress', ] + + // https://github.com/gradle/gradle/issues/12904 + options.headerOutputDirectory.convention(null) } dependencies { @@ -193,7 +196,7 @@ signing { } wrapper { - gradleVersion = '6.6.1' + gradleVersion = '6.7.1' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 12842 zcmY+q1ymhDmoSOmQr324hm`&0SZbS3R18r37J8Z0F-T>@gIK` zatajf2b1lO#&E_RnGDF51uk%8Wcxm3`%Yhe7Psx?*?eZ?8M9_+G(?L=s^OG1n#S(NP3gF?2Mr2^f5E7sM~iVC`Rn;(-^MZ zZu*ZXB;XmgvPls(e#)MMTObsEx9oNz-K?AmQ8pP&P7vqx*=5zxjU+ye_1R<%KSg1? z7H&Yh))(Ke!Pa+aVuWxPKa_~Qo_IH}*;tV8n~O*Xa?t3P^9=L%=wOL1=~{LVv}mU8Q#e6s>v}iV8cDP|EdY)`dp≶7^21 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

XiM^r_M1uFTw)(UPqqUJ zed7DJQvOpplYJoNl`A3a2Zr~v)Y}K|*%d7?8;dC*Af}0=(EXrk7hP8PM4v)ZawEv0 z5j5q_6vilv4*ppZ3Wke7%8b`odJu26#YseA?$sP8?@d?TpACTX`G^?%K=Hly9Y$Tj z5`i!}-WH0lQ3yt-&Pf&Z)OxjKfAZ3X`YI2)5o@9EiOA}?kX`^rk;yaOh^Li4VHcT& zd6ztJz^`H!8>cL)a%?Lnofzo0$WPrgkNcP#u@{%~EvA7g#go z+Q4$Q^o_MJTE0G_&9@cgp)WF>2zo>AG}C|(~E^_?ijc2EQ>> zFH1Lxc@i!gDHxvEsrfc+Kiz3Q6Z*NM+U6DH6*-Fv{YDY4>U&eegGH~Xmk~!sd6fw2 z!6^4(MZWhzf(xs6BHy=oS+dir0_JW(j*AIu$9&&p@}T;&6s7(yYidEdkp6pk9}Z(F zk6lG`d!HcJWP{7X)&P5FW;XWU6;zke2e+g*#1kW4L0Auj5pVA45Bhzt{8lj)XyMHu z0p;Q}t*NLnXbGQOTC9WdOQsX;_yLThOP$mzcEbqSBifmDecV;Fqhtm#KxfJTMhY!K zw{1L9za2QtuIWXu0ZYm@Uy72(9^vYajuP$(VAKn+&Jp z!%S;#BqO;02)n~6pFYK8oRC6wXx@kyb?VENM7l?NFg)%^=q4aZ* zwVCZxA8lalF&n`vk#gxD@!9~Aktc+h2UUUax3q2XKQLuL@CpVEpx2IyiH3bALY+Oi zDydtacE1Z|$z5qsCG=%F{}8_ozwieWuM4VcDesuu+wX&YjHm^Ryx)pVtiSLpch7<` zIw9QM%I;(VG9~#Rw2JNt+;?_=O2lA#YSudpgsM`e&(}1 zGdj7U)St4`+Cj#Vn>a;Bj~Q3S1G54;gzq)`b_Hh~ip(`BK#GMkI*RUcK+6ycjl?QN zP%sH5*R$$6A&8j8O4iFJhuHMXLJm|drH+=!_{`60&+tC8*m3Z=YsR@~%eaZyWQI|W zie7;BRL6`LR+4B{%FY~;-^*1GGTCLp!U6VoS36GUAWv$Rcc#|a7DD01{QPB=VyT)? z+1ZV>P=bP1;>v-+j0;BwenO`EKcIbmB3$r*@a~=?5#KB_R$3bTg zOjEutZmWuylIJQEM?28D!4uA#0CZEJHar0TFAW|NwP;WL|EIb_L2>;}e*N!K9E61S zH&u@m!n#CH{C_j}{#ybCRU8z7`Cs{b>@bxSkp3lOm`qAYgB>npnw%V>w}t_+S_Z)s zFhGKq^e%m@=^@sD0CJdAK=`Upbr}?}N zf;snqpt#dOwhxFg82(%Tw=ND+@`O0JGeOWd7-3R8A%Yu%FhiaYXD>p?t2+o%_1CKE z{g(>=g%}X(O%M!}KZK%$7-F<34wD-24`y$WLDv6z?ty=_b)Oi*x&?v}3j0f`AV3HL zWPAYw!XNs-1EmZ9=d=$6LAJISVJ4&gQM5=bh{!f0OmFNz8oMnGgOl_RK5VPP3@87C zpLS$muCo5YTmOslSjFLb`AXIJ@1|O{pm5r z9MxUb-8HMe*|TpFa%dE?+7}MVQ-A-N8wvtq85ROU2%KYt4etC52X0%W08haQgGSU| zvw=K$djTYSLy@4My_Te_8JcZp8Oozg{-ey>*Nm7BkGrX(M~HU6N16U>DSj<`;cy`u zR?0B23zx|*o1V=#fLZ=wd6*NfWjC`pqA^mt>DTZjGBeX`0ZK>Qgxz+37Dyb#NT4WT zl@7Lmh{WdY*h%e_bo6(oXKw=`(9=mft10fOR4< zCOUungtJZPAM~qw4Ydy9PhvYTi1ZRqR5nY)?OX2O3FJ6VXy&`&H%^U|d>N>@f zQ}Cd;DV}_bNiVTW8HcUJDRvXCUwGx|(r2-KbXY>jpd!~jmt(

p*eWM!w;MI3hb2}DlKZV3$r+FsPh`1u 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 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 0e023aa780b94f205417cc8683ccd5f31ef91453 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 30 Nov 2020 11:17:39 +0300 Subject: [PATCH 059/630] Fix the configuration of `selenium-java` dependency (#1417) https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 34f890170..a684b81dd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import org.apache.tools.ant.filters.* plugins { - id 'java' + id 'java-library' id 'idea' id 'maven-publish' id 'eclipse' @@ -53,7 +53,7 @@ compileJava { dependencies { compileOnly('org.projectlombok:lombok:1.18.12') annotationProcessor('org.projectlombok:lombok:1.18.12') - implementation ("org.seleniumhq.selenium:selenium-java") { + api ("org.seleniumhq.selenium:selenium-java") { version { strictly "${project.property('selenium.version')}" } From 57287df6500eddd20f03e85b94368f53d8d7a6d2 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 30 Nov 2020 16:25:13 +0530 Subject: [PATCH 060/630] Release 7.4.1 and update release notes --- README.md | 7 +++++++ build.gradle | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1bc98cd8..9b242ba3f 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,13 @@ dependencies { ``` ## Changelog +*7.4.1* +- **[BUG FIX]** + - Fix the configuration of `selenium-java` dependency. [#1417](https://github.com/appium/java-client/pull/1417) +- **[DEPENDENCY UPDATES]** + - `gradle` was updated to 6.7.1. + + *7.4.0* - **[ENHANCEMENTS]** - Add ability to set multiple settings. [#1409](https://github.com/appium/java-client/pull/1409) diff --git a/build.gradle b/build.gradle index a684b81dd..d141fc955 100644 --- a/build.gradle +++ b/build.gradle @@ -131,7 +131,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '7.4.0' + version = '7.4.1' from components.java pom { name = 'java-client' From 458cd69bfc90bb3886de24dba667c394807497a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Dec 2020 10:29:03 +0530 Subject: [PATCH 061/630] build(deps): bump lombok from 1.18.14 to 1.18.16 (#1406) Bumps [lombok](https://github.com/rzwitserloot/lombok) from 1.18.14 to 1.18.16. - [Release notes](https://github.com/rzwitserloot/lombok/releases) - [Changelog](https://github.com/rzwitserloot/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/rzwitserloot/lombok/compare/v1.18.14...v1.18.16) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index d141fc955..56b71ad16 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ configurations { dependencies { ecj 'org.eclipse.jdt:ecj:3.23.0' - lombok 'org.projectlombok:lombok:1.18.14' + lombok 'org.projectlombok:lombok:1.18.16' } java { @@ -51,7 +51,7 @@ compileJava { } dependencies { - compileOnly('org.projectlombok:lombok:1.18.12') + compileOnly('org.projectlombok:lombok:1.18.16') annotationProcessor('org.projectlombok:lombok:1.18.12') api ("org.seleniumhq.selenium:selenium-java") { version { From 59d7d997d516188e82dbd3a4469bb9ac896d1b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zuzana=20Krej=C4=8Dov=C3=A1?= Date: Thu, 3 Dec 2020 08:29:40 +0100 Subject: [PATCH 062/630] feat: Add 'boundElementsByIndex' to Settings (added in appium 1.18.0) (#1418) --- src/main/java/io/appium/java_client/Setting.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/appium/java_client/Setting.java b/src/main/java/io/appium/java_client/Setting.java index 90197b4ce..b5b84ca16 100644 --- a/src/main/java/io/appium/java_client/Setting.java +++ b/src/main/java/io/appium/java_client/Setting.java @@ -44,6 +44,7 @@ public enum Setting { MJPEG_SCALING_FACTOR("mjpegScalingFactor"), KEYBOARD_AUTOCORRECTION("keyboardAutocorrection"), KEYBOARD_PREDICTION("keyboardPrediction"), + BOUND_ELEMENTS_BY_INDEX("boundElementsByIndex"), // Android and iOS SHOULD_USE_COMPACT_RESPONSES("shouldUseCompactResponses"), ELEMENT_RESPONSE_ATTRIBUTES("elementResponseAttributes"), From a8c3c794262672a0ce3dd9451e834c4bfcd280fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:12:37 +0530 Subject: [PATCH 063/630] build(deps): bump org.owasp.dependencycheck from 6.0.2 to 6.0.3 (#1408) Bumps org.owasp.dependencycheck from 6.0.2 to 6.0.3. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 56b71ad16..f16d3eac0 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.0.2' + id 'org.owasp.dependencycheck' version '6.0.3' id 'com.github.johnrengelman.shadow' version '6.1.0' } From 80d187b260d227cc467a1bb07df83b4a938ef981 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Dec 2020 15:48:06 +0530 Subject: [PATCH 064/630] build(deps): bump webdrivermanager from 4.2.0 to 4.2.2 (#1400) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.2.0 to 4.2.2. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.2.0...webdrivermanager-4.2.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f16d3eac0..d810b63b2 100644 --- a/build.gradle +++ b/build.gradle @@ -85,7 +85,7 @@ dependencies { testImplementation 'junit:junit:4.13.1' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.0') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.2') { exclude group: 'org.seleniumhq.selenium' } } From 0be00cdb60a070f31eeb33b15dbb51858efb20c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Dec 2020 14:02:25 +0530 Subject: [PATCH 065/630] build(deps): bump spring-context from 5.3.1 to 5.3.2 (#1420) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.1 to 5.3.2. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.1...v5.3.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d810b63b2..514ed0df3 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.1' + implementation 'org.springframework:spring-context:5.3.2' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From a5f16210000bd936e689500b85175df94ed5a771 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 22 Dec 2020 16:10:47 +0300 Subject: [PATCH 066/630] fix: Use lower case for Windows platform key in ElementMap (#1421) --- src/main/java/io/appium/java_client/internal/ElementMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/internal/ElementMap.java b/src/main/java/io/appium/java_client/internal/ElementMap.java index 5522f7cb4..daeb644fb 100644 --- a/src/main/java/io/appium/java_client/internal/ElementMap.java +++ b/src/main/java/io/appium/java_client/internal/ElementMap.java @@ -37,7 +37,7 @@ public enum ElementMap { IOS_XCUI_TEST(AutomationName.IOS_XCUI_TEST.toLowerCase(), IOSElement.class), ANDROID_UI_AUTOMATOR(MobilePlatform.ANDROID.toLowerCase(), AndroidElement.class), IOS_UI_AUTOMATION(MobilePlatform.IOS.toLowerCase(), IOSElement.class), - WINDOWS(MobilePlatform.WINDOWS, WindowsElement.class); + WINDOWS(MobilePlatform.WINDOWS.toLowerCase(), WindowsElement.class); private static final Map mobileElementMap; From 2d32ed1ba405fac3fc07223039e4394db6f5c646 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Jan 2021 18:21:51 +0530 Subject: [PATCH 067/630] build(deps): bump org.owasp.dependencycheck from 6.0.3 to 6.0.4 (#1425) Bumps org.owasp.dependencycheck from 6.0.3 to 6.0.4. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 514ed0df3..b425c7e5e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.0.3' + id 'org.owasp.dependencycheck' version '6.0.4' id 'com.github.johnrengelman.shadow' version '6.1.0' } From f573237fba116f425f03b71b40702cdb2305d41d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Jan 2021 18:22:38 +0530 Subject: [PATCH 068/630] build(deps): bump ecj from 3.23.0 to 3.24.0 (#1422) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b425c7e5e..16a87408d 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.23.0' + ecj 'org.eclipse.jdt:ecj:3.24.0' lombok 'org.projectlombok:lombok:1.18.16' } From 3f438a6bb58792cadbb338b6290f0dcd791a0f15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 17:38:34 +0530 Subject: [PATCH 069/630] build(deps): bump org.owasp.dependencycheck from 6.0.4 to 6.0.5 (#1427) Bumps org.owasp.dependencycheck from 6.0.4 to 6.0.5. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 16a87408d..adf941030 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.0.4' + id 'org.owasp.dependencycheck' version '6.0.5' id 'com.github.johnrengelman.shadow' version '6.1.0' } From ad3b5fb4f750ef233e6ffc84402288dd30fcfdc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:02:52 +0530 Subject: [PATCH 070/630] build(deps): bump webdrivermanager from 4.2.2 to 4.3.1 (#1429) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index adf941030..459ffdbe3 100644 --- a/build.gradle +++ b/build.gradle @@ -85,7 +85,7 @@ dependencies { testImplementation 'junit:junit:4.13.1' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.2.2') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.3.1') { exclude group: 'org.seleniumhq.selenium' } } From eef3b83c98a6b065f57a0306a1af8a1783098b46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:03:29 +0530 Subject: [PATCH 071/630] build(deps): bump spring-context from 5.3.2 to 5.3.3 (#1428) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 459ffdbe3..e07170c22 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.2' + implementation 'org.springframework:spring-context:5.3.3' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 43d6514b224e0975653695a45e04d73522ed485e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:59:31 +0530 Subject: [PATCH 072/630] build(deps): bump org.owasp.dependencycheck from 6.0.5 to 6.1.0 (#1434) Bumps org.owasp.dependencycheck from 6.0.5 to 6.1.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e07170c22..e2f5e9cdb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.0.5' + id 'org.owasp.dependencycheck' version '6.1.0' id 'com.github.johnrengelman.shadow' version '6.1.0' } From e9ca49040098fa8475e8160720f908841be12fc8 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 4 Feb 2021 15:21:41 +0300 Subject: [PATCH 073/630] build: remove JCenter repository (#1438) https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index e2f5e9cdb..0c38bffc4 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,6 @@ plugins { } repositories { - jcenter() mavenCentral() } From ff10d910eae1706d4e0b4888979b9e4ffc91e35d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 4 Feb 2021 22:09:03 +0100 Subject: [PATCH 074/630] feat: Add Mac2Driver (#1439) --- .../java_client/mac/FindsByClassChain.java | 50 ++++++ .../java_client/mac/FindsByNsPredicate.java | 50 ++++++ .../io/appium/java_client/mac/Mac2Driver.java | 101 ++++++++++++ .../appium/java_client/mac/Mac2Element.java | 23 +++ .../mac/Mac2StartScreenRecordingOptions.java | 152 ++++++++++++++++++ .../mac/Mac2StopScreenRecordingOptions.java | 28 ++++ .../java_client/remote/AutomationName.java | 1 + .../java_client/remote/MobilePlatform.java | 1 + 8 files changed, 406 insertions(+) create mode 100644 src/main/java/io/appium/java_client/mac/FindsByClassChain.java create mode 100644 src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java create mode 100644 src/main/java/io/appium/java_client/mac/Mac2Driver.java create mode 100644 src/main/java/io/appium/java_client/mac/Mac2Element.java create mode 100644 src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java create mode 100644 src/main/java/io/appium/java_client/mac/Mac2StopScreenRecordingOptions.java diff --git a/src/main/java/io/appium/java_client/mac/FindsByClassChain.java b/src/main/java/io/appium/java_client/mac/FindsByClassChain.java new file mode 100644 index 000000000..3d4eaffcc --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/FindsByClassChain.java @@ -0,0 +1,50 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import io.appium.java_client.FindsByFluentSelector; +import io.appium.java_client.MobileSelector; +import org.openqa.selenium.WebElement; + +import java.util.List; + +public interface FindsByClassChain extends FindsByFluentSelector { + + /** + * Perform single element lookup by class chain expression. + * Read https://github.com/appium/appium-mac2-driver#element-location + * for more details on elements location strategies supported by Mac2 driver. + * + * @param using A valid class chain lookup expression. + * @return The found element + */ + default T findElementByClassChain(String using) { + return findElement(MobileSelector.IOS_CLASS_CHAIN.toString(), using); + } + + /** + * Perform multiple elements lookup by class chain search expression. + * Read https://github.com/appium/appium-mac2-driver#element-location + * for more details on elements location strategies supported by Mac2 driver. + * + * @param using A valid class chain lookup expression. + * @return The array of found elements or an empty one if no matches have been found. + */ + default List findElementsByClassChain(String using) { + return findElements(MobileSelector.IOS_CLASS_CHAIN.toString(), using); + } +} diff --git a/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java b/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java new file mode 100644 index 000000000..665732eb3 --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java @@ -0,0 +1,50 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import io.appium.java_client.FindsByFluentSelector; +import io.appium.java_client.MobileSelector; +import org.openqa.selenium.WebElement; + +import java.util.List; + +public interface FindsByNsPredicate extends FindsByFluentSelector { + + /** + * Perform single element lookup by predicate search expression. + * Read https://github.com/appium/appium-mac2-driver#element-location + * for more details on elements location strategies supported by Mac2 driver. + * + * @param using A valid predicate lookup expression. + * @return The found element + */ + default T findElementByNsPredicate(String using) { + return findElement(MobileSelector.IOS_PREDICATE_STRING.toString(), using); + } + + /** + * Perform multiple elements lookup by predicate search expression. + * Read https://github.com/appium/appium-mac2-driver#element-location + * for more details on elements location strategies supported by Mac2 driver. + * + * @param using A valid predicate lookup expression. + * @return The array of found elements or an empty one if no matches have been found. + */ + default List findElementsByNsPredicate(String using) { + return findElements(MobileSelector.IOS_PREDICATE_STRING.toString(), using); + } +} diff --git a/src/main/java/io/appium/java_client/mac/Mac2Driver.java b/src/main/java/io/appium/java_client/mac/Mac2Driver.java new file mode 100644 index 000000000..d76eaf5d7 --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/Mac2Driver.java @@ -0,0 +1,101 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.HasSettings; +import io.appium.java_client.internal.CapabilityHelpers; +import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.screenrecording.CanRecordScreen; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import io.appium.java_client.service.local.AppiumServiceBuilder; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URL; + +import static io.appium.java_client.remote.MobilePlatform.MAC; +import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; + +/** + * Mac2Driver is an officially supported Appium driver + * created to automate Mac OS apps. The driver uses W3C + * WebDriver protocol and is built on top of Apple's XCTest + * automation framework. Read https://github.com/appium/appium-mac2-driver + * for more details on how to configure and use it. + * + * @since Appium 1.20.0 + */ +public class Mac2Driver + extends AppiumDriver implements CanRecordScreen, FindsByClassChain, + FindsByNsPredicate, HasSettings { + public Mac2Driver(HttpCommandExecutor executor, Capabilities capabilities) { + super(executor, prepareCaps(capabilities)); + } + + public Mac2Driver(URL remoteAddress, Capabilities desiredCapabilities) { + super(remoteAddress, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(URL remoteAddress, HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities) { + super(remoteAddress, httpClientFactory, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(AppiumDriverLocalService service, Capabilities desiredCapabilities) { + super(service, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(AppiumDriverLocalService service, HttpClient.Factory httpClientFactory, + Capabilities desiredCapabilities) { + super(service, httpClientFactory, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(AppiumServiceBuilder builder, Capabilities desiredCapabilities) { + super(builder, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory, + Capabilities desiredCapabilities) { + super(builder, httpClientFactory, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities) { + super(httpClientFactory, prepareCaps(desiredCapabilities)); + } + + public Mac2Driver(Capabilities desiredCapabilities) { + super(prepareCaps(desiredCapabilities)); + } + + private static Capabilities prepareCaps(Capabilities originalCaps) { + DesiredCapabilities dc = new DesiredCapabilities(originalCaps); + if (originalCaps.getCapability(PLATFORM_NAME) == null) { + dc.setCapability(PLATFORM_NAME, MAC); + } + String automationName = CapabilityHelpers.getCapability(originalCaps, + MobileCapabilityType.AUTOMATION_NAME, String.class); + if (!AutomationName.MAC2.equalsIgnoreCase(automationName)) { + dc.setCapability(CapabilityHelpers.APPIUM_PREFIX + + MobileCapabilityType.AUTOMATION_NAME, AutomationName.MAC2); + } + return dc; + } +} diff --git a/src/main/java/io/appium/java_client/mac/Mac2Element.java b/src/main/java/io/appium/java_client/mac/Mac2Element.java new file mode 100644 index 000000000..905bada6e --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/Mac2Element.java @@ -0,0 +1,23 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import io.appium.java_client.MobileElement; + +public class Mac2Element extends MobileElement implements + FindsByClassChain, FindsByNsPredicate { +} diff --git a/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java new file mode 100644 index 000000000..45c573126 --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java @@ -0,0 +1,152 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; + +import java.time.Duration; +import java.util.Map; + +import static java.util.Optional.ofNullable; + +public class Mac2StartScreenRecordingOptions + extends BaseStartScreenRecordingOptions { + private Integer fps; + private String videoFilter; + private String preset; + private Boolean captureCursor; + private Boolean captureClicks; + private Integer deviceId; + + public static Mac2StartScreenRecordingOptions startScreenRecordingOptions() { + return new Mac2StartScreenRecordingOptions(); + } + + /** + * The count of frames per second in the resulting video. + * Increasing fps value also increases the size of the resulting + * video file and the CPU usage. + * + * @param fps The actual frames per second value. + * The default value is 15. + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions withFps(int fps) { + this.fps = fps; + return this; + } + + /** + * Whether to capture the mouse cursor while recording + * the screen. Disabled by default. + * + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions enableCursorCapture() { + this.captureCursor = true; + return this; + } + + /** + * Whether to capture the click gestures while recording + * the screen. Disabled by default. + * + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions enableClicksCapture() { + this.captureClicks = true; + return this; + } + + /** + * Screen device index to use for the recording. + * The list of available devices could be retrieved using + * `ffmpeg -f avfoundation -list_devices true -i` command. + * This option is mandatory and must be always provided. + * + * @param deviceId The valid screen device identifier. + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions withDeviceId(Integer deviceId) { + this.deviceId = deviceId; + return this; + } + + /** + * The video filter spec to apply for ffmpeg. + * See https://trac.ffmpeg.org/wiki/FilteringGuide for more details on the possible values. + * Example: Set it to `scale=ifnot(gte(iw\,1024)\,iw\,1024):-2` in order to limit the video width + * to 1024px. The height will be adjusted automatically to match the actual screen aspect ratio. + * + * @param videoFilter Valid ffmpeg video filter spec string. + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions withVideoFilter(String videoFilter) { + this.videoFilter = videoFilter; + return this; + } + + /** + * A preset is a collection of options that will provide a certain encoding speed to compression ratio. + * A slower preset will provide better compression (compression is quality per filesize). + * This means that, for example, if you target a certain file size or constant bit rate, you will + * achieve better quality with a slower preset. Read https://trac.ffmpeg.org/wiki/Encode/H.264 + * for more details. + * + * @param preset One of the supported encoding presets. Possible values are: + * - ultrafast + * - superfast + * - veryfast (default) + * - faster + * - fast + * - medium + * - slow + * - slower + * - veryslow + * @return self instance for chaining. + */ + public Mac2StartScreenRecordingOptions withPreset(String preset) { + this.preset = preset; + return this; + } + + /** + * The maximum recording time. The default value is 600 seconds (10 minutes). + * The minimum time resolution unit is one second. + * + * @param timeLimit The actual time limit of the recorded video. + * @return self instance for chaining. + */ + @Override + public Mac2StartScreenRecordingOptions withTimeLimit(Duration timeLimit) { + return super.withTimeLimit(timeLimit); + } + + @Override + public Map build() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(super.build()); + ofNullable(fps).map(x -> builder.put("fps", x)); + ofNullable(preset).map(x -> builder.put("preset", x)); + ofNullable(videoFilter).map(x -> builder.put("videoFilter", x)); + ofNullable(captureClicks).map(x -> builder.put("captureClicks", x)); + ofNullable(captureCursor).map(x -> builder.put("captureCursor", x)); + ofNullable(deviceId).map(x -> builder.put("deviceId", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/mac/Mac2StopScreenRecordingOptions.java b/src/main/java/io/appium/java_client/mac/Mac2StopScreenRecordingOptions.java new file mode 100644 index 000000000..8984460be --- /dev/null +++ b/src/main/java/io/appium/java_client/mac/Mac2StopScreenRecordingOptions.java @@ -0,0 +1,28 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.mac; + +import io.appium.java_client.screenrecording.BaseStopScreenRecordingOptions; + +public class Mac2StopScreenRecordingOptions extends + BaseStopScreenRecordingOptions { + + public static Mac2StopScreenRecordingOptions stopScreenRecordingOptions() { + return new Mac2StopScreenRecordingOptions(); + } + +} diff --git a/src/main/java/io/appium/java_client/remote/AutomationName.java b/src/main/java/io/appium/java_client/remote/AutomationName.java index ce85512c1..2f9cdf11f 100644 --- a/src/main/java/io/appium/java_client/remote/AutomationName.java +++ b/src/main/java/io/appium/java_client/remote/AutomationName.java @@ -25,4 +25,5 @@ public interface AutomationName { String ANDROID_UIAUTOMATOR2 = "UIAutomator2"; String YOUI_ENGINE = "youiengine"; String ESPRESSO = "Espresso"; + String MAC2 = "Mac2"; } diff --git a/src/main/java/io/appium/java_client/remote/MobilePlatform.java b/src/main/java/io/appium/java_client/remote/MobilePlatform.java index 07ecc8223..97e8deaf3 100644 --- a/src/main/java/io/appium/java_client/remote/MobilePlatform.java +++ b/src/main/java/io/appium/java_client/remote/MobilePlatform.java @@ -23,4 +23,5 @@ public interface MobilePlatform { String FIREFOX_OS = "FirefoxOS"; String WINDOWS = "Windows"; String TVOS = "tvOS"; + String MAC = "Mac"; } From d5ec15a7cb48b5729d0f155f5edd41feb2c3450e Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 19 Feb 2021 15:08:55 +0100 Subject: [PATCH 075/630] feat: Add support of multiple image occurrences (#1445) --- .../OccurrenceMatchingOptions.java | 29 +++++++++++++++++ .../OccurrenceMatchingResult.java | 31 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java index baad10d2a..e2990ea23 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java @@ -24,6 +24,8 @@ public class OccurrenceMatchingOptions extends BaseComparisonOptions { private Double threshold; + private Boolean multiple; + private Integer matchNeighbourThreshold; /** * At what normalized threshold to reject an occurrence. @@ -36,11 +38,38 @@ public OccurrenceMatchingOptions withThreshold(double threshold) { return this; } + /** + * Whether to enable the support of multiple image occurrences. + * + * @since Appium 1.21.0 + * @return self instance for chaining. + */ + public OccurrenceMatchingOptions enableMultiple() { + this.multiple = true; + return this; + } + + /** + * The pixel distance between matches we consider + * to be part of the same template match. This option is only + * considered if multiple matches mode is enabled. + * 10 pixels by default. + * + * @since Appium 1.21.0 + * @return self instance for chaining. + */ + public OccurrenceMatchingOptions withMatchNeighbourThreshold(int threshold) { + this.matchNeighbourThreshold = threshold; + return this; + } + @Override public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); builder.putAll(super.build()); ofNullable(threshold).map(x -> builder.put("threshold", x)); + ofNullable(matchNeighbourThreshold).map(x -> builder.put("matchNeighbourThreshold", x)); + ofNullable(multiple).map(x -> builder.put("multiple", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java index 273747247..256a1636a 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -18,13 +18,23 @@ import org.openqa.selenium.Rectangle; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class OccurrenceMatchingResult extends ComparisonResult { private static final String RECT = "rect"; + private static final String MULTIPLE = "multiple"; + + private final boolean isAtRoot; public OccurrenceMatchingResult(Map input) { + this(input, true); + } + + private OccurrenceMatchingResult(Map input, boolean isAtRoot) { super(input); + this.isAtRoot = isAtRoot; } /** @@ -37,4 +47,25 @@ public Rectangle getRect() { //noinspection unchecked return mapToRect((Map) getCommandResult().get(RECT)); } + + /** + * Returns the list of multiple matches (if any). + * This property only works if the `multiple` option is enabled. + * + * @since Appium 1.21.0 + * @return The list containing properties of each single match or an empty list. + * @throws IllegalStateException If the accessor is called on a non-root match instance. + */ + public List getMultiple() { + if (!isAtRoot) { + throw new IllegalStateException("Only the root match could contain multiple submatches"); + } + verifyPropertyPresence(MULTIPLE); + + //noinspection unchecked + List> multiple = (List>) getCommandResult().get(MULTIPLE); + return multiple.stream() + .map((m) -> new OccurrenceMatchingResult(m, false)) + .collect(Collectors.toList()); + } } From c14054ed9e95b7499c90a09a098b675529c86237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Feb 2021 20:20:20 +0530 Subject: [PATCH 076/630] build(deps): bump spring-context from 5.3.3 to 5.3.4 (#1446) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.3 to 5.3.4. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.3...v5.3.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0c38bffc4..3f468ea2b 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.11' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.3' + implementation 'org.springframework:spring-context:5.3.4' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 08826a7412515d982f5f682adba6af987702329b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Mar 2021 09:21:50 +0300 Subject: [PATCH 077/630] build(deps): bump commons-lang3 from 3.11 to 3.12.0 (#1447) Bumps commons-lang3 from 3.11 to 3.12.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3f468ea2b..7ff656454 100644 --- a/build.gradle +++ b/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' - implementation 'org.apache.commons:commons-lang3:3.11' + implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.8.0' implementation 'org.springframework:spring-context:5.3.4' implementation 'org.aspectj:aspectjweaver:1.9.6' From f726f3beddb3294684f1f441e865afc334dfc2f8 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Sat, 13 Mar 2021 16:59:38 +0530 Subject: [PATCH 078/630] Release 7.5.0 and update release notes --- README.md | 16 ++++++++++++++++ build.gradle | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b242ba3f..70fbf9242 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,22 @@ dependencies { ``` ## Changelog +*7.5.0* +- **[ENHANCEMENTS]** + - Add support for Appium Mac2Driver. [#1439](https://github.com/appium/java-client/pull/1439) + - Add support for multiple image occurrences. [#1445](https://github.com/appium/java-client/pull/1445) + - `BOUND_ELEMENTS_BY_INDEX` Setting was added. [#1418](https://github.com/appium/java-client/pull/1418) +- **[BUG FIX]** + - Use lower case for Windows platform key in ElementMap. [#1421](https://github.com/appium/java-client/pull/1421) +- **[DEPENDENCY UPDATES]** + - `org.apache.commons:commons-lang3` was updated to 3.12.0. + - `org.springframework:spring-context` was updated to 5.3.4. + - `org.owasp.dependencycheck` was updated to 6.1.0. + - `io.github.bonigarcia:webdrivermanager` was updated to 4.3.1. + - `org.eclipse.jdt:ecj` was updated to 3.24.0. + - `org.projectlombok:lombok` was updated to 1.18.16. + - `jcenter` repository was removed. + *7.4.1* - **[BUG FIX]** - Fix the configuration of `selenium-java` dependency. [#1417](https://github.com/appium/java-client/pull/1417) diff --git a/build.gradle b/build.gradle index 7ff656454..9fbc58bda 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '7.4.1' + version = '7.5.0' from components.java pom { name = 'java-client' From 03cbed0b2be49392126135af5c5107bfe9b75da1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Mar 2021 17:41:54 +0530 Subject: [PATCH 079/630] build(deps): bump org.owasp.dependencycheck from 6.1.0 to 6.1.2 (#1449) Bumps org.owasp.dependencycheck from 6.1.0 to 6.1.2. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9fbc58bda..b0572a287 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.1.0' + id 'org.owasp.dependencycheck' version '6.1.2' id 'com.github.johnrengelman.shadow' version '6.1.0' } From 9a2124c76315b24893079b9f1b3b2ae376e11686 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Mar 2021 17:59:27 +0530 Subject: [PATCH 080/630] build(deps): bump ecj from 3.24.0 to 3.25.0 (#1452) Bumps ecj from 3.24.0 to 3.25.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b0572a287..9aa8b2f62 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.24.0' + ecj 'org.eclipse.jdt:ecj:3.25.0' lombok 'org.projectlombok:lombok:1.18.16' } From fc814ed27eec6dfb28b775b75ea1bc78c428c30a Mon Sep 17 00:00:00 2001 From: root-intruder <58737722+root-intruder@users.noreply.github.com> Date: Tue, 23 Mar 2021 15:20:44 +0100 Subject: [PATCH 081/630] fix: bring back ability to automatically quote desired capabilities on windows systems (#1454) --- .../service/local/AppiumServiceBuilder.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java index caef4d36a..dc9b669be 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java @@ -25,6 +25,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.appium.java_client.remote.AndroidMobileCapabilityType; +import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.flags.ServerArgument; import org.apache.commons.io.IOUtils; @@ -32,6 +34,7 @@ import org.apache.commons.lang3.SystemUtils; import org.apache.commons.validator.routines.InetAddressValidator; import org.openqa.selenium.Capabilities; +import org.openqa.selenium.Platform; import org.openqa.selenium.os.ExecutableFinder; import org.openqa.selenium.remote.BrowserType; import org.openqa.selenium.remote.DesiredCapabilities; @@ -78,6 +81,7 @@ public final class AppiumServiceBuilder private File node; private String ipAddress = BROADCAST_IP_ADDRESS; private DesiredCapabilities capabilities; + private boolean autoQuoteCapabilitiesOnWindows = false; private static final Function APPIUM_JS_NOT_EXIST_ERROR = (fullPath) -> String.format( "The main Appium script does not exist at '%s'", fullPath.getAbsolutePath()); private static final Function NODE_JS_NOT_EXIST_ERROR = (fullPath) -> @@ -86,6 +90,8 @@ public final class AppiumServiceBuilder // The first starting is slow sometimes on some environment private long startupTimeout = 120; private TimeUnit timeUnit = TimeUnit.SECONDS; + private static final List PATH_CAPABILITIES = ImmutableList.of(AndroidMobileCapabilityType.KEYSTORE_PATH, + AndroidMobileCapabilityType.CHROMEDRIVER_EXECUTABLE, MobileCapabilityType.APP); public AppiumServiceBuilder() { usingPort(DEFAULT_APPIUM_PORT); @@ -239,6 +245,21 @@ public AppiumServiceBuilder withCapabilities(DesiredCapabilities capabilities) { return this; } + /** + * Adds a desired capabilities. + * + * @param capabilities is an instance of {@link DesiredCapabilities}. + * @param autoQuoteCapabilitiesOnWindows automatically escape quote all + * capabilities when calling appium. + * This is required on windows systems only. + * @return the self-reference. + */ + public AppiumServiceBuilder withCapabilities(DesiredCapabilities capabilities, + boolean autoQuoteCapabilitiesOnWindows) { + this.autoQuoteCapabilitiesOnWindows = autoQuoteCapabilitiesOnWindows; + return withCapabilities(capabilities); + } + /** * Sets an executable appium.js. * @@ -296,7 +317,46 @@ private void loadPathToMainScript() { this.appiumJS = findMainScript(); } + private String capabilitiesToQuotedCmdlineArg() { + if (capabilities == null) { + return "{}"; + } + StringBuilder result = new StringBuilder(); + Map capabilitiesMap = capabilities.asMap(); + Set> entries = capabilitiesMap.entrySet(); + + for (Map.Entry entry : entries) { + Object value = entry.getValue(); + + if (value == null) { + continue; + } + + if (value instanceof String) { + String valueString = (String) value; + if (PATH_CAPABILITIES.contains(entry.getKey())) { + value = "\\\"" + valueString.replace("\\", "/") + "\\\""; + } else { + value = "\\\"" + valueString + "\\\""; + } + } else { + value = String.valueOf(value); + } + + String key = "\\\"" + entry.getKey() + "\\\""; + if (result.length() > 0) { + result.append(", "); + } + result.append(key).append(": ").append(value); + } + + return "{" + result.toString() + "}"; + } + private String capabilitiesToCmdlineArg() { + if (autoQuoteCapabilitiesOnWindows && Platform.getCurrent().is(Platform.WINDOWS)) { + return capabilitiesToQuotedCmdlineArg(); + } Gson gson = new GsonBuilder() .disableHtmlEscaping() .serializeNulls() From 6b485190a0d7efcbd184f9354218768309322c36 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 24 Mar 2021 17:12:16 +0900 Subject: [PATCH 082/630] feat: add iOS related find by annotations for tvOS (#1456) --- .../java_client/pagefactory/DefaultElementByBuilder.java | 2 +- .../java_client/pagefactory/bys/builder/AppiumByBuilder.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java index 40a990898..d31b56fe4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -170,7 +170,7 @@ protected By buildMobileNativeBy() { getBys(AndroidFindBy.class, AndroidFindBys.class, AndroidFindAll.class)); } - if (isIOSXcuit() || isIOS()) { + if (isIOSXcuit() || isIOS() || isTvOS()) { return buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::iOSXCUITAutomation).orElse(null), getBys(iOSXCUITFindBy.class, iOSXCUITFindBys.class, iOSXCUITFindAll.class)); } diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java index 827a8ecbe..3a18cf940 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java @@ -19,6 +19,7 @@ import static io.appium.java_client.remote.AutomationName.IOS_XCUI_TEST; import static io.appium.java_client.remote.MobilePlatform.ANDROID; import static io.appium.java_client.remote.MobilePlatform.IOS; +import static io.appium.java_client.remote.MobilePlatform.TVOS; import static io.appium.java_client.remote.MobilePlatform.WINDOWS; import org.openqa.selenium.By; @@ -178,6 +179,10 @@ protected boolean isIOS() { return IOS.equalsIgnoreCase(platform); } + protected boolean isTvOS() { + return TVOS.equalsIgnoreCase(platform); + } + protected boolean isIOSXcuit() { return isIOS() && IOS_XCUI_TEST.equalsIgnoreCase(automation); } From feea319e6c5691c290d43cc7016803dcf990c1cc Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Thu, 25 Mar 2021 12:20:44 +0530 Subject: [PATCH 083/630] Release 7.5.1 and update release notes --- README.md | 9 +++++++++ build.gradle | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70fbf9242..bfcc35e51 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,15 @@ dependencies { ``` ## Changelog +*7.5.1* +- **[ENHANCEMENTS]** + - Add iOS related annotations to tvOS. [#1456](https://github.com/appium/java-client/pull/1456) +- **[BUG FIX]** + - Bring back automatic quote escaping for desired capabilities command line arguments on windows. [#1454](https://github.com/appium/java-client/pull/1454) +- **[DEPENDENCY UPDATES]** + - `org.owasp.dependencycheck` was updated to 6.1.2. + - `org.eclipse.jdt:ecj` was updated to 3.25.0. + *7.5.0* - **[ENHANCEMENTS]** - Add support for Appium Mac2Driver. [#1439](https://github.com/appium/java-client/pull/1439) diff --git a/build.gradle b/build.gradle index 9aa8b2f62..4fdaaeb50 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '7.5.0' + version = '7.5.1' from components.java pom { name = 'java-client' From e6203a6f46e89b65197450e225aaf99042c4a13f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 17:34:06 +0530 Subject: [PATCH 084/630] build(deps): bump org.owasp.dependencycheck from 6.1.2 to 6.1.3 (#1457) Bumps org.owasp.dependencycheck from 6.1.2 to 6.1.3. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4fdaaeb50..448d4a304 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.1.2' + id 'org.owasp.dependencycheck' version '6.1.3' id 'com.github.johnrengelman.shadow' version '6.1.0' } From 08d2f92a47b6bf2ca36afbcf1377453b94fda82e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Apr 2021 20:51:37 +0300 Subject: [PATCH 085/630] build(deps): bump org.owasp.dependencycheck from 6.1.3 to 6.1.5 (#1459) Bumps org.owasp.dependencycheck from 6.1.3 to 6.1.5. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 448d4a304..bf9996562 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.1.3' + id 'org.owasp.dependencycheck' version '6.1.5' id 'com.github.johnrengelman.shadow' version '6.1.0' } From fbba129c8e8ff1e807b03e314d83e719d25095cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 07:33:41 +0530 Subject: [PATCH 086/630] build(deps): bump lombok from 1.18.16 to 1.18.20 (#1460) Bumps [lombok](https://github.com/rzwitserloot/lombok) from 1.18.16 to 1.18.20. - [Release notes](https://github.com/rzwitserloot/lombok/releases) - [Changelog](https://github.com/rzwitserloot/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/rzwitserloot/lombok/compare/v1.18.16...v1.18.20) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index bf9996562..6ce80ef87 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ configurations { dependencies { ecj 'org.eclipse.jdt:ecj:3.25.0' - lombok 'org.projectlombok:lombok:1.18.16' + lombok 'org.projectlombok:lombok:1.18.20' } java { @@ -51,7 +51,7 @@ compileJava { dependencies { compileOnly('org.projectlombok:lombok:1.18.16') - annotationProcessor('org.projectlombok:lombok:1.18.12') + annotationProcessor('org.projectlombok:lombok:1.18.20') api ("org.seleniumhq.selenium:selenium-java") { version { strictly "${project.property('selenium.version')}" From c936f66742e7a1fb445f86331aabd3a481394abb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 14:04:34 +0530 Subject: [PATCH 087/630] build(deps): bump junit from 4.13.1 to 4.13.2 (#1442) Bumps [junit](https://github.com/junit-team/junit4) from 4.13.1 to 4.13.2. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.13.1...r4.13.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6ce80ef87..36413c423 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencies { implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' - testImplementation 'junit:junit:4.13.1' + testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.3.1') { exclude group: 'org.seleniumhq.selenium' From 6d68ea7fccea252d0df455147ca8db2ddadadf96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 10:06:59 +0300 Subject: [PATCH 088/630] build(deps): bump spring-context from 5.3.4 to 5.3.5 (#1453) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.4 to 5.3.5. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.4...v5.3.5) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 36413c423..7c426bd13 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.4' + implementation 'org.springframework:spring-context:5.3.5' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 0e05078d0ae8e4bebb80080790712e933e2ea666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 22:28:48 +0300 Subject: [PATCH 089/630] build(deps): bump webdrivermanager from 4.3.1 to 4.4.0 (#1463) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.3.1 to 4.4.0. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.3.1...webdrivermanager-4.4.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7c426bd13..1ab759596 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.3.1') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.0') { exclude group: 'org.seleniumhq.selenium' } } From 43ff656ee555a2c3003279dddcde71be5170d3c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 17:47:02 +0530 Subject: [PATCH 090/630] build(deps): bump spring-context from 5.3.5 to 5.3.6 (#1464) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.5 to 5.3.6. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.5...v5.3.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1ab759596..801a14af1 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.5' + implementation 'org.springframework:spring-context:5.3.6' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 96ad4acdd3dd4b19940d6724e2f3ffeeb0227a7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 17:33:38 +0300 Subject: [PATCH 091/630] build(deps): bump webdrivermanager from 4.4.0 to 4.4.1 (#1466) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.4.0 to 4.4.1. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.4.0...webdrivermanager-4.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 801a14af1..937ed4d71 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.0') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.1') { exclude group: 'org.seleniumhq.selenium' } } From 2a48d015d38a2c98862da6696d9534f90967cbed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 21:09:25 +0530 Subject: [PATCH 092/630] build(deps): bump webdrivermanager from 4.4.1 to 4.4.3 (#1471) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.4.1 to 4.4.3. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.4.1...webdrivermanager-4.4.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 937ed4d71..fd1192957 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.1') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.3') { exclude group: 'org.seleniumhq.selenium' } } From dcc7579934d15b0aa87127f83a70b26746afdfab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 18:06:30 +0530 Subject: [PATCH 093/630] build(deps): bump spring-context from 5.3.6 to 5.3.7 (#1472) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.6 to 5.3.7. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.6...v5.3.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fd1192957..12e6c224f 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.8.0' - implementation 'org.springframework:spring-context:5.3.6' + implementation 'org.springframework:spring-context:5.3.7' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From b3001672d25a42bd5033123cc6bd934d599d8f04 Mon Sep 17 00:00:00 2001 From: Dmitry Mishtal Date: Thu, 20 May 2021 19:48:19 +0300 Subject: [PATCH 094/630] fix: bind mac2element in element map for mac platform (#1474) --- src/main/java/io/appium/java_client/internal/ElementMap.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/internal/ElementMap.java b/src/main/java/io/appium/java_client/internal/ElementMap.java index daeb644fb..d2f056c5d 100644 --- a/src/main/java/io/appium/java_client/internal/ElementMap.java +++ b/src/main/java/io/appium/java_client/internal/ElementMap.java @@ -23,6 +23,7 @@ import io.appium.java_client.MobileElement; import io.appium.java_client.android.AndroidElement; import io.appium.java_client.ios.IOSElement; +import io.appium.java_client.mac.Mac2Element; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.windows.WindowsElement; @@ -37,8 +38,8 @@ public enum ElementMap { IOS_XCUI_TEST(AutomationName.IOS_XCUI_TEST.toLowerCase(), IOSElement.class), ANDROID_UI_AUTOMATOR(MobilePlatform.ANDROID.toLowerCase(), AndroidElement.class), IOS_UI_AUTOMATION(MobilePlatform.IOS.toLowerCase(), IOSElement.class), - WINDOWS(MobilePlatform.WINDOWS.toLowerCase(), WindowsElement.class); - + WINDOWS(MobilePlatform.WINDOWS.toLowerCase(), WindowsElement.class), + MAC(MobilePlatform.MAC.toLowerCase(), Mac2Element.class); private static final Map mobileElementMap; From 023cf3f359058a78b000d969eed9326224f5dc1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 19:12:26 +0530 Subject: [PATCH 095/630] build(deps): bump commons-io from 2.8.0 to 2.9.0 (#1480) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 12e6c224f..ef4b24afa 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' - implementation 'commons-io:commons-io:2.8.0' + implementation 'commons-io:commons-io:2.9.0' implementation 'org.springframework:spring-context:5.3.7' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From 4415440ec509ba964e50428565657a8e064d0c59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 19:12:59 +0530 Subject: [PATCH 096/630] build(deps): bump gson from 2.8.6 to 2.8.7 (#1479) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ef4b24afa..02170dd58 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencies { strictly "${project.property('selenium.version')}" } } - implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.code.gson:gson:2.8.7' implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' From 927db522c2ec057f16ed8eef516b00cc6a8dc232 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 May 2021 19:15:29 +0530 Subject: [PATCH 097/630] build(deps): bump org.owasp.dependencycheck from 6.1.5 to 6.2.0 (#1478) Bumps org.owasp.dependencycheck from 6.1.5 to 6.2.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 02170dd58..44147faae 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.1.5' + id 'org.owasp.dependencycheck' version '6.2.0' id 'com.github.johnrengelman.shadow' version '6.1.0' } From 5089dbcaf804d96d57c4eb0ed16f255059a11182 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jun 2021 15:17:54 +0300 Subject: [PATCH 098/630] build(deps): bump commons-io from 2.9.0 to 2.10.0 (#1483) Bumps commons-io from 2.9.0 to 2.10.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 44147faae..81483536f 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' - implementation 'commons-io:commons-io:2.9.0' + implementation 'commons-io:commons-io:2.10.0' implementation 'org.springframework:spring-context:5.3.7' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From d1e5fa7d16eada87413893cbcfe8f6b1ce82d404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jun 2021 16:45:24 +0300 Subject: [PATCH 099/630] build(deps): bump spring-context from 5.3.7 to 5.3.8 (#1485) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.7 to 5.3.8. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.7...v5.3.8) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 81483536f..cd04bca27 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.10.0' - implementation 'org.springframework:spring-context:5.3.7' + implementation 'org.springframework:spring-context:5.3.8' implementation 'org.aspectj:aspectjweaver:1.9.6' implementation 'org.slf4j:slf4j-api:1.7.30' From ceac42837ffe5fe7400c54d802e96318961698a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 11:01:25 +0300 Subject: [PATCH 100/630] build(deps): bump ecj from 3.25.0 to 3.26.0 (#1487) Bumps ecj from 3.25.0 to 3.26.0. --- updated-dependencies: - dependency-name: org.eclipse.jdt:ecj dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cd04bca27..e6f02ac48 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ configurations { } dependencies { - ecj 'org.eclipse.jdt:ecj:3.25.0' + ecj 'org.eclipse.jdt:ecj:3.26.0' lombok 'org.projectlombok:lombok:1.18.20' } From 979e6bdc0c472239ec8390d9cf24c064d4cc6aef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jun 2021 11:49:28 +0530 Subject: [PATCH 101/630] build(deps): bump slf4j-api from 1.7.30 to 1.7.31 (#1486) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e6f02ac48..3afc246f8 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { implementation 'commons-io:commons-io:2.10.0' implementation 'org.springframework:spring-context:5.3.8' implementation 'org.aspectj:aspectjweaver:1.9.6' - implementation 'org.slf4j:slf4j-api:1.7.30' + implementation 'org.slf4j:slf4j-api:1.7.31' testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' From 93a95fc689a116ac14f3bf8739037033a63f03f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Jun 2021 22:47:03 +0300 Subject: [PATCH 102/630] build(deps): bump aspectjweaver from 1.9.6 to 1.9.7 (#1490) Bumps [aspectjweaver](https://github.com/eclipse/org.aspectj) from 1.9.6 to 1.9.7. - [Release notes](https://github.com/eclipse/org.aspectj/releases) - [Commits](https://github.com/eclipse/org.aspectj/commits) --- updated-dependencies: - dependency-name: org.aspectj:aspectjweaver dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3afc246f8..948485713 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,7 @@ dependencies { implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.10.0' implementation 'org.springframework:spring-context:5.3.8' - implementation 'org.aspectj:aspectjweaver:1.9.6' + implementation 'org.aspectj:aspectjweaver:1.9.7' implementation 'org.slf4j:slf4j-api:1.7.31' testImplementation 'junit:junit:4.13.2' From a1e1d1d0702b45601593dda0679babac8266f416 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 1 Jul 2021 20:52:02 +0200 Subject: [PATCH 103/630] feat: Add support of extended Android geolocation (#1492) --- .../java_client/android/AndroidDriver.java | 3 +- .../geolocation/AndroidGeoLocation.java | 125 ++++++++++++++++++ .../SupportsExtendedGeolocationCommands.java | 40 ++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java create mode 100644 src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index 62016e814..1994bb69a 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -33,6 +33,7 @@ import io.appium.java_client.HasOnScreenKeyboard; import io.appium.java_client.LocksDevice; import io.appium.java_client.android.connection.HasNetworkConnection; +import io.appium.java_client.android.geolocation.SupportsExtendedGeolocationCommands; import io.appium.java_client.android.nativekey.PressesKey; import io.appium.java_client.battery.HasBattery; import io.appium.java_client.remote.MobilePlatform; @@ -68,7 +69,7 @@ public class AndroidDriver HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard, CanRecordScreen, SupportsSpecialEmulatorCommands, SupportsNetworkStateManagement, ListensToLogcatMessages, HasAndroidClipboard, - HasBattery, ExecuteCDPCommand { + HasBattery, ExecuteCDPCommand, SupportsExtendedGeolocationCommands { private static final String ANDROID_PLATFORM = MobilePlatform.ANDROID; diff --git a/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java b/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java new file mode 100644 index 000000000..9ab204317 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java @@ -0,0 +1,125 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android.geolocation; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +import static java.util.Optional.ofNullable; + +public class AndroidGeoLocation { + private Double longitude; + private Double latitude; + private Double altitude; + private Integer satellites; + private Double speed; + + /** + * Initializes AndroidLocation instance. + */ + public AndroidGeoLocation() { + + } + + /** + * Initializes AndroidLocation instance with longitude and latitude values. + * + * @param longitude longitude value + * @param latitude latitude value + */ + public AndroidGeoLocation(double longitude, double latitude) { + this.longitude = longitude; + this.latitude = latitude; + } + + /** + * Sets geo longitude value. This value is required to set. + * + * @param longitude geo longitude + * @return self instance for chaining + */ + public AndroidGeoLocation withLongitude(double longitude) { + this.longitude = longitude; + return this; + } + + /** + * Sets geo latitude value. This value is required to set. + * + * @param latitude geo latitude + * @return self instance for chaining + */ + public AndroidGeoLocation withLatitude(double latitude) { + this.latitude = latitude; + return this; + } + + /** + * Sets geo altitude value. + * + * @param altitude geo altitude + * @return self instance for chaining + */ + public AndroidGeoLocation withAltitude(double altitude) { + this.altitude = altitude; + return this; + } + + /** + * Sets the number of geo satellites being tracked. + * This number is respected on Emulators. + * + * @param satellites the count of satellites in range 1..12 + * @return self instance for chaining + */ + public AndroidGeoLocation withSatellites(int satellites) { + this.satellites = satellites; + return this; + } + + /** + * Sets the movement speed. It is measured in meters/second + * for real devices and in knots for emulators. + * + * @param speed the actual speed, which should be greater than zero + * @return self instance for chaining + */ + public AndroidGeoLocation withSpeed(double speed) { + this.speed = speed; + return this; + } + + /** + * Builds parameters map suitable for passing to the downstream API. + * + * @return Parameters mapping + */ + public Map build() { + ImmutableMap.Builder builder = ImmutableMap.builder(); + ofNullable(longitude).map(x -> builder.put("longitude", x)) + .orElseThrow(() -> new IllegalArgumentException( + "A valid 'longitude' must be provided")); + ofNullable(latitude).map(x -> builder.put("latitude", x)) + .orElseThrow(() -> new IllegalArgumentException( + "A valid 'latitude' must be provided")); + ofNullable(altitude).map(x -> builder.put("altitude", x)); + ofNullable(satellites).map(x -> builder.put("satellites", x)); + ofNullable(speed).map(x -> builder.put("speed", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java b/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java new file mode 100644 index 000000000..3587ad07e --- /dev/null +++ b/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java @@ -0,0 +1,40 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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 io.appium.java_client.android.geolocation; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.remote.DriverCommand; + +import java.util.AbstractMap; + +public interface SupportsExtendedGeolocationCommands extends ExecutesMethod { + + /** + * Allows to set geo location with extended parameters + * available for Android platform. + * + * @param location The location object to set. + */ + default void setLocation(AndroidGeoLocation location) { + ImmutableMap parameters = ImmutableMap + .of("location", location.build()); + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(DriverCommand.SET_LOCATION, parameters)); + } +} From 7ba73a98b57f5cf6f1d3c76c60acd47246588416 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 18:18:03 +0300 Subject: [PATCH 104/630] build(deps): bump commons-io from 2.10.0 to 2.11.0 (#1494) Bumps commons-io from 2.10.0 to 2.11.0. --- updated-dependencies: - dependency-name: commons-io:commons-io dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 948485713..54c32f31a 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' - implementation 'commons-io:commons-io:2.10.0' + implementation 'commons-io:commons-io:2.11.0' implementation 'org.springframework:spring-context:5.3.8' implementation 'org.aspectj:aspectjweaver:1.9.7' implementation 'org.slf4j:slf4j-api:1.7.31' From cfeaf498c66a4f4c38d98bf52c63571471aa3e23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 23:26:24 +0530 Subject: [PATCH 105/630] build(deps): bump spring-context from 5.3.8 to 5.3.9 (#1495) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.8 to 5.3.9. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.8...v5.3.9) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 54c32f31a..c6488b4e5 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.11.0' - implementation 'org.springframework:spring-context:5.3.8' + implementation 'org.springframework:spring-context:5.3.9' implementation 'org.aspectj:aspectjweaver:1.9.7' implementation 'org.slf4j:slf4j-api:1.7.31' From 9316538cec86c82e9444f9f90e2fc39cd4cac215 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 20 Jul 2021 15:29:42 +0300 Subject: [PATCH 106/630] chore: Upgrade to Gradle 7.1.1 (#1497) --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index c6488b4e5..5afdc2207 100644 --- a/build.gradle +++ b/build.gradle @@ -107,7 +107,7 @@ tasks.withType(JacocoReport) { description = 'Generate Jacoco coverage reports after running tests' sourceSets sourceSets.main reports { - html.enabled true + html.required = true html.destination file("${buildDir}/Reports/jacoco") } } @@ -195,7 +195,7 @@ signing { } wrapper { - gradleVersion = '6.7.1' + gradleVersion = '7.1.1' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 18435 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 B8dv}T delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1f3fdbc52..af7be50b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..744e882ed 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) From c185583e8f5d99a356c0574b09af47836b5d8dde Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 20 Jul 2021 15:31:17 +0300 Subject: [PATCH 107/630] chore: Prevent duplicate builds for PRs from base repo branches (#1496) --- .github/workflows/gradle-wrapper-validation.yml | 9 ++++++++- .github/workflows/gradle.yml | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 405a2b306..ba5a2db79 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,5 +1,12 @@ name: "Validate Gradle Wrapper" -on: [push, pull_request] + +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: validation: diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 04241b76f..44e24a9e0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,6 +1,12 @@ name: Appium Java Client CI -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + branches: + - master jobs: build: From 94d5c0050fe3e225549fa5c00ee0fbbb55c1ab5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:58:40 +0300 Subject: [PATCH 108/630] build(deps): bump gson from 2.8.7 to 2.8.8 (#1507) Bumps [gson](https://github.com/google/gson) from 2.8.7 to 2.8.8. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.8.7...gson-parent-2.8.8) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5afdc2207..a448344ec 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencies { strictly "${project.property('selenium.version')}" } } - implementation 'com.google.code.gson:gson:2.8.7' + implementation 'com.google.code.gson:gson:2.8.8' implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' From 227581b197f195ce38c3fbbc6c886936097414b1 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 23 Aug 2021 18:27:01 +0300 Subject: [PATCH 109/630] chore: Enable Dependabot for GitHub actions (#1500) --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bcf259eb9..c082a4d00 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,9 @@ updates: interval: weekly time: "11:00" open-pull-requests-limit: 10 +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + time: "11:00" + open-pull-requests-limit: 10 From 3a3e2f9f8088247667289353068b9ded9c34cbc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:35:29 +0300 Subject: [PATCH 110/630] build(deps): bump slf4j-api from 1.7.31 to 1.7.32 (#1501) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.31 to 1.7.32. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/commits) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a448344ec..d827cd41d 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { implementation 'commons-io:commons-io:2.11.0' implementation 'org.springframework:spring-context:5.3.9' implementation 'org.aspectj:aspectjweaver:1.9.7' - implementation 'org.slf4j:slf4j-api:1.7.31' + implementation 'org.slf4j:slf4j-api:1.7.32' testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' From bcd6881efe53713ea339a3e606dc0bc004969908 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 30 Aug 2021 11:09:42 +0530 Subject: [PATCH 111/630] feat: allow to add custom command dynamically (#1506) --- .../io/appium/java_client/AppiumDriver.java | 29 +++++++++++++++++-- .../remote/AppiumCommandExecutor.java | 3 ++ .../io/appium/java_client/ios/AppIOSTest.java | 2 +- .../appium/java_client/ios/BaseIOSTest.java | 4 +-- .../appium/java_client/ios/IOSDriverTest.java | 29 ++++++++++++++++++- 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 32785d1eb..fd512197a 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -29,7 +29,6 @@ import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; - import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; import org.openqa.selenium.DeviceRotation; @@ -39,7 +38,6 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.html5.Location; - import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DriverCommand; @@ -49,8 +47,10 @@ import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.html5.RemoteLocationContext; import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpMethod; import java.net.URL; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -288,6 +288,31 @@ public void rotate(ScreenOrientation orientation) { ImmutableMap.of("orientation", orientation.value().toUpperCase())); } + /** + * This method is used to add custom appium commands in Appium 2.0. + * + * @param httpMethod the available {@link HttpMethod}. + * @param url The url to URL template as https://www.w3.org/TR/webdriver/#endpoints. + * @param methodName The name of custom appium command. + */ + public void addCommand(HttpMethod httpMethod, String url, String methodName) { + switch (httpMethod) { + case GET: + MobileCommand.commandRepository.put(methodName, MobileCommand.getC(url)); + break; + case POST: + MobileCommand.commandRepository.put(methodName, MobileCommand.postC(url)); + break; + case DELETE: + MobileCommand.commandRepository.put(methodName, MobileCommand.deleteC(url)); + break; + default: + throw new WebDriverException(String.format("Unsupported HTTP Method: %s. Only %s methods are supported", + httpMethod, + Arrays.toString(HttpMethod.values()))); + } + } + @Override public ScreenOrientation getOrientation() { Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION); diff --git a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java index ea0ade5f0..2b0f77f7e 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -241,6 +241,9 @@ public Response execute(Command command) throws WebDriverException { } }); } + if (getAdditionalCommands().containsKey(command.getName())) { + super.defineCommand(command.getName(), getAdditionalCommands().get(command.getName())); + } Response response; try { diff --git a/src/test/java/io/appium/java_client/ios/AppIOSTest.java b/src/test/java/io/appium/java_client/ios/AppIOSTest.java index 12426ebd9..bdc75a01b 100644 --- a/src/test/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/AppIOSTest.java @@ -39,4 +39,4 @@ public static void beforeClass() throws Exception { driver = new IOSDriver<>(new URL("http://" + ip + ":" + PORT + "/wd/hub"), capabilities); } } -} +} \ No newline at end of file diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java index 45e7a8e2e..9275dd10b 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -31,9 +31,9 @@ public class BaseIOSTest { protected static IOSDriver driver; protected static final int PORT = 4723; public static final String DEVICE_NAME = System.getenv("IOS_DEVICE_NAME") != null - ? System.getenv("IOS_DEVICE_NAME") : "iPhone X"; + ? System.getenv("IOS_DEVICE_NAME") : "iPhone 12"; public static final String PLATFORM_VERSION = System.getenv("IOS_PLATFORM_VERSION") != null - ? System.getenv("IOS_PLATFORM_VERSION") : "11.4"; + ? System.getenv("IOS_PLATFORM_VERSION") : "14.5"; /** diff --git a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java index 40ecb5a9b..a1d7a0fb3 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -22,18 +22,21 @@ import static org.hamcrest.Matchers.greaterThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.MobileElement; import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.remote.HideKeyboardStrategy; import org.junit.Ignore; import org.junit.Test; - import org.openqa.selenium.By; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.html5.Location; +import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.http.HttpMethod; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; @@ -41,6 +44,30 @@ public class IOSDriverTest extends AppIOSTest { + @Test + public void addCustomCommandTest() { + driver.addCommand(HttpMethod.GET, "/sessions", "getSessions"); + final Response getSessions = driver.execute("getSessions"); + assertNotNull(getSessions.getSessionId()); + } + + @Test + public void addCustomCommandWithSessionIdTest() { + driver.addCommand(HttpMethod.POST, "/session/" + driver.getSessionId() + "/appium/app/launch", "launchApplication"); + final Response launchApplication = driver.execute("launchApplication"); + assertNotNull(launchApplication.getSessionId()); + } + + @Test + public void addCustomCommandWithElementIdTest() { + IOSElement intA = driver.findElementById("IntegerA"); + intA.clear(); + driver.addCommand(HttpMethod.POST, + String.format("/session/%s/appium/element/%s/value", driver.getSessionId(), intA.getId()), "setNewValue"); + final Response setNewValue = driver.execute("setNewValue", ImmutableMap.of("id", intA.getId(), "value", "8")); + assertNotNull(setNewValue.getSessionId()); + } + @Test public void getDeviceTimeTest() { String time = driver.getDeviceTime(); From 94b30221fb602b7a62c33d26a168d95ff77a4d2a Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Fri, 3 Sep 2021 17:57:05 +0530 Subject: [PATCH 112/630] feat: Add new flags to support Appium 2.0 (#1511) --- .../local/flags/GeneralServerFlag.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java b/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java index d59ff750d..817deae05 100644 --- a/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java +++ b/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java @@ -133,7 +133,40 @@ public enum GeneralServerFlag implements ServerArgument { * Default: [] * Sample: --deny-insecure=foo,bar */ - DENY_INSECURE("--deny-insecure"); + DENY_INSECURE("--deny-insecure"), + /** + * Plugins are little programs which can be added to an Appium installation and activated, for the purpose of + * extending or modifying the behavior of pretty much any aspect of Appium. + * Plugins are available with Appium as of Appium 2.0. + * To activate all plugins, you can use the single string "all" as the value (e.g --plugins=all) + * Default: [] + * Sample: --plugins=device-farm,images + */ + PLUGINS("--plugins"), + /** + * A comma-separated list of installed driver names that should be active for this server. + * All drivers will be active by default. + * Default: [] + * Sample: --drivers=uiautomator2,xcuitest + */ + DRIVERS("--drivers"), + /** + * Base path to use as the prefix for all webdriver routes running on this server. + * Sample: --base-path=/wd/hub + */ + BASEPATH("--base-path"), + /** + * Set the default desired client arguments for a plugin. + * Default: [] + * Sample: [ '{"images":{"foo1": "bar1", "foo2": "bar2"}}' | /path/to/pluginArgs.json ] + */ + PLUGINARGS("--plugin-args"), + /** + * Set the default desired client arguments for a driver. + * Default: [] + * Sample: [ '{"xcuitest": {"foo1": "bar1", "foo2": "bar2"}}' | /path/to/driverArgs.json ] + */ + DRIVERARGS("--driver-args"); private final String arg; From 93a125e5d57f51c94c50954242d2119b5e5885bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 14:34:09 +0300 Subject: [PATCH 113/630] build(deps): bump actions/setup-java from 1 to 2.3.0 (#1508) * build(deps): bump actions/setup-java from 1 to 2.3.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 1 to 2.3.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v1...v2.3.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Use Zulu distribution of OpenJDK https://github.com/actions/setup-java/blob/main/docs/switching-to-v2.md: > Use the `zulu` keyword if you would like to continue using the same distribution as in V1. * Fix JDK version: 1.8 -> 8 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Valery Yatsynovich --- .github/workflows/gradle.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 44e24a9e0..dbd99a5ee 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,9 +15,10 @@ jobs: steps: - uses: actions/checkout@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@v2.3.0 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Build with Gradle run: ./gradlew clean build -x signMavenJavaPublication -x test -x checkstyleTest From f8df435a9d09782d091b124c4eab07049adae0fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 16:48:08 +0300 Subject: [PATCH 114/630] build(deps): bump webdrivermanager from 4.4.3 to 5.0.1 (#1512) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 4.4.3 to 5.0.1. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-4.4.3...webdrivermanager-5.0.1) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d827cd41d..9df08d75a 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '4.4.3') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.0.1') { exclude group: 'org.seleniumhq.selenium' } } From 4397523c50c99e5857ccc30e33cc85d1c0ef726b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Sep 2021 16:48:30 +0300 Subject: [PATCH 115/630] build(deps): bump org.owasp.dependencycheck from 6.2.0 to 6.3.1 (#1513) Bumps org.owasp.dependencycheck from 6.2.0 to 6.3.1. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9df08d75a..ce186c9be 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.2.0' + id 'org.owasp.dependencycheck' version '6.3.1' id 'com.github.johnrengelman.shadow' version '6.1.0' } From 8bec046c903c7e4947d6467e59f8f6016b2cdc04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Sep 2021 11:27:21 +0300 Subject: [PATCH 116/630] build(deps): bump com.github.johnrengelman.shadow from 6.1.0 to 7.0.0 (#1468) Bumps com.github.johnrengelman.shadow from 6.1.0 to 7.0.0. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ce186c9be..991176d72 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'checkstyle' id 'signing' id 'org.owasp.dependencycheck' version '6.3.1' - id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'com.github.johnrengelman.shadow' version '7.0.0' } repositories { From 973a684809b8dbfe514abfa0d81db6dffc54096a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Sep 2021 16:34:23 +0300 Subject: [PATCH 117/630] build(deps): bump webdrivermanager from 5.0.1 to 5.0.2 (#1514) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.0.1 to 5.0.2. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-5.0.1...webdrivermanager-5.0.2) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 991176d72..67b47b56f 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.0.1') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.0.2') { exclude group: 'org.seleniumhq.selenium' } } From 381368c14f0d188926bc12f983af27370a917403 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:32:27 +0530 Subject: [PATCH 118/630] build(deps): bump spring-context from 5.3.9 to 5.3.10 (#1517) Bumps [spring-context](https://github.com/spring-projects/spring-framework) from 5.3.9 to 5.3.10. - [Release notes](https://github.com/spring-projects/spring-framework/releases) - [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.9...v5.3.10) --- updated-dependencies: - dependency-name: org.springframework:spring-context dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67b47b56f..6213e2798 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'commons-io:commons-io:2.11.0' - implementation 'org.springframework:spring-context:5.3.9' + implementation 'org.springframework:spring-context:5.3.10' implementation 'org.aspectj:aspectjweaver:1.9.7' implementation 'org.slf4j:slf4j-api:1.7.32' From 2f4062adddc9233471a21d5c8a87e508112405b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:32:51 +0530 Subject: [PATCH 119/630] build(deps): bump webdrivermanager from 5.0.2 to 5.0.3 (#1516) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.0.2 to 5.0.3. - [Release notes](https://github.com/bonigarcia/webdrivermanager/releases) - [Changelog](https://github.com/bonigarcia/webdrivermanager/blob/master/CHANGELOG.md) - [Commits](https://github.com/bonigarcia/webdrivermanager/compare/webdrivermanager-5.0.2...webdrivermanager-5.0.3) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6213e2798..e5e16a6f9 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.0.2') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.0.3') { exclude group: 'org.seleniumhq.selenium' } } From f7c08a2fb591f8fc58d6ffab11d9b046435f985a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 14:53:55 +0300 Subject: [PATCH 120/630] build(deps): bump org.owasp.dependencycheck from 6.3.1 to 6.3.2 (#1523) Bumps org.owasp.dependencycheck from 6.3.1 to 6.3.2. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e5e16a6f9..e2558dd3e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '6.3.1' + id 'org.owasp.dependencycheck' version '6.3.2' id 'com.github.johnrengelman.shadow' version '7.0.0' } From c7d01e749b891dad5958aa87580033d1ad3df65d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:50:06 +0300 Subject: [PATCH 121/630] build(deps): bump actions/setup-java from 2.3.0 to 2.3.1 (#1524) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v2.3.0...v2.3.1) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index dbd99a5ee..ce066fb6f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up JDK 8 - uses: actions/setup-java@v2.3.0 + uses: actions/setup-java@v2.3.1 with: distribution: 'zulu' java-version: 8 From 3466797b6627b8a1924198f4be8e95decf6db94c Mon Sep 17 00:00:00 2001 From: dr29bart Date: Thu, 7 Oct 2021 00:08:51 -0500 Subject: [PATCH 122/630] =?UTF-8?q?fix:=20[android]=20AndroidGeoLocation:?= =?UTF-8?q?=20update=20the=20constructor=20signature=20to=20mim=E2=80=A6?= =?UTF-8?q?=20(#1526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java_client/android/geolocation/AndroidGeoLocation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java b/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java index 9ab204317..f04a41fe2 100644 --- a/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java +++ b/src/main/java/io/appium/java_client/android/geolocation/AndroidGeoLocation.java @@ -39,10 +39,10 @@ public AndroidGeoLocation() { /** * Initializes AndroidLocation instance with longitude and latitude values. * - * @param longitude longitude value * @param latitude latitude value + * @param longitude longitude value */ - public AndroidGeoLocation(double longitude, double latitude) { + public AndroidGeoLocation(double latitude, double longitude) { this.longitude = longitude; this.latitude = latitude; } From 1d15347a66f1b8922e8528c9fd46907f388a055d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 15:15:09 +0300 Subject: [PATCH 123/630] build(deps): bump lombok from 1.18.20 to 1.18.22 (#1527) Bumps [lombok](https://github.com/projectlombok/lombok) from 1.18.20 to 1.18.22. - [Release notes](https://github.com/projectlombok/lombok/releases) - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.20...v1.18.22) --- updated-dependencies: - dependency-name: org.projectlombok:lombok dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e2558dd3e..d7e398b19 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ configurations { dependencies { ecj 'org.eclipse.jdt:ecj:3.26.0' - lombok 'org.projectlombok:lombok:1.18.20' + lombok 'org.projectlombok:lombok:1.18.22' } java { @@ -50,7 +50,7 @@ compileJava { } dependencies { - compileOnly('org.projectlombok:lombok:1.18.16') + compileOnly('org.projectlombok:lombok:1.18.22') annotationProcessor('org.projectlombok:lombok:1.18.20') api ("org.seleniumhq.selenium:selenium-java") { version { From f951fe8676fac4a77370bcc58f25cdb1c8fd89a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 15:23:51 +0300 Subject: [PATCH 124/630] build(deps): bump com.github.johnrengelman.shadow from 7.0.0 to 7.1.0 (#1528) Bumps com.github.johnrengelman.shadow from 7.0.0 to 7.1.0. --- updated-dependencies: - dependency-name: com.github.johnrengelman.shadow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7e398b19..c57bdfab5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'checkstyle' id 'signing' id 'org.owasp.dependencycheck' version '6.3.2' - id 'com.github.johnrengelman.shadow' version '7.0.0' + id 'com.github.johnrengelman.shadow' version '7.1.0' } repositories { From f2e8f93fd613d6570e8ce515d4a85a70e9872305 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 11 Oct 2021 19:26:23 +0530 Subject: [PATCH 125/630] Release 7.6.0 and update release notes --- README.md | 25 +++++++++++++++++++++++++ build.gradle | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bfcc35e51..f7d6b0c69 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,31 @@ dependencies { ``` ## Changelog +*7.6.0* +- **[ENHANCEMENTS]** + - Add custom commands dynamically [Appium 2.0]. [#1506](https://github.com/appium/java-client/pull/1506) + - New General Server flags are added [Appium 2.0]. [#1511](https://github.com/appium/java-client/pull/1511) + - Add support of extended Android geolocation. [#1492](https://github.com/appium/java-client/pull/1492) +- **[BUG FIX]** + - AndroidGeoLocation: update the constructor signature to mimic order of parameters in `org.openqa.selenium.html5.Location`. [#1526](https://github.com/appium/java-client/pull/1526) + - Prevent duplicate builds for PRs from base repo branches. [#1496](https://github.com/appium/java-client/pull/1496) + - Enable Dependabot for GitHub actions. [#1500](https://github.com/appium/java-client/pull/1500) + - bind mac2element in element map for mac platform. [#1474](https://github.com/appium/java-client/pull/1474) +- **[DEPENDENCY UPDATES]** + - `org.owasp.dependencycheck` was updated to 6.3.2. + - `org.projectlombok:lombok` was updated to 1.18.22. + - `com.github.johnrengelman.shadow` was updated to 7.1.0. + - `actions/setup-java` was updated to 2.3.1. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.0.3. + - `org.springframework:spring-context` was updated to 5.3.10. + - `org.slf4j:slf4j-api` was updated to 1.7.32. + - `com.google.code.gson:gson` was updated to 2.8.8. + - `gradle` was updated to 7.1.1. + - `commons-io:commons-io` was updated to 2.11.0. + - `org.aspectj:aspectjweaver` was updated to 1.9.7. + - `org.eclipse.jdt:ecj` was updated to 3.26.0. + - `'junit:junit` was updated to 4.13.2. + *7.5.1* - **[ENHANCEMENTS]** - Add iOS related annotations to tvOS. [#1456](https://github.com/appium/java-client/pull/1456) diff --git a/build.gradle b/build.gradle index c57bdfab5..356beb536 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '7.5.1' + version = '7.6.0' from components.java pom { name = 'java-client' From 8057ac63abe8a6c8408c6a4f9fb3149c90a4ffc5 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 21 Oct 2021 05:19:34 +0300 Subject: [PATCH 126/630] refactor!: Migrate to Selenium 4 (#1531) * refactor!: migrate to Selenium 4 BREAKING CHANGE: - interface `io.appium.java_client.MobileDriver` do not extend `org.openqa.selenium.internal.FindsByClassName`, `org.openqa.selenium.internal.FindsByCssSelector`, `org.openqa.selenium.internal.FindsById`, `org.openqa.selenium.internal.FindsByLinkText`, `org.openqa.selenium.internal.FindsByName`, `org.openqa.selenium.internal.FindsByTagName`, `org.openqa.selenium.internal.FindsByXPath` interfaces anymore because they were removed in Selenium Java client; - class `io.appium.java_client.DefaultGenericMobileElement` do not implement `org.openqa.selenium.internal.FindsByClassName`, `org.openqa.selenium.internal.FindsByCssSelector`, `org.openqa.selenium.internal.FindsById`, `org.openqa.selenium.internal.FindsByLinkText`, `org.openqa.selenium.internal.FindsByName`, `org.openqa.selenium.internal.FindsByTagName`, `org.openqa.selenium.internal.FindsByXPath` interfaces anymore because they were removed in Selenium Java client; - method `String io.appium.java_client.remote.MobileOptions#getPlatformName()` is removed in favor of `Platform org.openqa.selenium.Capabilities#getPlatformName()` - method `io.appium.java_client.service.local.AppiumServiceBuilder#withStartUpTimeOut` is removed in favor of `org.openqa.selenium.remote.service.DriverService.Builder#withTimeout` * refactor!: drop Appium FindsBy* iterfaces BREAKING CHANGE: - drop Appium `FindsBy*` iterfaces in the same way it was done in Selenium java client. The removed intefraces are: `io.appium.java_client.FindsByAccessibilityId`, `io.appium.java_client.FindsByAndroidDataMatcher`, `io.appium.java_client.FindsByAndroidUIAutomator`, `io.appium.java_client.FindsByAndroidViewMatcher`, `io.appium.java_client.FindsByAndroidViewTag`, `io.appium.java_client.FindsByCustom`, `io.appium.java_client.FindsByFluentSelector`, `io.appium.java_client.FindsByImage`, `io.appium.java_client.FindsByIosClassChain`, `io.appium.java_client.FindsByIosNSPredicate`, `io.appium.java_client.FindsByWindowsAutomation`, `io.appium.java_client.mac.FindsByClassChain`, `io.appium.java_client.mac.FindsByNsPredicate` - remove methods `findElements(String by, String using)` and `findElement(String by, String using)` from `io.appium.java_client.DefaultGenericMobileDriver` and `io.appium.java_client.DefaultGenericMobileElement` because the originals of these methods are deprecated in Selenium `RemoteWebDriver` and `RemoteWebElement` and throw `UnsupportedOperationException` - remove `io.appium.java_client.MobileSelector` as it's used once, the string values from the enum are inlined in `io.appium.java_client.MobileBy.java` * fix: introduce MobileBy.className The change made in Selenium 4 (https://github.com/SeleniumHQ/selenium/commit/0aaa401fde5f4a5ecf2d2a2325221307b9bb3e89#r58091435) broke Appium `class name` selector strategy. The workaround was implemented: `MobileBy#className`. * refactor!: drop deprecated method `AppiumDriver#substituteMobilePlatform` BREAKING CHANGE: drop deprecated method `io.appium.java_client.AppiumDriver#substituteMobilePlatform` --- gradle.properties | 2 +- .../io/appium/java_client/AppiumDriver.java | 63 +- .../DefaultGenericMobileDriver.java | 103 ---- .../DefaultGenericMobileElement.java | 132 +---- .../java_client/DriverMobileCommand.java | 27 - .../java_client/FindsByAccessibilityId.java | 52 -- .../FindsByAndroidDataMatcher.java | 32 - .../FindsByAndroidUIAutomator.java | 53 -- .../FindsByAndroidViewMatcher.java | 32 - .../java_client/FindsByAndroidViewTag.java | 52 -- .../io/appium/java_client/FindsByCustom.java | 55 -- .../java_client/FindsByFluentSelector.java | 51 -- .../io/appium/java_client/FindsByImage.java | 63 -- .../java_client/FindsByIosClassChain.java | 32 - .../java_client/FindsByIosNSPredicate.java | 32 - .../java_client/FindsByWindowsAutomation.java | 50 -- .../IllegalCoordinatesException.java | 28 - .../java/io/appium/java_client/MobileBy.java | 559 ++---------------- .../io/appium/java_client/MobileDriver.java | 43 +- .../io/appium/java_client/MobileElement.java | 42 -- .../io/appium/java_client/MobileSelector.java | 41 -- .../appium/java_client/android/Activity.java | 1 - .../java_client/android/AndroidDriver.java | 15 +- .../java_client/android/AndroidElement.java | 8 +- .../java_client/android/AndroidOptions.java | 10 +- .../java_client/events/DefaultAspect.java | 13 - .../JsonToMobileElementConverter.java | 2 +- .../io/appium/java_client/ios/IOSDriver.java | 7 +- .../io/appium/java_client/ios/IOSElement.java | 5 +- .../io/appium/java_client/ios/IOSOptions.java | 10 +- .../java_client/mac/FindsByClassChain.java | 50 -- .../java_client/mac/FindsByNsPredicate.java | 50 -- .../io/appium/java_client/mac/Mac2Driver.java | 4 +- .../appium/java_client/mac/Mac2Element.java | 3 +- .../pagefactory/bys/builder/Strategies.java | 2 +- .../remote/AppiumCommandExecutor.java | 19 +- .../remote/AppiumW3CHttpCommandCodec.java | 2 +- .../java_client/remote/MobileOptions.java | 13 +- .../local/AppiumDriverLocalService.java | 26 +- .../service/local/AppiumServiceBuilder.java | 34 +- .../touch/offset/ElementOption.java | 8 +- .../java_client/windows/WindowsDriver.java | 4 +- .../java_client/windows/WindowsElement.java | 3 +- .../java_client/ws/StringWebSocketClient.java | 41 +- .../java/org/openqa/selenium/WebDriver.java | 335 +++++++++-- .../java/org/openqa/selenium/WebElement.java | 179 ++++-- .../selenium/internal/FindsByClassName.java | 28 - .../selenium/internal/FindsByCssSelector.java | 28 - .../openqa/selenium/internal/FindsById.java | 28 - .../selenium/internal/FindsByLinkText.java | 32 - .../openqa/selenium/internal/FindsByName.java | 28 - .../selenium/internal/FindsByTagName.java | 28 - .../selenium/internal/FindsByXPath.java | 28 - .../AndroidAbilityToUseSupplierTest.java | 28 +- .../android/AndroidElementTest.java | 16 +- .../android/AndroidOptionsTest.java | 7 +- .../android/AndroidSearchingTest.java | 14 +- .../java_client/android/AndroidTouchTest.java | 41 +- .../java_client/android/FingerPrintTest.java | 13 +- .../java_client/android/IntentTest.java | 3 +- .../java_client/android/UIAutomator2Test.java | 6 +- .../java_client/appium/AndroidTest.java | 16 +- .../ios/IOSElementGenerationTest.java | 8 +- .../AbilityToDefineListenersExternally.java | 16 +- .../java_client/events/BaseListenerTest.java | 82 ++- .../events/DefaultEventListenerTest.java | 19 +- .../java_client/events/EmptyWebDriver.java | 105 +--- .../events/ExtendedEventListenerTest.java | 4 +- .../java_client/events/FewInstancesTest.java | 18 +- .../java_client/events/StubWebElement.java | 97 +-- .../appium/java_client/events/StubWindow.java | 5 + ...bDriverEventListenerCompatibilityTest.java | 11 +- .../appium/java_client/ios/IOSDriverTest.java | 10 +- .../java_client/ios/IOSElementTest.java | 7 +- .../ios/IOSNativeWebTapSettingTest.java | 13 +- .../java_client/ios/IOSOptionsTest.java | 3 +- .../java_client/ios/IOSSearchingTest.java | 16 +- .../appium/java_client/ios/IOSTouchTest.java | 27 +- .../java_client/ios/IOSWebViewTest.java | 10 +- 79 files changed, 756 insertions(+), 2427 deletions(-) delete mode 100644 src/main/java/io/appium/java_client/DriverMobileCommand.java delete mode 100644 src/main/java/io/appium/java_client/FindsByAccessibilityId.java delete mode 100644 src/main/java/io/appium/java_client/FindsByAndroidDataMatcher.java delete mode 100644 src/main/java/io/appium/java_client/FindsByAndroidUIAutomator.java delete mode 100644 src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java delete mode 100644 src/main/java/io/appium/java_client/FindsByAndroidViewTag.java delete mode 100644 src/main/java/io/appium/java_client/FindsByCustom.java delete mode 100644 src/main/java/io/appium/java_client/FindsByFluentSelector.java delete mode 100644 src/main/java/io/appium/java_client/FindsByImage.java delete mode 100644 src/main/java/io/appium/java_client/FindsByIosClassChain.java delete mode 100644 src/main/java/io/appium/java_client/FindsByIosNSPredicate.java delete mode 100644 src/main/java/io/appium/java_client/FindsByWindowsAutomation.java delete mode 100644 src/main/java/io/appium/java_client/IllegalCoordinatesException.java delete mode 100644 src/main/java/io/appium/java_client/MobileSelector.java delete mode 100644 src/main/java/io/appium/java_client/mac/FindsByClassChain.java delete mode 100644 src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByClassName.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByCssSelector.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsById.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByLinkText.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByName.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByTagName.java delete mode 100644 src/main/java/org/openqa/selenium/internal/FindsByXPath.java diff --git a/gradle.properties b/gradle.properties index 9bd535700..d2d4a6a43 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,4 @@ signing.secretKeyRingFile=PathToYourKeyRingFile ossrhUsername=your-jira-id ossrhPassword=your-jira-password -selenium.version=3.141.59 +selenium.version=4.0.0 diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index fd512197a..1e880c569 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -68,8 +68,7 @@ */ @SuppressWarnings("unchecked") public class AppiumDriver - extends DefaultGenericMobileDriver implements ComparesImages, FindsByImage, FindsByCustom, - ExecutesDriverScript, LogsEvents, HasSettings { + extends DefaultGenericMobileDriver implements ComparesImages, ExecutesDriverScript, LogsEvents, HasSettings { private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters @@ -134,23 +133,6 @@ public AppiumDriver(Capabilities desiredCapabilities) { this(AppiumDriverLocalService.buildDefaultService(), desiredCapabilities); } - /** - * Changes platform name and returns new capabilities. - * - * @param originalCapabilities the given {@link Capabilities}. - * @param newPlatform a {@link MobileCapabilityType#PLATFORM_NAME} value which has - * to be set up - * @return {@link Capabilities} with changed mobile platform value - * @deprecated Please use {@link #updateDefaultPlatformName(Capabilities, String)} instead - */ - @Deprecated - protected static Capabilities substituteMobilePlatform(Capabilities originalCapabilities, - String newPlatform) { - DesiredCapabilities dc = new DesiredCapabilities(originalCapabilities); - dc.setCapability(PLATFORM_NAME, newPlatform); - return dc; - } - /** * Changes platform name if it is not set and returns new capabilities. * @@ -174,49 +156,6 @@ public List findElements(By by) { return super.findElements(by); } - @Override - public List findElements(String by, String using) { - return super.findElements(by, using); - } - - @Override - public List findElementsById(String id) { - return super.findElementsById(id); - } - - public List findElementsByLinkText(String using) { - return super.findElementsByLinkText(using); - } - - public List findElementsByPartialLinkText(String using) { - return super.findElementsByPartialLinkText(using); - } - - public List findElementsByTagName(String using) { - return super.findElementsByTagName(using); - } - - public List findElementsByName(String using) { - return super.findElementsByName(using); - } - - public List findElementsByClassName(String using) { - return super.findElementsByClassName(using); - } - - public List findElementsByCssSelector(String using) { - return super.findElementsByCssSelector(using); - } - - public List findElementsByXPath(String using) { - return super.findElementsByXPath(using); - } - - @Override - public List findElementsByAccessibilityId(String using) { - return super.findElementsByAccessibilityId(using); - } - @Override public ExecuteMethod getExecuteMethod() { return executeMethod; diff --git a/src/main/java/io/appium/java_client/DefaultGenericMobileDriver.java b/src/main/java/io/appium/java_client/DefaultGenericMobileDriver.java index 0ca7a1dcc..e6d45f5f8 100644 --- a/src/main/java/io/appium/java_client/DefaultGenericMobileDriver.java +++ b/src/main/java/io/appium/java_client/DefaultGenericMobileDriver.java @@ -20,7 +20,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; -import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.CommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; @@ -49,112 +48,10 @@ public DefaultGenericMobileDriver(CommandExecutor executor, Capabilities desired return super.findElements(by); } - @Override public List findElements(String by, String using) { - return super.findElements(by, using); - } - @Override public T findElement(By by) { return (T) super.findElement(by); } - @Override public T findElement(String by, String using) { - return (T) super.findElement(by, using); - } - - @Override public List findElementsById(String id) { - return super.findElementsById(id); - } - - @Override public T findElementById(String id) { - return (T) super.findElementById(id); - } - - /** - * Finds a single element by link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByLinkText(String using) throws WebDriverException { - return (T) super.findElementByLinkText(using); - } - - /** - * Finds many elements by link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByLinkText(String using) throws WebDriverException { - return super.findElementsByLinkText(using); - } - - /** - * Finds a single element by partial link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByPartialLinkText(String using) throws WebDriverException { - return (T) super.findElementByPartialLinkText(using); - } - - /** - * Finds many elements by partial link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByPartialLinkText(String using) throws WebDriverException { - return super.findElementsByPartialLinkText(using); - } - - public T findElementByTagName(String using) { - return (T) super.findElementByTagName(using); - } - - public List findElementsByTagName(String using) { - return super.findElementsByTagName(using); - } - - public T findElementByName(String using) { - return (T) super.findElementByName(using); - } - - public List findElementsByName(String using) { - return super.findElementsByName(using); - } - - public T findElementByClassName(String using) { - return (T) super.findElementByClassName(using); - } - - public List findElementsByClassName(String using) { - return super.findElementsByClassName(using); - } - - /** - * Finds a single element by CSS selector. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByCssSelector(String using) throws WebDriverException { - return (T) super.findElementByCssSelector(using); - } - - /** - * Finds many elements by CSS selector. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByCssSelector(String using) throws WebDriverException { - return super.findElementsByCssSelector(using); - } - - public T findElementByXPath(String using) { - return (T) super.findElementByXPath(using); - } - - public List findElementsByXPath(String using) { - return super.findElementsByXPath(using); - } - @Override public String toString() { Capabilities capabilities = getCapabilities(); diff --git a/src/main/java/io/appium/java_client/DefaultGenericMobileElement.java b/src/main/java/io/appium/java_client/DefaultGenericMobileElement.java index 2d8154880..6e1bfc64a 100644 --- a/src/main/java/io/appium/java_client/DefaultGenericMobileElement.java +++ b/src/main/java/io/appium/java_client/DefaultGenericMobileElement.java @@ -21,13 +21,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.FindsByClassName; -import org.openqa.selenium.internal.FindsByCssSelector; -import org.openqa.selenium.internal.FindsById; -import org.openqa.selenium.internal.FindsByLinkText; -import org.openqa.selenium.internal.FindsByName; -import org.openqa.selenium.internal.FindsByTagName; -import org.openqa.selenium.internal.FindsByXPath; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.remote.Response; @@ -35,11 +28,7 @@ import java.util.Map; @SuppressWarnings({"unchecked", "rawtypes"}) -abstract class DefaultGenericMobileElement extends RemoteWebElement - implements FindsByClassName, - FindsByCssSelector, FindsById, - FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector, FindsByAccessibilityId, - ExecutesMethod { +abstract class DefaultGenericMobileElement extends RemoteWebElement implements ExecutesMethod { @Override public Response execute(String driverCommand, Map parameters) { return super.execute(driverCommand, parameters); @@ -53,127 +42,8 @@ abstract class DefaultGenericMobileElement extends RemoteW return super.findElements(by); } - @Override public List findElements(String by, String using) { - return super.findElements(by, using); - } - @Override public T findElement(By by) { return (T) super.findElement(by); } - @Override public T findElement(String by, String using) { - return (T) super.findElement(by, using); - } - - @Override public List findElementsById(String id) { - return super.findElementsById(id); - } - - @Override public T findElementById(String id) { - return (T) super.findElementById(id); - } - - /** - * Finds a single element by link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByLinkText(String using) throws WebDriverException { - return (T) super.findElementByLinkText(using); - } - - /** - * Finds many elements by link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByLinkText(String using) throws WebDriverException { - return super.findElementsByLinkText(using); - } - - /** - * Finds a single element by partial link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByPartialLinkText(String using) throws WebDriverException { - return (T) super.findElementByPartialLinkText(using); - } - - /** - * Finds many elements by partial link text. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByPartialLinkText(String using) throws WebDriverException { - return super.findElementsByPartialLinkText(using); - } - - public T findElementByTagName(String using) { - return (T) super.findElementByTagName(using); - } - - public List findElementsByTagName(String using) { - return super.findElementsByTagName(using); - } - - public T findElementByName(String using) { - return (T) super.findElementByName(using); - } - - public List findElementsByName(String using) { - return super.findElementsByName(using); - } - - public T findElementByClassName(String using) { - return (T) super.findElementByClassName(using); - } - - public List findElementsByClassName(String using) { - return super.findElementsByClassName(using); - } - - /** - * Finds a single element by CSS selector. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public T findElementByCssSelector(String using) throws WebDriverException { - return (T) super.findElementByCssSelector(using); - } - - /** - * Finds many elements by CSS selector. - * - * @throws WebDriverException This method doesn't work against native app UI. - */ - public List findElementsByCssSelector(String using) throws WebDriverException { - return super.findElementsByCssSelector(using); - } - - public T findElementByXPath(String using) { - return (T) super.findElementByXPath(using); - } - - public List findElementsByXPath(String using) { - return super.findElementsByXPath(using); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException because it may not work against native app UI. - */ - public void submit() throws WebDriverException { - super.submit(); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException because it may not work against native app UI. - */ - public String getCssValue(String propertyName) throws WebDriverException { - return super.getCssValue(propertyName); - } } diff --git a/src/main/java/io/appium/java_client/DriverMobileCommand.java b/src/main/java/io/appium/java_client/DriverMobileCommand.java deleted file mode 100644 index 9d991d3cb..000000000 --- a/src/main/java/io/appium/java_client/DriverMobileCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -/** - * An empty interface defining constants for the standard commands defined in the Mobile JSON - * wire protocol. - * - * @author jonahss@gmail.com (Jonah Stiennon) - */ -public interface DriverMobileCommand { - //TODO Jonah: we'll probably need this -} diff --git a/src/main/java/io/appium/java_client/FindsByAccessibilityId.java b/src/main/java/io/appium/java_client/FindsByAccessibilityId.java deleted file mode 100644 index 4d79a33a4..000000000 --- a/src/main/java/io/appium/java_client/FindsByAccessibilityId.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByAccessibilityId extends FindsByFluentSelector { - /** - * Method performs the searching for a single element by accessibility ID selector - * and value of the given selector. - * - * @param using an accessibility ID selector - * @return The first element that matches the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - * @throws NoSuchElementException when no one element is found - */ - default T findElementByAccessibilityId(String using) { - return findElement(MobileSelector.ACCESSIBILITY.toString(), using); - } - - /** - * Method performs the searching for a list of elements by accessibility ID selector - * and value of the given selector. - * - * @param using an accessibility ID selector - * @return a list of elements that match the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - */ - default List findElementsByAccessibilityId(String using) { - return findElements(MobileSelector.ACCESSIBILITY.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByAndroidDataMatcher.java b/src/main/java/io/appium/java_client/FindsByAndroidDataMatcher.java deleted file mode 100644 index a60477870..000000000 --- a/src/main/java/io/appium/java_client/FindsByAndroidDataMatcher.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByAndroidDataMatcher extends FindsByFluentSelector { - - default T findElementByAndroidDataMatcher(String using) { - return findElement(MobileSelector.ANDROID_DATA_MATCHER.toString(), using); - } - - default List findElementsByAndroidDataMatcher(String using) { - return findElements(MobileSelector.ANDROID_DATA_MATCHER.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByAndroidUIAutomator.java b/src/main/java/io/appium/java_client/FindsByAndroidUIAutomator.java deleted file mode 100644 index 50c7bafba..000000000 --- a/src/main/java/io/appium/java_client/FindsByAndroidUIAutomator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByAndroidUIAutomator extends FindsByFluentSelector { - - /** - * Method performs the searching for a single element by Android UIAutomator selector - * and value of the given selector. - * - * @param using an Android UIAutomator selector - * @return The first element that matches the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - * @throws NoSuchElementException when no one element is found - */ - default T findElementByAndroidUIAutomator(String using) { - return findElement(MobileSelector.ANDROID_UI_AUTOMATOR.toString(), using); - } - - /** - * Method performs the searching for a list of elements by Android UIAutomator selector - * and value of the given selector. - * - * @param using an Android UIAutomator selector - * @return a list of elements that match the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - */ - default List findElementsByAndroidUIAutomator(String using) { - return findElements(MobileSelector.ANDROID_UI_AUTOMATOR.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java b/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java deleted file mode 100644 index 1370cf3ae..000000000 --- a/src/main/java/io/appium/java_client/FindsByAndroidViewMatcher.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByAndroidViewMatcher extends FindsByFluentSelector { - - default T findElementByAndroidViewMatcher(String using) { - return findElement(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using); - } - - default List findElementsByAndroidViewMatcher(String using) { - return findElements(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByAndroidViewTag.java b/src/main/java/io/appium/java_client/FindsByAndroidViewTag.java deleted file mode 100644 index b1db5c432..000000000 --- a/src/main/java/io/appium/java_client/FindsByAndroidViewTag.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByAndroidViewTag extends FindsByFluentSelector { - /** - * Method performs the searching for a single element by view tag selector - * and value of the given selector. - * - * @param using an view tag selector - * @return The first element that matches the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - * @throws NoSuchElementException when no one element is found - */ - default T findElementByAndroidViewTag(String using) { - return findElement(MobileSelector.ANDROID_VIEWTAG.toString(), using); - } - - /** - * Method performs the searching for a list of elements by view tag selector - * and value of the given selector. - * - * @param using an view tag selector - * @return a list of elements that match the given selector - * - * @throws WebDriverException This method is not applicable with browser/webview UI. - */ - default List findElementsByAndroidViewTag(String using) { - return findElements(MobileSelector.ANDROID_VIEWTAG.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByCustom.java b/src/main/java/io/appium/java_client/FindsByCustom.java deleted file mode 100644 index f908fc424..000000000 --- a/src/main/java/io/appium/java_client/FindsByCustom.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByCustom extends FindsByFluentSelector { - /** - * Performs the lookup for a single element by sending a selector to a custom element finding - * plugin. This type of locator requires the use of the 'customFindModules' capability and a - * separately-installed element finding plugin. - * - * @param selector selector to pass to the custom element finding plugin - * @return The first element that matches the given selector - * @see - * The documentation on custom element finding plugins and their use - * @throws NoSuchElementException when no element is found - * @since Appium 1.9.2 - */ - default T findElementByCustom(String selector) { - return findElement(MobileSelector.CUSTOM.toString(), selector); - } - - /** - * Performs the lookup for a single element by sending a selector to a custom element finding - * plugin. This type of locator requires the use of the 'customFindModules' capability and a - * separately-installed element finding plugin. - * - * @param selector selector to pass to the custom element finding plugin - * @return a list of elements that match the given selector or an empty list - * @see - * The documentation on custom element finding plugins and their use - * @since Appium 1.9.2 - */ - default List findElementsByCustom(String selector) { - return findElements(MobileSelector.CUSTOM.toString(), selector); - } -} \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/FindsByFluentSelector.java b/src/main/java/io/appium/java_client/FindsByFluentSelector.java deleted file mode 100644 index f545f47be..000000000 --- a/src/main/java/io/appium/java_client/FindsByFluentSelector.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByFluentSelector { - - /** - * Method performs the searching for a single element by some selector defined by string - * and value of the given selector. - * - * @param by is a string selector - * @param using is a value of the given selector - * @return the first found element - * - * @throws org.openqa.selenium.WebDriverException when current session doesn't - * support the given selector or when value of the selector is not consistent. - * @throws org.openqa.selenium.NoSuchElementException when no one element is found - */ - T findElement(String by, String using); - - /** - * Method performs the searching for a list of elements by some selector defined by string - * and value of the given selector. - * - * @param by is a string selector - * @param using is a value of the given selector - * @return a list of elements - * - * @throws org.openqa.selenium.WebDriverException when current session doesn't support - * the given selector or when value of the selector is not consistent. - */ - List findElements(String by, String using); -} diff --git a/src/main/java/io/appium/java_client/FindsByImage.java b/src/main/java/io/appium/java_client/FindsByImage.java deleted file mode 100644 index 76de64fd1..000000000 --- a/src/main/java/io/appium/java_client/FindsByImage.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByImage extends FindsByFluentSelector { - /** - * Performs the lookup for a single element by matching its image template - * to the current full screen shot. This type of locator requires OpenCV libraries - * and bindings for NodeJS to be installed on the server machine. Lookup options - * fine-tuning might be done via {@link HasSettings#setSetting(Setting, Object)}. - * - * @param b64Template base64-encoded template image string. Supported image formats are the same - * as for OpenCV library. - * @return The first element that matches the given selector - * @throws NoSuchElementException when no element is found - * @see - * The documentation on Image Comparison Features - * @see - * The settings available for lookup fine-tuning - * @since Appium 1.8.2 - */ - default T findElementByImage(String b64Template) { - return findElement(MobileSelector.IMAGE.toString(), b64Template); - } - - /** - * Performs the lookup for a list of elements by matching them to image template - * in the current full screen shot. This type of locator requires OpenCV libraries - * and bindings for NodeJS to be installed on the server machine. Lookup options - * fine-tuning might be done via {@link HasSettings#setSetting(Setting, Object)}. - * - * @param b64Template base64-encoded template image string. Supported image formats are the same - * as for OpenCV library. - * @return a list of elements that match the given selector or an empty list - * @see - * The documentation on Image Comparison Features - * @see - * The settings available for lookup fine-tuning - * @since Appium 1.8.2 - */ - default List findElementsByImage(String b64Template) { - return findElements(MobileSelector.IMAGE.toString(), b64Template); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByIosClassChain.java b/src/main/java/io/appium/java_client/FindsByIosClassChain.java deleted file mode 100644 index 92482663a..000000000 --- a/src/main/java/io/appium/java_client/FindsByIosClassChain.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByIosClassChain extends FindsByFluentSelector { - - default T findElementByIosClassChain(String using) { - return findElement(MobileSelector.IOS_CLASS_CHAIN.toString(), using); - } - - default List findElementsByIosClassChain(String using) { - return findElements(MobileSelector.IOS_CLASS_CHAIN.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByIosNSPredicate.java b/src/main/java/io/appium/java_client/FindsByIosNSPredicate.java deleted file mode 100644 index 84bc3ff67..000000000 --- a/src/main/java/io/appium/java_client/FindsByIosNSPredicate.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByIosNSPredicate extends FindsByFluentSelector { - - default T findElementByIosNsPredicate(String using) { - return findElement(MobileSelector.IOS_PREDICATE_STRING.toString(), using); - } - - default List findElementsByIosNsPredicate(String using) { - return findElements(MobileSelector.IOS_PREDICATE_STRING.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/FindsByWindowsAutomation.java b/src/main/java/io/appium/java_client/FindsByWindowsAutomation.java deleted file mode 100644 index 4416eb63f..000000000 --- a/src/main/java/io/appium/java_client/FindsByWindowsAutomation.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByWindowsAutomation extends FindsByFluentSelector { - - /** - * Finds the first of elements that match the Windows UIAutomation selector supplied. - * - * @param selector a Windows UIAutomation selector - * @return The first element that matches the given selector - * @throws WebDriverException This method is not applicable with browser/webview UI. - * @throws NoSuchElementException when no one element is found - */ - default T findElementByWindowsUIAutomation(String selector) { - return findElement(MobileSelector.WINDOWS_UI_AUTOMATION.toString(), selector); - } - - /** - * Finds a list of elements that match the Windows UIAutomation selector supplied. - * - * @param selector a Windows UIAutomation selector - * @return a list of elements that match the given selector - * @throws WebDriverException This method is not applicable with browser/webview UI. - */ - default List findElementsByWindowsUIAutomation(String selector) { - return findElements(MobileSelector.WINDOWS_UI_AUTOMATION.toString(), selector); - } -} diff --git a/src/main/java/io/appium/java_client/IllegalCoordinatesException.java b/src/main/java/io/appium/java_client/IllegalCoordinatesException.java deleted file mode 100644 index 8167ad65d..000000000 --- a/src/main/java/io/appium/java_client/IllegalCoordinatesException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -import org.openqa.selenium.WebDriverException; - -public class IllegalCoordinatesException extends WebDriverException { - private static final long serialVersionUID = 1L; - - public IllegalCoordinatesException(String message) { - super(message); - } - -} diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index b8fd3d5db..183656627 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -20,45 +20,39 @@ import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; +import org.openqa.selenium.By.Remotable; import org.openqa.selenium.SearchContext; -import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import java.io.Serializable; import java.util.List; @SuppressWarnings("serial") -public abstract class MobileBy extends By { - - private static final String ERROR_TEXT = "The class %s of the given context " - + "doesn't implement %s nor %s. Sorry. It is impossible to find something."; +public abstract class MobileBy extends By implements Remotable { @Getter(AccessLevel.PROTECTED) private final String locatorString; - private final MobileSelector selector; - - private static IllegalArgumentException formIllegalArgumentException(Class givenClass, - Class class1, Class class2) { - return new IllegalArgumentException(String.format(ERROR_TEXT, givenClass.getCanonicalName(), - class1.getCanonicalName(), class2.getCanonicalName())); - } + private final Parameters parameters; - protected MobileBy(MobileSelector selector, String locatorString) { + protected MobileBy(String selector, String locatorString) { if (StringUtils.isBlank(locatorString)) { throw new IllegalArgumentException("Must supply a not empty locator value."); } this.locatorString = locatorString; - this.selector = selector; + this.parameters = new Parameters(selector, locatorString); } @SuppressWarnings("unchecked") @Override public List findElements(SearchContext context) { - return (List) ((FindsByFluentSelector) context) - .findElements(selector.toString(), getLocatorString()); + return context.findElements(this); } @Override public WebElement findElement(SearchContext context) { - return ((FindsByFluentSelector) context) - .findElement(selector.toString(), getLocatorString()); + return context.findElement(this); + } + + @Override + public Parameters getRemoteParameters() { + return parameters; } /** @@ -168,64 +162,20 @@ public static By custom(final String selector) { return new ByCustom(selector); } + /** + * For IOS it is the full name of the XCUI element and begins with XCUIElementType. + * For Android it is the full name of the UIAutomator2 class (e.g.: android.widget.TextView) + * @param selector the class name of the element + * @return an instance of {@link ByClassName} + */ + public static By className(final String selector) { + return new ByClassName(selector); + } public static class ByAndroidUIAutomator extends MobileBy implements Serializable { - public ByAndroidUIAutomator(String uiautomatorText) { - super(MobileSelector.ANDROID_UI_AUTOMATOR, uiautomatorText); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override - public List findElements(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAndroidUIAutomator.class.isAssignableFrom(contextClass)) { - return FindsByAndroidUIAutomator.class.cast(context) - .findElementsByAndroidUIAutomator(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidUIAutomator.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAndroidUIAutomator.class.isAssignableFrom(contextClass)) { - return FindsByAndroidUIAutomator.class.cast(context) - .findElementByAndroidUIAutomator(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidUIAutomator.class, - FindsByFluentSelector.class); + super("-android uiautomator", uiautomatorText); } @Override public String toString() { @@ -237,59 +187,7 @@ public List findElements(SearchContext context) throws WebDriverExce public static class ByAccessibilityId extends MobileBy implements Serializable { public ByAccessibilityId(String accessibilityId) { - super(MobileSelector.ACCESSIBILITY, accessibilityId); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override - public List findElements(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAccessibilityId.class.isAssignableFrom(contextClass)) { - return FindsByAccessibilityId.class.cast(context) - .findElementsByAccessibilityId(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAccessibilityId.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAccessibilityId.class.isAssignableFrom(contextClass)) { - return FindsByAccessibilityId.class.cast(context) - .findElementByAccessibilityId(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAccessibilityId.class, - FindsByFluentSelector.class); + super("accessibility id", accessibilityId); } @Override public String toString() { @@ -300,56 +198,7 @@ public List findElements(SearchContext context) throws WebDriverExce public static class ByIosClassChain extends MobileBy implements Serializable { protected ByIosClassChain(String locatorString) { - super(MobileSelector.IOS_CLASS_CHAIN, locatorString); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByIosClassChain.class.isAssignableFrom(contextClass)) { - return FindsByIosClassChain.class.cast(context) - .findElementsByIosClassChain(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByIosClassChain.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByIosClassChain.class.isAssignableFrom(contextClass)) { - return FindsByIosClassChain.class.cast(context) - .findElementByIosClassChain(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByIosClassChain.class, - FindsByFluentSelector.class); + super("-ios class chain", locatorString); } @Override public String toString() { @@ -360,176 +209,29 @@ protected ByIosClassChain(String locatorString) { public static class ByAndroidDataMatcher extends MobileBy implements Serializable { protected ByAndroidDataMatcher(String locatorString) { - super(MobileSelector.ANDROID_DATA_MATCHER, locatorString); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByAndroidDataMatcher.class.isAssignableFrom(contextClass)) { - return FindsByAndroidDataMatcher.class.cast(context) - .findElementsByAndroidDataMatcher(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidDataMatcher.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByAndroidDataMatcher.class.isAssignableFrom(contextClass)) { - return FindsByAndroidDataMatcher.class.cast(context) - .findElementByAndroidDataMatcher(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidDataMatcher.class, - FindsByFluentSelector.class); + super("-android datamatcher", locatorString); } @Override public String toString() { - return "By.FindsByAndroidDataMatcher: " + getLocatorString(); + return "By.AndroidDataMatcher: " + getLocatorString(); } } public static class ByAndroidViewMatcher extends MobileBy implements Serializable { protected ByAndroidViewMatcher(String locatorString) { - super(MobileSelector.ANDROID_VIEW_MATCHER, locatorString); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) { - return FindsByAndroidViewMatcher.class.cast(context) - .findElementsByAndroidViewMatcher(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) { - return FindsByAndroidViewMatcher.class.cast(context) - .findElementByAndroidViewMatcher(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class, - FindsByFluentSelector.class); + super("-android viewmatcher", locatorString); } @Override public String toString() { - return "By.FindsByAndroidViewMatcher: " + getLocatorString(); + return "By.AndroidViewMatcher: " + getLocatorString(); } } public static class ByIosNsPredicate extends MobileBy implements Serializable { protected ByIosNsPredicate(String locatorString) { - super(MobileSelector.IOS_PREDICATE_STRING, locatorString); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByIosNSPredicate.class.isAssignableFrom(contextClass)) { - return FindsByIosNSPredicate.class.cast(context) - .findElementsByIosNsPredicate(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByIosNSPredicate.class, - FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByIosNSPredicate.class.isAssignableFrom(contextClass)) { - return FindsByIosNSPredicate.class.cast(context) - .findElementByIosNsPredicate(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByIosNSPredicate.class, - FindsByFluentSelector.class); + super("-ios predicate string", locatorString); } @Override public String toString() { @@ -540,108 +242,15 @@ protected ByIosNsPredicate(String locatorString) { public static class ByWindowsAutomation extends MobileBy implements Serializable { protected ByWindowsAutomation(String locatorString) { - super(MobileSelector.WINDOWS_UI_AUTOMATION, locatorString); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByWindowsAutomation.class.isAssignableFrom(contextClass)) { - return FindsByWindowsAutomation.class.cast(context) - .findElementsByWindowsUIAutomation(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByWindowsAutomation.class, - FindsByFluentSelector.class); + super("-windows uiautomation", locatorString); } - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByWindowsAutomation.class.isAssignableFrom(contextClass)) { - return FindsByWindowsAutomation.class.cast(context) - .findElementByWindowsUIAutomation(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByIosNSPredicate.class, - FindsByWindowsAutomation.class); - } } public static class ByImage extends MobileBy implements Serializable { protected ByImage(String b64Template) { - super(MobileSelector.IMAGE, b64Template); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByImage.class.isAssignableFrom(contextClass)) { - return FindsByImage.class.cast(context).findElementsByImage(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByImage.class, FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByImage.class.isAssignableFrom(contextClass)) { - return FindsByImage.class.cast(context).findElementByImage(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByImage.class, FindsByFluentSelector.class); + super("-image", b64Template); } @Override public String toString() { @@ -652,52 +261,7 @@ protected ByImage(String b64Template) { public static class ByCustom extends MobileBy implements Serializable { protected ByCustom(String selector) { - super(MobileSelector.CUSTOM, selector); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override public List findElements(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByCustom.class.isAssignableFrom(contextClass)) { - return FindsByCustom.class.cast(context).findElementsByCustom(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByCustom.class, FindsByFluentSelector.class); - } - - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) { - Class contextClass = context.getClass(); - - if (FindsByCustom.class.isAssignableFrom(contextClass)) { - return FindsByCustom.class.cast(context).findElementByCustom(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByCustom.class, FindsByFluentSelector.class); + super("-custom", selector); } @Override public String toString() { @@ -708,63 +272,22 @@ protected ByCustom(String selector) { public static class ByAndroidViewTag extends MobileBy implements Serializable { public ByAndroidViewTag(String tag) { - super(MobileSelector.ANDROID_VIEWTAG, tag); + super("-android viewtag", tag); } - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @SuppressWarnings("unchecked") - @Override - public List findElements(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAndroidViewTag.class.isAssignableFrom(contextClass)) { - return FindsByAndroidViewTag.class.cast(context) - .findElementsByAndroidViewTag(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElements(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidViewTag.class, - FindsByFluentSelector.class); + @Override public String toString() { + return "By.AndroidViewTag: " + getLocatorString(); } + } + + public static class ByClassName extends MobileBy implements Serializable { - /** - * {@inheritDoc} - * - * @throws WebDriverException when current session doesn't support the given selector or when - * value of the selector is not consistent. - * @throws IllegalArgumentException when it is impossible to find something on the given - * {@link SearchContext} instance - */ - @Override public WebElement findElement(SearchContext context) throws WebDriverException, - IllegalArgumentException { - Class contextClass = context.getClass(); - - if (FindsByAndroidViewTag.class.isAssignableFrom(contextClass)) { - return FindsByAndroidViewTag.class.cast(context) - .findElementByAndroidViewTag(getLocatorString()); - } - - if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) { - return super.findElement(context); - } - - throw formIllegalArgumentException(contextClass, FindsByAndroidViewTag.class, - FindsByFluentSelector.class); + protected ByClassName(String selector) { + super("class name", selector); } @Override public String toString() { - return "By.AndroidViewTag: " + getLocatorString(); + return "By.className: " + getLocatorString(); } } } diff --git a/src/main/java/io/appium/java_client/MobileDriver.java b/src/main/java/io/appium/java_client/MobileDriver.java index e982d5419..708f3b8f0 100644 --- a/src/main/java/io/appium/java_client/MobileDriver.java +++ b/src/main/java/io/appium/java_client/MobileDriver.java @@ -22,55 +22,14 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.html5.LocationContext; -import org.openqa.selenium.internal.FindsByClassName; -import org.openqa.selenium.internal.FindsByCssSelector; -import org.openqa.selenium.internal.FindsById; -import org.openqa.selenium.internal.FindsByLinkText; -import org.openqa.selenium.internal.FindsByName; -import org.openqa.selenium.internal.FindsByTagName; -import org.openqa.selenium.internal.FindsByXPath; import java.util.List; public interface MobileDriver extends WebDriver, PerformsTouchActions, ContextAware, Rotatable, - FindsByAccessibilityId, LocationContext, HidesKeyboard, HasDeviceTime, - InteractsWithFiles, InteractsWithApps, HasAppStrings, FindsByClassName, FindsByCssSelector, FindsById, - FindsByLinkText, FindsByName, FindsByTagName, FindsByXPath, FindsByFluentSelector, ExecutesMethod, + LocationContext, HidesKeyboard, HasDeviceTime, InteractsWithFiles, InteractsWithApps, HasAppStrings, ExecutesMethod, HasSessionDetails { List findElements(By by); T findElement(By by); - - T findElementByClassName(String className); - - List findElementsByClassName(String className); - - T findElementByCssSelector(String cssSelector); - - List findElementsByCssSelector(String cssSelector); - - T findElementById(String id); - - List findElementsById(String id); - - T findElementByLinkText(String linkText); - - List findElementsByLinkText(String linkText); - - T findElementByPartialLinkText(String partialLinkText); - - List findElementsByPartialLinkText(String partialLinkText); - - T findElementByName(String name); - - List findElementsByName(String name); - - T findElementByTagName(String tagName); - - List findElementsByTagName(String tagName); - - T findElementByXPath(String xPath); - - List findElementsByXPath(String xPath); } diff --git a/src/main/java/io/appium/java_client/MobileElement.java b/src/main/java/io/appium/java_client/MobileElement.java index a8decf61d..45c932b12 100644 --- a/src/main/java/io/appium/java_client/MobileElement.java +++ b/src/main/java/io/appium/java_client/MobileElement.java @@ -29,8 +29,6 @@ public abstract class MobileElement extends DefaultGenericMobileElement { - protected FileDetector fileDetector; - /** * Method returns central coordinates of an element. * @return The instance of the {@link org.openqa.selenium.Point} @@ -46,46 +44,6 @@ public Point getCenter() { return super.findElements(by); } - @Override public List findElements(String by, String using) { - return super.findElements(by, using); - } - - @Override public List findElementsById(String id) { - return super.findElementsById(id); - } - - public List findElementsByLinkText(String using) { - return super.findElementsByLinkText(using); - } - - public List findElementsByPartialLinkText(String using) { - return super.findElementsByPartialLinkText(using); - } - - public List findElementsByTagName(String using) { - return super.findElementsByTagName(using); - } - - public List findElementsByName(String using) { - return super.findElementsByName(using); - } - - public List findElementsByClassName(String using) { - return super.findElementsByClassName(using); - } - - public List findElementsByCssSelector(String using) { - return super.findElementsByCssSelector(using); - } - - public List findElementsByXPath(String using) { - return super.findElementsByXPath(using); - } - - @Override public List findElementsByAccessibilityId(String using) { - return super.findElementsByAccessibilityId(using); - } - /** * This method sets the new value of the attribute "value". * diff --git a/src/main/java/io/appium/java_client/MobileSelector.java b/src/main/java/io/appium/java_client/MobileSelector.java deleted file mode 100644 index 0fbe3284e..000000000 --- a/src/main/java/io/appium/java_client/MobileSelector.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client; - -public enum MobileSelector { - ACCESSIBILITY("accessibility id"), - ANDROID_UI_AUTOMATOR("-android uiautomator"), - IOS_UI_AUTOMATION("-ios uiautomation"), - IOS_PREDICATE_STRING("-ios predicate string"), - IOS_CLASS_CHAIN("-ios class chain"), - WINDOWS_UI_AUTOMATION("-windows uiautomation"), - IMAGE("-image"), - ANDROID_VIEWTAG("-android viewtag"), - ANDROID_DATA_MATCHER("-android datamatcher"), - ANDROID_VIEW_MATCHER("-android viewmatcher"), - CUSTOM("-custom"); - - private final String selector; - - MobileSelector(String selector) { - this.selector = selector; - } - - @Override public String toString() { - return selector; - } -} diff --git a/src/main/java/io/appium/java_client/android/Activity.java b/src/main/java/io/appium/java_client/android/Activity.java index da957d746..41a17dc8c 100644 --- a/src/main/java/io/appium/java_client/android/Activity.java +++ b/src/main/java/io/appium/java_client/android/Activity.java @@ -2,7 +2,6 @@ import lombok.Data; import lombok.experimental.Accessors; -import okhttp3.Interceptor; import static com.google.common.base.Preconditions.checkArgument; import static org.apache.commons.lang3.StringUtils.isBlank; diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index 1994bb69a..be4bd8e3d 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -26,10 +26,6 @@ import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecuteCDPCommand; -import io.appium.java_client.FindsByAndroidDataMatcher; -import io.appium.java_client.FindsByAndroidViewMatcher; -import io.appium.java_client.FindsByAndroidUIAutomator; -import io.appium.java_client.FindsByAndroidViewTag; import io.appium.java_client.HasOnScreenKeyboard; import io.appium.java_client.LocksDevice; import io.appium.java_client.android.connection.HasNetworkConnection; @@ -63,13 +59,10 @@ */ public class AndroidDriver extends AppiumDriver - implements PressesKey, HasNetworkConnection, PushesFiles, StartsActivity, - FindsByAndroidUIAutomator, FindsByAndroidViewTag, FindsByAndroidDataMatcher, - FindsByAndroidViewMatcher, LocksDevice, HasAndroidSettings, HasAndroidDeviceDetails, - HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard, - CanRecordScreen, SupportsSpecialEmulatorCommands, - SupportsNetworkStateManagement, ListensToLogcatMessages, HasAndroidClipboard, - HasBattery, ExecuteCDPCommand, SupportsExtendedGeolocationCommands { + implements PressesKey, HasNetworkConnection, PushesFiles, StartsActivity,LocksDevice, HasAndroidSettings, + HasAndroidDeviceDetails, HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard, + CanRecordScreen, SupportsSpecialEmulatorCommands, SupportsNetworkStateManagement, ListensToLogcatMessages, + HasAndroidClipboard, HasBattery, ExecuteCDPCommand, SupportsExtendedGeolocationCommands { private static final String ANDROID_PLATFORM = MobilePlatform.ANDROID; diff --git a/src/main/java/io/appium/java_client/android/AndroidElement.java b/src/main/java/io/appium/java_client/android/AndroidElement.java index 95899e4db..5c1231c24 100644 --- a/src/main/java/io/appium/java_client/android/AndroidElement.java +++ b/src/main/java/io/appium/java_client/android/AndroidElement.java @@ -19,15 +19,9 @@ import static io.appium.java_client.android.AndroidMobileCommandHelper.replaceElementValueCommand; import io.appium.java_client.CommandExecutionHelper; -import io.appium.java_client.FindsByAndroidDataMatcher; -import io.appium.java_client.FindsByAndroidViewMatcher; -import io.appium.java_client.FindsByAndroidUIAutomator; -import io.appium.java_client.FindsByAndroidViewTag; import io.appium.java_client.MobileElement; -public class AndroidElement extends MobileElement - implements FindsByAndroidUIAutomator, FindsByAndroidDataMatcher, - FindsByAndroidViewMatcher, FindsByAndroidViewTag { +public class AndroidElement extends MobileElement { /** * This method replace current text value. * @param value a new value diff --git a/src/main/java/io/appium/java_client/android/AndroidOptions.java b/src/main/java/io/appium/java_client/android/AndroidOptions.java index 768bf75eb..780f92cd4 100644 --- a/src/main/java/io/appium/java_client/android/AndroidOptions.java +++ b/src/main/java/io/appium/java_client/android/AndroidOptions.java @@ -22,11 +22,15 @@ public class AndroidOptions extends MobileOptions { public AndroidOptions() { - setPlatformName(MobilePlatform.ANDROID); + setAndroidPlatformName(); } public AndroidOptions(Capabilities source) { - this(); - merge(source); + super(source); + setAndroidPlatformName(); + } + + private void setAndroidPlatformName() { + setPlatformName(MobilePlatform.ANDROID); } } diff --git a/src/main/java/io/appium/java_client/events/DefaultAspect.java b/src/main/java/io/appium/java_client/events/DefaultAspect.java index 345ec77ee..3ce9794be 100644 --- a/src/main/java/io/appium/java_client/events/DefaultAspect.java +++ b/src/main/java/io/appium/java_client/events/DefaultAspect.java @@ -97,19 +97,6 @@ class DefaultAspect { + "execution(* org.openqa.selenium.WebDriver.TargetLocator.*(..)) || " + "execution(* org.openqa.selenium.JavascriptExecutor.*(..)) || " + "execution(* org.openqa.selenium.ContextAware.*(..)) || " - + "execution(* io.appium.java_client.FindsByAccessibilityId.*(..)) || " - + "execution(* io.appium.java_client.FindsByAndroidUIAutomator.*(..)) || " - + "execution(* io.appium.java_client.FindsByAndroidDataMatcher.*(..)) || " - + "execution(* io.appium.java_client.FindsByAndroidViewMatcher.*(..)) || " - + "execution(* io.appium.java_client.FindsByWindowsAutomation.*(..)) || " - + "execution(* io.appium.java_client.FindsByIosNSPredicate.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByClassName.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByCssSelector.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsById.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByLinkText.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByName.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByTagName.*(..)) || " - + "execution(* org.openqa.selenium.internal.FindsByXPath.*(..)) || " + "execution(* org.openqa.selenium.WebDriver.Window.*(..)) || " + "execution(* io.appium.java_client.android.AndroidElement.*(..)) || " + "execution(* io.appium.java_client.ios.IOSElement.*(..)) || " diff --git a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java index 9f8674a90..d69e39312 100644 --- a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java +++ b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java @@ -22,9 +22,9 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; +import org.openqa.selenium.remote.JsonToWebElementConverter; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebElement; -import org.openqa.selenium.remote.internal.JsonToWebElementConverter; import java.lang.reflect.Constructor; diff --git a/src/main/java/io/appium/java_client/ios/IOSDriver.java b/src/main/java/io/appium/java_client/ios/IOSDriver.java index 229aac70b..ffd4a8a0c 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -22,8 +22,6 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumDriver; -import io.appium.java_client.FindsByIosClassChain; -import io.appium.java_client.FindsByIosNSPredicate; import io.appium.java_client.HasOnScreenKeyboard; import io.appium.java_client.HidesKeyboardWithKeyName; import io.appium.java_client.LocksDevice; @@ -59,9 +57,8 @@ */ public class IOSDriver extends AppiumDriver - implements HidesKeyboardWithKeyName, ShakesDevice, HasIOSSettings, HasOnScreenKeyboard, - LocksDevice, PerformsTouchID, FindsByIosNSPredicate, FindsByIosClassChain, - PushesFiles, CanRecordScreen, HasIOSClipboard, ListensToSyslogMessages, + implements HidesKeyboardWithKeyName, ShakesDevice, HasIOSSettings, HasOnScreenKeyboard, LocksDevice, + PerformsTouchID, PushesFiles, CanRecordScreen, HasIOSClipboard, ListensToSyslogMessages, HasBattery { private static final String IOS_DEFAULT_PLATFORM = MobilePlatform.IOS; diff --git a/src/main/java/io/appium/java_client/ios/IOSElement.java b/src/main/java/io/appium/java_client/ios/IOSElement.java index a406053a0..55aa47110 100644 --- a/src/main/java/io/appium/java_client/ios/IOSElement.java +++ b/src/main/java/io/appium/java_client/ios/IOSElement.java @@ -16,10 +16,7 @@ package io.appium.java_client.ios; -import io.appium.java_client.FindsByIosClassChain; -import io.appium.java_client.FindsByIosNSPredicate; import io.appium.java_client.MobileElement; -public class IOSElement extends MobileElement - implements FindsByIosNSPredicate, FindsByIosClassChain { +public class IOSElement extends MobileElement { } diff --git a/src/main/java/io/appium/java_client/ios/IOSOptions.java b/src/main/java/io/appium/java_client/ios/IOSOptions.java index 14af6aad6..9d4c82b6e 100644 --- a/src/main/java/io/appium/java_client/ios/IOSOptions.java +++ b/src/main/java/io/appium/java_client/ios/IOSOptions.java @@ -22,11 +22,15 @@ public class IOSOptions extends MobileOptions { public IOSOptions() { - setPlatformName(MobilePlatform.IOS); + setIOSPlatformName(); } public IOSOptions(Capabilities source) { - this(); - merge(source); + super(source); + setIOSPlatformName(); + } + + private void setIOSPlatformName() { + setPlatformName(MobilePlatform.IOS); } } diff --git a/src/main/java/io/appium/java_client/mac/FindsByClassChain.java b/src/main/java/io/appium/java_client/mac/FindsByClassChain.java deleted file mode 100644 index 3d4eaffcc..000000000 --- a/src/main/java/io/appium/java_client/mac/FindsByClassChain.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client.mac; - -import io.appium.java_client.FindsByFluentSelector; -import io.appium.java_client.MobileSelector; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByClassChain extends FindsByFluentSelector { - - /** - * Perform single element lookup by class chain expression. - * Read https://github.com/appium/appium-mac2-driver#element-location - * for more details on elements location strategies supported by Mac2 driver. - * - * @param using A valid class chain lookup expression. - * @return The found element - */ - default T findElementByClassChain(String using) { - return findElement(MobileSelector.IOS_CLASS_CHAIN.toString(), using); - } - - /** - * Perform multiple elements lookup by class chain search expression. - * Read https://github.com/appium/appium-mac2-driver#element-location - * for more details on elements location strategies supported by Mac2 driver. - * - * @param using A valid class chain lookup expression. - * @return The array of found elements or an empty one if no matches have been found. - */ - default List findElementsByClassChain(String using) { - return findElements(MobileSelector.IOS_CLASS_CHAIN.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java b/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java deleted file mode 100644 index 665732eb3..000000000 --- a/src/main/java/io/appium/java_client/mac/FindsByNsPredicate.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * 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 io.appium.java_client.mac; - -import io.appium.java_client.FindsByFluentSelector; -import io.appium.java_client.MobileSelector; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public interface FindsByNsPredicate extends FindsByFluentSelector { - - /** - * Perform single element lookup by predicate search expression. - * Read https://github.com/appium/appium-mac2-driver#element-location - * for more details on elements location strategies supported by Mac2 driver. - * - * @param using A valid predicate lookup expression. - * @return The found element - */ - default T findElementByNsPredicate(String using) { - return findElement(MobileSelector.IOS_PREDICATE_STRING.toString(), using); - } - - /** - * Perform multiple elements lookup by predicate search expression. - * Read https://github.com/appium/appium-mac2-driver#element-location - * for more details on elements location strategies supported by Mac2 driver. - * - * @param using A valid predicate lookup expression. - * @return The array of found elements or an empty one if no matches have been found. - */ - default List findElementsByNsPredicate(String using) { - return findElements(MobileSelector.IOS_PREDICATE_STRING.toString(), using); - } -} diff --git a/src/main/java/io/appium/java_client/mac/Mac2Driver.java b/src/main/java/io/appium/java_client/mac/Mac2Driver.java index d76eaf5d7..215c28971 100644 --- a/src/main/java/io/appium/java_client/mac/Mac2Driver.java +++ b/src/main/java/io/appium/java_client/mac/Mac2Driver.java @@ -44,9 +44,7 @@ * * @since Appium 1.20.0 */ -public class Mac2Driver - extends AppiumDriver implements CanRecordScreen, FindsByClassChain, - FindsByNsPredicate, HasSettings { +public class Mac2Driver extends AppiumDriver implements CanRecordScreen, HasSettings { public Mac2Driver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, prepareCaps(capabilities)); } diff --git a/src/main/java/io/appium/java_client/mac/Mac2Element.java b/src/main/java/io/appium/java_client/mac/Mac2Element.java index 905bada6e..e41042e8d 100644 --- a/src/main/java/io/appium/java_client/mac/Mac2Element.java +++ b/src/main/java/io/appium/java_client/mac/Mac2Element.java @@ -18,6 +18,5 @@ import io.appium.java_client.MobileElement; -public class Mac2Element extends MobileElement implements - FindsByClassChain, FindsByNsPredicate { +public class Mac2Element extends MobileElement { } diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java index 718cd403c..4b853f046 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java @@ -46,7 +46,7 @@ enum Strategies { }, BYCLASSNAME("className") { @Override By getBy(Annotation annotation) { - return By.className(getValue(annotation, this)); + return MobileBy.className(getValue(annotation, this)); } }, BYID("id") { diff --git a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java index 2b0f77f7e..28af10cfc 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -44,16 +44,21 @@ import org.openqa.selenium.remote.ProtocolHandshake; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.ResponseCodec; +import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; +import org.openqa.selenium.remote.http.Filter; import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; -import org.openqa.selenium.remote.http.W3CHttpCommandCodec; +import org.openqa.selenium.remote.http.WebSocket; +import org.openqa.selenium.remote.http.WebSocket.Listener; import org.openqa.selenium.remote.service.DriverService; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -156,13 +161,6 @@ protected HttpClient getClient() { return getPrivateFieldValue("client", HttpClient.class); } - protected HttpClient withRequestsPatchedByIdempotencyKey(HttpClient httpClient) { - return (request) -> { - request.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); - return httpClient.execute(request); - }; - } - private Response createSession(Command command) throws IOException { if (getCommandCodec() != null) { throw new SessionNotCreatedException("Session already exists"); @@ -191,7 +189,10 @@ public Result createSession(HttpClient client, Command command) throws IOExcepti createSessionMethod.setAccessible(true); Optional result = (Optional) createSessionMethod.invoke(this, - withRequestsPatchedByIdempotencyKey(client), contentStream, counter.getCount()); + client.with(httpHandler -> req -> { + req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); + return httpHandler.execute(req); + }), contentStream, counter.getCount()); return result.map(result1 -> { Result toReturn = result.get(); diff --git a/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java b/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java index aec7ebd75..0fe0ace05 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java +++ b/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java @@ -32,7 +32,7 @@ import org.openqa.selenium.interactions.KeyInput; import org.openqa.selenium.interactions.Sequence; -import org.openqa.selenium.remote.http.W3CHttpCommandCodec; +import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; import java.util.Collection; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/remote/MobileOptions.java b/src/main/java/io/appium/java_client/remote/MobileOptions.java index 6a7810f1a..bf8ea1f38 100644 --- a/src/main/java/io/appium/java_client/remote/MobileOptions.java +++ b/src/main/java/io/appium/java_client/remote/MobileOptions.java @@ -18,6 +18,7 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.Platform; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.remote.CapabilityType; @@ -38,7 +39,7 @@ public MobileOptions() { * @param source is Capabilities instance to merge into new instance */ public MobileOptions(Capabilities source) { - merge(source); + super(source); } /** @@ -52,16 +53,6 @@ public T setPlatformName(String platform) { return amend(CapabilityType.PLATFORM_NAME, platform); } - /** - * Get the kind of mobile device or emulator to use. - * - * @return String representing the kind of mobile device or emulator to use. - * @see org.openqa.selenium.remote.CapabilityType#PLATFORM_NAME - */ - public String getPlatformName() { - return (String) getCapability(CapabilityType.PLATFORM_NAME); - } - /** * Set the absolute local path for the location of the App. * The or remote http URL to a {@code .ipa} file (IOS), diff --git a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java index 3e82e0de4..37851fd4b 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java @@ -22,8 +22,6 @@ import static org.slf4j.event.Level.INFO; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.StringUtils; @@ -42,6 +40,7 @@ import java.net.URL; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; @@ -61,25 +60,22 @@ public final class AppiumDriverLocalService extends DriverService { private static final Duration DESTROY_TIMEOUT = Duration.ofSeconds(60); private final File nodeJSExec; - private final ImmutableList nodeJSArgs; - private final ImmutableMap nodeJSEnvironment; - private final long startupTimeout; - private final TimeUnit timeUnit; + private final List nodeJSArgs; + private final Map nodeJSEnvironment; + private final Duration startupTimeout; private final ReentrantLock lock = new ReentrantLock(true); //uses "fair" thread ordering policy private final ListOutputStream stream = new ListOutputStream().add(System.out); private final URL url; private CommandLine process = null; - AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort, - ImmutableList nodeJSArgs, ImmutableMap nodeJSEnvironment, - long startupTimeout, TimeUnit timeUnit) throws IOException { - super(nodeJSExec, nodeJSPort, nodeJSArgs, nodeJSEnvironment); + AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort, Duration startupTimeout, + List nodeJSArgs, Map nodeJSEnvironment) throws IOException { + super(nodeJSExec, nodeJSPort, startupTimeout, nodeJSArgs, nodeJSEnvironment); this.nodeJSExec = nodeJSExec; this.nodeJSArgs = nodeJSArgs; this.nodeJSEnvironment = nodeJSEnvironment; this.startupTimeout = startupTimeout; - this.timeUnit = timeUnit; this.url = new URL(String.format(URL_MASK, ipAddress, nodeJSPort)); } @@ -114,7 +110,7 @@ public boolean isRunning() { } try { - ping(1500, TimeUnit.MILLISECONDS); + ping(Duration.ofMillis(1500)); return true; } catch (UrlChecker.TimeoutException e) { return false; @@ -127,10 +123,10 @@ public boolean isRunning() { } - private void ping(long time, TimeUnit timeUnit) throws UrlChecker.TimeoutException, MalformedURLException { + private void ping(Duration timeout) throws UrlChecker.TimeoutException, MalformedURLException { // The operating system might block direct access to the universal broadcast IP address URL status = new URL(url.toString().replace(BROADCAST_IP_ADDRESS, "127.0.0.1") + "/status"); - new UrlChecker().waitUntilAvailable(time, timeUnit, status); + new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); } /** @@ -152,7 +148,7 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { process.setEnvironmentVariables(nodeJSEnvironment); process.copyOutputTo(stream); process.executeAsync(); - ping(startupTimeout, timeUnit); + ping(startupTimeout); } catch (Throwable e) { destroyProcess(); String msgTxt = "The local appium server has not been started. " diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java index dc9b669be..30147838e 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java @@ -21,7 +21,6 @@ import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -36,6 +35,7 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.os.ExecutableFinder; +import org.openqa.selenium.remote.Browser; import org.openqa.selenium.remote.BrowserType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.service.DriverService; @@ -47,6 +47,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -87,9 +88,6 @@ public final class AppiumServiceBuilder private static final Function NODE_JS_NOT_EXIST_ERROR = (fullPath) -> String.format("The main NodeJS executable does not exist at '%s'", fullPath.getAbsolutePath()); - // The first starting is slow sometimes on some environment - private long startupTimeout = 120; - private TimeUnit timeUnit = TimeUnit.SECONDS; private static final List PATH_CAPABILITIES = ImmutableList.of(AndroidMobileCapabilityType.KEYSTORE_PATH, AndroidMobileCapabilityType.CHROMEDRIVER_EXECUTABLE, MobileCapabilityType.APP); @@ -116,8 +114,8 @@ public int score(Capabilities capabilities) { } String browserName = capabilities.getBrowserName(); - if (browserName.equals(BrowserType.CHROME) || browserName.equals(BrowserType.ANDROID) - || browserName.equals(BrowserType.SAFARI)) { + if (Browser.CHROME.is(browserName) || browserName.equals(BrowserType.ANDROID) + || Browser.SAFARI.is(browserName)) { score++; } @@ -277,21 +275,6 @@ public AppiumServiceBuilder withIPAddress(String ipAddress) { return this; } - /** - * Sets start up timeout. - * - * @param time a time value for the service starting up. - * @param timeUnit a time unit for the service starting up. - * @return self-reference. - */ - public AppiumServiceBuilder withStartUpTimeOut(long time, TimeUnit timeUnit) { - checkNotNull(timeUnit); - checkArgument(time > 0, "Time value should be greater than zero", time); - this.startupTimeout = time; - this.timeUnit = timeUnit; - return this; - } - @Nullable private static File loadPathFromEnv(String envVarName) { String fullPath = System.getProperty(envVarName); @@ -474,11 +457,12 @@ public AppiumServiceBuilder withLogFile(File logFile) { @Override protected AppiumDriverLocalService createDriverService(File nodeJSExecutable, int nodeJSPort, - ImmutableList nodeArguments, - ImmutableMap nodeEnvironment) { + Duration startupTimeout, + List nodeArguments, + Map nodeEnvironment) { try { - return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, - nodeArguments, nodeEnvironment, startupTimeout, timeUnit); + return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, startupTimeout, nodeArguments, + nodeEnvironment); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/io/appium/java_client/touch/offset/ElementOption.java b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java index ede90103c..775b067d1 100644 --- a/src/main/java/io/appium/java_client/touch/offset/ElementOption.java +++ b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java @@ -6,7 +6,7 @@ import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.HasIdentity; +import org.openqa.selenium.remote.RemoteWebElement; import java.util.HashMap; import java.util.Map; @@ -84,9 +84,9 @@ public ElementOption withCoordinates(int xOffset, int yOffset) { public ElementOption withElement(WebElement element) { checkNotNull(element); checkArgument(true, "Element should be an instance of the class which " - + "implements org.openqa.selenium.internal.HasIdentity", - element instanceof HasIdentity); - elementId = ((HasIdentity) element).getId(); + + "extends org.openqa.selenium.remote.RemoteWebElement", + element instanceof RemoteWebElement); + elementId = ((RemoteWebElement) element).getId(); return this; } diff --git a/src/main/java/io/appium/java_client/windows/WindowsDriver.java b/src/main/java/io/appium/java_client/windows/WindowsDriver.java index 3c8126ac1..cf89449de 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsDriver.java +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -19,7 +19,6 @@ import static io.appium.java_client.remote.MobilePlatform.WINDOWS; import io.appium.java_client.AppiumDriver; -import io.appium.java_client.FindsByWindowsAutomation; import io.appium.java_client.HidesKeyboardWithKeyName; import io.appium.java_client.screenrecording.CanRecordScreen; import io.appium.java_client.service.local.AppiumDriverLocalService; @@ -32,8 +31,7 @@ import java.net.URL; public class WindowsDriver - extends AppiumDriver implements PressesKeyCode, HidesKeyboardWithKeyName, - FindsByWindowsAutomation, CanRecordScreen { + extends AppiumDriver implements PressesKeyCode, HidesKeyboardWithKeyName, CanRecordScreen { public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, updateDefaultPlatformName(capabilities, WINDOWS)); diff --git a/src/main/java/io/appium/java_client/windows/WindowsElement.java b/src/main/java/io/appium/java_client/windows/WindowsElement.java index 4f7ec7ba2..ad5d3a5d8 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsElement.java +++ b/src/main/java/io/appium/java_client/windows/WindowsElement.java @@ -16,8 +16,7 @@ package io.appium.java_client.windows; -import io.appium.java_client.FindsByWindowsAutomation; import io.appium.java_client.MobileElement; -public class WindowsElement extends MobileElement implements FindsByWindowsAutomation { +public class WindowsElement extends MobileElement { } diff --git a/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java b/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java index d7ebe559e..6a3148aa2 100644 --- a/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java +++ b/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java @@ -16,21 +16,21 @@ package io.appium.java_client.ws; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; - import java.net.URI; +import java.time.Duration; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nullable; -public class StringWebSocketClient extends WebSocketListener implements +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpMethod; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.WebSocket; + +public class StringWebSocketClient implements WebSocket.Listener, CanHandleMessages, CanHandleErrors, CanHandleConnects, CanHandleDisconnects { private final List> messageHandlers = new CopyOnWriteArrayList<>(); private final List> errorHandlers = new CopyOnWriteArrayList<>(); @@ -65,37 +65,36 @@ public void connect(URI endpoint) { return; } - OkHttpClient client = new OkHttpClient.Builder() - .readTimeout(0, TimeUnit.MILLISECONDS) - .build(); - Request request = new Request.Builder() - .url(endpoint.toString()) - .build(); - client.newWebSocket(request, this); - client.dispatcher().executorService().shutdown(); + ClientConfig clientConfig = ClientConfig.defaultConfig() + .readTimeout(Duration.ZERO) + .baseUri(endpoint); // To avoid NPE in org.openqa.selenium.remote.http.netty.NettyMessages (line 78) + HttpClient client = HttpClient.Factory.createDefault().createClient(clientConfig); + HttpRequest request = new HttpRequest(HttpMethod.GET, endpoint.toString()); + client.openSocket(request, this); + onOpen(); setEndpoint(endpoint); } - @Override - public void onOpen(WebSocket webSocket, Response response) { + public void onOpen() { getConnectionHandlers().forEach(Runnable::run); isListening = true; } @Override - public void onClosing(WebSocket webSocket, int code, String reason) { + public void onClose(int code, String reason) { getDisconnectionHandlers().forEach(Runnable::run); isListening = false; } @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { + public void onError(Throwable t) { getErrorHandlers().forEach(x -> x.accept(t)); } @Override - public void onMessage(WebSocket webSocket, String text) { + public void onText(CharSequence data) { + String text = data.toString(); getMessageHandlers().forEach(x -> x.accept(text)); } diff --git a/src/main/java/org/openqa/selenium/WebDriver.java b/src/main/java/org/openqa/selenium/WebDriver.java index 08de94859..c8f990896 100644 --- a/src/main/java/org/openqa/selenium/WebDriver.java +++ b/src/main/java/org/openqa/selenium/WebDriver.java @@ -21,14 +21,15 @@ import org.openqa.selenium.logging.Logs; import java.net.URL; +import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; /** - * The main interface to use for testing, which represents an idealised web browser. The methods in - * this class fall into three categories: + * WebDriver is a remote control interface that enables introspection and control of user agents + * (browsers). The methods in this interface fall into three categories: *