From 80be5584b0e231429dfe5c921fa17ecec34034f5 Mon Sep 17 00:00:00 2001 From: Anuj Kumar Date: Fri, 26 Aug 2022 18:33:46 +0200 Subject: [PATCH 001/314] test: Fix open notification test (#1749) --- build.gradle | 1 + .../io/appium/java_client/android/OpenNotificationsTest.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0d4205919..a044708d0 100644 --- a/build.gradle +++ b/build.gradle @@ -213,6 +213,7 @@ task uiAutomationTest( type: Test ) { filter { includeTestsMatching 'io.appium.java_client.android.SettingTest' includeTestsMatching 'io.appium.java_client.android.ClipboardTest' + includeTestsMatching 'io.appium.java_client.android.OpenNotificationsTest' includeTestsMatching '*.AndroidAppStringsTest' includeTestsMatching '*.pagefactory_tests.widget.tests.android.*' includeTestsMatching '*.pagefactory_tests.widget.tests.AndroidPageObjectTest' diff --git a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java index dde09bbc1..e29e92c45 100644 --- a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java +++ b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java @@ -1,7 +1,7 @@ package io.appium.java_client.android; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.openqa.selenium.By.id; +import static org.openqa.selenium.By.xpath; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebElement; @@ -18,7 +18,7 @@ public void openNotification() { WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20)); assertNotEquals(0, wait.until(input -> { List result = input - .findElements(id("com.android.systemui:id/settings_button")); + .findElements(xpath("//android.widget.Switch[contains(@content-desc, 'Wi-Fi')]")); return result.isEmpty() ? null : result; }).size()); From 6d05b8be8c1f0287e02bb2e8c40f9d67a8f94c31 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Aug 2022 13:27:55 +0300 Subject: [PATCH 002/314] build(deps): bump webdrivermanager from 5.2.3 to 5.3.0 (#1745) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.2.3 to 5.3.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-5.2.3...webdrivermanager-5.3.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 a044708d0..885ff233e 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.2.3') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.0') { exclude group: 'org.seleniumhq.selenium' } testImplementation ('org.seleniumhq.selenium:selenium-chrome-driver') { From 36cf25c82bdcc959fc8defbba6cf1142407adffe Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Tue, 30 Aug 2022 11:02:03 +0530 Subject: [PATCH 003/314] Release 8.2.0 and update release notes --- README.md | 20 ++++++++++++++++++++ build.gradle | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 39c936ba5..6af5f0be8 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,26 @@ dependencies { ``` ## Changelog +*8.2.0* +- **[ENHANCEMENTS]** + - AppiumDriverLocalService can handle outputStreams. [#1709](https://github.com/appium/java-client/pull/1709) + - Add creating a driver with ClientConfig. [#1735](https://github.com/appium/java-client/pull/1735) +- **[BUG FIX]** + - Update the environment argument type for mac SupportsEnvironmentOption. [#1712](https://github.com/appium/java-client/pull/1712) +- **[REFRACTOR]** + - Deprecate Appium ByAll in favour of Selenium ByAll. [#1740](https://github.com/appium/java-client/pull/1740) + - Bump Node.js version in pipeline. [#1713](https://github.com/appium/java-client/pull/1713) + - Switch unit tests to run on Junit 5 Jupiter Platform. [#1721](https://github.com/appium/java-client/pull/1721) + - Clean up unit tests asserting thrown exceptions. [#1741](https://github.com/appium/java-client/pull/1741) + - Fix open notification test. [#1749](https://github.com/appium/java-client/pull/1749) + - update Azure pipeline to use macos-11 VM image. [#1728](https://github.com/appium/java-client/pull/1728) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.4.0. + - `org.owasp.dependencycheck` was updated to 7.1.2. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.0. + - `gradle` was updated to 7.5.1. + - `com.google.code.gson:gson` was updated to 2.9.1. + *8.1.1* - **[BUG FIX]** - Perform safe typecast while getting the platform name. [#1702](https://github.com/appium/java-client/pull/1702) diff --git a/build.gradle b/build.gradle index 885ff233e..1552006d4 100644 --- a/build.gradle +++ b/build.gradle @@ -111,7 +111,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '8.1.1' + version = '8.2.0' from components.java pom { name = 'java-client' From c9a5c920156c480bfb8bd71198c4dd3fc703b4c6 Mon Sep 17 00:00:00 2001 From: Bipin Kumar Chaurasia Date: Tue, 30 Aug 2022 17:27:49 +0530 Subject: [PATCH 004/314] feat!: Updated spell for deprecated BYACCESSABILITY to BYACCESSIBILITY strategies enum (#1752) --- .../pagefactory/bys/builder/Strategies.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 db06bdbc1..6212ffcf7 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 @@ -41,11 +41,21 @@ enum Strategies { return super.getBy(annotation); } }, + /** + * This has been deprecated due to misspelling. + * @deprecated Use {@link Strategies#BYACCESSIBILITY} instead. + */ + @Deprecated BYACCESSABILITY("accessibility") { @Override By getBy(Annotation annotation) { return AppiumBy.accessibilityId(getValue(annotation, this)); } }, + BYACCESSIBILITY("accessibility") { + @Override By getBy(Annotation annotation) { + return AppiumBy.accessibilityId(getValue(annotation, this)); + } + }, BYCLASSNAME("className") { @Override By getBy(Annotation annotation) { return AppiumBy.className(getValue(annotation, this)); From 54fdd4b8c6fc2303851d46b6310f25e96b690e39 Mon Sep 17 00:00:00 2001 From: Bipin Kumar Chaurasia Date: Fri, 2 Sep 2022 00:37:41 +0530 Subject: [PATCH 005/314] test: updated JUnit5 test classes and methods to have default package visibility (#1755) --- .../java_client/internal/ConfigTest.java | 10 +-- .../tests/combined/CombinedAppTest.java | 7 +- .../tests/combined/CombinedWidgetTest.java | 2 +- .../service/local/ServerBuilderTest.java | 65 +++++++++---------- .../local/StartingAppLocallyAndroidTest.java | 23 ++++--- .../local/StartingAppLocallyIosTest.java | 8 +-- .../service/local/ThreadSafetyTest.java | 10 +-- 7 files changed, 61 insertions(+), 64 deletions(-) diff --git a/src/test/java/io/appium/java_client/internal/ConfigTest.java b/src/test/java/io/appium/java_client/internal/ConfigTest.java index cd2bd390c..df8611458 100644 --- a/src/test/java/io/appium/java_client/internal/ConfigTest.java +++ b/src/test/java/io/appium/java_client/internal/ConfigTest.java @@ -8,28 +8,28 @@ import org.junit.jupiter.api.Test; -public class ConfigTest { +class ConfigTest { private static final String EXISTING_KEY = "selenium.version"; private static final String MISSING_KEY = "bla"; @Test - public void verifyGettingExistingValue() { + void verifyGettingExistingValue() { assertThat(Config.main().getValue(EXISTING_KEY, String.class).length(), greaterThan(0)); assertTrue(Config.main().getOptionalValue(EXISTING_KEY, String.class).isPresent()); } @Test - public void verifyGettingNonExistingValue() { + void verifyGettingNonExistingValue() { assertThrows(IllegalArgumentException.class, () -> Config.main().getValue(MISSING_KEY, String.class)); } @Test - public void verifyGettingExistingValueWithWrongClass() { + void verifyGettingExistingValueWithWrongClass() { assertThrows(ClassCastException.class, () -> Config.main().getValue(EXISTING_KEY, Integer.class)); } @Test - public void verifyGettingNonExistingOptionalValue() { + void verifyGettingNonExistingOptionalValue() { assertFalse(Config.main().getOptionalValue(MISSING_KEY, String.class).isPresent()); } } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java index 8c93632ec..80e6eb1b3 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java @@ -13,14 +13,13 @@ import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget; import io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget; +import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.openqa.selenium.WebDriver; -import java.util.List; -import java.util.stream.Stream; - @SuppressWarnings({"unused", "unchecked"}) public class CombinedAppTest { @@ -53,7 +52,7 @@ public static Stream data() { @ParameterizedTest @MethodSource("data") - public void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { + void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { initElements(new AppiumFieldDecorator(driver), app); assertThat("Expected widget class was " + widgetClass.getName(), app.getWidget().getSelfReference().getClass(), diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java index 1d686c58e..342e37335 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java @@ -59,7 +59,7 @@ public static Stream data() { @ParameterizedTest @MethodSource("data") - public void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { + void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { initElements(new AppiumFieldDecorator(driver), app); assertThat("Expected widget class was " + widgetClass.getName(), app.getWidget().getSubWidget().getSelfReference().getClass(), 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 cb97479e8..ccdd117d2 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 @@ -27,20 +27,19 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.android.options.UiAutomator2Options; import io.github.bonigarcia.wdm.WebDriverManager; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import java.io.File; -import java.io.FileOutputStream; import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; @SuppressWarnings("ResultOfMethodCallIgnored") -public class ServerBuilderTest { +class ServerBuilderTest { /** * It may be impossible to find the path to the instance of appium server due to different circumstance. @@ -93,7 +92,7 @@ public void tearDown() throws Exception { } @Test - public void checkAbilityToAddLogMessageConsumer() { + void checkAbilityToAddLogMessageConsumer() { List log = new ArrayList<>(); service = buildDefaultService(); service.clearOutPutStreams(); @@ -103,21 +102,21 @@ public void checkAbilityToAddLogMessageConsumer() { } @Test - public void checkAbilityToStartDefaultService() { + void checkAbilityToStartDefaultService() { service = buildDefaultService(); service.start(); assertTrue(service.isRunning()); } @Test - public void checkAbilityToFindNodeDefinedInProperties() { + void checkAbilityToFindNodeDefinedInProperties() { File definedNode = PATH_T0_TEST_MAIN_JS.toFile(); setProperty(APPIUM_PATH, definedNode.getAbsolutePath()); assertThat(new AppiumServiceBuilder().createArgs().get(0), is(definedNode.getAbsolutePath())); } @Test - public void checkAbilityToUseNodeDefinedExplicitly() { + void checkAbilityToUseNodeDefinedExplicitly() { File mainJS = PATH_T0_TEST_MAIN_JS.toFile(); AppiumServiceBuilder builder = new AppiumServiceBuilder() .withAppiumJS(mainJS); @@ -126,21 +125,21 @@ public void checkAbilityToUseNodeDefinedExplicitly() { } @Test - public void checkAbilityToStartServiceOnAFreePort() { + void checkAbilityToStartServiceOnAFreePort() { service = new AppiumServiceBuilder().usingAnyFreePort().build(); service.start(); assertTrue(service.isRunning()); } @Test - public void checkAbilityToStartServiceUsingNonLocalhostIP() { + void checkAbilityToStartServiceUsingNonLocalhostIP() { service = new AppiumServiceBuilder().withIPAddress(testIP).build(); service.start(); assertTrue(service.isRunning()); } @Test - public void checkAbilityToStartServiceUsingFlags() { + void checkAbilityToStartServiceUsingFlags() { service = new AppiumServiceBuilder() .withArgument(CALLBACK_ADDRESS, testIP) .withArgument(SESSION_OVERRIDE) @@ -150,7 +149,7 @@ public void checkAbilityToStartServiceUsingFlags() { } @Test - public void checkAbilityToStartServiceUsingCapabilities() { + void checkAbilityToStartServiceUsingCapabilities() { UiAutomator2Options options = new UiAutomator2Options() .fullReset() .setNewCommandTimeout(Duration.ofSeconds(60)) @@ -165,7 +164,7 @@ public void checkAbilityToStartServiceUsingCapabilities() { } @Test - public void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { + void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { File app = ROOT_TEST_PATH.resolve("ApiDemos-debug.apk").toFile(); UiAutomator2Options options = new UiAutomator2Options() @@ -191,10 +190,10 @@ public void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { } @Test - public void checkAbilityToChangeOutputStream() throws Exception { + void checkAbilityToChangeOutputStream() throws Exception { testLogFile = new File("test"); testLogFile.createNewFile(); - stream = new FileOutputStream(testLogFile); + stream = Files.newOutputStream(testLogFile.toPath()); service = buildDefaultService(); service.addOutPutStream(stream); service.start(); @@ -202,10 +201,10 @@ public void checkAbilityToChangeOutputStream() throws Exception { } @Test - public void checkAbilityToChangeOutputStreamAfterTheServiceIsStarted() throws Exception { + void checkAbilityToChangeOutputStreamAfterTheServiceIsStarted() throws Exception { testLogFile = new File("test"); testLogFile.createNewFile(); - stream = new FileOutputStream(testLogFile); + stream = Files.newOutputStream(testLogFile.toPath()); service = buildDefaultService(); service.start(); service.addOutPutStream(stream); @@ -214,7 +213,7 @@ public void checkAbilityToChangeOutputStreamAfterTheServiceIsStarted() throws Ex } @Test - public void checkAbilityToShutDownService() { + void checkAbilityToShutDownService() { service = buildDefaultService(); service.start(); service.stop(); @@ -222,7 +221,7 @@ public void checkAbilityToShutDownService() { } @Test - public void checkAbilityToStartAndShutDownFewServices() throws Exception { + void checkAbilityToStartAndShutDownFewServices() throws Exception { List services = asList( new AppiumServiceBuilder().usingAnyFreePort().build(), new AppiumServiceBuilder().usingAnyFreePort().build(), @@ -236,7 +235,7 @@ public void checkAbilityToStartAndShutDownFewServices() throws Exception { } @Test - public void checkAbilityToStartServiceWithLogFile() throws Exception { + void checkAbilityToStartServiceWithLogFile() throws Exception { testLogFile = new File("Log.txt"); testLogFile.createNewFile(); service = new AppiumServiceBuilder().withLogFile(testLogFile).build(); @@ -246,7 +245,7 @@ public void checkAbilityToStartServiceWithLogFile() throws Exception { } @Test - public void checkAbilityToStartServiceWithPortUsingFlag() { + void checkAbilityToStartServiceWithPortUsingFlag() { String port = "8996"; String expectedUrl = String.format("http://0.0.0.0:%s/", port); @@ -259,7 +258,7 @@ public void checkAbilityToStartServiceWithPortUsingFlag() { } @Test - public void checkAbilityToStartServiceWithPortUsingShortFlag() { + void checkAbilityToStartServiceWithPortUsingShortFlag() { String port = "8996"; String expectedUrl = String.format("http://0.0.0.0:%s/", port); @@ -272,7 +271,7 @@ public void checkAbilityToStartServiceWithPortUsingShortFlag() { } @Test - public void checkAbilityToStartServiceWithIpUsingFlag() { + void checkAbilityToStartServiceWithIpUsingFlag() { String expectedUrl = String.format("http://%s:4723/", testIP); service = new AppiumServiceBuilder() @@ -284,7 +283,7 @@ public void checkAbilityToStartServiceWithIpUsingFlag() { } @Test - public void checkAbilityToStartServiceWithIpUsingShortFlag() { + void checkAbilityToStartServiceWithIpUsingShortFlag() { String expectedUrl = String.format("http://%s:4723/", testIP); service = new AppiumServiceBuilder() @@ -296,7 +295,7 @@ public void checkAbilityToStartServiceWithIpUsingShortFlag() { } @Test - public void checkAbilityToStartServiceWithLogFileUsingFlag() { + void checkAbilityToStartServiceWithLogFileUsingFlag() { testLogFile = new File("Log2.txt"); service = new AppiumServiceBuilder() @@ -307,7 +306,7 @@ public void checkAbilityToStartServiceWithLogFileUsingFlag() { } @Test - public void checkAbilityToStartServiceWithLogFileUsingShortFlag() { + void checkAbilityToStartServiceWithLogFileUsingShortFlag() { testLogFile = new File("Log3.txt"); service = new AppiumServiceBuilder() @@ -318,7 +317,7 @@ public void checkAbilityToStartServiceWithLogFileUsingShortFlag() { } @Test - public void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { + void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); String basePath = "wd/hub"; service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); @@ -328,7 +327,7 @@ public void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() } @Test - public void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { + void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); String basePath = "/wd/"; service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); @@ -338,17 +337,17 @@ public void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { } @Test - public void checkAbilityToValidateBasePathForEmptyBasePath() { + void checkAbilityToValidateBasePathForEmptyBasePath() { assertThrows(IllegalArgumentException.class, () -> new AppiumServiceBuilder().withArgument(BASEPATH, "")); } @Test - public void checkAbilityToValidateBasePathForBlankBasePath() { + void checkAbilityToValidateBasePathForBlankBasePath() { assertThrows(IllegalArgumentException.class, () -> new AppiumServiceBuilder().withArgument(BASEPATH, " ")); } @Test - public void checkAbilityToValidateBasePathForNullBasePath() { + void checkAbilityToValidateBasePathForNullBasePath() { assertThrows(NullPointerException.class, () -> new AppiumServiceBuilder().withArgument(BASEPATH, null)); } } diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java index 3d248f462..55906d13f 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java @@ -16,6 +16,12 @@ package io.appium.java_client.service.local; +import static io.appium.java_client.TestResources.apiDemosApk; +import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.remote.AutomationName; @@ -23,21 +29,14 @@ import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.github.bonigarcia.wdm.WebDriverManager; +import java.time.Duration; import org.junit.jupiter.api.Test; import org.openqa.selenium.Capabilities; -import java.time.Duration; - -import static io.appium.java_client.TestResources.apiDemosApk; -import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class StartingAppLocallyAndroidTest { +class StartingAppLocallyAndroidTest { @Test - public void startingAndroidAppWithCapabilitiesOnlyTest() { + void startingAndroidAppWithCapabilitiesOnlyTest() { AndroidDriver driver = new AndroidDriver(new UiAutomator2Options() .setDeviceName("Android Emulator") .autoGrantPermissions() @@ -57,7 +56,7 @@ public void startingAndroidAppWithCapabilitiesOnlyTest() { } @Test - public void startingAndroidAppWithCapabilitiesAndServiceTest() { + void startingAndroidAppWithCapabilitiesAndServiceTest() { AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) .withArgument(GeneralServerFlag.STRICT_CAPS); @@ -79,7 +78,7 @@ public void startingAndroidAppWithCapabilitiesAndServiceTest() { } @Test - public void startingAndroidAppWithCapabilitiesAndFlagsOnServerSideTest() { + void startingAndroidAppWithCapabilitiesAndFlagsOnServerSideTest() { UiAutomator2Options serverOptions = new UiAutomator2Options() .setDeviceName("Android Emulator") .fullReset() diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java index ec629f8ba..07bf8008a 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java @@ -33,9 +33,9 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; -public class StartingAppLocallyIosTest { +class StartingAppLocallyIosTest { @Test - public void startingIOSAppWithCapabilitiesOnlyTest() { + void startingIOSAppWithCapabilitiesOnlyTest() { XCUITestOptions options = new XCUITestOptions() .setPlatformVersion(BaseIOSTest.PLATFORM_VERSION) .setDeviceName(BaseIOSTest.DEVICE_NAME) @@ -57,7 +57,7 @@ public void startingIOSAppWithCapabilitiesOnlyTest() { @Test - public void startingIOSAppWithCapabilitiesAndServiceTest() { + void startingIOSAppWithCapabilitiesAndServiceTest() { XCUITestOptions options = new XCUITestOptions() .setPlatformVersion(BaseIOSTest.PLATFORM_VERSION) .setDeviceName(BaseIOSTest.DEVICE_NAME) @@ -80,7 +80,7 @@ public void startingIOSAppWithCapabilitiesAndServiceTest() { } @Test - public void startingIOSAppWithCapabilitiesAndFlagsOnServerSideTest() { + void startingIOSAppWithCapabilitiesAndFlagsOnServerSideTest() { XCUITestOptions serverOptions = new XCUITestOptions() .setPlatformVersion(BaseIOSTest.PLATFORM_VERSION) .setDeviceName(BaseIOSTest.DEVICE_NAME) diff --git a/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java b/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java index 50d59a5df..f4fb55b42 100644 --- a/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java +++ b/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java @@ -4,11 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import io.appium.java_client.service.local.AppiumDriverLocalService; -import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.jupiter.api.Test; -public class ThreadSafetyTest { +class ThreadSafetyTest { private final AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); private final Action run = new Action() { @@ -32,7 +30,8 @@ public class ThreadSafetyTest { }; private final Action stop2 = stop.clone(); - @Test public void whenFewTreadsDoTheSameWork() throws Throwable { + @Test + void whenFewTreadsDoTheSameWork() throws Throwable { TestThread runTestThread = new TestThread<>(run); TestThread runTestThread2 = new TestThread<>(run2); @@ -116,7 +115,8 @@ public class ThreadSafetyTest { } - @Test public void whenFewTreadsDoDifferentWork() throws Throwable { + @Test + void whenFewTreadsDoDifferentWork() throws Throwable { TestThread runTestThread = new TestThread<>(run); TestThread runTestThread2 = new TestThread<>(run2); From c8757f5172221d9f395b2eea1286e83ce6cb6f52 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 5 Sep 2022 20:50:40 +0200 Subject: [PATCH 006/314] ci: Verify if the PR title complies with conventional commits spec (#1757) --- .github/workflows/pr-title.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/pr-title.yml diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 000000000..b7167560f --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,13 @@ +name: Conventional Commits +on: + pull_request: + + +jobs: + lint: + name: https://www.conventionalcommits.org + runs-on: ubuntu-latest + steps: + - uses: beemojs/conventional-pr-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4ec8753e94e5d989fe1ffa6fb40e56a492f951eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 14:44:59 +0300 Subject: [PATCH 007/314] build(deps): bump slf4j-api from 1.7.36 to 2.0.1 (#1762) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.36 to 2.0.1. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.36...v_2.0.1) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] 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 1552006d4..8de85a21a 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:1.7.36' + implementation 'org.slf4j:slf4j-api:2.0.1' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation 'org.hamcrest:hamcrest:2.2' From fe17e854ad61605e2f4e1988fde2cd5615cfaf2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 12:56:23 +0300 Subject: [PATCH 008/314] build(deps): bump org.owasp.dependencycheck from 7.1.2 to 7.2.0 (#1763) Bumps org.owasp.dependencycheck from 7.1.2 to 7.2.0. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 8de85a21a..bb996e42a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.1.2' + id 'org.owasp.dependencycheck' version '7.2.0' id 'com.github.johnrengelman.shadow' version '7.1.2' } From 3d6abbdd499de7733dd7bebfa8cd10448937d54f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 14:28:50 +0300 Subject: [PATCH 009/314] build(deps): bump junit-jupiter from 5.9.0 to 5.9.1 (#1768) Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.9.0 to 5.9.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.0...r5.9.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 bb996e42a..f1a417751 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'commons-io:commons-io:2.11.0' implementation 'org.slf4j:slf4j-api:2.0.1' - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.0') { exclude group: 'org.seleniumhq.selenium' From abbcffed5df125a0e110b22dba6ed8df31444c5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:15:44 +0300 Subject: [PATCH 010/314] build(deps): bump slf4j-api from 2.0.1 to 2.0.2 (#1767) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.1...v_2.0.2) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 f1a417751..32758b058 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:2.0.1' + implementation 'org.slf4j:slf4j-api:2.0.2' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' From 31e99f31d3f796742c2dee27b05f5e1b0c85613a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 10:13:42 +0530 Subject: [PATCH 011/314] build(deps): bump org.owasp.dependencycheck from 7.2.0 to 7.2.1 (#1766) 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 32758b058..2e9865f5c 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.2.0' + id 'org.owasp.dependencycheck' version '7.2.1' id 'com.github.johnrengelman.shadow' version '7.1.2' } From a829beca70c2e4cd9ab00cf0f70e17a384323c1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 18:47:56 +0300 Subject: [PATCH 012/314] build(deps): bump slf4j-api from 2.0.2 to 2.0.3 (#1773) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.2...v_2.0.3) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 2e9865f5c..084b502cb 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:2.0.2' + implementation 'org.slf4j:slf4j-api:2.0.3' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' From 45eaae0fc5605d604bb70d5b2332baafc4fc070d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 22 Oct 2022 21:20:08 +0200 Subject: [PATCH 013/314] ci: Tune conventional commit settings --- .github/workflows/pr-title.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index b7167560f..f3387333b 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -9,5 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: beemojs/conventional-pr-action@v2 + with: + config-preset: angular env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 898d7ee7826b6e97d3296110af54033af140ce7a Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sat, 22 Oct 2022 20:22:44 -0700 Subject: [PATCH 014/314] feat: add directConnect feature (#1747) --- .github/workflows/gradle.yml | 2 +- build.gradle | 9 + gradle.properties | 2 +- .../java_client/AppiumClientConfig.java | 191 ++++++++++++++++++ .../io/appium/java_client/AppiumDriver.java | 3 +- .../java_client/android/AndroidDriver.java | 28 ++- .../appium/java_client/gecko/GeckoDriver.java | 27 ++- .../io/appium/java_client/ios/IOSDriver.java | 29 ++- .../io/appium/java_client/mac/Mac2Driver.java | 28 ++- .../remote/AppiumCommandExecutor.java | 126 +++++++++--- .../java_client/remote/DirectConnect.java | 77 +++++++ .../java_client/safari/SafariDriver.java | 28 ++- .../java_client/windows/WindowsDriver.java | 29 ++- .../events/stubs/EmptyWebDriver.java | 4 - .../java_client/internal/ConfigTest.java | 9 +- .../internal/DirectConnectTest.java | 56 +++++ .../widget/tests/AbstractStubWebDriver.java | 5 - 17 files changed, 602 insertions(+), 51 deletions(-) create mode 100644 src/main/java/io/appium/java_client/AppiumClientConfig.java create mode 100644 src/main/java/io/appium/java_client/remote/DirectConnect.java create mode 100644 src/test/java/io/appium/java_client/internal/DirectConnectTest.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e5981b475..caf838f59 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -27,4 +27,4 @@ jobs: java-version: ${{ matrix.java }} cache: 'gradle' - name: Build with Gradle - run: ./gradlew clean build -x test -x checkstyleTest + run: ./gradlew clean build -x unitTest -x checkstyleTest diff --git a/build.gradle b/build.gradle index 084b502cb..b0116c43b 100644 --- a/build.gradle +++ b/build.gradle @@ -191,6 +191,15 @@ processResources { ] } +task unitTest( type: Test ) { + useJUnitPlatform() + testLogging.showStandardStreams = true + testLogging.exceptionFormat = 'full' + filter { + includeTestsMatching 'io.appium.java_client.internal.*' + } +} + task xcuiTest( type: Test ) { useJUnitPlatform() testLogging.showStandardStreams = true diff --git a/gradle.properties b/gradle.properties index b11361057..9b086f575 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,4 @@ signing.secretKeyRingFile=PathToYourKeyRingFile ossrhUsername=your-jira-id ossrhPassword=your-jira-password -selenium.version=4.4.0 +selenium.version=4.5.0 diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java new file mode 100644 index 000000000..2e6128c03 --- /dev/null +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -0,0 +1,191 @@ +/* + * 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.Credentials; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.http.AddSeleniumUserAgent; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.Filter; + +import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.time.Duration; + +/** + * A class to store the appium http client configuration. + */ +public class AppiumClientConfig extends ClientConfig { + private final boolean directConnect; + + private static final Filter DEFAULT_FILTER = new AddSeleniumUserAgent(); + + private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(10); + + private static final Duration DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(10); + + /** + * Client side configuration. + * + * @param baseUri Base URL the client sends HTTP request to. + * @param connectionTimeout The client connection timeout. + * @param readTimeout The client read timeout. + * @param filters Filters to modify incoming {@link org.openqa.selenium.remote.http.HttpRequest} or outgoing + * {@link org.openqa.selenium.remote.http.HttpResponse}. + * @param proxy The client proxy preference. + * @param credentials Credentials used for authenticating http requests + * @param directConnect If directConnect is enabled. + */ + protected AppiumClientConfig( + URI baseUri, + Duration connectionTimeout, + Duration readTimeout, + Filter filters, + Proxy proxy, + Credentials credentials, + Boolean directConnect) { + super(baseUri, connectionTimeout, readTimeout, filters, proxy, credentials); + + this.directConnect = Require.nonNull("Direct Connect", directConnect); + } + + /** + * Return the instance of {@link AppiumClientConfig} with a default config. + * @return the instance of {@link AppiumClientConfig}. + */ + public static AppiumClientConfig defaultConfig() { + return new AppiumClientConfig( + null, + DEFAULT_CONNECTION_TIMEOUT, + DEFAULT_READ_TIMEOUT, + DEFAULT_FILTER, + null, + null, + false); + } + + /** + * Return the instance of {@link AppiumClientConfig} from the given {@link ClientConfig} parameters. + * @param clientConfig take a look at {@link ClientConfig} + * @return the instance of {@link AppiumClientConfig}. + */ + public static AppiumClientConfig fromClientConfig(ClientConfig clientConfig) { + return new AppiumClientConfig( + clientConfig.baseUri(), + clientConfig.connectionTimeout(), + clientConfig.readTimeout(), + clientConfig.filter(), + clientConfig.proxy(), + clientConfig.credentials(), + false); + } + + private AppiumClientConfig buildAppiumClientConfig(ClientConfig clientConfig, Boolean directConnect) { + return new AppiumClientConfig( + clientConfig.baseUri(), + clientConfig.connectionTimeout(), + clientConfig.readTimeout(), + clientConfig.filter(), + clientConfig.proxy(), + clientConfig.credentials(), + directConnect); + } + + @Override + public AppiumClientConfig baseUri(URI baseUri) { + ClientConfig clientConfig = super.baseUri(baseUri); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + @Override + public AppiumClientConfig baseUrl(URL baseUrl) { + try { + return baseUri(Require.nonNull("Base URL", baseUrl).toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public AppiumClientConfig connectionTimeout(Duration timeout) { + ClientConfig clientConfig = super.connectionTimeout(timeout); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + @Override + public AppiumClientConfig readTimeout(Duration timeout) { + ClientConfig clientConfig = super.connectionTimeout(timeout); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + @Override + public AppiumClientConfig withFilter(Filter filter) { + ClientConfig clientConfig = super.withFilter(filter); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + @Override + public AppiumClientConfig withRetries() { + ClientConfig clientConfig = super.withRetries(); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + + @Override + public ClientConfig proxy(Proxy proxy) { + ClientConfig clientConfig = super.proxy(proxy); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + @Override + public AppiumClientConfig authenticateAs(Credentials credentials) { + ClientConfig clientConfig = super.authenticateAs(credentials); + return buildAppiumClientConfig(clientConfig, directConnect); + } + + /** + * Whether enable directConnect feature described in + * + * Connecting Directly to Appium Hosts in Distributed Environments. + * + * @param directConnect if enable the directConnect feature + * @return A new instance of AppiumClientConfig + */ + public AppiumClientConfig directConnect(boolean directConnect) { + // follows ClientConfig's design + return new AppiumClientConfig( + this.baseUri(), + this.connectionTimeout(), + this.readTimeout(), + this.filter(), + this.proxy(), + this.credentials(), + directConnect + ); + } + + /** + * Whether enable directConnect feature is enabled. + * + * @return If the directConnect is enabled. Defaults false. + */ + public boolean isDirectConnectEnabled() { + return directConnect; + } +} diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 0f109af2c..d814657c6 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -41,7 +41,6 @@ import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.html5.RemoteLocationContext; -import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpMethod; @@ -84,7 +83,7 @@ public AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { this.remoteAddress = executor.getAddressOfRemoteServer(); } - public AppiumDriver(ClientConfig clientConfig, Capabilities capabilities) { + public AppiumDriver(AppiumClientConfig clientConfig, Capabilities capabilities) { this(new AppiumCommandExecutor(MobileCommand.commandRepository, clientConfig), capabilities); } 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 76810cac3..92ed19370 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableMap; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecuteCDPCommand; @@ -203,7 +204,32 @@ public AndroidDriver(HttpClient.Factory httpClientFactory, Capabilities capabili * */ public AndroidDriver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensurePlatformName(capabilities, ANDROID_PLATFORM)); + super(AppiumClientConfig.fromClientConfig(clientConfig), ensurePlatformName(capabilities, + ANDROID_PLATFORM)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * UiAutomator2Options options = new UiAutomator2Options();
+     * AndroidDriver driver = new AndroidDriver(appiumClientConfig, options);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public AndroidDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensurePlatformName(capabilities, ANDROID_PLATFORM)); } /** diff --git a/src/main/java/io/appium/java_client/gecko/GeckoDriver.java b/src/main/java/io/appium/java_client/gecko/GeckoDriver.java index 43d2072c8..6a7a55cab 100644 --- a/src/main/java/io/appium/java_client/gecko/GeckoDriver.java +++ b/src/main/java/io/appium/java_client/gecko/GeckoDriver.java @@ -16,6 +16,7 @@ package io.appium.java_client.gecko; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.service.local.AppiumDriverLocalService; @@ -94,7 +95,31 @@ public GeckoDriver(HttpClient.Factory httpClientFactory, Capabilities capabiliti * */ public GeckoDriver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensureAutomationName(capabilities, AUTOMATION_NAME)); + super(AppiumClientConfig.fromClientConfig(clientConfig), ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * GeckoOptions options = new GeckoOptions();
+     * GeckoDriver driver = new GeckoDriver(options, appiumClientConfig);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public GeckoDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensureAutomationName(capabilities, AUTOMATION_NAME)); } public GeckoDriver(Capabilities capabilities) { 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 18ecf3065..00098b14c 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableMap; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.HasAppStrings; import io.appium.java_client.HasDeviceTime; @@ -192,9 +193,35 @@ public IOSDriver(HttpClient.Factory httpClientFactory, Capabilities capabilities * */ public IOSDriver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensurePlatformName(capabilities, PLATFORM_NAME)); + super(AppiumClientConfig.fromClientConfig(clientConfig), + ensurePlatformName(capabilities, PLATFORM_NAME)); } + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * XCUITestOptions options = new XCUITestOptions();
+     * IOSDriver driver = new IOSDriver(options, appiumClientConfig);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public IOSDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensurePlatformName(capabilities, PLATFORM_NAME)); + } + + /** * Creates a new instance based on {@code capabilities}. * 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 b76de5943..905cbec77 100644 --- a/src/main/java/io/appium/java_client/mac/Mac2Driver.java +++ b/src/main/java/io/appium/java_client/mac/Mac2Driver.java @@ -16,6 +16,7 @@ package io.appium.java_client.mac; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.PerformsTouchActions; import io.appium.java_client.remote.AutomationName; @@ -105,7 +106,32 @@ public Mac2Driver(HttpClient.Factory httpClientFactory, Capabilities capabilitie * */ public Mac2Driver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensurePlatformAndAutomationNames( + super(AppiumClientConfig.fromClientConfig(clientConfig), ensurePlatformAndAutomationNames( + capabilities, PLATFORM_NAME, AUTOMATION_NAME)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * Mac2Options options = new Mac2Options();
+     * Mac2Driver driver = new Mac2Driver(appiumClientConfig, options);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public Mac2Driver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensurePlatformAndAutomationNames( capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } 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 01c551fb4..ec792852a 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -24,9 +24,9 @@ import com.google.common.base.Supplier; import com.google.common.base.Throwables; +import io.appium.java_client.AppiumClientConfig; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.CommandCodec; import org.openqa.selenium.remote.CommandExecutor; @@ -38,17 +38,18 @@ 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.ClientConfig; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; import org.openqa.selenium.remote.service.DriverService; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.lang.reflect.Field; import java.net.ConnectException; +import java.net.MalformedURLException; import java.net.URL; -import java.time.Duration; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -56,49 +57,71 @@ 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 static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(10); private final Optional serviceOptional; - private AppiumCommandExecutor(Map additionalCommands, DriverService service, - URL addressOfRemoteServer, - HttpClient.Factory httpClientFactory, - ClientConfig clientConfig) { + private final HttpClient.Factory httpClientFactory; + + private final AppiumClientConfig appiumClientConfig; + + /** + * Create an AppiumCommandExecutor instance. + * + * @param additionalCommands is the map of Appium commands + * @param service take a look at {@link DriverService} + * @param httpClientFactory take a look at {@link HttpClient.Factory} + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + */ + public AppiumCommandExecutor( + @Nonnull Map additionalCommands, + @Nullable DriverService service, + @Nullable HttpClient.Factory httpClientFactory, + @Nonnull AppiumClientConfig appiumClientConfig) { super(additionalCommands, - ofNullable(clientConfig).orElse( - ClientConfig.defaultConfig() - .baseUrl(Require.nonNull("Server URL", ofNullable(service) - .map(DriverService::getUrl) - .orElse(addressOfRemoteServer))) - .readTimeout(DEFAULT_READ_TIMEOUT) - ), - ofNullable(httpClientFactory).orElseGet(HttpCommandExecutor::getDefaultClientFactory) + appiumClientConfig, + ofNullable(httpClientFactory).orElseGet(AppiumCommandExecutor::getDefaultClientFactory) ); serviceOptional = ofNullable(service); + + this.httpClientFactory = httpClientFactory; + this.appiumClientConfig = appiumClientConfig; } public AppiumCommandExecutor(Map additionalCommands, DriverService service, HttpClient.Factory httpClientFactory) { - this(additionalCommands, checkNotNull(service), null, httpClientFactory, null); + this(additionalCommands, checkNotNull(service), httpClientFactory, + AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(service).getUrl())); } - public AppiumCommandExecutor(Map additionalCommands, - URL addressOfRemoteServer, HttpClient.Factory httpClientFactory) { - this(additionalCommands, null, checkNotNull(addressOfRemoteServer), httpClientFactory, null); + public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer, + HttpClient.Factory httpClientFactory) { + this(additionalCommands, null, httpClientFactory, + AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(addressOfRemoteServer))); } - public AppiumCommandExecutor(Map additionalCommands, ClientConfig clientConfig) { - this(additionalCommands, null, checkNotNull(clientConfig.baseUrl()), null, clientConfig); + public AppiumCommandExecutor(Map additionalCommands, AppiumClientConfig appiumClientConfig) { + this(additionalCommands, null, null, appiumClientConfig); } - public AppiumCommandExecutor(Map additionalCommands, - URL addressOfRemoteServer) { - this(additionalCommands, addressOfRemoteServer, HttpClient.Factory.createDefault()); + public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer) { + this(additionalCommands, null, HttpClient.Factory.createDefault(), + AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(addressOfRemoteServer))); + } + + public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer, + AppiumClientConfig appiumClientConfig) { + this(additionalCommands, null, HttpClient.Factory.createDefault(), + appiumClientConfig.baseUrl(checkNotNull(addressOfRemoteServer))); + } + + public AppiumCommandExecutor(Map additionalCommands, DriverService service) { + this(additionalCommands, service, HttpClient.Factory.createDefault(), + AppiumClientConfig.defaultConfig().baseUrl(service.getUrl())); } public AppiumCommandExecutor(Map additionalCommands, - DriverService service) { - this(additionalCommands, service, HttpClient.Factory.createDefault()); + DriverService service, AppiumClientConfig appiumClientConfig) { + this(additionalCommands, service, HttpClient.Factory.createDefault(), appiumClientConfig); } @SuppressWarnings("SameParameterValue") @@ -147,6 +170,21 @@ protected HttpClient getClient() { return getPrivateFieldValue(HttpCommandExecutor.class, "client", HttpClient.class); } + /** + * Override the http client in the HttpCommandExecutor class with a new http client instance with the given URL. + * It uses the same http client factory and client config for the new http client instance + * if the constructor got them. + * @param serverUrl A url to override. + */ + protected void overrideServerUrl(URL serverUrl) { + if (this.appiumClientConfig == null) { + return; + } + setPrivateFieldValue(HttpCommandExecutor.class, "client", + ofNullable(this.httpClientFactory).orElseGet(AppiumCommandExecutor::getDefaultClientFactory) + .createClient(this.appiumClientConfig.baseUrl(serverUrl))); + } + private Response createSession(Command command) throws IOException { if (getCommandCodec() != null) { throw new SessionNotCreatedException("Session already exists"); @@ -166,13 +204,45 @@ private Response createSession(Command command) throws IOException { setCommandCodec(new AppiumW3CHttpCommandCodec()); refreshAdditionalCommands(); setResponseCodec(dialect.getResponseCodec()); - return result.createResponse(); + Response response = result.createResponse(); + if (this.appiumClientConfig != null && this.appiumClientConfig.isDirectConnectEnabled()) { + setDirectConnect(response); + } + + return response; } public void refreshAdditionalCommands() { getAdditionalCommands().forEach(this::defineCommand); } + @SuppressWarnings("unchecked") + private void setDirectConnect(Response response) throws SessionNotCreatedException { + Map responseValue = (Map) response.getValue(); + + DirectConnect directConnect = new DirectConnect(responseValue); + + if (!directConnect.isValid()) { + return; + } + + if (!directConnect.getProtocol().equals("https")) { + throw new SessionNotCreatedException( + String.format("The given protocol '%s' as the direct connection url returned by " + + "the remote server is not accurate. Only 'https' is supported.", + directConnect.getProtocol())); + } + + URL newUrl; + try { + newUrl = directConnect.getUrl(); + } catch (MalformedURLException e) { + throw new SessionNotCreatedException(e.getMessage()); + } + + overrideServerUrl(newUrl); + } + @Override public Response execute(Command command) throws WebDriverException { if (DriverCommand.NEW_SESSION.equals(command.getName())) { diff --git a/src/main/java/io/appium/java_client/remote/DirectConnect.java b/src/main/java/io/appium/java_client/remote/DirectConnect.java new file mode 100644 index 000000000..a2049c589 --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/DirectConnect.java @@ -0,0 +1,77 @@ +package io.appium.java_client.remote; + +import javax.annotation.Nullable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; + +// TODO: simplify with lombok as another PR +public class DirectConnect { + private static final String DIRECT_CONNECT_PROTOCOL = "directConnectProtocol"; + private static final String DIRECT_CONNECT_PATH = "directConnectPath"; + private static final String DIRECT_CONNECT_HOST = "directConnectHost"; + private static final String DIRECT_CONNECT_PORT = "directConnectPort"; + + private final String protocol; + + private final String path; + + private final String host; + + private final String port; + + /** + * Create a DirectConnect instance. + * @param responseValue is the response body + */ + public DirectConnect(Map responseValue) { + this.protocol = this.getDirectConnectValue(responseValue, DIRECT_CONNECT_PROTOCOL); + this.path = this.getDirectConnectValue(responseValue, DIRECT_CONNECT_PATH); + this.host = this.getDirectConnectValue(responseValue, DIRECT_CONNECT_HOST); + this.port = this.getDirectConnectValue(responseValue, DIRECT_CONNECT_PORT); + } + + public String getProtocol() { + return protocol; + } + + @Nullable + private String getDirectConnectValue(Map responseValue, String key) { + Object directConnectPath = responseValue.get(APPIUM_PREFIX + key); + if (directConnectPath != null) { + return String.valueOf(directConnectPath); + } + directConnectPath = responseValue.get(key); + return directConnectPath == null ? null : String.valueOf(directConnectPath); + } + + /** + * Returns true if the {@link DirectConnect} instance member has nonnull values. + * @return true if each connection information has a nonnull value + */ + public boolean isValid() { + return Stream.of(this.protocol, this.path, this.host, this.port).noneMatch(Objects::isNull); + } + + /** + * Returns a URL instance built with members in the DirectConnect instance. + * @return A URL object + * @throws MalformedURLException if the built url was invalid + */ + public URL getUrl() throws MalformedURLException { + String newUrlCandidate = String.format("%s://%s:%s%s", this.protocol, this.host, this.port, this.path); + + try { + return new URL(newUrlCandidate); + } catch (MalformedURLException e) { + throw new MalformedURLException( + String.format("The remote server returned an invalid value to build the direct connect URL: %s", + newUrlCandidate) + ); + } + } +} diff --git a/src/main/java/io/appium/java_client/safari/SafariDriver.java b/src/main/java/io/appium/java_client/safari/SafariDriver.java index 0b8a8400d..97d1f96e4 100644 --- a/src/main/java/io/appium/java_client/safari/SafariDriver.java +++ b/src/main/java/io/appium/java_client/safari/SafariDriver.java @@ -16,6 +16,7 @@ package io.appium.java_client.safari; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.service.local.AppiumDriverLocalService; @@ -102,7 +103,32 @@ public SafariDriver(HttpClient.Factory httpClientFactory, Capabilities capabilit * */ public SafariDriver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensurePlatformAndAutomationNames( + super(AppiumClientConfig.fromClientConfig(clientConfig), ensurePlatformAndAutomationNames( + capabilities, PLATFORM_NAME, AUTOMATION_NAME)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * SafariOptions options = new SafariOptions();
+     * SafariDriver driver = new SafariDriver(appiumClientConfig, options);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public SafariDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensurePlatformAndAutomationNames( capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } 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 82af6e02d..9a441d68a 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsDriver.java +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -16,6 +16,7 @@ package io.appium.java_client.windows; +import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileCommand; import io.appium.java_client.PerformsTouchActions; @@ -45,6 +46,7 @@ public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, ensurePlatformAndAutomationNames(capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } + public WindowsDriver(URL remoteAddress, Capabilities capabilities) { super(remoteAddress, ensurePlatformAndAutomationNames( capabilities, PLATFORM_NAME, AUTOMATION_NAME)); @@ -100,7 +102,32 @@ public WindowsDriver(HttpClient.Factory httpClientFactory, Capabilities capabili * */ public WindowsDriver(ClientConfig clientConfig, Capabilities capabilities) { - super(clientConfig, ensurePlatformAndAutomationNames( + super(AppiumClientConfig.fromClientConfig(clientConfig), ensurePlatformAndAutomationNames( + capabilities, PLATFORM_NAME, AUTOMATION_NAME)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * WindowsOptions options = new WindowsOptions();
+     * WindowsDriver driver = new WindowsDriver(appiumClientConfig, options);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public WindowsDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensurePlatformAndAutomationNames( capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } diff --git a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java index 5316f56e4..01104b988 100644 --- a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java +++ b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java @@ -202,10 +202,6 @@ public Timeouts timeouts() { return null; } - public ImeHandler ime() { - return null; - } - public Window window() { return new StubWindow(); } diff --git a/src/test/java/io/appium/java_client/internal/ConfigTest.java b/src/test/java/io/appium/java_client/internal/ConfigTest.java index df8611458..05559de27 100644 --- a/src/test/java/io/appium/java_client/internal/ConfigTest.java +++ b/src/test/java/io/appium/java_client/internal/ConfigTest.java @@ -9,13 +9,14 @@ import org.junit.jupiter.api.Test; class ConfigTest { - private static final String EXISTING_KEY = "selenium.version"; + private static final String SELENIUM_EXISTING_KEY = "selenium.version"; + private static final String MISSING_KEY = "bla"; @Test void verifyGettingExistingValue() { - assertThat(Config.main().getValue(EXISTING_KEY, String.class).length(), greaterThan(0)); - assertTrue(Config.main().getOptionalValue(EXISTING_KEY, String.class).isPresent()); + assertThat(Config.main().getValue(SELENIUM_EXISTING_KEY, String.class).length(), greaterThan(0)); + assertTrue(Config.main().getOptionalValue(SELENIUM_EXISTING_KEY, String.class).isPresent()); } @Test @@ -25,7 +26,7 @@ void verifyGettingNonExistingValue() { @Test void verifyGettingExistingValueWithWrongClass() { - assertThrows(ClassCastException.class, () -> Config.main().getValue(EXISTING_KEY, Integer.class)); + assertThrows(ClassCastException.class, () -> Config.main().getValue(SELENIUM_EXISTING_KEY, Integer.class)); } @Test diff --git a/src/test/java/io/appium/java_client/internal/DirectConnectTest.java b/src/test/java/io/appium/java_client/internal/DirectConnectTest.java new file mode 100644 index 000000000..93b345474 --- /dev/null +++ b/src/test/java/io/appium/java_client/internal/DirectConnectTest.java @@ -0,0 +1,56 @@ +package io.appium.java_client.internal; + +import io.appium.java_client.remote.DirectConnect; +import org.junit.jupiter.api.Test; + +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class DirectConnectTest { + + @Test + void hasValidDirectConnectValuesWithoutAppiumPrefix() throws MalformedURLException { + Map responseValue = new HashMap<>(); + responseValue.put("directConnectProtocol", "https"); + responseValue.put("directConnectPath", "/path/to"); + responseValue.put("directConnectHost", "host"); + responseValue.put("directConnectPort", "8080"); + DirectConnect directConnect = new DirectConnect(responseValue); + assertTrue(directConnect.isValid()); + assertEquals(directConnect.getUrl().toString(), "https://host:8080/path/to"); + } + + @Test + void hasValidDirectConnectValuesWithAppiumPrefix() throws MalformedURLException { + Map responseValue = new HashMap<>(); + responseValue.put("appium:directConnectProtocol", "https"); + responseValue.put("appium:directConnectPath", "/path/to"); + responseValue.put("appium:directConnectHost", "host"); + responseValue.put("appium:directConnectPort", "8080"); + DirectConnect directConnect = new DirectConnect(responseValue); + assertTrue(directConnect.isValid()); + assertEquals(directConnect.getUrl().toString(), "https://host:8080/path/to"); + } + + @Test + void hasValidDirectConnectStringPort() { + Map responseValue = new HashMap<>(); + responseValue.put("appium:directConnectProtocol", "https"); + responseValue.put("appium:directConnectPath", "/path/to"); + responseValue.put("appium:directConnectHost", "host"); + responseValue.put("appium:directConnectPort", "port"); + DirectConnect directConnect = new DirectConnect(responseValue); + assertTrue(directConnect.isValid()); + assertThrowsExactly(MalformedURLException.class, directConnect::getUrl); + } + + @Test + void hasInvalidDirectConnect() { + Map responseValue = new HashMap<>(); + DirectConnect directConnect = new DirectConnect(responseValue); + assertFalse(directConnect.isValid()); + } +} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java index bea245a99..f71d1d8c4 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java @@ -173,11 +173,6 @@ public Timeouts pageLoadTimeout(long time, TimeUnit unit) { }; } - @Override - public ImeHandler ime() { - return null; - } - @Override public Window window() { return null; From f185039f9f75e86e041320324e314b9f1b655d71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:47:45 +0300 Subject: [PATCH 015/314] build(deps): bump org.owasp.dependencycheck from 7.2.1 to 7.3.0 (#1780) Bumps org.owasp.dependencycheck from 7.2.1 to 7.3.0. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 b0116c43b..58f80d652 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.2.1' + id 'org.owasp.dependencycheck' version '7.3.0' id 'com.github.johnrengelman.shadow' version '7.1.2' } From b5c9e39487d60c56e45bc3e59f14912d7791b77e Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 27 Oct 2022 23:24:53 +0300 Subject: [PATCH 016/314] fix: Enforce usage of Base64 compliant with RFC 4648 for all operations (#1785) --- build.gradle | 1 - .../java/io/appium/java_client/ComparesImages.java | 14 +++++++------- .../java/io/appium/java_client/PushesFiles.java | 4 ++-- .../imagecomparison/ComparisonResult.java | 4 ++-- .../java_client/android/AndroidDriverTest.java | 4 ++-- .../java_client/android/ImagesComparisonTest.java | 9 +++++---- .../java_client/ios/ImagesComparisonTest.java | 9 +++++---- 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/build.gradle b/build.gradle index 58f80d652..54be347a1 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,6 @@ dependencies { } } implementation 'com.google.code.gson:gson:2.9.1' - implementation 'commons-codec:commons-codec:1.15' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 3cb85036c..c1c97abc9 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -25,11 +25,11 @@ import io.appium.java_client.imagecomparison.OccurrenceMatchingResult; import io.appium.java_client.imagecomparison.SimilarityMatchingOptions; import io.appium.java_client.imagecomparison.SimilarityMatchingResult; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; +import java.util.Base64; import java.util.Map; import javax.annotation.Nullable; @@ -93,8 +93,8 @@ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2) thr */ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2, @Nullable FeaturesMatchingOptions options) throws IOException { - return matchImagesFeatures(Base64.encodeBase64(FileUtils.readFileToByteArray(image1)), - Base64.encodeBase64(FileUtils.readFileToByteArray(image2)), options); + return matchImagesFeatures(Base64.getEncoder().encode(FileUtils.readFileToByteArray(image1)), + Base64.getEncoder().encode(FileUtils.readFileToByteArray(image2)), options); } /** @@ -160,8 +160,8 @@ default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partia default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partialImage, @Nullable OccurrenceMatchingOptions options) throws IOException { - return findImageOccurrence(Base64.encodeBase64(FileUtils.readFileToByteArray(fullImage)), - Base64.encodeBase64(FileUtils.readFileToByteArray(partialImage)), options); + return findImageOccurrence(Base64.getEncoder().encode(FileUtils.readFileToByteArray(fullImage)), + Base64.getEncoder().encode(FileUtils.readFileToByteArray(partialImage)), options); } /** @@ -227,7 +227,7 @@ default SimilarityMatchingResult getImagesSimilarity(File image1, File image2) t default SimilarityMatchingResult getImagesSimilarity(File image1, File image2, @Nullable SimilarityMatchingOptions options) throws IOException { - return getImagesSimilarity(Base64.encodeBase64(FileUtils.readFileToByteArray(image1)), - Base64.encodeBase64(FileUtils.readFileToByteArray(image2)), options); + return getImagesSimilarity(Base64.getEncoder().encode(FileUtils.readFileToByteArray(image1)), + Base64.getEncoder().encode(FileUtils.readFileToByteArray(image2)), options); } } \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index d6da2ca82..a79e58ba9 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -19,11 +19,11 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.pushFileCommand; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; +import java.util.Base64; public interface PushesFiles extends ExecutesMethod { @@ -57,7 +57,7 @@ default void pushFile(String remotePath, File file) throws IOException { throw new IOException(String.format("The given file %s doesn't exist", file.getAbsolutePath())); } - pushFile(remotePath, Base64.encodeBase64(FileUtils.readFileToByteArray(file))); + pushFile(remotePath, Base64.getEncoder().encode(FileUtils.readFileToByteArray(file))); } } 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 1dcfa3e68..1ad0639b3 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -18,7 +18,6 @@ import lombok.AccessLevel; import lombok.Getter; -import org.apache.commons.codec.binary.Base64; import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; @@ -28,6 +27,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Map; public abstract class ComparisonResult { @@ -70,7 +70,7 @@ public byte[] getVisualization() { * @throws IOException On file system I/O error. */ public void storeVisualization(File destination) throws IOException { - final byte[] data = Base64.decodeBase64(getVisualization()); + final byte[] data = Base64.getDecoder().decode(getVisualization()); try (OutputStream stream = new FileOutputStream(destination)) { stream.write(data); } diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index 145bd32dc..f2e502781 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -27,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.fail; import io.appium.java_client.appmanagement.ApplicationState; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; @@ -37,6 +36,7 @@ import java.io.File; import java.time.Duration; import java.util.ArrayList; +import java.util.Base64; import java.util.List; public class AndroidDriverTest extends BaseAndroidTest { @@ -155,7 +155,7 @@ public void closeAppTest() { @Test public void pushFileTest() { - byte[] data = Base64.encodeBase64( + byte[] data = Base64.getEncoder().encode( "The eventual code is no more than the deposit of your understanding. ~E. W. Dijkstra" .getBytes()); driver.pushFile("/data/local/tmp/remote.txt", data); diff --git a/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java b/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java index adc8795ea..e49463b87 100644 --- a/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java +++ b/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.Base64; + import io.appium.java_client.imagecomparison.FeatureDetector; import io.appium.java_client.imagecomparison.FeaturesMatchingOptions; import io.appium.java_client.imagecomparison.FeaturesMatchingResult; @@ -30,7 +32,6 @@ import io.appium.java_client.imagecomparison.OccurrenceMatchingResult; import io.appium.java_client.imagecomparison.SimilarityMatchingOptions; import io.appium.java_client.imagecomparison.SimilarityMatchingResult; -import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Test; import org.openqa.selenium.OutputType; @@ -38,7 +39,7 @@ public class ImagesComparisonTest extends BaseAndroidTest { @Test public void verifyFeaturesMatching() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); FeaturesMatchingResult result = driver .matchImagesFeatures(screenshot, screenshot, new FeaturesMatchingOptions() .withDetectorName(FeatureDetector.ORB) @@ -56,7 +57,7 @@ public void verifyFeaturesMatching() { @Test public void verifyOccurrencesLookup() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); OccurrenceMatchingResult result = driver .findImageOccurrence(screenshot, screenshot, new OccurrenceMatchingOptions() .withEnabledVisualization()); @@ -66,7 +67,7 @@ public void verifyOccurrencesLookup() { @Test public void verifySimilarityCalculation() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); SimilarityMatchingResult result = driver .getImagesSimilarity(screenshot, screenshot, new SimilarityMatchingOptions() .withEnabledVisualization()); diff --git a/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java b/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java index ef52196b5..d724b4ecb 100644 --- a/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java +++ b/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.Base64; + import io.appium.java_client.imagecomparison.FeatureDetector; import io.appium.java_client.imagecomparison.FeaturesMatchingOptions; import io.appium.java_client.imagecomparison.FeaturesMatchingResult; @@ -30,7 +32,6 @@ import io.appium.java_client.imagecomparison.OccurrenceMatchingResult; import io.appium.java_client.imagecomparison.SimilarityMatchingOptions; import io.appium.java_client.imagecomparison.SimilarityMatchingResult; -import org.apache.commons.codec.binary.Base64; import org.junit.jupiter.api.Test; import org.openqa.selenium.OutputType; @@ -38,7 +39,7 @@ public class ImagesComparisonTest extends AppIOSTest { @Test public void verifyFeaturesMatching() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); FeaturesMatchingResult result = driver .matchImagesFeatures(screenshot, screenshot, new FeaturesMatchingOptions() .withDetectorName(FeatureDetector.ORB) @@ -56,7 +57,7 @@ public void verifyFeaturesMatching() { @Test public void verifyOccurrencesSearch() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); OccurrenceMatchingResult result = driver .findImageOccurrence(screenshot, screenshot, new OccurrenceMatchingOptions() .withEnabledVisualization()); @@ -66,7 +67,7 @@ public void verifyOccurrencesSearch() { @Test public void verifySimilarityCalculation() { - byte[] screenshot = Base64.encodeBase64(driver.getScreenshotAs(OutputType.BYTES)); + byte[] screenshot = Base64.getEncoder().encode(driver.getScreenshotAs(OutputType.BYTES)); SimilarityMatchingResult result = driver .getImagesSimilarity(screenshot, screenshot, new SimilarityMatchingOptions() .withEnabledVisualization()); From c7dcb1bf2e8e7cecea0ff1a23625211d6aaae84d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 29 Oct 2022 20:06:11 +0200 Subject: [PATCH 017/314] fix: Override getScreenshotAs to support the legacy base64 encoding (#1787) --- .../io/appium/java_client/AppiumDriver.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index d814657c6..450822a69 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -31,6 +31,7 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.OutputType; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; @@ -270,4 +271,24 @@ public Response execute(String driverCommand, Map parameters) { public Response execute(String command) { return super.execute(command, Collections.emptyMap()); } + + @Override + public X getScreenshotAs(OutputType outputType) { + // TODO: Eventually we should not override this method. + // TODO: Although, we have a legacy burden, + // TODO: so it's impossible to do it the other way as of Oct 29 2022. + // TODO: See https://github.com/SeleniumHQ/selenium/issues/11168 + return super.getScreenshotAs(new OutputType() { + @Override + public X convertFromBase64Png(String base64Png) { + String rfc4648Base64 = base64Png.replaceAll("\\r?\\n", ""); + return outputType.convertFromBase64Png(rfc4648Base64); + } + + @Override + public X convertFromPngBytes(byte[] png) { + return outputType.convertFromPngBytes(png); + } + }); + } } From cca6268ff8c4708f065571361b69f25c80f25a09 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 31 Oct 2022 00:08:56 -0700 Subject: [PATCH 018/314] feat: use own User Agent (#1779) * refer to com.google.common.net.HttpHeaders * tweak the logic to include UA * use parameterized test * use parameterized more, defined const * modify the logic to customize the UA, add tests * match as case insensitive * tweak, fixed addHeader not to add headers twice * add annotation * define a method to build UA --- build.gradle | 6 +- gradle.properties | 2 + .../java_client/AppiumClientConfig.java | 3 +- .../java_client/AppiumUserAgentFilter.java | 91 +++++++++++++++++++ .../remote/AppiumCommandExecutor.java | 4 + src/main/resources/main.properties | 1 + .../internal/AppiumUserAgentFilterTest.java | 67 ++++++++++++++ .../java_client/internal/ConfigTest.java | 23 +++-- 8 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 src/main/java/io/appium/java_client/AppiumUserAgentFilter.java create mode 100644 src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java diff --git a/build.gradle b/build.gradle index 54be347a1..51e50d861 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ java { ext { seleniumVersion = project.property('selenium.version') + appiumClientVersion = project.property('appiumClient.version') } dependencies { @@ -110,7 +111,7 @@ publishing { mavenJava(MavenPublication) { groupId = 'io.appium' artifactId = 'java-client' - version = '8.2.0' + version = appiumClientVersion from components.java pom { name = 'java-client' @@ -186,7 +187,8 @@ wrapper { processResources { filter ReplaceTokens, tokens: [ - 'selenium.version': seleniumVersion + 'selenium.version': seleniumVersion, + 'appiumClient.version': appiumClientVersion ] } diff --git a/gradle.properties b/gradle.properties index 9b086f575..239efd600 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,3 +8,5 @@ ossrhUsername=your-jira-id ossrhPassword=your-jira-password selenium.version=4.5.0 +# Please increment the value in a release +appiumClient.version=8.2.0 diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index 2e6128c03..317d85314 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -18,7 +18,6 @@ import org.openqa.selenium.Credentials; import org.openqa.selenium.internal.Require; -import org.openqa.selenium.remote.http.AddSeleniumUserAgent; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.Filter; @@ -34,7 +33,7 @@ public class AppiumClientConfig extends ClientConfig { private final boolean directConnect; - private static final Filter DEFAULT_FILTER = new AddSeleniumUserAgent(); + private static final Filter DEFAULT_FILTER = new AppiumUserAgentFilter(); private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(10); diff --git a/src/main/java/io/appium/java_client/AppiumUserAgentFilter.java b/src/main/java/io/appium/java_client/AppiumUserAgentFilter.java new file mode 100644 index 000000000..a36da9d08 --- /dev/null +++ b/src/main/java/io/appium/java_client/AppiumUserAgentFilter.java @@ -0,0 +1,91 @@ +/* + * 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.annotations.VisibleForTesting; +import com.google.common.net.HttpHeaders; +import io.appium.java_client.internal.Config; +import org.openqa.selenium.remote.http.AddSeleniumUserAgent; +import org.openqa.selenium.remote.http.Filter; +import org.openqa.selenium.remote.http.HttpHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Manage Appium Client configurations. + */ + +public class AppiumUserAgentFilter implements Filter { + + public static final String VERSION_KEY = "appiumClient.version"; + + private static final String USER_AGENT_PREFIX = "appium/"; + + /** + * A default User Agent name for Appium Java client. + * e.g. appium/8.2.0 (selenium/4.5.0 (java mac)) + */ + public static final String USER_AGENT = buildUserAgentHeaderValue(AddSeleniumUserAgent.USER_AGENT); + + private static String buildUserAgentHeaderValue(@Nonnull String previousUA) { + return String.format("%s%s (%s)", + USER_AGENT_PREFIX, Config.main().getValue(VERSION_KEY, String.class), previousUA); + } + + /** + * Returns true if the given User Agent includes "appium/", which + * implies the User Agent already has the Appium UA by this method. + * The matching is case-insensitive. + * @param userAgent the User Agent in the request headers. + * @return whether the given User Agent includes Appium UA + * like by this filter. + */ + @VisibleForTesting + public static boolean containsAppiumName(@Nullable String userAgent) { + return userAgent != null && userAgent.toLowerCase().contains(USER_AGENT_PREFIX.toLowerCase()); + } + + /** + * Returns the User Agent. If the given UA already has + * {@link USER_AGENT_PREFIX}, it returns the UA. + * IF the given UA does not have {@link USER_AGENT_PREFIX}, + * it returns UA with the Appium prefix. + * @param userAgent the User Agent in the request headers. + * @return the User Agent for the request + */ + public static String buildUserAgent(@Nullable String userAgent) { + if (userAgent == null) { + return USER_AGENT; + } + + if (containsAppiumName(userAgent)) { + return userAgent; + } + + return buildUserAgentHeaderValue(userAgent); + } + + @Override + public HttpHandler apply(HttpHandler next) { + + return req -> { + req.setHeader(HttpHeaders.USER_AGENT, buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); + return next.execute(req); + }; + } +} 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 ec792852a..43ce9fc01 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -24,6 +24,8 @@ import com.google.common.base.Supplier; import com.google.common.base.Throwables; +import com.google.common.net.HttpHeaders; +import io.appium.java_client.AppiumUserAgentFilter; import io.appium.java_client.AppiumClientConfig; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; @@ -192,6 +194,8 @@ private Response createSession(Command command) throws IOException { ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession( getClient().with((httpHandler) -> (req) -> { + req.setHeader(HttpHeaders.USER_AGENT, + AppiumUserAgentFilter.buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); return httpHandler.execute(req); }), command diff --git a/src/main/resources/main.properties b/src/main/resources/main.properties index a4236a9fe..9875b0c49 100644 --- a/src/main/resources/main.properties +++ b/src/main/resources/main.properties @@ -1 +1,2 @@ selenium.version=@selenium.version@ +appiumClient.version=@appiumClient.version@ diff --git a/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java new file mode 100644 index 000000000..10e33a1ee --- /dev/null +++ b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java @@ -0,0 +1,67 @@ +package io.appium.java_client.internal; + +import io.appium.java_client.AppiumUserAgentFilter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +public class AppiumUserAgentFilterTest { + @Test + void validateUserAgent() { + assertTrue(AppiumUserAgentFilter.USER_AGENT.startsWith("appium/")); + } + + public static Stream userAgentParams() { + return Stream.of( + Arguments.of("selenium/4.5.0 (java mac)", false), + Arguments.of("appium/8.2.0 (selenium/4.5.0 (java mac))", true), + Arguments.of("APPIUM/8.2.0 (selenium/4.5.0 (java mac))", true), + Arguments.of("something (Appium/8.2.0 (selenium/4.5.0 (java mac)))", true), + Arguments.of("something (appium/8.2.0 (selenium/4.5.0 (java mac)))", true) + ); + } + + @ParameterizedTest + @MethodSource("userAgentParams") + void validUserAgentIfContainsAppiumName(String userAgent, boolean expected) { + assertEquals(AppiumUserAgentFilter.containsAppiumName(userAgent), expected); + } + + @Test + void validBuildUserAgentNoUA() { + assertEquals(AppiumUserAgentFilter.buildUserAgent(null), AppiumUserAgentFilter.USER_AGENT); + } + + @Test + void validBuildUserAgentNoAppium1() { + String ua = AppiumUserAgentFilter.buildUserAgent("selenium/4.5.0 (java mac)"); + assertTrue(ua.startsWith("appium/")); + assertTrue(ua.endsWith("selenium/4.5.0 (java mac))")); + } + + @Test + void validBuildUserAgentNoAppium2() { + String ua = AppiumUserAgentFilter.buildUserAgent("customSelenium/4.5.0 (java mac)"); + assertTrue(ua.startsWith("appium/")); + assertTrue(ua.endsWith("customSelenium/4.5.0 (java mac))")); + } + + @Test + void validBuildUserAgentAlreadyHasAppium1() { + // Won't modify since the UA already has appium prefix + String ua = AppiumUserAgentFilter.buildUserAgent("appium/8.1.0 (selenium/4.5.0 (java mac))"); + assertEquals("appium/8.1.0 (selenium/4.5.0 (java mac))", ua); + } + + @Test + void validBuildUserAgentAlreadyHasAppium2() { + // Won't modify since the UA already has appium prefix + String ua = AppiumUserAgentFilter.buildUserAgent("something (appium/8.1.0 (selenium/4.5.0 (java mac)))"); + assertEquals("something (appium/8.1.0 (selenium/4.5.0 (java mac)))", ua); + } +} diff --git a/src/test/java/io/appium/java_client/internal/ConfigTest.java b/src/test/java/io/appium/java_client/internal/ConfigTest.java index 05559de27..67518eca7 100644 --- a/src/test/java/io/appium/java_client/internal/ConfigTest.java +++ b/src/test/java/io/appium/java_client/internal/ConfigTest.java @@ -6,17 +6,25 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.appium.java_client.AppiumUserAgentFilter; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.stream.Stream; class ConfigTest { private static final String SELENIUM_EXISTING_KEY = "selenium.version"; private static final String MISSING_KEY = "bla"; - @Test - void verifyGettingExistingValue() { - assertThat(Config.main().getValue(SELENIUM_EXISTING_KEY, String.class).length(), greaterThan(0)); - assertTrue(Config.main().getOptionalValue(SELENIUM_EXISTING_KEY, String.class).isPresent()); + @ParameterizedTest + @ValueSource(strings = {SELENIUM_EXISTING_KEY, AppiumUserAgentFilter.VERSION_KEY}) + void verifyGettingExistingValue(String key) { + assertThat(Config.main().getValue(key, String.class).length(), greaterThan(0)); + assertTrue(Config.main().getOptionalValue(key, String.class).isPresent()); } @Test @@ -24,9 +32,10 @@ void verifyGettingNonExistingValue() { assertThrows(IllegalArgumentException.class, () -> Config.main().getValue(MISSING_KEY, String.class)); } - @Test - void verifyGettingExistingValueWithWrongClass() { - assertThrows(ClassCastException.class, () -> Config.main().getValue(SELENIUM_EXISTING_KEY, Integer.class)); + @ParameterizedTest + @ValueSource(strings = {SELENIUM_EXISTING_KEY, AppiumUserAgentFilter.VERSION_KEY}) + void verifyGettingExistingValueWithWrongClass(String key) { + assertThrows(ClassCastException.class, () -> Config.main().getValue(key, Integer.class)); } @Test From 4bb67a59f82d02b3de2be20e8db6604dc8cf2133 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:24:10 +0300 Subject: [PATCH 019/314] build(deps): bump gson from 2.9.1 to 2.10 (#1788) Bumps [gson](https://github.com/google/gson) from 2.9.1 to 2.10. - [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.9.1...gson-parent-2.10) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 51e50d861..bf02a50a4 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ dependencies { prefer "${seleniumVersion}" } } - implementation 'com.google.code.gson:gson:2.9.1' + implementation 'com.google.code.gson:gson:2.10' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' From abee0bd396e6404c6503351b878b338f73c77ef9 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 1 Nov 2022 14:17:48 -0700 Subject: [PATCH 020/314] chore: use Lombok (#1789) * chore: use Lambok * Update src/main/java/io/appium/java_client/remote/DirectConnect.java Co-authored-by: Valery Yatsynovich Co-authored-by: Valery Yatsynovich --- .../java_client/remote/DirectConnect.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/appium/java_client/remote/DirectConnect.java b/src/main/java/io/appium/java_client/remote/DirectConnect.java index a2049c589..bfd0f4129 100644 --- a/src/main/java/io/appium/java_client/remote/DirectConnect.java +++ b/src/main/java/io/appium/java_client/remote/DirectConnect.java @@ -1,5 +1,9 @@ package io.appium.java_client.remote; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; + import javax.annotation.Nullable; import java.net.MalformedURLException; import java.net.URL; @@ -9,20 +13,20 @@ import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; -// TODO: simplify with lombok as another PR +@Accessors public class DirectConnect { private static final String DIRECT_CONNECT_PROTOCOL = "directConnectProtocol"; private static final String DIRECT_CONNECT_PATH = "directConnectPath"; private static final String DIRECT_CONNECT_HOST = "directConnectHost"; private static final String DIRECT_CONNECT_PORT = "directConnectPort"; - private final String protocol; + @Getter(AccessLevel.PUBLIC) private final String protocol; - private final String path; + @Getter(AccessLevel.PUBLIC) private final String path; - private final String host; + @Getter(AccessLevel.PUBLIC) private final String host; - private final String port; + @Getter(AccessLevel.PUBLIC) private final String port; /** * Create a DirectConnect instance. @@ -35,10 +39,6 @@ public DirectConnect(Map responseValue) { this.port = this.getDirectConnectValue(responseValue, DIRECT_CONNECT_PORT); } - public String getProtocol() { - return protocol; - } - @Nullable private String getDirectConnectValue(Map responseValue, String key) { Object directConnectPath = responseValue.get(APPIUM_PREFIX + key); From 57ce03be427466e57a71f30a9174ebc08fd0d60d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 3 Nov 2022 14:40:34 +0100 Subject: [PATCH 021/314] feat: Add alternative proxy implementation (#1790) --- build.gradle | 1 + docs/The-event_firing.md | 82 ++++++++- .../io/appium/java_client/proxy/Helpers.java | 157 ++++++++++++++++++ .../appium/java_client/proxy/Interceptor.java | 129 ++++++++++++++ .../java_client/proxy/MethodCallListener.java | 82 +++++++++ .../proxy/NotImplementedException.java | 20 +++ .../java_client/proxy/ProxyHelpersTest.java | 136 +++++++++++++++ 7 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/proxy/Helpers.java create mode 100644 src/main/java/io/appium/java_client/proxy/Interceptor.java create mode 100644 src/main/java/io/appium/java_client/proxy/MethodCallListener.java create mode 100644 src/main/java/io/appium/java_client/proxy/NotImplementedException.java create mode 100644 src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java diff --git a/build.gradle b/build.gradle index bf02a50a4..8918b815d 100644 --- a/build.gradle +++ b/build.gradle @@ -198,6 +198,7 @@ task unitTest( type: Test ) { testLogging.exceptionFormat = 'full' filter { includeTestsMatching 'io.appium.java_client.internal.*' + includeTestsMatching 'io.appium.java_client.proxy.*' } } diff --git a/docs/The-event_firing.md b/docs/The-event_firing.md index 7fa0a58d6..527fddeb8 100644 --- a/docs/The-event_firing.md +++ b/docs/The-event_firing.md @@ -3,7 +3,7 @@ since v8.0.0 # The purpose This feature allows end user to organize the event logging on the client side. -Also this feature may be useful in a binding with standard or custom reporting +Also, this feature may be useful in a binding with standard or custom reporting frameworks. The feature has been introduced first since Selenium API v4. # The API @@ -40,6 +40,7 @@ Listeners should implement WebDriverListener. It supports three types of events: To use this decorator you have to prepare a listener, create a decorator using this listener, decorate the original WebDriver instance with this decorator and use the new WebDriver instance created by the decorator instead of the original one: + ```java WebDriver original = new AndroidDriver(); // it is expected that MyListener class implements WebDriverListener @@ -66,6 +67,7 @@ decorated.get("http://example.com/"); WebElement header = decorated.findElement(By.tagName("h1")); // if an error happens during any of these calls the the onError event is fired ``` + The instance of WebDriver created by the decorator implements all the same interfaces as the original driver. A listener can subscribe to "specific" or "generic" events (or both). A "specific" event correspond to a single specific method, a "generic" event correspond to any @@ -74,3 +76,81 @@ implement a method with a name derived from the target method to be watched. The for "before"-events receive the parameters passed to the decorated method. The listener methods for "after"-events receive the parameters passed to the decorated method as well as the result returned by this method. + +## createProxy API (since Java Client 8.3.0) + +This API is unique to Appium Java Client and does not exist in Selenium. The reason for +its existence is the fact that the original event listeners API provided by Selenium is limited +because it can only use interface types for decorator objects. For example, the code below won't +work: + +```java +IOSDriver driver = new IOSDriver(new URL("http://doesnot.matter/"), new ImmutableCapabilities()) +{ + @Override + protected void startSession(Capabilities capabilities) + { + // Override in a sake of simplicity to avoid the actual session start + } +}; +WebDriverListener webDriverListener = new WebDriverListener() +{ +}; +IOSDriver decoratedDriver = (IOSDriver) new EventFiringDecorator(IOSDriver.class, webDriverListener).decorate( + driver); +``` + +The last line throws `ClassCastException` because `decoratedDriver` is of type `IOSDriver`, +which is a class rather than an interface. +See the issue [#1694](https://github.com/appium/java-client/issues/1694) for more +details. In order to workaround this limitation a special proxy implementation has been created, +which is capable of decorating class types: + +```java +import io.appium.java_client.proxy.MethodCallListener; +import io.appium.java_client.proxy.NotImplementedException; + +import static io.appium.java_client.proxy.Helpers.createProxy; + +// ... + +MethodCallListener listener = new MethodCallListener() { + @Override + public void beforeCall(Object target, Method method, Object[] args) { + if (!method.getName().equals("get")) { + throw new NotImplementedException(); + } + acc.append("beforeCall ").append(method.getName()).append("\n"); + } + + @Override + public void afterCall(Object target, Method method, Object[] args, Object result) { + if (!method.getName().equals("get")) { + throw new NotImplementedException(); + } + acc.append("afterCall ").append(method.getName()).append("\n"); + } +}; + +IOSDriver decoratedDriver = createProxy( + IOSDriver.class, + new Object[] {new URL("http://localhost:4723/"), new XCUITestOptions()}, + new Class[] {URL.class, Capabilities.class}, + listener +); + +decoratedDriver.get("http://example.com/"); + +assertThat(acc.toString().trim()).isEqualTo( + String.join("\n", + "beforeCall get", + "afterCall get" + ) +); +``` + +This proxy is not tied to WebDriver descendants and could be used to any classes that have +**public** constructors. It also allows to intercept exceptions thrown by **public** class methods and/or +change/replace the original methods behavior. It is important to know that callbacks are **not** invoked +for methods derived from the standard `Object` class, like `toString` or `equals`. +Check [unit tests](../src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java) for more examples. diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java new file mode 100644 index 000000000..286d7fc31 --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -0,0 +1,157 @@ +/* + * 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.proxy; + +import com.google.common.base.Preconditions; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.matcher.ElementMatchers; + +import java.util.Collection; +import java.util.Collections; + +public class Helpers { + private Helpers() { + } + + /** + * Creates a transparent proxy instance for the given class. + * It is possible to provide one or more method execution listeners + * or replace particular method calls completely. Callbacks + * defined in these listeners are going to be called when any + * **public** method of the given class is invoked. Overridden callbacks + * are expected to be skipped if they throw + * {@link io.appium.java_client.proxy.NotImplementedException}. + * + * @param cls the class to which the proxy should be created. + * Must not be an interface. + * @param constructorArgs Array of constructor arguments. Could be an + * empty array if the class provides a constructor without arguments. + * @param constructorArgTypes Array of constructor argument types. Must + * represent types of constructorArgs. + * @param listeners One or more method invocation listeners. + * @param Any class derived from Object + * @return Proxy instance + */ + public static T createProxy( + Class cls, + Object[] constructorArgs, + Class[] constructorArgTypes, + Collection listeners + ) { + Preconditions.checkArgument(constructorArgs.length == constructorArgTypes.length, + String.format( + "Constructor arguments array length %d must be equal to the types array length %d", + constructorArgs.length, constructorArgTypes.length + ) + ); + Preconditions.checkArgument(!listeners.isEmpty(), "The collection of listeners must not be empty"); + Preconditions.checkArgument(cls != null, "Class must not be null"); + Preconditions.checkArgument(!cls.isInterface(), "Class must not be an interface"); + + //noinspection resource + Class proxy = new ByteBuddy() + .subclass(cls) + .method(ElementMatchers.isPublic() + .and(ElementMatchers.not( + ElementMatchers.isDeclaredBy(Object.class) + .or(ElementMatchers.isOverriddenFrom(Object.class)) + ))) + .intercept(MethodDelegation.to(Interceptor.class)) + .make() + .load(cls.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) + .getLoaded() + .asSubclass(cls); + + try { + //noinspection unchecked + T instance = (T) proxy + .getConstructor(constructorArgTypes) + .newInstance(constructorArgs); + Interceptor.LISTENERS.put(instance, listeners); + return instance; + } catch (SecurityException | ReflectiveOperationException e) { + throw new IllegalStateException(String.format("Unable to create a proxy of %s", cls.getName()), e); + } + } + + /** + * Creates a transparent proxy instance for the given class. + * It is possible to provide one or more method execution listeners + * or replace particular method calls completely. Callbacks + * defined in these listeners are going to be called when any + * **public** method of the given class is invoked. Overridden callbacks + * are expected to be skipped if they throw NotImplementedException. + * + * @param cls the class to which the proxy should be created. + * Must not be an interface. Must expose a constructor + * without arguments. + * @param listeners One or more method invocation listeners. + * @param Any class derived from Object + * @return Proxy instance + */ + public static T createProxy(Class cls, Collection listeners) { + return createProxy(cls, new Object[]{}, new Class[]{}, listeners); + } + + /** + * Creates a transparent proxy instance for the given class. + * It is possible to provide one or more method execution listeners + * or replace particular method calls completely. Callbacks + * defined in these listeners are going to be called when any + * **public** method of the given class is invoked. Overridden callbacks + * are expected to be skipped if they throw NotImplementedException. + * + * @param cls the class to which the proxy should be created. + * Must not be an interface. Must expose a constructor + * without arguments. + * @param listener Method invocation listener. + * @param Any class derived from Object + * @return Proxy instance + */ + public static T createProxy(Class cls, MethodCallListener listener) { + return createProxy(cls, new Object[]{}, new Class[]{}, Collections.singletonList(listener)); + } + + /** + * Creates a transparent proxy instance for the given class. + * It is possible to provide one or more method execution listeners + * or replace particular method calls completely. Callbacks + * defined in these listeners are going to be called when any + * **public** method of the given class is invoked. Overridden callbacks + * are expected to be skipped if they throw NotImplementedException. + * + * @param cls the class to which the proxy should be created. + * Must not be an interface. + * @param constructorArgs Array of constructor arguments. Could be an + * empty array if the class provides a constructor without arguments. + * @param constructorArgTypes Array of constructor argument types. Must + * represent types of constructorArgs. + * @param listener Method invocation listener. + * @param Any class derived from Object + * @return Proxy instance + */ + public static T createProxy( + Class cls, + Object[] constructorArgs, + Class[] constructorArgTypes, + MethodCallListener listener + ) { + return createProxy(cls, constructorArgs, constructorArgTypes, Collections.singletonList(listener)); + } +} diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java new file mode 100644 index 000000000..921b86124 --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -0,0 +1,129 @@ +/* + * 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.proxy; + +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import net.bytebuddy.implementation.bind.annotation.This; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.WeakHashMap; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Interceptor { + private static final Logger logger = LoggerFactory.getLogger(Interceptor.class); + public static final Map> LISTENERS = new WeakHashMap<>(); + private static final Set OBJECT_METHOD_NAMES = Stream.of(Object.class.getMethods()) + .map(Method::getName) + .collect(Collectors.toSet()); + + /** + * A magic method used to wrap public method calls in classes + * patched by ByteBuddy and acting as proxies. + * + * @param self The reference to the original instance. + * @param method The reference to the original method. + * @param args The reference to method args. + * @param callable The reference to the non-patched callable to avoid call recursion. + * @return Either the original method result or the patched one. + */ + @RuntimeType + public static Object intercept( + @This Object self, + @Origin Method method, + @AllArguments Object[] args, + @SuperCall Callable callable + ) throws Throwable { + if (OBJECT_METHOD_NAMES.contains(method.getName())) { + return callable.call(); + } + Collection listeners = LISTENERS.get(self); + if (listeners == null || listeners.isEmpty()) { + return callable.call(); + } + + listeners.forEach(listener -> { + try { + listener.beforeCall(self, method, args); + } catch (NotImplementedException e) { + // ignore + } catch (Exception e) { + logger.error( + String.format("Got an unexpected error in beforeCall listener of %s.%s method", + self.getClass().getName(), method.getName()), e + ); + } + }); + + final UUID noResult = UUID.randomUUID(); + Object result = noResult; + for (MethodCallListener listener : listeners) { + try { + result = listener.call(self, method, args, callable); + break; + } catch (NotImplementedException e) { + // ignore + } catch (Exception e) { + try { + return listener.onError(self, method, args, e); + } catch (NotImplementedException ignore) { + // ignore + } + throw e; + } + } + if (noResult.equals(result)) { + try { + result = callable.call(); + } catch (Exception e) { + for (MethodCallListener listener : listeners) { + try { + return listener.onError(self, method, args, e); + } catch (NotImplementedException ignore) { + // ignore + } + } + throw e; + } + } + + final Object endResult = result == noResult ? null : result; + listeners.forEach(listener -> { + try { + listener.afterCall(self, method, args, endResult); + } catch (NotImplementedException e) { + // ignore + } catch (Exception e) { + logger.error( + String.format("Got an unexpected error in afterCall listener of %s.%s method", + self.getClass().getName(), method.getName()), e + ); + } + }); + return endResult; + } +} diff --git a/src/main/java/io/appium/java_client/proxy/MethodCallListener.java b/src/main/java/io/appium/java_client/proxy/MethodCallListener.java new file mode 100644 index 000000000..f0cc1be7a --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/MethodCallListener.java @@ -0,0 +1,82 @@ +/* + * 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.proxy; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; + +public interface MethodCallListener { + + /** + * The callback to be invoked before any public method of the proxy is called. + * The implementation is not expected to throw any exceptions. If a + * runtime exception is thrown then it is going to be silently logged. + * + * @param obj The proxy instance + * @param method Method to be called + * @param args Array of method arguments + */ + default void beforeCall(Object obj, Method method, Object[] args) { + throw new NotImplementedException(); + } + + /** + * Override this callback in order to change/customize the behavior + * of a single or multiple methods. The original method result + * will be replaced with the result returned by this callback. + * Also, any exception thrown by it will replace original method(s) + * exception. + * + * @param obj The proxy instance + * @param method Method to be replaced + * @param args Array of method arguments + * @param original The reference to the original method in case it is necessary to instrument its result. + * @return The type of the returned result should be castable to the returned type of the original method. + */ + default Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { + throw new NotImplementedException(); + } + + /** + * The callback to be invoked after any public method of the proxy is called. + * The implementation is not expected to throw any exceptions. If a + * runtime exception is thrown then it is going to be silently logged. + * + * @param obj The proxy instance + * @param method Method to be called + * @param args Array of method arguments + */ + default void afterCall(Object obj, Method method, Object[] args, Object result) { + throw new NotImplementedException(); + } + + /** + * The callback to be invoked if a public method or its + * {@link #call(Object, Method, Object[], Callable) Call} replacement throws an exception. + * + * @param obj The proxy instance + * @param method Method to be called + * @param args Array of method arguments + * @param e Exception instance thrown by the original method invocation. + * @return You could either (re)throw the exception in this callback or + * overwrite the behavior and return a result from it. It is expected that the + * type of the returned argument could be cast to the returned type of the original method. + */ + default Object onError(Object obj, Method method, Object[] args, Throwable e) throws Throwable { + throw new NotImplementedException(); + } +} diff --git a/src/main/java/io/appium/java_client/proxy/NotImplementedException.java b/src/main/java/io/appium/java_client/proxy/NotImplementedException.java new file mode 100644 index 000000000..861c114c8 --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/NotImplementedException.java @@ -0,0 +1,20 @@ +/* + * 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.proxy; + +public class NotImplementedException extends RuntimeException { +} diff --git a/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java new file mode 100644 index 000000000..fc389b6c6 --- /dev/null +++ b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java @@ -0,0 +1,136 @@ +/* + * 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.proxy; + +import io.appium.java_client.ios.IOSDriver; +import io.appium.java_client.ios.options.XCUITestOptions; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.UnreachableBrowserException; + +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.concurrent.Callable; + +import static io.appium.java_client.proxy.Helpers.createProxy; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ProxyHelpersTest { + + public static class FakeIOSDriver extends IOSDriver { + public FakeIOSDriver(URL url, Capabilities caps) { + super(url, caps); + } + + @Override + protected void startSession(Capabilities capabilities) {} + } + + @Test + void shouldFireBeforeAndAfterEvents() { + final StringBuilder acc = new StringBuilder(); + MethodCallListener listener = new MethodCallListener() { + @Override + public void beforeCall(Object target, Method method, Object[] args) { + acc.append("beforeCall ").append(method.getName()).append("\n"); + // should be ignored + throw new IllegalStateException(); + } + + @Override + public void afterCall(Object target, Method method, Object[] args, Object result) { + acc.append("afterCall ").append(method.getName()).append("\n"); + // should be ignored + throw new IllegalStateException(); + } + }; + RemoteWebDriver driver = createProxy(RemoteWebDriver.class, Collections.singletonList(listener)); + + assertThrows( + UnreachableBrowserException.class, + () -> driver.get("http://example.com/") + ); + + assertThat(acc.toString().trim(), is(equalTo( + String.join("\n", + "beforeCall get", + "beforeCall getSessionId", + "afterCall getSessionId", + "beforeCall getCapabilities", + "afterCall getCapabilities", + "beforeCall getCapabilities", + "afterCall getCapabilities") + ))); + } + + @Test + void shouldFireErrorEvents() { + MethodCallListener listener = new MethodCallListener() { + @Override + public Object onError(Object obj, Method method, Object[] args, Throwable e) { + throw new IllegalStateException(); + } + }; + RemoteWebDriver driver = createProxy(RemoteWebDriver.class, Collections.singletonList(listener)); + + assertThrows( + IllegalStateException.class, + () -> driver.get("http://example.com/") + ); + } + + @Test + void shouldFireCallEvents() throws MalformedURLException { + final StringBuilder acc = new StringBuilder(); + MethodCallListener listener = new MethodCallListener() { + @Override + public Object call(Object obj, Method method, Object[] args, Callable original) { + acc.append("onCall ").append(method.getName()).append("\n"); + throw new IllegalStateException(); + } + + @Override + public Object onError(Object obj, Method method, Object[] args, Throwable e) throws Throwable { + acc.append("onError ").append(method.getName()).append("\n"); + throw e; + } + }; + FakeIOSDriver driver = createProxy( + FakeIOSDriver.class, + new Object[] {new URL("http://localhost:4723/"), new XCUITestOptions()}, + new Class[] {URL.class, Capabilities.class}, + listener + ); + + assertThrows( + IllegalStateException.class, + () -> driver.get("http://example.com/") + ); + + assertThat(acc.toString().trim(), is(equalTo( + String.join("\n", + "onCall get", + "onError get") + ))); + } +} From e0ab871e6b8de72fc9d376a58fe1e24a26c7c9fe Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 3 Nov 2022 17:35:42 +0100 Subject: [PATCH 022/314] docs: Update readme and remove obsolete documents (#1792) --- README.md | 170 +++++++++++++++--- docs/Functions.md | 147 --------------- docs/Page-objects.md | 5 +- docs/Tech-stack.md | 19 -- docs/The-starting-of-an-Android-app.md | 156 ---------------- ...um-node-server-started-programmatically.md | 25 +-- docs/The-starting-of-an-iOS-app.md | 122 ------------- docs/Touch-actions.md | 44 ----- 8 files changed, 153 insertions(+), 535 deletions(-) delete mode 100644 docs/Functions.md delete mode 100644 docs/Tech-stack.md delete mode 100644 docs/The-starting-of-an-Android-app.md delete mode 100644 docs/The-starting-of-an-iOS-app.md delete mode 100644 docs/Touch-actions.md diff --git a/README.md b/README.md index 6af5f0be8..e2da9ca63 100644 --- a/README.md +++ b/README.md @@ -4,31 +4,48 @@ [![Javadocs](https://www.javadoc.io/badge/io.appium/java-client.svg)](https://www.javadoc.io/doc/io.appium/java-client) [![Build Status](https://travis-ci.org/appium/java-client.svg?branch=master)](https://travis-ci.org/appium/java-client) -This is the Java language binding for writing Appium Tests, conforms to [Mobile JSON Wire Protocol](https://github.com/SeleniumHQ/mobile-spec/blob/master/spec-draft.md) +This is the Java language bindings for writing Appium Tests that conform to [WebDriver Protocol](https://w3c.github.io/webdriver/) -[API docs](https://www.javadoc.io/doc/io.appium/java-client) +## v8 Migration -### Features and other interesting information +Since version 8 Appium Java Client had several major changes, which might require to +update your client code. Make sure to follow the [v7 to v8 Migration Guide](https://github.com/appium/java-client/blob/master/docs/v7-to-v8-migration-guide.md) +in order to streamline the migration process. -[Tech stack](https://github.com/appium/java-client/blob/master/docs/Tech-stack.md) +## Add Appium java client to your test framework -[How to install the project](https://github.com/appium/java-client/blob/master/docs/Installing-the-project.md) +### Stable -[WIKI](https://github.com/appium/java-client/wiki) +#### Maven -## v8 Migration +Add the following to pom.xml: -Since version 8 Appium Java Client had several major changes, which might require to -update your client code. Make sure to follow the [v7 to v8 Migration Guide](https://github.com/appium/java-client/blob/master/docs/v7-to-v8-migration-guide.md) -in order to streamline the migration process. +```xml + + io.appium + java-client + ${version.you.require} + test + +``` + +#### Gradle + +Add the following to build.gradle: + +```groovy +dependencies { + testImplementation 'io.appium:java-client:${version.you.require}' +} +``` -## How to install latest java client Beta/Snapshots +### Beta/Snapshots Java client project is available to use even before it is officially published to maven central. Refer [jitpack.io](https://jitpack.io/#appium/java-client) -### Maven +#### Maven - - Add the following to pom.xml: +Add the following to pom.xml: ```xml @@ -39,7 +56,7 @@ Java client project is available to use even before it is officially published t ``` - - Add the dependency: +Add the dependency: ```xml @@ -49,27 +66,140 @@ Java client project is available to use even before it is officially published t ``` -### Gradle +#### Gradle - - Add the JitPack repository to your build file. Add it in your root build.gradle at the end of repositories: +Add the JitPack repository to your build file. Add it in your root build.gradle at the end of repositories: -``` +```groovy allprojects { repositories { - ... + // ... maven { url 'https://jitpack.io/' } } } ``` - - Add the dependency: +Add the dependency: -``` +```groovy dependencies { implementation 'com.github.appium:java-client:latest commit id from master branch' } ``` +## Drivers Support + +Appium java client has dedicated classes to support the following Appium drivers: + +- [UiAutomator2](https://github.com/appium/appium-uiautomator2-driver) and [Espresso](https://github.com/appium/appium-espresso-driver): [AndroidDriver](src/main/java/io/appium/java_client/android/AndroidDriver.java) +- [XCUITest](https://github.com/appium/appium-xcuitest-driver): [IOSDriver](src/main/java/io/appium/java_client/ios/IOSDriver.java) +- [Windows](https://github.com/appium/appium-windows-driver): [WindowsDriver](src/main/java/io/appium/java_client/windows/WindowsDriver.java) +- [Safari](https://github.com/appium/appium-safari-driver): [SafariDriver](src/main/java/io/appium/java_client/safari/SafariDriver.java) +- [Gecko](https://github.com/appium/appium-geckodriver): [GeckoDriver](src/main/java/io/appium/java_client/gecko/GeckoDriver.java) +- [Mac2](https://github.com/appium/appium-mac2-driver): [Mac2Driver](src/main/java/io/appium/java_client/mac/Mac2Driver.java) + +To automate other platforms that are not listed above you could use +[AppiumDriver](src/main/java/io/appium/java_client/AppiumDriver.java) or its custom derivatives. + +Appium java client is built on top of Selenium and implements same interfaces that the foundation +[RemoteWebDriver](https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/remote/RemoteWebDriver.java) +does. However, Selenium lib is mostly focused on web browsers automation while +Appium is universal and covers wide range of possible platforms, e.g. mobile and desktop +operating systems, IOT devices, etc. Thus, the foundation `AppiumDriver` class in this package +extends `RemoteWebDriver` with additional features, and makes it more flexible, so it is not so +strictly focused on web-browser related operations. + +## Appium Server Service Wrapper + +Appium java client provides a dedicated class to control Appium server execution. +The class is [AppiumDriverLocalService](src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java). +It allows to run and verify the Appium server **locally** from your test framework code +and provides several convenient shortcuts. The service could be used as below: + +```java +AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); +service.start(); +try { + // do stuff with drivers +} finally { + service.stop(); +} +``` + +You could customize the service behavior, for example, provide custom +command line arguments or change paths to server executables +using [AppiumServiceBuilder](src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java) + +**Note** + +> AppiumDriverLocalService does not support the server management on non-local hosts + +## Usage Examples + +### UiAutomator2 + +```java +UiAutomator2Options options = new UiAutomator2Options() + .setUdid('123456') + .setApp("/home/myapp.apk"); +AndroidDriver driver = new AndroidDriver( + // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub + new URL("http://127.0.0.1:4723/"), options +); +try { + WebElement el = driver.findElement(AppiumBy.xpath, "//Button"); + el.click(); + driver.getPageSource(); +} finally { + driver.quit(); +} +``` + +### XCUITest + +```java +XCUITestOptions options = new XCUITestOptions() + .setUdid('123456') + .setApp("/home/myapp.ipa"); +IOSDriver driver = new IOSDriver( + // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub + new URL("http://127.0.0.1:4723/"), options +); +try { + WebElement el = driver.findElement(AppiumBy.accessibilityId, "myId"); + el.click(); + driver.getPageSource(); +} finally { + driver.quit(); +} +``` + +### Any generic driver that does not have a dedicated class + +```java +BaseOptions options = new BaseOptions() + .setPlatformName("myplatform") + .setAutomationName("mydriver") + .amend("mycapability1", "capvalue1") + .amend("mycapability2", "capvalue2"); +AppiumDriver driver = new AppiumDriver( + // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub + new URL("http://127.0.0.1:4723/"), options +); +try { + WebElement el = driver.findElement(AppiumBy.className, "myClass"); + el.click(); + driver.getPageSource(); +} finally { + driver.quit(); +} +``` + +Check the corresponding driver's READMEs to know the list of capabilities and features it supports. + +You could find much more code examples by checking client's +[unit and integration tests](src/test/java/io/appium/java_client). + ## Changelog *8.2.0* - **[ENHANCEMENTS]** diff --git a/docs/Functions.md b/docs/Functions.md deleted file mode 100644 index bdf962f7c..000000000 --- a/docs/Functions.md +++ /dev/null @@ -1,147 +0,0 @@ -Appium java client has some features based on [Java 8 Functional interfaces](https://www.oreilly.com/learning/java-8-functional-interfaces). - -# Conditions - -```java -io.appium.java_client.functions.AppiumFunction -``` -It extends -```java -java.util.function.Function -``` -and -```java -com.google.common.base.Function -``` -to make end user available to use _org.openqa.selenium.support.ui.Wait_. There is additional interface -```java -io.appium.java_client.functions.ExpectedCondition -``` -which extends -```java -io.appium.java_client.functions.AppiumFunction -``` - -and - -```java -org.openqa.selenium.support.ui.ExpectedCondition -``` - -This feature provides the ability to create complex condition of the waiting for something. - -```java -//waiting for elements - private final AppiumFunction> searchingFunction = input -> { - List result = input.findElements(By.tagName("a")); - - if (result.size() > 0) { - return result; - } - return null; -}; - -//waiting for some context using regular expression pattern -private final AppiumFunction contextFunction = input -> { - Set contexts = driver.getContextHandles(); - String current = driver.getContext(); - contexts.forEach(context -> { - Matcher m = input.matcher(context); - if (m.find()) { - driver.context(context); - } - }); - if (!current.equals(driver.getContext())) { - return driver; - } - return null; -}; -``` - -## using one function as pre-condition - -```java -@Test public void tezt() { - .... - Wait wait = new FluentWait<>(Pattern.compile("WEBVIEW")) - .withTimeout(30, TimeUnit.SECONDS); - List elements = wait.until(searchingFunction.compose(contextFunction)); - .... -} -``` - -## using one function as post-condition - -```java -import org.openqa.selenium.support.ui.FluentWait; -import org.openqa.selenium.support.ui.Wait; - -@Test public void tezt() { - .... - Wait wait = new FluentWait<>(Pattern.compile("WEBVIEW")) - .withTimeout(30, TimeUnit.SECONDS); - List elements = wait.until(contextFunction.andThen(searchingFunction)); - .... -} -``` - -# Touch action supplier - -[About touch actions](https://github.com/appium/java-client/blob/master/docs/Touch-actions.md) - -You can use suppliers to declare touch/multitouch actions for some screens/tests. Also it is possible to -create gesture libraries/utils using suppliers. Appium java client provides this interface - -```java -io.appium.java_client.functions.ActionSupplier -``` - -## Samples - -```java -private final ActionSupplier horizontalSwipe = () -> { - driver.findElementById("io.appium.android.apis:id/gallery"); - - AndroidElement gallery = driver.findElementById("io.appium.android.apis:id/gallery"); - List images = gallery - .findElementsByClassName("android.widget.ImageView"); - Point location = gallery.getLocation(); - Point center = gallery.getCenter(); - - return new TouchAction(driver).press(images.get(2), -10, center.y - location.y) - .waitAction(2000).moveTo(gallery, 10, center.y - location.y).release(); -}; - -private final ActionSupplier verticalSwiping = () -> - new TouchAction(driver).press(driver.findElementByAccessibilityId("Gallery")) - .waitAction(2000).moveTo(driver.findElementByAccessibilityId("Auto Complete")).release(); - -@Test public void tezt() { - ... - horizontalSwipe.get().perform(); - ... - verticalSwiping.get().perform(); - ... -} -``` - -```java -public class GestureUtils { - - public static ActionSupplier swipe(final AppiumDriver driver, final params) { - return () -> { - new TouchAction(driver).press(params) - .waitAction(params).moveTo(params).release(); - }; - } -} - -public class SomeTest { - @Test public void tezt() { - ... - GestureUtils.swipe(driver, params).get().perform(); - ... - } -} - -``` \ No newline at end of file diff --git a/docs/Page-objects.md b/docs/Page-objects.md index 7bc36a267..ce7a04f1f 100644 --- a/docs/Page-objects.md +++ b/docs/Page-objects.md @@ -46,16 +46,15 @@ List someElements; # The example for the crossplatform mobile native testing ```java -import io.appium.java_client.MobileElement; import io.appium.java_client.pagefactory.*; @AndroidFindBy(someStrategy) @iOSFindBy(someStrategy) -MobileElement someElement; +WebElement someElement; @AndroidFindBy(someStrategy) //for the crossplatform mobile native @iOSFindBy(someStrategy) //testing -List someElements; +List someElements; ``` # The fully cross platform example diff --git a/docs/Tech-stack.md b/docs/Tech-stack.md deleted file mode 100644 index cbaa01b2e..000000000 --- a/docs/Tech-stack.md +++ /dev/null @@ -1,19 +0,0 @@ -![](https://cloud.githubusercontent.com/assets/4927589/21467582/df8ab94e-ca03-11e6-969c-c6d30c6add67.png) -![](https://cloud.githubusercontent.com/assets/4927589/21467509/a97e084e-ca01-11e6-9d04-4f2b8e1c72df.png) -![](https://cloud.githubusercontent.com/assets/4927589/21467524/187a333a-ca02-11e6-8e3c-14c411448fdb.png) -![](https://cloud.githubusercontent.com/assets/4927589/21467531/6f576f1a-ca02-11e6-9f2b-2551ea0e0753.png) + **AspectJ** and **CGlib** - -This project is based on [Selenium java client](https://github.com/SeleniumHQ/selenium/tree/master/java/client). It already depends on it and extends it to mobile platforms. - -This project is built by [gradle](https://gradle.org/) - -Also tech stack includes [Spring framework](https://spring.io/projects/spring-framework) in binding with AspectJ. This is used by [event firing feature](https://github.com/appium/java-client/blob/master/docs/The-event_firing.md). Also **CGlib** is used by [Page Object tools](https://github.com/appium/java-client/blob/master/docs/Page-objects.md). - -It is the client framework. It is the thin client which just sends requests to Appium server and receives responses. Also it has some -high-level features which were designed to simplify user's work. - -# It supports: - -![](https://cloud.githubusercontent.com/assets/4927589/21467612/4b6b3f70-ca05-11e6-9a31-d3820e98dac6.png) -![](https://cloud.githubusercontent.com/assets/4927589/21467614/73883828-ca05-11e6-846d-3ed8847a7e08.jpg) -![](https://cloud.githubusercontent.com/assets/4927589/21467621/aab3ff6c-ca05-11e6-9170-2e7a19d3307c.png) \ No newline at end of file diff --git a/docs/The-starting-of-an-Android-app.md b/docs/The-starting-of-an-Android-app.md deleted file mode 100644 index 466756677..000000000 --- a/docs/The-starting-of-an-Android-app.md +++ /dev/null @@ -1,156 +0,0 @@ -# Steps: - -- you have to prepare environment for Android. [Details are provided here](https://appium.io/docs/en/drivers/android-uiautomator2/#basic-setup) - -- it needs to launch the appium server. You can launch Appium desktop application. If you use the server installed via npm then - - _$ node **the_path_to_main.js_file** --arg1 value1 --arg2 value2_ -It is not necessary to use arguments. [The list of arguments](https://appium.io/docs/en/writing-running-appium/server-args/) - - -# The starting of an app - -It looks like creation of a common [RemoteWebDriver](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/RemoteWebDriver.html) instance. - -[Common capabilities](https://appium.io/docs/en/writing-running-appium/caps/#general-capabilities) - -[Android-specific capabilities](https://appium.io/docs/en/writing-running-appium/caps/#android-only) - -[Common capabilities provided by Java client](https://javadoc.io/page/io.appium/java-client/latest/io/appium/java_client/remote/MobileCapabilityType.html) - -[Android-specific capabilities provided by Java client](https://javadoc.io/page/io.appium/java-client/latest/io/appium/java_client/remote/AndroidMobileCapabilityType.html) - -```java -import java.io.File; -import org.openqa.selenium.remote.DesiredCapabilities; -import io.appium.java_client.AppiumDriver; -import io.appium.java_client.android.AndroidDriver; -import io.appium.java_client.MobileElement; -import java.net.URL; - -... -File app = new File("The absolute or relative path to an *.apk file"); -DesiredCapabilities capabilities = new DesiredCapabilities(); -capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); -capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); -capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); -//you are free to set additional capabilities -AppiumDriver driver = new AppiumDriver<>( -new URL("http://target_ip:used_port/wd/hub"), //if it needs to use locally started server -//then the target_ip is 127.0.0.1 or 0.0.0.0 -//the default port is 4723 -capabilities); -``` - -or - -```java -import java.io.File; -import org.openqa.selenium.remote.DesiredCapabilities; -import io.appium.java_client.AppiumDriver; -import io.appium.java_client.MobileElement; -import java.net.URL; - -... -File app = new File("The absolute or relative path to an *.apk file"); -DesiredCapabilities capabilities = new DesiredCapabilities(); -capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); -capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); -//you are free to set additional capabilities -AppiumDriver driver = new AndroidDriver<>( -new URL("http://target_ip:used_port/wd/hub"), //if it needs to use locally started server -//then the target_ip is 127.0.0.1 or 0.0.0.0 -//the default port is 4723 -capabilities); -``` - - -## If it needs to start browser then - -This capability should be used - -```java -capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, MobileBrowserType.CHROME); -//if it is necessary to use the default Android browser then MobileBrowserType.BROWSER -//is your choice -``` - -## There are three automation types - -```java -capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.SELENDROID); -``` - -This automation type is usually recommended for old versions (<4.2) of Android. - -Default Android UIAutomator does not require any specific capability. However you can -```java -capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM); -``` - -You have to define this automation type to be able to use Android UIAutomator2 for new Android versions -```java -capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); -``` - -# Possible cases - -You can use ```io.appium.java_client.AppiumDriver``` and ```io.appium.java_client.android.AndroidDriver``` as well. The main difference -is that ```AndroidDriver``` implements all API that describes interaction with Android native/hybrid app. ```AppiumDriver``` allows to -use Android-specific API eventually. - - _The sample of the activity starting by_ ```io.appium.java_client.AppiumDriver``` - - ```java - import io.appium.java_client.android.StartsActivity; - import io.appium.java_client.android.Activity; - -... - -StartsActivity startsActivity = new StartsActivity() { - @Override - public Response execute(String driverCommand, Map parameters) { - return driver.execute(driverCommand, parameters); - } - - @Override - public Response execute(String driverCommand) { - return driver.execute(driverCommand); - } -}; - -Activity activity = new Activity("app package goes here", "app activity goes here") - .setWaitAppPackage("app wait package goes here"); - .setWaitAppActivity("app wait activity goes here"); -StartsActivity startsActivity.startActivity(activity); - ``` - -_Samples of the searching by AndroidUIAutomator using_ ```io.appium.java_client.AppiumDriver``` - -```java -import io.appium.java_client.FindsByAndroidUIAutomator; -import io.appium.java_client.android.AndroidElement; - -... - -FindsByAndroidUIAutomator findsByAndroidUIAutomator = - new FindsByAndroidUIAutomator() { - @Override - public AndroidElement findElement(String by, String using) { - return driver.findElement(by, using); - } - - @Override - public List findElements(String by, String using) { - return driver.findElements(by, using); - }; -}; - -findsByAndroidUIAutomator.findElementByAndroidUIAutomator("automatorString"); -``` - -```java -driver.findElement(MobileBy.AndroidUIAutomator("automatorString")); -``` - -All that ```AndroidDriver``` can do by design. diff --git a/docs/The-starting-of-an-app-using-Appium-node-server-started-programmatically.md b/docs/The-starting-of-an-app-using-Appium-node-server-started-programmatically.md index 5d0fc1e13..9397385c5 100644 --- a/docs/The-starting-of-an-app-using-Appium-node-server-started-programmatically.md +++ b/docs/The-starting-of-an-app-using-Appium-node-server-started-programmatically.md @@ -7,30 +7,6 @@ It works the similar way as common [ChromeDriver](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/chrome/ChromeDriver.html), [InternetExplorerDriver](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/ie/InternetExplorerDriver.html) of Selenium project or [PhantomJSDriver](https://cdn.rawgit.com/detro/ghostdriver/master/binding/java/docs/javadoc/org/openqa/selenium/phantomjs/PhantomJSDriver.html). They use subclasses of the [DriverService](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/service/DriverService.html). -# Which capabilities this feature provides - -This feature provides abilities and options of the starting of a local Appium node server. End users still able to open apps as usual - -```java - DesiredCapabilities capabilities = new DesiredCapabilities(); - capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); - capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 120); - driver = new AndroidDriver<>(new URL("remoteOrLocalAddress"), capabilities); -``` - -when the server is launched locally\remotely. Also user is free to launch a local Appium node server and open their app for the further testing the following way: - -```java - DesiredCapabilities capabilities = new DesiredCapabilities(); - capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); - capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 120); - driver = new AndroidDriver<>(capabilities); -``` - # How to prepare the local service before the starting @@ -49,6 +25,7 @@ when the server is launched locally\remotely. Also user is free to launch a loca ### FYI There are possible problems related to local environment which could break this: + ```java AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); ``` diff --git a/docs/The-starting-of-an-iOS-app.md b/docs/The-starting-of-an-iOS-app.md deleted file mode 100644 index 19b5ba2a0..000000000 --- a/docs/The-starting-of-an-iOS-app.md +++ /dev/null @@ -1,122 +0,0 @@ -# Steps: - -- you have to prepare environment for iOS. [Details are provided here](https://appium.io/docs/en/drivers/ios-xcuitest/#basic-setup) - -- it needs to launch the appium server. You can launch Appium desktop application. If you use the server installed via npm then - - _$ node **the_path_to_js_file** --arg1 value1 --arg2 value2_ -It is not necessary to use arguments. [The list of arguments](https://appium.io/docs/en/writing-running-appium/server-args/) - -# The starting of an app - -It looks like creation of a common [RemoteWebDriver](https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/remote/RemoteWebDriver.html) instance. - -[Common capabilities](https://appium.io/docs/en/writing-running-appium/caps/#general-capabilities) - -[iOS-specific capabilities](https://appium.io/docs/en/writing-running-appium/caps/#ios-only) - -[Common capabilities provided by Java client](https://javadoc.io/page/io.appium/java-client/latest/io/appium/java_client/remote/MobileCapabilityType.html) - -[iOS-specific capabilities provided by Java client](https://javadoc.io/page/io.appium/java-client/latest/io/appium/java_client/remote/IOSMobileCapabilityType.html) - - -```java -import java.io.File; -import org.openqa.selenium.remote.DesiredCapabilities; -import io.appium.java_client.AppiumDriver; -import io.appium.java_client.MobileElement; -import java.net.URL; - -... -File app = new File("The absolute or relative path to an *.app, *.zip or ipa file"); -DesiredCapabilities capabilities = new DesiredCapabilities(); -capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator"); -capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "The_target_version"); -capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.IOS); -//The_target_version is the supported iOS version, e.g. 8.1, 8.2, 9.2 etc -capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); -//you are free to set additional capabilities -AppiumDriver driver = new AppiumDriver<>( -new URL("http://target_ip:used_port/wd/hub"), //if it needs to use locally started server -//then the target_ip is 127.0.0.1 or 0.0.0.0 -//the default port is 4723 -capabilities); -``` - -or - -```java -import java.io.File; -import org.openqa.selenium.remote.DesiredCapabilities; -import io.appium.java_client.AppiumDriver; -import io.appium.java_client.ios.IOSDriver; -import io.appium.java_client.MobileElement; -import java.net.URL; - -... -File app = new File("The absolute or relative path to an *.app, *.zip or ipa file"); -DesiredCapabilities capabilities = new DesiredCapabilities(); -capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator"); -capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "The_target_version"); -//The_target_version is the supported iOS version, e.g. 8.1, 8.2, 9.2 etc -capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); -//you are free to set additional capabilities -AppiumDriver driver = new IOSDriver<>( -new URL("http://target_ip:used_port/wd/hub"), //if it needs to use locally started server -//then the target_ip is 127.0.0.1 or 0.0.0.0 -//the default port is 4723 -capabilities); -``` - -## If it needs to start browser then - -```java -capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, MobileBrowserType.SAFARI); -``` - -## There are two automation types - -Default iOS Automation (v < iOS 10.x) does not require any specific capability. However you can -```java -capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.APPIUM); -``` - -You have to define this automation type to be able to use XCUIT mode for new iOS versions (v > 10.x) -```java -capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST); -``` - -# Possible cases - -You can use ```io.appium.java_client.AppiumDriver``` and ```io.appium.java_client.ios.IOSDriver``` as well. The main difference -is that ```IOSDriver``` implements all API that describes interaction with iOS native/hybrid app. ```AppiumDriver``` allows to -use iOS-specific API eventually. - -_Samples of the searching by iOSNsPredicateString using_ ```io.appium.java_client.AppiumDriver``` - -```java -import io.appium.java_client.FindsByIosNSPredicate; -import io.appium.java_client.ios.IOSElement; - -... - -FindsByIosNSPredicate findsByIosNSPredicate = new FindsByIosNSPredicate() { - @Override - public IOSElement findElement(String by, String using) { - return driver.findElement(by, using); - } - - @Override - public List findElements(String by, String using) { - return driver.findElements(by, using); - } -}; - -findsByIosNSPredicate.findElementByIosNsPredicate("some predicate"); -``` - -```java -driver.findElement(MobileBy.iOSNsPredicateString("some predicate")); -``` - -All that ```IOSDriver``` can do by design. diff --git a/docs/Touch-actions.md b/docs/Touch-actions.md deleted file mode 100644 index 920a8d898..000000000 --- a/docs/Touch-actions.md +++ /dev/null @@ -1,44 +0,0 @@ -Appium server side provides abilities to emulate touch actions. It is possible construct single, complex and multiple touch actions. - -# How to use a single touch action - -```java -import io.appium.java_client.TouchAction; - -... -//tap -new TouchAction(driver) - .tap(driver - .findElementById("io.appium.android.apis:id/start")).perform(); -``` - -# How to construct complex actions - -```java -import io.appium.java_client.TouchAction; - -... -//swipe -TouchAction swipe = new TouchAction(driver).press(images.get(2), -10, center.y - location.y) - .waitAction(2000).moveTo(gallery, 10, center.y - location.y).release(); -swipe.perform(); -``` - -# How to construct multiple touch action. - -```java -import io.appium.java_client.TouchAction; -import io.appium.java_client.MultiTouchAction; - -... -//tap by few fingers - MultiTouchAction multiTouch = new MultiTouchAction(driver); - -for (int i = 0; i < fingers; i++) { - TouchAction tap = new TouchAction(driver); - multiTouch.add(tap.press(element).waitAction(duration).release()); -} - -multiTouch.perform(); -``` - From 8f6e2cffb101bf0d34a721ba9cb435e633d59b4c Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 3 Nov 2022 10:29:29 -0700 Subject: [PATCH 023/314] chore: remove unnecessary annotation (#1791) * chore: remove unnecessary annotation * add copyright * remove unused import --- .../java_client/remote/DirectConnect.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/remote/DirectConnect.java b/src/main/java/io/appium/java_client/remote/DirectConnect.java index bfd0f4129..809fdc736 100644 --- a/src/main/java/io/appium/java_client/remote/DirectConnect.java +++ b/src/main/java/io/appium/java_client/remote/DirectConnect.java @@ -1,8 +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.remote; import lombok.AccessLevel; import lombok.Getter; -import lombok.experimental.Accessors; import javax.annotation.Nullable; import java.net.MalformedURLException; @@ -13,7 +28,6 @@ import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; -@Accessors public class DirectConnect { private static final String DIRECT_CONNECT_PROTOCOL = "directConnectProtocol"; private static final String DIRECT_CONNECT_PATH = "directConnectPath"; From 83f2ac257bd40c6d6edeb8594fa57ce95e5f525d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 4 Nov 2022 14:15:31 +0300 Subject: [PATCH 024/314] chore: Force unified imports order (#1793) --- google-style.xml => appium-style.xml | 27 ++++++++--------- build.gradle | 2 +- .../io/appium/java_client/AppiumDriver.java | 10 +++---- .../java_client/AppiumExecutionMethod.java | 1 - .../appium/java_client/AppiumFluentWait.java | 1 - .../io/appium/java_client/ComparesImages.java | 6 ++-- .../appium/java_client/ErrorCodesMobile.java | 1 - .../io/appium/java_client/HasAppStrings.java | 6 ++-- .../io/appium/java_client/HasDeviceTime.java | 5 ++-- .../io/appium/java_client/HasSettings.java | 6 ++-- .../appium/java_client/InteractsWithApps.java | 21 +++++++------- .../io/appium/java_client/LocksDevice.java | 4 +-- .../io/appium/java_client/LogsEvents.java | 13 +++++---- .../java/io/appium/java_client/MobileBy.java | 4 +-- .../io/appium/java_client/MobileCommand.java | 3 +- .../appium/java_client/MultiTouchAction.java | 6 ++-- .../java_client/PerformsTouchActions.java | 6 ++-- .../io/appium/java_client/PullsFiles.java | 7 ++--- .../io/appium/java_client/PushesFiles.java | 6 ++-- .../appium/java_client/ScreenshotState.java | 7 ++--- .../io/appium/java_client/TouchAction.java | 9 +++--- .../java_client/android/AndroidDriver.java | 13 ++++----- .../android/AndroidMobileCommandHelper.java | 6 ++-- .../AndroidStartScreenRecordingOptions.java | 5 ++-- .../android/AuthenticatesByFinger.java | 4 +-- .../android/HasAndroidClipboard.java | 8 ++--- .../android/HasAndroidDeviceDetails.java | 6 ++-- .../HasSupportedPerformanceDataType.java | 6 ++-- .../android/ListensToLogcatMessages.java | 7 ++--- .../java_client/android/StartsActivity.java | 6 ++-- .../SupportsNetworkStateManagement.java | 6 ++-- .../SupportsSpecialEmulatorCommands.java | 6 ++-- .../AndroidInstallApplicationOptions.java | 9 +++--- .../AndroidRemoveApplicationOptions.java | 9 +++--- .../AndroidTerminateApplicationOptions.java | 9 +++--- .../connection/HasNetworkConnection.java | 6 ++-- .../android/nativekey/KeyEvent.java | 4 +-- .../android/nativekey/PressesKey.java | 6 ++-- .../android/options/EspressoOptions.java | 4 +-- .../android/options/UiAutomator2Options.java | 6 ++-- .../SupportsAndroidInstallTimeoutOption.java | 2 -- .../java_client/clipboard/HasClipboard.java | 10 +++---- .../BaseComparisonOptions.java | 4 +-- .../imagecomparison/ComparisonResult.java | 1 - .../FeaturesMatchingOptions.java | 6 ++-- .../OccurrenceMatchingOptions.java | 4 +-- .../java_client/ios/HasIOSClipboard.java | 6 ++-- .../io/appium/java_client/ios/IOSDriver.java | 9 +++--- .../ios/IOSMobileCommandHelper.java | 1 - .../ios/IOSStartScreenRecordingOptions.java | 7 ++--- .../ios/ListensToSyslogMessages.java | 7 ++--- .../java_client/ios/PerformsTouchID.java | 6 ++-- .../appium/java_client/ios/ShakesDevice.java | 4 +-- .../ios/touch/IOSPressOptions.java | 4 +-- .../pagefactory/AndroidFindAll.java | 8 ++--- .../pagefactory/AndroidFindBy.java | 8 ++--- .../pagefactory/AndroidFindByAllSet.java | 6 ++-- .../pagefactory/AndroidFindByChainSet.java | 6 ++-- .../pagefactory/AndroidFindBySet.java | 6 ++-- .../pagefactory/AndroidFindBys.java | 8 ++--- .../pagefactory/AppiumElementLocator.java | 13 ++++----- .../AppiumElementLocatorFactory.java | 8 ++--- .../pagefactory/AppiumFieldDecorator.java | 9 +++--- .../pagefactory/DefaultElementByBuilder.java | 8 ++--- .../pagefactory/ElementInterceptor.java | 4 +-- .../pagefactory/ElementListInterceptor.java | 4 +-- .../pagefactory/OverrideWidgetReader.java | 11 ++++--- .../java_client/pagefactory/Widget.java | 4 +-- .../pagefactory/WidgetByBuilder.java | 8 ++--- .../pagefactory/WidgetInterceptor.java | 6 ++-- .../pagefactory/WidgetListInterceptor.java | 8 ++--- .../pagefactory/WindowsFindAll.java | 8 ++--- .../pagefactory/WindowsFindBy.java | 8 ++--- .../pagefactory/WindowsFindByAllSet.java | 6 ++-- .../pagefactory/WindowsFindByChainSet.java | 6 ++-- .../pagefactory/WindowsFindBySet.java | 6 ++-- .../pagefactory/WindowsFindBys.java | 8 ++--- .../pagefactory/bys/ContentMappedBy.java | 8 ++--- .../bys/builder/AppiumByBuilder.java | 12 ++++---- .../pagefactory/bys/builder/ByAll.java | 6 ++-- .../pagefactory/bys/builder/ByChained.java | 4 +-- .../pagefactory/bys/builder/Strategies.java | 13 ++++----- .../pagefactory/iOSXCUITFindAll.java | 8 ++--- .../pagefactory/iOSXCUITFindBy.java | 8 ++--- .../pagefactory/iOSXCUITFindByAllSet.java | 6 ++-- .../pagefactory/iOSXCUITFindByChainSet.java | 6 ++-- .../pagefactory/iOSXCUITFindBySet.java | 6 ++-- .../pagefactory/iOSXCUITFindBys.java | 8 ++--- .../utils/WebDriverUnpackUtility.java | 10 +++---- .../remote/AppiumCommandExecutor.java | 13 ++++----- .../remote/AppiumW3CHttpCommandCodec.java | 8 ++--- .../options/SupportsBrowserVersionOption.java | 2 -- .../BaseScreenRecordingOptions.java | 6 ++-- .../BaseStartScreenRecordingOptions.java | 6 ++-- .../screenrecording/CanRecordScreen.java | 6 ++-- .../ScreenRecordingUploadOptions.java | 6 ++-- .../serverevents/ServerEvents.java | 3 +- .../java_client/serverevents/TimedEvent.java | 3 +- .../local/AppiumDriverLocalService.java | 14 ++++----- .../service/local/AppiumServiceBuilder.java | 11 +++---- .../java_client/touch/LongPressOptions.java | 8 ++--- .../appium/java_client/touch/TapOptions.java | 6 ++-- .../appium/java_client/touch/WaitOptions.java | 6 ++-- .../AbstractOptionCombinedWithPosition.java | 4 +-- .../touch/offset/ElementOption.java | 8 ++--- .../java_client/touch/offset/PointOption.java | 4 +-- .../options/SupportsAppArgumentsOption.java | 1 - .../java_client/ws/StringWebSocketClient.java | 15 +++++----- .../AndroidAbilityToUseSupplierTest.java | 12 ++++---- .../android/AndroidActivityTest.java | 4 +-- .../android/AndroidAppStringsTest.java | 4 +-- .../android/AndroidConnectionTest.java | 6 ++-- .../android/AndroidContextTest.java | 6 ++-- .../android/AndroidDriverTest.java | 20 ++++++------- .../android/AndroidElementTest.java | 8 ++--- .../android/AndroidFunctionTest.java | 14 ++++----- .../android/AndroidLogcatListenerTest.java | 4 +-- .../android/AndroidScreenRecordTest.java | 10 +++---- .../android/AndroidSearchingTest.java | 8 ++--- .../java_client/android/AndroidTouchTest.java | 21 +++++++------- .../java_client/android/BaseAndroidTest.java | 1 - .../java_client/android/BatteryTest.java | 4 +-- .../java_client/android/ClipboardTest.java | 4 +-- .../android/ExecuteCDPCommandTest.java | 1 - .../java_client/android/FingerPrintTest.java | 4 +-- .../android/ImagesComparisonTest.java | 16 +++++----- .../java_client/android/IntentTest.java | 8 ++--- .../java_client/android/KeyCodeTest.java | 8 ++--- .../java_client/android/LogEventTest.java | 8 ++--- .../android/OpenNotificationsTest.java | 6 ++-- .../java_client/android/UIAutomator2Test.java | 6 ++-- .../events/stubs/StubWebElement.java | 5 ++-- .../internal/AppiumUserAgentFilterTest.java | 3 +- .../java_client/internal/ConfigTest.java | 14 ++++----- .../internal/DirectConnectTest.java | 5 +++- .../appium/java_client/ios/ClipboardTest.java | 4 +-- .../appium/java_client/ios/IOSAlertTest.java | 8 ++--- .../java_client/ios/IOSAppStringsTest.java | 4 +-- .../java_client/ios/IOSContextTest.java | 6 ++-- .../appium/java_client/ios/IOSDriverTest.java | 18 ++++++------ .../java_client/ios/IOSElementTest.java | 13 ++++----- .../ios/IOSNativeWebTapSettingTest.java | 8 ++--- .../java_client/ios/IOSScreenRecordTest.java | 8 ++--- .../java_client/ios/IOSSearchingTest.java | 5 ++-- .../ios/IOSSyslogListenerTest.java | 4 +-- .../appium/java_client/ios/IOSTouchTest.java | 18 ++++++------ .../java_client/ios/IOSWebViewTest.java | 8 ++--- .../java_client/ios/ImagesComparisonTest.java | 16 +++++----- .../appium/java_client/ios/RotationTest.java | 4 +-- .../appium/java_client/ios/SettingTest.java | 4 +-- .../AndroidPageObjectTest.java | 17 +++++------ .../DesktopBrowserCompatibilityTest.java | 14 ++++----- .../MobileBrowserCompatibilityTest.java | 4 +-- .../pagefactory_tests/TimeoutTest.java | 24 +++++++-------- .../pagefactory_tests/XCUITModeTest.java | 22 +++++++------- .../widget/tests/AbstractStubWebDriver.java | 16 +++++----- .../widget/tests/DefaultStubWidget.java | 1 - .../widget/tests/StubWebElement.java | 6 ++-- .../widget/tests/WidgetTest.java | 12 ++++---- .../tests/android/AndroidWidgetTest.java | 10 +++---- .../tests/combined/CombinedAppTest.java | 17 ++++++----- .../tests/combined/CombinedWidgetTest.java | 12 ++++---- .../widget/tests/ios/XCUITWidgetTest.java | 10 +++---- .../tests/windows/WindowsWidgetTest.java | 10 +++---- .../service/local/ServerBuilderTest.java | 29 ++++++++++--------- .../local/StartingAppLocallyAndroidTest.java | 15 +++++----- .../local/StartingAppLocallyIosTest.java | 12 ++++---- .../service/local/ThreadSafetyTest.java | 4 +-- .../java_client/touch/FailsWithMatcher.java | 6 ++-- .../java_client/touch/TouchOptionsTests.java | 22 +++++++------- 170 files changed, 649 insertions(+), 692 deletions(-) rename google-style.xml => appium-style.xml (93%) diff --git a/google-style.xml b/appium-style.xml similarity index 93% rename from google-style.xml rename to appium-style.xml index 5762dbafb..03937a66d 100755 --- a/google-style.xml +++ b/appium-style.xml @@ -2,20 +2,10 @@ - - - @@ -41,9 +31,16 @@ - - - + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 8918b815d..b64f8cebb 100644 --- a/build.gradle +++ b/build.gradle @@ -96,7 +96,7 @@ jacocoTestReport.dependsOn test checkstyle { toolVersion = '8.32' - configFile = file("$projectDir/google-style.xml") + configFile = file("$projectDir/appium-style.xml") showViolations = true ignoreFailures = false } diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 450822a69..0c72f9c86 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -16,11 +16,6 @@ package io.appium.java_client; -import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; -import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME; -import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; -import static org.apache.commons.lang3.StringUtils.isBlank; - import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.AppiumNewSessionCommandPayload; @@ -51,6 +46,11 @@ import java.util.Collections; import java.util.Map; +import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; +import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME; +import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; +import static org.apache.commons.lang3.StringUtils.isBlank; + /** * Default Appium driver implementation. */ diff --git a/src/main/java/io/appium/java_client/AppiumExecutionMethod.java b/src/main/java/io/appium/java_client/AppiumExecutionMethod.java index 3abe1ef4f..9b9f79cb9 100644 --- a/src/main/java/io/appium/java_client/AppiumExecutionMethod.java +++ b/src/main/java/io/appium/java_client/AppiumExecutionMethod.java @@ -17,7 +17,6 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; - import org.openqa.selenium.remote.ExecuteMethod; import org.openqa.selenium.remote.Response; diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 8f197ef47..d914f6c16 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -17,7 +17,6 @@ package io.appium.java_client; import com.google.common.base.Throwables; - import lombok.AccessLevel; import lombok.Getter; import org.openqa.selenium.TimeoutException; diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index c1c97abc9..4aeefec6a 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -16,8 +16,6 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.compareImagesCommand; - import io.appium.java_client.imagecomparison.ComparisonMode; import io.appium.java_client.imagecomparison.FeaturesMatchingOptions; import io.appium.java_client.imagecomparison.FeaturesMatchingResult; @@ -27,11 +25,13 @@ import io.appium.java_client.imagecomparison.SimilarityMatchingResult; import org.apache.commons.io.FileUtils; +import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.Base64; import java.util.Map; -import javax.annotation.Nullable; + +import static io.appium.java_client.MobileCommand.compareImagesCommand; public interface ComparesImages extends ExecutesMethod { diff --git a/src/main/java/io/appium/java_client/ErrorCodesMobile.java b/src/main/java/io/appium/java_client/ErrorCodesMobile.java index 924625b08..6e7e66d69 100644 --- a/src/main/java/io/appium/java_client/ErrorCodesMobile.java +++ b/src/main/java/io/appium/java_client/ErrorCodesMobile.java @@ -18,7 +18,6 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; - import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.ErrorCodes; diff --git a/src/main/java/io/appium/java_client/HasAppStrings.java b/src/main/java/io/appium/java_client/HasAppStrings.java index 0c9b3905f..1983d4667 100644 --- a/src/main/java/io/appium/java_client/HasAppStrings.java +++ b/src/main/java/io/appium/java_client/HasAppStrings.java @@ -16,12 +16,12 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.GET_STRINGS; -import static io.appium.java_client.MobileCommand.prepareArguments; - import java.util.AbstractMap; import java.util.Map; +import static io.appium.java_client.MobileCommand.GET_STRINGS; +import static io.appium.java_client.MobileCommand.prepareArguments; + public interface HasAppStrings extends ExecutesMethod { /** * Get all defined Strings from an app for the default language. diff --git a/src/main/java/io/appium/java_client/HasDeviceTime.java b/src/main/java/io/appium/java_client/HasDeviceTime.java index fa9df8997..fb9fc59b6 100644 --- a/src/main/java/io/appium/java_client/HasDeviceTime.java +++ b/src/main/java/io/appium/java_client/HasDeviceTime.java @@ -16,16 +16,15 @@ package io.appium.java_client; -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; +import static io.appium.java_client.MobileCommand.GET_DEVICE_TIME; + public interface HasDeviceTime extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/HasSettings.java b/src/main/java/io/appium/java_client/HasSettings.java index 8210123a7..73befa6f5 100644 --- a/src/main/java/io/appium/java_client/HasSettings.java +++ b/src/main/java/io/appium/java_client/HasSettings.java @@ -16,9 +16,6 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.getSettingsCommand; -import static io.appium.java_client.MobileCommand.setSettingsCommand; - import org.openqa.selenium.remote.Response; import java.util.EnumMap; @@ -26,6 +23,9 @@ import java.util.Map.Entry; import java.util.stream.Collectors; +import static io.appium.java_client.MobileCommand.getSettingsCommand; +import static io.appium.java_client.MobileCommand.setSettingsCommand; + public interface HasSettings extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index f1a15146e..eb73e6e30 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -16,26 +16,25 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.ACTIVATE_APP; -import static io.appium.java_client.MobileCommand.INSTALL_APP; -import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; -import static io.appium.java_client.MobileCommand.QUERY_APP_STATE; -import static io.appium.java_client.MobileCommand.REMOVE_APP; -import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; -import static io.appium.java_client.MobileCommand.TERMINATE_APP; -import static io.appium.java_client.MobileCommand.prepareArguments; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.appmanagement.BaseActivateApplicationOptions; import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; +import javax.annotation.Nullable; import java.time.Duration; import java.util.AbstractMap; -import javax.annotation.Nullable; + +import static io.appium.java_client.MobileCommand.ACTIVATE_APP; +import static io.appium.java_client.MobileCommand.INSTALL_APP; +import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; +import static io.appium.java_client.MobileCommand.QUERY_APP_STATE; +import static io.appium.java_client.MobileCommand.REMOVE_APP; +import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; +import static io.appium.java_client.MobileCommand.TERMINATE_APP; +import static io.appium.java_client.MobileCommand.prepareArguments; @SuppressWarnings("rawtypes") public interface InteractsWithApps extends ExecutesMethod { diff --git a/src/main/java/io/appium/java_client/LocksDevice.java b/src/main/java/io/appium/java_client/LocksDevice.java index 944624002..ce4d794c8 100644 --- a/src/main/java/io/appium/java_client/LocksDevice.java +++ b/src/main/java/io/appium/java_client/LocksDevice.java @@ -16,12 +16,12 @@ package io.appium.java_client; +import java.time.Duration; + import static io.appium.java_client.MobileCommand.getIsDeviceLockedCommand; import static io.appium.java_client.MobileCommand.lockDeviceCommand; import static io.appium.java_client.MobileCommand.unlockDeviceCommand; -import java.time.Duration; - public interface LocksDevice extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/LogsEvents.java b/src/main/java/io/appium/java_client/LogsEvents.java index 7844b56a6..a29d9eecf 100644 --- a/src/main/java/io/appium/java_client/LogsEvents.java +++ b/src/main/java/io/appium/java_client/LogsEvents.java @@ -16,19 +16,20 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.GET_EVENTS; -import static io.appium.java_client.MobileCommand.LOG_EVENT; - import com.google.common.collect.ImmutableMap; import io.appium.java_client.serverevents.CommandEvent; import io.appium.java_client.serverevents.CustomEvent; -import io.appium.java_client.serverevents.TimedEvent; import io.appium.java_client.serverevents.ServerEvents; +import io.appium.java_client.serverevents.TimedEvent; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.remote.Response; + import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.openqa.selenium.json.Json; -import org.openqa.selenium.remote.Response; + +import static io.appium.java_client.MobileCommand.GET_EVENTS; +import static io.appium.java_client.MobileCommand.LOG_EVENT; public interface LogsEvents extends ExecutesMethod { diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index 65f382ec7..7d30bd855 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -16,10 +16,10 @@ package io.appium.java_client; -import java.io.Serializable; - import org.openqa.selenium.By; +import java.io.Serializable; + /** * Appium locating strategies. * diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 4d20164ad..b87e76949 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -17,7 +17,6 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; - import io.appium.java_client.imagecomparison.BaseComparisonOptions; import io.appium.java_client.imagecomparison.ComparisonMode; import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; @@ -26,12 +25,12 @@ import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.http.HttpMethod; +import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; /** * The repository of mobile commands defined in the Mobile JSON diff --git a/src/main/java/io/appium/java_client/MultiTouchAction.java b/src/main/java/io/appium/java_client/MultiTouchAction.java index ac66594dc..1549fbdf5 100644 --- a/src/main/java/io/appium/java_client/MultiTouchAction.java +++ b/src/main/java/io/appium/java_client/MultiTouchAction.java @@ -16,15 +16,15 @@ package io.appium.java_client; -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.stream.Collectors.toList; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.stream.Collectors.toList; + /** * Used for Webdriver 3 multi-touch gestures * See the Webriver 3 spec https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html diff --git a/src/main/java/io/appium/java_client/PerformsTouchActions.java b/src/main/java/io/appium/java_client/PerformsTouchActions.java index 0ac7776d6..539a38a52 100644 --- a/src/main/java/io/appium/java_client/PerformsTouchActions.java +++ b/src/main/java/io/appium/java_client/PerformsTouchActions.java @@ -16,12 +16,12 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.PERFORM_MULTI_TOUCH; -import static io.appium.java_client.MobileCommand.PERFORM_TOUCH_ACTION; - import java.util.List; import java.util.Map; +import static io.appium.java_client.MobileCommand.PERFORM_MULTI_TOUCH; +import static io.appium.java_client.MobileCommand.PERFORM_TOUCH_ACTION; + /** * Touch actions are deprecated. * Please use W3C Actions instead or the corresponding diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index 590e4aaa0..f85c18a6e 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -16,16 +16,15 @@ package io.appium.java_client; -import static io.appium.java_client.MobileCommand.PULL_FILE; -import static io.appium.java_client.MobileCommand.PULL_FOLDER; - import com.google.common.collect.ImmutableMap; - import org.openqa.selenium.remote.Response; import java.nio.charset.StandardCharsets; import java.util.Base64; +import static io.appium.java_client.MobileCommand.PULL_FILE; +import static io.appium.java_client.MobileCommand.PULL_FOLDER; + public interface PullsFiles extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index a79e58ba9..552c25a83 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -16,15 +16,15 @@ package io.appium.java_client; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.MobileCommand.pushFileCommand; - import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.util.Base64; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.pushFileCommand; + public interface PushesFiles extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/ScreenshotState.java b/src/main/java/io/appium/java_client/ScreenshotState.java index 5645b79e2..8c88397e5 100644 --- a/src/main/java/io/appium/java_client/ScreenshotState.java +++ b/src/main/java/io/appium/java_client/ScreenshotState.java @@ -21,9 +21,7 @@ import lombok.Setter; import lombok.experimental.Accessors; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - +import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -33,7 +31,8 @@ import java.util.function.Function; import java.util.function.Supplier; -import javax.imageio.ImageIO; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; @Accessors(chain = true) public class ScreenshotState { diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index 866e87140..98abeaafb 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -16,13 +16,8 @@ package io.appium.java_client; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.builder; -import static java.util.stream.Collectors.toList; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - import io.appium.java_client.touch.ActionOptions; import io.appium.java_client.touch.LongPressOptions; import io.appium.java_client.touch.TapOptions; @@ -33,6 +28,10 @@ import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.builder; +import static java.util.stream.Collectors.toList; + /** * Used for Webdriver 3 touch actions * See the Webriver 3 spec 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 92ed19370..f3b812584 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -16,13 +16,7 @@ package io.appium.java_client.android; -import static io.appium.java_client.android.AndroidMobileCommandHelper.endTestCoverageCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; @@ -32,9 +26,9 @@ import io.appium.java_client.HasOnScreenKeyboard; import io.appium.java_client.HidesKeyboard; import io.appium.java_client.InteractsWithApps; -import io.appium.java_client.PullsFiles; import io.appium.java_client.LocksDevice; import io.appium.java_client.PerformsTouchActions; +import io.appium.java_client.PullsFiles; import io.appium.java_client.PushesFiles; import io.appium.java_client.SupportsLegacyAppManagement; import io.appium.java_client.android.connection.HasNetworkConnection; @@ -59,6 +53,11 @@ import java.util.Collections; import java.util.Map; +import static io.appium.java_client.android.AndroidMobileCommandHelper.endTestCoverageCommand; +import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; +import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; +import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; + /** * Android driver implementation. */ diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index 867e96fdd..c5fa63080 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -16,18 +16,16 @@ package io.appium.java_client.android; -import static com.google.common.base.Preconditions.checkArgument; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.MobileCommand; - import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.remote.RemoteWebElement; import java.util.AbstractMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; + /** * This util class helps to prepare parameters of Android-specific JSONWP * commands. 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 c8378a86b..2ef6cdc29 100644 --- a/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java @@ -16,16 +16,15 @@ package io.appium.java_client.android; -import static java.util.Optional.ofNullable; - 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; +import static java.util.Optional.ofNullable; + public class AndroidStartScreenRecordingOptions extends BaseStartScreenRecordingOptions { private Integer bitRate; diff --git a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java index 717d5150d..15503e239 100644 --- a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java +++ b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java @@ -1,10 +1,10 @@ package io.appium.java_client.android; -import static io.appium.java_client.android.AndroidMobileCommandHelper.fingerPrintCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import static io.appium.java_client.android.AndroidMobileCommandHelper.fingerPrintCommand; + public interface AuthenticatesByFinger extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java b/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java index 063f689cc..40fc24a83 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java @@ -16,10 +16,6 @@ package io.appium.java_client.android; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; -import static io.appium.java_client.MobileCommand.prepareArguments; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.clipboard.ClipboardContentType; import io.appium.java_client.clipboard.HasClipboard; @@ -28,6 +24,10 @@ import java.util.AbstractMap; import java.util.Base64; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; +import static io.appium.java_client.MobileCommand.prepareArguments; + public interface HasAndroidClipboard extends HasClipboard { /** * Set the content of device's clipboard. diff --git a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java index 7984a0b43..66c084480 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java @@ -1,13 +1,13 @@ package io.appium.java_client.android; -import static io.appium.java_client.android.AndroidMobileCommandHelper.getDisplayDensityCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.getSystemBarsCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import java.util.Map; +import static io.appium.java_client.android.AndroidMobileCommandHelper.getDisplayDensityCommand; +import static io.appium.java_client.android.AndroidMobileCommandHelper.getSystemBarsCommand; + public interface HasAndroidDeviceDetails extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java index f64f87332..3afea7552 100644 --- a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java +++ b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java @@ -1,13 +1,13 @@ package io.appium.java_client.android; -import static io.appium.java_client.android.AndroidMobileCommandHelper.getPerformanceDataCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.getSupportedPerformanceDataTypesCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import java.util.List; +import static io.appium.java_client.android.AndroidMobileCommandHelper.getPerformanceDataCommand; +import static io.appium.java_client.android.AndroidMobileCommandHelper.getSupportedPerformanceDataTypesCommand; + public interface HasSupportedPerformanceDataType extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java index 4fffed837..44cae6765 100644 --- a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java +++ b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java @@ -16,11 +16,7 @@ package io.appium.java_client.android; -import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; import org.openqa.selenium.remote.RemoteWebDriver; @@ -30,6 +26,9 @@ import java.util.Collections; import java.util.function.Consumer; +import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; +import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; + public interface ListensToLogcatMessages extends ExecutesMethod { StringWebSocketClient getLogcatClient(); diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index bd21d9d86..ea4c04ce5 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -16,13 +16,13 @@ package io.appium.java_client.android; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; + import static io.appium.java_client.android.AndroidMobileCommandHelper.currentActivityCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.currentPackageCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.startActivityCommand; -import io.appium.java_client.CommandExecutionHelper; -import io.appium.java_client.ExecutesMethod; - public interface StartsActivity extends ExecutesMethod { /** * This method should start arbitrary activity during a test. If the activity belongs to diff --git a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java index df63b6e1a..9100fbb69 100644 --- a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java @@ -1,12 +1,12 @@ package io.appium.java_client.android; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; + import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleAirplaneCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleWifiCommand; -import io.appium.java_client.CommandExecutionHelper; -import io.appium.java_client.ExecutesMethod; - public interface SupportsNetworkStateManagement extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java index 9da6dfaec..6baeef8a0 100644 --- a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java +++ b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java @@ -1,5 +1,8 @@ package io.appium.java_client.android; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; + import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmCallCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmSignalStrengthCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmVoiceCommand; @@ -8,9 +11,6 @@ import static io.appium.java_client.android.AndroidMobileCommandHelper.powerCapacityCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.sendSMSCommand; -import io.appium.java_client.CommandExecutionHelper; -import io.appium.java_client.ExecutesMethod; - public interface SupportsSpecialEmulatorCommands extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java index a6eb8e088..67f2e09db 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java @@ -16,17 +16,16 @@ package io.appium.java_client.android.appmanagement; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; import java.time.Duration; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class AndroidInstallApplicationOptions extends BaseInstallApplicationOptions { private Boolean replace; diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java index c09253c51..2a4899f13 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java @@ -16,17 +16,16 @@ package io.appium.java_client.android.appmanagement; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import java.time.Duration; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class AndroidRemoveApplicationOptions extends BaseRemoveApplicationOptions { private Duration timeout; diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java index 6ad6c83f5..0e11e569d 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java @@ -16,17 +16,16 @@ package io.appium.java_client.android.appmanagement; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; import java.time.Duration; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class AndroidTerminateApplicationOptions extends BaseTerminateApplicationOptions { private Duration timeout; diff --git a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java index c17d450a1..7f76340a3 100644 --- a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java +++ b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java @@ -16,12 +16,12 @@ package io.appium.java_client.android.connection; -import static io.appium.java_client.android.AndroidMobileCommandHelper.getNetworkConnectionCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.setConnectionCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import static io.appium.java_client.android.AndroidMobileCommandHelper.getNetworkConnectionCommand; +import static io.appium.java_client.android.AndroidMobileCommandHelper.setConnectionCommand; + public interface HasNetworkConnection extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java index a8538b629..8bf0a99e2 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java +++ b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java @@ -16,12 +16,12 @@ package io.appium.java_client.android.nativekey; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static java.util.Optional.ofNullable; + public class KeyEvent { private Integer keyCode; private Integer metaState; diff --git a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java index b4c39767c..6c9f36212 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java +++ b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java @@ -16,14 +16,14 @@ package io.appium.java_client.android.nativekey; -import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; -import static io.appium.java_client.MobileCommand.PRESS_KEY_CODE; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import java.util.AbstractMap; +import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; +import static io.appium.java_client.MobileCommand.PRESS_KEY_CODE; + public interface PressesKey extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/android/options/EspressoOptions.java b/src/main/java/io/appium/java_client/android/options/EspressoOptions.java index 0baebf02f..c081be4f9 100644 --- a/src/main/java/io/appium/java_client/android/options/EspressoOptions.java +++ b/src/main/java/io/appium/java_client/android/options/EspressoOptions.java @@ -32,13 +32,13 @@ import io.appium.java_client.android.options.app.SupportsAllowTestPackagesOption; import io.appium.java_client.android.options.app.SupportsAndroidInstallTimeoutOption; import io.appium.java_client.android.options.app.SupportsAppActivityOption; -import io.appium.java_client.android.options.app.SupportsIntentOptionsOption; -import io.appium.java_client.android.options.app.SupportsAppWaitDurationOption; import io.appium.java_client.android.options.app.SupportsAppPackageOption; import io.appium.java_client.android.options.app.SupportsAppWaitActivityOption; +import io.appium.java_client.android.options.app.SupportsAppWaitDurationOption; import io.appium.java_client.android.options.app.SupportsAppWaitPackageOption; import io.appium.java_client.android.options.app.SupportsAutoGrantPermissionsOption; import io.appium.java_client.android.options.app.SupportsEnforceAppInstallOption; +import io.appium.java_client.android.options.app.SupportsIntentOptionsOption; import io.appium.java_client.android.options.app.SupportsRemoteAppsCacheLimitOption; import io.appium.java_client.android.options.app.SupportsUninstallOtherPackagesOption; import io.appium.java_client.android.options.avd.SupportsAvdArgsOption; diff --git a/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java b/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java index 14eddf9eb..adc161950 100644 --- a/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java +++ b/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java @@ -31,9 +31,9 @@ import io.appium.java_client.android.options.app.SupportsAllowTestPackagesOption; import io.appium.java_client.android.options.app.SupportsAndroidInstallTimeoutOption; import io.appium.java_client.android.options.app.SupportsAppActivityOption; -import io.appium.java_client.android.options.app.SupportsAppWaitDurationOption; import io.appium.java_client.android.options.app.SupportsAppPackageOption; import io.appium.java_client.android.options.app.SupportsAppWaitActivityOption; +import io.appium.java_client.android.options.app.SupportsAppWaitDurationOption; import io.appium.java_client.android.options.app.SupportsAppWaitForLaunchOption; import io.appium.java_client.android.options.app.SupportsAppWaitPackageOption; import io.appium.java_client.android.options.app.SupportsAutoGrantPermissionsOption; @@ -50,7 +50,6 @@ import io.appium.java_client.android.options.avd.SupportsAvdOption; import io.appium.java_client.android.options.avd.SupportsAvdReadyTimeoutOption; import io.appium.java_client.android.options.avd.SupportsGpsEnabledOption; -import io.appium.java_client.remote.options.SupportsIsHeadlessOption; import io.appium.java_client.android.options.avd.SupportsNetworkSpeedOption; import io.appium.java_client.android.options.context.SupportsAutoWebviewTimeoutOption; import io.appium.java_client.android.options.context.SupportsChromeLoggingPrefsOption; @@ -78,7 +77,6 @@ import io.appium.java_client.android.options.mjpeg.SupportsMjpegScreenshotUrlOption; import io.appium.java_client.android.options.mjpeg.SupportsMjpegServerPortOption; import io.appium.java_client.android.options.other.SupportsDisableSuppressAccessibilityServiceOption; -import io.appium.java_client.remote.options.SupportsSkipLogCaptureOption; import io.appium.java_client.android.options.other.SupportsUserProfileOption; import io.appium.java_client.android.options.server.SupportsDisableWindowAnimationOption; import io.appium.java_client.android.options.server.SupportsSkipDeviceInitializationOption; @@ -97,10 +95,12 @@ import io.appium.java_client.remote.options.SupportsClearSystemFilesOption; import io.appium.java_client.remote.options.SupportsDeviceNameOption; import io.appium.java_client.remote.options.SupportsEnablePerformanceLoggingOption; +import io.appium.java_client.remote.options.SupportsIsHeadlessOption; import io.appium.java_client.remote.options.SupportsLanguageOption; import io.appium.java_client.remote.options.SupportsLocaleOption; import io.appium.java_client.remote.options.SupportsOrientationOption; import io.appium.java_client.remote.options.SupportsOtherAppsOption; +import io.appium.java_client.remote.options.SupportsSkipLogCaptureOption; import io.appium.java_client.remote.options.SupportsUdidOption; import org.openqa.selenium.Capabilities; diff --git a/src/main/java/io/appium/java_client/android/options/app/SupportsAndroidInstallTimeoutOption.java b/src/main/java/io/appium/java_client/android/options/app/SupportsAndroidInstallTimeoutOption.java index 40a588c2e..ead32780b 100644 --- a/src/main/java/io/appium/java_client/android/options/app/SupportsAndroidInstallTimeoutOption.java +++ b/src/main/java/io/appium/java_client/android/options/app/SupportsAndroidInstallTimeoutOption.java @@ -24,8 +24,6 @@ import java.time.Duration; import java.util.Optional; -import static io.appium.java_client.internal.CapabilityHelpers.toDuration; - public interface SupportsAndroidInstallTimeoutOption> extends Capabilities, CanSetCapability { String ANDROID_INSTALL_TIMEOUT_OPTION = "androidInstallTimeout"; diff --git a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java index ca813c4e0..637a35caf 100644 --- a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java +++ b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java @@ -16,11 +16,6 @@ package io.appium.java_client.clipboard; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.MobileCommand.GET_CLIPBOARD; -import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; -import static io.appium.java_client.MobileCommand.prepareArguments; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; @@ -28,6 +23,11 @@ import java.util.AbstractMap; import java.util.Base64; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.GET_CLIPBOARD; +import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; +import static io.appium.java_client.MobileCommand.prepareArguments; + public interface HasClipboard extends ExecutesMethod { /** * Set the content of device's clipboard. diff --git a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java index 5489b9686..2563055cc 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java @@ -16,12 +16,12 @@ package io.appium.java_client.imagecomparison; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static java.util.Optional.ofNullable; + public abstract class BaseComparisonOptions> { private Boolean visualize; 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 1ad0639b3..49e487b59 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -18,7 +18,6 @@ import lombok.AccessLevel; import lombok.Getter; - import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; diff --git a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java index d4c89161f..5e72c6591 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java @@ -16,13 +16,13 @@ package io.appium.java_client.imagecomparison; -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Optional.ofNullable; + public class FeaturesMatchingOptions extends BaseComparisonOptions { private String detectorName; private String matchFunc; 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 d4a7810e6..818c9f2da 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java @@ -16,12 +16,12 @@ package io.appium.java_client.imagecomparison; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static java.util.Optional.ofNullable; + public class OccurrenceMatchingOptions extends BaseComparisonOptions { private Double threshold; private Boolean multiple; diff --git a/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java b/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java index 538c8cee3..f1dd7300e 100644 --- a/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java +++ b/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java @@ -16,11 +16,10 @@ package io.appium.java_client.ios; -import static com.google.common.base.Preconditions.checkNotNull; - import io.appium.java_client.clipboard.ClipboardContentType; import io.appium.java_client.clipboard.HasClipboard; +import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -29,7 +28,8 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; -import javax.imageio.ImageIO; + +import static com.google.common.base.Preconditions.checkNotNull; public interface HasIOSClipboard extends HasClipboard { /** 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 00098b14c..d450c1b0f 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -16,11 +16,7 @@ package io.appium.java_client.ios; -import static io.appium.java_client.MobileCommand.prepareArguments; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.HasAppStrings; @@ -29,9 +25,9 @@ import io.appium.java_client.HidesKeyboard; import io.appium.java_client.HidesKeyboardWithKeyName; import io.appium.java_client.InteractsWithApps; -import io.appium.java_client.PullsFiles; import io.appium.java_client.LocksDevice; import io.appium.java_client.PerformsTouchActions; +import io.appium.java_client.PullsFiles; import io.appium.java_client.PushesFiles; import io.appium.java_client.SupportsLegacyAppManagement; import io.appium.java_client.battery.HasBattery; @@ -56,6 +52,9 @@ import java.util.Collections; import java.util.Map; +import static io.appium.java_client.MobileCommand.prepareArguments; +import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; + /** * iOS driver implementation. */ diff --git a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java index d7f6af6b1..8e61cb5cf 100644 --- a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java @@ -17,7 +17,6 @@ package io.appium.java_client.ios; import com.google.common.collect.ImmutableMap; - import io.appium.java_client.MobileCommand; import java.util.AbstractMap; 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 d9b918977..a86dabac7 100644 --- a/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java @@ -16,17 +16,16 @@ package io.appium.java_client.ios; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - 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; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class IOSStartScreenRecordingOptions extends BaseStartScreenRecordingOptions { private String videoType; diff --git a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java index 6bb15821c..3341f753d 100644 --- a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java +++ b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java @@ -16,11 +16,7 @@ package io.appium.java_client.ios; -import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; - import com.google.common.collect.ImmutableMap; - import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; import org.openqa.selenium.remote.RemoteWebDriver; @@ -30,6 +26,9 @@ import java.util.Collections; import java.util.function.Consumer; +import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; +import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; + public interface ListensToSyslogMessages extends ExecutesMethod { StringWebSocketClient getSyslogClient(); diff --git a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java index 90e088e93..907a86655 100644 --- a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java +++ b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java @@ -16,12 +16,12 @@ package io.appium.java_client.ios; -import static io.appium.java_client.ios.IOSMobileCommandHelper.toggleTouchIdEnrollmentCommand; -import static io.appium.java_client.ios.IOSMobileCommandHelper.touchIdCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import static io.appium.java_client.ios.IOSMobileCommandHelper.toggleTouchIdEnrollmentCommand; +import static io.appium.java_client.ios.IOSMobileCommandHelper.touchIdCommand; + public interface PerformsTouchID extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/ios/ShakesDevice.java b/src/main/java/io/appium/java_client/ios/ShakesDevice.java index 208f05bb1..abd601852 100644 --- a/src/main/java/io/appium/java_client/ios/ShakesDevice.java +++ b/src/main/java/io/appium/java_client/ios/ShakesDevice.java @@ -16,11 +16,11 @@ package io.appium.java_client.ios; -import static io.appium.java_client.ios.IOSMobileCommandHelper.shakeCommand; - import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import static io.appium.java_client.ios.IOSMobileCommandHelper.shakeCommand; + public interface ShakesDevice extends ExecutesMethod { /** diff --git a/src/main/java/io/appium/java_client/ios/touch/IOSPressOptions.java b/src/main/java/io/appium/java_client/ios/touch/IOSPressOptions.java index 0720bd325..a6b8cdf7f 100644 --- a/src/main/java/io/appium/java_client/ios/touch/IOSPressOptions.java +++ b/src/main/java/io/appium/java_client/ios/touch/IOSPressOptions.java @@ -16,12 +16,12 @@ package io.appium.java_client.ios.touch; -import static java.util.Optional.ofNullable; - import io.appium.java_client.touch.offset.AbstractOptionCombinedWithPosition; import java.util.Map; +import static java.util.Optional.ofNullable; + public class IOSPressOptions extends AbstractOptionCombinedWithPosition { private Double pressure = null; diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java index 69fe8c1e0..63deca31f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a * series of {@link AndroidBy} tags 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 25b37c0b6..aa245d971 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page Object to indicate an alternative mechanism for locating the diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java index ab6afda28..fed03d755 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link AndroidFindAll} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java index 365146650..3fda1f27a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.AndroidFindBys} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java index 6e35ebccd..9b1f62519 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java @@ -16,13 +16,13 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.AndroidFindBy} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java index eabfeb7c2..db278a9ce 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page Object to indicate that lookup should use * a series of {@link io.appium.java_client.pagefactory.AndroidBy} tags. diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java index b8b0fbe8f..5208add47 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -16,15 +16,8 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; -import static io.appium.java_client.pagefactory.ThrowableUtil.isInvalidSelectorRootCause; -import static io.appium.java_client.pagefactory.ThrowableUtil.isStaleElementReferenceException; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; -import static java.lang.String.format; - import io.appium.java_client.pagefactory.bys.ContentMappedBy; import io.appium.java_client.pagefactory.locator.CacheableLocator; - import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.SearchContext; @@ -40,6 +33,12 @@ import java.util.function.Function; import java.util.function.Supplier; +import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; +import static io.appium.java_client.pagefactory.ThrowableUtil.isInvalidSelectorRootCause; +import static io.appium.java_client.pagefactory.ThrowableUtil.isStaleElementReferenceException; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; +import static java.lang.String.format; + class AppiumElementLocator implements CacheableLocator { private static final String exceptionMessageIfElementNotFound = "Can't locate an element by this strategy: %s"; diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 179ecbcc4..170cffa38 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -16,19 +16,19 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.WithTimeout.DurationBuilder.build; -import static java.util.Optional.ofNullable; - import io.appium.java_client.pagefactory.bys.builder.AppiumByBuilder; import io.appium.java_client.pagefactory.locator.CacheableElementLocatorFactory; import io.appium.java_client.pagefactory.locator.CacheableLocator; import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; +import javax.annotation.Nullable; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.time.Duration; -import javax.annotation.Nullable; + +import static io.appium.java_client.pagefactory.WithTimeout.DurationBuilder.build; +import static java.util.Optional.ofNullable; public class AppiumElementLocatorFactory implements CacheableElementLocatorFactory { private final SearchContext searchContext; 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 ea3b35e5a..8bb282859 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -16,12 +16,7 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; -import static java.time.Duration.ofSeconds; - import com.google.common.collect.ImmutableList; - import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; @@ -49,6 +44,10 @@ import java.util.List; import java.util.Map; +import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; +import static java.time.Duration.ofSeconds; + /** * Default decorator for use with PageFactory. Will decorate 1) all of the * WebElement fields and 2) List of WebElement that have 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 23445fcf8..512c978cc 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -16,10 +16,6 @@ package io.appium.java_client.pagefactory; -import static java.lang.Integer.signum; -import static java.util.Arrays.asList; -import static java.util.Optional.ofNullable; - import io.appium.java_client.pagefactory.bys.ContentMappedBy; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.bys.builder.AppiumByBuilder; @@ -46,6 +42,10 @@ import java.util.Map; import java.util.Optional; +import static java.lang.Integer.signum; +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; + public class DefaultElementByBuilder extends AppiumByBuilder { private static final String PRIORITY = "priority"; diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java index 35a703e8a..efe7bd95f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java @@ -16,8 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; - import io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -25,6 +23,8 @@ import java.lang.reflect.Method; +import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; + /** * Intercepts requests to {@link WebElement}. */ diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java index 5dd385b29..fc7164533 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java @@ -16,8 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; - import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; @@ -25,6 +23,8 @@ import java.lang.reflect.Method; import java.util.List; +import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; + /** * Intercepts requests to the list of {@link WebElement}. */ diff --git a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java index 4060e4fae..be25f1c31 100644 --- a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java +++ b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java @@ -16,13 +16,7 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.WidgetConstructorUtil.findConvenientConstructor; -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.WINDOWS; - import io.appium.java_client.pagefactory.bys.ContentType; -import io.appium.java_client.remote.AutomationName; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; @@ -30,6 +24,11 @@ import java.util.HashMap; import java.util.Map; +import static io.appium.java_client.pagefactory.WidgetConstructorUtil.findConvenientConstructor; +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.WINDOWS; + class OverrideWidgetReader { private static final Class EMPTY = Widget.class; private static final String HTML = "html"; diff --git a/src/main/java/io/appium/java_client/pagefactory/Widget.java b/src/main/java/io/appium/java_client/pagefactory/Widget.java index fe8a7f9e3..1b2fdaebe 100644 --- a/src/main/java/io/appium/java_client/pagefactory/Widget.java +++ b/src/main/java/io/appium/java_client/pagefactory/Widget.java @@ -16,8 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; - import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -27,6 +25,8 @@ import java.util.List; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; + /** * It is the Appium-specific extension of the Page Object design pattern. It allows user * to create objects which typify some element with nested sub-elements. Also it allows to diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java index 4f509598b..905844e4a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java @@ -16,10 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.OverrideWidgetReader.getDefaultOrHTMLWidgetClass; -import static io.appium.java_client.pagefactory.OverrideWidgetReader.getMobileNativeWidgetClass; -import static java.util.Optional.ofNullable; - import org.openqa.selenium.By; import java.lang.reflect.AnnotatedElement; @@ -28,6 +24,10 @@ import java.lang.reflect.Type; import java.util.List; +import static io.appium.java_client.pagefactory.OverrideWidgetReader.getDefaultOrHTMLWidgetClass; +import static io.appium.java_client.pagefactory.OverrideWidgetReader.getMobileNativeWidgetClass; +import static java.util.Optional.ofNullable; + public class WidgetByBuilder extends DefaultElementByBuilder { public WidgetByBuilder(String platform, String automation) { diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java index 747a9eadd..2c3296d35 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java @@ -16,9 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; - import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement; import io.appium.java_client.pagefactory.locator.CacheableLocator; @@ -34,6 +31,9 @@ import java.util.HashMap; import java.util.Map; +import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; + class WidgetInterceptor extends InterceptorOfASingleElement { private final Map> instantiationMap; diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java index 580de7fb2..04115aaa9 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java @@ -16,10 +16,6 @@ package io.appium.java_client.pagefactory; -import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; -import static java.util.Optional.ofNullable; - import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements; import io.appium.java_client.pagefactory.locator.CacheableLocator; @@ -34,6 +30,10 @@ import java.util.List; import java.util.Map; +import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; +import static java.util.Optional.ofNullable; + class WidgetListInterceptor extends InterceptorOfAListOfElements { private final Map> instantiationMap; diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java index 03be2f654..206c1dd08 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series * of {@link WindowsBy} tags diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java index 9ba86d6c3..2222d0312 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page Object to indicate an alternative mechanism for locating the * element or a list of elements. Used in conjunction with diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java index 9a1e2f3cd..31caf953d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindAll} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java index 583d0f5d5..b6518ba6a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindBys} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java index 5efd79322..2f7cda9f4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java @@ -16,13 +16,13 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindBy} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java index 605986de3..9120a41d6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page Object to indicate that lookup should use * a series of {@link WindowsBy} tags. diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java index 1f995bbe3..fe7edf9b9 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java @@ -16,16 +16,16 @@ package io.appium.java_client.pagefactory.bys; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; - import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebElement; +import javax.annotation.Nonnull; import java.util.List; import java.util.Map; -import javax.annotation.Nonnull; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; public class ContentMappedBy extends By { private final Map map; 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 423645881..330362140 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 @@ -16,12 +16,6 @@ package io.appium.java_client.pagefactory.bys.builder; -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; import org.openqa.selenium.support.pagefactory.AbstractAnnotations; import org.openqa.selenium.support.pagefactory.ByAll; @@ -38,6 +32,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +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; + /** * It is the basic handler of Appium-specific page object annotations * About the Page Object design pattern please read these documents: diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java index 59015ede7..243d49306 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java @@ -1,8 +1,5 @@ package io.appium.java_client.pagefactory.bys.builder; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.SearchContext; @@ -13,6 +10,9 @@ import java.util.Optional; import java.util.function.Function; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Mechanism used to locate elements within a document using a series of lookups. This class will * find all DOM elements that matches any of the locators in sequence, e.g. diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java index f8d2240cb..9a4196f23 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java @@ -16,8 +16,6 @@ package io.appium.java_client.pagefactory.bys.builder; -import static com.google.common.base.Preconditions.checkNotNull; - import io.appium.java_client.functions.AppiumFunction; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; @@ -28,6 +26,8 @@ import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; + public class ByChained extends org.openqa.selenium.support.pagefactory.ByChained { private final By[] bys; 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 6212ffcf7..a353e0257 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 @@ -16,6 +16,12 @@ package io.appium.java_client.pagefactory.bys.builder; +import io.appium.java_client.AppiumBy; +import io.appium.java_client.MobileBy; +import io.appium.java_client.pagefactory.AndroidBy; +import io.appium.java_client.pagefactory.AndroidFindBy; +import org.openqa.selenium.By; + import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -23,13 +29,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.openqa.selenium.By; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.MobileBy; -import io.appium.java_client.pagefactory.AndroidBy; -import io.appium.java_client.pagefactory.AndroidFindBy; - enum Strategies { BYUIAUTOMATOR("uiAutomator") { @Override By getBy(Annotation annotation) { diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java index c388f47d2..94a2241c6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series * of {@link io.appium.java_client.pagefactory.iOSXCUITBy} tags. 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 5194e4094..dbc6d23c0 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + @Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(iOSXCUITFindBySet.class) public @interface iOSXCUITFindBy { diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java index 0bb769ea7..240efa73d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.iOSXCUITFindAll} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java index 2b5fc28de..fcc1a9e87 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java @@ -1,12 +1,12 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.iOSXCUITFindBys} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java index f1fea5a89..ce7464d2a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java @@ -16,13 +16,13 @@ package io.appium.java_client.pagefactory; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - @Retention(RUNTIME) @Target({FIELD, TYPE}) public @interface iOSXCUITFindBySet { /** diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java index 78a0eba3a..ec8424569 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java @@ -16,14 +16,14 @@ package io.appium.java_client.pagefactory; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Used to mark a field on a Page Object to indicate that lookup should use * a series of {@link io.appium.java_client.pagefactory.iOSXCUITBy} tags. diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 8aa5f7634..e6bb6f6d6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -16,11 +16,6 @@ package io.appium.java_client.pagefactory.utils; -import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; -import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; - import io.appium.java_client.HasBrowserCheck; import io.appium.java_client.pagefactory.bys.ContentType; import org.openqa.selenium.ContextAware; @@ -29,6 +24,11 @@ import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.WrapsElement; +import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; +import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; + public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; 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 43ce9fc01..b3acea991 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -16,17 +16,11 @@ package io.appium.java_client.remote; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.throwIfUnchecked; -import static java.util.Optional.ofNullable; -import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; - import com.google.common.base.Supplier; import com.google.common.base.Throwables; - import com.google.common.net.HttpHeaders; -import io.appium.java_client.AppiumUserAgentFilter; import io.appium.java_client.AppiumClientConfig; +import io.appium.java_client.AppiumUserAgentFilter; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; @@ -56,6 +50,11 @@ import java.util.Optional; import java.util.UUID; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Throwables.throwIfUnchecked; +import static java.util.Optional.ofNullable; +import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; + public class AppiumCommandExecutor extends HttpCommandExecutor { // https://github.com/appium/appium-base-driver/pull/400 private static final String IDEMPOTENCY_KEY_HEADER = "X-Idempotency-Key"; 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 cd2d6991c..1fc6943a3 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java +++ b/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java @@ -16,6 +16,10 @@ package io.appium.java_client.remote; +import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; + +import java.util.Map; + import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_ATTRIBUTE; import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_LOCATION; import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW; @@ -26,10 +30,6 @@ import static org.openqa.selenium.remote.DriverCommand.SET_TIMEOUT; import static org.openqa.selenium.remote.DriverCommand.SUBMIT_ELEMENT; -import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; - -import java.util.Map; - public class AppiumW3CHttpCommandCodec extends W3CHttpCommandCodec { /** * This class overrides the built-in Selenium W3C commands codec, diff --git a/src/main/java/io/appium/java_client/remote/options/SupportsBrowserVersionOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsBrowserVersionOption.java index 4eece6cdb..0e940b5d6 100644 --- a/src/main/java/io/appium/java_client/remote/options/SupportsBrowserVersionOption.java +++ b/src/main/java/io/appium/java_client/remote/options/SupportsBrowserVersionOption.java @@ -18,8 +18,6 @@ import org.openqa.selenium.Capabilities; -import java.util.Optional; - public interface SupportsBrowserVersionOption> extends Capabilities, CanSetCapability { String BROWSER_VERSION_OPTION = "browserVersion"; 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 127cc29a9..308a9b05a 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java @@ -16,13 +16,13 @@ package io.appium.java_client.screenrecording; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public abstract class BaseScreenRecordingOptions> { private ScreenRecordingUploadOptions uploadOptions; diff --git a/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java index 206cc1a6c..43dff24cc 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java @@ -16,14 +16,14 @@ package io.appium.java_client.screenrecording; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.time.Duration; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public abstract class BaseStartScreenRecordingOptions> extends BaseScreenRecordingOptions> { private Boolean forceRestart; 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 cea74c04c..9743edb0a 100644 --- a/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java +++ b/src/main/java/io/appium/java_client/screenrecording/CanRecordScreen.java @@ -16,14 +16,14 @@ package io.appium.java_client.screenrecording; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; + import static io.appium.java_client.MobileCommand.START_RECORDING_SCREEN; import static io.appium.java_client.MobileCommand.STOP_RECORDING_SCREEN; import static io.appium.java_client.MobileCommand.startRecordingScreenCommand; import static io.appium.java_client.MobileCommand.stopRecordingScreenCommand; -import io.appium.java_client.CommandExecutionHelper; -import io.appium.java_client.ExecutesMethod; - public interface CanRecordScreen extends ExecutesMethod { /** 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 424bfeddd..62509b4eb 100644 --- a/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java @@ -16,13 +16,13 @@ package io.appium.java_client.screenrecording; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import com.google.common.collect.ImmutableMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class ScreenRecordingUploadOptions { private String remotePath; private String user; diff --git a/src/main/java/io/appium/java_client/serverevents/ServerEvents.java b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java index 901241ce5..283aa3b66 100644 --- a/src/main/java/io/appium/java_client/serverevents/ServerEvents.java +++ b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java @@ -1,10 +1,11 @@ package io.appium.java_client.serverevents; +import lombok.Data; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import lombok.Data; @Data public class ServerEvents { diff --git a/src/main/java/io/appium/java_client/serverevents/TimedEvent.java b/src/main/java/io/appium/java_client/serverevents/TimedEvent.java index dca5f1218..999ecbd39 100644 --- a/src/main/java/io/appium/java_client/serverevents/TimedEvent.java +++ b/src/main/java/io/appium/java_client/serverevents/TimedEvent.java @@ -1,8 +1,9 @@ package io.appium.java_client.serverevents; -import java.util.List; import lombok.Data; +import java.util.List; + @Data public class TimedEvent { public final String name; 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 d4cedc033..62d2a98ce 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 @@ -16,16 +16,9 @@ package io.appium.java_client.service.local; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; -import static org.slf4j.event.Level.DEBUG; -import static org.slf4j.event.Level.INFO; - import com.google.common.annotations.VisibleForTesting; - import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; - import org.openqa.selenium.net.UrlChecker; import org.openqa.selenium.os.CommandLine; import org.openqa.selenium.remote.service.DriverService; @@ -33,13 +26,13 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; import java.time.Duration; import java.util.List; @@ -52,7 +45,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.INFO; public final class AppiumDriverLocalService extends DriverService { 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 e0ce11520..1e6885c48 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 @@ -16,12 +16,7 @@ package io.appium.java_client.service.local; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; - import com.google.common.collect.ImmutableList; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.appium.java_client.remote.AndroidMobileCapabilityType; @@ -29,7 +24,6 @@ import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.appium.java_client.service.local.flags.ServerArgument; - import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -44,7 +38,6 @@ import javax.annotation.Nullable; import java.io.File; import java.io.IOException; - import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; @@ -57,6 +50,10 @@ import java.util.Set; import java.util.function.Function; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; + public final class AppiumServiceBuilder extends DriverService.Builder { diff --git a/src/main/java/io/appium/java_client/touch/LongPressOptions.java b/src/main/java/io/appium/java_client/touch/LongPressOptions.java index 198f476b5..ac8600b49 100644 --- a/src/main/java/io/appium/java_client/touch/LongPressOptions.java +++ b/src/main/java/io/appium/java_client/touch/LongPressOptions.java @@ -16,15 +16,15 @@ package io.appium.java_client.touch; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import io.appium.java_client.touch.offset.AbstractOptionCombinedWithPosition; import java.time.Duration; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class LongPressOptions extends AbstractOptionCombinedWithPosition { protected Duration duration = null; diff --git a/src/main/java/io/appium/java_client/touch/TapOptions.java b/src/main/java/io/appium/java_client/touch/TapOptions.java index 7c4a1e1a0..620df8286 100644 --- a/src/main/java/io/appium/java_client/touch/TapOptions.java +++ b/src/main/java/io/appium/java_client/touch/TapOptions.java @@ -16,13 +16,13 @@ package io.appium.java_client.touch; -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Optional.ofNullable; - import io.appium.java_client.touch.offset.AbstractOptionCombinedWithPosition; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Optional.ofNullable; + public class TapOptions extends AbstractOptionCombinedWithPosition { private Integer tapsCount = null; diff --git a/src/main/java/io/appium/java_client/touch/WaitOptions.java b/src/main/java/io/appium/java_client/touch/WaitOptions.java index 422f0b052..81aedd674 100644 --- a/src/main/java/io/appium/java_client/touch/WaitOptions.java +++ b/src/main/java/io/appium/java_client/touch/WaitOptions.java @@ -16,13 +16,13 @@ package io.appium.java_client.touch; +import java.time.Duration; +import java.util.Map; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.time.Duration.ofMillis; -import java.time.Duration; -import java.util.Map; - public class WaitOptions extends ActionOptions { protected Duration duration = ofMillis(0); diff --git a/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java index a497ab4a8..d10a05682 100644 --- a/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java +++ b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java @@ -1,11 +1,11 @@ package io.appium.java_client.touch.offset; -import static java.util.Optional.ofNullable; - import io.appium.java_client.touch.ActionOptions; import java.util.Map; +import static java.util.Optional.ofNullable; + public abstract class AbstractOptionCombinedWithPosition> extends ActionOptions> { private ActionOptions positionOption; 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 775b067d1..05b4684a3 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 @@ -1,9 +1,5 @@ package io.appium.java_client.touch.offset; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.Optional.ofNullable; - import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.RemoteWebElement; @@ -11,6 +7,10 @@ import java.util.HashMap; import java.util.Map; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + public class ElementOption extends PointOption { private String elementId; diff --git a/src/main/java/io/appium/java_client/touch/offset/PointOption.java b/src/main/java/io/appium/java_client/touch/offset/PointOption.java index 1cccd2486..9bad51609 100644 --- a/src/main/java/io/appium/java_client/touch/offset/PointOption.java +++ b/src/main/java/io/appium/java_client/touch/offset/PointOption.java @@ -1,12 +1,12 @@ package io.appium.java_client.touch.offset; -import static java.util.Optional.ofNullable; - import io.appium.java_client.touch.ActionOptions; import org.openqa.selenium.Point; import java.util.Map; +import static java.util.Optional.ofNullable; + public class PointOption> extends ActionOptions { protected Point coordinates; diff --git a/src/main/java/io/appium/java_client/windows/options/SupportsAppArgumentsOption.java b/src/main/java/io/appium/java_client/windows/options/SupportsAppArgumentsOption.java index e5a8f55c3..1b4465a88 100644 --- a/src/main/java/io/appium/java_client/windows/options/SupportsAppArgumentsOption.java +++ b/src/main/java/io/appium/java_client/windows/options/SupportsAppArgumentsOption.java @@ -20,7 +20,6 @@ import io.appium.java_client.remote.options.CanSetCapability; import org.openqa.selenium.Capabilities; -import java.net.URL; import java.util.Optional; public interface SupportsAppArgumentsOption> extends 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 6a3148aa2..99f16d8d3 100644 --- a/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java +++ b/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java @@ -16,20 +16,19 @@ package io.appium.java_client.ws; -import java.net.URI; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Consumer; - -import javax.annotation.Nullable; - 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; +import javax.annotation.Nullable; +import java.net.URI; +import java.time.Duration; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + public class StringWebSocketClient implements WebSocket.Listener, CanHandleMessages, CanHandleErrors, CanHandleConnects, CanHandleDisconnects { private final List> messageHandlers = new CopyOnWriteArrayList<>(); diff --git a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java index 38fa40b93..1a0cd129e 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java @@ -1,11 +1,5 @@ package io.appium.java_client.android; -import static io.appium.java_client.TestUtils.getCenter; -import static io.appium.java_client.touch.WaitOptions.waitOptions; -import static io.appium.java_client.touch.offset.ElementOption.element; -import static java.time.Duration.ofSeconds; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - import io.appium.java_client.AppiumBy; import io.appium.java_client.functions.ActionSupplier; import io.appium.java_client.touch.offset.ElementOption; @@ -16,6 +10,12 @@ import java.util.List; +import static io.appium.java_client.TestUtils.getCenter; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { private final ActionSupplier horizontalSwipe = () -> { diff --git a/src/test/java/io/appium/java_client/android/AndroidActivityTest.java b/src/test/java/io/appium/java_client/android/AndroidActivityTest.java index 86377ac11..149a393c4 100644 --- a/src/test/java/io/appium/java_client/android/AndroidActivityTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidActivityTest.java @@ -16,13 +16,13 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; - import io.appium.java_client.android.nativekey.AndroidKey; import io.appium.java_client.android.nativekey.KeyEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class AndroidActivityTest extends BaseAndroidTest { @BeforeEach public void setUp() { diff --git a/src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java b/src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java index 9aaeb88b3..e3fefd9b0 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java @@ -16,10 +16,10 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + public class AndroidAppStringsTest extends BaseAndroidTest { @Test public void getAppStrings() { diff --git a/src/test/java/io/appium/java_client/android/AndroidConnectionTest.java b/src/test/java/io/appium/java_client/android/AndroidConnectionTest.java index 88e1c1d6e..4c8f03cd4 100644 --- a/src/test/java/io/appium/java_client/android/AndroidConnectionTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidConnectionTest.java @@ -16,15 +16,15 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.android.connection.ConnectionState; import io.appium.java_client.android.connection.ConnectionStateBuilder; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + @TestMethodOrder(MethodOrderer.MethodName.class) public class AndroidConnectionTest extends BaseAndroidTest { diff --git a/src/test/java/io/appium/java_client/android/AndroidContextTest.java b/src/test/java/io/appium/java_client/android/AndroidContextTest.java index ed832f9e3..53a90f1dd 100644 --- a/src/test/java/io/appium/java_client/android/AndroidContextTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidContextTest.java @@ -16,13 +16,13 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - import io.appium.java_client.NoSuchContextException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class AndroidContextTest extends BaseAndroidTest { @BeforeAll public static void beforeClass2() throws Exception { diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index f2e502781..aa2434d50 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -16,16 +16,6 @@ package io.appium.java_client.android; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.lessThan; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - import io.appium.java_client.appmanagement.ApplicationState; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; @@ -39,6 +29,16 @@ import java.util.Base64; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + public class AndroidDriverTest extends BaseAndroidTest { @Test diff --git a/src/test/java/io/appium/java_client/android/AndroidElementTest.java b/src/test/java/io/appium/java_client/android/AndroidElementTest.java index 8ce9fd2fc..acb4d4dd2 100644 --- a/src/test/java/io/appium/java_client/android/AndroidElementTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidElementTest.java @@ -16,10 +16,6 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,6 +23,10 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.RemoteWebElement; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class AndroidElementTest extends BaseAndroidTest { @BeforeEach public void setup() { diff --git a/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java b/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java index 3b915eae1..79a0d8870 100644 --- a/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java @@ -1,12 +1,5 @@ package io.appium.java_client.android; -import static java.time.Duration.ofMillis; -import static java.time.Duration.ofSeconds; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.StringContains.containsString; -import static org.junit.jupiter.api.Assertions.assertThrows; - import io.appium.java_client.functions.AppiumFunction; import io.appium.java_client.functions.ExpectedCondition; import org.junit.jupiter.api.BeforeAll; @@ -25,6 +18,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofSeconds; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class AndroidFunctionTest extends BaseAndroidTest { private final AppiumFunction> searchingFunction = input -> { diff --git a/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java b/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java index f1045c8cf..e7a40383c 100644 --- a/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java @@ -1,7 +1,5 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertTrue; - import org.apache.commons.lang3.time.DurationFormatUtils; import org.junit.jupiter.api.Test; @@ -9,6 +7,8 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class AndroidLogcatListenerTest extends BaseAndroidTest { @Test diff --git a/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java b/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java index 63510fc26..b0ec3a1e7 100644 --- a/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java @@ -1,16 +1,16 @@ package io.appium.java_client.android; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.emptyString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebDriverException; import java.time.Duration; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + public class AndroidScreenRecordTest extends BaseAndroidTest { @BeforeEach diff --git a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java index 262b50c61..c9b06c49f 100644 --- a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java @@ -16,16 +16,16 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class AndroidSearchingTest extends BaseAndroidTest { @BeforeEach diff --git a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java index 958da3140..f3c9c0394 100644 --- a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java @@ -1,19 +1,8 @@ package io.appium.java_client.android; -import static io.appium.java_client.TestUtils.getCenter; -import static io.appium.java_client.touch.LongPressOptions.longPressOptions; -import static io.appium.java_client.touch.TapOptions.tapOptions; -import static io.appium.java_client.touch.WaitOptions.waitOptions; -import static io.appium.java_client.touch.offset.ElementOption.element; -import static io.appium.java_client.touch.offset.PointOption.point; -import static java.time.Duration.ofSeconds; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - import io.appium.java_client.AppiumBy; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; @@ -22,6 +11,16 @@ import java.util.List; +import static io.appium.java_client.TestUtils.getCenter; +import static io.appium.java_client.touch.LongPressOptions.longPressOptions; +import static io.appium.java_client.touch.TapOptions.tapOptions; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static io.appium.java_client.touch.offset.PointOption.point; +import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + public class AndroidTouchTest extends BaseAndroidTest { @BeforeEach 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 3e75fe6f1..d5eb6840f 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -18,7 +18,6 @@ import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; - import io.appium.java_client.service.local.AppiumServiceBuilder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; diff --git a/src/test/java/io/appium/java_client/android/BatteryTest.java b/src/test/java/io/appium/java_client/android/BatteryTest.java index 619fafac1..ae9fc9e26 100644 --- a/src/test/java/io/appium/java_client/android/BatteryTest.java +++ b/src/test/java/io/appium/java_client/android/BatteryTest.java @@ -16,13 +16,13 @@ package io.appium.java_client.android; +import org.junit.jupiter.api.Test; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import org.junit.jupiter.api.Test; - public class BatteryTest extends BaseAndroidTest { @Test public void veryGettingBatteryInformation() { diff --git a/src/test/java/io/appium/java_client/android/ClipboardTest.java b/src/test/java/io/appium/java_client/android/ClipboardTest.java index cf9ee8581..353d9a579 100644 --- a/src/test/java/io/appium/java_client/android/ClipboardTest.java +++ b/src/test/java/io/appium/java_client/android/ClipboardTest.java @@ -16,11 +16,11 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class ClipboardTest extends BaseAndroidTest { @BeforeEach public void setUp() { diff --git a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java index fc1a138c6..259dd0d4d 100644 --- a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java +++ b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java @@ -26,7 +26,6 @@ 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; diff --git a/src/test/java/io/appium/java_client/android/FingerPrintTest.java b/src/test/java/io/appium/java_client/android/FingerPrintTest.java index a32c482fe..4f1e17551 100644 --- a/src/test/java/io/appium/java_client/android/FingerPrintTest.java +++ b/src/test/java/io/appium/java_client/android/FingerPrintTest.java @@ -16,8 +16,6 @@ package io.appium.java_client.android; -import static io.appium.java_client.AppiumBy.androidUIAutomator; - import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; import org.junit.jupiter.api.AfterAll; @@ -32,6 +30,8 @@ import java.time.Duration; +import static io.appium.java_client.AppiumBy.androidUIAutomator; + public class FingerPrintTest { private static AppiumDriverLocalService service; private static AndroidDriver driver; diff --git a/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java b/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java index e49463b87..3632bfbf8 100644 --- a/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java +++ b/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java @@ -16,14 +16,6 @@ package io.appium.java_client.android; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.core.Is.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.Base64; - import io.appium.java_client.imagecomparison.FeatureDetector; import io.appium.java_client.imagecomparison.FeaturesMatchingOptions; import io.appium.java_client.imagecomparison.FeaturesMatchingResult; @@ -35,6 +27,14 @@ import org.junit.jupiter.api.Test; import org.openqa.selenium.OutputType; +import java.util.Base64; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class ImagesComparisonTest extends BaseAndroidTest { @Test 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 a25e6a0bf..ba4888821 100644 --- a/src/test/java/io/appium/java_client/android/IntentTest.java +++ b/src/test/java/io/appium/java_client/android/IntentTest.java @@ -1,9 +1,5 @@ package io.appium.java_client.android; -import static io.appium.java_client.TestResources.intentExampleApk; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; import org.junit.jupiter.api.AfterAll; @@ -13,6 +9,10 @@ import java.util.function.Predicate; +import static io.appium.java_client.TestResources.intentExampleApk; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class IntentTest { private static AppiumDriverLocalService service; protected static AndroidDriver driver; diff --git a/src/test/java/io/appium/java_client/android/KeyCodeTest.java b/src/test/java/io/appium/java_client/android/KeyCodeTest.java index 7a3ae47e3..7c50f4ceb 100644 --- a/src/test/java/io/appium/java_client/android/KeyCodeTest.java +++ b/src/test/java/io/appium/java_client/android/KeyCodeTest.java @@ -16,10 +16,6 @@ package io.appium.java_client.android; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.android.nativekey.AndroidKey; import io.appium.java_client.android.nativekey.KeyEvent; import io.appium.java_client.android.nativekey.KeyEventFlag; @@ -28,6 +24,10 @@ import org.junit.jupiter.api.Test; import org.openqa.selenium.By; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class KeyCodeTest extends BaseAndroidTest { private static final By PRESS_RESULT_VIEW = By.id("io.appium.android.apis:id/text"); diff --git a/src/test/java/io/appium/java_client/android/LogEventTest.java b/src/test/java/io/appium/java_client/android/LogEventTest.java index a473fbe32..ea820fa94 100644 --- a/src/test/java/io/appium/java_client/android/LogEventTest.java +++ b/src/test/java/io/appium/java_client/android/LogEventTest.java @@ -16,16 +16,16 @@ package io.appium.java_client.android; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.serverevents.CommandEvent; import io.appium.java_client.serverevents.CustomEvent; -import io.appium.java_client.serverevents.TimedEvent; import io.appium.java_client.serverevents.ServerEvents; +import io.appium.java_client.serverevents.TimedEvent; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class LogEventTest extends BaseAndroidTest { @Test diff --git a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java index e29e92c45..bfb4e576a 100644 --- a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java +++ b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java @@ -1,8 +1,5 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.openqa.selenium.By.xpath; - import org.junit.jupiter.api.Test; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; @@ -10,6 +7,9 @@ import java.time.Duration; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.openqa.selenium.By.xpath; + public class OpenNotificationsTest extends BaseAndroidTest { @Test public void openNotification() { diff --git a/src/test/java/io/appium/java_client/android/UIAutomator2Test.java b/src/test/java/io/appium/java_client/android/UIAutomator2Test.java index 6c2bf0857..0d9da052d 100644 --- a/src/test/java/io/appium/java_client/android/UIAutomator2Test.java +++ b/src/test/java/io/appium/java_client/android/UIAutomator2Test.java @@ -1,8 +1,5 @@ package io.appium.java_client.android; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; @@ -15,6 +12,9 @@ import java.time.Duration; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class UIAutomator2Test extends BaseAndroidTest { @AfterEach diff --git a/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java b/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java index 4be313211..5a50dc58a 100644 --- a/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java +++ b/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java @@ -17,8 +17,6 @@ package io.appium.java_client.events.stubs; import com.google.common.collect.ImmutableList; -import java.util.ArrayList; -import java.util.List; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.OutputType; @@ -27,6 +25,9 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; +import java.util.ArrayList; +import java.util.List; + public class StubWebElement implements WebElement { public StubWebElement() { } diff --git a/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java index 10e33a1ee..7b1eec03a 100644 --- a/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java +++ b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java @@ -8,7 +8,8 @@ import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class AppiumUserAgentFilterTest { @Test diff --git a/src/test/java/io/appium/java_client/internal/ConfigTest.java b/src/test/java/io/appium/java_client/internal/ConfigTest.java index 67518eca7..46651eac6 100644 --- a/src/test/java/io/appium/java_client/internal/ConfigTest.java +++ b/src/test/java/io/appium/java_client/internal/ConfigTest.java @@ -1,19 +1,15 @@ package io.appium.java_client.internal; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.AppiumUserAgentFilter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; -import java.util.stream.Stream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class ConfigTest { private static final String SELENIUM_EXISTING_KEY = "selenium.version"; diff --git a/src/test/java/io/appium/java_client/internal/DirectConnectTest.java b/src/test/java/io/appium/java_client/internal/DirectConnectTest.java index 93b345474..8255c3c5d 100644 --- a/src/test/java/io/appium/java_client/internal/DirectConnectTest.java +++ b/src/test/java/io/appium/java_client/internal/DirectConnectTest.java @@ -7,7 +7,10 @@ import java.util.HashMap; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; class DirectConnectTest { diff --git a/src/test/java/io/appium/java_client/ios/ClipboardTest.java b/src/test/java/io/appium/java_client/ios/ClipboardTest.java index af6b8a51d..b8ed26a44 100644 --- a/src/test/java/io/appium/java_client/ios/ClipboardTest.java +++ b/src/test/java/io/appium/java_client/ios/ClipboardTest.java @@ -16,10 +16,10 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class ClipboardTest extends AppIOSTest { @Test public void verifySetAndGetClipboardText() { 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 3c731e007..7e1fb6528 100644 --- a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java @@ -16,10 +16,6 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; - import io.appium.java_client.AppiumBy; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.AfterEach; @@ -33,6 +29,10 @@ import java.time.Duration; import java.util.function.Supplier; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; + @TestMethodOrder(MethodOrderer.MethodName.class) public class IOSAlertTest extends AppIOSTest { diff --git a/src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java b/src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java index 6080737f8..dc552dc91 100644 --- a/src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java @@ -16,10 +16,10 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + public class IOSAppStringsTest extends AppIOSTest { @Test public void getAppStrings() { diff --git a/src/test/java/io/appium/java_client/ios/IOSContextTest.java b/src/test/java/io/appium/java_client/ios/IOSContextTest.java index 5c51aa677..f2ac548b1 100644 --- a/src/test/java/io/appium/java_client/ios/IOSContextTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSContextTest.java @@ -16,14 +16,14 @@ package io.appium.java_client.ios; +import io.appium.java_client.NoSuchContextException; +import org.junit.jupiter.api.Test; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import io.appium.java_client.NoSuchContextException; -import org.junit.jupiter.api.Test; - public class IOSContextTest extends BaseIOSWebViewTest { @Test public void testGetContext() { 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 bcfd41d7d..ba62dcf34 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -16,15 +16,6 @@ package io.appium.java_client.ios; -import static io.appium.java_client.TestUtils.waitUntilTrue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.remote.HideKeyboardStrategy; @@ -43,6 +34,15 @@ import java.time.Duration; +import static io.appium.java_client.TestUtils.waitUntilTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + public class IOSDriverTest extends AppIOSTest { @BeforeEach public void setupEach() { diff --git a/src/test/java/io/appium/java_client/ios/IOSElementTest.java b/src/test/java/io/appium/java_client/ios/IOSElementTest.java index 18db02e2b..5d3d943a5 100644 --- a/src/test/java/io/appium/java_client/ios/IOSElementTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSElementTest.java @@ -1,10 +1,6 @@ package io.appium.java_client.ios; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.junit.jupiter.api.Assertions.assertEquals; - +import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; @@ -12,10 +8,13 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; -import io.appium.java_client.AppiumBy; - import java.time.Duration; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.jupiter.api.Assertions.assertEquals; + @TestMethodOrder(MethodOrderer.MethodName.class) public class IOSElementTest extends AppIOSTest { diff --git a/src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java b/src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java index e4d27f327..922dbb7cd 100644 --- a/src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java @@ -1,9 +1,5 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; - import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; @@ -11,6 +7,10 @@ import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertTrue; + public class IOSNativeWebTapSettingTest extends BaseSafariTest { @Test diff --git a/src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java b/src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java index cf88b7b4a..170ea9b6a 100644 --- a/src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java @@ -1,14 +1,14 @@ package io.appium.java_client.ios; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import org.junit.jupiter.api.Test; - -import java.time.Duration; - public class IOSScreenRecordTest extends AppIOSTest { @Test diff --git a/src/test/java/io/appium/java_client/ios/IOSSearchingTest.java b/src/test/java/io/appium/java_client/ios/IOSSearchingTest.java index 30213f480..5af445b0b 100644 --- a/src/test/java/io/appium/java_client/ios/IOSSearchingTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSSearchingTest.java @@ -16,11 +16,10 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - +import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.Test; -import io.appium.java_client.AppiumBy; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class IOSSearchingTest extends AppIOSTest { diff --git a/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java b/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java index d5e2b377f..34429e5df 100644 --- a/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java @@ -1,7 +1,5 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertTrue; - import org.apache.commons.lang3.time.DurationFormatUtils; import org.junit.jupiter.api.Test; @@ -9,6 +7,8 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class IOSSyslogListenerTest extends AppIOSTest { @Test 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 46a4d0474..38e66359f 100644 --- a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java @@ -1,14 +1,5 @@ package io.appium.java_client.ios; -import static io.appium.java_client.ios.touch.IOSPressOptions.iosPressOptions; -import static io.appium.java_client.touch.TapOptions.tapOptions; -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 org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; - import io.appium.java_client.AppiumBy; import io.appium.java_client.MultiTouchAction; import org.junit.jupiter.api.MethodOrderer; @@ -20,6 +11,15 @@ import java.time.Duration; +import static io.appium.java_client.ios.touch.IOSPressOptions.iosPressOptions; +import static io.appium.java_client.touch.TapOptions.tapOptions; +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 org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; + @TestMethodOrder(MethodOrderer.MethodName.class) public class IOSTouchTest extends AppIOSTest { diff --git a/src/test/java/io/appium/java_client/ios/IOSWebViewTest.java b/src/test/java/io/appium/java_client/ios/IOSWebViewTest.java index d03f1acdc..60943342e 100644 --- a/src/test/java/io/appium/java_client/ios/IOSWebViewTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSWebViewTest.java @@ -1,15 +1,15 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; - import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.assertTrue; + public class IOSWebViewTest extends BaseIOSWebViewTest { private static final Duration LOOKUP_TIMEOUT = Duration.ofSeconds(30); diff --git a/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java b/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java index d724b4ecb..8534f8f35 100644 --- a/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java +++ b/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java @@ -16,14 +16,6 @@ package io.appium.java_client.ios; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.core.Is.is; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import java.util.Base64; - import io.appium.java_client.imagecomparison.FeatureDetector; import io.appium.java_client.imagecomparison.FeaturesMatchingOptions; import io.appium.java_client.imagecomparison.FeaturesMatchingResult; @@ -35,6 +27,14 @@ import org.junit.jupiter.api.Test; import org.openqa.selenium.OutputType; +import java.util.Base64; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class ImagesComparisonTest extends AppIOSTest { @Test diff --git a/src/test/java/io/appium/java_client/ios/RotationTest.java b/src/test/java/io/appium/java_client/ios/RotationTest.java index 21d177fff..1d741845f 100644 --- a/src/test/java/io/appium/java_client/ios/RotationTest.java +++ b/src/test/java/io/appium/java_client/ios/RotationTest.java @@ -16,12 +16,12 @@ package io.appium.java_client.ios; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.DeviceRotation; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class RotationTest extends AppIOSTest { @AfterEach public void afterMethod() { 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 9fc2bfa4b..647b93e2d 100644 --- a/src/test/java/io/appium/java_client/ios/SettingTest.java +++ b/src/test/java/io/appium/java_client/ios/SettingTest.java @@ -20,12 +20,12 @@ import io.appium.java_client.Setting; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.util.EnumMap; import java.util.HashMap; import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class SettingTest extends AppIOSTest { @Test public void testSetShouldUseCompactResponses() { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java index 433aa2a1a..a135c30e3 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java @@ -16,16 +16,7 @@ package io.appium.java_client.pagefactory_tests; -import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; -import static java.time.Duration.ofSeconds; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.android.BaseAndroidTest; - import io.appium.java_client.pagefactory.AndroidBy; import io.appium.java_client.pagefactory.AndroidFindAll; import io.appium.java_client.pagefactory.AndroidFindBy; @@ -45,6 +36,14 @@ import java.util.ArrayList; import java.util.List; +import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; +import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class AndroidPageObjectTest extends BaseAndroidTest { private boolean populated = false; 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 c2cb1b6f4..aeefba86e 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,13 +16,6 @@ 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; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.pagefactory.AndroidFindBy; import io.appium.java_client.pagefactory.AppiumFieldDecorator; @@ -39,6 +32,13 @@ import java.util.List; +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; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + public class DesktopBrowserCompatibilityTest { @HowToUseLocators(iOSXCUITAutomation = ALL_POSSIBLE) diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java index dad9d7a18..824261c52 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java @@ -16,8 +16,6 @@ package io.appium.java_client.pagefactory_tests; -import static java.time.Duration.ofSeconds; - import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.pagefactory.AndroidFindBy; @@ -37,6 +35,8 @@ import java.util.List; +import static java.time.Duration.ofSeconds; + public class MobileBrowserCompatibilityTest { private WebDriver driver; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java index cfba2ba36..2fbe87f41 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java @@ -16,18 +16,6 @@ package io.appium.java_client.pagefactory_tests; -import static io.appium.java_client.pagefactory.AppiumFieldDecorator.DEFAULT_WAITING_TIMEOUT; -import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; -import static java.lang.Math.abs; -import static java.lang.String.format; -import static java.lang.System.currentTimeMillis; -import static java.time.Duration.ofSeconds; -import static java.time.temporal.ChronoUnit.SECONDS; -import static org.apache.commons.lang3.time.DurationFormatUtils.formatDuration; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.lessThanOrEqualTo; -import static org.openqa.selenium.support.PageFactory.initElements; - import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.WithTimeout; import org.junit.jupiter.api.AfterEach; @@ -43,6 +31,18 @@ import java.time.Duration; import java.util.List; +import static io.appium.java_client.pagefactory.AppiumFieldDecorator.DEFAULT_WAITING_TIMEOUT; +import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; +import static java.lang.Math.abs; +import static java.lang.String.format; +import static java.lang.System.currentTimeMillis; +import static java.time.Duration.ofSeconds; +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.apache.commons.lang3.time.DurationFormatUtils.formatDuration; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.openqa.selenium.support.PageFactory.initElements; + public class TimeoutTest { private static final long ACCEPTABLE_TIME_DIFF_MS = 1500; 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 418f35e71..9660da6a4 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 @@ -16,17 +16,6 @@ package io.appium.java_client.pagefactory_tests; -import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; -import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN; -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.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.ios.AppIOSTest; import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.HowToUseLocators; @@ -40,6 +29,17 @@ import java.util.List; +import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; +import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN; +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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + @TestMethodOrder(MethodOrderer.MethodName.class) public class XCUITModeTest extends AppIOSTest { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java index f71d1d8c4..6f7dbb1c9 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java @@ -1,13 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests; -import static com.google.common.collect.ImmutableList.of; -import static io.appium.java_client.remote.AutomationName.ANDROID_UIAUTOMATOR2; -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.WINDOWS; -import static org.apache.commons.lang3.StringUtils.EMPTY; - import io.appium.java_client.HasBrowserCheck; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; @@ -25,6 +17,14 @@ import java.util.Set; import java.util.concurrent.TimeUnit; +import static com.google.common.collect.ImmutableList.of; +import static io.appium.java_client.remote.AutomationName.ANDROID_UIAUTOMATOR2; +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.WINDOWS; +import static org.apache.commons.lang3.StringUtils.EMPTY; + public abstract class AbstractStubWebDriver implements WebDriver, HasBrowserCheck, diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java index e81022a57..0e6bc08b7 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java @@ -1,7 +1,6 @@ package io.appium.java_client.pagefactory_tests.widget.tests; import com.google.common.collect.ImmutableList; - import io.appium.java_client.pagefactory.Widget; import org.openqa.selenium.WebElement; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java index cffb170fd..43f0864ac 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java @@ -1,8 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.of; - import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.OutputType; @@ -15,6 +12,9 @@ import java.util.List; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.of; + public class StubWebElement implements WebElement, WrapsDriver { private final WebDriver driver; private final By by; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java index 0420d44f6..6e1f4ab2b 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java @@ -1,11 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests; -import static java.util.stream.Collectors.toList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.openqa.selenium.support.PageFactory.initElements; - import io.appium.java_client.pagefactory.AppiumFieldDecorator; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; @@ -13,6 +7,12 @@ import java.util.List; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsString; +import static org.openqa.selenium.support.PageFactory.initElements; + public abstract class WidgetTest { protected final AbstractApp app; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/android/AndroidWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/android/AndroidWidgetTest.java index a3eee515a..2e0f1e0ae 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/android/AndroidWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/android/AndroidWidgetTest.java @@ -1,16 +1,16 @@ package io.appium.java_client.pagefactory_tests.widget.tests.android; +import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; +import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; +import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; +import org.junit.jupiter.api.Test; + import static io.appium.java_client.AppiumBy.androidUIAutomator; import static io.appium.java_client.pagefactory_tests.widget.tests.android.AndroidApp.ANDROID_DEFAULT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.android.AndroidApp.ANDROID_EXTERNALLY_DEFINED_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.android.AnnotatedAndroidWidget.ANDROID_ROOT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget.ANDROID_SUB_WIDGET_LOCATOR; -import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; -import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; -import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; -import org.junit.jupiter.api.Test; - public class AndroidWidgetTest extends WidgetTest { public AndroidWidgetTest() { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java index 80e6eb1b3..100f0322c 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java @@ -1,11 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests.combined; -import static java.util.stream.Collectors.toList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.openqa.selenium.support.PageFactory.initElements; - import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.OverrideWidget; import io.appium.java_client.pagefactory_tests.widget.tests.AbstractApp; @@ -13,13 +7,20 @@ import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget; import io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget; -import java.util.List; -import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.openqa.selenium.WebDriver; +import java.util.List; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.openqa.selenium.support.PageFactory.initElements; + @SuppressWarnings({"unused", "unchecked"}) public class CombinedAppTest { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java index 342e37335..d54e3618c 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java @@ -1,11 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests.combined; -import static java.util.stream.Collectors.toList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.openqa.selenium.support.PageFactory.initElements; - import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.OverrideWidget; import io.appium.java_client.pagefactory_tests.widget.tests.AbstractApp; @@ -23,6 +17,12 @@ import java.util.List; import java.util.stream.Stream; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.openqa.selenium.support.PageFactory.initElements; + @SuppressWarnings({"unchecked", "unused"}) public class CombinedWidgetTest { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/ios/XCUITWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/ios/XCUITWidgetTest.java index 6a9bb0d53..edaa699f7 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/ios/XCUITWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/ios/XCUITWidgetTest.java @@ -1,16 +1,16 @@ package io.appium.java_client.pagefactory_tests.widget.tests.ios; +import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; +import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; +import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; +import org.junit.jupiter.api.Test; + import static io.appium.java_client.AppiumBy.iOSNsPredicateString; import static io.appium.java_client.pagefactory_tests.widget.tests.combined.DefaultIosXCUITWidget.XCUIT_SUB_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.ios.AnnotatedIosWidget.XCUIT_ROOT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.ios.IosApp.IOS_XCUIT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.ios.IosApp.XCUIT_EXTERNALLY_DEFINED_WIDGET_LOCATOR; -import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; -import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; -import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; -import org.junit.jupiter.api.Test; - public class XCUITWidgetTest extends WidgetTest { public XCUITWidgetTest() { diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java index d5990b7e5..023b24819 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java @@ -1,16 +1,16 @@ package io.appium.java_client.pagefactory_tests.widget.tests.windows; +import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; +import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; +import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; +import org.junit.jupiter.api.Test; + import static io.appium.java_client.MobileBy.windowsAutomation; import static io.appium.java_client.pagefactory_tests.widget.tests.windows.AnnotatedWindowsWidget.WINDOWS_ROOT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget.WINDOWS_SUB_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.windows.WindowsApp.WINDOWS_DEFAULT_WIDGET_LOCATOR; import static io.appium.java_client.pagefactory_tests.widget.tests.windows.WindowsApp.WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR; -import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; -import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; -import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; -import org.junit.jupiter.api.Test; - public class WindowsWidgetTest extends WidgetTest { public WindowsWidgetTest() { 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 ccdd117d2..923fa57b8 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,20 @@ package io.appium.java_client.service.local; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.android.options.UiAutomator2Options; +import io.github.bonigarcia.wdm.WebDriverManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + import static io.appium.java_client.TestResources.apiDemosApk; import static io.appium.java_client.TestUtils.getLocalIp4Address; import static io.appium.java_client.service.local.AppiumDriverLocalService.buildDefaultService; @@ -24,20 +39,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.google.common.collect.ImmutableMap; -import io.appium.java_client.android.options.UiAutomator2Options; -import io.github.bonigarcia.wdm.WebDriverManager; -import java.io.File; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - @SuppressWarnings("ResultOfMethodCallIgnored") class ServerBuilderTest { diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java index 55906d13f..36518f4ed 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java @@ -16,12 +16,6 @@ package io.appium.java_client.service.local; -import static io.appium.java_client.TestResources.apiDemosApk; -import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.remote.AutomationName; @@ -29,10 +23,17 @@ import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.github.bonigarcia.wdm.WebDriverManager; -import java.time.Duration; import org.junit.jupiter.api.Test; import org.openqa.selenium.Capabilities; +import java.time.Duration; + +import static io.appium.java_client.TestResources.apiDemosApk; +import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + class StartingAppLocallyAndroidTest { @Test diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java index 07bf8008a..6baf77701 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java @@ -16,12 +16,6 @@ package io.appium.java_client.service.local; -import static io.appium.java_client.TestResources.uiCatalogAppZip; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - import io.appium.java_client.ios.BaseIOSTest; import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.ios.options.XCUITestOptions; @@ -33,6 +27,12 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; +import static io.appium.java_client.TestResources.uiCatalogAppZip; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + class StartingAppLocallyIosTest { @Test void startingIOSAppWithCapabilitiesOnlyTest() { diff --git a/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java b/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java index f4fb55b42..8087da057 100644 --- a/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java +++ b/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java @@ -1,11 +1,11 @@ package io.appium.java_client.service.local; +import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; - class ThreadSafetyTest { private final AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); diff --git a/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java b/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java index f4ac4bec8..2d79fcfec 100644 --- a/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java +++ b/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java @@ -1,12 +1,12 @@ package io.appium.java_client.touch; -import static org.hamcrest.core.AllOf.allOf; -import static org.hamcrest.core.IsInstanceOf.instanceOf; - import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; +import static org.hamcrest.core.AllOf.allOf; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + public final class FailsWithMatcher extends TypeSafeMatcher { private final Matcher matcher; 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 b08249962..a68bc3fa6 100644 --- a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java +++ b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java @@ -1,5 +1,16 @@ package io.appium.java_client.touch; +import io.appium.java_client.touch.offset.ElementOption; +import io.appium.java_client.touch.offset.PointOption; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.Point; +import org.openqa.selenium.remote.RemoteWebElement; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static io.appium.java_client.touch.FailsWithMatcher.failsWith; import static io.appium.java_client.touch.LongPressOptions.longPressOptions; import static io.appium.java_client.touch.TapOptions.tapOptions; @@ -14,17 +25,6 @@ import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; -import io.appium.java_client.touch.offset.ElementOption; -import io.appium.java_client.touch.offset.PointOption; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.Point; -import org.openqa.selenium.remote.RemoteWebElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class TouchOptionsTests { private static final RemoteWebElement DUMMY_ELEMENT = new DummyElement(); From 2afe309a0834212e8a9c9b1525a9459036015eeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 16:50:27 +0300 Subject: [PATCH 025/314] build(deps): bump webdrivermanager from 5.3.0 to 5.3.1 (#1794) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.3.0 to 5.3.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-5.3.0...webdrivermanager-5.3.1) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 b64f8cebb..6db5a016d 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.0') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.1') { exclude group: 'org.seleniumhq.selenium' } testImplementation ('org.seleniumhq.selenium:selenium-chrome-driver') { From a3bf0f4a35475b1bbc8fdd9cba803e7c756bc8b5 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Sat, 19 Nov 2022 19:51:41 +0530 Subject: [PATCH 026/314] ci: automated artefact publish to maven central (#1803) * ci: automated artefact publish to maven central * ci: address review comments --- .github/workflows/publish.yml | 20 ++++++++++++++++++++ build.gradle | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..8f82b851b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,20 @@ +name: Publish package to the Maven Central Repository +on: + release: + types: [created] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + cache: 'gradle' + - name: Publish package + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + run: ./gradlew publish \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6db5a016d..a932f5b51 100644 --- a/build.gradle +++ b/build.gradle @@ -166,8 +166,8 @@ publishing { repositories { maven { credentials { - username "$ossrhUsername" - password "$ossrhPassword" + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") } def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/'" From 6a1429437a193aacbc19a63b4c588605bc8f9c23 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Mon, 21 Nov 2022 11:32:08 +0530 Subject: [PATCH 027/314] Release 8.2.1 and update release notes --- README.md | 24 ++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e2da9ca63..74ec4bfb0 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,30 @@ You could find much more code examples by checking client's [unit and integration tests](src/test/java/io/appium/java_client). ## Changelog +*8.2.1* +- **[ENHANCEMENTS]** + - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) + - Connect directly to Appium Hosts in Distributed Environments. [#1747](https://github.com/appium/java-client/pull/1747) + - use own User Agent. [#1779](https://github.com/appium/java-client/pull/1779) + - Add alternative proxy implementation. [#1790](https://github.com/appium/java-client/pull/1790) +- **[BUG FIX]** + - Enforce usage of Base64 compliant with RFC 4648 for all operations. [#1785](https://github.com/appium/java-client/pull/1785) + - Override getScreenshotAs to support the legacy base64 encoding. [#1787](https://github.com/appium/java-client/pull/1787) +- **[REFRACTOR]** + - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) + - JUnit5 test classes and methods are updated to have default package visibility. [#1755](https://github.com/appium/java-client/pull/1755) + - Verify if the PR title complies with conventional commits spec. [#1757](https://github.com/appium/java-client/pull/1757) + - Use Lombok in direct connect class. [#1789](https://github.com/appium/java-client/pull/1789) + - Update readme and remove obsolete documents. [#1792](https://github.com/appium/java-client/pull/1792) + - Remove unnecessary annotation. [#1791](https://github.com/appium/java-client/pull/1791) + - Force unified imports order. [#1793](https://github.com/appium/java-client/pull/1793) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.5.0. + - `org.owasp.dependencycheck` was updated to 7.3.0. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.1. + - `org.junit.jupiter:junit-jupiter` was updated to 5.9.1. + - `org.slf4j:slf4j-api` was updated to 2.0.3. + - `com.google.code.gson:gson` was updated to 2.10.0. *8.2.0* - **[ENHANCEMENTS]** - AppiumDriverLocalService can handle outputStreams. [#1709](https://github.com/appium/java-client/pull/1709) diff --git a/gradle.properties b/gradle.properties index 239efd600..250d8d3b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,4 @@ ossrhPassword=your-jira-password selenium.version=4.5.0 # Please increment the value in a release -appiumClient.version=8.2.0 +appiumClient.version=8.2.1 From 3d16a95a6bcd197730cb521ffbb9a2952ad5ee3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:37:39 +0300 Subject: [PATCH 028/314] build(deps): bump slf4j-api from 2.0.3 to 2.0.4 (#1804) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.3...v_2.0.4) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 a932f5b51..9ccdbfa40 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:2.0.3' + implementation 'org.slf4j:slf4j-api:2.0.4' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' From 26a6cdb62335e4842d03fa223c689c83596c3e25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:38:02 +0300 Subject: [PATCH 029/314] build(deps): bump org.owasp.dependencycheck from 7.3.0 to 7.3.2 (#1805) Bumps org.owasp.dependencycheck from 7.3.0 to 7.3.2. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 9ccdbfa40..9aa5a4b21 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.3.0' + id 'org.owasp.dependencycheck' version '7.3.2' id 'com.github.johnrengelman.shadow' version '7.1.2' } From bb12c03f812da6f1b39dbf399e84cba97f3b821a Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Thu, 24 Nov 2022 17:31:14 +0530 Subject: [PATCH 030/314] ci: fix automated maven repository publishing (#1807) --- .github/workflows/publish.yml | 2 ++ build.gradle | 3 +++ gradle.properties | 7 ------- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8f82b851b..8196bf5ff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,4 +17,6 @@ jobs: env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + PGP_SECRET: ${{ secrets.SIGNING_KEY }} + PGP_PASSPHRASE: ${{ secrets.SIGNING_PASSWORD }} run: ./gradlew publish \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9aa5a4b21..aac5e7189 100644 --- a/build.gradle +++ b/build.gradle @@ -177,6 +177,9 @@ publishing { } signing { + def signingKey = System.getenv("PGP_SECRET") + def signingPassword = System.getenv("PGP_PASSPHRASE") + useInMemoryPgpKeys(signingKey, signingPassword) sign publishing.publications.mavenJava } diff --git a/gradle.properties b/gradle.properties index 250d8d3b8..e23c7abf2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,5 @@ org.gradle.daemon=true -signing.keyId=YourKeyId -signing.password=YourPublicKeyPassword -signing.secretKeyRingFile=PathToYourKeyRingFile - -ossrhUsername=your-jira-id -ossrhPassword=your-jira-password - selenium.version=4.5.0 # Please increment the value in a release appiumClient.version=8.2.1 From 2250ea506304a02d139620d96002d674776900dc Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Thu, 24 Nov 2022 17:42:41 +0530 Subject: [PATCH 031/314] docs: update release notes --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 74ec4bfb0..ae034f7eb 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,7 @@ You could find much more code examples by checking client's - Connect directly to Appium Hosts in Distributed Environments. [#1747](https://github.com/appium/java-client/pull/1747) - use own User Agent. [#1779](https://github.com/appium/java-client/pull/1779) - Add alternative proxy implementation. [#1790](https://github.com/appium/java-client/pull/1790) + - Automated artefact publish to maven central. [#1803](https://github.com/appium/java-client/pull/1803) & [#1807](https://github.com/appium/java-client/pull/1807) - **[BUG FIX]** - Enforce usage of Base64 compliant with RFC 4648 for all operations. [#1785](https://github.com/appium/java-client/pull/1785) - Override getScreenshotAs to support the legacy base64 encoding. [#1787](https://github.com/appium/java-client/pull/1787) @@ -220,10 +221,10 @@ You could find much more code examples by checking client's - Force unified imports order. [#1793](https://github.com/appium/java-client/pull/1793) - **[DEPENDENCY UPDATES]** - `org.seleniumhq.selenium:selenium-java` was updated to 4.5.0. - - `org.owasp.dependencycheck` was updated to 7.3.0. + - `org.owasp.dependencycheck` was updated to 7.3.2. - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.1. - `org.junit.jupiter:junit-jupiter` was updated to 5.9.1. - - `org.slf4j:slf4j-api` was updated to 2.0.3. + - `org.slf4j:slf4j-api` was updated to 2.0.4. - `com.google.code.gson:gson` was updated to 2.10.0. *8.2.0* - **[ENHANCEMENTS]** From 7ee25a2cb250d7e5acf89da760d0e9c49a0b9bd9 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 28 Nov 2022 12:54:46 +0100 Subject: [PATCH 032/314] docs: Add troubleshooting section (#1808) --- README.md | 27 ++++++++++ docs/Installing-the-project.md | 91 ---------------------------------- 2 files changed, 27 insertions(+), 91 deletions(-) delete mode 100644 docs/Installing-the-project.md diff --git a/README.md b/README.md index ae034f7eb..f09334c06 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,33 @@ Check the corresponding driver's READMEs to know the list of capabilities and fe You could find much more code examples by checking client's [unit and integration tests](src/test/java/io/appium/java_client). +## Troubleshooting + +### InaccessibleObjectException is thrown in runtime if Java 16+ is used + +Appium Java client uses reflective access to private members of other modules +to ensure proper functionality of several features, like Page Object model. +If you get a runtime exception and `InaccessibleObjectException` is present in +the stacktrace, and your Java runtime is at version 16 or higher, then consider following +[Oracle's tutorial](https://docs.oracle.com/en/java/javase/16/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B) +and/or checking [existing issues](https://github.com/appium/java-client/search?q=InaccessibleObjectException&type=issues) +for possible solutions. Basically, the idea there would be to explicitly allow +access for particular modules using `--add-exports/--add-opens` command line arguments. + +Another possible, but weakly advised solution, would be to downgrade Java to +version 15 or lower. + +### Issues related to environment variables presence or to their values + +Such issues are usually the case when Appium server is started directly from your +framework code rather than run separately by a script or manually. Depending +on the way the server process is started it may or may not inherit the currently +active shell environment. That is why you may still receive errors about variables +presence even though these variables ar actually defined for your command line interpreter. +Again, there is no universal solution to that, as there are many ways to spin up a new +server process. Consider checking the [Appium Environment Troubleshooting](docs/environment.md) +document for more information on how to debug and fix process environment issues. + ## Changelog *8.2.1* - **[ENHANCEMENTS]** diff --git a/docs/Installing-the-project.md b/docs/Installing-the-project.md deleted file mode 100644 index 8417f9f79..000000000 --- a/docs/Installing-the-project.md +++ /dev/null @@ -1,91 +0,0 @@ -# Requirements - -Firstly you should install appium server. [Appium getting started](https://appium.io/docs/en/about-appium/getting-started/). The latest server version is recommended. - -Since version 5.x there many features based on Java 8. So we recommend to install JDK SE 8 and provide that source compatibility. - -# Maven - -Add the following to pom.xml: - -``` - - io.appium - java-client - ${version.you.require} - test - -``` - -If you haven't already, change the Java version: -``` - - 1.8 - 1.8 - -``` - -If it is necessary to change the version of Selenium then you can configure pom.xml like following: - -``` - - io.appium - java-client - ${version.you.require} - test - - - org.seleniumhq.selenium - selenium-java - - - - - - org.seleniumhq.selenium - selenium-java - ${selenium.version.you.require} - -``` - -# Gradle - -Add the following to build.gradle: - -``` -repositories { - jcenter() - maven { - url "http://repo.maven.apache.org/maven2" - } -} - -dependencies { - ... - testCompile group: 'io.appium', name: 'java-client', version: requiredVersion - ... -} -``` - -If it is necessary to change the version of Selenium then you can configure build.gradle like the sample below: - -``` -repositories { - jcenter() - maven { - url "http://repo.maven.apache.org/maven2" - } -} - -dependencies { - ... - testCompile group: 'io.appium', name: 'java-client', version: requiredVersion { - exclude module: 'selenium-java' - } - - testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', - version: requiredSeleniumVersion - ... -} -``` - From 80c6c1b8d968cb8aac33023f40af930f7ae77f1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:30:09 +0300 Subject: [PATCH 033/314] build(deps): bump slf4j-api from 2.0.4 to 2.0.5 (#1809) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.4...v_2.0.5) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 aac5e7189..3b89ad341 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:2.0.4' + implementation 'org.slf4j:slf4j-api:2.0.5' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' From d81e91a3b7ad2e24b24117d763c679240948877f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 30 Nov 2022 15:52:09 +0100 Subject: [PATCH 034/314] chore: Add CHANGELOG.md (#1810) --- CHANGELOG.md | 866 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 858 +------------------------------------------------- 2 files changed, 867 insertions(+), 857 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..0d8a55cd9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,866 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +*8.2.1* +- **[ENHANCEMENTS]** + - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) + - Connect directly to Appium Hosts in Distributed Environments. [#1747](https://github.com/appium/java-client/pull/1747) + - use own User Agent. [#1779](https://github.com/appium/java-client/pull/1779) + - Add alternative proxy implementation. [#1790](https://github.com/appium/java-client/pull/1790) + - Automated artefact publish to maven central. [#1803](https://github.com/appium/java-client/pull/1803) & [#1807](https://github.com/appium/java-client/pull/1807) +- **[BUG FIX]** + - Enforce usage of Base64 compliant with RFC 4648 for all operations. [#1785](https://github.com/appium/java-client/pull/1785) + - Override getScreenshotAs to support the legacy base64 encoding. [#1787](https://github.com/appium/java-client/pull/1787) +- **[REFACTOR]** + - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) + - JUnit5 test classes and methods are updated to have default package visibility. [#1755](https://github.com/appium/java-client/pull/1755) + - Verify if the PR title complies with conventional commits spec. [#1757](https://github.com/appium/java-client/pull/1757) + - Use Lombok in direct connect class. [#1789](https://github.com/appium/java-client/pull/1789) + - Update readme and remove obsolete documents. [#1792](https://github.com/appium/java-client/pull/1792) + - Remove unnecessary annotation. [#1791](https://github.com/appium/java-client/pull/1791) + - Force unified imports order. [#1793](https://github.com/appium/java-client/pull/1793) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.5.0. + - `org.owasp.dependencycheck` was updated to 7.3.2. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.1. + - `org.junit.jupiter:junit-jupiter` was updated to 5.9.1. + - `org.slf4j:slf4j-api` was updated to 2.0.4. + - `com.google.code.gson:gson` was updated to 2.10.0. + +*8.2.0* +- **[ENHANCEMENTS]** + - AppiumDriverLocalService can handle outputStreams. [#1709](https://github.com/appium/java-client/pull/1709) + - Add creating a driver with ClientConfig. [#1735](https://github.com/appium/java-client/pull/1735) +- **[BUG FIX]** + - Update the environment argument type for mac SupportsEnvironmentOption. [#1712](https://github.com/appium/java-client/pull/1712) +- **[REFACTOR]** + - Deprecate Appium ByAll in favour of Selenium ByAll. [#1740](https://github.com/appium/java-client/pull/1740) + - Bump Node.js version in pipeline. [#1713](https://github.com/appium/java-client/pull/1713) + - Switch unit tests to run on Junit 5 Jupiter Platform. [#1721](https://github.com/appium/java-client/pull/1721) + - Clean up unit tests asserting thrown exceptions. [#1741](https://github.com/appium/java-client/pull/1741) + - Fix open notification test. [#1749](https://github.com/appium/java-client/pull/1749) + - update Azure pipeline to use macos-11 VM image. [#1728](https://github.com/appium/java-client/pull/1728) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.4.0. + - `org.owasp.dependencycheck` was updated to 7.1.2. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.0. + - `gradle` was updated to 7.5.1. + - `com.google.code.gson:gson` was updated to 2.9.1. + +*8.1.1* +- **[BUG FIX]** + - Perform safe typecast while getting the platform name. [#1702](https://github.com/appium/java-client/pull/1702) + - Add prefix to platformVersion capability name. [#1704](https://github.com/appium/java-client/pull/1704) +- **[REFACTOR]** + - Update e2e tests to make it green. [#1706](https://github.com/appium/java-client/pull/1706) + - Ignore the test which has a connected server issue. [#1699](https://github.com/appium/java-client/pull/1699) + +*8.1.0* +- **[ENHANCEMENTS]** + - Add new EspressoBuildConfig options. [#1687](https://github.com/appium/java-client/pull/1687) +- **[DOCUMENTATION]** + - delete all references to removed MobileElement class. [#1677](https://github.com/appium/java-client/pull/1677) +- **[BUG FIX]** + - Pass orientation name capability in uppercase. [#1686](https://github.com/appium/java-client/pull/1686) + - correction for ping method to get proper status URL. [#1661](https://github.com/appium/java-client/pull/1661) + - Remove deprecated option classes. [#1679](https://github.com/appium/java-client/pull/1679) + - Remove obsolete event firing decorators. [#1676](https://github.com/appium/java-client/pull/1676) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.2.0. + - `org.owasp.dependencycheck` was updated to 7.1.0.1. + - `org.springframework:spring-context` was removed. [#1676](https://github.com/appium/java-client/pull/1676) + - `org.aspectj:aspectjweaver` was updated to 1.9.9. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.2.0. + - `org.projectlombok:lombok` was updated to 1.18.24. + +*8.0.0* +- **[DOCUMENTATION]** + - Set minimum Java version to 1.8.0. [#1631](https://github.com/appium/java-client/pull/1631) +- **[BUG FIX]** + - Make interfaces public to fix decorator creation. [#1644](https://github.com/appium/java-client/pull/1644) + - Do not convert argument names to lowercase. [#1627](https://github.com/appium/java-client/pull/1627) + - Avoid fallback to css for id and name locator annotations. [#1622](https://github.com/appium/java-client/pull/1622) + - Fix handling of chinese characters in `AppiumDriverLocalService`. [#1618](https://github.com/appium/java-client/pull/1618) +- **[DEPENDENCY UPDATES]** + - `org.owasp.dependencycheck` was updated to 7.0.0. + - `org.springframework:spring-context` was updated to 5.3.16. + - `actions/setup-java` was updated to 3. + - `actions/checkout` was updated to 3. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.1.0. + - `org.aspectj:aspectjweaver` was updated to 1.9.8. + - `org.slf4j:slf4j-api` was updated to 1.7.36. + - `com.github.johnrengelman.shadow` was updated to 7.1.2. + +*8.0.0-beta2* +- **[DOCUMENTATION]** + - Add a link to options builder examples to the migration guide. [#1595](https://github.com/appium/java-client/pull/1595) +- **[BUG FIX]** + - Filter out proxyClassLookup method from Proxy class (for Java 16+) in AppiumByBuilder. [#1575](https://github.com/appium/java-client/pull/1575) +- **[REFACTOR]** + - Add more nice functional stuff into page factory helpers. [#1584](https://github.com/appium/java-client/pull/1584) + - Switch e2e tests to use Appium2. [#1603](https://github.com/appium/java-client/pull/1603) + - relax constraints of Selenium dependencies versions. [#1606](https://github.com/appium/java-client/pull/1606) +- **[DEPENDENCY UPDATES]** + - Upgrade to Selenium 4.1.1. [#1613](https://github.com/appium/java-client/pull/1613) + - `org.owasp.dependencycheck` was updated to 6.5.1. + - `org.springframework:spring-context` was updated to 5.3.14. + - `actions/setup-java` was updated to 2.4.0. + - `gradle` was updated to 7.3. + +*8.0.0-beta* +- **[ENHANCEMENTS]** + - Start adding UiAutomator2 options. [#1543](https://github.com/appium/java-client/pull/1543) + - Add more UiAutomator2 options. [#1545](https://github.com/appium/java-client/pull/1545) + - Finish creating options for UiAutomator2 driver. [#1548](https://github.com/appium/java-client/pull/1548) + - Add WDA-related XCUITestOptions. [#1552](https://github.com/appium/java-client/pull/1552) + - Add web view options for XCUITest driver. [#1557](https://github.com/appium/java-client/pull/1557) + - Add the rest of XCUITest driver options. [#1561](https://github.com/appium/java-client/pull/1561) + - Add Espresso options. [#1563](https://github.com/appium/java-client/pull/1563) + - Add Windows driver options. [#1564](https://github.com/appium/java-client/pull/1564) + - Add Mac2 driver options. [#1565](https://github.com/appium/java-client/pull/1565) + - Add Gecko driver options. [#1573](https://github.com/appium/java-client/pull/1573) + - Add Safari driver options. [#1576](https://github.com/appium/java-client/pull/1576) + - Start adding XCUITest driver options. [#1551](https://github.com/appium/java-client/pull/1551) + - Implement driver-specific W3C option classes. [#1540](https://github.com/appium/java-client/pull/1540) + - Update Service to properly work with options. [#1550](https://github.com/appium/java-client/pull/1550) +- **[BREAKING CHANGE]** + - Migrate to Selenium 4. [#1531](https://github.com/appium/java-client/pull/1531) + - Make sure we only write W3C payload into create session command. [#1537](https://github.com/appium/java-client/pull/1537) + - Use the new session payload creator inherited from Selenium. [#1535](https://github.com/appium/java-client/pull/1535) + - unify locator factories naming and toString implementations. [#1538](https://github.com/appium/java-client/pull/1538) + - drop support of deprecated Selendroid driver. [#1553](https://github.com/appium/java-client/pull/1553) + - switch to javac compiler. [#1556](https://github.com/appium/java-client/pull/1556) + - revise used Selenium dependencies. [#1560](https://github.com/appium/java-client/pull/1560) + - change prefix to AppiumBy in locator toString implementation. [#1559](https://github.com/appium/java-client/pull/1559) + - enable dependencies caching. [#1567](https://github.com/appium/java-client/pull/1567) + - Include more tests into the pipeline. [#1566](https://github.com/appium/java-client/pull/1566) + - Tune setting of default platform names. [#1570](https://github.com/appium/java-client/pull/1570) + - Deprecate custom event listener implementation and default to the one provided by Selenium4. [#1541](https://github.com/appium/java-client/pull/1541) + - Deprecate touch actions. [#1569](https://github.com/appium/java-client/pull/1569) + - Deprecate legacy app management helpers. [#1571](https://github.com/appium/java-client/pull/1571) + - deprecate Windows UIAutomation selector. [#1562](https://github.com/appium/java-client/pull/1562) + - Remove unused entities. [#1572](https://github.com/appium/java-client/pull/1572) + - Remove setElementValue helper. [#1577](https://github.com/appium/java-client/pull/1577) + - Remove selenium package override. [#1555](https://github.com/appium/java-client/pull/1555) + - remove redundant exclusion of Gradle task signMavenJavaPublication. [#1568](https://github.com/appium/java-client/pull/1568) +- **[DEPENDENCY UPDATES]** + - `org.owasp.dependencycheck` was updated to 6.4.1. + - `com.google.code.gson:gson` was updated to 2.8.9. + +*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) +- **[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) + - 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) +- **[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) + - 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) + - Update Appium executable detection implementation. [#1256](https://github.com/appium/java-client/pull/1256) + - Avoid through NPE if any setting value is null. [#1241](https://github.com/appium/java-client/pull/1241) + - Settings API was improved to accept string names. [#1240](https://github.com/appium/java-client/pull/1240) + - Switch `runAppInBackground` iOS implementation in sync with other platforms. [#1229](https://github.com/appium/java-client/pull/1229) + - JavaDocs for AndroidMobileCapabilityType was updated. [#1238](https://github.com/appium/java-client/pull/1238) + - Github Actions were introduced instead of TravisCI. [#1219](https://github.com/appium/java-client/pull/1219) +- **[BUG FIX]** + - Fix return type of `getSystemBars` API. [#1216](https://github.com/appium/java-client/pull/1216) + - Avoid using `getSession` call for capabilities values retrieval [W3C Support]. [#1204](https://github.com/appium/java-client/pull/1204) + - Fix pagefactory list element initialisation when parameterised by generic type. [#1237](https://github.com/appium/java-client/pull/1237) + - Fix AndroidKey commands. [#1250](https://github.com/appium/java-client/pull/1250) + +*7.2.0* +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was reverted to stable version 3.141.59. [#1209](https://github.com/appium/java-client/pull/1209) + - `org.projectlombok:lombok:1.18.8` was introduced. [#1193](https://github.com/appium/java-client/pull/1193) +- **[ENHANCEMENTS]** + - `videoFilters` property was added to IOSStartScreenRecordingOptions. [#1180](https://github.com/appium/java-client/pull/1180) +- **[IMPROVEMENTS]** + - `Selendroid` automationName was deprecated. [#1198](https://github.com/appium/java-client/pull/1198) + - JavaDocs for AndroidMobileCapabilityType and IOSMobileCapabilityType were updated. [#1204](https://github.com/appium/java-client/pull/1204) + - JitPack builds were fixed. [#1203](https://github.com/appium/java-client/pull/1203) + +*7.1.0* +- **[ENHANCEMENTS]** + - Added an ability to get all the session details. [#1167 ](https://github.com/appium/java-client/pull/1167) + - `TRACK_SCROLL_EVENTS`, `ALLOW_INVISIBLE_ELEMENTS`, `ENABLE_NOTIFICATION_LISTENER`, + `NORMALIZE_TAG_NAMES` and `SHUTDOWN_ON_POWER_DISCONNECT` Android Settings were added. + - `KEYBOARD_AUTOCORRECTION`, `MJPEG_SCALING_FACTOR`, + `MJPEG_SERVER_SCREENSHOT_QUALITY`, `MJPEG_SERVER_FRAMERATE`, `SCREENSHOT_QUALITY` + and `KEYBOARD_PREDICTION` iOS Settings were added. + - `GET_MATCHED_IMAGE_RESULT`, `FIX_IMAGE_TEMPLATE_SCALE`, + `SHOULD_USE_COMPACT_RESPONSES`, `ELEMENT_RESPONSE_ATTRIBUTES` and + `DEFAULT_IMAGE_TEMPLATE_SCALE` settings were added for both Android and iOS [#1166](https://github.com/appium/java-client/pull/1166), [#1156 ](https://github.com/appium/java-client/pull/1156) and [#1120](https://github.com/appium/java-client/pull/1120) + - The new interface `io.appium.java_client.ExecutesDriverScript ` was added. [#1165](https://github.com/appium/java-client/pull/1165) + - Added an ability to get status of appium server. [#1153 ](https://github.com/appium/java-client/pull/1153) + - `tvOS` platform support was added. [#1142 ](https://github.com/appium/java-client/pull/1142) + - The new interface `io.appium.java_client. FindsByAndroidDataMatcher` was added. [#1106](https://github.com/appium/java-client/pull/1106) + - The selector strategy `io.appium.java_client.MobileBy.ByAndroidDataMatcher` was added. [#1106](https://github.com/appium/java-client/pull/1106) + - Selendroid for android and UIAutomation for iOS are removed. [#1077 ](https://github.com/appium/java-client/pull/1077) + - **[BUG FIX]** Platform Name enforced on driver creation is avoided now. [#1164 ](https://github.com/appium/java-client/pull/1164) + - **[BUG FIX]** Send both signalStrengh and signalStrength for `GSM_SIGNAL`. [#1115 ](https://github.com/appium/java-client/pull/1115) + - **[BUG FIX]** Null pointer exceptions when calling getCapabilities is handled better. [#1094 ](https://github.com/appium/java-client/pull/1094) + +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.0.0-alpha-1. + - `org.aspectj:aspectjweaver` was updated to 1.9.4. + - `org.apache.httpcomponents:httpclient` was updated to 4.5.9. + - `cglib:cglib` was updated to 3.2.12. + - `org.springframework:spring-context` was updated to 5.1.8.RELEASE. + - `io.github.bonigarcia:webdrivermanager` was updated to 3.6.1. + - `org.eclipse.jdt:ecj` was updated to 3.18.0. + - `com.github.jengelman.gradle.plugins:shadow` was updated to 5.1.0. + - `checkstyle` was updated to 8.22. + - `gradle` was updated to 5.4. + - `dependency-check-gradle` was updated to 5.1.0. + - `org.slf4j:slf4j-api` was updated to 1.7.26. + - `org.apache.commons:commons-lang3` was updated to 3.9. + +*7.0.0* +- **[ENHANCEMENTS]** + - The new interface `io.appium.java_client.FindsByAndroidViewTag` was added. [#996](https://github.com/appium/java-client/pull/996) + - The selector strategy `io.appium.java_client.MobileBy.ByAndroidViewTag` was added. [#996](https://github.com/appium/java-client/pull/996) + - The new interface `io.appium.java_client.FindsByImage` was added. [#990](https://github.com/appium/java-client/pull/990) + - The selector strategy `io.appium.java_client.MobileBy.ByImage` was added. [#990](https://github.com/appium/java-client/pull/990) + - The new interface `io.appium.java_client.FindsByCustom` was added. [#1041](https://github.com/appium/java-client/pull/1041) + - The selector strategy `io.appium.java_client.MobileBy.ByCustom` was added. [#1041](https://github.com/appium/java-client/pull/1041) + - DatatypeConverter is replaced with Base64 for JDK 9 compatibility. [#999](https://github.com/appium/java-client/pull/999) + - Expand touch options API to accept coordinates as Point. [#997](https://github.com/appium/java-client/pull/997) + - W3C capabilities written into firstMatch entity instead of alwaysMatch. [#1010](https://github.com/appium/java-client/pull/1010) + - `Selendroid` for android and `UIAutomation` for iOS is deprecated. [#1034](https://github.com/appium/java-client/pull/1034) and [#1074](https://github.com/appium/java-client/pull/1074) + - `videoScale` and `fps` screen recording options are introduced for iOS. [#1067](https://github.com/appium/java-client/pull/1067) + - `NORMALIZE_TAG_NAMES` setting was introduced for android. [#1073](https://github.com/appium/java-client/pull/1073) + - `threshold` argument was added to OccurrenceMatchingOptions. [#1060](https://github.com/appium/java-client/pull/1060) + - `org.openqa.selenium.internal.WrapsElement` replaced by `org.openqa.selenium.WrapsElement`. [#1053](https://github.com/appium/java-client/pull/1053) + - SLF4J logging support added into Appium Driver local service. [#1014](https://github.com/appium/java-client/pull/1014) + - `IMAGE_MATCH_THRESHOLD`, `FIX_IMAGE_FIND_SCREENSHOT_DIMENSIONS`, `FIX_IMAGE_TEMPLATE_SIZE`, `CHECK_IMAGE_ELEMENT_STALENESS`, `UPDATE_IMAGE_ELEMENT_POSITION` and `IMAGE_ELEMENT_TAP_STRATEGY` setting was introduced for image elements. [#1011](https://github.com/appium/java-client/pull/1011) +- **[BUG FIX]** Better handling of InvocationTargetException [#968](https://github.com/appium/java-client/pull/968) +- **[BUG FIX]** Map sending keys to active element for W3C compatibility. [#966](https://github.com/appium/java-client/pull/966) +- **[BUG FIX]** Error message on session creation is improved. [#994](https://github.com/appium/java-client/pull/994) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 3.141.59. + - `com.google.code.gson:gson` was updated to 2.8.5. + - `org.apache.httpcomponents:httpclient` was updated to 4.5.6. + - `cglib:cglib` was updated to 3.2.8. + - `org.apache.commons:commons-lang3` was updated to 3.8. + - `org.springframework:spring-context` was updated to 5.1.0.RELEASE. + - `io.github.bonigarcia:webdrivermanager` was updated to 3.0.0. + - `org.eclipse.jdt:ecj` was updated to 3.14.0. + - `org.slf4j:slf4j-api` was updated to 1.7.25. + - `jacoco` was updated to 0.8.2. + - `checkstyle` was updated to 8.12. + - `gradle` was updated to 4.10.1. + - `org.openpnp:opencv` was removed. + +*6.1.0* +- **[BUG FIX]** Initing web socket clients lazily. Report [#911](https://github.com/appium/java-client/issues/911). FIX: [#912](https://github.com/appium/java-client/pull/912). +- **[BUG FIX]** Fix session payload for W3C. [#913](https://github.com/appium/java-client/pull/913) +- **[ENHANCEMENT]** Added TouchAction constructor argument verification [#923](https://github.com/appium/java-client/pull/923) +- **[BUG FIX]** Set retry flag to true by default for OkHttpFactory. [#928](https://github.com/appium/java-client/pull/928) +- **[BUG FIX]** Fix class cast exception on getting battery info. [#935](https://github.com/appium/java-client/pull/935) +- **[ENHANCEMENT]** Added an optional format argument to getDeviceTime and update the documentation. [#939](https://github.com/appium/java-client/pull/939) +- **[ENHANCEMENT]** The switching web socket client implementation to okhttp library. [#941](https://github.com/appium/java-client/pull/941) +- **[BUG FIX]** Fix of the bug [#924](https://github.com/appium/java-client/issues/924). [#951](https://github.com/appium/java-client/pull/951) + +*6.0.0* +- **[ENHANCEMENT]** Added an ability to set pressure value for iOS. [#879](https://github.com/appium/java-client/pull/879) +- **[ENHANCEMENT]** Added new server arguments `RELAXED_SECURITY` and `ENABLE_HEAP_DUMP`. [#880](https://github.com/appium/java-client/pull/880) +- **[BUG FIX]** Use default Selenium HTTP client factory [#877](https://github.com/appium/java-client/pull/877) +- **[ENHANCEMENT]** Supporting syslog broadcast with iOS [#871](https://github.com/appium/java-client/pull/871) +- **[ENHANCEMENT]** Added isKeyboardShown command for iOS [#887](https://github.com/appium/java-client/pull/887) +- **[ENHANCEMENT]** Added battery information accessors [#882](https://github.com/appium/java-client/pull/882) +- **[BREAKING CHANGE]** Removal of deprecated code. [#881](https://github.com/appium/java-client/pull/881) +- **[BUG FIX]** Added `NewAppiumSessionPayload`. Bug report: [#875](https://github.com/appium/java-client/issues/875). FIX: [#894](https://github.com/appium/java-client/pull/894) +- **[ENHANCEMENT]** Added ESPRESSO automation name [#908](https://github.com/appium/java-client/pull/908) +- **[ENHANCEMENT]** Added a method for output streams cleanup [#909](https://github.com/appium/java-client/pull/909) +- **[DEPENDENCY UPDATES]** + - `com.google.code.gson:gson` was updated to 2.8.4 + - `org.springframework:spring-context` was updated to 5.0.5.RELEASE + - `org.aspectj:aspectjweaver` was updated to 1.9.1 + - `org.glassfish.tyrus:tyrus-clien` was updated to 1.13.1 + - `org.glassfish.tyrus:tyrus-container-grizzly` was updated to 1.2.1 + - `org.seleniumhq.selenium:selenium-java` was updated to 3.12.0 + + +*6.0.0-BETA5* +- **[ENHANCEMENT]** Added clipboard handlers. [#855](https://github.com/appium/java-client/pull/855) [#869](https://github.com/appium/java-client/pull/869) +- **[ENHANCEMENT]** Added wrappers for Android logcat broadcaster. [#858](https://github.com/appium/java-client/pull/858) +- **[ENHANCEMENT]** Add bugreport option to Android screen recorder. [#852](https://github.com/appium/java-client/pull/852) +- **[BUG FIX]** Avoid amending parameters for SET_ALERT_VALUE endpoint. [#867](https://github.com/appium/java-client/pull/867) +- **[BREAKING CHANGE]** Refactor network connection setting on Android. [#865](https://github.com/appium/java-client/pull/865) +- **[BUG FIX]** **[BREAKING CHANGE]** Refactor of the `io.appium.java_client.AppiumFluentWait`. It uses `java.time.Duration` for time settings instead of `org.openqa.selenium.support.ui.Duration` and `java.util.concurrent.TimeUnit` [#863](https://github.com/appium/java-client/pull/863) +- **[BREAKING CHANGE]** `io.appium.java_client.pagefactory.TimeOutDuration` became deprecated. It is going to be removed. Use `java.time.Duration` instead. FIX [#742](https://github.com/appium/java-client/issues/742) [#863](https://github.com/appium/java-client/pull/863). +- **[BREAKING CHANGE]** `io.appium.java_client.pagefactory.WithTimeOut#unit` became deprecated. It is going to be removed. Use `io.appium.java_client.pagefactory.WithTimeOut#chronoUnit` instead. FIX [#742](https://github.com/appium/java-client/issues/742) [#863](https://github.com/appium/java-client/pull/863). +- **[BREAKING CHANGE]** constructors of `io.appium.java_client.pagefactory.AppiumElementLocatorFactory`, `io.appium.java_client.pagefactory.AppiumFieldDecorator` and `io.appium.java_client.pagefactory.AppiumElementLocator` which use `io.appium.java_client.pagefactory.TimeOutDuration` as a parameter became deprecated. Use new constructors which use `java.time.Duration`. +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 3.11.0 + +*6.0.0-BETA4* +- **[ENHANCEMENT]** Added handler for isDispalyed in W3C mode. [#833](https://github.com/appium/java-client/pull/833) +- **[ENHANCEMENT]** Added handlers for sending SMS, making GSM Call, setting GSM signal, voice, power capacity and power AC. [#834](https://github.com/appium/java-client/pull/834) +- **[ENHANCEMENT]** Added handlers for toggling wifi, airplane mode and data in android. [#835](https://github.com/appium/java-client/pull/835) +- **[DEPENDENCY UPDATES]** + - `org.apache.httpcomponents:httpclient` was updated to 4.5.5 + - `cglib:cglib` was updated to 3.2.6 + - `org.springframework:spring-context` was updated to 5.0.3.RELEASE + +*6.0.0-BETA3* +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 3.9.1 +- **[BREAKING CHANGE]** Removal of deprecated listener-methods from the AlertEventListener. [#797](https://github.com/appium/java-client/pull/797) +- **[BUG FIX]**. Fix the `pushFile` command. [#812](https://github.com/appium/java-client/pull/812) [#816](https://github.com/appium/java-client/pull/816) +- **[ENHANCEMENT]**. Implemented custom command codec. [#817](https://github.com/appium/java-client/pull/817), [#825](https://github.com/appium/java-client/pull/825) +- **[ENHANCEMENT]** Added handlers for lock/unlock in iOS. [#799](https://github.com/appium/java-client/pull/799) +- **[ENHANCEMENT]** AddEd endpoints for screen recording API for iOS and Android. [#814](https://github.com/appium/java-client/pull/814) +- **[MAJOR ENHANCEMENT]** W3C compliance was provided. [#829](https://github.com/appium/java-client/pull/829) +- **[ENHANCEMENT]** New capability `MobileCapabilityType.FORCE_MJSONWP` [#829](https://github.com/appium/java-client/pull/829) +- **[ENHANCEMENT]** Updated applications management endpoints. [#824](https://github.com/appium/java-client/pull/824) + +*6.0.0-BETA2* +- **[ENHANCEMENT]** The `fingerPrint` ability was added. It is supported by Android for now. [#473](https://github.com/appium/java-client/pull/473) [#786](https://github.com/appium/java-client/pull/786) +- **[BUG FIX]**. Less strict verification of the `PointOption`. [#795](https://github.com/appium/java-client/pull/795) + +*6.0.0-BETA1* +- **[ENHANCEMENT]** **[REFACTOR]** **[BREAKING CHANGE]** **[MAJOR CHANGE]** Improvements of the TouchActions API [#756](https://github.com/appium/java-client/pull/756), [#760](https://github.com/appium/java-client/pull/760): + - `io.appium.java_client.touch.ActionOptions` and sublasses were added + - old methods of the `TouchActions` were marked `@Deprecated` + - new methods which take new options. +- **[ENHANCEMENT]**. Appium drivr local service uses default process environment by default. [#753](https://github.com/appium/java-client/pull/753) +- **[BUG FIX]**. Removed 'set' prefix from waitForIdleTimeout setting. [#754](https://github.com/appium/java-client/pull/754) +- **[BUG FIX]**. The asking for session details was optimized. Issue report [764](https://github.com/appium/java-client/issues/764). + FIX [#769](https://github.com/appium/java-client/pull/769) +- **[BUG FIX]** **[REFACTOR]**. Inconcistent MissingParameterException was removed. Improvements of MultiTouchAction. Report: [#102](https://github.com/appium/java-client/issues/102). FIX [#772](https://github.com/appium/java-client/pull/772) +- **[DEPENDENCY UPDATES]** + - `org.apache.commons:commons-lang3` was updated to 3.7 + - `commons-io:commons-io` was updated to 2.6 + - `org.springframework:spring-context` was updated to 5.0.2.RELEASE + - `org.aspectj:aspectjweaver` was updated to 1.8.13 + - `org.seleniumhq.selenium:selenium-java` was updated to 3.7.1 + +*5.0.4* +- **[BUG FIX]**. Client was crashing when user was testing iOS with server 1.7.0. Report: [#732](https://github.com/appium/java-client/issues/732). Fix: [#733](https://github.com/appium/java-client/pull/733). +- **[REFACTOR]** **[BREAKING CHANGE]** Excessive invocation of the implicit waiting timeout was removed. This is the breaking change because API of `AppiumElementLocator` and `AppiumElementLocatorFactory` was changed. Request: [#735](https://github.com/appium/java-client/issues/735), FIXES: [#738](https://github.com/appium/java-client/pull/738), [#741](https://github.com/appium/java-client/pull/741) +- **[DEPENDENCY UPDATES]** + - org.seleniumhq.selenium:selenium-java to 3.6.0 + - com.google.code.gson:gson to 2.8.2 + - org.springframework:spring-context to 5.0.0.RELEASE + - org.aspectj:aspectjweaver to 1.8.11 + +*5.0.3* +- **[BUG FIX]** Selenuim version was reverted from boundaries to the single number. Issue report: [#718](https://github.com/appium/java-client/issues/718). FIX: [#722](https://github.com/appium/java-client/pull/722) +- **[ENHANCEMENT]** The `pushFile` was added to IOSDriver. Feature request: [#720](https://github.com/appium/java-client/issues/720). Implementation: [#721](https://github.com/appium/java-client/pull/721). This feature requires appium node server v>=1.7.0 + +*5.0.2* **[BUG FIX RELEASE]** +- **[BUG FIX]** Dependency conflict resolving. The report: [#714](https://github.com/appium/java-client/issues/714). The fix: [#717](https://github.com/appium/java-client/pull/717). This change may affect users who use htmlunit-driver and/or phantomjsdriver. At this case it is necessary to add it to dependency list and to exclude old selenium versions. + +*5.0.1* **[BUG FIX RELEASE]** +- **[BUG FIX]** The fix of the element genering on iOS was fixed. Issue report: [#704](https://github.com/appium/java-client/issues/704). Fix: [#705](https://github.com/appium/java-client/pull/705) + +*5.0.0* +- **[REFACTOR]** **[BREAKING CHANGE]** 5.0.0 finalization. Removal of obsolete code. [#660](https://github.com/appium/java-client/pull/660) +- **[ENHANCEMENT]** Enable nativeWebTap setting for iOS. [#658](https://github.com/appium/java-client/pull/658) +- **[ENHANCEMENT]** The `getCurrentPackage` was added. [#657](https://github.com/appium/java-client/pull/657) +- **[ENHANCEMENT]** The `toggleTouchIDEnrollment` was added. [#659](https://github.com/appium/java-client/pull/659) +- **[BUG FIX]** The clearing of existing actions/parameters after perform is invoked. [#663](https://github.com/appium/java-client/pull/663) +- **[BUG FIX]** [#669](https://github.com/appium/java-client/pull/669) missed parameters of the `OverrideWidget` were added: + - `iOSXCUITAutomation` + - `windowsAutomation` +- **[BUG FIX]** ByAll was re-implemented. [#680](https://github.com/appium/java-client/pull/680) +- **[BUG FIX]** **[BREAKING CHANGE]** The issue of compliance with Selenium grid 3.x was fixed. This change is breaking because now java_client is compatible with appiun server v>=1.6.5. Issue report [#655](https://github.com/appium/java-client/issues/655). FIX [#682](https://github.com/appium/java-client/pull/682) +- **[BUG FIX]** issues related to latest Selenium changes were fixed. Issue report [#696](https://github.com/appium/java-client/issues/696). Fix: [#699](https://github.com/appium/java-client/pull/699). +- **[UPDATE]** Dependency update + - `selenium-java` was updated to 3.5.x + - `org.apache.commons-lang3` was updated to 3.6 + - `org.springframework.spring-context` was updated to 4.3.10.RELEASE +- **[ENHANCEMENT]** Update of the touch ID enroll method. The older `PerformsTouchID#toggleTouchIDEnrollment` was marked `Deprecated`. + It is recoomended to use `PerformsTouchID#toggleTouchIDEnrollment(boolean)` instead. [#695](https://github.com/appium/java-client/pull/695) + + +*5.0.0-BETA9* +- **[ENHANCEMENT]** Page factory: Mixed locator strategies were implemented. Feature request:[#565](https://github.com/appium/java-client/issues/565) Implementation: [#646](https://github.com/appium/java-client/pull/646) +- **[DEPRECATED]** All the content of the `io.appium.java_client.youiengine` package was marked `Deprecated`. It is going to be removed. [#652](https://github.com/appium/java-client/pull/652) +- **[UPDATE]** Update of the `com.google.code.gson:gson` to v2.8.1. + +*5.0.0-BETA8* +- **[ENHANCEMENT]** Page factory classes became which had package visibility are `public` now. [#630](https://github.com/appium/java-client/pull/630) + - `io.appium.java_client.pagefactory.AppiumElementLocatorFactory` + - `io.appium.java_client.pagefactory.DefaultElementByBuilder` + - `io.appium.java_client.pagefactory.WidgetByBuilder` + +- **[ENHANCEMENT]** New capabilities were added [#626](https://github.com/appium/java-client/pull/626): + - `AndroidMobileCapabilityType#AUTO_GRANT_PERMISSIONS` + - `AndroidMobileCapabilityType#ANDROID_NATURAL_ORIENTATION` + - `IOSMobileCapabilityType#XCODE_ORG_ID` + - `IOSMobileCapabilityType#XCODE_SIGNING_ID` + - `IOSMobileCapabilityType#UPDATE_WDA_BUNDLEID` + - `IOSMobileCapabilityType#RESET_ON_SESSION_START_ONLY` + - `IOSMobileCapabilityType#COMMAND_TIMEOUTS` + - `IOSMobileCapabilityType#WDA_STARTUP_RETRIES` + - `IOSMobileCapabilityType#WDA_STARTUP_RETRY_INTERVAL` + - `IOSMobileCapabilityType#CONNECT_HARDWARE_KEYBOARD` + - `IOSMobileCapabilityType#MAX_TYPING_FREQUENCY` + - `IOSMobileCapabilityType#SIMPLE_ISVISIBLE_CHECK` + - `IOSMobileCapabilityType#USE_CARTHAGE_SSL` + - `IOSMobileCapabilityType#SHOULD_USE_SINGLETON_TESTMANAGER` + - `IOSMobileCapabilityType#START_IWDP` + - `IOSMobileCapabilityType#ALLOW_TOUCHID_ENROLL` + - `MobileCapabilityType#EVENT_TIMINGS` + +- **[UPDATE]** Dependencies were updated: + - `org.seleniumhq.selenium:selenium-java` was updated to 3.4.0 + - `cglib:cglib` was updated to 3.2.5 + - `org.apache.httpcomponents:httpclient` was updated to 4.5.3 + - `commons-validator:commons-validator` was updated to 1.6 + - `org.springframework:spring-context` was updated to 4.3.8.RELEASE + + +*5.0.0-BETA7* +- **[ENHANCEMENT]** The ability to customize the polling strategy of the waiting was provided. [#612](https://github.com/appium/java-client/pull/612) +- **[ENHANCEMENT]** **[REFACTOR]** Methods which were representing time deltas instead of elementary types became `Deprecated`. Methods which use `java.time.Duration` are suugested to be used. [#611](https://github.com/appium/java-client/pull/611) +- **[ENHANCEMENT]** The ability to calculate screenshots overlap was included. [#595](https://github.com/appium/java-client/pull/595). + + +*5.0.0-BETA6* +- **[UPDATE]** Update to Selenium 3.3.1 +- **[ENHANCEMENT]** iOS XCUIT mode automation: API to run application in background was added. [#593](https://github.com/appium/java-client/pull/593) +- **[BUG FIX]** Issue report: [#594](https://github.com/appium/java-client/issues/594). FIX: [#597](https://github.com/appium/java-client/pull/597) +- **[ENHANCEMENT]** The class chain locator was added. [#599](https://github.com/appium/java-client/pull/599) + + +*5.0.0-BETA5* +- **[UPDATE]** Update to Selenium 3.2.0 +- **[BUG FIX]** Excessive dependency on `guava` was removed. It causes errors. Issue report: [#588](https://github.com/appium/java-client/issues/588). FIX: [#589](https://github.com/appium/java-client/pull/589). +- **[ENHANCEMENT]**. The capability `io.appium.java_client.remote.AndroidMobileCapabilityType#SYSTEM_PORT` was added. [#591](https://github.com/appium/java-client/pull/591) + +*5.0.0-BETA4* +- **[ENHANCEMENT]** Android. API to read the performance data was added. [#562](https://github.com/appium/java-client/pull/562) +- **[REFACTOR]** Android. Simplified the activity starting by reducing the number of parameters through POJO clas. Old methods which start activities were marked `@Deprecated`. [#579](https://github.com/appium/java-client/pull/579) [#585](https://github.com/appium/java-client/pull/585) +- **[BUG FIX]** Issue report:[#574](https://github.com/appium/java-client/issues/574). Fix:[#582](https://github.com/appium/java-client/pull/582) + +*5.0.0-BETA3* +[BUG FIX] +- **[BUG FIX]**:Issue report: [#567](https://github.com/appium/java-client/issues/567). Fix: [#568](https://github.com/appium/java-client/pull/568) + +*5.0.0-BETA2* +- **[BUG FIX]**:Issue report: [#549](https://github.com/appium/java-client/issues/549). Fix: [#551](https://github.com/appium/java-client/pull/551) +- New capabilities were added [#533](https://github.com/appium/java-client/pull/553): + - `IOSMobileCapabilityType#USE_NEW_WDA` + - `IOSMobileCapabilityType#WDA_LAUNCH_TIMEOUT` + - `IOSMobileCapabilityType#WDA_CONNECTION_TIMEOUT` + +The capability `IOSMobileCapabilityType#REAL_DEVICE_LOGGER` was removed. [#533](https://github.com/appium/java-client/pull/553) + +- **[BUG FIX]/[ENHANCEMENT]**. Issue report: [#552](https://github.com/appium/java-client/issues/552). FIX [#556](https://github.com/appium/java-client/pull/556) + - Additional methods were added to the `io.appium.java_client.HasSessionDetails` + - `String getPlatformName()` + - `String getAutomationName()` + - `boolean isBrowser()` + - `io.appium.java_client.HasSessionDetails` is used by the ` io.appium.java_client.internal.JsonToMobileElementConverter ` to define which instance of the `org.openqa.selenium.WebElement` subclass should be created. + +- **[ENHANCEMENT]**: The additional event firing feature. PR: [#559](https://github.com/appium/java-client/pull/559). The [WIKI chapter about the event firing](https://github.com/appium/java-client/blob/master/docs/The-event_firing.md) was updated. + +*5.0.0-BETA1* +- **[MAJOR ENHANCEMENT]**: Migration to Java 8. Epic: [#399](https://github.com/appium/java-client/issues/399) + - API with default implementation. PR [#470](https://github.com/appium/java-client/pull/470) + - Tools that provide _Page Object_ engines were redesigned. The migration to [repeatable annotations](http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html). Details you can read there: [#497](https://github.com/appium/java-client/pull/497). [Documentation was synced as well](https://github.com/appium/java-client/blob/master/docs/Page-objects.md#also-it-is-possible-to-define-chained-or-any-possible-locators). + - The new functional interface `io.appium.java_client.functions.AppiumFunctio`n was designed. It extends `java.util.function.Function` and `com.google.common.base.Function`. It was designed in order to provide compatibility with the `org.openqa.selenium.support.ui.Wait` [#543](https://github.com/appium/java-client/pull/543) + - The new functional interface `io.appium.java_client.functions.ExpectedCondition` was designed. It extends `io.appium.java_client.functions.AppiumFunction` and ```org.openqa.selenium.support.ui.ExpectedCondition```. [#543](https://github.com/appium/java-client/pull/543) + - The new functional interface `io.appium.java_client.functions.ActionSupplier` was designed. It extends ```java.util.function.Supplier```. [#543](https://github.com/appium/java-client/pull/543) + +- **[MAJOR ENHANCEMENT]**: Migration from Maven to Gradle. Feature request is [#214](https://github.com/appium/java-client/issues/214). Fixes: [#442](https://github.com/appium/java-client/pull/442), [#465](https://github.com/appium/java-client/pull/465). + +- **[MAJOR ENHANCEMENT]** **[MAJOR REFACTORING]**. Non-abstract **AppiumDriver**: + - Now the `io.appium.java_client.AppiumDriver` can use an instance of any `io.appium.java_client.MobileBy` subclass for the searching. It should work as expected when current session supports the given selector. It will throw `org.openqa.selenium.WebDriverException` otherwise. [#462](https://github.com/appium/java-client/pull/462) + - The new interface `io.appium.java_client.FindsByFluentSelector` was added. [#462](https://github.com/appium/java-client/pull/462) + - API was redesigned: + + these interfaces were marked deprecated and they are going to be removed [#513](https://github.com/appium/java-client/pull/513)[#514](https://github.com/appium/java-client/pull/514): + - `io.appium.java_client.DeviceActionShortcuts` + - `io.appium.java_client.android.AndroidDeviceActionShortcuts` + - `io.appium.java_client.ios.IOSDeviceActionShortcuts` + + instead following inerfaces were designed: + - `io.appium.java_client.HasDeviceTime` + - `io.appium.java_client.HidesKeyboard` + - `io.appium.java_client.HidesKeyboardWithKeyName` + - `io.appium.java_client.PressesKeyCode` + - `io.appium.java_client.ios.ShakesDevice` + - `io.appium.java_client.HasSessionDetails` + _That was done because Windows automation tools have some features that were considered as Android-specific and iOS-specific._ + + The list of classes and methods which were marked _deprecated_ and they are going to be removed + - `AppiumDriver#swipe(int, int, int, int, int)` + - `AppiumDriver#pinch(WebElement)` + - `AppiumDriver#pinch(int, int)` + - `AppiumDriver#zoom(WebElement)` + - `AppiumDriver#zoom(int, int)` + - `AppiumDriver#tap(int, WebElement, int)` + - `AppiumDriver#tap(int, int, int, int)` + - `AppiumDriver#swipe(int, int, int, int, int)` + - `MobileElement#swipe(SwipeElementDirection, int)` + - `MobileElement#swipe(SwipeElementDirection, int, int, int)` + - `MobileElement#zoom()` + - `MobileElement#pinch()` + - `MobileElement#tap(int, int)` + - `io.appium.java_client.SwipeElementDirection` and `io.appium.java_client.TouchebleElement` also were marked deprecated. + + redesign of `TouchAction` and `MultiTouchAction` + - constructors were redesigned. There is no strict binding of `AppiumDriver` and `TouchAction` /`MultiTouchAction`. They can consume any instance of a class that implements `PerformsTouchActions`. + - `io.appium.java_client.ios.IOSTouchAction` was added. It extends `io.appium.java_client.TouchAction`. + - the new interface `io.appium.java_client.PerformsActions` was added. It unifies `TouchAction` and `MultiTouchAction` now. [#543](https://github.com/appium/java-client/pull/543) + + `JsonToMobileElementConverter` re-design [#532](https://github.com/appium/java-client/pull/532): + - unused `MobileElementToJsonConverter` was removed + - `JsonToMobileElementConverter` is not rhe abstract class now. It generates instances of MobileElement subclasses according to current session parameters + - `JsonToAndroidElementConverter` is deprecated now + - `JsonToIOSElementConverter` is depreacated now + - `JsonToYouiEngineElementConverter` is deprecated now. + - constructors of 'AppiumDriver' were re-designed. + - constructors of 'AndroidDriver' were re-designed. + - constructors of 'IOSDriver' were re-designed. + +- **[MAJOR ENHANCEMENT]** Windows automation. Epic [#471](https://github.com/appium/java-client/issues/471) + - The new interface `io.appium.java_client.FindsByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. + - The new selector strategy `io.appium.java_client.MobileBy.ByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. + - `io.appium.java_client.windows.WindowsDriver` was designed. [#538](https://github.com/appium/java-client/pull/538) + - `io.appium.java_client.windows.WindowsElement` was designed. [#538](https://github.com/appium/java-client/pull/538) + - `io.appium.java_client.windows.WindowsKeyCode ` was added. [#538](https://github.com/appium/java-client/pull/538) + - Page object tools were updated [#538](https://github.com/appium/java-client/pull/538) + - the `io.appium.java_client.pagefactory.WindowsFindBy` annotation was added. + - `io.appium.java_client.pagefactory.AppiumFieldDecorator` and supporting tools were actualized. + +- **[MAJOR ENHANCEMENT]** iOS XCUIT mode automation: + - `io.appium.java_client.remote.AutomationName#IOS_XCUI_TEST` was added + - The new interface `io.appium.java_client.FindsByIosNSPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. It is implemented by `io.appium.java_client.ios.IOSDriver` and `io.appium.java_client.ios.IOSElement`. + - The new selector strategy `io.appium.java_client.MobileBy.ByIosNsPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. + - Page object tools were updated [#545](https://github.com/appium/java-client/pull/545), [#546](https://github.com/appium/java-client/pull/546) + - the `io.appium.java_client.pagefactory.iOSXCUITFindBy` annotation was added. + - `io.appium.java_client.pagefactory.AppiumFieldDecorator` and supporting tools were actualized. + +- [ENHANCEMENT] Added the ability to set UiAutomator Congfigurator values. [#410](https://github.com/appium/java-client/pull/410). + [#477](https://github.com/appium/java-client/pull/477). +- [ENHANCEMENT]. Additional methods which perform device rotation were implemented. [#489](https://github.com/appium/java-client/pull/489). [#439](https://github.com/appium/java-client/pull/439). But it works for iOS in XCUIT mode and for Android in UIAutomator2 mode only. The feature request: [#7131](https://github.com/appium/appium/issues/7131) +- [ENHANCEMENT]. TouchID Implementation (iOS Sim Only). Details: [#509](https://github.com/appium/java-client/pull/509) +- [ENHANCEMENT]. The ability to use port, ip and log file as server arguments was provided. Feature request: [#521](https://github.com/appium/java-client/issues/521). Fixes: [#522](https://github.com/appium/java-client/issues/522), [#524](https://github.com/appium/java-client/issues/524). +- [ENHANCEMENT]. The new interface ```io.appium.java_client.android.HasDeviceDetails``` was added. It is implemented by ```io.appium.java_client.android.AndroidDriver``` by default. [#518](https://github.com/appium/java-client/pull/518) +- [ENHANCEMENT]. New touch actions were added. ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement, int, int)``` and ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement)```. [#523](https://github.com/appium/java-client/pull/523), [#444](https://github.com/appium/java-client/pull/444) +- [ENHANCEMENT]. All constructors declared by `io.appium.java_client.AppiumDriver` are public now. +- [BUG FIX]: There was the issue when "@WithTimeout" was changing general timeout of the waiting for elements. Bug report: [#467](https://github.com/appium/java-client/issues/467). Fixes: [#468](https://github.com/appium/java-client/issues/468), [#469](https://github.com/appium/java-client/issues/469), [#480](https://github.com/appium/java-client/issues/480). Read: [supported-settings](https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md#supported-settings) +- Added the server flag `io.appium.java_client.service.local.flags.AndroidServerFlag#REBOOT`. [#476](https://github.com/appium/java-client/pull/476) +- Added `io.appium.java_client.remote.AndroidMobileCapabilityType.APP_WAIT_DURATION ` capability. [#461](https://github.com/appium/java-client/pull/461) +- the new automation type `io.appium.java_client.remote.MobilePlatform#ANDROID_UIAUTOMATOR2` was add. +- the new automation type `io.appium.java_client.remote.MobilePlatform#YOUI_ENGINE` was add. +- Additional capabilities were addede: + - `IOSMobileCapabilityType#CUSTOM_SSL_CERT` + - `IOSMobileCapabilityType#TAP_WITH_SHORT_PRESS_DURATION` + - `IOSMobileCapabilityType#SCALE_FACTOR` + - `IOSMobileCapabilityType#WDA_LOCAL_PORT` + - `IOSMobileCapabilityType#SHOW_XCODE_LOG` + - `IOSMobileCapabilityType#REAL_DEVICE_LOGGER` + - `IOSMobileCapabilityType#IOS_INSTALL_PAUSE` + - `IOSMobileCapabilityType#XCODE_CONFIG_FILE` + - `IOSMobileCapabilityType#KEYCHAIN_PASSWORD` + - `IOSMobileCapabilityType#USE_PREBUILT_WDA` + - `IOSMobileCapabilityType#PREVENT_WDAATTACHMENTS` + - `IOSMobileCapabilityType#WEB_DRIVER_AGENT_URL` + - `IOSMobileCapabilityType#KEYCHAIN_PATH` + - `MobileCapabilityType#CLEAR_SYSTEM_FILES` +- **[UPDATE]** to Selenium 3.0.1. +- **[UPDATE]** to Spring Framework 4.3.5.RELEASE. +- **[UPDATE]** to AspectJ weaver 1.8.10. + + + +*4.1.2* + +- Following capabilities were added: + - `io.appium.java_client.remote.AndroidMobileCapabilityType.ANDROID_INSTALL_TIMEOUT` + - `io.appium.java_client.remote.AndroidMobileCapabilityType.NATIVE_WEB_SCREENSHOT` + - `io.appium.java_client.remote.AndroidMobileCapabilityType.ANDROID_SCREENSHOT_PATH`. The pull request: [#452](https://github.com/appium/java-client/pull/452) +- `org.openqa.selenium.Alert` was reimplemented for iOS. Details: [#459](https://github.com/appium/java-client/pull/459) +- The deprecated `io.appium.java_client.generic.searchcontext` was removed. +- The dependency on `com.google.code.gson` was updated to 2.7. Also it was adde to exclusions + for `org.seleniumhq.selenium` `selenium-java`. +- The new AutomationName was added. IOS_XCUI_TEST. It is needed for the further development. +- The new MobilePlatform was added. WINDOWS. It is needed for the further development. + +*4.1.1* + +BUG FIX: Issue [#450](https://github.com/appium/java-client/issues/450). Fix: [#451](https://github.com/appium/java-client/issues/451). Thanks to [@tutunang](https://github.com/appium/java-client/pull/451) for the report. + +*4.1.0* +- all code marked `@Deprecated` was removed. +- `getSessionDetails()` was added. Thanks to [@saikrishna321](https://github.com/saikrishna321) for the contribution. +- FIX [#362](https://github.com/appium/java-client/issues/362), [#220](https://github.com/appium/java-client/issues/220), [#323](https://github.com/appium/java-client/issues/323). Details read there: [#413](https://github.com/appium/java-client/pull/413) +- FIX [#392](https://github.com/appium/java-client/issues/392). Thanks to [@truebit](https://github.com/truebit) for the bug report. +- The dependency on `cglib` was replaced by the dependency on `cglib-nodep`. FIX [#418](https://github.com/appium/java-client/issues/418) +- The casting to the weaker interface `HasIdentity` instead of class `RemoteWebElement` was added. It is the internal refactoring of the `TouchAction`. [#432](https://github.com/appium/java-client/pull/432). Thanks to [@asolntsev](https://github.com/asolntsev) for the contribution. +- The `setValue` method was moved to `MobileElement`. It works against text input elements on Android. +- The dependency on `org.springframework` `spring-context` v`4.3.2.RELEASE` was added +- The dependency on `org.aspectj` `aspectjweaver` v`1.8.9` was added +- ENHANCEMENT: The alternative event firing engine. The feature request: [#242](https://github.com/appium/java-client/issues/242). + Implementation: [#437](https://github.com/appium/java-client/pull/437). Also [new WIKI chapter](https://github.com/appium/java-client/blob/master/docs/The-event_firing.md) was added. +- ENHANCEMENT: Convenient access to specific commands for each supported mobile OS. Details: [#445](https://github.com/appium/java-client/pull/445) +- dependencies and plugins were updated +- ENHANCEMENT: `YouiEngineDriver` was added. Details: [appium server #6215](https://github.com/appium/appium/pull/6215), [#429](https://github.com/appium/java-client/pull/429), [#448](https://github.com/appium/java-client/pull/448). It is just the draft of the new solution that is going to be extended further. Please stay tuned. There are many interesting things are coming up. Thanks to `You I Engine` team for the contribution. + +*4.0.0* +- all code marked `@Deprecated` was removed. Java client won't support old servers (v<1.5.0) + anymore. +- the ability to start an activity using Android intent actions, intent categories, flags and arguments + was added to `AndroidDriver`. Thanks to [@saikrishna321](https://github.com/saikrishna321) for the contribution. +- `scrollTo()` and `scrollToExact()` became deprecated. They are going to be removed in the next release. +- The interface `io.appium.java_client.ios.GetsNamedTextField` and the declared method `T getNamedTextField(String name)` are + deprecated as well. They are going to be removed in the next release. +- Methods `findElements(String by, String using)` and `findElement(String by, String using)` of `org.openga.selenium.remote.RemoteWebdriver` are public now. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget). +- the `io.appium.java_client.NetworkConnectionSetting` class was marked deprecated +- the enum `io.appium.java_client.android.Connection` was added. All supported network bitmasks are defined there. +- Android. Old methods which get/set connection were marked `@Deprecated` +- Android. New methods which consume/return `io.appium.java_client.android.Connection` were added. +- the `commandRepository` field is public now. The modification of the `MobileCommand` +- Constructors like `AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities)` were added to + `io.appium.java_client.android.AndroidDriver` and `io.appium.java_client.ios.IOSDriver` +- The refactoring of `io.appium.java_client.internal.JsonToMobileElementConverter`. Now it accepts + `org.openqa.selenium.remote.RemoteWebDriver` as the constructor parameter. It is possible to re-use + `io.appium.java_client.android.internal.JsonToAndroidElementConverter` or + `io.appium.java_client.ios.internal.JsonToIOSElementConverter` by RemoteWebDriver when it is needed. +- Constructors of the abstract `io.appium.java_client.AppiumDriver` were redesigned. Now they require + a subclass of `io.appium.java_client.internal.JsonToMobileElementConverter`. Constructors of + `io.appium.java_client.android.AndroidDriver` and `io.appium.java_client.ios.IOSDriver` are same still. +- The `pushFile(String remotePath, File file)` was added to AndroidDriver +- FIX of TouchAction. Instances of the TouchAction class are reusable now +- FIX of the swiping issue (iOS, server version >= 1.5.0). Now the swiping is implemented differently by + AndroidDriver and IOSDriver. Thanks to [@truebit](https://github.com/truebit) and [@nuggit32](https://github.com/nuggit32) for the catching. +- the project was integrated with [maven-checkstyle-plugin](https://maven.apache.org/plugins/maven-checkstyle-plugin/). Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the work +- source code was improved according to code style checking rules. +- the integration with `org.owasp dependency-check-maven` was added. Thanks to [@saikrishna321](https://github.com/saikrishna321) + for the work. +- the integration with `org.jacoco jacoco-maven-plugin` was added. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the contribution. + +*3.4.1* +- Update to Selenium v2.53.0 +- all dependencies were updated to latest versions +- the dependency on org.apache.commons commons-lang3 v3.4 was added +- the fix of Widget method invocation.[#340](https://github.com/appium/java-client/issues/340). A class visibility was taken into account. Thanks to [aznime](https://github.com/aznime) for the catching. + Server flags were added: + - GeneralServerFlag.ASYNC_TRACE + - IOSServerFlag.WEBKIT_DEBUG_PROXY_PORT +- Source code was formatted using [eclipse-java-google-style.xml](https://google-styleguide.googlecode.com/svn/trunk/eclipse-java-google-style.xml). This is not the complete solution. The code style checking is going to be added further. Thanks to [SrinivasanTarget](https://github.com/SrinivasanTarget) for the work! + +*3.4.0* +- Update to Selenium v2.52.0 +- `getAppStrings()` methods are deprecated now. They are going to be removed. `getAppStringMap()` methods were added and now return a map with app strings (keys and values) + instead of a string. Thanks to [@rgonalo](https://github.com/rgonalo) for the contribution. +- Add `getAppStringMap(String language, String stringFile)` method to allow searching app strings in the specified file +- FIXED of the bug which causes deadlocks of AppiumDriver LocalService in multithreading. Thanks to [saikrishna321](https://github.com/saikrishna321) for the [bug report](https://github.com/appium/java-client/issues/283). +- FIXED Zoom methods, thanks to [@kkhaidukov](https://github.com/kkhaidukov) +- FIXED The issue of compatibility of AppiumServiceBuilder with Appium node server v >= 1.5.x. Take a look at [#305](https://github.com/appium/java-client/issues/305) +- `getDeviceTime()` was added. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the contribution. +- FIXED `longPressKeyCode()` methods. Now they use the convenient JSONWP command.Thanks to [@kirillbilchenko](https://github.com/kirillbilchenko) for the proposed fix. +- FIXED javadoc. +- Page object tools were updated. Details read here: [#311](https://github.com/appium/java-client/issues/311), [#313](https://github.com/appium/java-client/pull/313), [#317](https://github.com/appium/java-client/pull/317). By.name locator strategy is deprecated for Android and iOS. It is still valid for the Selendroid mode. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the helping. +- The method `lockScreen(seconds)` is deprecated and it is going to be removed in the next release. Since Appium node server v1.5.x it is recommended to use + `AndroidDriver.lockDevice()...AndroidDriver.unlockDevice()` or `IOSDriver.lockDevice(int seconds)` instead. Thanks to [@namannigam](https://github.com/namannigam) for + the catching. Read [#315](https://github.com/appium/java-client/issues/315) +- `maven-release-plugin` was added to POM.XML configuration +- [#320](https://github.com/appium/java-client/issues/320) fix. The `Widget.getSelfReference()` was added. This method allows to extract a real widget-object from inside a proxy at some extraordinary situations. Read: [PR](https://github.com/appium/java-client/pull/327). Thanks to [SergeyErmakovMercDev](https://github.com/SergeyErmakovMercDev) for the reporting. +- all capabilities were added according to [this description](https://github.com/appium/appium/blob/1.5/docs/en/writing-running-appium/caps.md). There are three classes: `io.appium.java_client.remote.MobileCapabilityType` (just modified), `io.appium.java_client.remote.AndroidMobileCapabilityType` (android-specific capabilities), `io.appium.java_client.remote.IOSMobileCapabilityType` (iOS-specific capabilities). Details are here: [#326](https://github.com/appium/java-client/pull/326) +- some server flags were marked `deprecated` because they are deprecated since server node v1.5.x. These flags are going to be removed at the java client release. Details are here: [#326](https://github.com/appium/java-client/pull/326) +- The ability to start Appium node programmatically using desired capabilities. This feature is compatible with Appium node server v >= 1.5.x. Details are here: [#326](https://github.com/appium/java-client/pull/326) + +*3.3.0* +- updated the dependency on Selenium to version 2.48.2 +- bug fix and enhancements of io.appium.java_client.service.local.AppiumDriverLocalService + - FIXED bug which was found and reproduced with Eclipse for Mac OS X. Please read about details here: [#252](https://github.com/appium/java-client/issues/252) + Thanks to [saikrishna321](https://github.com/saikrishna321) for the bug report + - FIXED bug which was found out by [Jonahss](https://github.com/Jonahss). Thanks for the reporting. Details: [#272](https://github.com/appium/java-client/issues/272) + and [#273](https://github.com/appium/java-client/issues/273) + - For starting an appium server using localService, added additional environment variable to specify the location of Node.js binary: NODE_BINARY_PATH + - The ability to set additional output streams was provided +- The additional __startActivity()__ method was added to AndroidDriver. It allows to start activities without the stopping of a target app + Thanks to [deadmoto](https://github.com/deadmoto) for the contribution +- The additional extension of the Page Object design pattern was designed. Please read about details here: [#267](https://github.com/appium/java-client/pull/267) +- New public constructors to AndroidDriver/IOSDriver that allow passing a custom HttpClient.Factory Details: [#276](https://github.com/appium/java-client/pull/278) thanks to [baechul](https://github.com/baechul) + +*3.2.0* +- updated the dependency on Selenium to version 2.47.1 +- the new dependency on commons-validator v1.4.1 +- the ability to start programmatically/silently an Appium node server is provided now. Details please read at [#240](https://github.com/appium/java-client/pull/240). + Historical reference: [The similar solution](https://github.com/Genium-Framework/Appium-Support) has been designed by [@Hassan-Radi](https://github.com/Hassan-Radi). + The mentioned framework and the current solution use different approaches. +- Throwing declarations were added to some searching methods. The __"getMouse"__ method of RemoteWebDriver was marked __Deprecated__ +- Add `replaceValue` method for elements. +- Replace `sendKeyEvent()` method in android with pressKeyCode(int key) and added: pressKeyCode(int key, Integer metastate), longPressKeyCode(int key), longPressKeyCode(int key, Integer metastate) + +*3.1.1* +- Page-object findBy strategies are now aware of which driver (iOS or Android) you are using. For more details see the Pull Request: https://github.com/appium/java-client/pull/213 +- If somebody desires to use their own Webdriver implementation then it has to implement HasCapabilities. +- Added a new annotation: `WithTimeout`. This annotation allows one to specify a specific timeout for finding an element which overrides the drivers default timeout. For more info see: https://github.com/appium/java-client/pull/210 +- Corrected an uninformative Exception message. + +*3.0.0* +- AppiumDriver class is now a Generic. This allows us to return elements of class MobileElement (and its subclasses) instead of always returning WebElements and requiring users to cast to MobileElement. See https://github.com/appium/java-client/pull/182 +- Full set of Android KeyEvents added. +- Selenium client version updated to 2.46 +- PageObject enhancements +- Junit dependency removed + +*2.2.0* +- Added new TouchAction methods for LongPress, on an element, at x,y coordinates, or at an offset from within an element +- SwipeElementDirection changed. Read the documentation, it's now smarter about how/where to swipe +- Added APPIUM_VERSION MobileCapabilityType +- `sendKeyEvent()` moved from AppiumDriver to AndroidDriver +- `linkText` and `partialLinkText` locators added +- setValue() moved from MobileElement to iOSElement +- Fixed Selendroid PageAnnotations + +*2.1.0* +- Moved hasAppString() from AndroidDriver to AppiumDriver +- Fixes to PageFactory +- Added @AndroidFindAll and @iOSFindAll +- Added toggleLocationServices() to AndroidDriver +- Added touchAction methods to MobileElement, so now you can do `element.pinch()`, `element.zoom()`, etc. +- Added the ability to choose a direction to swipe over an element. Use the `SwipeElementDirection` enums: `UP, DOWN, LEFT, RIGHT` + +*2.0.0* +- AppiumDriver is now an abstract class, use IOSDriver and AndroidDriver which both extend it. You no longer need to include the `PLATFORM_NAME` desired capability since it's automatic for each class. Thanks to @TikhomirovSergey for all their work +- ScrollTo() and ScrollToExact() methods reimplemented +- Zoom() and Pinch() are now a little smarter and less likely to fail if you element is near the edge of the screen. Congratulate @BJap on their first PR! + +*1.7.0* +- Removed `scrollTo()` and `scrollToExact()` methods because they relied on `complexFind()`. They will be added back in the next version! +- Removed `complexFind()` +- Added `startActivity()` method +- Added `isLocked()` method +- Added `getSettings()` and `ignoreUnimportantViews()` methods + +*1.6.2* +- Added MobilePlatform interface (Android, IOS, FirefoxOS) +- Added MobileBrowserType interface (Safari, Browser, Chromium, Chrome) +- Added MobileCapabilityType.APP_WAIT_ACTIVITY +- Fixed small Integer cast issue (in Eclipse it won't compile) +- Set -source and -target of the Java Compiler to 1.7 (for maven compiler plugin) +- Fixed bug in Page Factory + +*1.6.1* +- Fixed the logic for checking connection status on NetworkConnectionSetting objects + +*1.6.0* +- Added @findBy annotations. Explanation here: https://github.com/appium/java-client/pull/68 Thanks to TikhomirovSergey +- Appium Driver now implements LocationContext interface, so setLocation() works for setting GPS coordinates + +*1.5.0* +- Added MobileCapabilityType enums for desired capabilities +- `findElement` and `findElements` return MobileElement objects (still need to be casted, but no longer instantiated) +- new appium v1.2 `hideKeyboard()` strategies added +- `getNetworkConnection()` and `setNetworkConnection()` commands added + +*1.4.0* +- Added openNotifications() method, to open the notifications shade on Android +- Added pullFolder() method, to pull an entire folder as a zip archive from a device/simulator +- Upgraded Selenium dependency to 2.42.2 + +*1.3.0* +- MultiGesture with a single TouchAction fixed for Android +- Now depends upon Selenium java client 2.42.1 +- Cleanup of Errorcode handling, due to merging a change into Selenium + +*1.2.1* +- fix dependency issue + +*1.2.0* +- complexFind() now returns MobileElement objects +- added scrollTo() and scrollToExact() methods for use with complexFind() + +*1.1.0* +- AppiumDriver now implements Rotatable. rotate() and getOrientation() methods added +- when no appium server is running, the proper error is thrown, instead of a NullPointerException + +*1.0.2* +- recompiled to include some missing methods such as shake() and complexFind() diff --git a/README.md b/README.md index f09334c06..596d6e564 100644 --- a/README.md +++ b/README.md @@ -228,864 +228,8 @@ server process. Consider checking the [Appium Environment Troubleshooting](docs/ document for more information on how to debug and fix process environment issues. ## Changelog -*8.2.1* -- **[ENHANCEMENTS]** - - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) - - Connect directly to Appium Hosts in Distributed Environments. [#1747](https://github.com/appium/java-client/pull/1747) - - use own User Agent. [#1779](https://github.com/appium/java-client/pull/1779) - - Add alternative proxy implementation. [#1790](https://github.com/appium/java-client/pull/1790) - - Automated artefact publish to maven central. [#1803](https://github.com/appium/java-client/pull/1803) & [#1807](https://github.com/appium/java-client/pull/1807) -- **[BUG FIX]** - - Enforce usage of Base64 compliant with RFC 4648 for all operations. [#1785](https://github.com/appium/java-client/pull/1785) - - Override getScreenshotAs to support the legacy base64 encoding. [#1787](https://github.com/appium/java-client/pull/1787) -- **[REFRACTOR]** - - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) - - JUnit5 test classes and methods are updated to have default package visibility. [#1755](https://github.com/appium/java-client/pull/1755) - - Verify if the PR title complies with conventional commits spec. [#1757](https://github.com/appium/java-client/pull/1757) - - Use Lombok in direct connect class. [#1789](https://github.com/appium/java-client/pull/1789) - - Update readme and remove obsolete documents. [#1792](https://github.com/appium/java-client/pull/1792) - - Remove unnecessary annotation. [#1791](https://github.com/appium/java-client/pull/1791) - - Force unified imports order. [#1793](https://github.com/appium/java-client/pull/1793) -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 4.5.0. - - `org.owasp.dependencycheck` was updated to 7.3.2. - - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.1. - - `org.junit.jupiter:junit-jupiter` was updated to 5.9.1. - - `org.slf4j:slf4j-api` was updated to 2.0.4. - - `com.google.code.gson:gson` was updated to 2.10.0. -*8.2.0* -- **[ENHANCEMENTS]** - - AppiumDriverLocalService can handle outputStreams. [#1709](https://github.com/appium/java-client/pull/1709) - - Add creating a driver with ClientConfig. [#1735](https://github.com/appium/java-client/pull/1735) -- **[BUG FIX]** - - Update the environment argument type for mac SupportsEnvironmentOption. [#1712](https://github.com/appium/java-client/pull/1712) -- **[REFRACTOR]** - - Deprecate Appium ByAll in favour of Selenium ByAll. [#1740](https://github.com/appium/java-client/pull/1740) - - Bump Node.js version in pipeline. [#1713](https://github.com/appium/java-client/pull/1713) - - Switch unit tests to run on Junit 5 Jupiter Platform. [#1721](https://github.com/appium/java-client/pull/1721) - - Clean up unit tests asserting thrown exceptions. [#1741](https://github.com/appium/java-client/pull/1741) - - Fix open notification test. [#1749](https://github.com/appium/java-client/pull/1749) - - update Azure pipeline to use macos-11 VM image. [#1728](https://github.com/appium/java-client/pull/1728) -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 4.4.0. - - `org.owasp.dependencycheck` was updated to 7.1.2. - - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.0. - - `gradle` was updated to 7.5.1. - - `com.google.code.gson:gson` was updated to 2.9.1. -*8.1.1* -- **[BUG FIX]** - - Perform safe typecast while getting the platform name. [#1702](https://github.com/appium/java-client/pull/1702) - - Add prefix to platformVersion capability name. [#1704](https://github.com/appium/java-client/pull/1704) -- **[REFRACTOR]** - - Update e2e tests to make it green. [#1706](https://github.com/appium/java-client/pull/1706) - - Ignore the test which has a connected server issue. [#1699](https://github.com/appium/java-client/pull/1699) - -*8.1.0* -- **[ENHANCEMENTS]** - - Add new EspressoBuildConfig options. [#1687](https://github.com/appium/java-client/pull/1687) -- **[DOCUMENTATION]** - - delete all references to removed MobileElement class. [#1677](https://github.com/appium/java-client/pull/1677) -- **[BUG FIX]** - - Pass orientation name capability in uppercase. [#1686](https://github.com/appium/java-client/pull/1686) - - correction for ping method to get proper status URL. [#1661](https://github.com/appium/java-client/pull/1661) - - Remove deprecated option classes. [#1679](https://github.com/appium/java-client/pull/1679) - - Remove obsolete event firing decorators. [#1676](https://github.com/appium/java-client/pull/1676) -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 4.2.0. - - `org.owasp.dependencycheck` was updated to 7.1.0.1. - - `org.springframework:spring-context` was removed. [#1676](https://github.com/appium/java-client/pull/1676) - - `org.aspectj:aspectjweaver` was updated to 1.9.9. - - `io.github.bonigarcia:webdrivermanager` was updated to 5.2.0. - - `org.projectlombok:lombok` was updated to 1.18.24. - -*8.0.0* -- **[DOCUMENTATION]** - - Set minimum Java version to 1.8.0. [#1631](https://github.com/appium/java-client/pull/1631) -- **[BUG FIX]** - - Make interfaces public to fix decorator creation. [#1644](https://github.com/appium/java-client/pull/1644) - - Do not convert argument names to lowercase. [#1627](https://github.com/appium/java-client/pull/1627) - - Avoid fallback to css for id and name locator annotations. [#1622](https://github.com/appium/java-client/pull/1622) - - Fix handling of chinese characters in `AppiumDriverLocalService`. [#1618](https://github.com/appium/java-client/pull/1618) -- **[DEPENDENCY UPDATES]** - - `org.owasp.dependencycheck` was updated to 7.0.0. - - `org.springframework:spring-context` was updated to 5.3.16. - - `actions/setup-java` was updated to 3. - - `actions/checkout` was updated to 3. - - `io.github.bonigarcia:webdrivermanager` was updated to 5.1.0. - - `org.aspectj:aspectjweaver` was updated to 1.9.8. - - `org.slf4j:slf4j-api` was updated to 1.7.36. - - `com.github.johnrengelman.shadow` was updated to 7.1.2. - -*8.0.0-beta2* -- **[DOCUMENTATION]** - - Add a link to options builder examples to the migration guide. [#1595](https://github.com/appium/java-client/pull/1595) -- **[BUG FIX]** - - Filter out proxyClassLookup method from Proxy class (for Java 16+) in AppiumByBuilder. [#1575](https://github.com/appium/java-client/pull/1575) -- **[REFRACTOR]** - - Add more nice functional stuff into page factory helpers. [#1584](https://github.com/appium/java-client/pull/1584) - - Switch e2e tests to use Appium2. [#1603](https://github.com/appium/java-client/pull/1603) - - relax constraints of Selenium dependencies versions. [#1606](https://github.com/appium/java-client/pull/1606) -- **[DEPENDENCY UPDATES]** - - Upgrade to Selenium 4.1.1. [#1613](https://github.com/appium/java-client/pull/1613) - - `org.owasp.dependencycheck` was updated to 6.5.1. - - `org.springframework:spring-context` was updated to 5.3.14. - - `actions/setup-java` was updated to 2.4.0. - - `gradle` was updated to 7.3. - -*8.0.0-beta* -- **[ENHANCEMENTS]** - - Start adding UiAutomator2 options. [#1543](https://github.com/appium/java-client/pull/1543) - - Add more UiAutomator2 options. [#1545](https://github.com/appium/java-client/pull/1545) - - Finish creating options for UiAutomator2 driver. [#1548](https://github.com/appium/java-client/pull/1548) - - Add WDA-related XCUITestOptions. [#1552](https://github.com/appium/java-client/pull/1552) - - Add web view options for XCUITest driver. [#1557](https://github.com/appium/java-client/pull/1557) - - Add the rest of XCUITest driver options. [#1561](https://github.com/appium/java-client/pull/1561) - - Add Espresso options. [#1563](https://github.com/appium/java-client/pull/1563) - - Add Windows driver options. [#1564](https://github.com/appium/java-client/pull/1564) - - Add Mac2 driver options. [#1565](https://github.com/appium/java-client/pull/1565) - - Add Gecko driver options. [#1573](https://github.com/appium/java-client/pull/1573) - - Add Safari driver options. [#1576](https://github.com/appium/java-client/pull/1576) - - Start adding XCUITest driver options. [#1551](https://github.com/appium/java-client/pull/1551) - - Implement driver-specific W3C option classes. [#1540](https://github.com/appium/java-client/pull/1540) - - Update Service to properly work with options. [#1550](https://github.com/appium/java-client/pull/1550) -- **[BREAKING CHANGE]** - - Migrate to Selenium 4. [#1531](https://github.com/appium/java-client/pull/1531) - - Make sure we only write W3C payload into create session command. [#1537](https://github.com/appium/java-client/pull/1537) - - Use the new session payload creator inherited from Selenium. [#1535](https://github.com/appium/java-client/pull/1535) - - unify locator factories naming and toString implementations. [#1538](https://github.com/appium/java-client/pull/1538) - - drop support of deprecated Selendroid driver. [#1553](https://github.com/appium/java-client/pull/1553) - - switch to javac compiler. [#1556](https://github.com/appium/java-client/pull/1556) - - revise used Selenium dependencies. [#1560](https://github.com/appium/java-client/pull/1560) - - change prefix to AppiumBy in locator toString implementation. [#1559](https://github.com/appium/java-client/pull/1559) - - enable dependencies caching. [#1567](https://github.com/appium/java-client/pull/1567) - - Include more tests into the pipeline. [#1566](https://github.com/appium/java-client/pull/1566) - - Tune setting of default platform names. [#1570](https://github.com/appium/java-client/pull/1570) - - Deprecate custom event listener implementation and default to the one provided by Selenium4. [#1541](https://github.com/appium/java-client/pull/1541) - - Deprecate touch actions. [#1569](https://github.com/appium/java-client/pull/1569) - - Deprecate legacy app management helpers. [#1571](https://github.com/appium/java-client/pull/1571) - - deprecate Windows UIAutomation selector. [#1562](https://github.com/appium/java-client/pull/1562) - - Remove unused entities. [#1572](https://github.com/appium/java-client/pull/1572) - - Remove setElementValue helper. [#1577](https://github.com/appium/java-client/pull/1577) - - Remove selenium package override. [#1555](https://github.com/appium/java-client/pull/1555) - - remove redundant exclusion of Gradle task signMavenJavaPublication. [#1568](https://github.com/appium/java-client/pull/1568) -- **[DEPENDENCY UPDATES]** - - `org.owasp.dependencycheck` was updated to 6.4.1. - - `com.google.code.gson:gson` was updated to 2.8.9. - -*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) -- **[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) - - 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) -- **[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) - - 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) - - Update Appium executable detection implementation. [#1256](https://github.com/appium/java-client/pull/1256) - - Avoid through NPE if any setting value is null. [#1241](https://github.com/appium/java-client/pull/1241) - - Settings API was improved to accept string names. [#1240](https://github.com/appium/java-client/pull/1240) - - Switch `runAppInBackground` iOS implementation in sync with other platforms. [#1229](https://github.com/appium/java-client/pull/1229) - - JavaDocs for AndroidMobileCapabilityType was updated. [#1238](https://github.com/appium/java-client/pull/1238) - - Github Actions were introduced instead of TravisCI. [#1219](https://github.com/appium/java-client/pull/1219) -- **[BUG FIX]** - - Fix return type of `getSystemBars` API. [#1216](https://github.com/appium/java-client/pull/1216) - - Avoid using `getSession` call for capabilities values retrieval [W3C Support]. [#1204](https://github.com/appium/java-client/pull/1204) - - Fix pagefactory list element initialisation when parameterised by generic type. [#1237](https://github.com/appium/java-client/pull/1237) - - Fix AndroidKey commands. [#1250](https://github.com/appium/java-client/pull/1250) - -*7.2.0* -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was reverted to stable version 3.141.59. [#1209](https://github.com/appium/java-client/pull/1209) - - `org.projectlombok:lombok:1.18.8` was introduced. [#1193](https://github.com/appium/java-client/pull/1193) -- **[ENHANCEMENTS]** - - `videoFilters` property was added to IOSStartScreenRecordingOptions. [#1180](https://github.com/appium/java-client/pull/1180) -- **[IMPROVEMENTS]** - - `Selendroid` automationName was deprecated. [#1198](https://github.com/appium/java-client/pull/1198) - - JavaDocs for AndroidMobileCapabilityType and IOSMobileCapabilityType were updated. [#1204](https://github.com/appium/java-client/pull/1204) - - JitPack builds were fixed. [#1203](https://github.com/appium/java-client/pull/1203) - -*7.1.0* -- **[ENHANCEMENTS]** - - Added an ability to get all the session details. [#1167 ](https://github.com/appium/java-client/pull/1167) - - `TRACK_SCROLL_EVENTS`, `ALLOW_INVISIBLE_ELEMENTS`, `ENABLE_NOTIFICATION_LISTENER`, - `NORMALIZE_TAG_NAMES` and `SHUTDOWN_ON_POWER_DISCONNECT` Android Settings were added. - - `KEYBOARD_AUTOCORRECTION`, `MJPEG_SCALING_FACTOR`, - `MJPEG_SERVER_SCREENSHOT_QUALITY`, `MJPEG_SERVER_FRAMERATE`, `SCREENSHOT_QUALITY` - and `KEYBOARD_PREDICTION` iOS Settings were added. - - `GET_MATCHED_IMAGE_RESULT`, `FIX_IMAGE_TEMPLATE_SCALE`, - `SHOULD_USE_COMPACT_RESPONSES`, `ELEMENT_RESPONSE_ATTRIBUTES` and - `DEFAULT_IMAGE_TEMPLATE_SCALE` settings were added for both Android and iOS [#1166](https://github.com/appium/java-client/pull/1166), [#1156 ](https://github.com/appium/java-client/pull/1156) and [#1120](https://github.com/appium/java-client/pull/1120) - - The new interface `io.appium.java_client.ExecutesDriverScript ` was added. [#1165](https://github.com/appium/java-client/pull/1165) - - Added an ability to get status of appium server. [#1153 ](https://github.com/appium/java-client/pull/1153) - - `tvOS` platform support was added. [#1142 ](https://github.com/appium/java-client/pull/1142) - - The new interface `io.appium.java_client. FindsByAndroidDataMatcher` was added. [#1106](https://github.com/appium/java-client/pull/1106) - - The selector strategy `io.appium.java_client.MobileBy.ByAndroidDataMatcher` was added. [#1106](https://github.com/appium/java-client/pull/1106) - - Selendroid for android and UIAutomation for iOS are removed. [#1077 ](https://github.com/appium/java-client/pull/1077) - - **[BUG FIX]** Platform Name enforced on driver creation is avoided now. [#1164 ](https://github.com/appium/java-client/pull/1164) - - **[BUG FIX]** Send both signalStrengh and signalStrength for `GSM_SIGNAL`. [#1115 ](https://github.com/appium/java-client/pull/1115) - - **[BUG FIX]** Null pointer exceptions when calling getCapabilities is handled better. [#1094 ](https://github.com/appium/java-client/pull/1094) - -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 4.0.0-alpha-1. - - `org.aspectj:aspectjweaver` was updated to 1.9.4. - - `org.apache.httpcomponents:httpclient` was updated to 4.5.9. - - `cglib:cglib` was updated to 3.2.12. - - `org.springframework:spring-context` was updated to 5.1.8.RELEASE. - - `io.github.bonigarcia:webdrivermanager` was updated to 3.6.1. - - `org.eclipse.jdt:ecj` was updated to 3.18.0. - - `com.github.jengelman.gradle.plugins:shadow` was updated to 5.1.0. - - `checkstyle` was updated to 8.22. - - `gradle` was updated to 5.4. - - `dependency-check-gradle` was updated to 5.1.0. - - `org.slf4j:slf4j-api` was updated to 1.7.26. - - `org.apache.commons:commons-lang3` was updated to 3.9. - -*7.0.0* -- **[ENHANCEMENTS]** - - The new interface `io.appium.java_client.FindsByAndroidViewTag` was added. [#996](https://github.com/appium/java-client/pull/996) - - The selector strategy `io.appium.java_client.MobileBy.ByAndroidViewTag` was added. [#996](https://github.com/appium/java-client/pull/996) - - The new interface `io.appium.java_client.FindsByImage` was added. [#990](https://github.com/appium/java-client/pull/990) - - The selector strategy `io.appium.java_client.MobileBy.ByImage` was added. [#990](https://github.com/appium/java-client/pull/990) - - The new interface `io.appium.java_client.FindsByCustom` was added. [#1041](https://github.com/appium/java-client/pull/1041) - - The selector strategy `io.appium.java_client.MobileBy.ByCustom` was added. [#1041](https://github.com/appium/java-client/pull/1041) - - DatatypeConverter is replaced with Base64 for JDK 9 compatibility. [#999](https://github.com/appium/java-client/pull/999) - - Expand touch options API to accept coordinates as Point. [#997](https://github.com/appium/java-client/pull/997) - - W3C capabilities written into firstMatch entity instead of alwaysMatch. [#1010](https://github.com/appium/java-client/pull/1010) - - `Selendroid` for android and `UIAutomation` for iOS is deprecated. [#1034](https://github.com/appium/java-client/pull/1034) and [#1074](https://github.com/appium/java-client/pull/1074) - - `videoScale` and `fps` screen recording options are introduced for iOS. [#1067](https://github.com/appium/java-client/pull/1067) - - `NORMALIZE_TAG_NAMES` setting was introduced for android. [#1073](https://github.com/appium/java-client/pull/1073) - - `threshold` argument was added to OccurrenceMatchingOptions. [#1060](https://github.com/appium/java-client/pull/1060) - - `org.openqa.selenium.internal.WrapsElement` replaced by `org.openqa.selenium.WrapsElement`. [#1053](https://github.com/appium/java-client/pull/1053) - - SLF4J logging support added into Appium Driver local service. [#1014](https://github.com/appium/java-client/pull/1014) - - `IMAGE_MATCH_THRESHOLD`, `FIX_IMAGE_FIND_SCREENSHOT_DIMENSIONS`, `FIX_IMAGE_TEMPLATE_SIZE`, `CHECK_IMAGE_ELEMENT_STALENESS`, `UPDATE_IMAGE_ELEMENT_POSITION` and `IMAGE_ELEMENT_TAP_STRATEGY` setting was introduced for image elements. [#1011](https://github.com/appium/java-client/pull/1011) -- **[BUG FIX]** Better handling of InvocationTargetException [#968](https://github.com/appium/java-client/pull/968) -- **[BUG FIX]** Map sending keys to active element for W3C compatibility. [#966](https://github.com/appium/java-client/pull/966) -- **[BUG FIX]** Error message on session creation is improved. [#994](https://github.com/appium/java-client/pull/994) -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 3.141.59. - - `com.google.code.gson:gson` was updated to 2.8.5. - - `org.apache.httpcomponents:httpclient` was updated to 4.5.6. - - `cglib:cglib` was updated to 3.2.8. - - `org.apache.commons:commons-lang3` was updated to 3.8. - - `org.springframework:spring-context` was updated to 5.1.0.RELEASE. - - `io.github.bonigarcia:webdrivermanager` was updated to 3.0.0. - - `org.eclipse.jdt:ecj` was updated to 3.14.0. - - `org.slf4j:slf4j-api` was updated to 1.7.25. - - `jacoco` was updated to 0.8.2. - - `checkstyle` was updated to 8.12. - - `gradle` was updated to 4.10.1. - - `org.openpnp:opencv` was removed. - -*6.1.0* -- **[BUG FIX]** Initing web socket clients lazily. Report [#911](https://github.com/appium/java-client/issues/911). FIX: [#912](https://github.com/appium/java-client/pull/912). -- **[BUG FIX]** Fix session payload for W3C. [#913](https://github.com/appium/java-client/pull/913) -- **[ENHANCEMENT]** Added TouchAction constructor argument verification [#923](https://github.com/appium/java-client/pull/923) -- **[BUG FIX]** Set retry flag to true by default for OkHttpFactory. [#928](https://github.com/appium/java-client/pull/928) -- **[BUG FIX]** Fix class cast exception on getting battery info. [#935](https://github.com/appium/java-client/pull/935) -- **[ENHANCEMENT]** Added an optional format argument to getDeviceTime and update the documentation. [#939](https://github.com/appium/java-client/pull/939) -- **[ENHANCEMENT]** The switching web socket client implementation to okhttp library. [#941](https://github.com/appium/java-client/pull/941) -- **[BUG FIX]** Fix of the bug [#924](https://github.com/appium/java-client/issues/924). [#951](https://github.com/appium/java-client/pull/951) - -*6.0.0* -- **[ENHANCEMENT]** Added an ability to set pressure value for iOS. [#879](https://github.com/appium/java-client/pull/879) -- **[ENHANCEMENT]** Added new server arguments `RELAXED_SECURITY` and `ENABLE_HEAP_DUMP`. [#880](https://github.com/appium/java-client/pull/880) -- **[BUG FIX]** Use default Selenium HTTP client factory [#877](https://github.com/appium/java-client/pull/877) -- **[ENHANCEMENT]** Supporting syslog broadcast with iOS [#871](https://github.com/appium/java-client/pull/871) -- **[ENHANCEMENT]** Added isKeyboardShown command for iOS [#887](https://github.com/appium/java-client/pull/887) -- **[ENHANCEMENT]** Added battery information accessors [#882](https://github.com/appium/java-client/pull/882) -- **[BREAKING CHANGE]** Removal of deprecated code. [#881](https://github.com/appium/java-client/pull/881) -- **[BUG FIX]** Added `NewAppiumSessionPayload`. Bug report: [#875](https://github.com/appium/java-client/issues/875). FIX: [#894](https://github.com/appium/java-client/pull/894) -- **[ENHANCEMENT]** Added ESPRESSO automation name [#908](https://github.com/appium/java-client/pull/908) -- **[ENHANCEMENT]** Added a method for output streams cleanup [#909](https://github.com/appium/java-client/pull/909) -- **[DEPENDENCY UPDATES]** - - `com.google.code.gson:gson` was updated to 2.8.4 - - `org.springframework:spring-context` was updated to 5.0.5.RELEASE - - `org.aspectj:aspectjweaver` was updated to 1.9.1 - - `org.glassfish.tyrus:tyrus-clien` was updated to 1.13.1 - - `org.glassfish.tyrus:tyrus-container-grizzly` was updated to 1.2.1 - - `org.seleniumhq.selenium:selenium-java` was updated to 3.12.0 - - -*6.0.0-BETA5* -- **[ENHANCEMENT]** Added clipboard handlers. [#855](https://github.com/appium/java-client/pull/855) [#869](https://github.com/appium/java-client/pull/869) -- **[ENHANCEMENT]** Added wrappers for Android logcat broadcaster. [#858](https://github.com/appium/java-client/pull/858) -- **[ENHANCEMENT]** Add bugreport option to Android screen recorder. [#852](https://github.com/appium/java-client/pull/852) -- **[BUG FIX]** Avoid amending parameters for SET_ALERT_VALUE endpoint. [#867](https://github.com/appium/java-client/pull/867) -- **[BREAKING CHANGE]** Refactor network connection setting on Android. [#865](https://github.com/appium/java-client/pull/865) -- **[BUG FIX]** **[BREAKING CHANGE]** Refactor of the `io.appium.java_client.AppiumFluentWait`. It uses `java.time.Duration` for time settings instead of `org.openqa.selenium.support.ui.Duration` and `java.util.concurrent.TimeUnit` [#863](https://github.com/appium/java-client/pull/863) -- **[BREAKING CHANGE]** `io.appium.java_client.pagefactory.TimeOutDuration` became deprecated. It is going to be removed. Use `java.time.Duration` instead. FIX [#742](https://github.com/appium/java-client/issues/742) [#863](https://github.com/appium/java-client/pull/863). -- **[BREAKING CHANGE]** `io.appium.java_client.pagefactory.WithTimeOut#unit` became deprecated. It is going to be removed. Use `io.appium.java_client.pagefactory.WithTimeOut#chronoUnit` instead. FIX [#742](https://github.com/appium/java-client/issues/742) [#863](https://github.com/appium/java-client/pull/863). -- **[BREAKING CHANGE]** constructors of `io.appium.java_client.pagefactory.AppiumElementLocatorFactory`, `io.appium.java_client.pagefactory.AppiumFieldDecorator` and `io.appium.java_client.pagefactory.AppiumElementLocator` which use `io.appium.java_client.pagefactory.TimeOutDuration` as a parameter became deprecated. Use new constructors which use `java.time.Duration`. -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 3.11.0 - -*6.0.0-BETA4* -- **[ENHANCEMENT]** Added handler for isDispalyed in W3C mode. [#833](https://github.com/appium/java-client/pull/833) -- **[ENHANCEMENT]** Added handlers for sending SMS, making GSM Call, setting GSM signal, voice, power capacity and power AC. [#834](https://github.com/appium/java-client/pull/834) -- **[ENHANCEMENT]** Added handlers for toggling wifi, airplane mode and data in android. [#835](https://github.com/appium/java-client/pull/835) -- **[DEPENDENCY UPDATES]** - - `org.apache.httpcomponents:httpclient` was updated to 4.5.5 - - `cglib:cglib` was updated to 3.2.6 - - `org.springframework:spring-context` was updated to 5.0.3.RELEASE - -*6.0.0-BETA3* -- **[DEPENDENCY UPDATES]** - - `org.seleniumhq.selenium:selenium-java` was updated to 3.9.1 -- **[BREAKING CHANGE]** Removal of deprecated listener-methods from the AlertEventListener. [#797](https://github.com/appium/java-client/pull/797) -- **[BUG FIX]**. Fix the `pushFile` command. [#812](https://github.com/appium/java-client/pull/812) [#816](https://github.com/appium/java-client/pull/816) -- **[ENHANCEMENT]**. Implemented custom command codec. [#817](https://github.com/appium/java-client/pull/817), [#825](https://github.com/appium/java-client/pull/825) -- **[ENHANCEMENT]** Added handlers for lock/unlock in iOS. [#799](https://github.com/appium/java-client/pull/799) -- **[ENHANCEMENT]** AddEd endpoints for screen recording API for iOS and Android. [#814](https://github.com/appium/java-client/pull/814) -- **[MAJOR ENHANCEMENT]** W3C compliance was provided. [#829](https://github.com/appium/java-client/pull/829) -- **[ENHANCEMENT]** New capability `MobileCapabilityType.FORCE_MJSONWP` [#829](https://github.com/appium/java-client/pull/829) -- **[ENHANCEMENT]** Updated applications management endpoints. [#824](https://github.com/appium/java-client/pull/824) - -*6.0.0-BETA2* -- **[ENHANCEMENT]** The `fingerPrint` ability was added. It is supported by Android for now. [#473](https://github.com/appium/java-client/pull/473) [#786](https://github.com/appium/java-client/pull/786) -- **[BUG FIX]**. Less strict verification of the `PointOption`. [#795](https://github.com/appium/java-client/pull/795) - -*6.0.0-BETA1* -- **[ENHANCEMENT]** **[REFACTOR]** **[BREAKING CHANGE]** **[MAJOR CHANGE]** Improvements of the TouchActions API [#756](https://github.com/appium/java-client/pull/756), [#760](https://github.com/appium/java-client/pull/760): - - `io.appium.java_client.touch.ActionOptions` and sublasses were added - - old methods of the `TouchActions` were marked `@Deprecated` - - new methods which take new options. -- **[ENHANCEMENT]**. Appium drivr local service uses default process environment by default. [#753](https://github.com/appium/java-client/pull/753) -- **[BUG FIX]**. Removed 'set' prefix from waitForIdleTimeout setting. [#754](https://github.com/appium/java-client/pull/754) -- **[BUG FIX]**. The asking for session details was optimized. Issue report [764](https://github.com/appium/java-client/issues/764). -FIX [#769](https://github.com/appium/java-client/pull/769) -- **[BUG FIX]** **[REFACTOR]**. Inconcistent MissingParameterException was removed. Improvements of MultiTouchAction. Report: [#102](https://github.com/appium/java-client/issues/102). FIX [#772](https://github.com/appium/java-client/pull/772) -- **[DEPENDENCY UPDATES]** - - `org.apache.commons:commons-lang3` was updated to 3.7 - - `commons-io:commons-io` was updated to 2.6 - - `org.springframework:spring-context` was updated to 5.0.2.RELEASE - - `org.aspectj:aspectjweaver` was updated to 1.8.13 - - `org.seleniumhq.selenium:selenium-java` was updated to 3.7.1 - -*5.0.4* -- **[BUG FIX]**. Client was crashing when user was testing iOS with server 1.7.0. Report: [#732](https://github.com/appium/java-client/issues/732). Fix: [#733](https://github.com/appium/java-client/pull/733). -- **[REFACTOR]** **[BREAKING CHANGE]** Excessive invocation of the implicit waiting timeout was removed. This is the breaking change because API of `AppiumElementLocator` and `AppiumElementLocatorFactory` was changed. Request: [#735](https://github.com/appium/java-client/issues/735), FIXES: [#738](https://github.com/appium/java-client/pull/738), [#741](https://github.com/appium/java-client/pull/741) -- **[DEPENDENCY UPDATES]** - - org.seleniumhq.selenium:selenium-java to 3.6.0 - - com.google.code.gson:gson to 2.8.2 - - org.springframework:spring-context to 5.0.0.RELEASE - - org.aspectj:aspectjweaver to 1.8.11 - -*5.0.3* -- **[BUG FIX]** Selenuim version was reverted from boundaries to the single number. Issue report: [#718](https://github.com/appium/java-client/issues/718). FIX: [#722](https://github.com/appium/java-client/pull/722) -- **[ENHANCEMENT]** The `pushFile` was added to IOSDriver. Feature request: [#720](https://github.com/appium/java-client/issues/720). Implementation: [#721](https://github.com/appium/java-client/pull/721). This feature requires appium node server v>=1.7.0 - -*5.0.2* **[BUG FIX RELEASE]** -- **[BUG FIX]** Dependency conflict resolving. The report: [#714](https://github.com/appium/java-client/issues/714). The fix: [#717](https://github.com/appium/java-client/pull/717). This change may affect users who use htmlunit-driver and/or phantomjsdriver. At this case it is necessary to add it to dependency list and to exclude old selenium versions. - -*5.0.1* **[BUG FIX RELEASE]** -- **[BUG FIX]** The fix of the element genering on iOS was fixed. Issue report: [#704](https://github.com/appium/java-client/issues/704). Fix: [#705](https://github.com/appium/java-client/pull/705) - -*5.0.0* -- **[REFACTOR]** **[BREAKING CHANGE]** 5.0.0 finalization. Removal of obsolete code. [#660](https://github.com/appium/java-client/pull/660) -- **[ENHANCEMENT]** Enable nativeWebTap setting for iOS. [#658](https://github.com/appium/java-client/pull/658) -- **[ENHANCEMENT]** The `getCurrentPackage` was added. [#657](https://github.com/appium/java-client/pull/657) -- **[ENHANCEMENT]** The `toggleTouchIDEnrollment` was added. [#659](https://github.com/appium/java-client/pull/659) -- **[BUG FIX]** The clearing of existing actions/parameters after perform is invoked. [#663](https://github.com/appium/java-client/pull/663) -- **[BUG FIX]** [#669](https://github.com/appium/java-client/pull/669) missed parameters of the `OverrideWidget` were added: - - `iOSXCUITAutomation` - - `windowsAutomation` -- **[BUG FIX]** ByAll was re-implemented. [#680](https://github.com/appium/java-client/pull/680) -- **[BUG FIX]** **[BREAKING CHANGE]** The issue of compliance with Selenium grid 3.x was fixed. This change is breaking because now java_client is compatible with appiun server v>=1.6.5. Issue report [#655](https://github.com/appium/java-client/issues/655). FIX [#682](https://github.com/appium/java-client/pull/682) -- **[BUG FIX]** issues related to latest Selenium changes were fixed. Issue report [#696](https://github.com/appium/java-client/issues/696). Fix: [#699](https://github.com/appium/java-client/pull/699). -- **[UPDATE]** Dependency update - - `selenium-java` was updated to 3.5.x - - `org.apache.commons-lang3` was updated to 3.6 - - `org.springframework.spring-context` was updated to 4.3.10.RELEASE -- **[ENHANCEMENT]** Update of the touch ID enroll method. The older `PerformsTouchID#toggleTouchIDEnrollment` was marked `Deprecated`. -It is recoomended to use `PerformsTouchID#toggleTouchIDEnrollment(boolean)` instead. [#695](https://github.com/appium/java-client/pull/695) - - -*5.0.0-BETA9* -- **[ENHANCEMENT]** Page factory: Mixed locator strategies were implemented. Feature request:[#565](https://github.com/appium/java-client/issues/565) Implementation: [#646](https://github.com/appium/java-client/pull/646) -- **[DEPRECATED]** All the content of the `io.appium.java_client.youiengine` package was marked `Deprecated`. It is going to be removed. [#652](https://github.com/appium/java-client/pull/652) -- **[UPDATE]** Update of the `com.google.code.gson:gson` to v2.8.1. - -*5.0.0-BETA8* -- **[ENHANCEMENT]** Page factory classes became which had package visibility are `public` now. [#630](https://github.com/appium/java-client/pull/630) - - `io.appium.java_client.pagefactory.AppiumElementLocatorFactory` - - `io.appium.java_client.pagefactory.DefaultElementByBuilder` - - `io.appium.java_client.pagefactory.WidgetByBuilder` - -- **[ENHANCEMENT]** New capabilities were added [#626](https://github.com/appium/java-client/pull/626): - - `AndroidMobileCapabilityType#AUTO_GRANT_PERMISSIONS` - - `AndroidMobileCapabilityType#ANDROID_NATURAL_ORIENTATION` - - `IOSMobileCapabilityType#XCODE_ORG_ID` - - `IOSMobileCapabilityType#XCODE_SIGNING_ID` - - `IOSMobileCapabilityType#UPDATE_WDA_BUNDLEID` - - `IOSMobileCapabilityType#RESET_ON_SESSION_START_ONLY` - - `IOSMobileCapabilityType#COMMAND_TIMEOUTS` - - `IOSMobileCapabilityType#WDA_STARTUP_RETRIES` - - `IOSMobileCapabilityType#WDA_STARTUP_RETRY_INTERVAL` - - `IOSMobileCapabilityType#CONNECT_HARDWARE_KEYBOARD` - - `IOSMobileCapabilityType#MAX_TYPING_FREQUENCY` - - `IOSMobileCapabilityType#SIMPLE_ISVISIBLE_CHECK` - - `IOSMobileCapabilityType#USE_CARTHAGE_SSL` - - `IOSMobileCapabilityType#SHOULD_USE_SINGLETON_TESTMANAGER` - - `IOSMobileCapabilityType#START_IWDP` - - `IOSMobileCapabilityType#ALLOW_TOUCHID_ENROLL` - - `MobileCapabilityType#EVENT_TIMINGS` - -- **[UPDATE]** Dependencies were updated: - - `org.seleniumhq.selenium:selenium-java` was updated to 3.4.0 - - `cglib:cglib` was updated to 3.2.5 - - `org.apache.httpcomponents:httpclient` was updated to 4.5.3 - - `commons-validator:commons-validator` was updated to 1.6 - - `org.springframework:spring-context` was updated to 4.3.8.RELEASE - - -*5.0.0-BETA7* -- **[ENHANCEMENT]** The ability to customize the polling strategy of the waiting was provided. [#612](https://github.com/appium/java-client/pull/612) -- **[ENHANCEMENT]** **[REFACTOR]** Methods which were representing time deltas instead of elementary types became `Deprecated`. Methods which use `java.time.Duration` are suugested to be used. [#611](https://github.com/appium/java-client/pull/611) -- **[ENHANCEMENT]** The ability to calculate screenshots overlap was included. [#595](https://github.com/appium/java-client/pull/595). - - -*5.0.0-BETA6* -- **[UPDATE]** Update to Selenium 3.3.1 -- **[ENHANCEMENT]** iOS XCUIT mode automation: API to run application in background was added. [#593](https://github.com/appium/java-client/pull/593) -- **[BUG FIX]** Issue report: [#594](https://github.com/appium/java-client/issues/594). FIX: [#597](https://github.com/appium/java-client/pull/597) -- **[ENHANCEMENT]** The class chain locator was added. [#599](https://github.com/appium/java-client/pull/599) - - -*5.0.0-BETA5* -- **[UPDATE]** Update to Selenium 3.2.0 -- **[BUG FIX]** Excessive dependency on `guava` was removed. It causes errors. Issue report: [#588](https://github.com/appium/java-client/issues/588). FIX: [#589](https://github.com/appium/java-client/pull/589). -- **[ENHANCEMENT]**. The capability `io.appium.java_client.remote.AndroidMobileCapabilityType#SYSTEM_PORT` was added. [#591](https://github.com/appium/java-client/pull/591) - -*5.0.0-BETA4* -- **[ENHANCEMENT]** Android. API to read the performance data was added. [#562](https://github.com/appium/java-client/pull/562) -- **[REFACTOR]** Android. Simplified the activity starting by reducing the number of parameters through POJO clas. Old methods which start activities were marked `@Deprecated`. [#579](https://github.com/appium/java-client/pull/579) [#585](https://github.com/appium/java-client/pull/585) -- **[BUG FIX]** Issue report:[#574](https://github.com/appium/java-client/issues/574). Fix:[#582](https://github.com/appium/java-client/pull/582) - -*5.0.0-BETA3* -[BUG FIX] -- **[BUG FIX]**:Issue report: [#567](https://github.com/appium/java-client/issues/567). Fix: [#568](https://github.com/appium/java-client/pull/568) - -*5.0.0-BETA2* -- **[BUG FIX]**:Issue report: [#549](https://github.com/appium/java-client/issues/549). Fix: [#551](https://github.com/appium/java-client/pull/551) -- New capabilities were added [#533](https://github.com/appium/java-client/pull/553): - - `IOSMobileCapabilityType#USE_NEW_WDA` - - `IOSMobileCapabilityType#WDA_LAUNCH_TIMEOUT` - - `IOSMobileCapabilityType#WDA_CONNECTION_TIMEOUT` - -The capability `IOSMobileCapabilityType#REAL_DEVICE_LOGGER` was removed. [#533](https://github.com/appium/java-client/pull/553) - -- **[BUG FIX]/[ENHANCEMENT]**. Issue report: [#552](https://github.com/appium/java-client/issues/552). FIX [#556](https://github.com/appium/java-client/pull/556) - - Additional methods were added to the `io.appium.java_client.HasSessionDetails` - - `String getPlatformName()` - - `String getAutomationName()` - - `boolean isBrowser()` - - `io.appium.java_client.HasSessionDetails` is used by the ` io.appium.java_client.internal.JsonToMobileElementConverter ` to define which instance of the `org.openqa.selenium.WebElement` subclass should be created. - -- **[ENHANCEMENT]**: The additional event firing feature. PR: [#559](https://github.com/appium/java-client/pull/559). The [WIKI chapter about the event firing](https://github.com/appium/java-client/blob/master/docs/The-event_firing.md) was updated. - -*5.0.0-BETA1* -- **[MAJOR ENHANCEMENT]**: Migration to Java 8. Epic: [#399](https://github.com/appium/java-client/issues/399) - - API with default implementation. PR [#470](https://github.com/appium/java-client/pull/470) - - Tools that provide _Page Object_ engines were redesigned. The migration to [repeatable annotations](http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html). Details you can read there: [#497](https://github.com/appium/java-client/pull/497). [Documentation was synced as well](https://github.com/appium/java-client/blob/master/docs/Page-objects.md#also-it-is-possible-to-define-chained-or-any-possible-locators). - - The new functional interface `io.appium.java_client.functions.AppiumFunctio`n was designed. It extends `java.util.function.Function` and `com.google.common.base.Function`. It was designed in order to provide compatibility with the `org.openqa.selenium.support.ui.Wait` [#543](https://github.com/appium/java-client/pull/543) - - The new functional interface `io.appium.java_client.functions.ExpectedCondition` was designed. It extends `io.appium.java_client.functions.AppiumFunction` and ```org.openqa.selenium.support.ui.ExpectedCondition```. [#543](https://github.com/appium/java-client/pull/543) - - The new functional interface `io.appium.java_client.functions.ActionSupplier` was designed. It extends ```java.util.function.Supplier```. [#543](https://github.com/appium/java-client/pull/543) - -- **[MAJOR ENHANCEMENT]**: Migration from Maven to Gradle. Feature request is [#214](https://github.com/appium/java-client/issues/214). Fixes: [#442](https://github.com/appium/java-client/pull/442), [#465](https://github.com/appium/java-client/pull/465). - -- **[MAJOR ENHANCEMENT]** **[MAJOR REFACTORING]**. Non-abstract **AppiumDriver**: - - Now the `io.appium.java_client.AppiumDriver` can use an instance of any `io.appium.java_client.MobileBy` subclass for the searching. It should work as expected when current session supports the given selector. It will throw `org.openqa.selenium.WebDriverException` otherwise. [#462](https://github.com/appium/java-client/pull/462) - - The new interface `io.appium.java_client.FindsByFluentSelector` was added. [#462](https://github.com/appium/java-client/pull/462) - - API was redesigned: - - these interfaces were marked deprecated and they are going to be removed [#513](https://github.com/appium/java-client/pull/513)[#514](https://github.com/appium/java-client/pull/514): - - `io.appium.java_client.DeviceActionShortcuts` - - `io.appium.java_client.android.AndroidDeviceActionShortcuts` - - `io.appium.java_client.ios.IOSDeviceActionShortcuts` - - instead following inerfaces were designed: - - `io.appium.java_client.HasDeviceTime` - - `io.appium.java_client.HidesKeyboard` - - `io.appium.java_client.HidesKeyboardWithKeyName` - - `io.appium.java_client.PressesKeyCode` - - `io.appium.java_client.ios.ShakesDevice` - - `io.appium.java_client.HasSessionDetails` - _That was done because Windows automation tools have some features that were considered as Android-specific and iOS-specific._ - - The list of classes and methods which were marked _deprecated_ and they are going to be removed - - `AppiumDriver#swipe(int, int, int, int, int)` - - `AppiumDriver#pinch(WebElement)` - - `AppiumDriver#pinch(int, int)` - - `AppiumDriver#zoom(WebElement)` - - `AppiumDriver#zoom(int, int)` - - `AppiumDriver#tap(int, WebElement, int)` - - `AppiumDriver#tap(int, int, int, int)` - - `AppiumDriver#swipe(int, int, int, int, int)` - - `MobileElement#swipe(SwipeElementDirection, int)` - - `MobileElement#swipe(SwipeElementDirection, int, int, int)` - - `MobileElement#zoom()` - - `MobileElement#pinch()` - - `MobileElement#tap(int, int)` - - `io.appium.java_client.SwipeElementDirection` and `io.appium.java_client.TouchebleElement` also were marked deprecated. - - redesign of `TouchAction` and `MultiTouchAction` - - constructors were redesigned. There is no strict binding of `AppiumDriver` and `TouchAction` /`MultiTouchAction`. They can consume any instance of a class that implements `PerformsTouchActions`. - - `io.appium.java_client.ios.IOSTouchAction` was added. It extends `io.appium.java_client.TouchAction`. - - the new interface `io.appium.java_client.PerformsActions` was added. It unifies `TouchAction` and `MultiTouchAction` now. [#543](https://github.com/appium/java-client/pull/543) - - `JsonToMobileElementConverter` re-design [#532](https://github.com/appium/java-client/pull/532): - - unused `MobileElementToJsonConverter` was removed - - `JsonToMobileElementConverter` is not rhe abstract class now. It generates instances of MobileElement subclasses according to current session parameters - - `JsonToAndroidElementConverter` is deprecated now - - `JsonToIOSElementConverter` is depreacated now - - `JsonToYouiEngineElementConverter` is deprecated now. - - constructors of 'AppiumDriver' were re-designed. - - constructors of 'AndroidDriver' were re-designed. - - constructors of 'IOSDriver' were re-designed. - -- **[MAJOR ENHANCEMENT]** Windows automation. Epic [#471](https://github.com/appium/java-client/issues/471) - - The new interface `io.appium.java_client.FindsByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. - - The new selector strategy `io.appium.java_client.MobileBy.ByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. - - `io.appium.java_client.windows.WindowsDriver` was designed. [#538](https://github.com/appium/java-client/pull/538) - - `io.appium.java_client.windows.WindowsElement` was designed. [#538](https://github.com/appium/java-client/pull/538) - - `io.appium.java_client.windows.WindowsKeyCode ` was added. [#538](https://github.com/appium/java-client/pull/538) - - Page object tools were updated [#538](https://github.com/appium/java-client/pull/538) - - the `io.appium.java_client.pagefactory.WindowsFindBy` annotation was added. - - `io.appium.java_client.pagefactory.AppiumFieldDecorator` and supporting tools were actualized. - -- **[MAJOR ENHANCEMENT]** iOS XCUIT mode automation: - - `io.appium.java_client.remote.AutomationName#IOS_XCUI_TEST` was added - - The new interface `io.appium.java_client.FindsByIosNSPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. It is implemented by `io.appium.java_client.ios.IOSDriver` and `io.appium.java_client.ios.IOSElement`. - - The new selector strategy `io.appium.java_client.MobileBy.ByIosNsPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. - - Page object tools were updated [#545](https://github.com/appium/java-client/pull/545), [#546](https://github.com/appium/java-client/pull/546) - - the `io.appium.java_client.pagefactory.iOSXCUITFindBy` annotation was added. - - `io.appium.java_client.pagefactory.AppiumFieldDecorator` and supporting tools were actualized. - -- [ENHANCEMENT] Added the ability to set UiAutomator Congfigurator values. [#410](https://github.com/appium/java-client/pull/410). -[#477](https://github.com/appium/java-client/pull/477). -- [ENHANCEMENT]. Additional methods which perform device rotation were implemented. [#489](https://github.com/appium/java-client/pull/489). [#439](https://github.com/appium/java-client/pull/439). But it works for iOS in XCUIT mode and for Android in UIAutomator2 mode only. The feature request: [#7131](https://github.com/appium/appium/issues/7131) -- [ENHANCEMENT]. TouchID Implementation (iOS Sim Only). Details: [#509](https://github.com/appium/java-client/pull/509) -- [ENHANCEMENT]. The ability to use port, ip and log file as server arguments was provided. Feature request: [#521](https://github.com/appium/java-client/issues/521). Fixes: [#522](https://github.com/appium/java-client/issues/522), [#524](https://github.com/appium/java-client/issues/524). -- [ENHANCEMENT]. The new interface ```io.appium.java_client.android.HasDeviceDetails``` was added. It is implemented by ```io.appium.java_client.android.AndroidDriver``` by default. [#518](https://github.com/appium/java-client/pull/518) -- [ENHANCEMENT]. New touch actions were added. ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement, int, int)``` and ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement)```. [#523](https://github.com/appium/java-client/pull/523), [#444](https://github.com/appium/java-client/pull/444) -- [ENHANCEMENT]. All constructors declared by `io.appium.java_client.AppiumDriver` are public now. -- [BUG FIX]: There was the issue when "@WithTimeout" was changing general timeout of the waiting for elements. Bug report: [#467](https://github.com/appium/java-client/issues/467). Fixes: [#468](https://github.com/appium/java-client/issues/468), [#469](https://github.com/appium/java-client/issues/469), [#480](https://github.com/appium/java-client/issues/480). Read: [supported-settings](https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md#supported-settings) -- Added the server flag `io.appium.java_client.service.local.flags.AndroidServerFlag#REBOOT`. [#476](https://github.com/appium/java-client/pull/476) -- Added `io.appium.java_client.remote.AndroidMobileCapabilityType.APP_WAIT_DURATION ` capability. [#461](https://github.com/appium/java-client/pull/461) -- the new automation type `io.appium.java_client.remote.MobilePlatform#ANDROID_UIAUTOMATOR2` was add. -- the new automation type `io.appium.java_client.remote.MobilePlatform#YOUI_ENGINE` was add. -- Additional capabilities were addede: - - `IOSMobileCapabilityType#CUSTOM_SSL_CERT` - - `IOSMobileCapabilityType#TAP_WITH_SHORT_PRESS_DURATION` - - `IOSMobileCapabilityType#SCALE_FACTOR` - - `IOSMobileCapabilityType#WDA_LOCAL_PORT` - - `IOSMobileCapabilityType#SHOW_XCODE_LOG` - - `IOSMobileCapabilityType#REAL_DEVICE_LOGGER` - - `IOSMobileCapabilityType#IOS_INSTALL_PAUSE` - - `IOSMobileCapabilityType#XCODE_CONFIG_FILE` - - `IOSMobileCapabilityType#KEYCHAIN_PASSWORD` - - `IOSMobileCapabilityType#USE_PREBUILT_WDA` - - `IOSMobileCapabilityType#PREVENT_WDAATTACHMENTS` - - `IOSMobileCapabilityType#WEB_DRIVER_AGENT_URL` - - `IOSMobileCapabilityType#KEYCHAIN_PATH` - - `MobileCapabilityType#CLEAR_SYSTEM_FILES` -- **[UPDATE]** to Selenium 3.0.1. -- **[UPDATE]** to Spring Framework 4.3.5.RELEASE. -- **[UPDATE]** to AspectJ weaver 1.8.10. - - - -*4.1.2* - -- Following capabilities were added: - - `io.appium.java_client.remote.AndroidMobileCapabilityType.ANDROID_INSTALL_TIMEOUT` - - `io.appium.java_client.remote.AndroidMobileCapabilityType.NATIVE_WEB_SCREENSHOT` - - `io.appium.java_client.remote.AndroidMobileCapabilityType.ANDROID_SCREENSHOT_PATH`. The pull request: [#452](https://github.com/appium/java-client/pull/452) -- `org.openqa.selenium.Alert` was reimplemented for iOS. Details: [#459](https://github.com/appium/java-client/pull/459) -- The deprecated `io.appium.java_client.generic.searchcontext` was removed. -- The dependency on `com.google.code.gson` was updated to 2.7. Also it was adde to exclusions -for `org.seleniumhq.selenium` `selenium-java`. -- The new AutomationName was added. IOS_XCUI_TEST. It is needed for the further development. -- The new MobilePlatform was added. WINDOWS. It is needed for the further development. - -*4.1.1* - -BUG FIX: Issue [#450](https://github.com/appium/java-client/issues/450). Fix: [#451](https://github.com/appium/java-client/issues/451). Thanks to [@tutunang](https://github.com/appium/java-client/pull/451) for the report. - -*4.1.0* -- all code marked `@Deprecated` was removed. -- `getSessionDetails()` was added. Thanks to [@saikrishna321](https://github.com/saikrishna321) for the contribution. -- FIX [#362](https://github.com/appium/java-client/issues/362), [#220](https://github.com/appium/java-client/issues/220), [#323](https://github.com/appium/java-client/issues/323). Details read there: [#413](https://github.com/appium/java-client/pull/413) -- FIX [#392](https://github.com/appium/java-client/issues/392). Thanks to [@truebit](https://github.com/truebit) for the bug report. -- The dependency on `cglib` was replaced by the dependency on `cglib-nodep`. FIX [#418](https://github.com/appium/java-client/issues/418) -- The casting to the weaker interface `HasIdentity` instead of class `RemoteWebElement` was added. It is the internal refactoring of the `TouchAction`. [#432](https://github.com/appium/java-client/pull/432). Thanks to [@asolntsev](https://github.com/asolntsev) for the contribution. -- The `setValue` method was moved to `MobileElement`. It works against text input elements on Android. -- The dependency on `org.springframework` `spring-context` v`4.3.2.RELEASE` was added -- The dependency on `org.aspectj` `aspectjweaver` v`1.8.9` was added -- ENHANCEMENT: The alternative event firing engine. The feature request: [#242](https://github.com/appium/java-client/issues/242). -Implementation: [#437](https://github.com/appium/java-client/pull/437). Also [new WIKI chapter](https://github.com/appium/java-client/blob/master/docs/The-event_firing.md) was added. -- ENHANCEMENT: Convenient access to specific commands for each supported mobile OS. Details: [#445](https://github.com/appium/java-client/pull/445) -- dependencies and plugins were updated -- ENHANCEMENT: `YouiEngineDriver` was added. Details: [appium server #6215](https://github.com/appium/appium/pull/6215), [#429](https://github.com/appium/java-client/pull/429), [#448](https://github.com/appium/java-client/pull/448). It is just the draft of the new solution that is going to be extended further. Please stay tuned. There are many interesting things are coming up. Thanks to `You I Engine` team for the contribution. - -*4.0.0* -- all code marked `@Deprecated` was removed. Java client won't support old servers (v<1.5.0) -anymore. -- the ability to start an activity using Android intent actions, intent categories, flags and arguments -was added to `AndroidDriver`. Thanks to [@saikrishna321](https://github.com/saikrishna321) for the contribution. -- `scrollTo()` and `scrollToExact()` became deprecated. They are going to be removed in the next release. -- The interface `io.appium.java_client.ios.GetsNamedTextField` and the declared method `T getNamedTextField(String name)` are -deprecated as well. They are going to be removed in the next release. -- Methods `findElements(String by, String using)` and `findElement(String by, String using)` of `org.openga.selenium.remote.RemoteWebdriver` are public now. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget). -- the `io.appium.java_client.NetworkConnectionSetting` class was marked deprecated -- the enum `io.appium.java_client.android.Connection` was added. All supported network bitmasks are defined there. -- Android. Old methods which get/set connection were marked `@Deprecated` -- Android. New methods which consume/return `io.appium.java_client.android.Connection` were added. -- the `commandRepository` field is public now. The modification of the `MobileCommand` -- Constructors like `AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities)` were added to -`io.appium.java_client.android.AndroidDriver` and `io.appium.java_client.ios.IOSDriver` -- The refactoring of `io.appium.java_client.internal.JsonToMobileElementConverter`. Now it accepts -`org.openqa.selenium.remote.RemoteWebDriver` as the constructor parameter. It is possible to re-use -`io.appium.java_client.android.internal.JsonToAndroidElementConverter` or -`io.appium.java_client.ios.internal.JsonToIOSElementConverter` by RemoteWebDriver when it is needed. -- Constructors of the abstract `io.appium.java_client.AppiumDriver` were redesigned. Now they require -a subclass of `io.appium.java_client.internal.JsonToMobileElementConverter`. Constructors of -`io.appium.java_client.android.AndroidDriver` and `io.appium.java_client.ios.IOSDriver` are same still. -- The `pushFile(String remotePath, File file)` was added to AndroidDriver -- FIX of TouchAction. Instances of the TouchAction class are reusable now -- FIX of the swiping issue (iOS, server version >= 1.5.0). Now the swiping is implemented differently by -AndroidDriver and IOSDriver. Thanks to [@truebit](https://github.com/truebit) and [@nuggit32](https://github.com/nuggit32) for the catching. -- the project was integrated with [maven-checkstyle-plugin](https://maven.apache.org/plugins/maven-checkstyle-plugin/). Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the work -- source code was improved according to code style checking rules. -- the integration with `org.owasp dependency-check-maven` was added. Thanks to [@saikrishna321](https://github.com/saikrishna321) -for the work. -- the integration with `org.jacoco jacoco-maven-plugin` was added. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the contribution. - -*3.4.1* -- Update to Selenium v2.53.0 -- all dependencies were updated to latest versions -- the dependency on org.apache.commons commons-lang3 v3.4 was added -- the fix of Widget method invocation.[#340](https://github.com/appium/java-client/issues/340). A class visibility was taken into account. Thanks to [aznime](https://github.com/aznime) for the catching. -Server flags were added: - - GeneralServerFlag.ASYNC_TRACE - - IOSServerFlag.WEBKIT_DEBUG_PROXY_PORT -- Source code was formatted using [eclipse-java-google-style.xml](https://google-styleguide.googlecode.com/svn/trunk/eclipse-java-google-style.xml). This is not the complete solution. The code style checking is going to be added further. Thanks to [SrinivasanTarget](https://github.com/SrinivasanTarget) for the work! - -*3.4.0* -- Update to Selenium v2.52.0 -- `getAppStrings()` methods are deprecated now. They are going to be removed. `getAppStringMap()` methods were added and now return a map with app strings (keys and values) -instead of a string. Thanks to [@rgonalo](https://github.com/rgonalo) for the contribution. -- Add `getAppStringMap(String language, String stringFile)` method to allow searching app strings in the specified file -- FIXED of the bug which causes deadlocks of AppiumDriver LocalService in multithreading. Thanks to [saikrishna321](https://github.com/saikrishna321) for the [bug report](https://github.com/appium/java-client/issues/283). -- FIXED Zoom methods, thanks to [@kkhaidukov](https://github.com/kkhaidukov) -- FIXED The issue of compatibility of AppiumServiceBuilder with Appium node server v >= 1.5.x. Take a look at [#305](https://github.com/appium/java-client/issues/305) -- `getDeviceTime()` was added. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the contribution. -- FIXED `longPressKeyCode()` methods. Now they use the convenient JSONWP command.Thanks to [@kirillbilchenko](https://github.com/kirillbilchenko) for the proposed fix. -- FIXED javadoc. -- Page object tools were updated. Details read here: [#311](https://github.com/appium/java-client/issues/311), [#313](https://github.com/appium/java-client/pull/313), [#317](https://github.com/appium/java-client/pull/317). By.name locator strategy is deprecated for Android and iOS. It is still valid for the Selendroid mode. Thanks to [@SrinivasanTarget](https://github.com/SrinivasanTarget) for the helping. -- The method `lockScreen(seconds)` is deprecated and it is going to be removed in the next release. Since Appium node server v1.5.x it is recommended to use -`AndroidDriver.lockDevice()...AndroidDriver.unlockDevice()` or `IOSDriver.lockDevice(int seconds)` instead. Thanks to [@namannigam](https://github.com/namannigam) for -the catching. Read [#315](https://github.com/appium/java-client/issues/315) -- `maven-release-plugin` was added to POM.XML configuration -- [#320](https://github.com/appium/java-client/issues/320) fix. The `Widget.getSelfReference()` was added. This method allows to extract a real widget-object from inside a proxy at some extraordinary situations. Read: [PR](https://github.com/appium/java-client/pull/327). Thanks to [SergeyErmakovMercDev](https://github.com/SergeyErmakovMercDev) for the reporting. -- all capabilities were added according to [this description](https://github.com/appium/appium/blob/1.5/docs/en/writing-running-appium/caps.md). There are three classes: `io.appium.java_client.remote.MobileCapabilityType` (just modified), `io.appium.java_client.remote.AndroidMobileCapabilityType` (android-specific capabilities), `io.appium.java_client.remote.IOSMobileCapabilityType` (iOS-specific capabilities). Details are here: [#326](https://github.com/appium/java-client/pull/326) -- some server flags were marked `deprecated` because they are deprecated since server node v1.5.x. These flags are going to be removed at the java client release. Details are here: [#326](https://github.com/appium/java-client/pull/326) -- The ability to start Appium node programmatically using desired capabilities. This feature is compatible with Appium node server v >= 1.5.x. Details are here: [#326](https://github.com/appium/java-client/pull/326) - -*3.3.0* -- updated the dependency on Selenium to version 2.48.2 -- bug fix and enhancements of io.appium.java_client.service.local.AppiumDriverLocalService - - FIXED bug which was found and reproduced with Eclipse for Mac OS X. Please read about details here: [#252](https://github.com/appium/java-client/issues/252) - Thanks to [saikrishna321](https://github.com/saikrishna321) for the bug report - - FIXED bug which was found out by [Jonahss](https://github.com/Jonahss). Thanks for the reporting. Details: [#272](https://github.com/appium/java-client/issues/272) - and [#273](https://github.com/appium/java-client/issues/273) - - For starting an appium server using localService, added additional environment variable to specify the location of Node.js binary: NODE_BINARY_PATH - - The ability to set additional output streams was provided -- The additional __startActivity()__ method was added to AndroidDriver. It allows to start activities without the stopping of a target app -Thanks to [deadmoto](https://github.com/deadmoto) for the contribution -- The additional extension of the Page Object design pattern was designed. Please read about details here: [#267](https://github.com/appium/java-client/pull/267) -- New public constructors to AndroidDriver/IOSDriver that allow passing a custom HttpClient.Factory Details: [#276](https://github.com/appium/java-client/pull/278) thanks to [baechul](https://github.com/baechul) - -*3.2.0* -- updated the dependency on Selenium to version 2.47.1 -- the new dependency on commons-validator v1.4.1 -- the ability to start programmatically/silently an Appium node server is provided now. Details please read at [#240](https://github.com/appium/java-client/pull/240). -Historical reference: [The similar solution](https://github.com/Genium-Framework/Appium-Support) has been designed by [@Hassan-Radi](https://github.com/Hassan-Radi). -The mentioned framework and the current solution use different approaches. -- Throwing declarations were added to some searching methods. The __"getMouse"__ method of RemoteWebDriver was marked __Deprecated__ -- Add `replaceValue` method for elements. -- Replace `sendKeyEvent()` method in android with pressKeyCode(int key) and added: pressKeyCode(int key, Integer metastate), longPressKeyCode(int key), longPressKeyCode(int key, Integer metastate) - -*3.1.1* -- Page-object findBy strategies are now aware of which driver (iOS or Android) you are using. For more details see the Pull Request: https://github.com/appium/java-client/pull/213 -- If somebody desires to use their own Webdriver implementation then it has to implement HasCapabilities. -- Added a new annotation: `WithTimeout`. This annotation allows one to specify a specific timeout for finding an element which overrides the drivers default timeout. For more info see: https://github.com/appium/java-client/pull/210 -- Corrected an uninformative Exception message. - -*3.0.0* -- AppiumDriver class is now a Generic. This allows us to return elements of class MobileElement (and its subclasses) instead of always returning WebElements and requiring users to cast to MobileElement. See https://github.com/appium/java-client/pull/182 -- Full set of Android KeyEvents added. -- Selenium client version updated to 2.46 -- PageObject enhancements -- Junit dependency removed - -*2.2.0* -- Added new TouchAction methods for LongPress, on an element, at x,y coordinates, or at an offset from within an element -- SwipeElementDirection changed. Read the documentation, it's now smarter about how/where to swipe -- Added APPIUM_VERSION MobileCapabilityType -- `sendKeyEvent()` moved from AppiumDriver to AndroidDriver -- `linkText` and `partialLinkText` locators added -- setValue() moved from MobileElement to iOSElement -- Fixed Selendroid PageAnnotations - -*2.1.0* -- Moved hasAppString() from AndroidDriver to AppiumDriver -- Fixes to PageFactory -- Added @AndroidFindAll and @iOSFindAll -- Added toggleLocationServices() to AndroidDriver -- Added touchAction methods to MobileElement, so now you can do `element.pinch()`, `element.zoom()`, etc. -- Added the ability to choose a direction to swipe over an element. Use the `SwipeElementDirection` enums: `UP, DOWN, LEFT, RIGHT` - -*2.0.0* -- AppiumDriver is now an abstract class, use IOSDriver and AndroidDriver which both extend it. You no longer need to include the `PLATFORM_NAME` desired capability since it's automatic for each class. Thanks to @TikhomirovSergey for all their work -- ScrollTo() and ScrollToExact() methods reimplemented -- Zoom() and Pinch() are now a little smarter and less likely to fail if you element is near the edge of the screen. Congratulate @BJap on their first PR! - -*1.7.0* -- Removed `scrollTo()` and `scrollToExact()` methods because they relied on `complexFind()`. They will be added back in the next version! -- Removed `complexFind()` -- Added `startActivity()` method -- Added `isLocked()` method -- Added `getSettings()` and `ignoreUnimportantViews()` methods - -*1.6.2* -- Added MobilePlatform interface (Android, IOS, FirefoxOS) -- Added MobileBrowserType interface (Safari, Browser, Chromium, Chrome) -- Added MobileCapabilityType.APP_WAIT_ACTIVITY -- Fixed small Integer cast issue (in Eclipse it won't compile) -- Set -source and -target of the Java Compiler to 1.7 (for maven compiler plugin) -- Fixed bug in Page Factory - -*1.6.1* -- Fixed the logic for checking connection status on NetworkConnectionSetting objects - -*1.6.0* -- Added @findBy annotations. Explanation here: https://github.com/appium/java-client/pull/68 Thanks to TikhomirovSergey -- Appium Driver now implements LocationContext interface, so setLocation() works for setting GPS coordinates - -*1.5.0* -- Added MobileCapabilityType enums for desired capabilities -- `findElement` and `findElements` return MobileElement objects (still need to be casted, but no longer instantiated) -- new appium v1.2 `hideKeyboard()` strategies added -- `getNetworkConnection()` and `setNetworkConnection()` commands added - -*1.4.0* -- Added openNotifications() method, to open the notifications shade on Android -- Added pullFolder() method, to pull an entire folder as a zip archive from a device/simulator -- Upgraded Selenium dependency to 2.42.2 - -*1.3.0* -- MultiGesture with a single TouchAction fixed for Android -- Now depends upon Selenium java client 2.42.1 -- Cleanup of Errorcode handling, due to merging a change into Selenium - -*1.2.1* -- fix dependency issue - -*1.2.0* -- complexFind() now returns MobileElement objects -- added scrollTo() and scrollToExact() methods for use with complexFind() - -*1.1.0* -- AppiumDriver now implements Rotatable. rotate() and getOrientation() methods added -- when no appium server is running, the proper error is thrown, instead of a NullPointerException - -*1.0.2* -- recompiled to include some missing methods such as shake() and complexFind() +Visit [CHANGELOG.md](CHANGELOG.md) to see the full list of changes between versions. ## Running tests From 843941aff92e89080dccd1f704b9201d3a945921 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 2 Dec 2022 10:28:35 +0100 Subject: [PATCH 035/314] chore: Bump Selenium version to 4.7.0 (#1811) --- gradle.properties | 2 +- .../remote/AppiumProtocolHandshake.java | 25 +++++++++++-------- .../java_client/remote/SupportsRotation.java | 3 +-- .../events/stubs/EmptyWebDriver.java | 19 +------------- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/gradle.properties b/gradle.properties index e23c7abf2..633c79fd6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.5.0 +selenium.version=4.7.0 # Please increment the value in a release appiumClient.version=8.2.1 diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 98b128554..0f42b9a51 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -30,7 +30,6 @@ import org.openqa.selenium.remote.ProtocolHandshake; import org.openqa.selenium.remote.http.HttpHandler; -import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; @@ -39,6 +38,7 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; @@ -62,7 +62,7 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de try { Method getW3CMethod = NewSessionPayload.class.getDeclaredMethod("getW3C"); getW3CMethod.setAccessible(true); - //noinspection unchecked + //noinspection unchecked,resource ((Stream>) getW3CMethod.invoke(srcPayload)) .findFirst() .map(json::write) @@ -111,26 +111,31 @@ public Result createSession(HttpHandler client, Command command) throws IOExcept public Either createSession( HttpHandler client, NewSessionPayload payload) throws IOException { int threshold = (int) Math.min(Runtime.getRuntime().freeMemory() / 10, Integer.MAX_VALUE); - FileBackedOutputStream os = new FileBackedOutputStream(threshold); + FileBackedOutputStream os = new FileBackedOutputStream(threshold, true); try (CountingOutputStream counter = new CountingOutputStream(os); Writer writer = new OutputStreamWriter(counter, UTF_8)) { writeJsonPayload(payload, writer); - try (InputStream rawIn = os.asByteSource().openBufferedStream(); - BufferedInputStream contentStream = new BufferedInputStream(rawIn)) { - Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod("createSession", - HttpHandler.class, InputStream.class, long.class); + Supplier contentSupplier = () -> { + try { + return os.asByteSource().openBufferedStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + try { + Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod( + "createSession", HttpHandler.class, Supplier.class, long.class + ); createSessionMethod.setAccessible(true); //noinspection unchecked return (Either) createSessionMethod.invoke( - this, client, contentStream, counter.getCount() + this, client, contentSupplier, counter.getCount() ); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new WebDriverException(e); } - } finally { - os.reset(); } } } diff --git a/src/main/java/io/appium/java_client/remote/SupportsRotation.java b/src/main/java/io/appium/java_client/remote/SupportsRotation.java index 8ac22a707..6e1af3a58 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsRotation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsRotation.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.DeviceRotation; -import org.openqa.selenium.Rotatable; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DriverCommand; @@ -27,7 +26,7 @@ import java.util.Map; -public interface SupportsRotation extends WebDriver, ExecutesMethod, Rotatable { +public interface SupportsRotation extends WebDriver, ExecutesMethod { /** * Get device rotation. * diff --git a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java index 01104b988..9b6af0820 100644 --- a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java +++ b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java @@ -22,13 +22,10 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.ContextAware; import org.openqa.selenium.Cookie; -import org.openqa.selenium.DeviceRotation; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.OutputType; -import org.openqa.selenium.Rotatable; -import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; @@ -43,7 +40,7 @@ import java.util.Map; import java.util.Set; -public class EmptyWebDriver implements WebDriver, ContextAware, Rotatable, +public class EmptyWebDriver implements WebDriver, ContextAware, JavascriptExecutor, HasCapabilities, TakesScreenshot { public EmptyWebDriver() { } @@ -64,20 +61,6 @@ public String getContext() { return ""; } - public void rotate(ScreenOrientation orientation) { - } - - public void rotate(DeviceRotation rotation) { - } - - public ScreenOrientation getOrientation() { - return null; - } - - public DeviceRotation rotation() { - return null; - } - public void get(String url) { } From 7a98b69649a6d205eb8c55accf672265d52844b8 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Fri, 2 Dec 2022 16:21:00 +0530 Subject: [PATCH 036/314] Release 8.3.0 and update release notes --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d8a55cd9..bc957b07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +*8.3.0* +- **[DOCUMENTATION]** + - Added troubleshooting section. [#1808](https://github.com/appium/java-client/pull/1808) + - Added CHANGELOG.md. [#1810](https://github.com/appium/java-client/pull/1810) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.7.0. + - `org.slf4j:slf4j-api` was updated to 2.0.5. + *8.2.1* - **[ENHANCEMENTS]** - BYACCESSABILITY is deprecated in favor of BYACCESSIBILITY. [#1752](https://github.com/appium/java-client/pull/1752) diff --git a/gradle.properties b/gradle.properties index 633c79fd6..716bc2a39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.7.0 # Please increment the value in a release -appiumClient.version=8.2.1 +appiumClient.version=8.3.0 From f575ee49e24ce5ef352c962c77376765928bf4a3 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 5 Dec 2022 08:06:41 +0100 Subject: [PATCH 037/314] feat: Add a possibility to connect to a running session (#1813) --- .../io/appium/java_client/AppiumDriver.java | 52 ++++++++++++--- .../appium/java_client/AppiumFluentWait.java | 18 +----- .../java_client/android/AndroidDriver.java | 14 +++++ .../appium/java_client/gecko/GeckoDriver.java | 14 +++++ .../internal/ReflectionHelpers.java | 63 +++++++++++++++++++ .../java_client/internal/SessionHelpers.java | 59 +++++++++++++++++ .../io/appium/java_client/ios/IOSDriver.java | 13 ++++ .../io/appium/java_client/mac/Mac2Driver.java | 13 ++++ .../remote/AppiumCommandExecutor.java | 22 ++----- .../java_client/safari/SafariDriver.java | 13 ++++ .../local/AppiumDriverLocalService.java | 20 +++--- .../java_client/windows/WindowsDriver.java | 5 +- .../internal/SessionConnectTest.java | 37 +++++++++++ 13 files changed, 292 insertions(+), 51 deletions(-) create mode 100644 src/main/java/io/appium/java_client/internal/ReflectionHelpers.java create mode 100644 src/main/java/io/appium/java_client/internal/SessionHelpers.java create mode 100644 src/test/java/io/appium/java_client/internal/SessionConnectTest.java diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 0c72f9c86..debd404d3 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -16,9 +16,13 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.internal.CapabilityHelpers; +import io.appium.java_client.internal.ReflectionHelpers; +import io.appium.java_client.internal.SessionHelpers; import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.AppiumNewSessionCommandPayload; +import io.appium.java_client.remote.AppiumW3CHttpCommandCodec; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.options.BaseOptions; import io.appium.java_client.service.local.AppiumDriverLocalService; @@ -36,11 +40,11 @@ import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec; import org.openqa.selenium.remote.html5.RemoteLocationContext; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpMethod; -import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -128,6 +132,42 @@ public AppiumDriver(Capabilities capabilities) { this(AppiumDriverLocalService.buildDefaultService(), capabilities); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + * @param platformName The name of the target platform. + * @param automationName The name of the target automation. + */ + public AppiumDriver(URL remoteSessionAddress, String platformName, String automationName) { + super(); + ReflectionHelpers.setPrivateFieldValue( + RemoteWebDriver.class, this, "capabilities", new ImmutableCapabilities( + ImmutableMap.of( + PLATFORM_NAME, platformName, + APPIUM_PREFIX + AUTOMATION_NAME, automationName + ) + ) + ); + SessionHelpers.SessionAddress sessionAddress = SessionHelpers.parseSessionAddress(remoteSessionAddress); + AppiumCommandExecutor executor = new AppiumCommandExecutor( + MobileCommand.commandRepository, sessionAddress.getServerUrl() + ); + executor.setCommandCodec(new AppiumW3CHttpCommandCodec()); + executor.setResponseCodec(new W3CHttpResponseCodec()); + setCommandExecutor(executor); + this.executeMethod = new AppiumExecutionMethod(this); + locationContext = new RemoteLocationContext(executeMethod); + super.setErrorHandler(errorHandler); + this.remoteAddress = executor.getAddressOfRemoteServer(); + + setSessionId(sessionAddress.getId()); + } + /** * Changes platform name if it is not set and returns merged capabilities. * @@ -252,13 +292,9 @@ && isBlank((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { rawCapabilities.remove(CapabilityType.BROWSER_NAME); } MutableCapabilities returnedCapabilities = new BaseOptions<>(rawCapabilities); - try { - Field capsField = RemoteWebDriver.class.getDeclaredField("capabilities"); - capsField.setAccessible(true); - capsField.set(this, returnedCapabilities); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new WebDriverException(e); - } + ReflectionHelpers.setPrivateFieldValue( + RemoteWebDriver.class, this, "capabilities", returnedCapabilities + ); setSessionId(response.getSessionId()); } diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index d914f6c16..9061600a0 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -17,6 +17,7 @@ package io.appium.java_client; import com.google.common.base.Throwables; +import io.appium.java_client.internal.ReflectionHelpers; import lombok.AccessLevel; import lombok.Getter; import org.openqa.selenium.TimeoutException; @@ -24,7 +25,6 @@ import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.Sleeper; -import java.lang.reflect.Field; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -99,23 +99,11 @@ public AppiumFluentWait(T input, Clock clock, Sleeper sleeper) { } private B getPrivateFieldValue(String fieldName, Class fieldType) { - try { - final Field f = getClass().getSuperclass().getDeclaredField(fieldName); - f.setAccessible(true); - return fieldType.cast(f.get(this)); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new WebDriverException(e); - } + return ReflectionHelpers.getPrivateFieldValue(FluentWait.class, this, fieldName, fieldType); } private Object getPrivateFieldValue(String fieldName) { - try { - final Field f = getClass().getSuperclass().getDeclaredField(fieldName); - f.setAccessible(true); - return f.get(this); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new WebDriverException(e); - } + return getPrivateFieldValue(fieldName, Object.class); } protected Clock getClock() { 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 f3b812584..0fc1c5917 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -240,6 +240,20 @@ public AndroidDriver(Capabilities capabilities) { super(ensurePlatformName(capabilities, ANDROID_PLATFORM)); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + * @param automationName The name of the target automation. + */ + public AndroidDriver(URL remoteSessionAddress, String automationName) { + super(remoteSessionAddress, ANDROID_PLATFORM, automationName); + } + /** * Get test-coverage data. * diff --git a/src/main/java/io/appium/java_client/gecko/GeckoDriver.java b/src/main/java/io/appium/java_client/gecko/GeckoDriver.java index 6a7a55cab..07cb9e3e7 100644 --- a/src/main/java/io/appium/java_client/gecko/GeckoDriver.java +++ b/src/main/java/io/appium/java_client/gecko/GeckoDriver.java @@ -75,6 +75,20 @@ public GeckoDriver(HttpClient.Factory httpClientFactory, Capabilities capabiliti super(httpClientFactory, ensureAutomationName(capabilities, AUTOMATION_NAME)); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + * @param platformName The name of the target platform. + */ + public GeckoDriver(URL remoteSessionAddress, String platformName) { + super(remoteSessionAddress, platformName, AUTOMATION_NAME); + } + /** * Creates a new instance based on the given ClientConfig and {@code capabilities}. * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. diff --git a/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java new file mode 100644 index 000000000..cbaa7c796 --- /dev/null +++ b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java @@ -0,0 +1,63 @@ +/* + * 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.internal; + +import org.openqa.selenium.WebDriverException; + +import java.lang.reflect.Field; + +public class ReflectionHelpers { + + /** + * Sets the given value to a private instance field. + * + * @param cls The target class or a superclass. + * @param target Target instance. + * @param fieldName Target field name. + * @param newValue The value to be set. + * @return The same instance for chaining. + */ + public static T setPrivateFieldValue(Class cls, T target, String fieldName, Object newValue) { + try { + final Field f = cls.getDeclaredField(fieldName); + f.setAccessible(true); + f.set(target, newValue); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new WebDriverException(e); + } + return target; + } + + /** + * Fetches the value of a private instance field. + * + * @param cls The target class or a superclass. + * @param target Target instance. + * @param fieldName Target field name. + * @param fieldType Field type. + * @return The retrieved field value. + */ + public static T getPrivateFieldValue(Class cls, Object target, String fieldName, Class fieldType) { + try { + final Field f = cls.getDeclaredField(fieldName); + f.setAccessible(true); + return fieldType.cast(f.get(target)); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new WebDriverException(e); + } + } +} diff --git a/src/main/java/io/appium/java_client/internal/SessionHelpers.java b/src/main/java/io/appium/java_client/internal/SessionHelpers.java new file mode 100644 index 000000000..b3b9f0eca --- /dev/null +++ b/src/main/java/io/appium/java_client/internal/SessionHelpers.java @@ -0,0 +1,59 @@ +/* + * 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.internal; + +import lombok.Data; +import org.openqa.selenium.InvalidArgumentException; +import org.openqa.selenium.WebDriverException; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SessionHelpers { + private static final Pattern SESSION = Pattern.compile("/session/([^/]+)"); + + @Data public static class SessionAddress { + private final URL serverUrl; + private final String id; + } + + /** + * Parses the address of a running remote session. + * + * @param address The address string containing /session/id suffix. + * @return Parsed address object. + * @throws InvalidArgumentException If no session identifier could be parsed. + */ + public static SessionAddress parseSessionAddress(URL address) { + String addressString = address.toString(); + Matcher matcher = SESSION.matcher(addressString); + if (!matcher.find()) { + throw new InvalidArgumentException( + String.format("The server URL '%s' must include /session/ suffix", addressString) + ); + } + try { + return new SessionAddress( + new URL(addressString.replace(matcher.group(), "")), matcher.group(1) + ); + } catch (MalformedURLException e) { + throw new WebDriverException(e); + } + } +} 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 d450c1b0f..ad00a5f90 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -31,6 +31,7 @@ import io.appium.java_client.PushesFiles; import io.appium.java_client.SupportsLegacyAppManagement; import io.appium.java_client.battery.HasBattery; +import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.SupportsContextSwitching; import io.appium.java_client.remote.SupportsLocation; import io.appium.java_client.remote.SupportsRotation; @@ -220,6 +221,18 @@ public IOSDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilitie super(appiumClientConfig, ensurePlatformName(capabilities, PLATFORM_NAME)); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + */ + public IOSDriver(URL remoteSessionAddress) { + super(remoteSessionAddress, PLATFORM_NAME, AutomationName.IOS_XCUI_TEST); + } /** * Creates a new instance based on {@code capabilities}. 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 905cbec77..46911c314 100644 --- a/src/main/java/io/appium/java_client/mac/Mac2Driver.java +++ b/src/main/java/io/appium/java_client/mac/Mac2Driver.java @@ -86,6 +86,19 @@ public Mac2Driver(HttpClient.Factory httpClientFactory, Capabilities capabilitie capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + */ + public Mac2Driver(URL remoteSessionAddress) { + super(remoteSessionAddress, PLATFORM_NAME, AUTOMATION_NAME); + } + /** * Creates a new instance based on the given ClientConfig and {@code capabilities}. * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. 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 b3acea991..0085f457d 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -21,6 +21,7 @@ import com.google.common.net.HttpHeaders; import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumUserAgentFilter; +import io.appium.java_client.internal.ReflectionHelpers; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; @@ -42,7 +43,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; -import java.lang.reflect.Field; import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URL; @@ -128,25 +128,13 @@ public AppiumCommandExecutor(Map additionalCommands, @SuppressWarnings("SameParameterValue") protected B getPrivateFieldValue( Class cls, String fieldName, Class fieldType) { - try { - final Field f = cls.getDeclaredField(fieldName); - f.setAccessible(true); - return fieldType.cast(f.get(this)); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new WebDriverException(e); - } + return ReflectionHelpers.getPrivateFieldValue(cls, this, fieldName, fieldType); } @SuppressWarnings("SameParameterValue") protected void setPrivateFieldValue( Class cls, String fieldName, Object newValue) { - try { - final Field f = cls.getDeclaredField(fieldName); - f.setAccessible(true); - f.set(this, newValue); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new WebDriverException(e); - } + ReflectionHelpers.setPrivateFieldValue(cls, this, fieldName, newValue); } protected Map getAdditionalCommands() { @@ -159,11 +147,11 @@ protected CommandCodec getCommandCodec() { return getPrivateFieldValue(HttpCommandExecutor.class, "commandCodec", CommandCodec.class); } - protected void setCommandCodec(CommandCodec newCodec) { + public void setCommandCodec(CommandCodec newCodec) { setPrivateFieldValue(HttpCommandExecutor.class, "commandCodec", newCodec); } - protected void setResponseCodec(ResponseCodec codec) { + public void setResponseCodec(ResponseCodec codec) { setPrivateFieldValue(HttpCommandExecutor.class, "responseCodec", codec); } diff --git a/src/main/java/io/appium/java_client/safari/SafariDriver.java b/src/main/java/io/appium/java_client/safari/SafariDriver.java index 97d1f96e4..f8f10bb01 100644 --- a/src/main/java/io/appium/java_client/safari/SafariDriver.java +++ b/src/main/java/io/appium/java_client/safari/SafariDriver.java @@ -83,6 +83,19 @@ public SafariDriver(HttpClient.Factory httpClientFactory, Capabilities capabilit capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + */ + public SafariDriver(URL remoteSessionAddress) { + super(remoteSessionAddress, PLATFORM_NAME, AUTOMATION_NAME); + } + /** * Creates a new instance based on the given ClientConfig and {@code capabilities}. * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. 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 62d2a98ce..b58191ec2 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 @@ -17,6 +17,7 @@ package io.appium.java_client.service.local; import com.google.common.annotations.VisibleForTesting; +import io.appium.java_client.internal.ReflectionHelpers; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.net.UrlChecker; @@ -31,7 +32,6 @@ 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; @@ -226,15 +226,15 @@ private int destroyProcess(Duration timeout) { // 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); + Object osProcess = ReflectionHelpers.getPrivateFieldValue( + process.getClass(), process, "process", Object.class + ); + Object watchdog = ReflectionHelpers.getPrivateFieldValue( + osProcess.getClass(), osProcess, "executeWatchdog", Object.class + ); + Process nativeProcess = ReflectionHelpers.getPrivateFieldValue( + watchdog.getClass(), watchdog, "process", Process.class + ); nativeProcess.destroy(); nativeProcess.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS); } catch (Exception e) { 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 9a441d68a..7c6e68a7a 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsDriver.java +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -46,7 +46,6 @@ public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, ensurePlatformAndAutomationNames(capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } - public WindowsDriver(URL remoteAddress, Capabilities capabilities) { super(remoteAddress, ensurePlatformAndAutomationNames( capabilities, PLATFORM_NAME, AUTOMATION_NAME)); @@ -82,6 +81,10 @@ public WindowsDriver(HttpClient.Factory httpClientFactory, Capabilities capabili capabilities, PLATFORM_NAME, AUTOMATION_NAME)); } + public WindowsDriver(URL remoteSessionAddress) { + super(remoteSessionAddress, PLATFORM_NAME, AUTOMATION_NAME); + } + /** * Creates a new instance based on the given ClientConfig and {@code capabilities}. * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. diff --git a/src/test/java/io/appium/java_client/internal/SessionConnectTest.java b/src/test/java/io/appium/java_client/internal/SessionConnectTest.java new file mode 100644 index 000000000..52b4b051a --- /dev/null +++ b/src/test/java/io/appium/java_client/internal/SessionConnectTest.java @@ -0,0 +1,37 @@ +/* + * 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.internal; + +import io.appium.java_client.ios.IOSDriver; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebDriverException; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SessionConnectTest { + + @Test + void canConnectToASession() throws MalformedURLException { + IOSDriver driver = new IOSDriver(new URL("http://localhost:4723/session/1234")); + assertEquals(driver.getSessionId().toString(), "1234"); + assertThrows(WebDriverException.class, driver::quit); + } + +} From 2f4b2c2e168998e4b8f82bab1957b6f70baabcd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:44:44 +0300 Subject: [PATCH 038/314] build(deps): bump org.owasp.dependencycheck from 7.3.2 to 7.4.0 (#1814) Bumps org.owasp.dependencycheck from 7.3.2 to 7.4.0. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] 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 3b89ad341..42275f208 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.3.2' + id 'org.owasp.dependencycheck' version '7.4.0' id 'com.github.johnrengelman.shadow' version '7.1.2' } From 318aa4025b2da700bb3c640200bf81be45428577 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 9 Dec 2022 13:47:56 +0100 Subject: [PATCH 039/314] fix: Use ipv4 address instead of localhost (#1815) --- .../io/appium/java_client/android/ListensToLogcatMessages.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java index 44cae6765..8f9cf1b38 100644 --- a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java +++ b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java @@ -38,7 +38,7 @@ public interface ListensToLogcatMessages extends ExecutesMethod { * is assigned to the default port (4723). */ default void startLogcatBroadcast() { - startLogcatBroadcast("localhost", DEFAULT_APPIUM_PORT); + startLogcatBroadcast("127.0.0.1", DEFAULT_APPIUM_PORT); } /** From a6a08b191be42eada88a97052d34061b358d5088 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 07:54:54 +0200 Subject: [PATCH 040/314] build(deps): bump slf4j-api from 2.0.5 to 2.0.6 (#1824) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.5 to 2.0.6. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.5...v_2.0.6) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 42275f208..701ccbbf7 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,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.slf4j:slf4j-api:2.0.5' + implementation 'org.slf4j:slf4j-api:2.0.6' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' From 20e64ae3dbd78abe4ed4e5baf8bbb7627d5d7274 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 17:41:13 +0300 Subject: [PATCH 041/314] build(deps): bump org.owasp.dependencycheck from 7.4.0 to 7.4.1 (#1817) Bumps org.owasp.dependencycheck from 7.4.0 to 7.4.1. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 701ccbbf7..aec7f9f34 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.4.0' + id 'org.owasp.dependencycheck' version '7.4.1' id 'com.github.johnrengelman.shadow' version '7.1.2' } From 99804d0f9270b4a4617d35a035cee10648078dac Mon Sep 17 00:00:00 2001 From: rerorero Date: Fri, 30 Dec 2022 01:02:24 +0900 Subject: [PATCH 042/314] chore: deprecate tapWithShortPressDuration cap (#1825) --- .../io/appium/java_client/remote/IOSMobileCapabilityType.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java index 410e551a9..b23150244 100644 --- a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java @@ -170,7 +170,10 @@ public interface IOSMobileCapabilityType extends CapabilityType { /** * The desired capability to specify a length for tapping, if the regular * tap is too long for the app under test. The XCUITest specific capability. + * + * @deprecated This capability is not being used. */ + @Deprecated String TAP_WITH_SHORT_PRESS_DURATION = "tapWithShortPressDuration"; /** From 25c178a893517013b750ac010c0b07b25a16d5f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jan 2023 21:16:19 +0300 Subject: [PATCH 043/314] build(deps): bump org.owasp.dependencycheck from 7.4.1 to 7.4.3 (#1826) Bumps org.owasp.dependencycheck from 7.4.1 to 7.4.3. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 aec7f9f34..e167b6e64 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.4.1' + id 'org.owasp.dependencycheck' version '7.4.3' id 'com.github.johnrengelman.shadow' version '7.1.2' } From a90d718c2241fc4916efd8f2a09d4ed0d1e1f3b7 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 5 Jan 2023 17:11:18 +0300 Subject: [PATCH 044/314] ci: Run Gradle wrapper validation only on Gradle files changes (#1828) --- .github/workflows/gradle-wrapper-validation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index ba0201318..a0d1170f2 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -4,9 +4,19 @@ on: push: branches: - master + paths: + - 'gradlew' + - 'gradlew.bat' + - 'gradle/wrapper/**' + - '.github/workflows/gradle-wrapper-validation.yml' pull_request: branches: - master + paths: + - 'gradlew' + - 'gradlew.bat' + - 'gradle/wrapper/**' + - '.github/workflows/gradle-wrapper-validation.yml' jobs: validation: From 05b203e79744adbb7165b4792605e4351278191e Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 5 Jan 2023 18:27:53 +0300 Subject: [PATCH 045/314] build: Upgrade to Gradle 7.6 (#1830) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 12 ++++++++---- gradlew.bat | 1 + 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index e167b6e64..95ae332fa 100644 --- a/build.gradle +++ b/build.gradle @@ -184,7 +184,7 @@ signing { } wrapper { - gradleVersion = '7.5.1' + gradleVersion = '7.6' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36524 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%0DGK?7GHTj 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 VwlMN}`%er%aGR6olj~j${vQ;P=LY}) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8fad3f5a9..2b22d057a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/gradlew.bat b/gradlew.bat index 53a6b238d..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% From 46931ddd9f2da89ba268131c636604241aa883c5 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 5 Jan 2023 18:40:18 +0300 Subject: [PATCH 046/314] ci: Skip GH Actions build on changes in docs (#1829) --- .github/workflows/gradle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index caf838f59..397bf1661 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -4,9 +4,15 @@ on: push: branches: - master + paths-ignore: + - 'docs/**' + - '*.md' pull_request: branches: - master + paths-ignore: + - 'docs/**' + - '*.md' jobs: build: From 26f045a05d96d473e0153509c9939da0271fe4a1 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 5 Jan 2023 19:17:35 +0300 Subject: [PATCH 047/314] docs: Describe transitive Selenium dependencies management (#1827) --- README.md | 7 +++ docs/transitive-dependencies-management.md | 68 ++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 docs/transitive-dependencies-management.md diff --git a/README.md b/README.md index 596d6e564..507aa8899 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,13 @@ dependencies { } ``` +### How to pin Selenium dependencies? + +Appium Java Client declares Selenium dependencies using open version range which is handled in differently by different +build tools. Sometimes users may want to pin used Selenium dependencies for [various reasons](https://github.com/appium/java-client/issues/1823). +Follow the [Transitive Dependencies Management article](docs/transitive-dependencies-management.md) for more information +about establishing a fixed Selenium version for your Java test framework. + ## Drivers Support Appium java client has dedicated classes to support the following Appium drivers: diff --git a/docs/transitive-dependencies-management.md b/docs/transitive-dependencies-management.md new file mode 100644 index 000000000..dc148d816 --- /dev/null +++ b/docs/transitive-dependencies-management.md @@ -0,0 +1,68 @@ +# Maven + +Maven downloads dependency of [the latest version](https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution#DependencyMediationandConflictResolution-DependencyVersionRanges) +matching the declared range by default, in other words whenever new versions of Selenium 4 libraries are published +they are pulled transitively as Appium Java Client dependencies at the first project (re)build automatically. + +In order to pin Selenium dependencies they should be declared in `pom.xml` in the following way: + +```xml + + + io.appium + java-client + X.Y.Z + + + org.seleniumhq.selenium + selenium-api + + + org.seleniumhq.selenium + selenium-remote-driver + + + org.seleniumhq.selenium + selenium-support + + + + + org.seleniumhq.selenium + selenium-api + A.B.C + + + org.seleniumhq.selenium + selenium-remote-driver + A.B.C + + + org.seleniumhq.selenium + selenium-support + A.B.C + + +``` + +# Gradle + +Gradle uses [Module Metadata](https://docs.gradle.org/current/userguide/publishing_gradle_module_metadata.html) +to perform improved dependency resolution whenever it is available. Gradle Module Metadata for Appium Java Client is +published automatically with every release and is available on Maven Central. + +Appium Java Client declares [preferred](https://docs.gradle.org/current/userguide/rich_versions.html#rich-version-constraints) +Selenium dependencies version which is equal to the lowest boundary in the version range, i.e. the lowest compatible +Selenium dependencies are pulled by Gradle by default. It's strictly recommended to do not use versions lower than the +range boundary, because unresolvable compilation and runtime errors may occur. + +In order to use newer Selenium dependencies they should be explicitly added to Gradle build script (`build.gradle`): + +```gradle +dependencies { + implementation('io.appium:java-client:X.Y.Z') + implementation('org.seleniumhq.selenium:selenium-api:A.B.C') + implementation('org.seleniumhq.selenium:selenium-remote-driver:A.B.C') + implementation('org.seleniumhq.selenium:selenium-support:A.B.C') +} +``` From 1d8a01e8298229eb8cd57d9062c03f9c833eb081 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 6 Jan 2023 08:56:42 +0300 Subject: [PATCH 048/314] build: Remove Checkstyle exclusion of removed Selenium package (#1831) --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 95ae332fa..aab5eeaec 100644 --- a/build.gradle +++ b/build.gradle @@ -100,7 +100,6 @@ checkstyle { showViolations = true ignoreFailures = false } -checkstyleMain.excludes = ['**/org/openqa/selenium/**'] javadoc { options.addStringOption('encoding', 'UTF-8') From 6bdbe08ea1d71c8cf4b54bd90c09c052ddaf586c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:04:19 +0300 Subject: [PATCH 049/314] build(deps): bump gson from 2.10 to 2.10.1 (#1832) Bumps [gson](https://github.com/google/gson) from 2.10 to 2.10.1. - [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.10...gson-parent-2.10.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 aab5eeaec..8c4a4e0ab 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ dependencies { prefer "${seleniumVersion}" } } - implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.code.gson:gson:2.10.1' implementation 'cglib:cglib:3.3.0' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' From 78336d19027a0980c386877b854debcbd8d699ae Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 23 Jan 2023 23:55:35 +0200 Subject: [PATCH 050/314] test: Fix test broken by updates in `appium-xcuitest-driver` (#1839) Changes affecting the behavior of the test: - https://github.com/appium/appium-xcuitest-driver/pull/1487 - https://github.com/appium/appium/pull/18037 --- src/test/java/io/appium/java_client/ios/IOSDriverTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ba62dcf34..43d296d55 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -73,7 +73,7 @@ public void addCustomCommandWithElementIdTest() { String.format("/session/%s/appium/element/%s/value", driver.getSessionId(), ((RemoteWebElement) intA).getId()), "setNewValue"); final Response setNewValue = driver.execute("setNewValue", - ImmutableMap.of("id", ((RemoteWebElement) intA).getId(), "value", "8")); + ImmutableMap.of("id", ((RemoteWebElement) intA).getId(), "text", "8")); assertNotNull(setNewValue.getSessionId()); } From 66e9b605e0019d328d61e346c9665e5f05e88717 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:22:18 +0300 Subject: [PATCH 051/314] build(deps): bump webdrivermanager from 5.3.1 to 5.3.2 (#1837) Bumps [webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.3.1 to 5.3.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.3.1...webdrivermanager-5.3.2) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] 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 8c4a4e0ab..1069c134e 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.1') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.2') { exclude group: 'org.seleniumhq.selenium' } testImplementation ('org.seleniumhq.selenium:selenium-chrome-driver') { From a51433694d90bb733065504789ed2b0f69885303 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 10:16:51 +0530 Subject: [PATCH 052/314] build(deps): bump junit-jupiter from 5.9.1 to 5.9.2 (#1834) 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 1069c134e..5f57abe5b 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'commons-io:commons-io:2.11.0' implementation 'org.slf4j:slf4j-api:2.0.6' - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.2') { exclude group: 'org.seleniumhq.selenium' From 37ac6e4167878c16002e59c5d9aa6b490431690a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 08:01:06 +0200 Subject: [PATCH 053/314] build(deps): bump org.owasp.dependencycheck from 7.4.3 to 8.0.1 (#1838) Bumps org.owasp.dependencycheck from 7.4.3 to 8.0.1. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] 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 5f57abe5b..ec2d1801f 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'checkstyle' id 'signing' - id 'org.owasp.dependencycheck' version '7.4.3' + id 'org.owasp.dependencycheck' version '8.0.1' id 'com.github.johnrengelman.shadow' version '7.1.2' } From 8e3b2e4eb37fbbd59d073d4884c157b77cee4e99 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 24 Jan 2023 22:49:35 +0200 Subject: [PATCH 054/314] build: Migrate to Checkstyle 10 (#1842) --- .github/workflows/gradle.yml | 2 +- appium-style.xml | 3 +- azure-pipelines.yml | 6 +- build.gradle | 17 +++-- jitpack.yml | 2 +- .../java/io/appium/java_client/MobileBy.java | 44 +++++++------ .../io/appium/java_client/MobileCommand.java | 62 +++++++------------ .../android/AndroidMobileCommandHelper.java | 18 +++--- .../internal/CapabilityHelpers.java | 2 +- .../pagefactory/HowToUseLocators.java | 6 +- .../pagefactory/bys/builder/ByAll.java | 2 - 11 files changed, 75 insertions(+), 89 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 397bf1661..dfde4399d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,4 +33,4 @@ jobs: java-version: ${{ matrix.java }} cache: 'gradle' - name: Build with Gradle - run: ./gradlew clean build -x unitTest -x checkstyleTest + run: ./gradlew clean build -x unitTest diff --git a/appium-style.xml b/appium-style.xml index 03937a66d..24b63f8c6 100755 --- a/appium-style.xml +++ b/appium-style.xml @@ -175,10 +175,9 @@ - - + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f158f224d..558a97c23 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,7 +43,7 @@ jobs: jdkArchitectureOption: 'x64' publishJUnitResults: true tasks: 'build' - options: 'uiAutomationTest -x checkstyleTest -x test' + options: 'uiAutomationTest -x test' - job: iOS_E2E_Tests # timeoutInMinutes: '90' steps: @@ -63,7 +63,7 @@ jobs: jdkArchitectureOption: 'x64' publishJUnitResults: true tasks: 'build' - options: 'xcuiTest -x checkstyleTest -x test' + options: 'xcuiTest -x test' - job: Misc_Tests steps: - task: Gradle@2 @@ -75,4 +75,4 @@ jobs: jdkArchitectureOption: 'x64' publishJUnitResults: true tasks: 'build' - options: 'miscTest -x checkstyleTest -x test -x signMavenJavaPublication' + options: 'miscTest -x test -x signMavenJavaPublication' diff --git a/build.gradle b/build.gradle index ec2d1801f..837836f17 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,6 @@ plugins { id 'eclipse' id 'maven-publish' id 'jacoco' - id 'checkstyle' id 'signing' id 'org.owasp.dependencycheck' version '8.0.1' id 'com.github.johnrengelman.shadow' version '7.1.2' @@ -94,11 +93,17 @@ tasks.withType(JacocoReport) { } jacocoTestReport.dependsOn test -checkstyle { - toolVersion = '8.32' - configFile = file("$projectDir/appium-style.xml") - showViolations = true - ignoreFailures = false +// Checkstyle requires Java 11 starting from 10.0 +if (JavaVersion.current().isJava11Compatible()) { + apply plugin: 'checkstyle' + + checkstyle { + toolVersion = '10.6.0' + configFile = file("$projectDir/appium-style.xml") + showViolations = true + ignoreFailures = false + } + checkstyleTest.enabled = false } javadoc { diff --git a/jitpack.yml b/jitpack.yml index c4432703f..e594bc18b 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - openjdk8 install: - - ./gradlew clean build publishToMavenLocal -x signMavenJavaPublication -x test -x checkstyleTest + - ./gradlew clean build publishToMavenLocal -x signMavenJavaPublication -x test diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index 7d30bd855..902baa249 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -35,9 +35,10 @@ protected MobileBy(String selector, String locatorString, String locatorName) { /** * Refer to https://developer.android.com/training/testing/ui-automator - * @deprecated Use {@link AppiumBy#androidUIAutomator(String)} instead. + * * @param uiautomatorText is Android UIAutomator string * @return an instance of {@link ByAndroidUIAutomator} + * @deprecated Use {@link AppiumBy#androidUIAutomator(String)} instead. */ @Deprecated public static By AndroidUIAutomator(final String uiautomatorText) { @@ -50,9 +51,10 @@ public static By AndroidUIAutomator(final String uiautomatorText) { * About iOS accessibility * https://developer.apple.com/library/ios/documentation/UIKit/Reference/ * UIAccessibilityIdentification_Protocol/index.html - * @deprecated Use {@link AppiumBy#accessibilityId(String)} instead. + * * @param accessibilityId id is a convenient UI automation accessibility Id. * @return an instance of {@link ByAndroidUIAutomator} + * @deprecated Use {@link AppiumBy#accessibilityId(String)} instead. */ @Deprecated public static By AccessibilityId(final String accessibilityId) { @@ -61,11 +63,12 @@ public static By AccessibilityId(final String accessibilityId) { /** * This locator strategy is available in XCUITest Driver mode. - * @deprecated Use {@link AppiumBy#iOSClassChain(String)} instead. + * * @param iOSClassChainString is a valid class chain locator string. * See * the documentation for more details * @return an instance of {@link ByIosClassChain} + * @deprecated Use {@link AppiumBy#iOSClassChain(String)} instead. */ @Deprecated public static By iOSClassChain(final String iOSClassChainString) { @@ -74,11 +77,12 @@ public static By iOSClassChain(final String iOSClassChainString) { /** * This locator strategy is only available in Espresso Driver mode. - * @deprecated Use {@link AppiumBy#androidDataMatcher(String)} instead. + * * @param dataMatcherString is a valid json string detailing hamcrest matcher for Espresso onData(). - * See - * the documentation for more details + * See + * the documentation for more details * @return an instance of {@link ByAndroidDataMatcher} + * @deprecated Use {@link AppiumBy#androidDataMatcher(String)} instead. */ @Deprecated public static By androidDataMatcher(final String dataMatcherString) { @@ -87,11 +91,12 @@ public static By androidDataMatcher(final String dataMatcherString) { /** * This locator strategy is only available in Espresso Driver mode. - * @deprecated Use {@link AppiumBy#androidViewMatcher(String)} instead. + * * @param viewMatcherString is a valid json string detailing hamcrest matcher for Espresso onView(). - * See - * the documentation for more details + * See + * the documentation for more details * @return an instance of {@link ByAndroidViewMatcher} + * @deprecated Use {@link AppiumBy#androidViewMatcher(String)} instead. */ @Deprecated public static By androidViewMatcher(final String viewMatcherString) { @@ -100,9 +105,10 @@ public static By androidViewMatcher(final String viewMatcherString) { /** * This locator strategy is available in XCUITest Driver mode. - * @deprecated Use {@link AppiumBy#iOSNsPredicateString(String)} instead. + * * @param iOSNsPredicateString is an iOS NsPredicate String * @return an instance of {@link ByIosNsPredicate} + * @deprecated Use {@link AppiumBy#iOSNsPredicateString(String)} instead. */ @Deprecated public static By iOSNsPredicateString(final String iOSNsPredicateString) { @@ -111,9 +117,10 @@ public static By iOSNsPredicateString(final String iOSNsPredicateString) { /** * The Windows UIAutomation selector. - * @deprecated Not supported on the server side. + * * @param windowsAutomation The element name in the Windows UIAutomation selector * @return an instance of {@link MobileBy.ByWindowsAutomation} + * @deprecated Not supported on the server side. */ @Deprecated public static By windowsAutomation(final String windowsAutomation) { @@ -122,10 +129,11 @@ public static By windowsAutomation(final String windowsAutomation) { /** * This locator strategy is available in Espresso Driver mode. - * @deprecated Use {@link AppiumBy#androidViewTag(String)} instead. - * @since Appium 1.8.2 beta + * * @param tag is an view tag string * @return an instance of {@link ByAndroidViewTag} + * @since Appium 1.8.2 beta + * @deprecated Use {@link AppiumBy#androidViewTag(String)} instead. */ @Deprecated public static By AndroidViewTag(final String tag) { @@ -136,15 +144,15 @@ public static By AndroidViewTag(final String tag) { * This locator strategy is available only if OpenCV libraries and * NodeJS bindings are installed on the server machine. * - * @deprecated Use {@link AppiumBy#image(String)} instead. + * @param b64Template base64-encoded template image string. Supported image formats are the same + * as for OpenCV library. + * @return an instance of {@link ByImage} * @see * The documentation on Image Comparison Features * @see * The settings available for lookup fine-tuning * @since Appium 1.8.2 - * @param b64Template base64-encoded template image string. Supported image formats are the same - * as for OpenCV library. - * @return an instance of {@link ByImage} + * @deprecated Use {@link AppiumBy#image(String)} instead. */ @Deprecated public static By image(final String b64Template) { @@ -155,10 +163,10 @@ public static By image(final String b64Template) { * This type of locator requires the use of the 'customFindModules' capability and a * separately-installed element finding plugin. * - * @deprecated Use {@link AppiumBy#custom(String)} instead. * @param selector selector to pass to the custom element finding plugin * @return an instance of {@link ByCustom} * @since Appium 1.9.2 + * @deprecated Use {@link AppiumBy#custom(String)} instead. */ @Deprecated public static By custom(final String selector) { diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index b87e76949..f5e9c7fb1 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -317,13 +317,11 @@ public static AppiumCommandInfo deleteC(String url) { } /** - * This method forms a {@link java.util.Map} of parameters for the - * keyboard hiding. + * This method forms a {@link Map} of parameters for the keyboard hiding. * * @param keyName The button pressed by the mobile driver to attempt hiding the * keyboard. - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> hideKeyboardCommand(String keyName) { return new AbstractMap.SimpleEntry<>( @@ -331,14 +329,12 @@ public static AppiumCommandInfo deleteC(String url) { } /** - * This method forms a {@link java.util.Map} of parameters for the - * keyboard hiding. + * This method forms a {@link Map} of parameters for the keyboard hiding. * * @param strategy HideKeyboardStrategy. * @param keyName a String, representing the text displayed on the button of the * keyboard you want to press. For example: "Done". - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> hideKeyboardCommand(String strategy, String keyName) { @@ -381,12 +377,10 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * key event invocation. + * This method forms a {@link Map} of parameters for the key event invocation. * * @param key code for the key pressed on the device. - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> pressKeyCodeCommand(int key) { return new AbstractMap.SimpleEntry<>( @@ -394,13 +388,11 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * key event invocation. + * This method forms a {@link Map} of parameters for the key event invocation. * * @param key code for the key pressed on the Android device. * @param metastate metastate for the keypress. - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> pressKeyCodeCommand(int key, Integer metastate) { @@ -411,12 +403,10 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * long key event invocation. + * This method forms a {@link Map} of parameters for the long key event invocation. * * @param key code for the long key pressed on the device. - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> longPressKeyCodeCommand(int key) { return new AbstractMap.SimpleEntry<>( @@ -424,13 +414,11 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * long key event invocation. + * This method forms a {@link Map} of parameters for the long key event invocation. * * @param key code for the long key pressed on the Android device. * @param metastate metastate for the long key press. - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> longPressKeyCodeCommand(int key, Integer metastate) { @@ -441,12 +429,10 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * device locking. + * This method forms a {@link Map} of parameters for the device locking. * * @param duration for how long to lock the screen for. Minimum time resolution is one second - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> lockDeviceCommand(Duration duration) { return new AbstractMap.SimpleEntry<>( @@ -454,22 +440,18 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * device unlocking. + * This method forms a {@link Map} of parameters for the device unlocking. * - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> unlockDeviceCommand() { return new AbstractMap.SimpleEntry<>(UNLOCK, ImmutableMap.of()); } /** - * This method forms a {@link java.util.Map} of parameters for the - * device locked query. + * This method forms a {@link Map} of parameters for the device locked query. * - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> getIsDeviceLockedCommand() { return new AbstractMap.SimpleEntry<>(IS_LOCKED, ImmutableMap.of()); @@ -488,13 +470,11 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * This method forms a {@link java.util.Map} of parameters for the - * file pushing. + * This method forms a {@link Map} of parameters for the file pushing. * * @param remotePath Path to file to write data to on remote device * @param base64Data Base64 encoded byte array of data to write to remote device - * @return a key-value pair. The key is the command name. The value is a - * {@link java.util.Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> pushFileCommand(String remotePath, byte[] base64Data) { String[] parameters = new String[]{"path", "data"}; @@ -513,7 +493,7 @@ public static ImmutableMap prepareArguments(String[] params, } /** - * Forms a {@link java.util.Map} of parameters for images comparison. + * Forms a {@link Map} of parameters for images comparison. * * @param mode one of possible comparison modes * @param img1Data base64-encoded data of the first image diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index c5fa63080..6b46ce945 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -113,11 +113,9 @@ public class AndroidMobileCommandHelper extends MobileCommand { } /** - * This method forms a {@link Map} of parameters to - * Retrieve the display density of the Android device. + * This method forms a {@link Map} of parameters to retrieve the display density of the Android device. * - * @return a key-value pair. The key is the command name. The value is a - * {@link Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> getDisplayDensityCommand() { return new AbstractMap.SimpleEntry<>(GET_DISPLAY_DENSITY, ImmutableMap.of()); @@ -126,16 +124,15 @@ public class AndroidMobileCommandHelper extends MobileCommand { /** * This method forms a {@link Map} of parameters for the getting of a network connection value. * - * @return a key-value pair. The key is the command name. The value is a - * {@link Map} command arguments. + * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ public static Map.Entry> getNetworkConnectionCommand() { return new AbstractMap.SimpleEntry<>(GET_NETWORK_CONNECTION, ImmutableMap.of()); } /** - * This method forms a {@link Map} of parameters to - * Retrieve visibility and bounds information of the status and navigation bars. + * This method forms a {@link Map} of parameters to retrieve visibility and bounds information of the status and + * navigation bars. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ @@ -144,8 +141,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { } /** - * This method forms a {@link java.util.Map} of parameters for the - * finger print authentication invocation. + * This method forms a {@link Map} of parameters for the finger print authentication invocation. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ @@ -243,7 +239,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { } /** - * This method forms a {@link java.util.Map} of parameters for the element. + * This method forms a {@link Map} of parameters for the element. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ 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 995385b4b..3645a7520 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -159,8 +159,8 @@ public static Duration toDuration(Object value, * Converts generic capability value to a url. * * @param value The capability value. - * @throws IllegalArgumentException If the given value cannot be parsed to a valid url. * @return null is the passed value is null otherwise the converted value. + * @throws IllegalArgumentException If the given value cannot be parsed to a valid url. */ @Nullable public static URL toUrl(Object value) { diff --git a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java index 88a9abb7d..2e091064f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java +++ b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java @@ -29,7 +29,7 @@ * or the searching by all possible locators. * * @return the strategy which defines how to use locators which are described by the - * {@link AndroidFindBy} annotation + * {@link AndroidFindBy} annotation */ LocatorGroupStrategy androidAutomation() default LocatorGroupStrategy.CHAIN; @@ -39,7 +39,7 @@ * or the searching by all possible locators. * * @return the strategy which defines how to use locators which are described by the - * {@link WindowsFindBy} annotation + * {@link WindowsFindBy} annotation */ LocatorGroupStrategy windowsAutomation() default LocatorGroupStrategy.CHAIN; @@ -49,7 +49,7 @@ * or the searching by all possible locators. * * @return the strategy which defines how to use locators which are described by the - * {@link iOSXCUITFindBy} annotation + * {@link iOSXCUITFindBy} annotation */ LocatorGroupStrategy iOSXCUITAutomation() default LocatorGroupStrategy.CHAIN; } diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java index 243d49306..4999c294d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java @@ -16,11 +16,9 @@ /** * Mechanism used to locate elements within a document using a series of lookups. This class will * find all DOM elements that matches any of the locators in sequence, e.g. - * *
  * driver.findElements(new ByAll(by1, by2))
  * 
- * * will find all elements that match by1 and then all elements that match by2. * This means that the list of elements returned may not be in document order. * From 7f9b6c08b417cef63bbfc2f231e9047dd53e0274 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Jan 2023 04:57:45 +0200 Subject: [PATCH 055/314] docs: Fix build badge to point GH Actions CI (#1844) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 507aa8899..6825ba82c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.appium/java-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.appium/java-client) [![Javadocs](https://www.javadoc.io/badge/io.appium/java-client.svg)](https://www.javadoc.io/doc/io.appium/java-client) -[![Build Status](https://travis-ci.org/appium/java-client.svg?branch=master)](https://travis-ci.org/appium/java-client) +[![Appium Java Client CI](https://github.com/appium/java-client/actions/workflows/gradle.yml/badge.svg)](https://github.com/appium/java-client/actions/workflows/gradle.yml) This is the Java language bindings for writing Appium Tests that conform to [WebDriver Protocol](https://w3c.github.io/webdriver/) From 54070e602b28b7d7b06c5c1f7099dc2f24cb3f8d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Jan 2023 04:58:26 +0200 Subject: [PATCH 056/314] build: Enable Checkstyle checks for test code (#1843) * build: Move Checkstyle config to a dedicated folder * build: Configure Checkstyle suppressions for test code * build: Fix all Checkstyle violations in test code * build: Enable check for Checkstyle violations in test code during build --- build.gradle | 3 +- .../checkstyle/appium-style.xml | 5 +++ config/checkstyle/suppressions.xml | 5 +++ .../java_client/android/LogEventTest.java | 4 +- .../drivers/options/OptionsBuildingTest.java | 4 +- .../internal/SessionConnectTest.java | 1 + .../appium/java_client/ios/IOSDriverTest.java | 3 +- .../AndroidPageObjectTest.java | 38 +++++++++---------- .../pagefactory_tests/TimeoutTest.java | 6 +-- .../tests/combined/CombinedAppTest.java | 24 ++++++------ .../java_client/proxy/ProxyHelpersTest.java | 3 +- .../service/local/ServerBuilderTest.java | 4 +- 12 files changed, 57 insertions(+), 43 deletions(-) rename appium-style.xml => config/checkstyle/appium-style.xml (98%) create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index 837836f17..798d03182 100644 --- a/build.gradle +++ b/build.gradle @@ -99,11 +99,10 @@ if (JavaVersion.current().isJava11Compatible()) { checkstyle { toolVersion = '10.6.0' - configFile = file("$projectDir/appium-style.xml") + configFile = configDirectory.file('appium-style.xml').get().getAsFile() showViolations = true ignoreFailures = false } - checkstyleTest.enabled = false } javadoc { diff --git a/appium-style.xml b/config/checkstyle/appium-style.xml similarity index 98% rename from appium-style.xml rename to config/checkstyle/appium-style.xml index 24b63f8c6..2feb5eeff 100755 --- a/appium-style.xml +++ b/config/checkstyle/appium-style.xml @@ -203,4 +203,9 @@
+ + + + +
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 000000000..0587e646e --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/test/java/io/appium/java_client/android/LogEventTest.java b/src/test/java/io/appium/java_client/android/LogEventTest.java index ea820fa94..16d28f31e 100644 --- a/src/test/java/io/appium/java_client/android/LogEventTest.java +++ b/src/test/java/io/appium/java_client/android/LogEventTest.java @@ -36,8 +36,8 @@ public void verifyLoggingCustomEvents() { driver.logEvent(evt); ServerEvents events = driver.getEvents(); boolean hasCustomEvent = events.events.stream().anyMatch((TimedEvent event) -> - event.name.equals("appium:funEvent") && - event.occurrences.get(0).intValue() > 0 + event.name.equals("appium:funEvent") + && event.occurrences.get(0).intValue() > 0 ); boolean hasCommandName = events.commands.stream().anyMatch((CommandEvent event) -> event.name.equals("logCustomEvent") diff --git a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java index ab10f1ec1..908ff709e 100644 --- a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java +++ b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java @@ -108,7 +108,7 @@ public void canBuildEspressoOptions() { "com.dep1:1.2.3", "com.dep2:1.2.3" )) - ); + ); assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); assertEquals("CN", options.getAppLocale().orElse(null).getCountry().orElse(null)); assertEquals(2, options.getEspressoBuildConfig().orElse(null) @@ -172,7 +172,7 @@ public void canBuildSafariOptions() { .setWebkitWebrtc(new WebrtcData() .withDisableIceCandidateFiltering(true) .withDisableInsecureMediaCapture(true) - ); + ); assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); assertTrue(options.doesSafariUseSimulator().orElse(false)); assertTrue(options.getWebkitWebrtc().orElse(null) diff --git a/src/test/java/io/appium/java_client/internal/SessionConnectTest.java b/src/test/java/io/appium/java_client/internal/SessionConnectTest.java index 52b4b051a..a97653882 100644 --- a/src/test/java/io/appium/java_client/internal/SessionConnectTest.java +++ b/src/test/java/io/appium/java_client/internal/SessionConnectTest.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.appium.java_client.internal; import io.appium.java_client.ios.IOSDriver; 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 43d296d55..f977289c0 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -60,7 +60,8 @@ public void addCustomCommandTest() { @Test public void addCustomCommandWithSessionIdTest() { - driver.addCommand(HttpMethod.POST, "/session/" + driver.getSessionId() + "/appium/app/launch", "launchApplication"); + driver.addCommand(HttpMethod.POST, "/session/" + driver.getSessionId() + "/appium/app/launch", + "launchApplication"); final Response launchApplication = driver.execute("launchApplication"); assertNotNull(launchApplication.getSessionId()); } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java index a135c30e3..abcd92c80 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java @@ -167,41 +167,41 @@ public class AndroidPageObjectTest extends BaseAndroidTest { @AndroidFindBy(id = "android:id/text1", priority = 2) @AndroidFindAll(value = { - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), - @AndroidBy(id = "android:id/fakeId")}, priority = 1) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidBy(id = "android:id/fakeId")}, priority = 1) @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")") private WebElement androidElementViewFoundByMixedSearching; @AndroidFindBy(id = "android:id/text1", priority = 2) @AndroidFindAll(value = { - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), - @AndroidBy(id = "android:id/fakeId")}, priority = 1) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidBy(id = "android:id/fakeId")}, priority = 1) @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")") private List androidElementsViewFoundByMixedSearching; @AndroidFindBys({ - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), - @AndroidBy(className = "android.widget.FrameLayout")}) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(className = "android.widget.FrameLayout")}) @AndroidFindBys({@AndroidBy(id = "android:id/text1", priority = 1), - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) private WebElement androidElementViewFoundByMixedSearching2; @AndroidFindBys({ - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), - @AndroidBy(className = "android.widget.FrameLayout")}) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(className = "android.widget.FrameLayout")}) @AndroidFindBys({ - @AndroidBy(id = "android:id/text1", priority = 1), - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) + @AndroidBy(id = "android:id/text1", priority = 1), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) private List androidElementsViewFoundByMixedSearching2; @HowToUseLocators(androidAutomation = ALL_POSSIBLE) @AndroidFindBy(id = "android:id/fakeId1") @AndroidFindBy(id = "android:id/fakeId2", priority = 1) @AndroidFindBys(value = { - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), - @AndroidBy(id = "android:id/text1", priority = 3), - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), - @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(id = "android:id/text1", priority = 3), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), + @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) @AndroidFindBy(id = "android:id/fakeId3", priority = 3) @AndroidFindBy(id = "android:id/fakeId4", priority = 4) private WebElement androidElementViewFoundByMixedSearching3; @@ -210,10 +210,10 @@ public class AndroidPageObjectTest extends BaseAndroidTest { @AndroidFindBy(id = "android:id/fakeId1") @AndroidFindBy(id = "android:id/fakeId2", priority = 1) @AndroidFindBys(value = { - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), - @AndroidBy(id = "android:id/text1", priority = 3), - @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), - @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(id = "android:id/text1", priority = 3), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), + @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) @AndroidFindBy(id = "android:id/fakeId3", priority = 3) @AndroidFindBy(id = "android:id/fakeId4", priority = 4) private List androidElementsViewFoundByMixedSearching3; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java index 2fbe87f41..1ae9157a3 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java @@ -50,13 +50,13 @@ public class TimeoutTest { private WebDriver driver; @FindAll({ - @FindBy(className = "ClassWhichDoesNotExist"), - @FindBy(className = "OneAnotherClassWhichDoesNotExist")}) + @FindBy(className = "ClassWhichDoesNotExist"), + @FindBy(className = "OneAnotherClassWhichDoesNotExist")}) private List stubElements; @WithTimeout(time = 5, chronoUnit = SECONDS) @FindAll({@FindBy(className = "ClassWhichDoesNotExist"), - @FindBy(className = "OneAnotherClassWhichDoesNotExist")}) + @FindBy(className = "OneAnotherClassWhichDoesNotExist")}) private List stubElements2; private Duration timeOutDuration; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java index 100f0322c..2a453c2d9 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java @@ -19,6 +19,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.openqa.selenium.support.PageFactory.initElements; @SuppressWarnings({"unused", "unchecked"}) @@ -31,29 +32,30 @@ public class CombinedAppTest { */ public static Stream data() { return Stream.of( - Arguments.of(new CombinedApp(), new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class), - Arguments.of(new CombinedApp(), new AbstractStubWebDriver.StubIOSXCUITDriver(), + arguments(new CombinedApp(), new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class), + arguments(new CombinedApp(), new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultIosXCUITWidget.class), - Arguments.of(new CombinedApp(), new AbstractStubWebDriver.StubWindowsDriver(), DefaultWindowsWidget.class), - Arguments.of(new CombinedApp(), new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class), - Arguments.of(new CombinedApp(), new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), + arguments(new CombinedApp(), new AbstractStubWebDriver.StubWindowsDriver(), DefaultWindowsWidget.class), + arguments(new CombinedApp(), new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class), + arguments(new CombinedApp(), new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class), - Arguments.of(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubAndroidDriver(), + arguments(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class), - Arguments.of(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubIOSXCUITDriver(), + arguments(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultStubWidget.class), - Arguments.of(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubWindowsDriver(), + arguments(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubWindowsDriver(), DefaultStubWidget.class), - Arguments.of(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubBrowserDriver(), + arguments(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class), - Arguments.of(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), + arguments(new PartiallyCombinedApp(), new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class) ); } @ParameterizedTest @MethodSource("data") - void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { + void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, + Class widgetClass) { initElements(new AppiumFieldDecorator(driver), app); assertThat("Expected widget class was " + widgetClass.getName(), app.getWidget().getSelfReference().getClass(), diff --git a/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java index fc389b6c6..218c56270 100644 --- a/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java +++ b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java @@ -43,7 +43,8 @@ public FakeIOSDriver(URL url, Capabilities caps) { } @Override - protected void startSession(Capabilities capabilities) {} + protected void startSession(Capabilities capabilities) { + } } @Test 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 923fa57b8..5d4fd5715 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 @@ -319,21 +319,21 @@ void checkAbilityToStartServiceWithLogFileUsingShortFlag() { @Test void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); String basePath = "wd/hub"; service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath + "/", service.getUrl().toString()); } @Test void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); String basePath = "/wd/"; service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath.substring(1), service.getUrl().toString()); } From bd5635f55d6a69b7d7514f070f65f537cf5ceae9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Jan 2023 19:11:46 +0200 Subject: [PATCH 057/314] chore: Configure `CODEOWNERS` to automate review requests (#1846) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..49cf4f31e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @mykola-mokhnach @SrinivasanTarget @saikrishna321 @valfirst From 8ee8500593c90052f29a370ac97ae1edd1aba9a5 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Jan 2023 21:04:56 +0200 Subject: [PATCH 058/314] ci: Enable execution of unit tests in CI (#1845) --- .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 dfde4399d..e9d7c7ef5 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,4 +33,4 @@ jobs: java-version: ${{ matrix.java }} cache: 'gradle' - name: Build with Gradle - run: ./gradlew clean build -x unitTest + run: ./gradlew clean build unitTest From 2c87e7be2de19d9a7d3ff72195e22ef912eb8d00 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Jan 2023 21:05:43 +0200 Subject: [PATCH 059/314] test: Add Simple SLF4J binding to unit tests runtime (#1848) This change fixes the following error: ``` SLF4J: No SLF4J providers were found. SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details. ``` --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 798d03182..c4bff9b07 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') + slf4jVersion = '2.0.6' } dependencies { @@ -54,7 +55,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.slf4j:slf4j-api:2.0.6' + implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation 'org.hamcrest:hamcrest:2.2' @@ -67,6 +68,7 @@ dependencies { prefer "${seleniumVersion}" } } + testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } ext { From ca86a3905dc8826354eab543f6e88d543f14a1ca Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 26 Jan 2023 09:53:46 +0200 Subject: [PATCH 060/314] perf: Improve performance of proxy `Interceptor` logging (#1849) --- .../io/appium/java_client/proxy/Interceptor.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index 921b86124..d343fc9c1 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -72,10 +72,10 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - logger.error( - String.format("Got an unexpected error in beforeCall listener of %s.%s method", - self.getClass().getName(), method.getName()), e - ); + logger.atError() + .addArgument(() -> self.getClass().getName()) + .addArgument(method::getName) + .log("Got an unexpected error in beforeCall listener of {}.{} method", e); } }); @@ -118,10 +118,10 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - logger.error( - String.format("Got an unexpected error in afterCall listener of %s.%s method", - self.getClass().getName(), method.getName()), e - ); + logger.atError() + .addArgument(() -> self.getClass().getName()) + .addArgument(method::getName) + .log("Got an unexpected error in afterCall listener of {}.{} method", e); } }); return endResult; From 4be4904c239ccddb7a1fbe9492be8e687711f0a1 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 27 Jan 2023 12:51:05 +0200 Subject: [PATCH 061/314] build: Merge misc tests suite into unit tests suite (#1850) --- azure-pipelines.yml | 12 ------------ build.gradle | 16 ++++------------ 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 558a97c23..c52cbfca3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -64,15 +64,3 @@ jobs: publishJUnitResults: true tasks: 'build' options: 'xcuiTest -x test' -- job: Misc_Tests - steps: - - task: Gradle@2 - inputs: - gradleWrapperFile: 'gradlew' - gradleOptions: '-Xmx3072m' - javaHomeOption: 'JDKVersion' - jdkVersionOption: "$(JDK_VERSION)" - jdkArchitectureOption: 'x64' - publishJUnitResults: true - tasks: 'build' - options: 'miscTest -x test -x signMavenJavaPublication' diff --git a/build.gradle b/build.gradle index c4bff9b07..7e9991551 100644 --- a/build.gradle +++ b/build.gradle @@ -205,8 +205,12 @@ task unitTest( type: Test ) { testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' filter { + includeTestsMatching 'io.appium.java_client.drivers.options.*' + includeTestsMatching 'io.appium.java_client.events.*' includeTestsMatching 'io.appium.java_client.internal.*' includeTestsMatching 'io.appium.java_client.proxy.*' + includeTestsMatching 'io.appium.java_client.remote.*' + includeTestsMatching 'io.appium.java_client.touch.*' } } @@ -241,15 +245,3 @@ task uiAutomationTest( type: Test ) { includeTestsMatching 'io.appium.java_client.service.local.ThreadSafetyTest' } } - -task miscTest( type: Test ) { - useJUnitPlatform() - testLogging.showStandardStreams = true - testLogging.exceptionFormat = 'full' - filter { - includeTestsMatching 'io.appium.java_client.touch.*' - includeTestsMatching 'io.appium.java_client.events.*' - includeTestsMatching 'io.appium.java_client.remote.*' - includeTestsMatching 'io.appium.java_client.drivers.options.*' - } -} From 585bb1b02f5b87169b19564ac045513d8a065f3d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 1 Feb 2023 05:58:54 +0200 Subject: [PATCH 062/314] build: Bump JaCoCo from `0.8.5` to `0.8.8` (#1854) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7e9991551..098ebe28f 100644 --- a/build.gradle +++ b/build.gradle @@ -82,7 +82,7 @@ dependencyCheck { } jacoco { - toolVersion = '0.8.5' + toolVersion = '0.8.8' } tasks.withType(JacocoReport) { From dc5a97d20e4f3c90793a65f5534d5143cde7c7eb Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 2 Feb 2023 12:23:24 +0200 Subject: [PATCH 063/314] build: Make unit tests execution a part of Gradle build lifecycle (#1853) --- .github/workflows/gradle.yml | 2 +- azure-pipelines.yml | 6 ++---- build.gradle | 3 ++- jitpack.yml | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e9d7c7ef5..cd399065c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -33,4 +33,4 @@ jobs: java-version: ${{ matrix.java }} cache: 'gradle' - name: Build with Gradle - run: ./gradlew clean build unitTest + run: ./gradlew clean build diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c52cbfca3..a3d59c308 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,8 +42,7 @@ jobs: jdkVersionOption: "$(JDK_VERSION)" jdkArchitectureOption: 'x64' publishJUnitResults: true - tasks: 'build' - options: 'uiAutomationTest -x test' + tasks: 'build uiAutomationTest' - job: iOS_E2E_Tests # timeoutInMinutes: '90' steps: @@ -62,5 +61,4 @@ jobs: jdkVersionOption: "$(JDK_VERSION)" jdkArchitectureOption: 'x64' publishJUnitResults: true - tasks: 'build' - options: 'xcuiTest -x test' + tasks: 'build xcuiTest' diff --git a/build.gradle b/build.gradle index 098ebe28f..f5decafd4 100644 --- a/build.gradle +++ b/build.gradle @@ -200,7 +200,7 @@ processResources { ] } -task unitTest( type: Test ) { +test { useJUnitPlatform() testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' @@ -212,6 +212,7 @@ task unitTest( type: Test ) { includeTestsMatching 'io.appium.java_client.remote.*' includeTestsMatching 'io.appium.java_client.touch.*' } + finalizedBy jacocoTestReport } task xcuiTest( type: Test ) { diff --git a/jitpack.yml b/jitpack.yml index e594bc18b..f5edf063b 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - openjdk8 install: - - ./gradlew clean build publishToMavenLocal -x signMavenJavaPublication -x test + - ./gradlew clean build publishToMavenLocal -x signMavenJavaPublication From 3adfd03af555cd92b62fc942af102de9f808f648 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 12:18:43 +0300 Subject: [PATCH 064/314] build(deps): bump org.projectlombok:lombok from 1.18.24 to 1.18.26 (#1856) * build(deps): bump org.projectlombok:lombok from 1.18.24 to 1.18.26 Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.24 to 1.18.26. - [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.24...v1.18.26) --- updated-dependencies: - dependency-name: org.projectlombok:lombok dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * fix: Bump lombok used by annotation processor as well --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Valery Yatsynovich --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f5decafd4..cec2cf33b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' + compileOnly 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' api ('org.seleniumhq.selenium:selenium-api') { version { From d4240e7b1d63baf3ca797e561aa14266d7bbae81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 11:15:18 +0300 Subject: [PATCH 065/314] build(deps): Bump org.owasp.dependencycheck from 8.0.1 to 8.1.0 (#1863) Bumps org.owasp.dependencycheck from 8.0.1 to 8.1.0. --- 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 cec2cf33b..59c376ed0 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.0.1' + id 'org.owasp.dependencycheck' version '8.1.0' id 'com.github.johnrengelman.shadow' version '7.1.2' } From a9fd23ffd7c0ef77119afb00699a8f5b464d34e3 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 27 Feb 2023 13:22:29 +0300 Subject: [PATCH 066/314] chore: Upgrade to Gradle 8.0.1 (#1865) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 59c376ed0..0b8eb9c4d 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ tasks.withType(JacocoReport) { sourceSets sourceSets.main reports { html.required = true - html.destination file("${buildDir}/Reports/jacoco") + html.outputLocation = file("${buildDir}/Reports/jacoco") } } jacocoTestReport.dependsOn test diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 5094 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 delta 5107 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 I6Xdi10eY*&F8}}l diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2b22d057a..6ec1567a0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..79a61d421 100755 --- a/gradlew +++ b/gradlew @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac From 919c2257dbe7acd2e62c36aa4bd6611f50d88e05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:08:48 +0300 Subject: [PATCH 067/314] build(deps): Bump com.github.johnrengelman.shadow from 7.1.2 to 8.0.0 (#1866) Bumps com.github.johnrengelman.shadow from 7.1.2 to 8.0.0. --- updated-dependencies: - dependency-name: com.github.johnrengelman.shadow 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 0b8eb9c4d..3452af8a3 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'signing' id 'org.owasp.dependencycheck' version '8.1.0' - id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'com.github.johnrengelman.shadow' version '8.0.0' } repositories { From 73518775dfee83b5930d158f12d9eba1232b8e21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 15:39:26 +0300 Subject: [PATCH 068/314] build(deps): Bump com.github.johnrengelman.shadow from 8.0.0 to 8.1.0 (#1868) Bumps com.github.johnrengelman.shadow from 8.0.0 to 8.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 3452af8a3..63ff01ba1 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'signing' id 'org.owasp.dependencycheck' version '8.1.0' - id 'com.github.johnrengelman.shadow' version '8.0.0' + id 'com.github.johnrengelman.shadow' version '8.1.0' } repositories { From 653213f513783a9bea1cca83664d7a52fb1fba1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 16:33:46 +0300 Subject: [PATCH 069/314] build(deps): Bump org.owasp.dependencycheck from 8.1.0 to 8.1.2 (#1867) Bumps org.owasp.dependencycheck from 8.1.0 to 8.1.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 63ff01ba1..3b06d2e62 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.1.0' + id 'org.owasp.dependencycheck' version '8.1.2' id 'com.github.johnrengelman.shadow' version '8.1.0' } From 71f51cde48d1015b791ae4825a14cd9650f5fd85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:24:02 +0300 Subject: [PATCH 070/314] build(deps): Bump slf4jVersion from 2.0.6 to 2.0.7 (#1870) Bumps `slf4jVersion` from 2.0.6 to 2.0.7. Updates `org.slf4j:slf4j-api` from 2.0.6 to 2.0.7 - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/commits) Updates `org.slf4j:slf4j-simple` from 2.0.6 to 2.0.7 - [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 - dependency-name: org.slf4j:slf4j-simple 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 3b06d2e62..b9319ca4d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.6' + slf4jVersion = '2.0.7' } dependencies { From 4107f7068fa4dce393c869500944c5a2ee3d48af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 22:01:00 +0300 Subject: [PATCH 071/314] build(deps): Bump org.owasp.dependencycheck from 8.1.2 to 8.2.1 (#1873) Bumps org.owasp.dependencycheck from 8.1.2 to 8.2.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 b9319ca4d..b1cade693 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.1.2' + id 'org.owasp.dependencycheck' version '8.2.1' id 'com.github.johnrengelman.shadow' version '8.1.0' } From 06ce52a24d82fcb4e65314daf06087cd1665da43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 17:29:48 +0300 Subject: [PATCH 072/314] build(deps): Bump com.github.johnrengelman.shadow from 8.1.0 to 8.1.1 (#1874) Bumps com.github.johnrengelman.shadow from 8.1.0 to 8.1.1. --- updated-dependencies: - dependency-name: com.github.johnrengelman.shadow 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 b1cade693..3c6be8d18 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'signing' id 'org.owasp.dependencycheck' version '8.2.1' - id 'com.github.johnrengelman.shadow' version '8.1.0' + id 'com.github.johnrengelman.shadow' version '8.1.1' } repositories { From 0c269b77bbe48858a43d9e08cf7fc99659b176bd Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 28 Mar 2023 17:36:06 +0200 Subject: [PATCH 073/314] fix: Avoid NPE in destroyProcess call (#1878) --- .../java_client/service/local/AppiumDriverLocalService.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 b58191ec2..b16570f1e 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 @@ -54,7 +54,6 @@ public final class AppiumDriverLocalService extends DriverService { private static final String URL_MASK = "http://%s:%d/"; private static final Logger LOG = LoggerFactory.getLogger(AppiumDriverLocalService.class); - 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); @@ -217,7 +216,7 @@ public void stop() { * @return The exit code of the process or zero if the process was not running. */ private int destroyProcess(Duration timeout) { - if (!process.isRunning()) { + if (process == null || !process.isRunning()) { return 0; } From 6bd82d6f6d5d310f56f94c858ce63f2b9e743d39 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 4 Apr 2023 07:22:12 +0200 Subject: [PATCH 074/314] chore: Bump the minimum selenium client version to 4.8.2 (#1880) --- gradle.properties | 2 +- .../local/AppiumDriverLocalService.java | 6 +----- .../service/local/AppiumServiceBuilder.java | 21 +++++++++++++++---- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/gradle.properties b/gradle.properties index 716bc2a39..e1a1cc258 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.7.0 +selenium.version=4.8.2 # Please increment the value in a release appiumClient.version=8.3.0 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 b16570f1e..d2b4d37fc 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 @@ -123,11 +123,7 @@ public URL getUrl() { public boolean isRunning() { lock.lock(); try { - if (process == null) { - return false; - } - - if (!process.isRunning()) { + if (process == null || !process.isRunning()) { return false; } 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 1e6885c48..0b795872d 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 @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.appium.java_client.internal.ReflectionHelpers; import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileBrowserType; import io.appium.java_client.remote.MobileCapabilityType; @@ -160,7 +161,6 @@ private static File findMainScript() { return mainAppiumJs; } - @Override protected File findDefaultExecutable() { if (this.node != null) { validatePath(this.node.getAbsolutePath(), NODE_JS_NOT_EXIST_ERROR.apply(this.node)); @@ -228,8 +228,10 @@ public AppiumServiceBuilder withArgument(ServerArgument argument, String value) private static String sanitizeBasePath(String basePath) { basePath = checkNotNull(basePath).trim(); - checkArgument(!basePath.isEmpty(), - "Given base path is not valid - blank or empty values are not allowed for base path"); + checkArgument( + !basePath.isEmpty(), + "Given base path is not valid - blank or empty values are not allowed for base path" + ); return basePath.endsWith("/") ? basePath : basePath + "/"; } @@ -401,6 +403,17 @@ protected ImmutableList createArgs() { return new ImmutableList.Builder().addAll(argList).build(); } + @Override + public AppiumDriverLocalService build() { + File driverExecutable = ReflectionHelpers.getPrivateFieldValue( + DriverService.Builder.class, this, "exe", File.class + ); + if (driverExecutable == null) { + usingDriverExecutable(findDefaultExecutable()); + } + return super.build(); + } + /** * Sets which Node.js the builder will use. * @@ -468,4 +481,4 @@ protected AppiumDriverLocalService createDriverService(File nodeJSExecutable, in return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, startupTimeout, nodeArguments, nodeEnvironment).withBasePath(basePath); } -} +} \ No newline at end of file From 9be6de41e41f03afb68ce239db86e71545c63f6a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 14 Apr 2023 08:34:11 +0200 Subject: [PATCH 075/314] refactor: Replace non-W3C API calls with corresponding extension calls in app management (#1883) --- .../java_client/CommandExecutionHelper.java | 29 ++++++++ .../appium/java_client/InteractsWithApps.java | 70 +++++++++---------- .../io/appium/java_client/MobileCommand.java | 61 ++++++++++++++++ 3 files changed, 124 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 00557b6da..5290f319a 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -18,8 +18,14 @@ import org.openqa.selenium.remote.Response; +import javax.annotation.Nullable; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; + public final class CommandExecutionHelper { public static T execute(ExecutesMethod executesMethod, @@ -37,4 +43,27 @@ private static T handleResponse(Response response) { } return null; } + + public static T executeScript(ExecutesMethod executesMethod, String scriptName) { + return executeScript(executesMethod, scriptName, null); + } + + /** + * Simplifies arguments preparation for the script execution command. + * + * @param executesMethod Method executor instance. + * @param scriptName Extension script name. + * @param args Extension script arguments (if present). + * @return Script execution result. + */ + public static T executeScript( + ExecutesMethod executesMethod, String scriptName, @Nullable Map args + ) { + Map payload = new HashMap<>(); + payload.put("script", scriptName); + if (args != null) { + payload.put("args", args.isEmpty() ? Collections.emptyList() : Collections.singletonList(args)); + } + return execute(executesMethod, new AbstractMap.SimpleEntry<>(EXECUTE_SCRIPT, payload)); + } } diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index eb73e6e30..fc5e17ada 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -20,21 +20,17 @@ import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.appmanagement.BaseActivateApplicationOptions; import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; +import io.appium.java_client.appmanagement.BaseOptions; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; import javax.annotation.Nullable; import java.time.Duration; -import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; -import static io.appium.java_client.MobileCommand.ACTIVATE_APP; -import static io.appium.java_client.MobileCommand.INSTALL_APP; -import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; -import static io.appium.java_client.MobileCommand.QUERY_APP_STATE; -import static io.appium.java_client.MobileCommand.REMOVE_APP; import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; -import static io.appium.java_client.MobileCommand.TERMINATE_APP; -import static io.appium.java_client.MobileCommand.prepareArguments; @SuppressWarnings("rawtypes") public interface InteractsWithApps extends ExecutesMethod { @@ -56,12 +52,11 @@ default void installApp(String appPath) { * the particular platform. */ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions options) { - String[] parameters = options == null ? new String[]{"appPath"} : - new String[]{"appPath", "options"}; - Object[] values = options == null ? new Object[]{appPath} : - new Object[]{appPath, options.build()}; - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(INSTALL_APP, prepareArguments(parameters, values))); + Map args = new HashMap<>(); + args.put("app", appPath); + args.put("appPath", appPath); + Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); + CommandExecutionHelper.executeScript(this, "mobile: installApp", args); } /** @@ -71,8 +66,10 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions * @return True if app is installed, false otherwise. */ default boolean isAppInstalled(String bundleId) { - return CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(IS_APP_INSTALLED, prepareArguments("bundleId", bundleId))); + return CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( + "bundleId", bundleId, + "appId", bundleId + )); } /** @@ -106,12 +103,11 @@ default boolean removeApp(String bundleId) { * @return true if the uninstall was successful. */ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOptions options) { - String[] parameters = options == null ? new String[]{"bundleId"} : - new String[]{"bundleId", "options"}; - Object[] values = options == null ? new Object[]{bundleId} : - new Object[]{bundleId, options.build()}; - return CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(REMOVE_APP, prepareArguments(parameters, values))); + Map args = new HashMap<>(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); + return CommandExecutionHelper.executeScript(this, "mobile: removeApp", args); } /** @@ -133,12 +129,11 @@ default void activateApp(String bundleId) { * particular platform. */ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptions options) { - String[] parameters = options == null ? new String[]{"bundleId"} : - new String[]{"bundleId", "options"}; - Object[] values = options == null ? new Object[]{bundleId} : - new Object[]{bundleId, options.build()}; - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(ACTIVATE_APP, prepareArguments(parameters, values))); + Map args = new HashMap<>(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); + CommandExecutionHelper.executeScript(this, "mobile: activateApp", args); } /** @@ -148,8 +143,12 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio * @return one of possible {@link ApplicationState} values, */ default ApplicationState queryAppState(String bundleId) { - return ApplicationState.ofCode(CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId)))); + return ApplicationState.ofCode( + CommandExecutionHelper.executeScript(this, "mobile: queryAppState", ImmutableMap.of( + "bundleId", bundleId, + "appId", bundleId + )) + ); } /** @@ -171,11 +170,10 @@ default boolean terminateApp(String bundleId) { * @return true if the app was running before and has been successfully stopped. */ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplicationOptions options) { - String[] parameters = options == null ? new String[]{"bundleId"} : - new String[]{"bundleId", "options"}; - Object[] values = options == null ? new Object[]{bundleId} : - new Object[]{bundleId, options.build()}; - return CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(TERMINATE_APP, prepareArguments(parameters, values))); + Map args = new HashMap<>(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); + return CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args); } } diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index f5e9c7fb1..dbcab47b2 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -35,83 +35,144 @@ /** * The repository of mobile commands defined in the Mobile JSON * wire protocol. + * + * Most of these commands are platform-specific obsolete things and should eventually be replaced with + * calls to corresponding `mobile:` extensions, so we don't abuse non-w3c APIs */ public class MobileCommand { //General + @Deprecated protected static final String RESET; + @Deprecated protected static final String GET_STRINGS; + @Deprecated public static final String SET_VALUE; + @Deprecated protected static final String PULL_FILE; + @Deprecated protected static final String PULL_FOLDER; public static final String RUN_APP_IN_BACKGROUND; + @Deprecated protected static final String PERFORM_TOUCH_ACTION; + @Deprecated protected static final String PERFORM_MULTI_TOUCH; + @Deprecated public static final String LAUNCH_APP; + @Deprecated public static final String CLOSE_APP; + @Deprecated protected static final String GET_DEVICE_TIME; + @Deprecated protected static final String GET_SESSION; protected static final String LOG_EVENT; protected static final String GET_EVENTS; //region Applications Management + @Deprecated protected static final String IS_APP_INSTALLED; + @Deprecated protected static final String INSTALL_APP; + @Deprecated protected static final String ACTIVATE_APP; + @Deprecated protected static final String QUERY_APP_STATE; + @Deprecated protected static final String TERMINATE_APP; + @Deprecated protected static final String REMOVE_APP; //endregion //region Clipboard + @Deprecated public static final String GET_CLIPBOARD; + @Deprecated public static final String SET_CLIPBOARD; //endregion + @Deprecated protected static final String GET_PERFORMANCE_DATA; + @Deprecated protected static final String GET_SUPPORTED_PERFORMANCE_DATA_TYPES; + @Deprecated public static final String START_RECORDING_SCREEN; + @Deprecated public static final String STOP_RECORDING_SCREEN; + @Deprecated protected static final String HIDE_KEYBOARD; + @Deprecated protected static final String LOCK; //iOS + @Deprecated protected static final String SHAKE; + @Deprecated protected static final String TOUCH_ID; + @Deprecated protected static final String TOUCH_ID_ENROLLMENT; //Android + @Deprecated protected static final String CURRENT_ACTIVITY; + @Deprecated protected static final String END_TEST_COVERAGE; + @Deprecated protected static final String GET_DISPLAY_DENSITY; + @Deprecated protected static final String GET_NETWORK_CONNECTION; + @Deprecated protected static final String GET_SYSTEM_BARS; + @Deprecated protected static final String IS_KEYBOARD_SHOWN; + @Deprecated protected static final String IS_LOCKED; + @Deprecated public static final String LONG_PRESS_KEY_CODE; + @Deprecated protected static final String FINGER_PRINT; + @Deprecated protected static final String OPEN_NOTIFICATIONS; + @Deprecated public static final String PRESS_KEY_CODE; + @Deprecated protected static final String PUSH_FILE; + @Deprecated protected static final String SET_NETWORK_CONNECTION; + @Deprecated protected static final String START_ACTIVITY; + @Deprecated protected static final String TOGGLE_LOCATION_SERVICES; + @Deprecated protected static final String UNLOCK; + @Deprecated public static final String REPLACE_VALUE; protected static final String GET_SETTINGS; + @Deprecated protected static final String SET_SETTINGS; + @Deprecated protected static final String GET_CURRENT_PACKAGE; + @Deprecated protected static final String SEND_SMS; + @Deprecated protected static final String GSM_CALL; + @Deprecated protected static final String GSM_SIGNAL; + @Deprecated protected static final String GSM_VOICE; + @Deprecated protected static final String NETWORK_SPEED; + @Deprecated protected static final String POWER_CAPACITY; + @Deprecated protected static final String POWER_AC_STATE; + @Deprecated protected static final String TOGGLE_WIFI; + @Deprecated protected static final String TOGGLE_AIRPLANE_MODE; + @Deprecated protected static final String TOGGLE_DATA; protected static final String COMPARE_IMAGES; protected static final String EXECUTE_DRIVER_SCRIPT; + @Deprecated protected static final String GET_ALLSESSION; protected static final String EXECUTE_GOOGLE_CDP_COMMAND; From 5733d27bd75551bc0efec793afbf0b3b869e4636 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 14 Apr 2023 08:58:40 +0200 Subject: [PATCH 076/314] refactor: Switch the time getter to use mobile extensions (#1884) --- .../java_client/CommandExecutionHelper.java | 6 ++---- .../io/appium/java_client/HasDeviceTime.java | 17 +++-------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 5290f319a..e0a379e17 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -38,10 +38,8 @@ public static T execute(ExecutesMethod executesMethod, String command) { } private static T handleResponse(Response response) { - if (response != null) { - return (T) response.getValue(); - } - return null; + //noinspection unchecked + return response == null ? null : (T) response.getValue(); } public static T executeScript(ExecutesMethod executesMethod, String scriptName) { diff --git a/src/main/java/io/appium/java_client/HasDeviceTime.java b/src/main/java/io/appium/java_client/HasDeviceTime.java index fb9fc59b6..f29e3f3f1 100644 --- a/src/main/java/io/appium/java_client/HasDeviceTime.java +++ b/src/main/java/io/appium/java_client/HasDeviceTime.java @@ -16,14 +16,7 @@ package io.appium.java_client; -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; - -import static io.appium.java_client.MobileCommand.GET_DEVICE_TIME; public interface HasDeviceTime extends ExecutesMethod { @@ -38,12 +31,9 @@ public interface HasDeviceTime extends ExecutesMethod { * @return Device time string */ default String getDeviceTime(String format) { - Map params = ImmutableMap.of( - "script", "mobile: getDeviceTime", - "args", ImmutableList.of(ImmutableMap.of("format", format)) + return CommandExecutionHelper.executeScript( + this, "mobile: getDeviceTime", ImmutableMap.of("format", format) ); - Response response = execute(DriverCommand.EXECUTE_SCRIPT, params); - return response.getValue().toString(); } /** @@ -53,7 +43,6 @@ default String getDeviceTime(String format) { * @return Device time string */ default String getDeviceTime() { - Response response = execute(GET_DEVICE_TIME); - return response.getValue().toString(); + return CommandExecutionHelper.executeScript(this, "mobile: getDeviceTime"); } } From 6f19f27d36ab2d25936b02c908b30babe5d3e51a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 14 Apr 2023 16:18:16 +0300 Subject: [PATCH 077/314] build: Upgrade to Gradle 8.1 (#1882) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 ++++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 3c6be8d18..88070b21e 100644 --- a/build.gradle +++ b/build.gradle @@ -189,7 +189,7 @@ signing { } wrapper { - gradleVersion = '7.6' + gradleVersion = '8.1' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 8979 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 F{TFmrHLU;u delta 8469 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 Date: Mon, 17 Apr 2023 15:55:58 +0200 Subject: [PATCH 078/314] refactor: Switch file management APIs to use mobile: extensions (#1886) --- .../io/appium/java_client/PullsFiles.java | 30 ++++++++--------- .../io/appium/java_client/PushesFiles.java | 33 ++++++++----------- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index f85c18a6e..2dd403771 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -17,14 +17,10 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; -import org.openqa.selenium.remote.Response; import java.nio.charset.StandardCharsets; import java.util.Base64; -import static io.appium.java_client.MobileCommand.PULL_FILE; -import static io.appium.java_client.MobileCommand.PULL_FOLDER; - public interface PullsFiles extends ExecutesMethod { /** @@ -33,15 +29,16 @@ public interface PullsFiles extends ExecutesMethod { * built with debuggable flag enabled in order to get access to its container * on the internal file system. * - * @param remotePath If the path starts with @applicationId// prefix, then the file - * will be pulled from the root of the corresponding application container. - * Otherwise, the root folder is considered as / on Android and - * on iOS it is a media folder root (real devices only). + * @param remotePath Path to file to read data from the remote device. + * Check the documentation on `mobile: pullFile` + * extension for more details on possible values + * for different platforms. * @return A byte array of Base64 encoded data. */ default byte[] pullFile(String remotePath) { - Response response = execute(PULL_FILE, ImmutableMap.of("path", remotePath)); - String base64String = response.getValue().toString(); + String base64String = CommandExecutionHelper.executeScript(this, "mobile: pullFile", ImmutableMap.of( + "remotePath", remotePath + )); return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } @@ -51,15 +48,16 @@ default byte[] pullFile(String remotePath) { * built with debuggable flag enabled in order to get access to its container * on the internal file system. * - * @param remotePath If the path starts with @applicationId/ prefix, then the folder - * will be pulled from the root of the corresponding application container. - * Otherwise, the root folder is considered as / on Android and - * on iOS it is a media folder root (real devices only). + * @param remotePath Path to a folder to read data from the remote device. + * Check the documentation on `mobile: pullFolder` + * extension for more details on possible values + * for different platforms. * @return A byte array of Base64 encoded zip archive data. */ default byte[] pullFolder(String remotePath) { - Response response = execute(PULL_FOLDER, ImmutableMap.of("path", remotePath)); - String base64String = response.getValue().toString(); + String base64String = CommandExecutionHelper.executeScript(this, "mobile: pullFolder", ImmutableMap.of( + "remotePath", remotePath + )); return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index 552c25a83..813dcb5b1 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -16,47 +16,40 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Base64; -import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.MobileCommand.pushFileCommand; - public interface PushesFiles extends ExecutesMethod { /** - * Saves base64 encoded data as a media file on the remote system. + * Saves base64-encoded data as a file on the remote system. * - * @param remotePath Path to file to write data to on remote device - * Only the filename part matters there on Simulator, so the remote end - * can figure out which type of media data it is and save - * it into a proper folder on the target device. Check - * 'xcrun simctl addmedia' output to get more details on - * supported media types. - * If the path starts with @applicationId/ prefix, then the file - * will be pushed to the root of the corresponding application container. + * @param remotePath Path to file to write data to on remote device. + * Check the documentation on `mobile: pushFile` + * extension for more details on possible values + * for different platforms. * @param base64Data Base64 encoded byte array of media file data to write to remote device */ default void pushFile(String remotePath, byte[] base64Data) { - CommandExecutionHelper.execute(this, pushFileCommand(remotePath, base64Data)); + CommandExecutionHelper.executeScript(this, "mobile: pushFile", ImmutableMap.of( + "remotePath", remotePath, + "payload", new String(base64Data, StandardCharsets.UTF_8) + )); } /** - * Saves base64 encoded data as a media file on the remote system. + * Sends the file to the remote device. * * @param remotePath See the documentation on {@link #pushFile(String, byte[])} * @param file Is an existing local file to be written to the remote device - * @throws IOException when there are problems with a file or current file system + * @throws IOException when there are problems with a file on current file system */ default void pushFile(String remotePath, File file) throws IOException { - checkNotNull(file, "A reference to file should not be NULL"); - if (!file.exists()) { - throw new IOException(String.format("The given file %s doesn't exist", - file.getAbsolutePath())); - } pushFile(remotePath, Base64.getEncoder().encode(FileUtils.readFileToByteArray(file))); } From 1a7a12a2b625fe5da523bcd0ca61183433f4ad30 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 19 Apr 2023 12:21:32 +0200 Subject: [PATCH 079/314] refactor: Use mobile extensions for app strings getters and keyboard commands (#1890) --- .../java_client/CommandExecutionHelper.java | 26 ++++++----- .../io/appium/java_client/HasAppStrings.java | 46 +++++++++++++++---- .../appium/java_client/HasBrowserCheck.java | 12 ++--- .../java_client/HasOnScreenKeyboard.java | 13 +++++- .../io/appium/java_client/HidesKeyboard.java | 13 +++++- .../java_client/HidesKeyboardWithKeyName.java | 20 +++++++- .../appium/java_client/InteractsWithApps.java | 17 +++++-- .../io/appium/java_client/PullsFiles.java | 14 ++++-- .../java_client/android/AndroidDriver.java | 8 +--- .../android/ListensToLogcatMessages.java | 10 ++-- .../connection/HasNetworkConnection.java | 40 ++++++++++++++-- .../io/appium/java_client/ios/IOSDriver.java | 9 +--- .../ios/ListensToSyslogMessages.java | 10 ++-- 13 files changed, 166 insertions(+), 72 deletions(-) diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index e0a379e17..0a41f17b6 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -16,32 +16,37 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; import java.util.AbstractMap; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; public final class CommandExecutionHelper { - public static T execute(ExecutesMethod executesMethod, - Map.Entry> keyValuePair) { + @Nullable + public static T execute( + ExecutesMethod executesMethod, Map.Entry> keyValuePair + ) { return handleResponse(executesMethod.execute(keyValuePair.getKey(), keyValuePair.getValue())); } + @Nullable public static T execute(ExecutesMethod executesMethod, String command) { return handleResponse(executesMethod.execute(command)); } + @Nullable private static T handleResponse(Response response) { //noinspection unchecked return response == null ? null : (T) response.getValue(); } + @Nullable public static T executeScript(ExecutesMethod executesMethod, String scriptName) { return executeScript(executesMethod, scriptName, null); } @@ -50,18 +55,17 @@ public static T executeScript(ExecutesMethod executesMethod, String scriptNa * Simplifies arguments preparation for the script execution command. * * @param executesMethod Method executor instance. - * @param scriptName Extension script name. - * @param args Extension script arguments (if present). + * @param scriptName Extension script name. + * @param args Extension script arguments (if present). * @return Script execution result. */ + @Nullable public static T executeScript( ExecutesMethod executesMethod, String scriptName, @Nullable Map args ) { - Map payload = new HashMap<>(); - payload.put("script", scriptName); - if (args != null) { - payload.put("args", args.isEmpty() ? Collections.emptyList() : Collections.singletonList(args)); - } - return execute(executesMethod, new AbstractMap.SimpleEntry<>(EXECUTE_SCRIPT, payload)); + return execute(executesMethod, new AbstractMap.SimpleEntry<>(EXECUTE_SCRIPT, ImmutableMap.of( + "script", scriptName, + "args", (args == null || args.isEmpty()) ? Collections.emptyList() : Collections.singletonList(args) + ))); } } diff --git a/src/main/java/io/appium/java_client/HasAppStrings.java b/src/main/java/io/appium/java_client/HasAppStrings.java index 1983d4667..d093e777f 100644 --- a/src/main/java/io/appium/java_client/HasAppStrings.java +++ b/src/main/java/io/appium/java_client/HasAppStrings.java @@ -16,6 +16,9 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.UnsupportedCommandException; + import java.util.AbstractMap; import java.util.Map; @@ -25,37 +28,62 @@ public interface HasAppStrings extends ExecutesMethod { /** * Get all defined Strings from an app for the default language. + * See the documentation for 'mobile: getAppStrings' extension for more details. * * @return a map with localized strings defined in the app */ default Map getAppStringMap() { - return CommandExecutionHelper.execute(this, GET_STRINGS); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute(this, GET_STRINGS); + } } /** * Get all defined Strings from an app for the specified language. + * See the documentation for 'mobile: getAppStrings' extension for more details. * * @param language strings language code * @return a map with localized strings defined in the app */ default Map getAppStringMap(String language) { - return CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(GET_STRINGS, - prepareArguments("language", language))); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings", ImmutableMap.of( + "language", language + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments("language", language)) + ); + } } /** * Get all defined Strings from an app for the specified language and - * strings filename. + * strings filename. See the documentation for 'mobile: getAppStrings' + * extension for more details. * * @param language strings language code - * @param stringFile strings filename + * @param stringFile strings filename. Ignored on Android * @return a map with localized strings defined in the app */ default Map getAppStringMap(String language, String stringFile) { - String[] parameters = new String[] {"language", "stringFile"}; - Object[] values = new Object[] {language, stringFile}; - return CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments(parameters, values))); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings", ImmutableMap.of( + "language", language, + "stringFile", stringFile + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + String[] parameters = new String[]{"language", "stringFile"}; + Object[] values = new Object[]{language, stringFile}; + return CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments(parameters, values)) + ); + } } } diff --git a/src/main/java/io/appium/java_client/HasBrowserCheck.java b/src/main/java/io/appium/java_client/HasBrowserCheck.java index efa5c6c62..6b00a8d5d 100644 --- a/src/main/java/io/appium/java_client/HasBrowserCheck.java +++ b/src/main/java/io/appium/java_client/HasBrowserCheck.java @@ -1,17 +1,14 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.internal.CapabilityHelpers; import org.openqa.selenium.ContextAware; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; -import java.util.Collections; - +import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; public interface HasBrowserCheck extends ExecutesMethod, HasCapabilities { /** @@ -24,10 +21,9 @@ default boolean isBrowser() { CapabilityType.BROWSER_NAME, String.class); if (!isBlank(browserName)) { try { - return (boolean) execute(EXECUTE_SCRIPT, ImmutableMap.of( - "script", "return !!window.navigator;", - "args", Collections.emptyList() - )).getValue(); + return checkNotNull( + CommandExecutionHelper.executeScript(this, "return !!window.navigator;") + ); } catch (WebDriverException ign) { // ignore } diff --git a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java index 7a9d6febb..322c849db 100644 --- a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java +++ b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java @@ -1,15 +1,24 @@ package io.appium.java_client; +import org.openqa.selenium.UnsupportedCommandException; + +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.isKeyboardShownCommand; public interface HasOnScreenKeyboard extends ExecutesMethod { /** - * Check if the keyboard is displayed. + * Check if the on-screen keyboard is displayed. + * See the documentation for 'mobile: isKeyboardShown' extension for more details. * * @return true if keyboard is displayed. False otherwise */ default boolean isKeyboardShown() { - return CommandExecutionHelper.execute(this, isKeyboardShownCommand()); + try { + return checkNotNull(CommandExecutionHelper.executeScript(this, "mobile: isKeyboardShown")); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return checkNotNull(CommandExecutionHelper.execute(this, isKeyboardShownCommand())); + } } } diff --git a/src/main/java/io/appium/java_client/HidesKeyboard.java b/src/main/java/io/appium/java_client/HidesKeyboard.java index 5f292b0ce..6de1e0516 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboard.java +++ b/src/main/java/io/appium/java_client/HidesKeyboard.java @@ -16,14 +16,25 @@ package io.appium.java_client; +import org.openqa.selenium.UnsupportedCommandException; + import static io.appium.java_client.MobileCommand.HIDE_KEYBOARD; public interface HidesKeyboard extends ExecutesMethod { /** * Hides the keyboard if it is showing. + * If the on-screen keyboard does not have any dedicated button that + * hides it then an error is going to be thrown. In such case you must emulate + * same actions an app user would do to hide the keyboard. + * See the documentation for 'mobile: hideKeyboard' extension for more details. */ default void hideKeyboard() { - execute(HIDE_KEYBOARD); + try { + CommandExecutionHelper.executeScript(this, "mobile: hideKeyboard"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, HIDE_KEYBOARD); + } } } diff --git a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java index 2549ad018..c7e809911 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java +++ b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java @@ -16,19 +16,33 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.UnsupportedCommandException; + import static io.appium.java_client.MobileCommand.hideKeyboardCommand; public interface HidesKeyboardWithKeyName extends HidesKeyboard { /** * Hides the keyboard by pressing the button specified by keyName if it is - * showing. + * showing. If the on-screen keyboard does not have any dedicated button that + * hides it then an error is going to be thrown. In such case you must emulate + * same actions an app user would do to hide the keyboard. + * See the documentation for 'mobile: hideKeyboard' extension for more details. * * @param keyName The button pressed by the mobile driver to attempt hiding the * keyboard. */ default void hideKeyboard(String keyName) { - CommandExecutionHelper.execute(this, hideKeyboardCommand(keyName)); + try { + CommandExecutionHelper.executeScript(this, "mobile: hideKeyboard", ImmutableMap.of( + "keys", ImmutableList.of(keyName) + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, hideKeyboardCommand(keyName)); + } } /** @@ -39,7 +53,9 @@ default void hideKeyboard(String keyName) { * @param strategy HideKeyboardStrategy. * @param keyName a String, representing the text displayed on the button of the * keyboard you want to press. For example: "Done". + * @deprecated This API is deprecated and will be removed in the future. */ + @Deprecated default void hideKeyboard(String strategy, String keyName) { CommandExecutionHelper.execute(this, hideKeyboardCommand(strategy, keyName)); } diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index fc5e17ada..fedf957c8 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; @SuppressWarnings("rawtypes") @@ -66,10 +67,12 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions * @return True if app is installed, false otherwise. */ default boolean isAppInstalled(String bundleId) { - return CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( "bundleId", bundleId, "appId", bundleId - )); + )) + ); } /** @@ -107,7 +110,9 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption args.put("bundleId", bundleId); args.put("appId", bundleId); Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - return CommandExecutionHelper.executeScript(this, "mobile: removeApp", args); + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: removeApp", args) + ); } /** @@ -144,10 +149,12 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio */ default ApplicationState queryAppState(String bundleId) { return ApplicationState.ofCode( + checkNotNull( CommandExecutionHelper.executeScript(this, "mobile: queryAppState", ImmutableMap.of( "bundleId", bundleId, "appId", bundleId )) + ) ); } @@ -174,6 +181,8 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication args.put("bundleId", bundleId); args.put("appId", bundleId); Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - return CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args); + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args) + ); } } diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index 2dd403771..0042b0997 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -21,6 +21,8 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; +import static com.google.common.base.Preconditions.checkNotNull; + public interface PullsFiles extends ExecutesMethod { /** @@ -36,9 +38,11 @@ public interface PullsFiles extends ExecutesMethod { * @return A byte array of Base64 encoded data. */ default byte[] pullFile(String remotePath) { - String base64String = CommandExecutionHelper.executeScript(this, "mobile: pullFile", ImmutableMap.of( + String base64String = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: pullFile", ImmutableMap.of( "remotePath", remotePath - )); + )) + ); return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } @@ -55,9 +59,11 @@ default byte[] pullFile(String remotePath) { * @return A byte array of Base64 encoded zip archive data. */ default byte[] pullFolder(String remotePath) { - String base64String = CommandExecutionHelper.executeScript(this, "mobile: pullFolder", ImmutableMap.of( + String base64String = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: pullFolder", ImmutableMap.of( "remotePath", remotePath - )); + )) + ); return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } 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 0fc1c5917..c7290882b 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -16,7 +16,6 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; import io.appium.java_client.CommandExecutionHelper; @@ -50,13 +49,10 @@ import org.openqa.selenium.remote.http.HttpClient; import java.net.URL; -import java.util.Collections; -import java.util.Map; import static io.appium.java_client.android.AndroidMobileCommandHelper.endTestCoverageCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; /** * Android driver implementation. @@ -275,11 +271,9 @@ public void toggleLocationServices() { CommandExecutionHelper.execute(this, toggleLocationServicesCommand()); } - @SuppressWarnings("unchecked") @Override public AndroidBatteryInfo getBatteryInfo() { - return new AndroidBatteryInfo((Map) execute(EXECUTE_SCRIPT, ImmutableMap.of( - "script", "mobile: batteryInfo", "args", Collections.emptyList())).getValue()); + return new AndroidBatteryInfo(CommandExecutionHelper.executeScript(this, "mobile: batteryInfo")); } @Override diff --git a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java index 8f9cf1b38..8d16bf7de 100644 --- a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java +++ b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java @@ -16,18 +16,16 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; import org.openqa.selenium.remote.RemoteWebDriver; import java.net.URI; import java.net.URISyntaxException; -import java.util.Collections; import java.util.function.Consumer; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; public interface ListensToLogcatMessages extends ExecutesMethod { StringWebSocketClient getLogcatClient(); @@ -58,8 +56,7 @@ default void startLogcatBroadcast(String host) { * @param port the port of the host where Appium server is running */ default void startLogcatBroadcast(String host, int port) { - execute(EXECUTE_SCRIPT, ImmutableMap.of("script", "mobile: startLogsBroadcast", - "args", Collections.emptyList())); + CommandExecutionHelper.executeScript(this, "mobile: startLogsBroadcast"); final URI endpointUri; try { endpointUri = new URI(String.format("ws://%s:%s/ws/session/%s/appium/device/logcat", @@ -132,7 +129,6 @@ default void removeAllLogcatListeners() { */ default void stopLogcatBroadcast() { removeAllLogcatListeners(); - execute(EXECUTE_SCRIPT, ImmutableMap.of("script", "mobile: stopLogsBroadcast", - "args", Collections.emptyList())); + CommandExecutionHelper.executeScript(this, "mobile: stopLogsBroadcast"); } } diff --git a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java index 7f76340a3..f77bf7d3a 100644 --- a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java +++ b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java @@ -16,9 +16,14 @@ package io.appium.java_client.android.connection; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.getNetworkConnectionCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.setConnectionCommand; @@ -31,8 +36,21 @@ public interface HasNetworkConnection extends ExecutesMethod { * @return Connection object, which represents the resulting state */ default ConnectionState setConnection(ConnectionState connection) { - return new ConnectionState(CommandExecutionHelper.execute(this, - setConnectionCommand(connection.getBitMask()))); + try { + CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + "wifi", connection.isWiFiEnabled(), + "data", connection.isDataEnabled(), + "airplaneMode", connection.isAirplaneModeEnabled() + )); + return getConnection(); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return new ConnectionState( + checkNotNull( + CommandExecutionHelper.execute(this, setConnectionCommand(connection.getBitMask())) + ) + ); + } } /** @@ -41,6 +59,22 @@ default ConnectionState setConnection(ConnectionState connection) { * @return Connection object, which lets you to inspect the current status */ default ConnectionState getConnection() { - return new ConnectionState(CommandExecutionHelper.execute(this, getNetworkConnectionCommand())); + try { + Map result = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + ); + return new ConnectionState( + ((boolean) result.get("wifi") ? ConnectionState.WIFI_MASK : 0) + | ((boolean) result.get("data") ? ConnectionState.DATA_MASK : 0) + | ((boolean) result.get("airplaneMode") ? ConnectionState.AIRPLANE_MODE_MASK : 0) + ); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return new ConnectionState( + checkNotNull( + CommandExecutionHelper.execute(this, getNetworkConnectionCommand()) + ) + ); + } } } 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 ad00a5f90..2b735d649 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -16,9 +16,9 @@ package io.appium.java_client.ios; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.AppiumDriver; +import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.HasAppStrings; import io.appium.java_client.HasDeviceTime; import io.appium.java_client.HasOnScreenKeyboard; @@ -50,11 +50,8 @@ import org.openqa.selenium.remote.http.HttpClient; import java.net.URL; -import java.util.Collections; -import java.util.Map; import static io.appium.java_client.MobileCommand.prepareArguments; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; /** * iOS driver implementation. @@ -247,11 +244,9 @@ public IOSDriver(Capabilities capabilities) { return new InnerTargetLocator(); } - @SuppressWarnings("unchecked") @Override public IOSBatteryInfo getBatteryInfo() { - return new IOSBatteryInfo((Map) execute(EXECUTE_SCRIPT, ImmutableMap.of( - "script", "mobile: batteryInfo", "args", Collections.emptyList())).getValue()); + return new IOSBatteryInfo(CommandExecutionHelper.executeScript(this, "mobile: batteryInfo")); } private class InnerTargetLocator extends RemoteTargetLocator { diff --git a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java index 3341f753d..ef0bd3f9d 100644 --- a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java +++ b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java @@ -16,18 +16,16 @@ package io.appium.java_client.ios; -import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; import org.openqa.selenium.remote.RemoteWebDriver; import java.net.URI; import java.net.URISyntaxException; -import java.util.Collections; import java.util.function.Consumer; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; -import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; public interface ListensToSyslogMessages extends ExecutesMethod { @@ -59,8 +57,7 @@ default void startSyslogBroadcast(String host) { * @param port the port of the host where Appium server is running */ default void startSyslogBroadcast(String host, int port) { - execute(EXECUTE_SCRIPT, ImmutableMap.of("script", "mobile: startLogsBroadcast", - "args", Collections.emptyList())); + CommandExecutionHelper.executeScript(this, "mobile: startLogsBroadcast"); final URI endpointUri; try { endpointUri = new URI(String.format("ws://%s:%s/ws/session/%s/appium/device/syslog", @@ -132,7 +129,6 @@ default void removeAllSyslogListeners() { * Stops syslog messages broadcast via web socket. */ default void stopSyslogBroadcast() { - execute(EXECUTE_SCRIPT, ImmutableMap.of("script", "mobile: stopLogsBroadcast", - "args", Collections.emptyList())); + CommandExecutionHelper.executeScript(this, "mobile: stopLogsBroadcast"); } } From 1c0cf78fa995d8153709c991ebdbad0f1e01dc7c Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 21 Apr 2023 12:10:55 +0200 Subject: [PATCH 080/314] refactor: Finish replacing iOS extensions with their mobile alternatives (#1892) --- .../io/appium/java_client/LocksDevice.java | 33 +++++++++++++++++-- .../io/appium/java_client/MobileCommand.java | 23 ++++++++++++- .../ios/IOSMobileCommandHelper.java | 11 +++++-- .../java_client/ios/PerformsTouchID.java | 22 +++++++------ .../appium/java_client/ios/ShakesDevice.java | 10 ++++-- 5 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/appium/java_client/LocksDevice.java b/src/main/java/io/appium/java_client/LocksDevice.java index ce4d794c8..6f3033e05 100644 --- a/src/main/java/io/appium/java_client/LocksDevice.java +++ b/src/main/java/io/appium/java_client/LocksDevice.java @@ -16,8 +16,12 @@ package io.appium.java_client; +import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.UnsupportedCommandException; + import java.time.Duration; +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.getIsDeviceLockedCommand; import static io.appium.java_client.MobileCommand.lockDeviceCommand; import static io.appium.java_client.MobileCommand.unlockDeviceCommand; @@ -41,7 +45,14 @@ default void lockDevice() { * A negative/zero value will lock the device and return immediately. */ default void lockDevice(Duration duration) { - CommandExecutionHelper.execute(this, lockDeviceCommand(duration)); + try { + CommandExecutionHelper.executeScript(this, "mobile: lock", ImmutableMap.of( + "seconds", duration.getSeconds() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, lockDeviceCommand(duration)); + } } /** @@ -49,7 +60,16 @@ default void lockDevice(Duration duration) { * is not locked. */ default void unlockDevice() { - CommandExecutionHelper.execute(this, unlockDeviceCommand()); + try { + //noinspection ConstantConditions + if (!(Boolean) CommandExecutionHelper.executeScript(this, "mobile: isLocked")) { + return; + } + CommandExecutionHelper.executeScript(this, "mobile: unlock"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, unlockDeviceCommand()); + } } /** @@ -58,6 +78,13 @@ default void unlockDevice() { * @return true if the device is locked or false otherwise. */ default boolean isDeviceLocked() { - return CommandExecutionHelper.execute(this, getIsDeviceLockedCommand()); + try { + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: isLocked") + ); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return checkNotNull(CommandExecutionHelper.execute(this, getIsDeviceLockedCommand())); + } } } diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index dbcab47b2..8c2f4f350 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -35,7 +35,6 @@ /** * The repository of mobile commands defined in the Mobile JSON * wire protocol. - * * Most of these commands are platform-specific obsolete things and should eventually be replaced with * calls to corresponding `mobile:` extensions, so we don't abuse non-w3c APIs */ @@ -383,7 +382,9 @@ public static AppiumCommandInfo deleteC(String url) { * @param keyName The button pressed by the mobile driver to attempt hiding the * keyboard. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> hideKeyboardCommand(String keyName) { return new AbstractMap.SimpleEntry<>( HIDE_KEYBOARD, prepareArguments("keyName", keyName)); @@ -396,7 +397,9 @@ public static AppiumCommandInfo deleteC(String url) { * @param keyName a String, representing the text displayed on the button of the * keyboard you want to press. For example: "Done". * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> hideKeyboardCommand(String strategy, String keyName) { String[] parameters = new String[]{"strategy", "key"}; @@ -442,7 +445,9 @@ public static ImmutableMap prepareArguments(String[] params, * * @param key code for the key pressed on the device. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> pressKeyCodeCommand(int key) { return new AbstractMap.SimpleEntry<>( PRESS_KEY_CODE, prepareArguments("keycode", key)); @@ -454,7 +459,9 @@ public static ImmutableMap prepareArguments(String[] params, * @param key code for the key pressed on the Android device. * @param metastate metastate for the keypress. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> pressKeyCodeCommand(int key, Integer metastate) { String[] parameters = new String[]{"keycode", "metastate"}; @@ -468,7 +475,9 @@ public static ImmutableMap prepareArguments(String[] params, * * @param key code for the long key pressed on the device. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> longPressKeyCodeCommand(int key) { return new AbstractMap.SimpleEntry<>( LONG_PRESS_KEY_CODE, prepareArguments("keycode", key)); @@ -480,7 +489,9 @@ public static ImmutableMap prepareArguments(String[] params, * @param key code for the long key pressed on the Android device. * @param metastate metastate for the long key press. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> longPressKeyCodeCommand(int key, Integer metastate) { String[] parameters = new String[]{"keycode", "metastate"}; @@ -494,7 +505,9 @@ public static ImmutableMap prepareArguments(String[] params, * * @param duration for how long to lock the screen for. Minimum time resolution is one second * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> lockDeviceCommand(Duration duration) { return new AbstractMap.SimpleEntry<>( LOCK, prepareArguments("seconds", duration.getSeconds())); @@ -504,7 +517,9 @@ public static ImmutableMap prepareArguments(String[] params, * This method forms a {@link Map} of parameters for the device unlocking. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> unlockDeviceCommand() { return new AbstractMap.SimpleEntry<>(UNLOCK, ImmutableMap.of()); } @@ -513,7 +528,9 @@ public static ImmutableMap prepareArguments(String[] params, * This method forms a {@link Map} of parameters for the device locked query. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> getIsDeviceLockedCommand() { return new AbstractMap.SimpleEntry<>(IS_LOCKED, ImmutableMap.of()); } @@ -536,7 +553,9 @@ public static ImmutableMap prepareArguments(String[] params, * @param remotePath Path to file to write data to on remote device * @param base64Data Base64 encoded byte array of data to write to remote device * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> pushFileCommand(String remotePath, byte[] base64Data) { String[] parameters = new String[]{"path", "data"}; Object[] values = new Object[]{remotePath, new String(base64Data, StandardCharsets.UTF_8)}; @@ -580,7 +599,9 @@ public static ImmutableMap prepareArguments(String[] params, * This method forms a {@link Map} of parameters for the checking of the keyboard state (is it shown or not). * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated This helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> isKeyboardShownCommand() { return new AbstractMap.SimpleEntry<>(IS_KEYBOARD_SHOWN, ImmutableMap.of()); } diff --git a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java index 8e61cb5cf..390079d19 100644 --- a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java @@ -22,23 +22,28 @@ import java.util.AbstractMap; import java.util.Map; +@Deprecated public class IOSMobileCommandHelper extends MobileCommand { /** * This method forms a {@link Map} of parameters for the device shaking. * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated this helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> shakeCommand() { return new AbstractMap.SimpleEntry<>(SHAKE, ImmutableMap.of()); } - + /** * This method forms a {@link Map} of parameters for the touchId simulator. - * + * * @param match If true, simulates a successful fingerprint scan. If false, simulates a failed fingerprint scan. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated this helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> touchIdCommand(boolean match) { return new AbstractMap.SimpleEntry<>( TOUCH_ID, prepareArguments("match", match)); @@ -50,7 +55,9 @@ public class IOSMobileCommandHelper extends MobileCommand { * * @param enabled Whether to enable or disable Touch ID Enrollment for Simulator. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. + * @deprecated this helper is deprecated and will be removed in future versions. */ + @Deprecated public static Map.Entry> toggleTouchIdEnrollmentCommand(boolean enabled) { return new AbstractMap.SimpleEntry<>( TOUCH_ID_ENROLLMENT, prepareArguments("enabled", enabled)); diff --git a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java index 907a86655..af574bd2f 100644 --- a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java +++ b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java @@ -16,34 +16,36 @@ package io.appium.java_client.ios; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; -import static io.appium.java_client.ios.IOSMobileCommandHelper.toggleTouchIdEnrollmentCommand; -import static io.appium.java_client.ios.IOSMobileCommandHelper.touchIdCommand; - public interface PerformsTouchID extends ExecutesMethod { /** - * Simulate touchId event. + * Simulate touchId event on iOS Simulator. Check the documentation on 'mobile: sendBiometricMatch' + * extension for more details. * * @param match If true, simulates a successful fingerprint scan. If false, simulates a failed fingerprint scan. */ default void performTouchID(boolean match) { - CommandExecutionHelper.execute(this, touchIdCommand(match)); + CommandExecutionHelper.executeScript(this, "mobile: sendBiometricMatch", ImmutableMap.of( + "type", "touchId", + "match", match + )); } /** - * Enrolls touchId in iOS Simulators. This call will only work if Appium process or its - * parent application (e.g. Terminal.app or Appium.app) has - * access to Mac OS accessibility in System Preferences > - * Security & Privacy > Privacy > Accessibility list. + * Enrolls touchId in iOS Simulator. Check the documentation on 'mobile: enrollBiometric' + * extension for more details. * * @param enabled Whether to enable or disable Touch ID Enrollment. The actual state of the feature * will only be changed if the current value is different from the previous one. * Multiple calls of the method with the same argument value have no effect. */ default void toggleTouchIDEnrollment(boolean enabled) { - CommandExecutionHelper.execute(this, toggleTouchIdEnrollmentCommand(enabled)); + CommandExecutionHelper.executeScript(this, "mobile: enrollBiometric", ImmutableMap.of( + "isEnabled", enabled + )); } } diff --git a/src/main/java/io/appium/java_client/ios/ShakesDevice.java b/src/main/java/io/appium/java_client/ios/ShakesDevice.java index abd601852..1f34ecbfa 100644 --- a/src/main/java/io/appium/java_client/ios/ShakesDevice.java +++ b/src/main/java/io/appium/java_client/ios/ShakesDevice.java @@ -18,15 +18,21 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import static io.appium.java_client.ios.IOSMobileCommandHelper.shakeCommand; public interface ShakesDevice extends ExecutesMethod { /** - * Simulate shaking the device. + * Simulate shaking the Simulator. This API does not work for real devices. */ default void shake() { - CommandExecutionHelper.execute(this, shakeCommand()); + try { + CommandExecutionHelper.executeScript(this, "mobile: shake"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, shakeCommand()); + } } } From 99445bf9b169839014298aa48c976ee943f0b528 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 23 Apr 2023 20:09:45 +0200 Subject: [PATCH 081/314] refactor: Change some Android APIs to use mobile extensions (#1893) --- .../java_client/android/StartsActivity.java | 11 +++-- .../SupportsNetworkStateManagement.java | 48 ++++++++++++++++--- .../android/nativekey/PressesKey.java | 25 ++++++++-- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index ea4c04ce5..f01d50998 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -40,13 +40,16 @@ public interface StartsActivity extends ExecutesMethod { * * * @param activity The {@link Activity} object + * @deprecated Use 'mobile: startActivity' extension instead */ + @Deprecated default void startActivity(Activity activity) { CommandExecutionHelper.execute(this, - startActivityCommand(activity.getAppPackage(), activity.getAppActivity(), - activity.getAppWaitPackage(), activity.getAppWaitActivity(), - activity.getIntentAction(), activity.getIntentCategory(), activity.getIntentFlags(), - activity.getOptionalIntentArguments(), activity.isStopApp())); + startActivityCommand(activity.getAppPackage(), activity.getAppActivity(), + activity.getAppWaitPackage(), activity.getAppWaitActivity(), + activity.getIntentAction(), activity.getIntentCategory(), activity.getIntentFlags(), + activity.getOptionalIntentArguments(), activity.isStopApp()) + ); } /** diff --git a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java index 9100fbb69..9fdbd23c9 100644 --- a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java @@ -1,8 +1,13 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleAirplaneCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleWifiCommand; @@ -13,21 +18,52 @@ public interface SupportsNetworkStateManagement extends ExecutesMethod { * Toggles Wifi on and off. */ default void toggleWifi() { - CommandExecutionHelper.execute(this, toggleWifiCommand()); + try { + Map result = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + ); + CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + "wifi", !((Boolean) result.get("wifi")) + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, toggleWifiCommand()); + } } /** - * Toggle Airplane mode and this works on OS 6.0 and lesser - * and does not work on OS 7.0 and greater + * Toggle Airplane mode and this works on Android versions below + * 6 and above 10. */ default void toggleAirplaneMode() { - CommandExecutionHelper.execute(this, toggleAirplaneCommand()); + try { + Map result = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + ); + CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + "airplaneMode", !((Boolean) result.get("airplaneMode")) + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, toggleAirplaneCommand()); + } } /** - * Toggle Mobile Data and this works on Emulator and rooted device. + * Toggle Mobile Data and this works on Emulators and real devices + * running Android version above 10. */ default void toggleData() { - CommandExecutionHelper.execute(this, toggleDataCommand()); + try { + Map result = checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + ); + CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + "data", !((Boolean) result.get("data")) + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, toggleDataCommand()); + } } } diff --git a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java index 6c9f36212..9584ff517 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java +++ b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java @@ -16,10 +16,13 @@ package io.appium.java_client.android.nativekey; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import java.util.AbstractMap; +import java.util.Map; import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; import static io.appium.java_client.MobileCommand.PRESS_KEY_CODE; @@ -32,8 +35,13 @@ public interface PressesKey extends ExecutesMethod { * @param keyEvent The generated native key event */ default void pressKey(KeyEvent keyEvent) { - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(PRESS_KEY_CODE, keyEvent.build())); + try { + CommandExecutionHelper.executeScript(this, "mobile: pressKey", keyEvent.build()); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(PRESS_KEY_CODE, keyEvent.build())); + } } /** @@ -42,7 +50,16 @@ default void pressKey(KeyEvent keyEvent) { * @param keyEvent The generated native key event */ default void longPressKey(KeyEvent keyEvent) { - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(LONG_PRESS_KEY_CODE, keyEvent.build())); + try { + Map args = ImmutableMap.builder() + .putAll(keyEvent.build()) + .put("isLongPress", true) + .build(); + CommandExecutionHelper.executeScript(this, "mobile: pressKey", args); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(LONG_PRESS_KEY_CODE, keyEvent.build())); + } } } From c6fcd2ae98d8e9af7ca099f1c6b1b7543755fe81 Mon Sep 17 00:00:00 2001 From: wazzeps <120301053+wazzeps@users.noreply.github.com> Date: Mon, 24 Apr 2023 21:31:11 +0200 Subject: [PATCH 082/314] feat: Add SupportsEnforceAppInstallOption to XCUITestOptions (#1895) --- .../java_client/android/options/EspressoOptions.java | 2 +- .../java_client/android/options/UiAutomator2Options.java | 2 +- .../appium/java_client/ios/options/XCUITestOptions.java | 2 ++ .../java_client/remote/IOSMobileCapabilityType.java | 9 +++++++++ .../options}/SupportsEnforceAppInstallOption.java | 6 ++---- 5 files changed, 15 insertions(+), 6 deletions(-) rename src/main/java/io/appium/java_client/{android/options/app => remote/options}/SupportsEnforceAppInstallOption.java (91%) diff --git a/src/main/java/io/appium/java_client/android/options/EspressoOptions.java b/src/main/java/io/appium/java_client/android/options/EspressoOptions.java index c081be4f9..8cbe2cf33 100644 --- a/src/main/java/io/appium/java_client/android/options/EspressoOptions.java +++ b/src/main/java/io/appium/java_client/android/options/EspressoOptions.java @@ -37,7 +37,6 @@ import io.appium.java_client.android.options.app.SupportsAppWaitDurationOption; import io.appium.java_client.android.options.app.SupportsAppWaitPackageOption; import io.appium.java_client.android.options.app.SupportsAutoGrantPermissionsOption; -import io.appium.java_client.android.options.app.SupportsEnforceAppInstallOption; import io.appium.java_client.android.options.app.SupportsIntentOptionsOption; import io.appium.java_client.android.options.app.SupportsRemoteAppsCacheLimitOption; import io.appium.java_client.android.options.app.SupportsUninstallOtherPackagesOption; @@ -89,6 +88,7 @@ import io.appium.java_client.remote.options.SupportsAppOption; import io.appium.java_client.remote.options.SupportsAutoWebViewOption; import io.appium.java_client.remote.options.SupportsDeviceNameOption; +import io.appium.java_client.remote.options.SupportsEnforceAppInstallOption; import io.appium.java_client.remote.options.SupportsIsHeadlessOption; import io.appium.java_client.remote.options.SupportsLanguageOption; import io.appium.java_client.remote.options.SupportsLocaleOption; diff --git a/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java b/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java index adc161950..3c6a7bc51 100644 --- a/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java +++ b/src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java @@ -37,7 +37,6 @@ import io.appium.java_client.android.options.app.SupportsAppWaitForLaunchOption; import io.appium.java_client.android.options.app.SupportsAppWaitPackageOption; import io.appium.java_client.android.options.app.SupportsAutoGrantPermissionsOption; -import io.appium.java_client.android.options.app.SupportsEnforceAppInstallOption; import io.appium.java_client.android.options.app.SupportsIntentActionOption; import io.appium.java_client.android.options.app.SupportsIntentCategoryOption; import io.appium.java_client.android.options.app.SupportsIntentFlagsOption; @@ -95,6 +94,7 @@ import io.appium.java_client.remote.options.SupportsClearSystemFilesOption; import io.appium.java_client.remote.options.SupportsDeviceNameOption; import io.appium.java_client.remote.options.SupportsEnablePerformanceLoggingOption; +import io.appium.java_client.remote.options.SupportsEnforceAppInstallOption; import io.appium.java_client.remote.options.SupportsIsHeadlessOption; import io.appium.java_client.remote.options.SupportsLanguageOption; import io.appium.java_client.remote.options.SupportsLocaleOption; diff --git a/src/main/java/io/appium/java_client/ios/options/XCUITestOptions.java b/src/main/java/io/appium/java_client/ios/options/XCUITestOptions.java index 2eb61386d..53465b4dd 100644 --- a/src/main/java/io/appium/java_client/ios/options/XCUITestOptions.java +++ b/src/main/java/io/appium/java_client/ios/options/XCUITestOptions.java @@ -106,6 +106,7 @@ import io.appium.java_client.remote.options.SupportsClearSystemFilesOption; import io.appium.java_client.remote.options.SupportsDeviceNameOption; import io.appium.java_client.remote.options.SupportsEnablePerformanceLoggingOption; +import io.appium.java_client.remote.options.SupportsEnforceAppInstallOption; import io.appium.java_client.remote.options.SupportsIsHeadlessOption; import io.appium.java_client.remote.options.SupportsLanguageOption; import io.appium.java_client.remote.options.SupportsLocaleOption; @@ -136,6 +137,7 @@ public class XCUITestOptions extends BaseOptions implements SupportsOtherAppsOption, SupportsAppPushTimeoutOption, SupportsAppInstallStrategyOption, + SupportsEnforceAppInstallOption, // WebDriverAgent options: https://github.com/appium/appium-xcuitest-driver#webdriveragent SupportsXcodeCertificateOptions, SupportsKeychainOptions, diff --git a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java index b23150244..046dd4952 100644 --- a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java @@ -302,6 +302,15 @@ public interface IOSMobileCapabilityType extends CapabilityType { */ String UPDATE_WDA_BUNDLEID = "updatedWDABundleId"; + /** + * By default application installation is not skipped if newer or the same version of this app is already present on + * the device under test. Setting this option to {@code false} will enforce Appium to always skip the current + * application build. Defaults to {@code true}. + * + * @since 4.19.0 + */ + String ENFORCE_APP_INSTALL = "enforceAppInstall"; + /** * Whether to perform reset on test session finish (false) or not (true). * Keeping this variable set to true and Simulator running diff --git a/src/main/java/io/appium/java_client/android/options/app/SupportsEnforceAppInstallOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsEnforceAppInstallOption.java similarity index 91% rename from src/main/java/io/appium/java_client/android/options/app/SupportsEnforceAppInstallOption.java rename to src/main/java/io/appium/java_client/remote/options/SupportsEnforceAppInstallOption.java index 3acc25875..5e343938c 100644 --- a/src/main/java/io/appium/java_client/android/options/app/SupportsEnforceAppInstallOption.java +++ b/src/main/java/io/appium/java_client/remote/options/SupportsEnforceAppInstallOption.java @@ -14,10 +14,8 @@ * limitations under the License. */ -package io.appium.java_client.android.options.app; +package io.appium.java_client.remote.options; -import io.appium.java_client.remote.options.BaseOptions; -import io.appium.java_client.remote.options.CanSetCapability; import org.openqa.selenium.Capabilities; import java.util.Optional; @@ -40,7 +38,7 @@ default T enforceAppInstall() { /** * Allows setting whether the application under test is always reinstalled even - * if a newer version of it already exists on the device under test. false by default. + * if a newer version of it already exists on the device under test. False (Android), true (iOS) by default. * * @param value True to allow test packages installation. * @return self instance for chaining. From ec812f244a13279771421afc4a695b8a15b87865 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 26 Apr 2023 11:10:47 +0200 Subject: [PATCH 083/314] refactor: Change backgroundApp command to use the corresponding mobile extension (#1896) --- .../io/appium/java_client/InteractsWithApps.java | 16 ++++++++++++---- .../io/appium/java_client/MobileCommand.java | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index fedf957c8..0793cd79f 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -23,6 +23,7 @@ import io.appium.java_client.appmanagement.BaseOptions; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; +import org.openqa.selenium.UnsupportedCommandException; import javax.annotation.Nullable; import java.time.Duration; @@ -76,15 +77,22 @@ default boolean isAppInstalled(String bundleId) { } /** - * Runs the current app as a background app for the time + * Runs the current app in the background for the time * requested. This is a synchronous method, it blocks while the * application is in background. * - * @param duration The time to run App in background. Minimum time resolution is one millisecond. - * Passing zero or a negative value will switch to Home screen and return immediately. + * @param duration The time to run App in background. Minimum time resolution unit is one millisecond. + * Passing a negative value will switch to Home screen and return immediately. */ default void runAppInBackground(Duration duration) { - execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", duration.toMillis() / 1000.0)); + try { + CommandExecutionHelper.executeScript(this, "mobile: backgroundApp", ImmutableMap.of( + "seconds", duration.toMillis() / 1000.0 + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", duration.toMillis() / 1000.0)); + } } /** diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 8c2f4f350..5b2a6587d 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -50,6 +50,7 @@ public class MobileCommand { protected static final String PULL_FILE; @Deprecated protected static final String PULL_FOLDER; + @Deprecated public static final String RUN_APP_IN_BACKGROUND; @Deprecated protected static final String PERFORM_TOUCH_ACTION; From 6a4adb7bc70e538c459b3202ee33b2638b24f573 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 26 Apr 2023 14:49:48 +0200 Subject: [PATCH 084/314] fix: Send arguments for mobile methods depending on the target platform (#1897) --- .../appium/java_client/InteractsWithApps.java | 165 +++++++++++++----- 1 file changed, 125 insertions(+), 40 deletions(-) diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 0793cd79f..da39a0ded 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -23,18 +23,26 @@ import io.appium.java_client.appmanagement.BaseOptions; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; +import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.UnsupportedCommandException; import javax.annotation.Nullable; import java.time.Duration; -import java.util.HashMap; +import java.util.AbstractMap; +import java.util.Collections; import java.util.Map; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.ACTIVATE_APP; +import static io.appium.java_client.MobileCommand.INSTALL_APP; +import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; +import static io.appium.java_client.MobileCommand.QUERY_APP_STATE; +import static io.appium.java_client.MobileCommand.REMOVE_APP; import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; +import static io.appium.java_client.MobileCommand.TERMINATE_APP; -@SuppressWarnings("rawtypes") +@SuppressWarnings({"rawtypes", "unchecked"}) public interface InteractsWithApps extends ExecutesMethod { /** @@ -54,11 +62,23 @@ default void installApp(String appPath) { * the particular platform. */ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions options) { - Map args = new HashMap<>(); - args.put("app", appPath); - args.put("appPath", appPath); - Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - CommandExecutionHelper.executeScript(this, "mobile: installApp", args); + try { + Map args = ImmutableMap.builder() + .put("app", appPath) + .put("appPath", appPath) + .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) + .build(); + CommandExecutionHelper.executeScript(this, "mobile: installApp", args); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + Map args = ImmutableMap.builder() + .put("appPath", appPath) + .putAll(Optional.ofNullable(options).map( + (opts) -> ImmutableMap.of("options", opts.build()) + ).orElseGet(ImmutableMap::of)) + .build(); + CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(INSTALL_APP, args)); + } } /** @@ -68,12 +88,21 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions * @return True if app is installed, false otherwise. */ default boolean isAppInstalled(String bundleId) { - return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( - "bundleId", bundleId, - "appId", bundleId - )) - ); + try { + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( + "bundleId", bundleId, + "appId", bundleId + )) + ); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + return checkNotNull( + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId)) + ) + ); + } } /** @@ -114,13 +143,30 @@ default boolean removeApp(String bundleId) { * @return true if the uninstall was successful. */ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOptions options) { - Map args = new HashMap<>(); - args.put("bundleId", bundleId); - args.put("appId", bundleId); - Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: removeApp", args) - ); + try { + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .put("appId", bundleId) + .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) + .build(); + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: removeApp", args) + ); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .putAll(Optional.ofNullable(options).map( + (opts) -> ImmutableMap.of("options", opts.build()) + ).orElseGet(ImmutableMap::of)) + .build(); + //noinspection RedundantCast + return checkNotNull( + (Boolean) CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(REMOVE_APP, args) + ) + ); + } } /** @@ -142,11 +188,23 @@ default void activateApp(String bundleId) { * particular platform. */ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptions options) { - Map args = new HashMap<>(); - args.put("bundleId", bundleId); - args.put("appId", bundleId); - Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - CommandExecutionHelper.executeScript(this, "mobile: activateApp", args); + try { + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .put("appId", bundleId) + .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) + .build(); + CommandExecutionHelper.executeScript(this, "mobile: activateApp", args); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .putAll(Optional.ofNullable(options).map( + (opts) -> ImmutableMap.of("options", opts.build()) + ).orElseGet(ImmutableMap::of)) + .build(); + CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(ACTIVATE_APP, args)); + } } /** @@ -156,14 +214,24 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio * @return one of possible {@link ApplicationState} values, */ default ApplicationState queryAppState(String bundleId) { - return ApplicationState.ofCode( - checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: queryAppState", ImmutableMap.of( - "bundleId", bundleId, - "appId", bundleId - )) - ) - ); + try { + return ApplicationState.ofCode( + checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: queryAppState", ImmutableMap.of( + "bundleId", bundleId, + "appId", bundleId + )) + ) + ); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + return ApplicationState.ofCode( + checkNotNull( + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId))) + ) + ); + } } /** @@ -185,12 +253,29 @@ default boolean terminateApp(String bundleId) { * @return true if the app was running before and has been successfully stopped. */ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplicationOptions options) { - Map args = new HashMap<>(); - args.put("bundleId", bundleId); - args.put("appId", bundleId); - Optional.ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); - return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args) - ); + try { + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .put("appId", bundleId) + .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) + .build(); + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args) + ); + } catch (UnsupportedCommandException | InvalidArgumentException e) { + // TODO: Remove the fallback + Map args = ImmutableMap.builder() + .put("bundleId", bundleId) + .putAll(Optional.ofNullable(options).map( + (opts) -> ImmutableMap.of("options", opts.build()) + ).orElseGet(ImmutableMap::of)) + .build(); + //noinspection RedundantCast + return checkNotNull( + (Boolean) CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(TERMINATE_APP, args) + ) + ); + } } } From d163f8d2bd231a0ccca0ec80c28be197fbdc4674 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 29 Apr 2023 09:12:03 +0200 Subject: [PATCH 085/314] feat: Switch more Android helpers to use extensions (#1898) --- .../io/appium/java_client/MobileCommand.java | 18 ++-- .../java_client/android/AndroidDriver.java | 2 + .../android/AndroidMobileCommandHelper.java | 24 +++++ .../android/AuthenticatesByFinger.java | 11 +- .../android/CanReplaceElementValue.java | 26 +++-- .../android/HasAndroidDeviceDetails.java | 15 ++- .../java_client/android/StartsActivity.java | 30 +++++- .../SupportsSpecialEmulatorCommands.java | 102 +++++++++++++++--- .../android/AndroidDriverTest.java | 5 - 9 files changed, 190 insertions(+), 43 deletions(-) diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 5b2a6587d..721c854a7 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -112,7 +112,7 @@ public class MobileCommand { protected static final String TOUCH_ID_ENROLLMENT; //Android @Deprecated - protected static final String CURRENT_ACTIVITY; + public static final String CURRENT_ACTIVITY; @Deprecated protected static final String END_TEST_COVERAGE; @Deprecated @@ -149,21 +149,21 @@ public class MobileCommand { @Deprecated protected static final String SET_SETTINGS; @Deprecated - protected static final String GET_CURRENT_PACKAGE; + public static final String GET_CURRENT_PACKAGE; @Deprecated - protected static final String SEND_SMS; + public static final String SEND_SMS; @Deprecated - protected static final String GSM_CALL; + public static final String GSM_CALL; @Deprecated - protected static final String GSM_SIGNAL; + public static final String GSM_SIGNAL; @Deprecated - protected static final String GSM_VOICE; + public static final String GSM_VOICE; @Deprecated - protected static final String NETWORK_SPEED; + public static final String NETWORK_SPEED; @Deprecated - protected static final String POWER_CAPACITY; + public static final String POWER_CAPACITY; @Deprecated - protected static final String POWER_AC_STATE; + public static final String POWER_AC_STATE; @Deprecated protected static final String TOGGLE_WIFI; @Deprecated 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 c7290882b..9606be761 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -255,7 +255,9 @@ public AndroidDriver(URL remoteSessionAddress, String automationName) { * * @param intent intent to broadcast. * @param path path to .ec file. + * @deprecated This API will be removed */ + @Deprecated public void endTestCoverage(String intent, String path) { CommandExecutionHelper.execute(this, endTestCoverageCommand(intent, path)); } diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index 6b46ce945..1bed116d7 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -37,6 +37,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> currentActivityCommand() { return new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()); } @@ -46,6 +47,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> currentPackageCommand() { return new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()); } @@ -57,6 +59,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @param path path to .ec file. * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> endTestCoverageCommand(String intent, String path) { String[] parameters = new String[] {"intent", "path"}; @@ -117,6 +120,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> getDisplayDensityCommand() { return new AbstractMap.SimpleEntry<>(GET_DISPLAY_DENSITY, ImmutableMap.of()); } @@ -126,6 +130,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> getNetworkConnectionCommand() { return new AbstractMap.SimpleEntry<>(GET_NETWORK_CONNECTION, ImmutableMap.of()); } @@ -136,6 +141,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> getSystemBarsCommand() { return new AbstractMap.SimpleEntry<>(GET_SYSTEM_BARS, ImmutableMap.of()); } @@ -145,6 +151,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> isLockedCommand() { return new AbstractMap.SimpleEntry<>(IS_LOCKED, ImmutableMap.of()); } @@ -155,6 +162,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @param fingerPrintId finger prints stored in Android Keystore system (from 1 to 10) * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> fingerPrintCommand(int fingerPrintId) { return new AbstractMap.SimpleEntry<>(FINGER_PRINT, prepareArguments("fingerprintId", fingerPrintId)); @@ -165,6 +173,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> openNotificationsCommand() { return new AbstractMap.SimpleEntry<>(OPEN_NOTIFICATIONS, ImmutableMap.of()); } @@ -175,6 +184,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @param bitMask The bitmask of the desired connection * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> setConnectionCommand(long bitMask) { String[] parameters = new String[] {"name", "parameters"}; Object[] values = new Object[] {"network_connection", ImmutableMap.of("type", bitMask)}; @@ -198,6 +208,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. * @throws IllegalArgumentException when any required argument is empty */ + @Deprecated public static Map.Entry> startActivityCommand(String appPackage, String appActivity, String appWaitPackage, String appWaitActivity, String intentAction, String intentCategory, String intentFlags, @@ -234,6 +245,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> toggleLocationServicesCommand() { return new AbstractMap.SimpleEntry<>(TOGGLE_LOCATION_SERVICES, ImmutableMap.of()); } @@ -243,6 +255,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> unlockCommand() { return new AbstractMap.SimpleEntry<>(UNLOCK, ImmutableMap.of()); } @@ -256,6 +269,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @param value a new value * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> replaceElementValueCommand( RemoteWebElement remoteWebElement, String value) { String[] parameters = new String[] {"id", "value"}; @@ -275,6 +289,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> sendSMSCommand( String phoneNumber, String message) { ImmutableMap parameters = ImmutableMap @@ -294,6 +309,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> gsmCallCommand( String phoneNumber, GsmCallActions gsmCallActions) { String[] parameters = new String[] {"phoneNumber", "action"}; @@ -309,6 +325,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> gsmSignalStrengthCommand( GsmSignalStrength gsmSignalStrength) { return new AbstractMap.SimpleEntry<>(GSM_SIGNAL, @@ -327,6 +344,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> gsmVoiceCommand( GsmVoiceState gsmVoiceState) { return new AbstractMap.SimpleEntry<>(GSM_VOICE, @@ -341,6 +359,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> networkSpeedCommand( NetworkSpeed networkSpeed) { return new AbstractMap.SimpleEntry<>(NETWORK_SPEED, @@ -355,6 +374,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> powerCapacityCommand( int percent) { return new AbstractMap.SimpleEntry<>(POWER_CAPACITY, @@ -369,6 +389,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> powerACCommand( PowerACState powerACState) { return new AbstractMap.SimpleEntry<>(POWER_AC_STATE, @@ -380,6 +401,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> toggleWifiCommand() { return new AbstractMap.SimpleEntry<>(TOGGLE_WIFI, ImmutableMap.of()); } @@ -389,6 +411,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> toggleAirplaneCommand() { return new AbstractMap.SimpleEntry<>(TOGGLE_AIRPLANE_MODE, ImmutableMap.of()); } @@ -398,6 +421,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ + @Deprecated public static Map.Entry> toggleDataCommand() { return new AbstractMap.SimpleEntry<>(TOGGLE_DATA, ImmutableMap.of()); } diff --git a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java index 15503e239..a9ecf128f 100644 --- a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java +++ b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java @@ -1,7 +1,9 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import static io.appium.java_client.android.AndroidMobileCommandHelper.fingerPrintCommand; @@ -13,6 +15,13 @@ public interface AuthenticatesByFinger extends ExecutesMethod { * @param fingerPrintId finger prints stored in Android Keystore system (from 1 to 10) */ default void fingerPrint(int fingerPrintId) { - CommandExecutionHelper.execute(this, fingerPrintCommand(fingerPrintId)); + try { + CommandExecutionHelper.executeScript(this, "mobile: fingerprint", ImmutableMap.of( + "fingerprintId", fingerPrintId + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, fingerPrintCommand(fingerPrintId)); + } } } diff --git a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java index 72a170f32..949e7964c 100644 --- a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java +++ b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java @@ -1,21 +1,35 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.MobileCommand; +import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.remote.RemoteWebElement; public interface CanReplaceElementValue extends ExecutesMethod { /** - * Replaces element value with the given one. + * Sends a text to the given element by replacing its previous content. * * @param element The destination element. - * @param value The value to set. + * @param value The text to enter. It could also contain Unicode characters. + * If the text ends with `\\n` (the backslash must be escaped, so the + * char is NOT translated into `0x0A`) then the Enter key press is going to + * be emulated after it is entered (the `\\n` substring itself will be cut + * off from the typed text). */ default void replaceElementValue(RemoteWebElement element, String value) { - this.execute(MobileCommand.REPLACE_VALUE, ImmutableMap.of( - "id", element.getId(), - "value", value - )); + try { + CommandExecutionHelper.executeScript(this, "mobile: replaceValue", ImmutableMap.of( + "elementId", element.getId(), + "text", value + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(MobileCommand.REPLACE_VALUE, ImmutableMap.of( + "id", element.getId(), + "value", value + )); + } } } diff --git a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java index 66c084480..bcdcf1fd4 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java @@ -2,6 +2,7 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import java.util.Map; @@ -16,7 +17,12 @@ public interface HasAndroidDeviceDetails extends ExecutesMethod { @return The density value in dpi */ default Long getDisplayDensity() { - return CommandExecutionHelper.execute(this, getDisplayDensityCommand()); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getDisplayDensity"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute(this, getDisplayDensityCommand()); + } } /** @@ -25,7 +31,12 @@ default Long getDisplayDensity() { @return The map where keys are bar types and values are mappings of bar properties. */ default Map> getSystemBars() { - return CommandExecutionHelper.execute(this, getSystemBarsCommand()); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getSystemBars"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute(this, getSystemBarsCommand()); + } } } diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index f01d50998..9291e5020 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -16,11 +16,17 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; -import static io.appium.java_client.android.AndroidMobileCommandHelper.currentActivityCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.currentPackageCommand; +import javax.annotation.Nullable; + +import java.util.AbstractMap; + +import static io.appium.java_client.MobileCommand.CURRENT_ACTIVITY; +import static io.appium.java_client.MobileCommand.GET_CURRENT_PACKAGE; import static io.appium.java_client.android.AndroidMobileCommandHelper.startActivityCommand; public interface StartsActivity extends ExecutesMethod { @@ -57,8 +63,16 @@ default void startActivity(Activity activity) { * * @return a current activity being run on the mobile device. */ + @Nullable default String currentActivity() { - return CommandExecutionHelper.execute(this, currentActivityCommand()); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getCurrentActivity"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()) + ); + } } /** @@ -66,7 +80,15 @@ default String currentActivity() { * * @return a current package being run on the mobile device. */ + @Nullable default String getCurrentPackage() { - return CommandExecutionHelper.execute(this, currentPackageCommand()); + try { + return CommandExecutionHelper.executeScript(this, "mobile: getCurrentPackage"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute( + this, new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()) + ); + } } } diff --git a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java index 6baeef8a0..29cd6415e 100644 --- a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java +++ b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java @@ -1,15 +1,17 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; -import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmCallCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmSignalStrengthCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.gsmVoiceCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.networkSpeedCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.powerACCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.powerCapacityCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.sendSMSCommand; +import static io.appium.java_client.MobileCommand.GSM_CALL; +import static io.appium.java_client.MobileCommand.GSM_SIGNAL; +import static io.appium.java_client.MobileCommand.GSM_VOICE; +import static io.appium.java_client.MobileCommand.NETWORK_SPEED; +import static io.appium.java_client.MobileCommand.POWER_AC_STATE; +import static io.appium.java_client.MobileCommand.POWER_CAPACITY; +import static io.appium.java_client.MobileCommand.SEND_SMS; public interface SupportsSpecialEmulatorCommands extends ExecutesMethod { @@ -20,17 +22,39 @@ public interface SupportsSpecialEmulatorCommands extends ExecutesMethod { * @param message The message content. */ default void sendSMS(String phoneNumber, String message) { - CommandExecutionHelper.execute(this, sendSMSCommand(phoneNumber, message)); + try { + CommandExecutionHelper.executeScript(this, "mobile: sendSms", ImmutableMap.of( + "phoneNumber", phoneNumber, + "message", message + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(SEND_SMS, ImmutableMap.of( + "phoneNumber", phoneNumber, + "message", message + )); + } } /** * Emulate GSM call event on the connected emulator. * * @param phoneNumber The phone number of the caller. - * @param gsmCallActions One of available {@link GsmCallActions} values. + * @param gsmCallAction One of available {@link GsmCallActions} values. */ - default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallActions) { - CommandExecutionHelper.execute(this, gsmCallCommand(phoneNumber, gsmCallActions)); + default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { + try { + CommandExecutionHelper.executeScript(this, "mobile: gsmCall", ImmutableMap.of( + "phoneNumber", phoneNumber, + "action", gsmCallAction.toString().toLowerCase() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(GSM_CALL, ImmutableMap.of( + "phoneNumber", phoneNumber, + "action", gsmCallAction.toString().toLowerCase() + )); + } } /** @@ -39,7 +63,17 @@ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallActions) { * @param gsmSignalStrength One of available {@link GsmSignalStrength} values. */ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { - CommandExecutionHelper.execute(this, gsmSignalStrengthCommand(gsmSignalStrength)); + try { + CommandExecutionHelper.executeScript(this, "mobile: gsmSignal", ImmutableMap.of( + "strength", gsmSignalStrength.ordinal() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(GSM_SIGNAL, ImmutableMap.of( + "signalStrengh", gsmSignalStrength.ordinal(), + "signalStrength", gsmSignalStrength.ordinal() + )); + } } /** @@ -48,7 +82,16 @@ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { * @param gsmVoiceState One of available {@link GsmVoiceState} values. */ default void setGsmVoice(GsmVoiceState gsmVoiceState) { - CommandExecutionHelper.execute(this, gsmVoiceCommand(gsmVoiceState)); + try { + CommandExecutionHelper.executeScript(this, "mobile: gsmVoice", ImmutableMap.of( + "state", gsmVoiceState.toString().toLowerCase() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(GSM_VOICE, ImmutableMap.of( + "state", gsmVoiceState.name().toLowerCase() + )); + } } /** @@ -57,7 +100,16 @@ default void setGsmVoice(GsmVoiceState gsmVoiceState) { * @param networkSpeed One of available {@link NetworkSpeed} values. */ default void setNetworkSpeed(NetworkSpeed networkSpeed) { - CommandExecutionHelper.execute(this, networkSpeedCommand(networkSpeed)); + try { + CommandExecutionHelper.executeScript(this, "mobile: networkSpeed", ImmutableMap.of( + "speed", networkSpeed.toString().toLowerCase() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(NETWORK_SPEED, ImmutableMap.of( + "netspeed", networkSpeed.name().toLowerCase() + )); + } } /** @@ -66,7 +118,16 @@ default void setNetworkSpeed(NetworkSpeed networkSpeed) { * @param percent Percentage value in range [0, 100]. */ default void setPowerCapacity(int percent) { - CommandExecutionHelper.execute(this, powerCapacityCommand(percent)); + try { + CommandExecutionHelper.executeScript(this, "mobile: powerCapacity", ImmutableMap.of( + "percent", percent + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(POWER_CAPACITY, ImmutableMap.of( + "percent", percent + )); + } } /** @@ -75,7 +136,16 @@ default void setPowerCapacity(int percent) { * @param powerACState One of available {@link PowerACState} values. */ default void setPowerAC(PowerACState powerACState) { - CommandExecutionHelper.execute(this, powerACCommand(powerACState)); + try { + CommandExecutionHelper.executeScript(this, "mobile: powerAC", ImmutableMap.of( + "state", powerACState.toString().toLowerCase() + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + this.execute(POWER_AC_STATE, ImmutableMap.of( + "state", powerACState.name().toLowerCase() + )); + } } } diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index aa2434d50..246951d8a 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -246,11 +246,6 @@ public void resetTest() { driver.resetApp(); } - @Test - public void endTestCoverage() { - driver.endTestCoverage("android.intent.action.MAIN", ""); - } - @Test public void deviceDetailsAndKeyboardTest() { assertFalse(driver.isKeyboardShown()); From 95d4272def2ad28a9c9ac29aa631fa7a2e664daf Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 29 Apr 2023 18:29:14 +0200 Subject: [PATCH 086/314] ci: Perform xcuitest driver prebuild (#1900) --- azure-pipelines.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a3d59c308..4cea8df6d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,17 +4,17 @@ # https://docs.microsoft.com/azure/devops/pipelines/languages/java pool: - vmImage: 'macos-11' + vmImage: 'macos-12' variables: ANDROID_EMU_NAME: test ANDROID_EMU_ABI: x86 ANDROID_EMU_TARGET: android-28 ANDROID_EMU_TAG: default - XCODE_VERSION: 13.2 - IOS_PLATFORM_VERSION: 15.2 - IOS_DEVICE_NAME: iPhone X - NODE_VERSION: 16.x + XCODE_VERSION: 14.2 + IOS_PLATFORM_VERSION: 16.2 + IOS_DEVICE_NAME: iPhone 12 + NODE_VERSION: 18.x JDK_VERSION: 1.8 jobs: @@ -50,9 +50,14 @@ jobs: - script: | sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer xcrun simctl list - displayName: Simulator configuration - - script: $NVM_DIR/versions/node/`node --version`/bin/appium driver install xcuitest + target_sim_id=$(xcrun simctl list devices available | grep "$IOS_DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) + open -Fn "/Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer/Applications/Simulator.app" + xcrun simctl bootstatus $target_sim_id -b + displayName: Prepare iOS Simulator + - script: $NVM_DIR/versions/node/$(node --version)/bin/appium driver install xcuitest displayName: Install XCUITest driver + - script: $NVM_DIR/versions/node/$(node --version)/bin/appium driver run xcuitest build-wda + displayName: Prebuild XCUITest driver - task: Gradle@2 inputs: gradleWrapperFile: 'gradlew' From ba63c064bdcf25642ed458b18d784e96efd16e84 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 30 Apr 2023 16:25:42 +0200 Subject: [PATCH 087/314] refactor: Finish migrating Android helpers to mobile extensions (#1901) --- .../java_client/android/AndroidDriver.java | 15 ++------ .../android/CanReplaceElementValue.java | 1 + .../java_client/android/HasNotifications.java | 22 ++++++++++++ .../android/SupportsGpsStateManagement.java | 35 +++++++++++++++++++ 4 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 src/main/java/io/appium/java_client/android/HasNotifications.java create mode 100644 src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.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 9606be761..27b3bd5cf 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -51,8 +51,6 @@ import java.net.URL; import static io.appium.java_client.android.AndroidMobileCommandHelper.endTestCoverageCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; -import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; /** * Android driver implementation. @@ -86,6 +84,8 @@ public class AndroidDriver extends AppiumDriver implements HasBattery, ExecuteCDPCommand, CanReplaceElementValue, + SupportsGpsStateManagement, + HasNotifications, SupportsExtendedGeolocationCommands { private static final String ANDROID_PLATFORM = Platform.ANDROID.name(); @@ -262,17 +262,6 @@ public void endTestCoverage(String intent, String path) { CommandExecutionHelper.execute(this, endTestCoverageCommand(intent, path)); } - /** - * Open the notification shade, on Android devices. - */ - public void openNotifications() { - CommandExecutionHelper.execute(this, openNotificationsCommand()); - } - - public void toggleLocationServices() { - CommandExecutionHelper.execute(this, toggleLocationServicesCommand()); - } - @Override public AndroidBatteryInfo getBatteryInfo() { return new AndroidBatteryInfo(CommandExecutionHelper.executeScript(this, "mobile: batteryInfo")); diff --git a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java index 949e7964c..eb81ba417 100644 --- a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java +++ b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java @@ -28,6 +28,7 @@ default void replaceElementValue(RemoteWebElement element, String value) { // TODO: Remove the fallback this.execute(MobileCommand.REPLACE_VALUE, ImmutableMap.of( "id", element.getId(), + "text", value, "value", value )); } diff --git a/src/main/java/io/appium/java_client/android/HasNotifications.java b/src/main/java/io/appium/java_client/android/HasNotifications.java new file mode 100644 index 000000000..c2e870031 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/HasNotifications.java @@ -0,0 +1,22 @@ +package io.appium.java_client.android; + +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; + +import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; + +public interface HasNotifications extends ExecutesMethod { + + /** + * Opens notification drawer on the device under test. + */ + default void openNotifications() { + try { + CommandExecutionHelper.executeScript(this, "mobile: openNotifications"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, openNotificationsCommand()); + } + } +} diff --git a/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java new file mode 100644 index 000000000..597c98193 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java @@ -0,0 +1,35 @@ +package io.appium.java_client.android; + +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; + +public interface SupportsGpsStateManagement extends ExecutesMethod { + + /** + * Toggles GPS service state. + * This method only works reliably since API 31 (Android 12). + */ + default void toggleLocationServices() { + try { + CommandExecutionHelper.executeScript(this, "mobile: toggleGps"); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, toggleLocationServicesCommand()); + } + } + + /** + * Check GPS service state. + * + * @return true if GPS service is enabled. + */ + default boolean isLocationServicesEnabled() { + return checkNotNull( + CommandExecutionHelper.executeScript(this, "mobile: isGpsEnabled") + ); + } +} From 62a25e55be9e1966921f7112afba8fd45b9142e5 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 1 May 2023 19:33:34 +0200 Subject: [PATCH 088/314] chore: Avoid sending unnecessary requests if corresponding extensions are absent (#1903) --- .../io/appium/java_client/AppiumDriver.java | 19 ++++ .../CanRememberExtensionPresence.java | 25 +++++ .../io/appium/java_client/HasAppStrings.java | 19 ++-- .../java_client/HasOnScreenKeyboard.java | 9 +- .../io/appium/java_client/HidesKeyboard.java | 7 +- .../java_client/HidesKeyboardWithKeyName.java | 5 +- .../appium/java_client/InteractsWithApps.java | 63 ++++++++---- .../io/appium/java_client/LocksDevice.java | 21 ++-- .../android/AuthenticatesByFinger.java | 8 +- .../android/CanReplaceElementValue.java | 21 ++-- .../android/HasAndroidDeviceDetails.java | 13 ++- .../java_client/android/HasNotifications.java | 8 +- .../java_client/android/StartsActivity.java | 15 ++- .../android/SupportsGpsStateManagement.java | 8 +- .../SupportsNetworkStateManagement.java | 24 +++-- .../SupportsSpecialEmulatorCommands.java | 95 ++++++++++++------- .../connection/HasNetworkConnection.java | 19 +++- .../android/nativekey/PressesKey.java | 21 ++-- .../appium/java_client/ios/ShakesDevice.java | 8 +- 19 files changed, 282 insertions(+), 126 deletions(-) create mode 100644 src/main/java/io/appium/java_client/CanRememberExtensionPresence.java diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index debd404d3..38c80bf6d 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -32,6 +32,7 @@ import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.OutputType; import org.openqa.selenium.SessionNotCreatedException; +import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.DriverCommand; @@ -48,7 +49,9 @@ import java.net.URL; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME; @@ -64,6 +67,7 @@ public class AppiumDriver extends RemoteWebDriver implements ExecutesDriverScript, LogsEvents, HasBrowserCheck, + CanRememberExtensionPresence, HasSettings { private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); @@ -71,6 +75,7 @@ public class AppiumDriver extends RemoteWebDriver implements private final URL remoteAddress; protected final RemoteLocationContext locationContext; private final ExecuteMethod executeMethod; + private final Set absentExtensionNames = new HashSet<>(); /** * Creates a new instance based on command {@code executor} and {@code capabilities}. @@ -327,4 +332,18 @@ public X convertFromPngBytes(byte[] png) { } }); } + + @Override + public AppiumDriver assertExtensionExists(String extName) { + if (absentExtensionNames.contains(extName)) { + throw new UnsupportedCommandException(); + } + return this; + } + + @Override + public AppiumDriver markExtensionAbsence(String extName) { + absentExtensionNames.add(extName); + return this; + } } diff --git a/src/main/java/io/appium/java_client/CanRememberExtensionPresence.java b/src/main/java/io/appium/java_client/CanRememberExtensionPresence.java new file mode 100644 index 000000000..36cd4b903 --- /dev/null +++ b/src/main/java/io/appium/java_client/CanRememberExtensionPresence.java @@ -0,0 +1,25 @@ +package io.appium.java_client; + +import org.openqa.selenium.UnsupportedCommandException; + +public interface CanRememberExtensionPresence { + /** + * Verifies if the given extension is not present in the list of absent extensions + * for the given driver instance. + * This API is designed for private usage. + * + * @param extName extension name. + * @return self instance for chaining. + * @throws UnsupportedCommandException if the extension is listed in the list of absents. + */ + ExecutesMethod assertExtensionExists(String extName); + + /** + * Marks the given extension as absent for the given driver instance. + * This API is designed for private usage. + * + * @param extName extension name. + * @return self instance for chaining. + */ + ExecutesMethod markExtensionAbsence(String extName); +} diff --git a/src/main/java/io/appium/java_client/HasAppStrings.java b/src/main/java/io/appium/java_client/HasAppStrings.java index d093e777f..439e08bcd 100644 --- a/src/main/java/io/appium/java_client/HasAppStrings.java +++ b/src/main/java/io/appium/java_client/HasAppStrings.java @@ -25,7 +25,7 @@ import static io.appium.java_client.MobileCommand.GET_STRINGS; import static io.appium.java_client.MobileCommand.prepareArguments; -public interface HasAppStrings extends ExecutesMethod { +public interface HasAppStrings extends ExecutesMethod, CanRememberExtensionPresence { /** * Get all defined Strings from an app for the default language. * See the documentation for 'mobile: getAppStrings' extension for more details. @@ -33,11 +33,12 @@ public interface HasAppStrings extends ExecutesMethod { * @return a map with localized strings defined in the app */ default Map getAppStringMap() { + final String extName = "mobile: getAppStrings"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings"); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return CommandExecutionHelper.execute(this, GET_STRINGS); + return CommandExecutionHelper.execute(markExtensionAbsence(extName), GET_STRINGS); } } @@ -49,14 +50,16 @@ default Map getAppStringMap() { * @return a map with localized strings defined in the app */ default Map getAppStringMap(String language) { + final String extName = "mobile: getAppStrings"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings", ImmutableMap.of( + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "language", language )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments("language", language)) + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments("language", language)) ); } } @@ -71,8 +74,9 @@ default Map getAppStringMap(String language) { * @return a map with localized strings defined in the app */ default Map getAppStringMap(String language, String stringFile) { + final String extName = "mobile: getAppStrings"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getAppStrings", ImmutableMap.of( + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "language", language, "stringFile", stringFile )); @@ -81,7 +85,8 @@ default Map getAppStringMap(String language, String stringFile) String[] parameters = new String[]{"language", "stringFile"}; Object[] values = new Object[]{language, stringFile}; return CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments(parameters, values)) + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments(parameters, values)) ); } } diff --git a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java index 322c849db..d54c4a91f 100644 --- a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java +++ b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java @@ -5,7 +5,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.isKeyboardShownCommand; -public interface HasOnScreenKeyboard extends ExecutesMethod { +public interface HasOnScreenKeyboard extends ExecutesMethod, CanRememberExtensionPresence { /** * Check if the on-screen keyboard is displayed. @@ -14,11 +14,14 @@ public interface HasOnScreenKeyboard extends ExecutesMethod { * @return true if keyboard is displayed. False otherwise */ default boolean isKeyboardShown() { + final String extName = "mobile: isKeyboardShown"; try { - return checkNotNull(CommandExecutionHelper.executeScript(this, "mobile: isKeyboardShown")); + return checkNotNull(CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName)); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return checkNotNull(CommandExecutionHelper.execute(this, isKeyboardShownCommand())); + return checkNotNull( + CommandExecutionHelper.execute(markExtensionAbsence(extName), isKeyboardShownCommand()) + ); } } } diff --git a/src/main/java/io/appium/java_client/HidesKeyboard.java b/src/main/java/io/appium/java_client/HidesKeyboard.java index 6de1e0516..a6f522102 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboard.java +++ b/src/main/java/io/appium/java_client/HidesKeyboard.java @@ -20,7 +20,7 @@ import static io.appium.java_client.MobileCommand.HIDE_KEYBOARD; -public interface HidesKeyboard extends ExecutesMethod { +public interface HidesKeyboard extends ExecutesMethod, CanRememberExtensionPresence { /** * Hides the keyboard if it is showing. @@ -30,11 +30,12 @@ public interface HidesKeyboard extends ExecutesMethod { * See the documentation for 'mobile: hideKeyboard' extension for more details. */ default void hideKeyboard() { + final String extName = "mobile: hideKeyboard"; try { - CommandExecutionHelper.executeScript(this, "mobile: hideKeyboard"); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, HIDE_KEYBOARD); + CommandExecutionHelper.execute(markExtensionAbsence(extName), HIDE_KEYBOARD); } } } diff --git a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java index c7e809911..35d7a7b0c 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java +++ b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java @@ -35,13 +35,14 @@ public interface HidesKeyboardWithKeyName extends HidesKeyboard { * keyboard. */ default void hideKeyboard(String keyName) { + final String extName = "mobile: hideKeyboard"; try { - CommandExecutionHelper.executeScript(this, "mobile: hideKeyboard", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "keys", ImmutableList.of(keyName) )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, hideKeyboardCommand(keyName)); + CommandExecutionHelper.execute(markExtensionAbsence(extName), hideKeyboardCommand(keyName)); } } diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index da39a0ded..45f1c285d 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -43,7 +43,7 @@ import static io.appium.java_client.MobileCommand.TERMINATE_APP; @SuppressWarnings({"rawtypes", "unchecked"}) -public interface InteractsWithApps extends ExecutesMethod { +public interface InteractsWithApps extends ExecutesMethod, CanRememberExtensionPresence { /** * Install an app on the mobile device. @@ -62,13 +62,14 @@ default void installApp(String appPath) { * the particular platform. */ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions options) { + final String extName = "mobile: installApp"; try { Map args = ImmutableMap.builder() .put("app", appPath) .put("appPath", appPath) .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); - CommandExecutionHelper.executeScript(this, "mobile: installApp", args); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback Map args = ImmutableMap.builder() @@ -77,7 +78,9 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions (opts) -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); - CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(INSTALL_APP, args)); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(INSTALL_APP, args) + ); } } @@ -88,9 +91,10 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions * @return True if app is installed, false otherwise. */ default boolean isAppInstalled(String bundleId) { + final String extName = "mobile: isAppInstalled"; try { return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: isAppInstalled", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "bundleId", bundleId, "appId", bundleId )) @@ -98,8 +102,9 @@ default boolean isAppInstalled(String bundleId) { } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback return checkNotNull( - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId)) + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId)) ) ); } @@ -114,13 +119,19 @@ default boolean isAppInstalled(String bundleId) { * Passing a negative value will switch to Home screen and return immediately. */ default void runAppInBackground(Duration duration) { + final String extName = "mobile: backgroundApp"; try { - CommandExecutionHelper.executeScript(this, "mobile: backgroundApp", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "seconds", duration.toMillis() / 1000.0 )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", duration.toMillis() / 1000.0)); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(RUN_APP_IN_BACKGROUND, ImmutableMap.of( + "seconds", duration.toMillis() / 1000.0) + ) + ); } } @@ -143,6 +154,7 @@ default boolean removeApp(String bundleId) { * @return true if the uninstall was successful. */ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOptions options) { + final String extName = "mobile: removeApp"; try { Map args = ImmutableMap.builder() .put("bundleId", bundleId) @@ -150,7 +162,7 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: removeApp", args) + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback @@ -163,7 +175,8 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption //noinspection RedundantCast return checkNotNull( (Boolean) CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(REMOVE_APP, args) + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(REMOVE_APP, args) ) ); } @@ -188,13 +201,14 @@ default void activateApp(String bundleId) { * particular platform. */ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptions options) { + final String extName = "mobile: activateApp"; try { Map args = ImmutableMap.builder() .put("bundleId", bundleId) .put("appId", bundleId) .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); - CommandExecutionHelper.executeScript(this, "mobile: activateApp", args); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback Map args = ImmutableMap.builder() @@ -203,7 +217,9 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio (opts) -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); - CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(ACTIVATE_APP, args)); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(ACTIVATE_APP, args) + ); } } @@ -214,21 +230,27 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio * @return one of possible {@link ApplicationState} values, */ default ApplicationState queryAppState(String bundleId) { + final String extName = "mobile: queryAppState"; try { return ApplicationState.ofCode( checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: queryAppState", ImmutableMap.of( - "bundleId", bundleId, - "appId", bundleId - )) + CommandExecutionHelper.executeScript( + assertExtensionExists(extName), + extName, ImmutableMap.of( + "bundleId", bundleId, + "appId", bundleId + ) + ) ) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback return ApplicationState.ofCode( checkNotNull( - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId))) + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId)) + ) ) ); } @@ -253,6 +275,7 @@ default boolean terminateApp(String bundleId) { * @return true if the app was running before and has been successfully stopped. */ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplicationOptions options) { + final String extName = "mobile: terminateApp"; try { Map args = ImmutableMap.builder() .put("bundleId", bundleId) @@ -260,7 +283,7 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: terminateApp", args) + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback @@ -273,7 +296,7 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication //noinspection RedundantCast return checkNotNull( (Boolean) CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(TERMINATE_APP, args) + markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(TERMINATE_APP, args) ) ); } diff --git a/src/main/java/io/appium/java_client/LocksDevice.java b/src/main/java/io/appium/java_client/LocksDevice.java index 6f3033e05..60ba88ffc 100644 --- a/src/main/java/io/appium/java_client/LocksDevice.java +++ b/src/main/java/io/appium/java_client/LocksDevice.java @@ -26,7 +26,7 @@ import static io.appium.java_client.MobileCommand.lockDeviceCommand; import static io.appium.java_client.MobileCommand.unlockDeviceCommand; -public interface LocksDevice extends ExecutesMethod { +public interface LocksDevice extends ExecutesMethod, CanRememberExtensionPresence { /** * This method locks a device. It will return silently if the device @@ -45,13 +45,14 @@ default void lockDevice() { * A negative/zero value will lock the device and return immediately. */ default void lockDevice(Duration duration) { + final String extName = "mobile: lock"; try { - CommandExecutionHelper.executeScript(this, "mobile: lock", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "seconds", duration.getSeconds() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, lockDeviceCommand(duration)); + CommandExecutionHelper.execute(markExtensionAbsence(extName), lockDeviceCommand(duration)); } } @@ -60,15 +61,16 @@ default void lockDevice(Duration duration) { * is not locked. */ default void unlockDevice() { + final String extName = "mobile: unlock"; try { //noinspection ConstantConditions - if (!(Boolean) CommandExecutionHelper.executeScript(this, "mobile: isLocked")) { + if (!(Boolean) CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: isLocked")) { return; } - CommandExecutionHelper.executeScript(this, "mobile: unlock"); + CommandExecutionHelper.executeScript(this, extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, unlockDeviceCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), unlockDeviceCommand()); } } @@ -78,13 +80,16 @@ default void unlockDevice() { * @return true if the device is locked or false otherwise. */ default boolean isDeviceLocked() { + final String extName = "mobile: isLocked"; try { return checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: isLocked") + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName) ); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return checkNotNull(CommandExecutionHelper.execute(this, getIsDeviceLockedCommand())); + return checkNotNull( + CommandExecutionHelper.execute(markExtensionAbsence(extName), getIsDeviceLockedCommand()) + ); } } } diff --git a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java index a9ecf128f..178ec4206 100644 --- a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java +++ b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java @@ -1,13 +1,14 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; import static io.appium.java_client.android.AndroidMobileCommandHelper.fingerPrintCommand; -public interface AuthenticatesByFinger extends ExecutesMethod { +public interface AuthenticatesByFinger extends ExecutesMethod, CanRememberExtensionPresence { /** * Authenticate users by using their finger print scans on supported emulators. @@ -15,13 +16,14 @@ public interface AuthenticatesByFinger extends ExecutesMethod { * @param fingerPrintId finger prints stored in Android Keystore system (from 1 to 10) */ default void fingerPrint(int fingerPrintId) { + final String extName = "mobile: fingerprint"; try { - CommandExecutionHelper.executeScript(this, "mobile: fingerprint", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "fingerprintId", fingerPrintId )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, fingerPrintCommand(fingerPrintId)); + CommandExecutionHelper.execute(markExtensionAbsence(extName), fingerPrintCommand(fingerPrintId)); } } } diff --git a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java index eb81ba417..2b48a9e29 100644 --- a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java +++ b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java @@ -1,13 +1,16 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.MobileCommand; import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.remote.RemoteWebElement; -public interface CanReplaceElementValue extends ExecutesMethod { +import java.util.AbstractMap; + +public interface CanReplaceElementValue extends ExecutesMethod, CanRememberExtensionPresence { /** * Sends a text to the given element by replacing its previous content. * @@ -19,18 +22,22 @@ public interface CanReplaceElementValue extends ExecutesMethod { * off from the typed text). */ default void replaceElementValue(RemoteWebElement element, String value) { + final String extName = "mobile: replaceValue"; try { - CommandExecutionHelper.executeScript(this, "mobile: replaceValue", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "elementId", element.getId(), "text", value )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(MobileCommand.REPLACE_VALUE, ImmutableMap.of( - "id", element.getId(), - "text", value, - "value", value - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(MobileCommand.REPLACE_VALUE, ImmutableMap.of( + "id", element.getId(), + "text", value, + "value", value + )) + ); } } } diff --git a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java index bcdcf1fd4..3e230b3a3 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidDeviceDetails.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -9,7 +10,7 @@ import static io.appium.java_client.android.AndroidMobileCommandHelper.getDisplayDensityCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.getSystemBarsCommand; -public interface HasAndroidDeviceDetails extends ExecutesMethod { +public interface HasAndroidDeviceDetails extends ExecutesMethod, CanRememberExtensionPresence { /** Retrieve the display density of the Android device. @@ -17,11 +18,12 @@ public interface HasAndroidDeviceDetails extends ExecutesMethod { @return The density value in dpi */ default Long getDisplayDensity() { + final String extName = "mobile: getDisplayDensity"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getDisplayDensity"); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return CommandExecutionHelper.execute(this, getDisplayDensityCommand()); + return CommandExecutionHelper.execute(markExtensionAbsence(extName), getDisplayDensityCommand()); } } @@ -31,11 +33,12 @@ default Long getDisplayDensity() { @return The map where keys are bar types and values are mappings of bar properties. */ default Map> getSystemBars() { + final String extName = "mobile: getSystemBars"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getSystemBars"); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return CommandExecutionHelper.execute(this, getSystemBarsCommand()); + return CommandExecutionHelper.execute(markExtensionAbsence(extName), getSystemBarsCommand()); } } diff --git a/src/main/java/io/appium/java_client/android/HasNotifications.java b/src/main/java/io/appium/java_client/android/HasNotifications.java index c2e870031..0b7f7365b 100644 --- a/src/main/java/io/appium/java_client/android/HasNotifications.java +++ b/src/main/java/io/appium/java_client/android/HasNotifications.java @@ -1,22 +1,24 @@ package io.appium.java_client.android; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; import static io.appium.java_client.android.AndroidMobileCommandHelper.openNotificationsCommand; -public interface HasNotifications extends ExecutesMethod { +public interface HasNotifications extends ExecutesMethod, CanRememberExtensionPresence { /** * Opens notification drawer on the device under test. */ default void openNotifications() { + final String extName = "mobile: openNotifications"; try { - CommandExecutionHelper.executeScript(this, "mobile: openNotifications"); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, openNotificationsCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), openNotificationsCommand()); } } } diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index 9291e5020..f1d538881 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -17,6 +17,7 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -29,7 +30,7 @@ import static io.appium.java_client.MobileCommand.GET_CURRENT_PACKAGE; import static io.appium.java_client.android.AndroidMobileCommandHelper.startActivityCommand; -public interface StartsActivity extends ExecutesMethod { +public interface StartsActivity extends ExecutesMethod, CanRememberExtensionPresence { /** * This method should start arbitrary activity during a test. If the activity belongs to * another application, that application is started and the activity is opened. @@ -65,12 +66,14 @@ default void startActivity(Activity activity) { */ @Nullable default String currentActivity() { + final String extName = "mobile: getCurrentActivity"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getCurrentActivity"); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()) + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()) ); } } @@ -82,12 +85,14 @@ default String currentActivity() { */ @Nullable default String getCurrentPackage() { + final String extName = "mobile: getCurrentPackage"; try { - return CommandExecutionHelper.executeScript(this, "mobile: getCurrentPackage"); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return CommandExecutionHelper.execute( - this, new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()) + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()) ); } } diff --git a/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java index 597c98193..14854412e 100644 --- a/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -7,18 +8,19 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; -public interface SupportsGpsStateManagement extends ExecutesMethod { +public interface SupportsGpsStateManagement extends ExecutesMethod, CanRememberExtensionPresence { /** * Toggles GPS service state. * This method only works reliably since API 31 (Android 12). */ default void toggleLocationServices() { + final String extName = "mobile: toggleGps"; try { - CommandExecutionHelper.executeScript(this, "mobile: toggleGps"); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, toggleLocationServicesCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), toggleLocationServicesCommand()); } } diff --git a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java index 9fdbd23c9..8f4dfa246 100644 --- a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java @@ -1,6 +1,7 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -12,22 +13,23 @@ import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleWifiCommand; -public interface SupportsNetworkStateManagement extends ExecutesMethod { +public interface SupportsNetworkStateManagement extends ExecutesMethod, CanRememberExtensionPresence { /** * Toggles Wifi on and off. */ default void toggleWifi() { + final String extName = "mobile: setConnectivity"; try { Map result = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( "wifi", !((Boolean) result.get("wifi")) )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, toggleWifiCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), toggleWifiCommand()); } } @@ -36,16 +38,17 @@ default void toggleWifi() { * 6 and above 10. */ default void toggleAirplaneMode() { + final String extName = "mobile: setConnectivity"; try { Map result = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( "airplaneMode", !((Boolean) result.get("airplaneMode")) )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, toggleAirplaneCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), toggleAirplaneCommand()); } } @@ -54,16 +57,17 @@ default void toggleAirplaneMode() { * running Android version above 10. */ default void toggleData() { + final String extName = "mobile: setConnectivity"; try { Map result = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( "data", !((Boolean) result.get("data")) )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, toggleDataCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), toggleDataCommand()); } } } diff --git a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java index 29cd6415e..025f00b05 100644 --- a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java +++ b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java @@ -1,10 +1,13 @@ package io.appium.java_client.android; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; +import java.util.AbstractMap; + import static io.appium.java_client.MobileCommand.GSM_CALL; import static io.appium.java_client.MobileCommand.GSM_SIGNAL; import static io.appium.java_client.MobileCommand.GSM_VOICE; @@ -13,7 +16,7 @@ import static io.appium.java_client.MobileCommand.POWER_CAPACITY; import static io.appium.java_client.MobileCommand.SEND_SMS; -public interface SupportsSpecialEmulatorCommands extends ExecutesMethod { +public interface SupportsSpecialEmulatorCommands extends ExecutesMethod, CanRememberExtensionPresence { /** * Emulate send SMS event on the connected emulator. @@ -22,17 +25,21 @@ public interface SupportsSpecialEmulatorCommands extends ExecutesMethod { * @param message The message content. */ default void sendSMS(String phoneNumber, String message) { + final String extName = "mobile: sendSms"; try { - CommandExecutionHelper.executeScript(this, "mobile: sendSms", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "phoneNumber", phoneNumber, "message", message )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(SEND_SMS, ImmutableMap.of( - "phoneNumber", phoneNumber, - "message", message - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(SEND_SMS, ImmutableMap.of( + "phoneNumber", phoneNumber, + "message", message + )) + ); } } @@ -43,17 +50,21 @@ default void sendSMS(String phoneNumber, String message) { * @param gsmCallAction One of available {@link GsmCallActions} values. */ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { + final String extName = "mobile: gsmCall"; try { - CommandExecutionHelper.executeScript(this, "mobile: gsmCall", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "phoneNumber", phoneNumber, "action", gsmCallAction.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(GSM_CALL, ImmutableMap.of( - "phoneNumber", phoneNumber, - "action", gsmCallAction.toString().toLowerCase() - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GSM_CALL, ImmutableMap.of( + "phoneNumber", phoneNumber, + "action", gsmCallAction.toString().toLowerCase() + )) + ); } } @@ -63,16 +74,20 @@ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { * @param gsmSignalStrength One of available {@link GsmSignalStrength} values. */ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { + final String extName = "mobile: gsmSignal"; try { - CommandExecutionHelper.executeScript(this, "mobile: gsmSignal", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "strength", gsmSignalStrength.ordinal() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(GSM_SIGNAL, ImmutableMap.of( - "signalStrengh", gsmSignalStrength.ordinal(), - "signalStrength", gsmSignalStrength.ordinal() - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GSM_SIGNAL, ImmutableMap.of( + "signalStrengh", gsmSignalStrength.ordinal(), + "signalStrength", gsmSignalStrength.ordinal() + )) + ); } } @@ -82,15 +97,19 @@ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { * @param gsmVoiceState One of available {@link GsmVoiceState} values. */ default void setGsmVoice(GsmVoiceState gsmVoiceState) { + final String extName = "mobile: gsmVoice"; try { - CommandExecutionHelper.executeScript(this, "mobile: gsmVoice", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "state", gsmVoiceState.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(GSM_VOICE, ImmutableMap.of( - "state", gsmVoiceState.name().toLowerCase() - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(GSM_VOICE, ImmutableMap.of( + "state", gsmVoiceState.name().toLowerCase() + )) + ); } } @@ -100,15 +119,19 @@ default void setGsmVoice(GsmVoiceState gsmVoiceState) { * @param networkSpeed One of available {@link NetworkSpeed} values. */ default void setNetworkSpeed(NetworkSpeed networkSpeed) { + final String extName = "mobile: networkSpeed"; try { - CommandExecutionHelper.executeScript(this, "mobile: networkSpeed", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "speed", networkSpeed.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(NETWORK_SPEED, ImmutableMap.of( - "netspeed", networkSpeed.name().toLowerCase() - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(NETWORK_SPEED, ImmutableMap.of( + "netspeed", networkSpeed.name().toLowerCase() + )) + ); } } @@ -118,15 +141,19 @@ default void setNetworkSpeed(NetworkSpeed networkSpeed) { * @param percent Percentage value in range [0, 100]. */ default void setPowerCapacity(int percent) { + final String extName = "mobile: powerCapacity"; try { - CommandExecutionHelper.executeScript(this, "mobile: powerCapacity", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "percent", percent )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(POWER_CAPACITY, ImmutableMap.of( - "percent", percent - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(POWER_CAPACITY, ImmutableMap.of( + "percent", percent + )) + ); } } @@ -136,15 +163,19 @@ default void setPowerCapacity(int percent) { * @param powerACState One of available {@link PowerACState} values. */ default void setPowerAC(PowerACState powerACState) { + final String extName = "mobile: powerAC"; try { - CommandExecutionHelper.executeScript(this, "mobile: powerAC", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "state", powerACState.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - this.execute(POWER_AC_STATE, ImmutableMap.of( - "state", powerACState.name().toLowerCase() - )); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(POWER_AC_STATE, ImmutableMap.of( + "state", powerACState.name().toLowerCase() + )) + ); } } diff --git a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java index f77bf7d3a..0774c74a2 100644 --- a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java +++ b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java @@ -17,6 +17,7 @@ package io.appium.java_client.android.connection; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -27,7 +28,7 @@ import static io.appium.java_client.android.AndroidMobileCommandHelper.getNetworkConnectionCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.setConnectionCommand; -public interface HasNetworkConnection extends ExecutesMethod { +public interface HasNetworkConnection extends ExecutesMethod, CanRememberExtensionPresence { /** * Set the network connection of the device. @@ -36,8 +37,9 @@ public interface HasNetworkConnection extends ExecutesMethod { * @return Connection object, which represents the resulting state */ default ConnectionState setConnection(ConnectionState connection) { + final String extName = "mobile: setConnectivity"; try { - CommandExecutionHelper.executeScript(this, "mobile: setConnectivity", ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( "wifi", connection.isWiFiEnabled(), "data", connection.isDataEnabled(), "airplaneMode", connection.isAirplaneModeEnabled() @@ -47,7 +49,10 @@ default ConnectionState setConnection(ConnectionState connection) { // TODO: Remove the fallback return new ConnectionState( checkNotNull( - CommandExecutionHelper.execute(this, setConnectionCommand(connection.getBitMask())) + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + setConnectionCommand(connection.getBitMask()) + ) ) ); } @@ -59,9 +64,10 @@ default ConnectionState setConnection(ConnectionState connection) { * @return Connection object, which lets you to inspect the current status */ default ConnectionState getConnection() { + final String extName = "mobile: getConnectivity"; try { Map result = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: getConnectivity") + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName) ); return new ConnectionState( ((boolean) result.get("wifi") ? ConnectionState.WIFI_MASK : 0) @@ -72,7 +78,10 @@ default ConnectionState getConnection() { // TODO: Remove the fallback return new ConnectionState( checkNotNull( - CommandExecutionHelper.execute(this, getNetworkConnectionCommand()) + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + getNetworkConnectionCommand() + ) ) ); } diff --git a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java index 9584ff517..b262096c0 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java +++ b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java @@ -17,6 +17,7 @@ package io.appium.java_client.android.nativekey; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; @@ -27,7 +28,7 @@ import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; import static io.appium.java_client.MobileCommand.PRESS_KEY_CODE; -public interface PressesKey extends ExecutesMethod { +public interface PressesKey extends ExecutesMethod, CanRememberExtensionPresence { /** * Send a key event to the device under test. @@ -35,12 +36,15 @@ public interface PressesKey extends ExecutesMethod { * @param keyEvent The generated native key event */ default void pressKey(KeyEvent keyEvent) { + final String extName = "mobile: pressKey"; try { - CommandExecutionHelper.executeScript(this, "mobile: pressKey", keyEvent.build()); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, keyEvent.build()); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(PRESS_KEY_CODE, keyEvent.build())); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(PRESS_KEY_CODE, keyEvent.build()) + ); } } @@ -50,16 +54,19 @@ default void pressKey(KeyEvent keyEvent) { * @param keyEvent The generated native key event */ default void longPressKey(KeyEvent keyEvent) { + final String extName = "mobile: pressKey"; try { Map args = ImmutableMap.builder() .putAll(keyEvent.build()) .put("isLongPress", true) .build(); - CommandExecutionHelper.executeScript(this, "mobile: pressKey", args); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, - new AbstractMap.SimpleEntry<>(LONG_PRESS_KEY_CODE, keyEvent.build())); + CommandExecutionHelper.execute( + markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(LONG_PRESS_KEY_CODE, keyEvent.build()) + ); } } } diff --git a/src/main/java/io/appium/java_client/ios/ShakesDevice.java b/src/main/java/io/appium/java_client/ios/ShakesDevice.java index 1f34ecbfa..57302ef8a 100644 --- a/src/main/java/io/appium/java_client/ios/ShakesDevice.java +++ b/src/main/java/io/appium/java_client/ios/ShakesDevice.java @@ -16,23 +16,25 @@ package io.appium.java_client.ios; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; import static io.appium.java_client.ios.IOSMobileCommandHelper.shakeCommand; -public interface ShakesDevice extends ExecutesMethod { +public interface ShakesDevice extends ExecutesMethod, CanRememberExtensionPresence { /** * Simulate shaking the Simulator. This API does not work for real devices. */ default void shake() { + final String extName = "mobile: shake"; try { - CommandExecutionHelper.executeScript(this, "mobile: shake"); + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - CommandExecutionHelper.execute(this, shakeCommand()); + CommandExecutionHelper.execute(markExtensionAbsence(extName), shakeCommand()); } } } From 2f62c0c025ecbc59fe957ee6e76a9cbc0974cdf8 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Sun, 7 May 2023 14:11:11 +0800 Subject: [PATCH 089/314] Release 8.4.0 and update release notes --- CHANGELOG.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc957b07f..cdf57b46d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +*8.4.0* +- **[ENHANCEMENTS]** + - Added possibility to connect to a running session. [#1813](https://github.com/appium/java-client/pull/1813) + - deprecate tapWithShortPressDuration capability.[#1825](https://github.com/appium/java-client/pull/1825) + - Add SupportsEnforceAppInstallOption to XCUITestOptions.[#1895](https://github.com/appium/java-client/pull/1895) +- **[BUG FIX]** + - Use ipv4 address instead of localhost. [#1815](https://github.com/appium/java-client/pull/1815) + - Fix test broken by updates in `appium-xcuitest-driver`. [#1839](https://github.com/appium/java-client/pull/1839) + - Merge misc tests suite into unit tests suite. [#1850](https://github.com/appium/java-client/pull/1850) + - Avoid NPE in destroyProcess call. [#1878](https://github.com/appium/java-client/pull/1878) + - Send arguments for mobile methods depending on the target platform. [#1897](https://github.com/appium/java-client/pull/1897) +- **[REFACTOR]** + - Run Gradle wrapper validation only on Gradle files changes. [#1828](https://github.com/appium/java-client/pull/1828) + - Skip GH Actions build on changes in docs. [#1829](https://github.com/appium/java-client/pull/1829) + - Remove Checkstyle exclusion of removed Selenium package. [#1831](https://github.com/appium/java-client/pull/1831) + - Enable Checkstyle checks for test code. [#1843](https://github.com/appium/java-client/pull/1843) + - Configure `CODEOWNERS` to automate review requests. [#1846](https://github.com/appium/java-client/pull/1846) + - Enable execution of unit tests in CI. [#1845](https://github.com/appium/java-client/pull/1845) + - Add Simple SLF4J binding to unit tests runtime. [#1848](https://github.com/appium/java-client/pull/1848) + - Improve performance of proxy `Interceptor` logging. [#1849](https://github.com/appium/java-client/pull/1849) + - Make unit tests execution a part of Gradle build lifecycle. [#1853](https://github.com/appium/java-client/pull/1853) + - Replace non-W3C API calls with corresponding extension calls in app management. [#1883](https://github.com/appium/java-client/pull/1883) + - Switch the time getter to use mobile extensions. [#1884](https://github.com/appium/java-client/pull/1884) + - Switch file management APIs to use mobile: extensions. [#1886](https://github.com/appium/java-client/pull/1886) + - Use mobile extensions for app strings getters and keyboard commands. [#1890](https://github.com/appium/java-client/pull/1890) + - Finish replacing iOS extensions with their mobile alternatives. [#1892](https://github.com/appium/java-client/pull/1892) + - Change some Android APIs to use mobile extensions. [#1893](https://github.com/appium/java-client/pull/1893) + - Change backgroundApp command to use the corresponding mobile extension. [#1896](https://github.com/appium/java-client/pull/1896) + - Switch more Android helpers to use extensions. [#1898](https://github.com/appium/java-client/pull/1898) + - Perform xcuitest driver prebuild. [#1900](https://github.com/appium/java-client/pull/1900) + - Finish migrating Android helpers to mobile extensions. [#1901](https://github.com/appium/java-client/pull/1901) + - Avoid sending unnecessary requests if corresponding extensions are absent. [#1903](https://github.com/appium/java-client/pull/1903) +- **[DOCUMENTATION]** + - Describe transitive Selenium dependencies management. [#1827](https://github.com/appium/java-client/pull/1827) + - Fix build badge to point GH Actions CI. [#1844](https://github.com/appium/java-client/pull/1844) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.8.2. + - `org.slf4j:slf4j-api` was updated to 2.0.7. + - `org.owasp.dependencycheck` was updated to 8.2.1. + - `gradle` was updated to 8.1.0. + - `com.google.code.gson:gson` was updated to 2.10.1. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.2. + - `org.junit.jupiter:junit-jupiter` was updated to 5.9.2. + - `checkstyle` was updated to 10.0. + - `jacoco` was updated to 0.8.8. + - `org.projectlombok:lombok` was updated to 1.18.26. + - `com.github.johnrengelman.shadow` was updated to 8.1.1. + *8.3.0* - **[DOCUMENTATION]** - Added troubleshooting section. [#1808](https://github.com/appium/java-client/pull/1808) diff --git a/gradle.properties b/gradle.properties index e1a1cc258..78924349f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.8.2 # Please increment the value in a release -appiumClient.version=8.3.0 +appiumClient.version=8.4.0 From 50bb0ae80bbce23c3bd160aadce4da89a784aa50 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 7 May 2023 08:21:33 +0200 Subject: [PATCH 090/314] refactor: Replace performance data APIs with mobile extensions (#1905) --- .../HasSupportedPerformanceDataType.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java index 3afea7552..ee6a3bce8 100644 --- a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java +++ b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java @@ -1,14 +1,17 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import java.util.List; import static io.appium.java_client.android.AndroidMobileCommandHelper.getPerformanceDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.getSupportedPerformanceDataTypesCommand; -public interface HasSupportedPerformanceDataType extends ExecutesMethod { +public interface HasSupportedPerformanceDataType extends ExecutesMethod, CanRememberExtensionPresence { /** * returns the information type of the system state which is supported to read @@ -18,7 +21,15 @@ public interface HasSupportedPerformanceDataType extends ExecutesMethod { * */ default List getSupportedPerformanceDataTypes() { - return CommandExecutionHelper.execute(this, getSupportedPerformanceDataTypesCommand()); + final String extName = "mobile: getPerformanceDataTypes"; + try { + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute( + markExtensionAbsence(extName), getSupportedPerformanceDataTypesCommand() + ); + } } /** @@ -50,7 +61,17 @@ default List getSupportedPerformanceDataTypes() { * in case of cpu info : [[user, kernel], [0.9, 1.3]] */ default List> getPerformanceData(String packageName, String dataType, int dataReadTimeout) { - return CommandExecutionHelper.execute(this, - getPerformanceDataCommand(packageName, dataType, dataReadTimeout)); + final String extName = "mobile: getPerformanceData"; + try { + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + "packageName", packageName, + "dataType", dataType + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute( + markExtensionAbsence(extName), getPerformanceDataCommand(packageName, dataType, dataReadTimeout) + ); + } } } From f2fb595a81cc8cfeca22fd7ff80847f277c2a2a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 7 May 2023 23:06:08 +0300 Subject: [PATCH 091/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.9.2 to 5.9.3 (#1902) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 88070b21e..a9e9809e1 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { implementation 'commons-io:commons-io:2.11.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.2') { exclude group: 'org.seleniumhq.selenium' From 9c18b2ff3c32867669d56921351d94bd5ce5ed15 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 9 May 2023 12:41:36 +0300 Subject: [PATCH 092/314] chore: Bump minimum Selenium client version to 4.9.1 (#1908) Selenium 4.9.1 introduced 2 breaking changes that cause compilation errors: - https://github.com/SeleniumHQ/selenium/commit/66e51be38cbd492c11605397e1b50c1cb786a579 - https://github.com/SeleniumHQ/selenium/commit/3d8c6fa7a950ce129f4bd8fb347c71a2cdd4bf07 --- gradle.properties | 2 +- src/main/java/io/appium/java_client/AppiumDriver.java | 4 ++-- .../java_client/service/local/AppiumServiceBuilder.java | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index 78924349f..51f30e7f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.8.2 +selenium.version=4.9.1 # Please increment the value in a release appiumClient.version=8.4.0 diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 38c80bf6d..ead0a05d0 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -290,8 +290,8 @@ protected void startSession(Capabilities capabilities) { } @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; - // A workaround for Selenium API enforcing some legacy capability values - rawCapabilities.remove(CapabilityType.PLATFORM); + // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version + rawCapabilities.remove("platform"); if (rawCapabilities.containsKey(CapabilityType.BROWSER_NAME) && isBlank((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { rawCapabilities.remove(CapabilityType.BROWSER_NAME); 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 0b795872d..9d3bce564 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 @@ -404,14 +404,13 @@ protected ImmutableList createArgs() { } @Override - public AppiumDriverLocalService build() { + protected void loadSystemProperties() { File driverExecutable = ReflectionHelpers.getPrivateFieldValue( DriverService.Builder.class, this, "exe", File.class ); if (driverExecutable == null) { usingDriverExecutable(findDefaultExecutable()); } - return super.build(); } /** From 1cc687fa3dc7830fe4e01b8d419cdb10edc09ca8 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 9 May 2023 17:26:03 +0300 Subject: [PATCH 093/314] fix: Restore Jitpack builds (#1911) --- build.gradle | 1 + jitpack.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a9e9809e1..7809cd92a 100644 --- a/build.gradle +++ b/build.gradle @@ -182,6 +182,7 @@ publishing { } signing { + required { !'true'.equalsIgnoreCase(project.findProperty('signingDisabled')) } def signingKey = System.getenv("PGP_SECRET") def signingPassword = System.getenv("PGP_PASSPHRASE") useInMemoryPgpKeys(signingKey, signingPassword) diff --git a/jitpack.yml b/jitpack.yml index f5edf063b..8e5135fbb 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - openjdk8 install: - - ./gradlew clean build publishToMavenLocal -x signMavenJavaPublication + - ./gradlew clean build publishToMavenLocal -PsigningDisabled=true From 5694912c739eb67ae6c05d8921927564fe073cc2 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 9 May 2023 18:16:27 +0300 Subject: [PATCH 094/314] fix: Add fallback commands for file management APIs (#1910) --- .../io/appium/java_client/PullsFiles.java | 47 ++++++++++++++----- .../io/appium/java_client/PushesFiles.java | 19 ++++++-- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index 0042b0997..4b60a5748 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -19,11 +19,14 @@ import com.google.common.collect.ImmutableMap; import java.nio.charset.StandardCharsets; +import java.util.AbstractMap; import java.util.Base64; import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.PULL_FILE; +import static io.appium.java_client.MobileCommand.PULL_FOLDER; -public interface PullsFiles extends ExecutesMethod { +public interface PullsFiles extends ExecutesMethod, CanRememberExtensionPresence { /** * Pull a file from the remote system. @@ -38,11 +41,22 @@ public interface PullsFiles extends ExecutesMethod { * @return A byte array of Base64 encoded data. */ default byte[] pullFile(String remotePath) { - String base64String = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: pullFile", ImmutableMap.of( - "remotePath", remotePath - )) - ); + final String extName = "mobile: pullFile"; + String base64String; + try { + base64String = checkNotNull( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, + ImmutableMap.of("remotePath", remotePath) + ) + ); + } catch (UnsupportedOperationException e) { + // TODO: Remove the fallback + base64String = checkNotNull( + CommandExecutionHelper.execute(markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(PULL_FILE, ImmutableMap.of("path", remotePath)) + ) + ); + } return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } @@ -59,11 +73,22 @@ default byte[] pullFile(String remotePath) { * @return A byte array of Base64 encoded zip archive data. */ default byte[] pullFolder(String remotePath) { - String base64String = checkNotNull( - CommandExecutionHelper.executeScript(this, "mobile: pullFolder", ImmutableMap.of( - "remotePath", remotePath - )) - ); + final String extName = "mobile: pullFolder"; + String base64String; + try { + base64String = checkNotNull( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, + ImmutableMap.of("remotePath", remotePath) + ) + ); + } catch (UnsupportedOperationException e) { + // TODO: Remove the fallback + base64String = checkNotNull( + CommandExecutionHelper.execute(markExtensionAbsence(extName), + new AbstractMap.SimpleEntry<>(PULL_FOLDER, ImmutableMap.of("path", remotePath)) + ) + ); + } return Base64.getDecoder().decode(base64String.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index 813dcb5b1..1bfd7f8eb 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -18,13 +18,16 @@ import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; +import org.openqa.selenium.UnsupportedCommandException; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; -public interface PushesFiles extends ExecutesMethod { +import static io.appium.java_client.MobileCommand.pushFileCommand; + +public interface PushesFiles extends ExecutesMethod, CanRememberExtensionPresence { /** * Saves base64-encoded data as a file on the remote system. @@ -36,10 +39,16 @@ public interface PushesFiles extends ExecutesMethod { * @param base64Data Base64 encoded byte array of media file data to write to remote device */ default void pushFile(String remotePath, byte[] base64Data) { - CommandExecutionHelper.executeScript(this, "mobile: pushFile", ImmutableMap.of( - "remotePath", remotePath, - "payload", new String(base64Data, StandardCharsets.UTF_8) - )); + final String extName = "mobile: pushFile"; + try { + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + "remotePath", remotePath, + "payload", new String(base64Data, StandardCharsets.UTF_8) + )); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(markExtensionAbsence(extName), pushFileCommand(remotePath, base64Data)); + } } /** From 53d0f68da4d784c8b48d9866e2dab6adc99ebe67 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Wed, 10 May 2023 00:08:25 +0800 Subject: [PATCH 095/314] Release 8.5.0 and update release notes --- CHANGELOG.md | 10 ++++++++++ gradle.properties | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf57b46d..1b8089b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +*8.5.0* +- **[BUG FIX]** + - Restore Jitpack builds. [#1911](https://github.com/appium/java-client/pull/1911) + - Add fallback commands for file management APIs. [#1910](https://github.com/appium/java-client/pull/1910) +- **[REFACTOR]** + - Replace performance data APIs with mobile extensions. [#1905](https://github.com/appium/java-client/pull/1905) +- **[DEPENDENCY UPDATES]** + - `org.seleniumhq.selenium:selenium-java` was updated to 4.9.1. + - `org.junit.jupiter:junit-jupiter` was updated to 5.9.3. + *8.4.0* - **[ENHANCEMENTS]** - Added possibility to connect to a running session. [#1813](https://github.com/appium/java-client/pull/1813) diff --git a/gradle.properties b/gradle.properties index 51f30e7f9..fca46f2cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.9.1 # Please increment the value in a release -appiumClient.version=8.4.0 +appiumClient.version=8.5.0 From 79106ef59cd4892bf055a28df4672f4da4ff7251 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 10 May 2023 07:31:24 +0300 Subject: [PATCH 096/314] fix: Use correct exception type for fallback at file/folder pulling (#1912) --- src/main/java/io/appium/java_client/PullsFiles.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index 4b60a5748..c6d4e0898 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -17,6 +17,7 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; +import org.openqa.selenium.UnsupportedCommandException; import java.nio.charset.StandardCharsets; import java.util.AbstractMap; @@ -49,7 +50,7 @@ default byte[] pullFile(String remotePath) { ImmutableMap.of("remotePath", remotePath) ) ); - } catch (UnsupportedOperationException e) { + } catch (UnsupportedCommandException e) { // TODO: Remove the fallback base64String = checkNotNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), @@ -81,7 +82,7 @@ default byte[] pullFolder(String remotePath) { ImmutableMap.of("remotePath", remotePath) ) ); - } catch (UnsupportedOperationException e) { + } catch (UnsupportedCommandException e) { // TODO: Remove the fallback base64String = checkNotNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), From 35ac97204be620e0b3cc291472ac7c9568d37262 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 11 May 2023 22:17:24 +0200 Subject: [PATCH 097/314] fix: Update autoWebview capability name (#1917) --- .../java_client/remote/options/SupportsAutoWebViewOption.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/remote/options/SupportsAutoWebViewOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsAutoWebViewOption.java index 8a5d7e9fd..3cd2d7e93 100644 --- a/src/main/java/io/appium/java_client/remote/options/SupportsAutoWebViewOption.java +++ b/src/main/java/io/appium/java_client/remote/options/SupportsAutoWebViewOption.java @@ -24,7 +24,7 @@ public interface SupportsAutoWebViewOption> extends Capabilities, CanSetCapability { - String AUTO_WEB_VIEW_OPTION = "autoWebView"; + String AUTO_WEB_VIEW_OPTION = "autoWebview"; /** * Set the app to move directly into Webview context. From 4560e23fd8d893ab5c64b5eb36392cb34b030097 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sat, 13 May 2023 23:04:01 +0300 Subject: [PATCH 098/314] ci: Move execution of E2E tests to GitHub Actions (#1913) --- .azure-templates/bootstrap_steps.yml | 10 -- .github/workflows/gradle.yml | 97 ++++++++++++++++++- azure-pipelines.yml | 69 ------------- .../appium/java_client/ios/BaseIOSTest.java | 1 + 4 files changed, 94 insertions(+), 83 deletions(-) delete mode 100644 .azure-templates/bootstrap_steps.yml delete mode 100644 azure-pipelines.yml diff --git a/.azure-templates/bootstrap_steps.yml b/.azure-templates/bootstrap_steps.yml deleted file mode 100644 index baf4b6979..000000000 --- a/.azure-templates/bootstrap_steps.yml +++ /dev/null @@ -1,10 +0,0 @@ -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@next diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index cd399065c..872468fad 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,23 +14,112 @@ on: - 'docs/**' - '*.md' +env: + ANDROID_SDK_VERSION: 28 + ANDROID_EMU_NAME: test + XCODE_VERSION: 14.2 + IOS_DEVICE_NAME: iPhone 12 + IOS_PLATFORM_VERSION: 16.2 + jobs: build: - runs-on: macOS-latest - strategy: matrix: - java: [ 8, 11, 17 ] + include: + # TODO: add new LTS Java ( 21 ) once it's released + - java: 8 + platform: macos-latest + e2e-tests: android + - java: 11 + # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available + platform: macos-12 + e2e-tests: ios + - java: 17 + platform: ubuntu-latest + fail-fast: false + + runs-on: ${{ matrix.platform }} - name: JDK ${{ matrix.java }} + name: JDK ${{ matrix.java }} - ${{ matrix.platform }} steps: - uses: actions/checkout@v3 + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v3 with: distribution: 'zulu' java-version: ${{ matrix.java }} cache: 'gradle' + - name: Build with Gradle run: ./gradlew clean build + + - name: Install Node.js + if: ${{ matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' }} + uses: actions/setup-node@v3 + with: + node-version: 'lts/*' + + - name: Install Appium + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' + run: npm install -g appium@next + + - name: Install UIA2 driver + if: matrix.e2e-tests == 'android' + run: appium driver install uiautomator2 + + - name: AVD cache + if: matrix.e2e-tests == 'android' + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ env.ANDROID_SDK_VERSION }} + + - name: Generate AVD snapshot for caching + if: matrix.e2e-tests == 'android' && steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ env.ANDROID_SDK_VERSION }} + avd-name: ${{ env.ANDROID_EMU_NAME }} + force-avd-creation: false + script: echo "Generated AVD snapshot for caching." + + - name: Run Android E2E tests + if: matrix.e2e-tests == 'android' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ env.ANDROID_SDK_VERSION }} + avd-name: ${{ env.ANDROID_EMU_NAME }} + force-avd-creation: false + emulator-options: -no-snapshot -delay-adb + script: ./gradlew uiAutomationTest + + - name: Select Xcode + if: matrix.e2e-tests == 'ios' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: "${{ env.XCODE_VERSION }}" + + - name: Install XCUITest driver + if: matrix.e2e-tests == 'ios' + run: appium driver install xcuitest + + - name: Prebuild XCUITest driver + if: matrix.e2e-tests == 'ios' + run: appium driver run xcuitest build-wda + + - name: Prepare iOS simulator + if: matrix.e2e-tests == 'ios' + run: | + xcrun simctl list + target_sim_id=$(xcrun simctl list devices available | grep "$IOS_DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) + open -Fn "/Applications/Xcode_$XCODE_VERSION.app/Contents/Developer/Applications/Simulator.app" + xcrun simctl bootstatus $target_sim_id -b + + - name: Run iOS E2E tests + if: matrix.e2e-tests == 'ios' + run: ./gradlew xcuiTest diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 4cea8df6d..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Gradle -# Build your Java project and run tests with Gradle using a Gradle wrapper script. -# Add steps that analyze code, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/java - -pool: - vmImage: 'macos-12' - -variables: - ANDROID_EMU_NAME: test - ANDROID_EMU_ABI: x86 - ANDROID_EMU_TARGET: android-28 - ANDROID_EMU_TAG: default - XCODE_VERSION: 14.2 - IOS_PLATFORM_VERSION: 16.2 - IOS_DEVICE_NAME: iPhone 12 - NODE_VERSION: 18.x - JDK_VERSION: 1.8 - -jobs: -- job: Android_E2E_Tests - steps: - - template: .azure-templates/bootstrap_steps.yml - - script: $NVM_DIR/versions/node/`node --version`/bin/appium driver install uiautomator2 - displayName: Install UIA2 driver - - script: | - 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 -delay-adb > /dev/null 2>&1 & - $ANDROID_HOME/platform-tools/adb wait-for-device - $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 uiAutomationTest' -- 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 - target_sim_id=$(xcrun simctl list devices available | grep "$IOS_DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) - open -Fn "/Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer/Applications/Simulator.app" - xcrun simctl bootstatus $target_sim_id -b - displayName: Prepare iOS Simulator - - script: $NVM_DIR/versions/node/$(node --version)/bin/appium driver install xcuitest - displayName: Install XCUITest driver - - script: $NVM_DIR/versions/node/$(node --version)/bin/appium driver run xcuitest build-wda - displayName: Prebuild XCUITest driver - - task: Gradle@2 - inputs: - gradleWrapperFile: 'gradlew' - gradleOptions: '-Xmx3072m' - javaHomeOption: 'JDKVersion' - jdkVersionOption: "$(JDK_VERSION)" - jdkArchitectureOption: 'x64' - publishJUnitResults: true - tasks: 'build xcuiTest' 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 dfdf3c4da..1661e19c9 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -44,6 +44,7 @@ public static AppiumDriverLocalService startAppiumServer() { service = new AppiumServiceBuilder() .withIPAddress("127.0.0.1") .usingPort(PORT) + .withTimeout(Duration.ofSeconds(40)) .build(); service.start(); return service; From 925bab75572d0ab2478f77d87e68e1bdb7526542 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 15 May 2023 11:25:51 +0300 Subject: [PATCH 099/314] docs: Initiate Selenium client compatibility matrix (#1918) --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6825ba82c..104c256e4 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,20 @@ dependencies { } ``` -### How to pin Selenium dependencies? +### Compatibility Matrix +| Appium Java Client | Selenium client | +|--------------------|---------------------------------------------| +| `8.5.0` | `4.9.1` | +| `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` | +| `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` | +| `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` | + +#### Why is it so complicated? + +Selenium client does not follow [Semantic Versioning](https://semver.org/), so breaking changes might be introduced +even in patches, which requires Appium team to update Java client in response. + +#### How to pin Selenium dependencies? Appium Java Client declares Selenium dependencies using open version range which is handled in differently by different build tools. Sometimes users may want to pin used Selenium dependencies for [various reasons](https://github.com/appium/java-client/issues/1823). From 9cd90ed45684ca6771b368601793e5d486c6d152 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 May 2023 15:33:25 +0300 Subject: [PATCH 100/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#1919) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.3.2 to 5.3.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.3.2...webdrivermanager-5.3.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 7809cd92a..f494aa4a7 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.2') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.3') { exclude group: 'org.seleniumhq.selenium' } testImplementation ('org.seleniumhq.selenium:selenium-chrome-driver') { From f2ac9e5ce197e41d5c0bb9fdb55be1b9e2114a57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 15:36:36 +0300 Subject: [PATCH 101/314] build(deps): Bump commons-io:commons-io from 2.11.0 to 2.12.0 (#1922) Bumps commons-io:commons-io from 2.11.0 to 2.12.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 f494aa4a7..52bf70f38 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,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.11.0' + implementation 'commons-io:commons-io:2.12.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' From 6af53a0a4ad4d08f8d42396969be5753e6d1929a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 25 May 2023 20:37:56 +0200 Subject: [PATCH 102/314] refactor: Replace cglib with bytebuddy (#1923) --- build.gradle | 1 - .../AppiumElementLocatorFactory.java | 4 +- .../pagefactory/AppiumFieldDecorator.java | 43 +++--- .../pagefactory/DefaultElementByBuilder.java | 6 +- .../pagefactory/ElementInterceptor.java | 7 +- .../pagefactory/ElementListInterceptor.java | 8 +- .../pagefactory/ThrowableUtil.java | 8 +- .../pagefactory/WidgetInterceptor.java | 50 ++++--- .../pagefactory/WidgetListInterceptor.java | 30 ++-- .../InterceptorOfAListOfElements.java | 21 ++- .../InterceptorOfASingleElement.java | 23 ++- .../pagefactory/utils/ProxyFactory.java | 61 ++++++-- .../utils/WebDriverUnpackUtility.java | 9 +- .../io/appium/java_client/proxy/Helpers.java | 67 +++++++-- .../appium/java_client/proxy/Interceptor.java | 17 +-- .../proxy/ProxyListenersContainer.java | 140 ++++++++++++++++++ .../widget/tests/WidgetTest.java | 1 - .../tests/combined/CombinedAppTest.java | 8 +- .../tests/combined/CombinedWidgetTest.java | 9 +- .../tests/windows/AnnotatedWindowsWidget.java | 15 -- .../tests/windows/DefaultWindowsWidget.java | 35 ----- .../tests/windows/ExtendedWindowsWidget.java | 9 -- .../widget/tests/windows/WindowsApp.java | 125 ---------------- .../tests/windows/WindowsWidgetTest.java | 51 ------- .../java_client/proxy/ProxyHelpersTest.java | 1 - 25 files changed, 355 insertions(+), 394 deletions(-) create mode 100644 src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java delete mode 100644 src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/AnnotatedWindowsWidget.java delete mode 100644 src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/DefaultWindowsWidget.java delete mode 100644 src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/ExtendedWindowsWidget.java delete mode 100644 src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsApp.java delete mode 100644 src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java diff --git a/build.gradle b/build.gradle index 52bf70f38..2c2371e99 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ dependencies { } } implementation 'com.google.code.gson:gson:2.10.1' - 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.12.0' diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 170cffa38..0fc8e3d38 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -53,7 +53,9 @@ public AppiumElementLocatorFactory(SearchContext searchContext, Duration duratio return this.createLocator((AnnotatedElement) field); } - @Override public @Nullable CacheableLocator createLocator(AnnotatedElement annotatedElement) { + @Override + @Nullable + public CacheableLocator createLocator(AnnotatedElement annotatedElement) { Duration customDuration; if (annotatedElement.isAnnotationPresent(WithTimeout.class)) { WithTimeout withTimeout = annotatedElement.getAnnotation(WithTimeout.class); 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 8bb282859..b1119f34e 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -32,6 +32,7 @@ import org.openqa.selenium.support.pagefactory.ElementLocator; import org.openqa.selenium.support.pagefactory.FieldDecorator; +import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; @@ -92,17 +93,17 @@ public AppiumFieldDecorator(SearchContext context, Duration duration) { this.duration = duration; defaultElementFieldDecoracor = new DefaultFieldDecorator( - new AppiumElementLocatorFactory(context, duration, - new DefaultElementByBuilder(platform, automation))) { + new AppiumElementLocatorFactory(context, duration, new DefaultElementByBuilder(platform, automation)) + ) { @Override protected WebElement proxyForLocator(ClassLoader ignored, ElementLocator locator) { return proxyForAnElement(locator); } @Override - @SuppressWarnings("unchecked") protected List proxyForListLocator(ClassLoader ignored, ElementLocator locator) { ElementListInterceptor elementInterceptor = new ElementListInterceptor(locator); + //noinspection unchecked return getEnhancedProxy(ArrayList.class, elementInterceptor); } @@ -121,14 +122,14 @@ protected boolean isDecoratableList(Field field) { List bounds = (listType instanceof TypeVariable) ? Arrays.asList(((TypeVariable) listType).getBounds()) : Collections.emptyList(); - return availableElementClasses.stream() .anyMatch((webElClass) -> webElClass.equals(listType) || bounds.contains(webElClass)); } }; - widgetLocatorFactory = - new AppiumElementLocatorFactory(context, duration, new WidgetByBuilder(platform, automation)); + widgetLocatorFactory = new AppiumElementLocatorFactory( + context, duration, new WidgetByBuilder(platform, automation) + ); } public AppiumFieldDecorator(SearchContext context) { @@ -144,14 +145,10 @@ public AppiumFieldDecorator(SearchContext context) { */ public Object decorate(ClassLoader ignored, Field field) { Object result = defaultElementFieldDecoracor.decorate(ignored, field); - if (result != null) { - return result; - } - - return decorateWidget(field); + return result == null ? decorateWidget(field) : result; } - @SuppressWarnings("unchecked") + @Nullable private Object decorateWidget(Field field) { Class type = field.getType(); if (!Widget.class.isAssignableFrom(type) && !List.class.isAssignableFrom(type)) { @@ -177,30 +174,34 @@ private Object decorateWidget(Field field) { if (!Widget.class.isAssignableFrom((Class) listType)) { return null; } + //noinspection unchecked widgetType = (Class) listType; } else { return null; } } else { + //noinspection unchecked widgetType = (Class) field.getType(); } CacheableLocator locator = widgetLocatorFactory.createLocator(field); - Map> map = - OverrideWidgetReader.read(widgetType, field, platform); + Map> map = OverrideWidgetReader.read(widgetType, field, platform); if (isAlist) { - return getEnhancedProxy(ArrayList.class, - new WidgetListInterceptor(locator, webDriver, map, widgetType, - duration)); + return getEnhancedProxy( + ArrayList.class, + new WidgetListInterceptor(locator, webDriver, map, widgetType, duration) + ); } - Constructor constructor = - WidgetConstructorUtil.findConvenientConstructor(widgetType); - return getEnhancedProxy(widgetType, new Class[]{constructor.getParameterTypes()[0]}, + Constructor constructor = WidgetConstructorUtil.findConvenientConstructor(widgetType); + return getEnhancedProxy( + widgetType, + new Class[]{constructor.getParameterTypes()[0]}, new Object[]{proxyForAnElement(locator)}, - new WidgetInterceptor(locator, webDriver, null, map, duration)); + new WidgetInterceptor(locator, webDriver, null, map, duration) + ); } private WebElement proxyForAnElement(ElementLocator locator) { 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 512c978cc..23195e5ce 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -206,15 +206,13 @@ public By buildBy() { String idOrName = ((Field) annotatedElementContainer.getAnnotated()).getName(); if (defaultBy == null && mobileNativeBy == null) { - defaultBy = - new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); + defaultBy = new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); mobileNativeBy = new By.ById(idOrName); return returnMappedBy(defaultBy, mobileNativeBy); } if (defaultBy == null) { - defaultBy = - new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); + defaultBy = new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); return returnMappedBy(defaultBy, mobileNativeBy); } diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java index efe7bd95f..5047fcc11 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java @@ -28,13 +28,14 @@ /** * Intercepts requests to {@link WebElement}. */ -class ElementInterceptor extends InterceptorOfASingleElement { +public class ElementInterceptor extends InterceptorOfASingleElement { - ElementInterceptor(ElementLocator locator, WebDriver driver) { + public ElementInterceptor(ElementLocator locator, WebDriver driver) { super(locator, driver); } - @Override protected Object getObject(WebElement element, Method method, Object[] args) + @Override + protected Object getObject(WebElement element, Method method, Object[] args) throws Throwable { try { return method.invoke(element, args); diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java index fc7164533..77e68a329 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java @@ -28,14 +28,14 @@ /** * Intercepts requests to the list of {@link WebElement}. */ -class ElementListInterceptor extends InterceptorOfAListOfElements { +public class ElementListInterceptor extends InterceptorOfAListOfElements { - ElementListInterceptor(ElementLocator locator) { + public ElementListInterceptor(ElementLocator locator) { super(locator); } - @Override protected Object getObject(List elements, Method method, Object[] args) - throws Throwable { + @Override + protected Object getObject(List elements, Method method, Object[] args) throws Throwable { try { return method.invoke(elements, args); } catch (Throwable t) { diff --git a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java index ca65e9e24..c025a53dd 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java +++ b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java @@ -33,8 +33,8 @@ protected static boolean isInvalidSelectorRootCause(Throwable e) { return true; } - if (String.valueOf(e.getMessage()).contains(INVALID_SELECTOR_PATTERN) || String - .valueOf(e.getMessage()).contains("Locator Strategy \\w+ is not supported")) { + if (String.valueOf(e.getMessage()).contains(INVALID_SELECTOR_PATTERN) + || String.valueOf(e.getMessage()).contains("Locator Strategy \\w+ is not supported")) { return true; } @@ -54,8 +54,8 @@ protected static boolean isStaleElementReferenceException(Throwable e) { } protected static Throwable extractReadableException(Throwable e) { - if (!RuntimeException.class.equals(e.getClass()) && !InvocationTargetException.class - .equals(e.getClass())) { + if (!RuntimeException.class.equals(e.getClass()) + && !InvocationTargetException.class.equals(e.getClass())) { return e; } diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java index 2c3296d35..9a9d1133d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java @@ -19,55 +19,62 @@ import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import net.sf.cglib.proxy.MethodProxy; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; +import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.time.Duration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; -class WidgetInterceptor extends InterceptorOfASingleElement { +public class WidgetInterceptor extends InterceptorOfASingleElement { private final Map> instantiationMap; private final Map cachedInstances = new HashMap<>(); private final Duration duration; private WebElement cachedElement; - WidgetInterceptor(CacheableLocator locator, WebDriver driver, WebElement cachedElement, - Map> instantiationMap, - Duration duration) { + /** + * Proxy interceptor class for widgets. + */ + public WidgetInterceptor( + CacheableLocator locator, + WebDriver driver, + @Nullable + WebElement cachedElement, + Map> instantiationMap, + Duration duration + ) { super(locator, driver); this.cachedElement = cachedElement; this.instantiationMap = instantiationMap; this.duration = duration; } - - @Override protected Object getObject(WebElement element, Method method, Object[] args) - throws Throwable { + @Override + protected Object getObject(WebElement element, Method method, Object[] args) throws Throwable { ContentType type = getCurrentContentType(element); if (cachedElement == null - || (locator != null && !((CacheableLocator) locator) - .isLookUpCached()) - || cachedInstances.size() == 0) { + || (locator != null && !((CacheableLocator) locator).isLookUpCached()) + || cachedInstances.isEmpty() + ) { cachedElement = element; Constructor constructor = instantiationMap.get(type); Class clazz = constructor.getDeclaringClass(); - int modifiers = clazz.getModifiers(); - if (Modifier.isAbstract(modifiers)) { - throw new InstantiationException(clazz.getName() - + " is abstract so " - + "it can't be instantiated"); + if (Modifier.isAbstract(clazz.getModifiers())) { + throw new InstantiationException( + String.format("%s is abstract so it cannot be instantiated", clazz.getName()) + ); } Widget widget = constructor.newInstance(cachedElement); @@ -82,11 +89,10 @@ class WidgetInterceptor extends InterceptorOfASingleElement { } } - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) - throws Throwable { - if (locator != null) { - return super.intercept(obj, method, args, proxy); - } - return getObject(cachedElement, method, args); + @Override + public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { + return locator == null + ? getObject(cachedElement, method, args) + : super.call(obj, method, args, original); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java index 04115aaa9..136ce8e4d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java @@ -19,7 +19,6 @@ import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import io.appium.java_client.pagefactory.utils.ProxyFactory; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -31,11 +30,11 @@ import java.util.Map; import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; +import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; import static java.util.Optional.ofNullable; -class WidgetListInterceptor extends InterceptorOfAListOfElements { - +public class WidgetListInterceptor extends InterceptorOfAListOfElements { private final Map> instantiationMap; private final List cachedWidgets = new ArrayList<>(); private final Class declaredType; @@ -43,7 +42,10 @@ class WidgetListInterceptor extends InterceptorOfAListOfElements { private final WebDriver driver; private List cachedElements; - WidgetListInterceptor(CacheableLocator locator, WebDriver driver, + /** + * Proxy interceptor class for lists of widgets. + */ + public WidgetListInterceptor(CacheableLocator locator, WebDriver driver, Map> instantiationMap, Class declaredType, Duration duration) { super(locator); @@ -53,22 +55,22 @@ class WidgetListInterceptor extends InterceptorOfAListOfElements { this.driver = driver; } - - @Override protected Object getObject(List elements, Method method, Object[] args) - throws Throwable { - if (cachedElements == null || (locator != null && !((CacheableLocator) locator) - .isLookUpCached())) { + @Override + protected Object getObject(List elements, Method method, Object[] args) throws Throwable { + if (cachedElements == null || (locator != null && !((CacheableLocator) locator).isLookUpCached())) { cachedElements = elements; cachedWidgets.clear(); ContentType type = null; for (WebElement element : cachedElements) { type = ofNullable(type).orElseGet(() -> getCurrentContentType(element)); - Class[] params = - new Class[] {instantiationMap.get(type).getParameterTypes()[0]}; - cachedWidgets.add(ProxyFactory - .getEnhancedProxy(declaredType, params, new Object[] {element}, - new WidgetInterceptor(null, driver, element, instantiationMap, duration))); + Class[] params = new Class[] {instantiationMap.get(type).getParameterTypes()[0]}; + cachedWidgets.add( + getEnhancedProxy( + declaredType, params, new Object[] {element}, + new WidgetInterceptor(null, driver, element, instantiationMap, duration) + ) + ); } } try { diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java index 8b96c6c68..d276ce616 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java @@ -16,33 +16,30 @@ package io.appium.java_client.pagefactory.interceptors; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import io.appium.java_client.proxy.MethodCallListener; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; -public abstract class InterceptorOfAListOfElements implements MethodInterceptor { +public abstract class InterceptorOfAListOfElements implements MethodCallListener { protected final ElementLocator locator; public InterceptorOfAListOfElements(ElementLocator locator) { this.locator = locator; } - protected abstract Object getObject(List elements, Method method, Object[] args) - throws InvocationTargetException, IllegalAccessException, InstantiationException, Throwable; + protected abstract Object getObject( + List elements, Method method, Object[] args + ) throws Throwable; - /** - * Look at {@link MethodInterceptor#intercept(Object, Method, Object[], MethodProxy)}. - */ - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) - throws Throwable { + @Override + public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { - return proxy.invokeSuper(obj, args); + return original.call(); } List realElements = new ArrayList<>(locator.findElements()); diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java index d06555eaf..7ac12b3d6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java @@ -16,16 +16,16 @@ package io.appium.java_client.pagefactory.interceptors; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import io.appium.java_client.proxy.MethodCallListener; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.support.pagefactory.ElementLocator; import java.lang.reflect.Method; +import java.util.concurrent.Callable; -public abstract class InterceptorOfASingleElement implements MethodInterceptor { +public abstract class InterceptorOfASingleElement implements MethodCallListener { protected final ElementLocator locator; protected final WebDriver driver; @@ -34,25 +34,20 @@ public InterceptorOfASingleElement(ElementLocator locator, WebDriver driver) { this.driver = driver; } - protected abstract Object getObject(WebElement element, Method method, Object[] args) - throws Throwable; - - /** - * Look at {@link MethodInterceptor#intercept(Object, Method, Object[], MethodProxy)}. - */ - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) - throws Throwable { + protected abstract Object getObject(WebElement element, Method method, Object[] args) throws Throwable; + @Override + public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { if (method.getName().equals("toString") && args.length == 0) { return locator.toString(); } if (Object.class.equals(method.getDeclaringClass())) { - return proxy.invokeSuper(obj, args); + return original.call(); } - if (WrapsDriver.class.isAssignableFrom(method.getDeclaringClass()) && method.getName() - .equals("getWrappedDriver")) { + if (WrapsDriver.class.isAssignableFrom(method.getDeclaringClass()) + && method.getName().equals("getWrappedDriver")) { return driver; } 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 390ebcd92..d0b8e4832 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 @@ -16,43 +16,76 @@ package io.appium.java_client.pagefactory.utils; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; +import io.appium.java_client.proxy.MethodCallListener; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static io.appium.java_client.proxy.Helpers.OBJECT_METHOD_NAMES; +import static io.appium.java_client.proxy.Helpers.createProxy; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; /** * Original class is a super class of a * proxy object here. */ public final class ProxyFactory { + private static final Set NON_PROXYABLE_METHODS = setWithout(OBJECT_METHOD_NAMES, "toString"); + + @SuppressWarnings("unchecked") + private static Set setWithout(@SuppressWarnings("SameParameterValue") Set source, T... items) { + Set result = new HashSet<>(source); + Arrays.asList(items).forEach(result::remove); + return Collections.unmodifiableSet(result); + } private ProxyFactory() { super(); } - public static T getEnhancedProxy(Class requiredClazz, MethodInterceptor interceptor) { - return getEnhancedProxy(requiredClazz, new Class[] {}, new Object[] {}, interceptor); + /** + * Creates a proxy instance for the given class with an empty constructor. + * + * @param The proxy object class. + * @param requiredClazz is a {@link java.lang.Class} whose instance should be created + * @param listener is the instance of a method listener class + * @return a proxied instance of the desired class + */ + public static T getEnhancedProxy(Class requiredClazz, MethodCallListener listener) { + return getEnhancedProxy(requiredClazz, new Class[] {}, new Object[] {}, listener); } /** - * It returns some proxies created by CGLIB. + * Creates a proxy instance for the given class. * * @param The proxy object class. - * @param requiredClazz is a {@link java.lang.Class} whose instance should be created + * @param cls 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 * class. * @param values is an array of @link java.lang.Object}. It should be convenient to * parameter types of some declared constructor which belongs to desired * class. - * @param interceptor is the instance of {@link net.sf.cglib.proxy.MethodInterceptor} + * @param listener is the instance of a method listener class * @return a proxied instance of the desired class */ - @SuppressWarnings("unchecked") - public static T getEnhancedProxy(Class requiredClazz, Class[] params, Object[] values, - MethodInterceptor interceptor) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(requiredClazz); - enhancer.setCallback(interceptor); - return (T) enhancer.create(params, values); + public static T getEnhancedProxy( + Class cls, Class[] params, Object[] values, MethodCallListener listener + ) { + ElementMatcher extraMatcher = ElementMatchers.not(namedOneOf( + NON_PROXYABLE_METHODS.toArray(new String[0]) + )); + return createProxy( + cls, + values, + params, + Collections.singletonList(listener), + extraMatcher + ); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index e6bb6f6d6..53ba1506c 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -24,6 +24,8 @@ import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.WrapsElement; +import javax.annotation.Nullable; + import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; import static java.util.Optional.ofNullable; @@ -45,21 +47,20 @@ public final class WebDriverUnpackUtility { * {@link WrapsDriver} or {@link WrapsElement} then this method returns null. * */ + @Nullable public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext) { if (searchContext instanceof WebDriver) { return (WebDriver) searchContext; } if (searchContext instanceof WrapsDriver) { - return unpackWebDriverFromSearchContext( - ((WrapsDriver) searchContext).getWrappedDriver()); + return unpackWebDriverFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver()); } // Search context it is not only WebDriver. WebElement is search context too. // RemoteWebElement implements WrapsDriver if (searchContext instanceof WrapsElement) { - return unpackWebDriverFromSearchContext( - ((WrapsElement) searchContext).getWrappedElement()); + return unpackWebDriverFromSearchContext(((WrapsElement) searchContext).getWrappedElement()); } return null; diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 286d7fc31..2dd5912ca 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -18,14 +18,28 @@ import com.google.common.base.Preconditions; import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; +import javax.annotation.Nullable; +import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; public class Helpers { + + public static final Set OBJECT_METHOD_NAMES = Stream.of(Object.class.getMethods()) + .map(Method::getName) + .collect(Collectors.toSet()); + private Helpers() { } @@ -53,6 +67,40 @@ public static T createProxy( Object[] constructorArgs, Class[] constructorArgTypes, Collection listeners + ) { + ElementMatcher extraMatcher = ElementMatchers.not(namedOneOf( + OBJECT_METHOD_NAMES.toArray(new String[0]) + )); + return createProxy(cls, constructorArgs, constructorArgTypes, listeners, extraMatcher); + } + + /** + * Creates a transparent proxy instance for the given class. + * It is possible to provide one or more method execution listeners + * or replace particular method calls completely. Callbacks + * defined in these listeners are going to be called when any + * **public** method of the given class is invoked. Overridden callbacks + * are expected to be skipped if they throw + * {@link io.appium.java_client.proxy.NotImplementedException}. + * !!! This API is designed for private usage. + * + * @param cls The class to which the proxy should be created. + * Must not be an interface. + * @param constructorArgs Array of constructor arguments. Could be an + * empty array if the class provides a constructor without arguments. + * @param constructorArgTypes Array of constructor argument types. Must + * represent types of constructorArgs. + * @param listeners One or more method invocation listeners. + * @param extraMethodMatcher Optional additional method proxy conditions + * @param Any class derived from Object + * @return Proxy instance + */ + public static T createProxy( + Class cls, + Object[] constructorArgs, + Class[] constructorArgTypes, + Collection listeners, + @Nullable ElementMatcher extraMethodMatcher ) { Preconditions.checkArgument(constructorArgs.length == constructorArgTypes.length, String.format( @@ -64,27 +112,22 @@ public static T createProxy( Preconditions.checkArgument(cls != null, "Class must not be null"); Preconditions.checkArgument(!cls.isInterface(), "Class must not be an interface"); + ElementMatcher.Junction matcher = ElementMatchers.isPublic(); //noinspection resource Class proxy = new ByteBuddy() .subclass(cls) - .method(ElementMatchers.isPublic() - .and(ElementMatchers.not( - ElementMatchers.isDeclaredBy(Object.class) - .or(ElementMatchers.isOverriddenFrom(Object.class)) - ))) + .method(extraMethodMatcher == null ? matcher : matcher.and(extraMethodMatcher)) .intercept(MethodDelegation.to(Interceptor.class)) .make() - .load(cls.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) + .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .asSubclass(cls); try { - //noinspection unchecked - T instance = (T) proxy - .getConstructor(constructorArgTypes) - .newInstance(constructorArgs); - Interceptor.LISTENERS.put(instance, listeners); - return instance; + return ProxyListenersContainer.getInstance().setListeners( + cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs)), + listeners + ); } catch (SecurityException | ReflectiveOperationException e) { throw new IllegalStateException(String.format("Unable to create a proxy of %s", cls.getName()), e); } diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index d343fc9c1..dce2fd807 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -26,20 +26,11 @@ import java.lang.reflect.Method; import java.util.Collection; -import java.util.Map; -import java.util.Set; import java.util.UUID; -import java.util.WeakHashMap; import java.util.concurrent.Callable; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class Interceptor { private static final Logger logger = LoggerFactory.getLogger(Interceptor.class); - public static final Map> LISTENERS = new WeakHashMap<>(); - private static final Set OBJECT_METHOD_NAMES = Stream.of(Object.class.getMethods()) - .map(Method::getName) - .collect(Collectors.toSet()); /** * A magic method used to wrap public method calls in classes @@ -51,6 +42,7 @@ public class Interceptor { * @param callable The reference to the non-patched callable to avoid call recursion. * @return Either the original method result or the patched one. */ + @SuppressWarnings("unused") @RuntimeType public static Object intercept( @This Object self, @@ -58,11 +50,8 @@ public static Object intercept( @AllArguments Object[] args, @SuperCall Callable callable ) throws Throwable { - if (OBJECT_METHOD_NAMES.contains(method.getName())) { - return callable.call(); - } - Collection listeners = LISTENERS.get(self); - if (listeners == null || listeners.isEmpty()) { + Collection listeners = ProxyListenersContainer.getInstance().getListeners(self); + if (listeners.isEmpty()) { return callable.call(); } diff --git a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java new file mode 100644 index 000000000..4b314b02b --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java @@ -0,0 +1,140 @@ +/* + * 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.proxy; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Semaphore; + +class ProxyListenersContainer { + private static ProxyListenersContainer INSTANCE; + + public static synchronized ProxyListenersContainer getInstance() { + if (INSTANCE == null) { + INSTANCE = new ProxyListenersContainer(); + } + return INSTANCE; + } + + private final Semaphore listenersGuard = new Semaphore(1); + // Previously WeakHashMap has been used because of O(1) lookup performance, although + // we had to change it to a list, which has O(N). The reason for that is that + // maps implicitly call `hashCode` API on instances, which might not always + // work as expected for arbitrary proxies + private final List, Collection>> listeners = new LinkedList<>(); + + private static class Pair { + private final K key; + private final V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + } + + private ProxyListenersContainer() { + } + + /** + * Assign listeners for the particular proxied instance. + * + * @param proxyInstance The proxied instance. + * @param listeners Collection of listeners. + * @return The same given instance. + */ + public T setListeners(T proxyInstance, Collection listeners) { + try { + listenersGuard.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + int i = 0; + boolean wasInstancePresent = false; + while (i < this.listeners.size()) { + Pair, Collection> pair = this.listeners.get(i); + Object key = pair.getKey().get(); + if (key == null) { + // The instance has been garbage-collected + this.listeners.remove(i); + continue; + } + + if (key == proxyInstance) { + pair.getValue().clear(); + pair.getValue().addAll(listeners); + wasInstancePresent = true; + } + i++; + } + if (!wasInstancePresent) { + this.listeners.add(new Pair<>(new WeakReference<>(proxyInstance), new HashSet<>(listeners))); + } + } finally { + listenersGuard.release(); + } + return proxyInstance; + } + + /** + * Fetches listeners for the particular proxied instance. + * + * @param proxyInstance The proxied instance. + */ + public Collection getListeners(Object proxyInstance) { + try { + listenersGuard.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + int i = 0; + Collection result = Collections.emptySet(); + while (i < listeners.size()) { + Pair, Collection> pair = listeners.get(i); + Object key = pair.getKey().get(); + if (key == null) { + // The instance has been garbage-collected + listeners.remove(i); + continue; + } + + if (key == proxyInstance) { + result = pair.getValue(); + } + i++; + } + return result; + } finally { + listenersGuard.release(); + } + } + +} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java index 6e1f4ab2b..2f8e2d60d 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/WidgetTest.java @@ -37,7 +37,6 @@ protected WidgetTest(AbstractApp app, WebDriver driver) { protected static void checkThatLocatorsAreCreatedCorrectly(DefaultStubWidget single, List multiple, By rootLocator, By subLocator) { - assertThat(single.toString(), containsString(rootLocator.toString())); assertThat(multiple.stream().map(DefaultStubWidget::toString).collect(toList()), contains(containsString(rootLocator.toString()), diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java index 2a453c2d9..c3ee905fb 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java @@ -6,7 +6,6 @@ import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget; -import io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -35,7 +34,6 @@ public static Stream data() { arguments(new CombinedApp(), new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class), arguments(new CombinedApp(), new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultIosXCUITWidget.class), - arguments(new CombinedApp(), new AbstractStubWebDriver.StubWindowsDriver(), DefaultWindowsWidget.class), arguments(new CombinedApp(), new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class), arguments(new CombinedApp(), new AbstractStubWebDriver.StubAndroidBrowserOrWebViewDriver(), DefaultFindByWidget.class), @@ -72,14 +70,12 @@ public static class CombinedApp implements AbstractApp { @OverrideWidget(html = DefaultFindByWidget.class, androidUIAutomator = DefaultAndroidWidget.class, - iOSXCUITAutomation = DefaultIosXCUITWidget.class, - windowsAutomation = DefaultWindowsWidget.class) + iOSXCUITAutomation = DefaultIosXCUITWidget.class) private DefaultStubWidget singleWidget; @OverrideWidget(html = DefaultFindByWidget.class, androidUIAutomator = DefaultAndroidWidget.class, - iOSXCUITAutomation = DefaultIosXCUITWidget.class, - windowsAutomation = DefaultWindowsWidget.class) + iOSXCUITAutomation = DefaultIosXCUITWidget.class) private List multipleWidget; @Override diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java index d54e3618c..3c1c9145d 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java @@ -6,7 +6,6 @@ import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget; -import io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -38,8 +37,6 @@ public static Stream data() { new AbstractStubWebDriver.StubAndroidDriver(), DefaultAndroidWidget.class), Arguments.of(new AppWithCombinedWidgets(), new AbstractStubWebDriver.StubIOSXCUITDriver(), DefaultIosXCUITWidget.class), - Arguments.of(new AppWithCombinedWidgets(), - new AbstractStubWebDriver.StubWindowsDriver(), DefaultWindowsWidget.class), Arguments.of(new AppWithCombinedWidgets(), new AbstractStubWebDriver.StubBrowserDriver(), DefaultFindByWidget.class), Arguments.of(new AppWithCombinedWidgets(), @@ -79,15 +76,13 @@ public static class CombinedWidget extends DefaultStubWidget { @OverrideWidget(html = DefaultFindByWidget.class, androidUIAutomator = DefaultAndroidWidget.class, - iOSXCUITAutomation = DefaultIosXCUITWidget.class, - windowsAutomation = DefaultWindowsWidget.class + iOSXCUITAutomation = DefaultIosXCUITWidget.class ) private DefaultStubWidget singleWidget; @OverrideWidget(html = DefaultFindByWidget.class, androidUIAutomator = DefaultAndroidWidget.class, - iOSXCUITAutomation = DefaultIosXCUITWidget.class, - windowsAutomation = DefaultWindowsWidget.class + iOSXCUITAutomation = DefaultIosXCUITWidget.class ) private List multipleWidget; diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/AnnotatedWindowsWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/AnnotatedWindowsWidget.java deleted file mode 100644 index 733d0db95..000000000 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/AnnotatedWindowsWidget.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.appium.java_client.pagefactory_tests.widget.tests.windows; - -import io.appium.java_client.pagefactory.WindowsFindBy; -import io.appium.java_client.pagefactory.iOSXCUITFindBy; -import org.openqa.selenium.WebElement; - -@WindowsFindBy(windowsAutomation = "SOME_ROOT_LOCATOR") -@iOSXCUITFindBy(iOSNsPredicate = "XCUIT_SOME_ROOT_LOCATOR") -public class AnnotatedWindowsWidget extends DefaultWindowsWidget { - public static String WINDOWS_ROOT_WIDGET_LOCATOR = "SOME_ROOT_LOCATOR"; - - protected AnnotatedWindowsWidget(WebElement element) { - super(element); - } -} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/DefaultWindowsWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/DefaultWindowsWidget.java deleted file mode 100644 index ab7b81a41..000000000 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/DefaultWindowsWidget.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.appium.java_client.pagefactory_tests.widget.tests.windows; - -import io.appium.java_client.pagefactory.WindowsFindBy; -import io.appium.java_client.pagefactory.iOSXCUITFindBy; -import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; -import org.openqa.selenium.WebElement; - -import java.util.List; - -public class DefaultWindowsWidget extends DefaultStubWidget { - - public static String WINDOWS_SUB_WIDGET_LOCATOR = "SOME_SUB_LOCATOR"; - - @WindowsFindBy(windowsAutomation = "SOME_SUB_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "XCUIT_SOME_SUB_LOCATOR") - private DefaultWindowsWidget singleWidget; - - @WindowsFindBy(windowsAutomation = "SOME_SUB_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "XCUIT_SOME_SUB_LOCATOR") - private List multipleWidgets; - - protected DefaultWindowsWidget(WebElement element) { - super(element); - } - - @Override - public DefaultWindowsWidget getSubWidget() { - return singleWidget; - } - - @Override - public List getSubWidgets() { - return multipleWidgets; - } -} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/ExtendedWindowsWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/ExtendedWindowsWidget.java deleted file mode 100644 index 14cc95f65..000000000 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/ExtendedWindowsWidget.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.appium.java_client.pagefactory_tests.widget.tests.windows; - -import org.openqa.selenium.WebElement; - -public class ExtendedWindowsWidget extends AnnotatedWindowsWidget { - protected ExtendedWindowsWidget(WebElement element) { - super(element); - } -} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsApp.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsApp.java deleted file mode 100644 index 07eb5784d..000000000 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsApp.java +++ /dev/null @@ -1,125 +0,0 @@ -package io.appium.java_client.pagefactory_tests.widget.tests.windows; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.pagefactory.WindowsFindBy; -import io.appium.java_client.pagefactory.iOSXCUITFindBy; -import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; - -import java.util.List; - -public class WindowsApp implements ExtendedApp { - - public static String WINDOWS_DEFAULT_WIDGET_LOCATOR = "SOME_WINDOWS_DEFAULT_LOCATOR"; - - public static String WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR = "WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR"; - - @WindowsFindBy(windowsAutomation = "SOME_WINDOWS_DEFAULT_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "SOME_XCUIT_DEFAULT_LOCATOR") - private DefaultWindowsWidget singleIosWidget; - - @WindowsFindBy(windowsAutomation = "SOME_WINDOWS_DEFAULT_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "SOME_XCUIT_DEFAULT_LOCATOR") - private List multipleIosWidgets; - - /** - * This class is annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * This field was added to check that locator is created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)} - */ - private AnnotatedWindowsWidget singleAnnotatedIosWidget; - - /** - * This class is annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * This field was added to check that locator is created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)}. - */ - private List multipleIosIosWidgets; - - /** - * This class is not annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * But the superclass is annotated by these annotations. This field was added to check that locator is - * created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)}. - */ - private ExtendedWindowsWidget singleExtendedIosWidget; - - /** - * This class is not annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * But the superclass is annotated by these annotations. This field was added to check that locator is - * created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)}. - */ - private List multipleExtendedIosWidgets; - - /** - * This class is not annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * But the superclass is annotated by these annotations. This field was added to check that locator is - * created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)}. - */ - @WindowsFindBy(windowsAutomation = "WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "SOME_XCUIT_EXTERNALLY_DEFINED_LOCATOR") - private ExtendedWindowsWidget singleOverriddenIosWidget; - - /** - * This class is not annotated by {@link WindowsFindBy} and - * {@link io.appium.java_client.pagefactory.iOSXCUITFindBy}. - * But the superclass is annotated by these annotations. This field was added to check that locator is - * created correctly according to current platform. - * It is expected that the root element and sub-elements are found using - * {@link AppiumBy#windowsAutomation(String)}. - */ - @WindowsFindBy(windowsAutomation = "WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR") - @iOSXCUITFindBy(iOSNsPredicate = "SOME_XCUIT_EXTERNALLY_DEFINED_LOCATOR") - private List multipleOverriddenIosWidgets; - - @Override - public DefaultWindowsWidget getWidget() { - return singleIosWidget; - } - - @Override - public List getWidgets() { - return multipleIosWidgets; - } - - @Override - public DefaultWindowsWidget getAnnotatedWidget() { - return singleAnnotatedIosWidget; - } - - @Override - public List getAnnotatedWidgets() { - return multipleIosIosWidgets; - } - - @Override - public DefaultWindowsWidget getExtendedWidget() { - return singleExtendedIosWidget; - } - - @Override - public List getExtendedWidgets() { - return multipleExtendedIosWidgets; - } - - @Override - public DefaultWindowsWidget getExtendedWidgetWithOverriddenLocators() { - return singleOverriddenIosWidget; - } - - @Override - public List getExtendedWidgetsWithOverriddenLocators() { - return multipleOverriddenIosWidgets; - } -} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java deleted file mode 100644 index 023b24819..000000000 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/windows/WindowsWidgetTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.appium.java_client.pagefactory_tests.widget.tests.windows; - -import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; -import io.appium.java_client.pagefactory_tests.widget.tests.ExtendedApp; -import io.appium.java_client.pagefactory_tests.widget.tests.WidgetTest; -import org.junit.jupiter.api.Test; - -import static io.appium.java_client.MobileBy.windowsAutomation; -import static io.appium.java_client.pagefactory_tests.widget.tests.windows.AnnotatedWindowsWidget.WINDOWS_ROOT_WIDGET_LOCATOR; -import static io.appium.java_client.pagefactory_tests.widget.tests.windows.DefaultWindowsWidget.WINDOWS_SUB_WIDGET_LOCATOR; -import static io.appium.java_client.pagefactory_tests.widget.tests.windows.WindowsApp.WINDOWS_DEFAULT_WIDGET_LOCATOR; -import static io.appium.java_client.pagefactory_tests.widget.tests.windows.WindowsApp.WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR; - -public class WindowsWidgetTest extends WidgetTest { - - public WindowsWidgetTest() { - super(new WindowsApp(), new AbstractStubWebDriver.StubWindowsDriver()); - } - - @Test - @Override - public void checkThatWidgetsAreCreatedCorrectly() { - checkThatLocatorsAreCreatedCorrectly(app.getWidget(), app.getWidgets(), - windowsAutomation(WINDOWS_DEFAULT_WIDGET_LOCATOR), windowsAutomation(WINDOWS_SUB_WIDGET_LOCATOR)); - } - - @Test - @Override - public void checkCaseWhenWidgetClassHasDeclaredLocatorAnnotation() { - checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getAnnotatedWidget(), - ((ExtendedApp) app).getAnnotatedWidgets(), - windowsAutomation(WINDOWS_ROOT_WIDGET_LOCATOR), windowsAutomation(WINDOWS_SUB_WIDGET_LOCATOR)); - } - - @Test - @Override - public void checkCaseWhenWidgetClassHasNoDeclaredAnnotationButItHasSuperclass() { - checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getExtendedWidget(), - ((ExtendedApp) app).getExtendedWidgets(), - windowsAutomation(WINDOWS_ROOT_WIDGET_LOCATOR), windowsAutomation(WINDOWS_SUB_WIDGET_LOCATOR)); - } - - @Test - @Override - public void checkCaseWhenBothWidgetFieldAndClassHaveDeclaredAnnotations() { - checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getExtendedWidgetWithOverriddenLocators(), - ((ExtendedApp) app).getExtendedWidgetsWithOverriddenLocators(), - windowsAutomation(WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR), - windowsAutomation(WINDOWS_SUB_WIDGET_LOCATOR)); - } -} diff --git a/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java index 218c56270..a8767629d 100644 --- a/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java +++ b/src/test/java/io/appium/java_client/proxy/ProxyHelpersTest.java @@ -93,7 +93,6 @@ public Object onError(Object obj, Method method, Object[] args, Throwable e) { } }; RemoteWebDriver driver = createProxy(RemoteWebDriver.class, Collections.singletonList(listener)); - assertThrows( IllegalStateException.class, () -> driver.get("http://example.com/") From a42ba4d918a8a5b223b93391aee529fec0e72b73 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 29 May 2023 11:16:17 +0000 Subject: [PATCH 103/314] chore: Improve the error message on service startup (#1928) --- .../local/AppiumDriverLocalService.java | 57 +++++++++++++------ .../service/local/AppiumServiceBuilder.java | 15 ++--- .../service/local/ServerBuilderTest.java | 6 +- 3 files changed, 46 insertions(+), 32 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 d2b4d37fc..08f289289 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 @@ -35,6 +35,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -46,7 +47,8 @@ import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS; import static org.slf4j.event.Level.DEBUG; import static org.slf4j.event.Level.INFO; @@ -57,6 +59,7 @@ public final class AppiumDriverLocalService extends DriverService { 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 static final Duration IS_RUNNING_PING_TIMEOUT = Duration.ofMillis(1500); private final File nodeJSExec; private final List nodeJSArgs; @@ -106,7 +109,7 @@ private static URL addSuffix(URL url, String suffix) { @SneakyThrows @SuppressWarnings("SameParameterValue") private static URL replaceHost(URL source, String oldHost, String newHost) { - return new URL(source.toString().replace(oldHost, newHost)); + return new URL(source.toString().replaceFirst(oldHost, newHost)); } /** @@ -128,7 +131,7 @@ public boolean isRunning() { } try { - ping(Duration.ofMillis(1500)); + ping(IS_RUNNING_PING_TIMEOUT); return true; } catch (UrlChecker.TimeoutException e) { return false; @@ -142,8 +145,15 @@ public boolean isRunning() { } private void ping(Duration timeout) throws UrlChecker.TimeoutException, MalformedURLException { - // The operating system might block direct access to the universal broadcast IP address - URL status = addSuffix(replaceHost(getUrl(), BROADCAST_IP_ADDRESS, "127.0.0.1"), "/status"); + URL url = getUrl(); + String host = url.getHost(); + // The operating system will block direct access to the universal broadcast IP address + if (host.equals(BROADCAST_IP4_ADDRESS)) { + url = replaceHost(url, BROADCAST_IP4_ADDRESS, "127.0.0.1"); + } else if (host.equals(BROADCAST_IP6_ADDRESS)) { + url = replaceHost(url, BROADCAST_IP6_ADDRESS, "::1"); + } + URL status = addSuffix(url, "/status"); new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); } @@ -161,25 +171,36 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } try { - process = new CommandLine(this.nodeJSExec.getCanonicalPath(), - nodeJSArgs.toArray(new String[]{})); + process = new CommandLine( + this.nodeJSExec.getCanonicalPath(), + nodeJSArgs.toArray(new String[]{}) + ); process.setEnvironmentVariables(nodeJSEnvironment); process.copyOutputTo(stream); process.executeAsync(); ping(startupTimeout); - } catch (Throwable e) { + } catch (Exception e) { + final Optional output = Optional.ofNullable(process) + .map(CommandLine::getStdOut) + .filter((o) -> !StringUtils.isBlank(o)); destroyProcess(); - String msgTxt = "The local appium server has not been started. " - + "The given Node.js executable: " + this.nodeJSExec.getAbsolutePath() - + " Arguments: " + nodeJSArgs.toString() + " " + "\n"; - if (process != null) { - String processStream = process.getStdOut(); - if (!StringUtils.isBlank(processStream)) { - msgTxt = msgTxt + "Process output: " + processStream + "\n"; - } + List errorLines = new ArrayList<>(); + errorLines.add("The local appium server has not been started"); + errorLines.add(String.format("Reason: %s", e.getMessage())); + if (e instanceof UrlChecker.TimeoutException) { + errorLines.add(String.format( + "Consider increasing the server startup timeout value (currently %sms)", + startupTimeout.toMillis() + )); } - - throw new AppiumServerHasNotBeenStartedLocallyException(msgTxt, e); + errorLines.add( + String.format("Node.js executable path: %s", nodeJSExec.getAbsolutePath()) + ); + errorLines.add(String.format("Arguments: %s", nodeJSArgs)); + output.ifPresent((o) -> errorLines.add(String.format("Output: %s", o))); + throw new AppiumServerHasNotBeenStartedLocallyException( + StringUtils.joinWith("\n", errorLines), e + ); } } finally { lock.unlock(); 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 9d3bce564..0e354d441 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 @@ -29,7 +29,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; 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; @@ -72,13 +71,14 @@ public final class AppiumServiceBuilder */ private static final String NODE_PATH = "NODE_BINARY_PATH"; - public static final String BROADCAST_IP_ADDRESS = "0.0.0.0"; + public static final String BROADCAST_IP4_ADDRESS = "0.0.0.0"; + public static final String BROADCAST_IP6_ADDRESS = "::"; 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<>(); private File appiumJS; private File node; - private String ipAddress = BROADCAST_IP_ADDRESS; + private String ipAddress = BROADCAST_IP4_ADDRESS; private Capabilities capabilities; private boolean autoQuoteCapabilitiesOnWindows = false; private static final Function APPIUM_JS_NOT_EXIST_ERROR = (fullPath) -> String.format( @@ -363,14 +363,7 @@ protected ImmutableList createArgs() { argList.add(String.valueOf(getPort())); if (StringUtils.isBlank(ipAddress)) { - ipAddress = BROADCAST_IP_ADDRESS; - } else { - InetAddressValidator validator = InetAddressValidator.getInstance(); - if (!validator.isValid(ipAddress) && !validator.isValidInet4Address(ipAddress) - && !validator.isValidInet6Address(ipAddress)) { - throw new IllegalArgumentException( - "The invalid IP address " + ipAddress + " is defined"); - } + ipAddress = BROADCAST_IP4_ADDRESS; } argList.add("--address"); argList.add(ipAddress); 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 5d4fd5715..c8d9dd0f9 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 @@ -19,7 +19,7 @@ import static io.appium.java_client.TestUtils.getLocalIp4Address; import static io.appium.java_client.service.local.AppiumDriverLocalService.buildDefaultService; import static io.appium.java_client.service.local.AppiumServiceBuilder.APPIUM_PATH; -import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; import static io.appium.java_client.service.local.flags.GeneralServerFlag.BASEPATH; import static io.appium.java_client.service.local.flags.GeneralServerFlag.CALLBACK_ADDRESS; @@ -323,7 +323,7 @@ void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath + "/", service.getUrl().toString()); } @@ -333,7 +333,7 @@ void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath.substring(1), service.getUrl().toString()); } From 8ebb5c320a1b98fbc14ac2f2c9cec1447db76c34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 21:42:48 +0300 Subject: [PATCH 104/314] build(deps): Bump org.projectlombok:lombok from 1.18.26 to 1.18.28 (#1929) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.26 to 1.18.28. - [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.26...v1.18.28) --- 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 2c2371e99..7b5532915 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.26' - annotationProcessor 'org.projectlombok:lombok:1.18.26' + compileOnly 'org.projectlombok:lombok:1.18.28' + annotationProcessor 'org.projectlombok:lombok:1.18.28' api ('org.seleniumhq.selenium:selenium-api') { version { From 9176e8ead6ae19116713d8eb4fd15958bca4f4ca Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Fri, 2 Jun 2023 20:18:53 +0800 Subject: [PATCH 105/314] Release 8.5.1 and update release notes --- CHANGELOG.md | 15 +++++++++++++++ gradle.properties | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b8089b6c..cf1920ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +*8.5.1* +- **[BUG FIX]** + - Use correct exception type for fallback at file/folder pulling. [#1912](https://github.com/appium/java-client/pull/1912) + - Update autoWebview capability name. [#1917](https://github.com/appium/java-client/pull/1917) +- **[REFACTOR]** + - Move execution of E2E tests to GitHub Actions. [#1913](https://github.com/appium/java-client/pull/1913) + - Replace cglib with bytebuddy. [#1923](https://github.com/appium/java-client/pull/1923) + - Improve the error message on service startup. [#1928](https://github.com/appium/java-client/pull/1928) +- **[DOCUMENTATION]** + - Initiate Selenium client compatibility matrix. [#1918](https://github.com/appium/java-client/pull/1918) +- **[DEPENDENCY UPDATES]** + - `io.github.bonigarcia:webdrivermanager` was updated to 5.3.3. + - `org.projectlombok:lombok` was updated to 1.18.28. + - `commons-io:commons-io` was updated to 2.12.0. + *8.5.0* - **[BUG FIX]** - Restore Jitpack builds. [#1911](https://github.com/appium/java-client/pull/1911) diff --git a/gradle.properties b/gradle.properties index fca46f2cd..e7f91060b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.9.1 # Please increment the value in a release -appiumClient.version=8.5.0 +appiumClient.version=8.5.1 From 28adafa4cc9e8ac570732cf7c095cf30a3bf4093 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 8 Jun 2023 11:37:28 +0300 Subject: [PATCH 106/314] docs: Add the latest versions of clients to the compatibility matrix (#1935) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 104c256e4..5a7c669a4 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ dependencies { ### Compatibility Matrix | Appium Java Client | Selenium client | |--------------------|---------------------------------------------| -| `8.5.0` | `4.9.1` | +| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0` | | `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` | | `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` | | `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` | From 431edd3d89531414c0af7553a10fcf648e32a18a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 8 Jun 2023 13:10:50 +0000 Subject: [PATCH 107/314] fix: Exclude abstract methods from proxy matching (#1937) --- .../java_client/pagefactory/utils/ProxyFactory.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 d0b8e4832..7cec1ad07 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 @@ -19,7 +19,6 @@ import io.appium.java_client.proxy.MethodCallListener; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.matcher.ElementMatchers; import java.util.Arrays; import java.util.Collections; @@ -28,7 +27,9 @@ import static io.appium.java_client.proxy.Helpers.OBJECT_METHOD_NAMES; import static io.appium.java_client.proxy.Helpers.createProxy; +import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.not; /** * Original class is a super class of a @@ -77,9 +78,11 @@ public static T getEnhancedProxy(Class requiredClazz, MethodCallListener public static T getEnhancedProxy( Class cls, Class[] params, Object[] values, MethodCallListener listener ) { - ElementMatcher extraMatcher = ElementMatchers.not(namedOneOf( - NON_PROXYABLE_METHODS.toArray(new String[0]) - )); + ElementMatcher extraMatcher = not( + namedOneOf(NON_PROXYABLE_METHODS.toArray(new String[0])) + ).and( + not(isAbstract()) + ); return createProxy( cls, values, From 0c7086fd19b830ac6c7548842f185a4b969b3e64 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 8 Jun 2023 19:31:54 +0000 Subject: [PATCH 108/314] chore: Mark Windows page object annotations as deprecated (#1938) --- src/main/java/io/appium/java_client/pagefactory/WindowsBy.java | 2 ++ .../java/io/appium/java_client/pagefactory/WindowsFindAll.java | 2 ++ .../java/io/appium/java_client/pagefactory/WindowsFindBy.java | 2 ++ .../io/appium/java_client/pagefactory/WindowsFindByAllSet.java | 2 ++ .../appium/java_client/pagefactory/WindowsFindByChainSet.java | 2 ++ .../io/appium/java_client/pagefactory/WindowsFindBySet.java | 2 ++ .../java/io/appium/java_client/pagefactory/WindowsFindBys.java | 2 ++ 7 files changed, 14 insertions(+) diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java index e953db2fc..3c599c326 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java @@ -18,7 +18,9 @@ /** * Used to build a complex Windows automation locator. + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated public @interface WindowsBy { /** diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java index 206c1dd08..ac1d0d0a5 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java @@ -29,7 +29,9 @@ * of {@link WindowsBy} tags * It will then search for all elements that match any criteria. Note that elements * are not guaranteed to be in document order. + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(WindowsFindByAllSet.class) public @interface WindowsFindAll { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java index 2222d0312..f1c67be98 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java @@ -30,7 +30,9 @@ * {@link org.openqa.selenium.support.PageFactory} * this allows users to quickly and easily create PageObjects. * using Windows automation selectors, accessibility, id, name, class name, tag and xpath + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(WindowsFindBySet.class) public @interface WindowsFindBy { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java index 31caf953d..f93e00d92 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java @@ -10,7 +10,9 @@ /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindAll} + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Target(value = {TYPE, FIELD}) @Retention(value = RUNTIME) public @interface WindowsFindByAllSet { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java index b6518ba6a..b8a3e9f23 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java @@ -10,7 +10,9 @@ /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindBys} + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Target(value = {TYPE, FIELD}) @Retention(value = RUNTIME) public @interface WindowsFindByChainSet { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java index 2f7cda9f4..e953d63a1 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java @@ -26,7 +26,9 @@ /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindBy} + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Target(value = {TYPE, FIELD}) @Retention(value = RUNTIME) public @interface WindowsFindBySet { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java index 9120a41d6..358a77ae8 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java @@ -27,7 +27,9 @@ /** * Used to mark a field on a Page Object to indicate that lookup should use * a series of {@link WindowsBy} tags. + * @deprecated This annotation is deprecated and will be removed. */ +@Deprecated @Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(WindowsFindByChainSet.class) public @interface WindowsFindBys { From 22719abb8ab7fdd37d302d37f9673669d09e0d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 16:03:56 +0300 Subject: [PATCH 109/314] build(deps): Bump commons-io:commons-io from 2.12.0 to 2.13.0 (#1942) Bumps commons-io:commons-io from 2.12.0 to 2.13.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 7b5532915..f46ed8024 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.12.0' - implementation 'commons-io:commons-io:2.12.0' + implementation 'commons-io:commons-io:2.13.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' From 41177668fa16b67df94528350c007b160357ddb3 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 26 Jun 2023 17:20:21 +0300 Subject: [PATCH 110/314] test: Increase server start timeout for iOS tests (#1951) --- src/test/java/io/appium/java_client/ios/BaseIOSTest.java | 3 ++- .../java_client/service/local/StartingAppLocallyIosTest.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) 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 1661e19c9..2790465a6 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -34,6 +34,7 @@ public class BaseIOSTest { ? System.getenv("IOS_PLATFORM_VERSION") : "14.5"; public static final Duration WDA_LAUNCH_TIMEOUT = Duration.ofSeconds(240); + public static final Duration SERVER_START_TIMEOUT = Duration.ofSeconds(40); /** * Starts a local server. @@ -44,7 +45,7 @@ public static AppiumDriverLocalService startAppiumServer() { service = new AppiumServiceBuilder() .withIPAddress("127.0.0.1") .usingPort(PORT) - .withTimeout(Duration.ofSeconds(40)) + .withTimeout(SERVER_START_TIMEOUT) .build(); service.start(); return service; diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java index 6baf77701..3ce6bd9af 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java @@ -66,7 +66,8 @@ void startingIOSAppWithCapabilitiesAndServiceTest() { AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) - .withArgument(GeneralServerFlag.STRICT_CAPS); + .withArgument(GeneralServerFlag.STRICT_CAPS) + .withTimeout(BaseIOSTest.SERVER_START_TIMEOUT); IOSDriver driver = new IOSDriver(builder, options); try { @@ -92,6 +93,7 @@ void startingIOSAppWithCapabilitiesAndFlagsOnServerSideTest() { AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) .withArgument(GeneralServerFlag.STRICT_CAPS) + .withTimeout(BaseIOSTest.SERVER_START_TIMEOUT) .withCapabilities(serverOptions); IOSDriver driver = new IOSDriver(builder, clientOptions); From 305ce1a0c1eee4cca4897629df835cb964ec64b8 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 26 Jun 2023 18:41:40 +0300 Subject: [PATCH 111/314] build: Bump Checkstyle from `10.6.0` to `10.12.1` (#1948) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f46ed8024..3cf178884 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ if (JavaVersion.current().isJava11Compatible()) { apply plugin: 'checkstyle' checkstyle { - toolVersion = '10.6.0' + toolVersion = '10.12.1' configFile = configDirectory.file('appium-style.xml').get().getAsFile() showViolations = true ignoreFailures = false From a574d3f83fb22ae9fd92c86669275752c749a0e9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 26 Jun 2023 21:21:36 +0300 Subject: [PATCH 112/314] test: Fix Android test: `--base-path` arg must start with `/` (#1952) --- .../appium/java_client/service/local/ServerBuilderTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c8d9dd0f9..aae940553 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 @@ -319,11 +319,11 @@ void checkAbilityToStartServiceWithLogFileUsingShortFlag() { @Test void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { - String basePath = "wd/hub"; + String basePath = "/wd/hub"; service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); + String baseUrl = String.format("http://%s:%d", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath + "/", service.getUrl().toString()); } From 3643b01358e053a93a4962cb7583c5e16557d277 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:25:39 +0300 Subject: [PATCH 113/314] build(deps): Bump org.owasp.dependencycheck from 8.2.1 to 8.3.1 (#1950) Bumps org.owasp.dependencycheck from 8.2.1 to 8.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 3cf178884..8753cab22 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.2.1' + id 'org.owasp.dependencycheck' version '8.3.1' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 8177613ed3485bf30cd3600b587deacf79c7db83 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Tue, 11 Jul 2023 16:51:34 -0700 Subject: [PATCH 114/314] fix: AppiumClientConfig#readTimeout to call super.readTimeout (#1959) --- src/main/java/io/appium/java_client/AppiumClientConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index 317d85314..b25a3032d 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -129,7 +129,7 @@ public AppiumClientConfig connectionTimeout(Duration timeout) { @Override public AppiumClientConfig readTimeout(Duration timeout) { - ClientConfig clientConfig = super.connectionTimeout(timeout); + ClientConfig clientConfig = super.readTimeout(timeout); return buildAppiumClientConfig(clientConfig, directConnect); } From 70da13c312ab128281dfce32f54193a292682521 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 12 Jul 2023 09:49:23 +0000 Subject: [PATCH 115/314] chore: Deprecate obsolete capabilities constants (#1961) --- .../appium/java_client/remote/AndroidMobileCapabilityType.java | 3 +++ .../io/appium/java_client/remote/IOSMobileCapabilityType.java | 3 +++ .../io/appium/java_client/remote/MobileCapabilityType.java | 3 +++ .../io/appium/java_client/remote/YouiEngineCapabilityType.java | 3 +++ 4 files changed, 12 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 9ec293fa5..6b698d004 100644 --- a/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java @@ -23,7 +23,10 @@ * Read:
* * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#android-only + * + * @deprecated This interface will be removed. Use Options instead. */ +@Deprecated public interface AndroidMobileCapabilityType extends CapabilityType { /** diff --git a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java index 046dd4952..162353dc1 100644 --- a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java @@ -27,7 +27,10 @@ * and
* * https://github.com/appium/appium-xcuitest-driver#desired-capabilities + * + * @deprecated This interface will be removed. Use Options instead. */ +@Deprecated public interface IOSMobileCapabilityType extends CapabilityType { /** 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 f1aba4b9c..eeaa44405 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -23,7 +23,10 @@ * Read:
* * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#general-capabilities + * + * @deprecated This interface will be removed. Use Options instead. */ +@Deprecated public interface MobileCapabilityType extends CapabilityType { /** diff --git a/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java b/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java index 80301762d..1657103a6 100644 --- a/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java @@ -4,7 +4,10 @@ /** * The list of youiengine-specific capabilities. + * + * @deprecated This interface will be removed. Use Options instead. */ +@Deprecated public interface YouiEngineCapabilityType extends CapabilityType { /** * IP address of the app to execute commands against. From e337d12f426ec5955af1fef54b92fcef0ac8bb08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:22:14 +0300 Subject: [PATCH 116/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.9.3 to 5.10.0 (#1965) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.9.3 to 5.10.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.3...r5.10.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 8753cab22..819154260 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ dependencies { implementation 'commons-io:commons-io:2.13.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.3') { exclude group: 'org.seleniumhq.selenium' From 8bf7d8f70dda1cf70b5ab759001b0511568c9839 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:56:21 +0300 Subject: [PATCH 117/314] build(deps): Bump org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0 (#1973) Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 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 819154260..7a8607c08 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ dependencies { } implementation 'com.google.code.gson:gson:2.10.1' implementation 'commons-validator:commons-validator:1.7' - implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'org.apache.commons:commons-lang3:3.13.0' implementation 'commons-io:commons-io:2.13.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" From 1f8d893856265b71859ae91496ffe56af6fe0c52 Mon Sep 17 00:00:00 2001 From: Brett Fisher <32568586+fishey2@users.noreply.github.com> Date: Tue, 1 Aug 2023 07:11:15 +0100 Subject: [PATCH 118/314] fix: Correct spelling and semantic mistakes in method naming (#1967) (#1970) --- .../SupportsShowChromedriverLogOption.java | 25 +++++++++++++++++-- .../SupportsCustomSslCertOption.java | 12 ++++++++- .../SupportsSimulatorTracePointerOption.java | 12 ++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java b/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java index d201069e5..bd86a041e 100644 --- a/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java +++ b/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java @@ -45,18 +45,39 @@ default T showChromedriverLog() { * @param value Whether to forward chromedriver output to the Appium server log. * @return self instance for chaining. */ - default T setDhowChromedriverLog(boolean value) { + default T setShowChromedriverLog(boolean value) { return amend(SHOW_CHROMEDRIVER_LOG_OPTION, value); } + /** + * If set to true then all the output from chromedriver binary will be + * forwarded to the Appium server log. false by default. + * + * @deprecated Use {@link SupportsShowChromedriverLogOption#setShowChromedriverLog(boolean)} instead. + */ + @Deprecated + default T setDhowChromedriverLog(boolean value) { + return setShowChromedriverLog(value); + } + /** * Get whether to forward chromedriver output to the Appium server log. * * @return True or false. */ - default Optional doesDhowChromedriverLog() { + default Optional doesShowChromedriverLog() { return Optional.ofNullable( toSafeBoolean(getCapability(SHOW_CHROMEDRIVER_LOG_OPTION)) ); } + + /** + * Get whether to forward chromedriver output to the Appium server log. + * + * @deprecated Use {@link SupportsShowChromedriverLogOption#doesShowChromedriverLog()} (boolean)} instead. + */ + @Deprecated + default Optional doesDhowChromedriverLog() { + return doesShowChromedriverLog(); + } } diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java index 93611eec1..e5ceefef2 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java @@ -40,9 +40,19 @@ default T setCustomSSLCert(String cert) { /** * Get the SSL certificate content. * - * @return Certificate content. + * @deprecated use {@link SupportsCustomSslCertOption#getCustomSSLCert()} instead */ + @Deprecated default Optional setCustomSSLCert() { + return getCustomSSLCert(); + } + + /** + * Get the SSL certificate content. + * + * @return Certificate content. + */ + default Optional getCustomSSLCert() { return Optional.ofNullable((String) getCapability(CUSTOM_SSLCERT_OPTION)); } } diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java index a0341725a..3958de502 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java @@ -55,7 +55,17 @@ default T setSimulatorTracePointer(boolean value) { * * @return True or false. */ - default Optional doesSimulatorTracePointerd() { + default Optional doesSimulatorTracePointer() { return Optional.ofNullable(toSafeBoolean(getCapability(SIMULATOR_TRACE_POINTER_OPTION))); } + + /** + * Get whether to highlight pointer moves in the Simulator window. + * + * @deprecated use {@link SupportsSimulatorTracePointerOption#doesSimulatorTracePointer()} instead + */ + @Deprecated + default Optional doesSimulatorTracePointerd() { + return doesSimulatorTracePointer(); + } } From 8636dac0e850f1597c1b56246795958d12d19d23 Mon Sep 17 00:00:00 2001 From: Bipin Kumar Chaurasia Date: Wed, 2 Aug 2023 00:07:52 +0530 Subject: [PATCH 119/314] docs(java-client): Added correct url path for the latest appium documentation (#1974) --- src/main/java/io/appium/java_client/AppiumBy.java | 2 +- .../java_client/remote/IOSMobileCapabilityType.java | 4 ++-- .../appium/java_client/remote/MobileCapabilityType.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index 10a8c5b76..297e9a9b3 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -147,7 +147,7 @@ public static By custom(final String selector) { /** * This locator strategy is available only if OpenCV libraries and - * NodeJS bindings are installed on the server machine. + * Node.js bindings are installed on the server machine. * * @see * The documentation on Image Comparison Features diff --git a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java index 162353dc1..b8b517132 100644 --- a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java @@ -25,8 +25,8 @@ * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#ios-only *
* and
- * - * https://github.com/appium/appium-xcuitest-driver#desired-capabilities + * + * https://appium.github.io/appium-xcuitest-driver/latest/capabilities/ * * @deprecated This interface will be removed. Use Options instead. */ 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 eeaa44405..44cc27214 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -21,8 +21,8 @@ /** * The list of common capabilities.
* Read:
- * - * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#general-capabilities + * + * https://appium.io/docs/en/latest/guides/caps/#appium-capabilities * * @deprecated This interface will be removed. Use Options instead. */ @@ -117,8 +117,8 @@ public interface MobileCapabilityType extends CapabilityType { * (e.g., the start and end of each command, etc.). Defaults to {@code false}. * To enable, use {@code true}. The timings are then reported as {@code events} property on response * to querying the current session. See the - * - * event timing docs for the the structure of this response. + * + * event timing docs for the structure of this response. */ String EVENT_TIMINGS = "eventTimings"; From aa103a161b0a875f630f32adeb435b485ad67f59 Mon Sep 17 00:00:00 2001 From: Bipin Kumar Chaurasia Date: Wed, 2 Aug 2023 17:19:16 +0530 Subject: [PATCH 120/314] fix: Added fixes for No service provider found for `io.appium.java_client.events.api.Listener` (#1975) --- .../services/io.appium.java_client.events.api.Listener | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/test/resources/META-INF/services/io.appium.java_client.events.api.Listener diff --git a/src/test/resources/META-INF/services/io.appium.java_client.events.api.Listener b/src/test/resources/META-INF/services/io.appium.java_client.events.api.Listener deleted file mode 100644 index 6faceb0d0..000000000 --- a/src/test/resources/META-INF/services/io.appium.java_client.events.api.Listener +++ /dev/null @@ -1,10 +0,0 @@ -io.appium.java_client.events.listeners.AlertListener -io.appium.java_client.events.listeners.RotationListener -io.appium.java_client.events.listeners.WindowListener -io.appium.java_client.events.listeners.ContextListener -io.appium.java_client.events.listeners.ElementListener -io.appium.java_client.events.listeners.ExceptionListener -io.appium.java_client.events.listeners.JavaScriptListener -io.appium.java_client.events.listeners.NavigationListener -io.appium.java_client.events.listeners.SearchingListener -io.appium.java_client.events.listeners.AppiumListener From d415493b17894c6b11eb8420120c851395f349c8 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 18 Aug 2023 14:49:54 +0300 Subject: [PATCH 121/314] chore: Bump Gradle from `8.1` to `8.3` (#1982) --- build.gradle | 3 ++- gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 8 ++++++-- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 7a8607c08..f98fe8319 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ dependencies { implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.3') { exclude group: 'org.seleniumhq.selenium' @@ -189,7 +190,7 @@ signing { } wrapper { - gradleVersion = '8.1' + gradleVersion = '8.3' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch delta 37652 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 delta 36122 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 Ac-xW1# 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 Q>rd#Eb%7 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a21c6ebe2..d11cdd907 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb4..0adc8e1a5 100755 --- a/gradlew +++ b/gradlew @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. From 547e0630f184300bd5d39f28c4bb0fd5309d95f9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 18 Aug 2023 15:19:58 +0300 Subject: [PATCH 122/314] test: Run tests against latest Selenium release (#1978) --- build.gradle | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index f98fe8319..4c2e414b7 100644 --- a/build.gradle +++ b/build.gradle @@ -62,12 +62,11 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation ('org.seleniumhq.selenium:selenium-chrome-driver') { - version { - strictly "[${seleniumVersion}, 5.0)" - prefer "${seleniumVersion}" - } - } + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.11.0') + testImplementation 'org.seleniumhq.selenium:selenium-api' + testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' + testImplementation 'org.seleniumhq.selenium:selenium-support' + testImplementation 'org.seleniumhq.selenium:selenium-chrome-driver' testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } From 263fb8cba7bc4645a34fa7e50f6200d827170c5b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 18 Aug 2023 15:03:42 +0200 Subject: [PATCH 123/314] fix: Use weak references to elements inside of interceptor objects (#1981) --- .../pagefactory/AppiumElementLocator.java | 92 +++++++++++++------ .../AppiumElementLocatorFactory.java | 47 +++++++--- .../pagefactory/AppiumFieldDecorator.java | 83 ++++++++++++----- .../pagefactory/ElementInterceptor.java | 3 +- .../pagefactory/WidgetInterceptor.java | 30 +++--- .../pagefactory/WidgetListInterceptor.java | 34 +++++-- .../InterceptorOfAListOfElements.java | 5 +- .../InterceptorOfASingleElement.java | 18 +++- 8 files changed, 223 insertions(+), 89 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java index 5208add47..3aba5bcdc 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -27,9 +27,11 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.FluentWait; +import java.lang.ref.WeakReference; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -46,10 +48,36 @@ class AppiumElementLocator implements CacheableLocator { private final boolean shouldCache; private final By by; private final Duration duration; + private final WeakReference searchContextReference; private final SearchContext searchContext; + private WebElement cachedElement; private List cachedElementList; + /** + * Creates a new mobile element locator. It instantiates {@link WebElement} + * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation + * sets + * + * @param searchContextReference The context reference to use when finding the element + * @param by a By locator strategy + * @param shouldCache is the flag that signalizes that elements which + * are found once should be cached + * @param duration timeout parameter for the element to be found + */ + AppiumElementLocator( + WeakReference searchContextReference, + By by, + boolean shouldCache, + Duration duration + ) { + this.searchContextReference = searchContextReference; + this.searchContext = null; + this.shouldCache = shouldCache; + this.duration = duration; + this.by = by; + } + /** * Creates a new mobile element locator. It instantiates {@link WebElement} * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation @@ -61,15 +89,25 @@ class AppiumElementLocator implements CacheableLocator { * are found once should be cached * @param duration timeout parameter for the element to be found */ - - public AppiumElementLocator(SearchContext searchContext, By by, boolean shouldCache, - Duration duration) { + public AppiumElementLocator( + SearchContext searchContext, + By by, + boolean shouldCache, + Duration duration + ) { + this.searchContextReference = null; this.searchContext = searchContext; this.shouldCache = shouldCache; this.duration = duration; this.by = by; } + private Optional getSearchContext() { + return searchContext == null + ? Optional.ofNullable(searchContextReference).map(WeakReference::get) + : Optional.of(searchContext); + } + /** * This methods makes sets some settings of the {@link By} according to * the given instance of {@link SearchContext}. If there is some {@link ContentMappedBy} @@ -85,8 +123,7 @@ private static By getBy(By currentBy, SearchContext currentContent) { return currentBy; } - return ContentMappedBy.class.cast(currentBy) - .useContent(getCurrentContentType(currentContent)); + return ((ContentMappedBy) currentBy).useContent(getCurrentContentType(currentContent)); } private T waitFor(Supplier supplier) { @@ -98,8 +135,7 @@ private T waitFor(Supplier supplier) { return wait.until(function); } catch (TimeoutException e) { if (function.foundStaleElementReferenceException != null) { - throw StaleElementReferenceException - .class.cast(function.foundStaleElementReferenceException); + throw (StaleElementReferenceException) function.foundStaleElementReferenceException; } throw e; } @@ -113,10 +149,15 @@ public WebElement findElement() { return cachedElement; } + SearchContext searchContext = getSearchContext() + .orElseThrow(() -> new IllegalStateException( + String.format("The element %s is not locatable anymore " + + "because its context has been garbage collected", by) + )); + By bySearching = getBy(this.by, searchContext); try { - WebElement result = waitFor(() -> - searchContext.findElement(bySearching)); + WebElement result = waitFor(() -> searchContext.findElement(bySearching)); if (shouldCache) { cachedElement = result; } @@ -134,12 +175,17 @@ public List findElements() { return cachedElementList; } + SearchContext searchContext = getSearchContext() + .orElseThrow(() -> new IllegalStateException( + String.format("Elements %s are not locatable anymore " + + "because their context has been garbage collected", by) + )); + List result; try { result = waitFor(() -> { - List list = searchContext - .findElements(getBy(by, searchContext)); - return list.size() > 0 ? list : null; + List list = searchContext.findElements(getBy(by, searchContext)); + return list.isEmpty() ? null : list; }); } catch (TimeoutException | StaleElementReferenceException e) { result = new ArrayList<>(); @@ -171,30 +217,22 @@ public T apply(Supplier supplier) { return supplier.get(); } catch (Throwable e) { boolean isRootCauseStaleElementReferenceException = false; - Throwable shouldBeThrown; boolean isRootCauseInvalidSelector = isInvalidSelectorRootCause(e); - if (!isRootCauseInvalidSelector) { isRootCauseStaleElementReferenceException = isStaleElementReferenceException(e); } - if (isRootCauseStaleElementReferenceException) { foundStaleElementReferenceException = extractReadableException(e); } + if (isRootCauseInvalidSelector || isRootCauseStaleElementReferenceException) { + return null; + } - if (!isRootCauseInvalidSelector & !isRootCauseStaleElementReferenceException) { - shouldBeThrown = extractReadableException(e); - if (shouldBeThrown != null) { - if (NoSuchElementException.class.equals(shouldBeThrown.getClass())) { - throw NoSuchElementException.class.cast(shouldBeThrown); - } else { - throw new WebDriverException(shouldBeThrown); - } - } else { - throw new WebDriverException(e); - } + Throwable excToThrow = extractReadableException(e); + if (excToThrow instanceof WebDriverException) { + throw (WebDriverException) excToThrow; } else { - return null; + throw new WebDriverException(excToThrow); } } } diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 0fc8e3d38..787e34e9e 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -23,6 +23,7 @@ import org.openqa.selenium.SearchContext; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.time.Duration; @@ -32,6 +33,7 @@ public class AppiumElementLocatorFactory implements CacheableElementLocatorFactory { private final SearchContext searchContext; + private final WeakReference searchContextReference; private final Duration duration; private final AppiumByBuilder builder; @@ -39,22 +41,46 @@ public class AppiumElementLocatorFactory implements CacheableElementLocatorFacto * Creates a new mobile element locator factory. * * @param searchContext The context to use when finding the element - * @param duration timeout parameters for the elements to be found - * @param builder is handler of Appium-specific page object annotations + * @param duration timeout parameters for the elements to be found + * @param builder is handler of Appium-specific page object annotations */ - public AppiumElementLocatorFactory(SearchContext searchContext, Duration duration, - AppiumByBuilder builder) { + public AppiumElementLocatorFactory( + SearchContext searchContext, + Duration duration, + AppiumByBuilder builder + ) { this.searchContext = searchContext; + this.searchContextReference = null; this.duration = duration; this.builder = builder; } - public @Nullable CacheableLocator createLocator(Field field) { - return this.createLocator((AnnotatedElement) field); + /** + * Creates a new mobile element locator factory. + * + * @param searchContextReference The context reference to use when finding the element + * @param duration timeout parameters for the elements to be found + * @param builder is handler of Appium-specific page object annotations + */ + AppiumElementLocatorFactory( + WeakReference searchContextReference, + Duration duration, + AppiumByBuilder builder + ) { + this.searchContextReference = searchContextReference; + this.searchContext = null; + this.duration = duration; + this.builder = builder; } + @Nullable @Override + public CacheableLocator createLocator(Field field) { + return this.createLocator((AnnotatedElement) field); + } + @Nullable + @Override public CacheableLocator createLocator(AnnotatedElement annotatedElement) { Duration customDuration; if (annotatedElement.isAnnotationPresent(WithTimeout.class)) { @@ -63,14 +89,13 @@ public CacheableLocator createLocator(AnnotatedElement annotatedElement) { } else { customDuration = duration; } - builder.setAnnotated(annotatedElement); By byResult = builder.buildBy(); - return ofNullable(byResult) - .map(by -> new AppiumElementLocator(searchContext, by, builder.isLookupCached(), customDuration)) + .map(by -> searchContextReference != null + ? new AppiumElementLocator(searchContextReference, by, builder.isLookupCached(), customDuration) + : new AppiumElementLocator(searchContext, by, builder.isLookupCached(), customDuration) + ) .orElse(null); } - - } 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 b1119f34e..c2f11f473 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -30,9 +30,11 @@ import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator; import org.openqa.selenium.support.pagefactory.ElementLocator; +import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; import org.openqa.selenium.support.pagefactory.FieldDecorator; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; @@ -60,11 +62,13 @@ */ public class AppiumFieldDecorator implements FieldDecorator { - private static final List> availableElementClasses = ImmutableList.of(WebElement.class, - RemoteWebElement.class); + private static final List> availableElementClasses = ImmutableList.of( + WebElement.class, + RemoteWebElement.class + ); public static final Duration DEFAULT_WAITING_TIMEOUT = ofSeconds(1); - private final WebDriver webDriver; - private final DefaultFieldDecorator defaultElementFieldDecoracor; + private final WeakReference webDriverReference; + private final DefaultFieldDecorator defaultElementFieldDecorator; private final AppiumElementLocatorFactory widgetLocatorFactory; private final String platform; private final String automation; @@ -79,10 +83,45 @@ public class AppiumFieldDecorator implements FieldDecorator { * @param duration is a desired duration of the waiting for an element presence. */ public AppiumFieldDecorator(SearchContext context, Duration duration) { - this.webDriver = unpackWebDriverFromSearchContext(context); + WebDriver wd = unpackWebDriverFromSearchContext(context); + this.webDriverReference = wd == null ? null : new WeakReference<>(wd); + if (wd instanceof HasCapabilities) { + Capabilities caps = ((HasCapabilities) wd).getCapabilities(); + 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; + } + + this.duration = duration; + + defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( + context, duration, new DefaultElementByBuilder(platform, automation) + )); + + widgetLocatorFactory = new AppiumElementLocatorFactory( + context, duration, new WidgetByBuilder(platform, automation) + ); + } + + public AppiumFieldDecorator(SearchContext context) { + this(context, DEFAULT_WAITING_TIMEOUT); + } - if (this.webDriver instanceof HasCapabilities) { - Capabilities caps = ((HasCapabilities) this.webDriver).getCapabilities(); + /** + * Creates field decorator based on {@link SearchContext} and timeout {@code duration}. + * + * @param contextReference reference to {@link SearchContext} + * It may be the instance of {@link WebDriver} or {@link WebElement} or + * {@link Widget} or some other user's extension/implementation. + * @param duration is a desired duration of the waiting for an element presence. + */ + AppiumFieldDecorator(WeakReference contextReference, Duration duration) { + WebDriver wd = unpackWebDriverFromSearchContext(contextReference.get()); + this.webDriverReference = wd == null ? null : new WeakReference<>(wd); + if (wd instanceof HasCapabilities) { + Capabilities caps = ((HasCapabilities) wd).getCapabilities(); this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); this.automation = CapabilityHelpers.getCapability(caps, MobileCapabilityType.AUTOMATION_NAME, String.class); } else { @@ -92,9 +131,17 @@ public AppiumFieldDecorator(SearchContext context, Duration duration) { this.duration = duration; - defaultElementFieldDecoracor = new DefaultFieldDecorator( - new AppiumElementLocatorFactory(context, duration, new DefaultElementByBuilder(platform, automation)) - ) { + defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( + contextReference, duration, new DefaultElementByBuilder(platform, automation) + )); + + widgetLocatorFactory = new AppiumElementLocatorFactory( + contextReference, duration, new WidgetByBuilder(platform, automation) + ); + } + + private DefaultFieldDecorator createFieldDecorator(ElementLocatorFactory factory) { + return new DefaultFieldDecorator(factory) { @Override protected WebElement proxyForLocator(ClassLoader ignored, ElementLocator locator) { return proxyForAnElement(locator); @@ -126,14 +173,6 @@ protected boolean isDecoratableList(Field field) { .anyMatch((webElClass) -> webElClass.equals(listType) || bounds.contains(webElClass)); } }; - - widgetLocatorFactory = new AppiumElementLocatorFactory( - context, duration, new WidgetByBuilder(platform, automation) - ); - } - - public AppiumFieldDecorator(SearchContext context) { - this(context, DEFAULT_WAITING_TIMEOUT); } /** @@ -144,7 +183,7 @@ public AppiumFieldDecorator(SearchContext context) { * @return a field value or null. */ public Object decorate(ClassLoader ignored, Field field) { - Object result = defaultElementFieldDecoracor.decorate(ignored, field); + Object result = defaultElementFieldDecorator.decorate(ignored, field); return result == null ? decorateWidget(field) : result; } @@ -191,7 +230,7 @@ private Object decorateWidget(Field field) { if (isAlist) { return getEnhancedProxy( ArrayList.class, - new WidgetListInterceptor(locator, webDriver, map, widgetType, duration) + new WidgetListInterceptor(locator, webDriverReference, map, widgetType, duration) ); } @@ -200,12 +239,12 @@ private Object decorateWidget(Field field) { widgetType, new Class[]{constructor.getParameterTypes()[0]}, new Object[]{proxyForAnElement(locator)}, - new WidgetInterceptor(locator, webDriver, null, map, duration) + new WidgetInterceptor(locator, webDriverReference, null, map, duration) ); } private WebElement proxyForAnElement(ElementLocator locator) { - ElementInterceptor elementInterceptor = new ElementInterceptor(locator, webDriver); + ElementInterceptor elementInterceptor = new ElementInterceptor(locator, webDriverReference); return getEnhancedProxy(RemoteWebElement.class, elementInterceptor); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java index 5047fcc11..82b61990b 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java @@ -21,6 +21,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; +import java.lang.ref.WeakReference; import java.lang.reflect.Method; import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; @@ -30,7 +31,7 @@ */ public class ElementInterceptor extends InterceptorOfASingleElement { - public ElementInterceptor(ElementLocator locator, WebDriver driver) { + public ElementInterceptor(ElementLocator locator, WeakReference driver) { super(locator, driver); } diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java index 9a9d1133d..d00c6e3c9 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java @@ -24,6 +24,7 @@ import org.openqa.selenium.support.PageFactory; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -34,27 +35,29 @@ import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType; +import static java.util.Optional.ofNullable; public class WidgetInterceptor extends InterceptorOfASingleElement { private final Map> instantiationMap; private final Map cachedInstances = new HashMap<>(); private final Duration duration; - private WebElement cachedElement; + private WeakReference cachedElementReference; /** * Proxy interceptor class for widgets. */ public WidgetInterceptor( + @Nullable CacheableLocator locator, - WebDriver driver, + WeakReference driverReference, @Nullable - WebElement cachedElement, + WeakReference cachedElementReference, Map> instantiationMap, Duration duration ) { - super(locator, driver); - this.cachedElement = cachedElement; + super(locator, driverReference); + this.cachedElementReference = cachedElementReference; this.instantiationMap = instantiationMap; this.duration = duration; } @@ -62,24 +65,24 @@ public WidgetInterceptor( @Override protected Object getObject(WebElement element, Method method, Object[] args) throws Throwable { ContentType type = getCurrentContentType(element); - if (cachedElement == null + WebElement cachedElement = cachedElementReference == null ? null : cachedElementReference.get(); + if (cachedElement == null || !cachedInstances.containsKey(type) || (locator != null && !((CacheableLocator) locator).isLookUpCached()) - || cachedInstances.isEmpty() ) { - cachedElement = element; + cachedElementReference = new WeakReference<>(element); Constructor constructor = instantiationMap.get(type); Class clazz = constructor.getDeclaringClass(); if (Modifier.isAbstract(clazz.getModifiers())) { throw new InstantiationException( - String.format("%s is abstract so it cannot be instantiated", clazz.getName()) + String.format("%s is abstract so it cannot be instantiated", clazz.getName()) ); } - Widget widget = constructor.newInstance(cachedElement); + Widget widget = constructor.newInstance(element); cachedInstances.put(type, widget); - PageFactory.initElements(new AppiumFieldDecorator(widget, duration), widget); + PageFactory.initElements(new AppiumFieldDecorator(new WeakReference<>(widget), duration), widget); } try { method.setAccessible(true); @@ -91,8 +94,9 @@ protected Object getObject(WebElement element, Method method, Object[] args) thr @Override public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { - return locator == null - ? getObject(cachedElement, method, args) + WebElement element = ofNullable(cachedElementReference).map(WeakReference::get).orElse(null); + return locator == null && element != null + ? getObject(element, method, args) : super.call(obj, method, args, original); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java index 136ce8e4d..031a0e624 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java @@ -22,12 +22,16 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException; import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; @@ -39,15 +43,20 @@ public class WidgetListInterceptor extends InterceptorOfAListOfElements { private final List cachedWidgets = new ArrayList<>(); private final Class declaredType; private final Duration duration; - private final WebDriver driver; - private List cachedElements; + private final WeakReference driver; + private final List> cachedElementReferences = new ArrayList<>(); /** * Proxy interceptor class for lists of widgets. */ - public WidgetListInterceptor(CacheableLocator locator, WebDriver driver, - Map> instantiationMap, - Class declaredType, Duration duration) { + public WidgetListInterceptor( + @Nullable + CacheableLocator locator, + WeakReference driver, + Map> instantiationMap, + Class declaredType, + Duration duration + ) { super(locator); this.instantiationMap = instantiationMap; this.declaredType = declaredType; @@ -57,20 +66,27 @@ public WidgetListInterceptor(CacheableLocator locator, WebDriver driver, @Override protected Object getObject(List elements, Method method, Object[] args) throws Throwable { - if (cachedElements == null || (locator != null && !((CacheableLocator) locator).isLookUpCached())) { - cachedElements = elements; + List cachedElements = cachedElementReferences.stream() + .map(WeakReference::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (cachedElements.size() != cachedWidgets.size() + || (locator != null && !((CacheableLocator) locator).isLookUpCached())) { cachedWidgets.clear(); + cachedElementReferences.clear(); ContentType type = null; - for (WebElement element : cachedElements) { + for (WebElement element : elements) { type = ofNullable(type).orElseGet(() -> getCurrentContentType(element)); Class[] params = new Class[] {instantiationMap.get(type).getParameterTypes()[0]}; + WeakReference elementReference = new WeakReference<>(element); cachedWidgets.add( getEnhancedProxy( declaredType, params, new Object[] {element}, - new WidgetInterceptor(null, driver, element, instantiationMap, duration) + new WidgetInterceptor(null, driver, elementReference, instantiationMap, duration) ) ); + cachedElementReferences.add(elementReference); } } try { diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java index d276ce616..62e1442aa 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java @@ -20,6 +20,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; +import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -28,7 +29,7 @@ public abstract class InterceptorOfAListOfElements implements MethodCallListener { protected final ElementLocator locator; - public InterceptorOfAListOfElements(ElementLocator locator) { + public InterceptorOfAListOfElements(@Nullable ElementLocator locator) { this.locator = locator; } @@ -38,7 +39,7 @@ protected abstract Object getObject( @Override public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { - if (Object.class.equals(method.getDeclaringClass())) { + if (locator == null || Object.class.equals(method.getDeclaringClass())) { return original.call(); } diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java index 7ac12b3d6..7eea82233 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java @@ -22,22 +22,32 @@ import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.support.pagefactory.ElementLocator; +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.util.concurrent.Callable; public abstract class InterceptorOfASingleElement implements MethodCallListener { protected final ElementLocator locator; - protected final WebDriver driver; + private final WeakReference driverReference; - public InterceptorOfASingleElement(ElementLocator locator, WebDriver driver) { + public InterceptorOfASingleElement( + @Nullable + ElementLocator locator, + WeakReference driverReference + ) { this.locator = locator; - this.driver = driver; + this.driverReference = driverReference; } protected abstract Object getObject(WebElement element, Method method, Object[] args) throws Throwable; @Override public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { + if (locator == null) { + return original.call(); + } + if (method.getName().equals("toString") && args.length == 0) { return locator.toString(); } @@ -48,7 +58,7 @@ public Object call(Object obj, Method method, Object[] args, Callable origina if (WrapsDriver.class.isAssignableFrom(method.getDeclaringClass()) && method.getName().equals("getWrappedDriver")) { - return driver; + return driverReference.get(); } WebElement realElement = locator.findElement(); From 16763424b5f8f729739ce8a2bf8c8eed9c6177d6 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 18 Aug 2023 17:17:51 +0200 Subject: [PATCH 124/314] ci: Increase the server startup timeout (#1983) --- src/test/java/io/appium/java_client/ios/BaseIOSTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2790465a6..fe5764e7b 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -33,8 +33,8 @@ public class BaseIOSTest { public static final String PLATFORM_VERSION = System.getenv("IOS_PLATFORM_VERSION") != null ? System.getenv("IOS_PLATFORM_VERSION") : "14.5"; - public static final Duration WDA_LAUNCH_TIMEOUT = Duration.ofSeconds(240); - public static final Duration SERVER_START_TIMEOUT = Duration.ofSeconds(40); + public static final Duration WDA_LAUNCH_TIMEOUT = Duration.ofMinutes(4); + public static final Duration SERVER_START_TIMEOUT = Duration.ofMinutes(3); /** * Starts a local server. From 09d3ac0f8cff055eccf35b65fd1c5b45e1a66996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:14:48 +0300 Subject: [PATCH 125/314] build(deps): Bump org.owasp.dependencycheck from 8.3.1 to 8.4.0 (#1984) Bumps org.owasp.dependencycheck from 8.3.1 to 8.4.0. --- 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 4c2e414b7..674612f83 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.3.1' + id 'org.owasp.dependencycheck' version '8.4.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 2217c705a01ff9729f6305caddb2187f18d681c9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 22 Aug 2023 11:53:05 +0300 Subject: [PATCH 126/314] docs: Add Selenium `4.11.0` to compatibility matrix (#1986) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a7c669a4..560610b0d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ dependencies { ### Compatibility Matrix | Appium Java Client | Selenium client | |--------------------|---------------------------------------------| -| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0` | +| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0` | | `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` | | `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` | | `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` | From d3b304660983ff27ad469f3d3e3c926e33d26c96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 11:55:24 +0300 Subject: [PATCH 127/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#1987) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.3.3 to 5.5.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-5.3.3...webdrivermanager-5.5.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager 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 674612f83..7ff7df443 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.3.3') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.0') { exclude group: 'org.seleniumhq.selenium' } testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.11.0') From 149fd0ce75fe7ae5b8a052970109a3f3d4e81dd1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:41:13 +0300 Subject: [PATCH 128/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#1988) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.5.0 to 5.5.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.5.0...webdrivermanager-5.5.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 7ff7df443..17880238c 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.0') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.2') { exclude group: 'org.seleniumhq.selenium' } testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.11.0') From 8af67b4373be70f50bedaddcf525f4f73e29e2da Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 31 Aug 2023 19:43:48 +0200 Subject: [PATCH 129/314] tests: Use server releases from the main branch for testing (#1994) --- .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 872468fad..a76a31d52 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -63,7 +63,7 @@ jobs: - name: Install Appium if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' - run: npm install -g appium@next + run: npm install --location=global appium - name: Install UIA2 driver if: matrix.e2e-tests == 'android' From 85e60962ff37c2d290e1cd4f3aa7df0a0d5176a9 Mon Sep 17 00:00:00 2001 From: AlessandroMiccoli <57630383+AlessandroMiccoli@users.noreply.github.com> Date: Fri, 1 Sep 2023 06:23:46 +0100 Subject: [PATCH 130/314] chore: patch AutomationName with Chromium (#1993) --- src/main/java/io/appium/java_client/remote/AutomationName.java | 2 ++ 1 file changed, 2 insertions(+) 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 e38474dbc..4ee53ea1a 100644 --- a/src/main/java/io/appium/java_client/remote/AutomationName.java +++ b/src/main/java/io/appium/java_client/remote/AutomationName.java @@ -34,6 +34,8 @@ public interface AutomationName { String SAFARI = "Safari"; // https://github.com/appium/appium-geckodriver String GECKO = "Gecko"; + // https://github.com/appium/appium-chromium-driver + String CHROMIUM = "Chromium"; // Third-party drivers // https://github.com/YOU-i-Labs/appium-youiengine-driver From 0e3238188dd6f9e0d6ffa057a9288705f1229e5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 17:38:47 +0300 Subject: [PATCH 131/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#1996) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.5.2 to 5.5.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.5.2...webdrivermanager-5.5.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 17880238c..8a91c7d73 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.2') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.11.0') From a52aa43d376c2e531ebc8c310e3f0ed662c18ede Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 17:39:09 +0300 Subject: [PATCH 132/314] build(deps): Bump org.seleniumhq.selenium:selenium-bom (#1998) Bumps [org.seleniumhq.selenium:selenium-bom](https://github.com/SeleniumHQ/selenium) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.11.0...selenium-4.12.0) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-bom 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 8a91c7d73..0bc9176ca 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.11.0') + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.12.0') testImplementation 'org.seleniumhq.selenium:selenium-api' testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' testImplementation 'org.seleniumhq.selenium:selenium-support' From 892b3ef20ff74a06c635c7e46f3d3f4924e2efc5 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 4 Sep 2023 17:53:11 +0300 Subject: [PATCH 133/314] docs: Add Selenium `4.12.0` to compatibility matrix (#1999) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 560610b0d..d15590125 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ dependencies { ### Compatibility Matrix | Appium Java Client | Selenium client | |--------------------|---------------------------------------------| -| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0` | +| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0` | | `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` | | `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` | | `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` | From 7975e57055026f6008f35eb4739b861dd723d496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:12:07 +0300 Subject: [PATCH 134/314] build(deps): Bump slf4jVersion from 2.0.7 to 2.0.9 (#1997) Bumps `slf4jVersion` from 2.0.7 to 2.0.9. Updates `org.slf4j:slf4j-api` from 2.0.7 to 2.0.9 Updates `org.slf4j:slf4j-simple` from 2.0.7 to 2.0.9 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 0bc9176ca..dcafd5e65 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.7' + slf4jVersion = '2.0.9' } dependencies { From 3e0daa2c403a9b6ad478ac72cc171bb2083e298f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:28:14 +0300 Subject: [PATCH 135/314] build(deps): Bump actions/checkout from 3 to 4 (#2000) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout 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> --- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/gradle.yml | 2 +- .github/workflows/publish.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index a0d1170f2..f6851f4b1 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -23,5 +23,5 @@ jobs: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a76a31d52..d427bec19 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -43,7 +43,7 @@ jobs: name: JDK ${{ matrix.java }} - ${{ matrix.platform }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8196bf5ff..7e95af207 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v3 with: From 18c61f271647922365d1931edcb6a41533a50c0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 11:40:50 +0300 Subject: [PATCH 136/314] build(deps): Bump org.seleniumhq.selenium:selenium-bom (#2001) Bumps [org.seleniumhq.selenium:selenium-bom](https://github.com/SeleniumHQ/selenium) from 4.12.0 to 4.12.1. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/commits) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-bom 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 dcafd5e65..ea1602d18 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.12.0') + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.12.1') testImplementation 'org.seleniumhq.selenium:selenium-api' testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' testImplementation 'org.seleniumhq.selenium:selenium-support' From 2fd8c0cadce1a97a8a73e6ede893499f3d80414b Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 5 Sep 2023 23:22:10 +0300 Subject: [PATCH 137/314] docs: Add Selenium 4.12.1 to compatibility matrix (#2002) --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d15590125..7fd7a96ee 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,12 @@ dependencies { ``` ### Compatibility Matrix -| Appium Java Client | Selenium client | -|--------------------|---------------------------------------------| -| `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0` | -| `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` | -| `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` | -| `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` | + Appium Java Client | Selenium client +--------------------|----------------- + `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` + `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` + `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` + `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` #### Why is it so complicated? From d8cf93c79df1b33a3aef3ac98ef8ba09733715ca Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 7 Sep 2023 18:33:02 +0200 Subject: [PATCH 138/314] tests: Remove obsolete API calls from tests (#2006) --- .../AndroidAbilityToUseSupplierTest.java | 7 +- .../android/AndroidActivityTest.java | 74 ------------------ .../android/AndroidContextTest.java | 3 +- .../android/AndroidDriverTest.java | 16 ++-- .../android/AndroidElementTest.java | 9 +-- .../android/AndroidFunctionTest.java | 3 +- .../android/AndroidScreenRecordTest.java | 3 +- .../android/AndroidSearchingTest.java | 3 +- .../java_client/android/AndroidTouchTest.java | 34 ++++----- .../java_client/android/BaseAndroidTest.java | 10 +++ .../java_client/android/ClipboardTest.java | 4 +- .../java_client/android/IntentTest.java | 75 ------------------- .../java_client/android/KeyCodeTest.java | 3 +- .../android/OpenNotificationsTest.java | 5 +- .../java_client/android/UIAutomator2Test.java | 3 +- .../appium/java_client/ios/IOSDriverTest.java | 23 ++---- 16 files changed, 55 insertions(+), 220 deletions(-) delete mode 100644 src/test/java/io/appium/java_client/android/AndroidActivityTest.java delete mode 100644 src/test/java/io/appium/java_client/android/IntentTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java index 1a0cd129e..21389d54a 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import io.appium.java_client.functions.ActionSupplier; import io.appium.java_client.touch.offset.ElementOption; @@ -46,8 +47,7 @@ public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { .release(); @Test public void horizontalSwipingWithSupplier() { - Activity activity = new Activity("io.appium.android.apis", ".view.Gallery1"); - driver.startActivity(activity); + startActivity(".view.Gallery1"); WebElement gallery = driver.findElement(By.id("io.appium.android.apis:id/gallery")); List images = gallery.findElements(AppiumBy.className("android.widget.ImageView")); int originalImageCount = images.size(); @@ -59,7 +59,8 @@ public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { } @Test public void verticalSwipingWithSupplier() throws Exception { - driver.resetApp(); + driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); driver.findElement(AppiumBy.accessibilityId("Views")).click(); Point originalLocation = driver.findElement(AppiumBy.accessibilityId("Gallery")).getLocation(); diff --git a/src/test/java/io/appium/java_client/android/AndroidActivityTest.java b/src/test/java/io/appium/java_client/android/AndroidActivityTest.java deleted file mode 100644 index 149a393c4..000000000 --- a/src/test/java/io/appium/java_client/android/AndroidActivityTest.java +++ /dev/null @@ -1,74 +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.android; - -import io.appium.java_client.android.nativekey.AndroidKey; -import io.appium.java_client.android.nativekey.KeyEvent; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class AndroidActivityTest extends BaseAndroidTest { - - @BeforeEach public void setUp() { - Activity activity = new Activity("io.appium.android.apis", ".ApiDemos"); - driver.startActivity(activity); - } - - @Test public void startActivityInThisAppTestCase() { - Activity activity = new Activity("io.appium.android.apis", - ".accessibility.AccessibilityNodeProviderActivity"); - driver.startActivity(activity); - assertEquals(driver.currentActivity(), - ".accessibility.AccessibilityNodeProviderActivity"); - } - - @Test public void startActivityWithWaitingAppTestCase() { - final Activity activity = new Activity("io.appium.android.apis", - ".accessibility.AccessibilityNodeProviderActivity") - .setAppWaitPackage("io.appium.android.apis") - .setAppWaitActivity(".accessibility.AccessibilityNodeProviderActivity"); - driver.startActivity(activity); - assertEquals(driver.currentActivity(), - ".accessibility.AccessibilityNodeProviderActivity"); - } - - @Test public void startActivityInNewAppTestCase() { - Activity activity = new Activity("com.android.settings", ".Settings"); - driver.startActivity(activity); - assertEquals(driver.currentActivity(), ".Settings"); - driver.pressKey(new KeyEvent(AndroidKey.BACK)); - assertEquals(driver.currentActivity(), ".ApiDemos"); - } - - @Test public void startActivityInNewAppTestCaseWithoutClosingApp() { - Activity activity = new Activity("io.appium.android.apis", - ".accessibility.AccessibilityNodeProviderActivity"); - driver.startActivity(activity); - assertEquals(driver.currentActivity(), ".accessibility.AccessibilityNodeProviderActivity"); - - Activity newActivity = new Activity("com.android.settings", ".Settings") - .setAppWaitPackage("com.android.settings") - .setAppWaitActivity(".Settings") - .setStopApp(false); - driver.startActivity(newActivity); - assertEquals(driver.currentActivity(), ".Settings"); - driver.pressKey(new KeyEvent(AndroidKey.BACK)); - assertEquals(driver.currentActivity(), ".accessibility.AccessibilityNodeProviderActivity"); - } -} diff --git a/src/test/java/io/appium/java_client/android/AndroidContextTest.java b/src/test/java/io/appium/java_client/android/AndroidContextTest.java index 53a90f1dd..fdc47664b 100644 --- a/src/test/java/io/appium/java_client/android/AndroidContextTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidContextTest.java @@ -26,8 +26,7 @@ public class AndroidContextTest extends BaseAndroidTest { @BeforeAll public static void beforeClass2() throws Exception { - Activity activity = new Activity("io.appium.android.apis", ".view.WebView1"); - driver.startActivity(activity); + startActivity(".view.WebView1"); Thread.sleep(20000); } diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index 246951d8a..45f048272 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; @@ -148,8 +149,8 @@ public void isAppNotInstalledTest() { @Test public void closeAppTest() { - driver.closeApp(); - driver.launchApp(); + driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); assertEquals(".ApiDemos", driver.currentActivity()); } @@ -241,11 +242,6 @@ public void pullFileTest() { assert (data.length > 0); } - @Test - public void resetTest() { - driver.resetApp(); - } - @Test public void deviceDetailsAndKeyboardTest() { assertFalse(driver.isKeyboardShown()); @@ -255,7 +251,7 @@ public void deviceDetailsAndKeyboardTest() { @Test public void getSupportedPerformanceDataTypesTest() { - driver.startActivity(new Activity(APP_ID, ".ApiDemos")); + startActivity(".ApiDemos"); List dataTypes = new ArrayList<>(); dataTypes.add("cpuinfo"); @@ -270,13 +266,11 @@ public void getSupportedPerformanceDataTypesTest() { for (int i = 0; i < supportedPerformanceDataTypes.size(); ++i) { assertEquals(dataTypes.get(i), supportedPerformanceDataTypes.get(i)); } - - } @Test public void getPerformanceDataTest() { - driver.startActivity(new Activity(APP_ID, ".ApiDemos")); + startActivity(".ApiDemos"); List supportedPerformanceDataTypes = driver.getSupportedPerformanceDataTypes(); diff --git a/src/test/java/io/appium/java_client/android/AndroidElementTest.java b/src/test/java/io/appium/java_client/android/AndroidElementTest.java index acb4d4dd2..44c8473d6 100644 --- a/src/test/java/io/appium/java_client/android/AndroidElementTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidElementTest.java @@ -30,8 +30,7 @@ public class AndroidElementTest extends BaseAndroidTest { @BeforeEach public void setup() { - Activity activity = new Activity("io.appium.android.apis", ".ApiDemos"); - driver.startActivity(activity); + startActivity(".ApiDemos"); } @@ -57,8 +56,7 @@ public class AndroidElementTest extends BaseAndroidTest { @Test public void replaceValueTest() { String originalValue = "original value"; - Activity activity = new Activity("io.appium.android.apis", ".view.Controls1"); - driver.startActivity(activity); + startActivity(".view.Controls1"); WebElement editElement = driver .findElement(AppiumBy.androidUIAutomator("resourceId(\"io.appium.android.apis:id/edit\")")); editElement.sendKeys(originalValue); @@ -81,8 +79,7 @@ public class AndroidElementTest extends BaseAndroidTest { @Test public void setValueTest() { String value = "new value"; - Activity activity = new Activity("io.appium.android.apis", ".view.Controls1"); - driver.startActivity(activity); + startActivity(".view.Controls1"); WebElement editElement = driver .findElement(AppiumBy.androidUIAutomator("resourceId(\"io.appium.android.apis:id/edit\")")); editElement.sendKeys(value); diff --git a/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java b/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java index 79a0d8870..79d327ae1 100644 --- a/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java @@ -68,8 +68,7 @@ public class AndroidFunctionTest extends BaseAndroidTest { @BeforeAll public static void startWebViewActivity() { if (driver != null) { - Activity activity = new Activity("io.appium.android.apis", ".view.WebView1"); - driver.startActivity(activity); + startActivity(".view.WebView1"); } } diff --git a/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java b/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java index b0ec3a1e7..b9abd9ff6 100644 --- a/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java @@ -15,8 +15,7 @@ public class AndroidScreenRecordTest extends BaseAndroidTest { @BeforeEach public void setUp() { - Activity activity = new Activity("io.appium.android.apis", ".ApiDemos"); - driver.startActivity(activity); + startActivity(".ApiDemos"); } @Test diff --git a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java index c9b06c49f..64390962d 100644 --- a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java @@ -30,8 +30,7 @@ public class AndroidSearchingTest extends BaseAndroidTest { @BeforeEach public void setup() { - Activity activity = new Activity("io.appium.android.apis", ".ApiDemos"); - driver.startActivity(activity); + startActivity(".ApiDemos"); } @Test public void findByAccessibilityIdTest() { diff --git a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java index f3c9c0394..84cb15753 100644 --- a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; @@ -25,12 +26,12 @@ public class AndroidTouchTest extends BaseAndroidTest { @BeforeEach public void setUp() { - driver.resetApp(); + driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); } @Test public void dragNDropByElementTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.DragAndDropDemo"); - driver.startActivity(activity); + startActivity(".view.DragAndDropDemo"); WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); @@ -46,8 +47,7 @@ public void setUp() { } @Test public void dragNDropByElementAndDurationTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.DragAndDropDemo"); - driver.startActivity(activity); + startActivity(".view.DragAndDropDemo"); WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); @@ -65,8 +65,7 @@ public void setUp() { } @Test public void dragNDropByCoordinatesTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.DragAndDropDemo"); - driver.startActivity(activity); + startActivity(".view.DragAndDropDemo"); WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); @@ -85,8 +84,7 @@ public void setUp() { } @Test public void dragNDropByCoordinatesAndDurationTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.DragAndDropDemo"); - driver.startActivity(activity); + startActivity(".view.DragAndDropDemo"); WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); @@ -107,8 +105,7 @@ public void setUp() { } @Test public void pressByCoordinatesTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.Buttons1"); - driver.startActivity(activity); + startActivity(".view.Buttons1"); Point point = driver.findElement(By.id("io.appium.android.apis:id/button_toggle")).getLocation(); new TouchAction(driver) .press(point(point.x + 20, point.y + 30)) @@ -119,8 +116,7 @@ public void setUp() { } @Test public void pressByElementTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.Buttons1"); - driver.startActivity(activity); + startActivity(".view.Buttons1"); new TouchAction(driver) .press(element(driver.findElement(By.id("io.appium.android.apis:id/button_toggle")))) .waitAction(waitOptions(ofSeconds(1))) @@ -130,8 +126,7 @@ public void setUp() { } @Test public void tapActionTestByElement() throws Exception { - Activity activity = new Activity("io.appium.android.apis", ".view.ChronometerDemo"); - driver.startActivity(activity); + startActivity(".view.ChronometerDemo"); WebElement chronometer = driver.findElement(By.id("io.appium.android.apis:id/chronometer")); TouchAction startStop = new TouchAction(driver) @@ -148,8 +143,7 @@ public void setUp() { } @Test public void tapActionTestByCoordinates() throws Exception { - Activity activity = new Activity("io.appium.android.apis", ".view.ChronometerDemo"); - driver.startActivity(activity); + startActivity(".view.ChronometerDemo"); WebElement chronometer = driver.findElement(By.id("io.appium.android.apis:id/chronometer")); Point center1 = getCenter(driver.findElement(By.id("io.appium.android.apis:id/start"))); @@ -166,8 +160,7 @@ public void setUp() { } @Test public void horizontalSwipingTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.Gallery1"); - driver.startActivity(activity); + startActivity(".view.Gallery1"); WebElement gallery = driver.findElement(By.id("io.appium.android.apis:id/gallery")); List images = gallery.findElements(AppiumBy.className("android.widget.ImageView")); @@ -186,8 +179,7 @@ public void setUp() { } @Test public void multiTouchTest() { - Activity activity = new Activity("io.appium.android.apis", ".view.Buttons1"); - driver.startActivity(activity); + startActivity(".view.Buttons1"); TouchAction press = new TouchAction(driver) .press(element(driver.findElement(By.id("io.appium.android.apis:id/button_toggle")))) .waitAction(waitOptions(ofSeconds(1))) 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 d5eb6840f..28c1c95f4 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; @@ -59,4 +60,13 @@ public class BaseAndroidTest { service.stop(); } } + + public static void startActivity(String name) { + driver.executeScript( + "mobile: startActivity", + ImmutableMap.of( + "component", String.format("%s/%s", APP_ID, name) + ) + ); + } } diff --git a/src/test/java/io/appium/java_client/android/ClipboardTest.java b/src/test/java/io/appium/java_client/android/ClipboardTest.java index 353d9a579..7b576bcc2 100644 --- a/src/test/java/io/appium/java_client/android/ClipboardTest.java +++ b/src/test/java/io/appium/java_client/android/ClipboardTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,7 +25,8 @@ public class ClipboardTest extends BaseAndroidTest { @BeforeEach public void setUp() { - driver.resetApp(); + driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); } @Test public void verifySetAndGetClipboardText() { diff --git a/src/test/java/io/appium/java_client/android/IntentTest.java b/src/test/java/io/appium/java_client/android/IntentTest.java deleted file mode 100644 index ba4888821..000000000 --- a/src/test/java/io/appium/java_client/android/IntentTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.appium.java_client.android; - -import io.appium.java_client.android.options.UiAutomator2Options; -import io.appium.java_client.service.local.AppiumDriverLocalService; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; - -import java.util.function.Predicate; - -import static io.appium.java_client.TestResources.intentExampleApk; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class IntentTest { - private static AppiumDriverLocalService service; - protected static AndroidDriver driver; - - /** - * initialization. - */ - @BeforeAll public static void beforeClass() { - service = AppiumDriverLocalService.buildDefaultService(); - service.start(); - - if (service == null || !service.isRunning()) { - throw new RuntimeException("An appium server node is not started!"); - } - - UiAutomator2Options options = new UiAutomator2Options() - .setDeviceName("Android Emulator") - .setApp(intentExampleApk().toAbsolutePath().toString()); - driver = new AndroidDriver(service.getUrl(), options); - } - - /** - * finishing. - */ - @AfterAll public static void afterClass() { - if (driver != null) { - driver.quit(); - } - if (service != null) { - service.stop(); - } - } - - - @Test public void startActivityWithIntent() { - Predicate predicate = driver -> { - Activity activity = new Activity("com.android.mms", - ".ui.ComposeMessageActivity") - .setIntentAction("android.intent.action.SEND") - .setIntentCategory("android.intent.category.DEFAULT") - .setIntentFlags("0x4000000") - .setOptionalIntentArguments("-d \"TestIntent\" -t \"text/plain\""); - driver.startActivity(activity); - return true; - }; - assertTrue(predicate.test(driver)); - - } - - @Test public void startActivityWithDefaultIntentAndDefaultCategoryWithOptionalArgs() { - final Activity activity = new Activity("com.prgguru.android", ".GreetingActivity") - .setIntentAction("android.intent.action.MAIN") - .setIntentCategory("android.intent.category.DEFAULT") - .setIntentFlags("0x4000000") - .setOptionalIntentArguments("--es \"USERNAME\" \"AppiumIntentTest\" -t \"text/plain\""); - driver.startActivity(activity); - assertEquals(driver.findElement(By.id("com.prgguru.android:id/textView1")).getText(), - "Welcome AppiumIntentTest"); - } -} diff --git a/src/test/java/io/appium/java_client/android/KeyCodeTest.java b/src/test/java/io/appium/java_client/android/KeyCodeTest.java index 7c50f4ceb..7ed431166 100644 --- a/src/test/java/io/appium/java_client/android/KeyCodeTest.java +++ b/src/test/java/io/appium/java_client/android/KeyCodeTest.java @@ -33,8 +33,7 @@ public class KeyCodeTest extends BaseAndroidTest { @BeforeEach public void setUp() { - final Activity activity = new Activity(driver.getCurrentPackage(), ".text.KeyEventText"); - driver.startActivity(activity); + startActivity(".text.KeyEventText"); } @Test diff --git a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java index bfb4e576a..1c5848934 100644 --- a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java +++ b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java @@ -1,5 +1,6 @@ package io.appium.java_client.android; +import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; @@ -13,7 +14,9 @@ public class OpenNotificationsTest extends BaseAndroidTest { @Test public void openNotification() { - driver.closeApp(); + driver.executeScript("mobile: terminateApp", ImmutableMap.of( + "appId", APP_ID + )); driver.openNotifications(); WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20)); assertNotEquals(0, wait.until(input -> { diff --git a/src/test/java/io/appium/java_client/android/UIAutomator2Test.java b/src/test/java/io/appium/java_client/android/UIAutomator2Test.java index 0d9da052d..47ac3239b 100644 --- a/src/test/java/io/appium/java_client/android/UIAutomator2Test.java +++ b/src/test/java/io/appium/java_client/android/UIAutomator2Test.java @@ -59,8 +59,7 @@ public void testPortraitUpsideDown() { @Test public void testToastMSGIsDisplayed() { final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); - Activity activity = new Activity("io.appium.android.apis", ".view.PopupMenu1"); - driver.startActivity(activity); + startActivity(".view.PopupMenu1"); wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy .accessibilityId("Make a Popup!"))); 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 f977289c0..6ead922b8 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; -import io.appium.java_client.remote.HideKeyboardStrategy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -29,8 +28,6 @@ import org.openqa.selenium.remote.RemoteWebElement; 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; import java.time.Duration; @@ -60,10 +57,10 @@ public void addCustomCommandTest() { @Test public void addCustomCommandWithSessionIdTest() { - driver.addCommand(HttpMethod.POST, "/session/" + driver.getSessionId() + "/appium/app/launch", - "launchApplication"); - final Response launchApplication = driver.execute("launchApplication"); - assertNotNull(launchApplication.getSessionId()); + driver.addCommand(HttpMethod.POST, "/session/" + driver.getSessionId() + "/appium/app/strings", + "getAppStrings"); + final Response getStrings = driver.execute("getAppStrings"); + assertNotNull(getStrings.getSessionId()); } @Test @@ -85,14 +82,8 @@ public void getDeviceTimeTest() { } @Test public void resetTest() { - driver.resetApp(); - } - - @Test public void hideKeyboardWithParametersTest() { - new WebDriverWait(driver, Duration.ofSeconds(30)) - .until(ExpectedConditions.presenceOfElementLocated(By.id("IntegerA"))) - .click(); - driver.hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done"); + driver.executeScript("mobile: terminateApp", ImmutableMap.of("bundleId", BUNDLE_ID)); + driver.executeScript("mobile: activateApp", ImmutableMap.of("bundleId", BUNDLE_ID)); } @Disabled @@ -170,7 +161,7 @@ public void putAIntoBackgroundWithoutRestoreTest() { driver.toggleTouchIDEnrollment(true); driver.performTouchID(true); driver.performTouchID(false); - //noinspection SimplifiableAssertion + //noinspection SimplifiableAssertion,EqualsWithItself assertEquals(true, true); } } From 923806ab01da3e7e23f175529153277b6a1691ed Mon Sep 17 00:00:00 2001 From: AlessandroMiccoli <57630383+AlessandroMiccoli@users.noreply.github.com> Date: Fri, 8 Sep 2023 07:08:02 +0100 Subject: [PATCH 139/314] feat: Implementation of Chromium driver plus capabilities (#2003) --- .../java_client/chromium/ChromiumDriver.java | 140 ++++++++++++++++++ .../chromium/options/ChromiumOptions.java | 58 ++++++++ .../options/SupportsAutodownloadOption.java | 51 +++++++ .../options/SupportsBuildCheckOption.java | 51 +++++++ .../SupportsChromeDrivePortOption.java | 51 +++++++ .../options/SupportsExecutableDirOption.java | 49 ++++++ .../options/SupportsExecutableOption.java | 48 ++++++ .../options/SupportsLogPathOption.java | 48 ++++++ .../SupportsUseSystemExecutableOption.java | 53 +++++++ .../options/SupportsVerboseOption.java | 51 +++++++ .../drivers/options/OptionsBuildingTest.java | 34 ++++- 11 files changed, 632 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/appium/java_client/chromium/ChromiumDriver.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/ChromiumOptions.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsAutodownloadOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsChromeDrivePortOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsExecutableDirOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsExecutableOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsLogPathOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsUseSystemExecutableOption.java create mode 100644 src/main/java/io/appium/java_client/chromium/options/SupportsVerboseOption.java diff --git a/src/main/java/io/appium/java_client/chromium/ChromiumDriver.java b/src/main/java/io/appium/java_client/chromium/ChromiumDriver.java new file mode 100644 index 000000000..e6366f708 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/ChromiumDriver.java @@ -0,0 +1,140 @@ +/* + * 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.chromium; + +import io.appium.java_client.AppiumClientConfig; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.remote.AutomationName; +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.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URL; + +/** + *

ChromiumDriver is an officially supported Appium driver created to automate Mobile browsers + * and web views based on the Chromium engine. The driver uses W3CWebDriver protocol and is built + * on top of chromium driver server.

+ *
+ *
+ */ +public class ChromiumDriver extends AppiumDriver { + private static final String AUTOMATION_NAME = AutomationName.CHROMIUM; + + public ChromiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { + super(executor, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(URL remoteAddress, Capabilities capabilities) { + super(remoteAddress, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(URL remoteAddress, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(remoteAddress, httpClientFactory, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(AppiumDriverLocalService service, Capabilities capabilities) { + super(service, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(AppiumDriverLocalService service, HttpClient.Factory httpClientFactory, + Capabilities capabilities) { + super(service, httpClientFactory, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(AppiumServiceBuilder builder, Capabilities capabilities) { + super(builder, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory, + Capabilities capabilities) { + super(builder, httpClientFactory, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(httpClientFactory, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + /** + * This is a special constructor used to connect to a running driver instance. + * It does not do any necessary verifications, but rather assumes the given + * driver session is already running at `remoteSessionAddress`. + * The maintenance of driver state(s) is the caller's responsibility. + * !!! This API is supposed to be used for **debugging purposes only**. + * + * @param remoteSessionAddress The address of the **running** session including the session identifier. + * @param platformName The name of the target platform. + */ + public ChromiumDriver(URL remoteSessionAddress, String platformName) { + super(remoteSessionAddress, platformName, AUTOMATION_NAME); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * ClientConfig clientConfig = ClientConfig.defaultConfig()
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * ChromiumOptions options = new ChromiumOptions();
+     * ChromiumDriver driver = new ChromiumDriver(clientConfig, options);
+     *
+     * 
+ * + * @param clientConfig take a look at {@link ClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public ChromiumDriver(ClientConfig clientConfig, Capabilities capabilities) { + super(AppiumClientConfig.fromClientConfig(clientConfig), ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + /** + * Creates a new instance based on the given ClientConfig and {@code capabilities}. + * The HTTP client is default client generated by {@link HttpCommandExecutor#getDefaultClientFactory}. + * For example: + * + *
+     *
+     * AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
+     *     .directConnect(true)
+     *     .baseUri(URI.create("WebDriver URL"))
+     *     .readTimeout(Duration.ofMinutes(5));
+     * ChromiumOptions options = new ChromiumOptions();
+     * ChromiumDriver driver = new ChromiumDriver(options, appiumClientConfig);
+     *
+     * 
+ * + * @param appiumClientConfig take a look at {@link AppiumClientConfig} + * @param capabilities take a look at {@link Capabilities} + * + */ + public ChromiumDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, ensureAutomationName(capabilities, AUTOMATION_NAME)); + } + + public ChromiumDriver(Capabilities capabilities) { + super(ensureAutomationName(capabilities, AUTOMATION_NAME)); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/ChromiumOptions.java b/src/main/java/io/appium/java_client/chromium/options/ChromiumOptions.java new file mode 100644 index 000000000..dfc5d7d02 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/ChromiumOptions.java @@ -0,0 +1,58 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.SupportsBrowserNameOption; +import org.openqa.selenium.Capabilities; + +import java.util.Map; + +/** + *

Options class that sets options for Chromium when testing websites.

+ *
+ * @see appium-chromium-driver usage section + */ +public class ChromiumOptions extends BaseOptions implements + SupportsBrowserNameOption, + SupportsChromeDrivePortOption, + SupportsExecutableOption, + SupportsExecutableDirOption, + SupportsVerboseOption, + SupportsLogPathOption, + SupportsBuildCheckOption, + SupportsAutodownloadOption, + SupportsUseSystemExecutableOption { + public ChromiumOptions() { + setCommonOptions(); + } + + public ChromiumOptions(Capabilities source) { + super(source); + setCommonOptions(); + } + + public ChromiumOptions(Map source) { + super(source); + setCommonOptions(); + } + + private void setCommonOptions() { + setAutomationName(AutomationName.CHROMIUM); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsAutodownloadOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsAutodownloadOption.java new file mode 100644 index 000000000..a1cefdffe --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsAutodownloadOption.java @@ -0,0 +1,51 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean; + +public interface SupportsAutodownloadOption> extends + Capabilities, CanSetCapability { + String AUTODOWNLOAD_ENABLED = "autodownloadEnabled"; + + /** + * Set to false for disabling automatic downloading of Chrome drivers. + * Unless disable build check preference has been user-set, the capability + * is present because the default value is true. + * + * @param autodownloadEnabled flag. + * @return self instance for chaining. + */ + default T setAutodownloadEnabled(boolean autodownloadEnabled) { + return amend(AUTODOWNLOAD_ENABLED, autodownloadEnabled); + } + + /** + * Get the auto download flag. + * + * @return auto download flag. + */ + default Optional isAutodownloadEnabled() { + return Optional.ofNullable(toSafeBoolean(getCapability(AUTODOWNLOAD_ENABLED))); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java new file mode 100644 index 000000000..d9e95f065 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java @@ -0,0 +1,51 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean; + +public interface SupportsBuildCheckOption> extends + Capabilities, CanSetCapability { + String DISABLE_BUILD_CHECK = "disableBuildCheck"; + + /** + * Set to true to add the --disable-build-check flag when starting WebDriver. + * Unless disable build check preference has been user-set, the capability + * is not present because the default value is false. + * + * @param BuildCheckDisabled flag for --disable-build-check. + * @return self instance for chaining. + */ + default T setBuildCheckDisabled(boolean BuildCheckDisabled) { + return amend(DISABLE_BUILD_CHECK, BuildCheckDisabled); + } + + /** + * Get disable build check flag. + * + * @return disable build check flag. + */ + default Optional isBuildCheckDisabled() { + return Optional.ofNullable(toSafeBoolean(getCapability(DISABLE_BUILD_CHECK))); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsChromeDrivePortOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsChromeDrivePortOption.java new file mode 100644 index 000000000..68cace279 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsChromeDrivePortOption.java @@ -0,0 +1,51 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toInteger; + +public interface SupportsChromeDrivePortOption> extends + Capabilities, CanSetCapability { + String CHROME_DRIVER_PORT = "chromedriverPort"; + + /** + * The port to start WebDriver processes on. Unless the chrome drive port preference + * has been user-set, it will listen on port 9515, which is the default + * value for this capability. + * + * @param port port number in range 0..65535. + * @return self instance for chaining. + */ + default T setChromeDriverPort(int port) { + return amend(CHROME_DRIVER_PORT, port); + } + + /** + * Get the number of the port for the chrome driver to listen on. + * + * @return Chrome driver port value. + */ + default Optional getChromeDriverPort() { + return Optional.ofNullable(toInteger(getCapability(CHROME_DRIVER_PORT))); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableDirOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableDirOption.java new file mode 100644 index 000000000..c525ab7ad --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableDirOption.java @@ -0,0 +1,49 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +public interface SupportsExecutableDirOption> extends + Capabilities, CanSetCapability { + String EXECUTABLE_DIR = "executableDir"; + + /** + * A directory within which is found any number of WebDriver binaries. + * If set, the driver will search this directory for WebDrivers of the + * appropriate version to use for your browser. + * + * @param directory of WebDriver binaries. + * @return self instance for chaining. + */ + default T setExecutableDir(String directory) { + return amend(EXECUTABLE_DIR, directory); + } + + /** + * Get a directory within which is found any number of WebDriver binaries. + * + * @return executable directory of a Driver binary. + */ + default Optional getExecutableDir() { + return Optional.ofNullable((String) getCapability(EXECUTABLE_DIR)); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableOption.java new file mode 100644 index 000000000..84e730e62 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsExecutableOption.java @@ -0,0 +1,48 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +public interface SupportsExecutableOption> extends + Capabilities, CanSetCapability { + String EXECUTABLE = "executable"; + + /** + * The absolute path to a WebDriver binary executable. + * If set, the driver will use that path instead of its own WebDriver. + * + * @param path absolute of a WebDriver. + * @return self instance for chaining. + */ + default T setExecutable(String path) { + return amend(EXECUTABLE, path); + } + + /** + * Get the absolute path to a WebDriver binary executable. + * + * @return executable absolute path. + */ + default Optional getExecutable() { + return Optional.ofNullable((String) getCapability(EXECUTABLE)); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsLogPathOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsLogPathOption.java new file mode 100644 index 000000000..cf1b8713d --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsLogPathOption.java @@ -0,0 +1,48 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +public interface SupportsLogPathOption> extends + Capabilities, CanSetCapability { + String LOG_PATH = "logPath"; + + /** + * If set, the path to use with the --log-path parameter directing + * WebDriver to write its log to that path. + * + * @param logPath where to write the logs. + * @return self instance for chaining. + */ + default T setLogPath(String logPath) { + return amend(LOG_PATH, logPath); + } + + /** + * Get the log path where the WebDrive writes the logs. + * + * @return the log path. + */ + default Optional getLogPath() { + return Optional.ofNullable((String) getCapability(LOG_PATH)); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsUseSystemExecutableOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsUseSystemExecutableOption.java new file mode 100644 index 000000000..6d51b332b --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsUseSystemExecutableOption.java @@ -0,0 +1,53 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean; + +public interface SupportsUseSystemExecutableOption> extends + Capabilities, CanSetCapability { + String USE_SYSTEM_EXECUTABLE = "useSystemExecutable"; + + /** + * Set to true to use the version of WebDriver bundled with this driver, + * rather than attempting to download a new one based on the version of the + * browser under test. + * Unless disable build check preference has been user-set, the capability + * is not present because the default value is false. + * + * @param useSystemExecutable flag. + * @return self instance for chaining. + */ + default T setUseSystemExecutable(boolean useSystemExecutable) { + return amend(USE_SYSTEM_EXECUTABLE, useSystemExecutable); + } + + /** + * Get the use system executable flag. + * + * @return use system executable flag. + */ + default Optional isUseSystemExecutable() { + return Optional.ofNullable(toSafeBoolean(getCapability(USE_SYSTEM_EXECUTABLE))); + } +} diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsVerboseOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsVerboseOption.java new file mode 100644 index 000000000..14aa571d2 --- /dev/null +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsVerboseOption.java @@ -0,0 +1,51 @@ +/* + * 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.chromium.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean; + +public interface SupportsVerboseOption> extends + Capabilities, CanSetCapability { + String VERBOSE = "verbose"; + + /** + * Set to true to add the --verbose flag when starting WebDriver. + * Unless the verbose preference has been user-set, the capability + * is not present because the default value is false. + * + * @param verbose flag for --verbose. + * @return self instance for chaining. + */ + default T setVerbose(boolean verbose) { + return amend(VERBOSE, verbose); + } + + /** + * Get the verbose flag. + * + * @return verbose flag. + */ + default Optional isVerbose() { + return Optional.ofNullable(toSafeBoolean(getCapability(VERBOSE))); + } +} diff --git a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java index 908ff709e..04574448c 100644 --- a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java +++ b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java @@ -23,6 +23,7 @@ import io.appium.java_client.android.options.localization.AppLocale; import io.appium.java_client.android.options.server.EspressoBuildConfig; import io.appium.java_client.android.options.signing.KeystoreConfig; +import io.appium.java_client.chromium.options.ChromiumOptions; import io.appium.java_client.gecko.options.GeckoOptions; import io.appium.java_client.gecko.options.Verbosity; import io.appium.java_client.ios.options.XCUITestOptions; @@ -112,7 +113,7 @@ public void canBuildEspressoOptions() { assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); assertEquals("CN", options.getAppLocale().orElse(null).getCountry().orElse(null)); assertEquals(2, options.getEspressoBuildConfig().orElse(null) - .left().getAdditionalAppDependencies().orElse(null).size()); + .left().getAdditionalAppDependencies().orElse(null).size()); assertTrue(options.doesForceEspressoRebuild().orElse(false)); } @@ -154,7 +155,7 @@ public void canBuildGeckoOptions() { options.setNewCommandTimeout(Duration.ofSeconds(10)) .setVerbosity(Verbosity.TRACE) .setMozFirefoxOptions(ImmutableMap.of( - "profile", "yolo" + "profile", "yolo" )); assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); assertEquals(Verbosity.TRACE, options.getVerbosity().orElse(null)); @@ -180,4 +181,33 @@ public void canBuildSafariOptions() { assertTrue(options.getWebkitWebrtc().orElse(null) .doesDisableInsecureMediaCapture().orElse(false)); } + + @Test + public void canBuildChromiumOptions() { + // Given + // When + ChromiumOptions options = new ChromiumOptions(); + + options.setNewCommandTimeout(Duration.ofSeconds(10)) + .setPlatformName(Platform.MAC.name()) + .withBrowserName("Chrome") + .setAutodownloadEnabled(true) + .setBuildCheckDisabled(true) + .setChromeDriverPort(5485) + .setExecutable("/absolute/executable/path") + .setLogPath("/wonderful/log/path") + .setVerbose(true); + + // Then + assertEquals(AutomationName.CHROMIUM, options.getAutomationName().orElse(null)); + assertEquals("Chrome", options.getBrowserName()); + assertTrue(options.isAutodownloadEnabled().orElse(null)); + assertTrue(options.isBuildCheckDisabled().orElse(null)); + assertEquals(5485, options.getChromeDriverPort().orElse(null)); + assertFalse(options.getExecutableDir().isPresent()); + assertEquals("/absolute/executable/path", options.getExecutable().orElse(null)); + assertEquals("/wonderful/log/path", options.getLogPath().orElse(null)); + assertFalse(options.isUseSystemExecutable().isPresent()); + assertTrue(options.isVerbose().orElse(null)); + } } From 692e3a0311c39f47d9d6687eb6bec09f32f95e6b Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sun, 10 Sep 2023 08:14:13 +0300 Subject: [PATCH 140/314] docs: Add known compatibility issue for Selenium `4.12.1` (#2008) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7fd7a96ee..dacbe1340 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client --------------------|----------------- - `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` + `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004) `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` From 4e3b23bb9077bb932f890ed6706e5f7339091d7d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 20 Sep 2023 14:58:59 +0300 Subject: [PATCH 141/314] fix: Change scope of `selenium-support` dependency to `compile` (#2019) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ea1602d18..1021b18b2 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ dependencies { prefer "${seleniumVersion}" } } - implementation ('org.seleniumhq.selenium:selenium-support') { + api ('org.seleniumhq.selenium:selenium-support') { version { strictly "[${seleniumVersion}, 5.0)" prefer "${seleniumVersion}" From 01b9e16127dfcd60ce73d2fd1bd0f2602010f909 Mon Sep 17 00:00:00 2001 From: ashwithpoojary98 <61794427+ashwithpoojary98@users.noreply.github.com> Date: Sat, 23 Sep 2023 23:31:30 +0530 Subject: [PATCH 142/314] fix: Fix Code style issues to match Java standards (#2017) --- .../java/io/appium/java_client/AppiumBy.java | 22 +++++++ .../java_client/CommandExecutionHelper.java | 3 + .../appium/java_client/ErrorCodesMobile.java | 3 + .../appium/java_client/InteractsWithApps.java | 8 +-- .../appium/java_client/ScreenshotState.java | 4 +- .../java/io/appium/java_client/Setting.java | 1 + .../android/options/app/IntentOptions.java | 10 ++-- .../app/SupportsActivityOptionsOption.java | 2 +- .../app/SupportsIntentOptionsOption.java | 2 +- .../options/avd/SupportsAvdArgsOption.java | 2 +- .../options/avd/SupportsAvdEnvOption.java | 2 +- .../localization/SupportsAppLocaleOption.java | 2 +- .../options/server/EspressoBuildConfig.java | 2 +- .../SupportsEspressoBuildConfigOption.java | 2 +- .../options/SupportsBuildCheckOption.java | 6 +- .../OccurrenceMatchingResult.java | 2 +- .../internal/CapabilityHelpers.java | 3 + .../appium/java_client/internal/Config.java | 2 +- .../internal/ReflectionHelpers.java | 13 ++-- .../java_client/internal/SessionHelpers.java | 3 + .../other/SupportsCommandTimeoutsOption.java | 2 +- .../simulator/SupportsPermissionsOption.java | 2 +- ...imulatorPasteboardAutomaticSyncOption.java | 2 +- .../ios/options/wda/ProcessArguments.java | 4 +- .../wda/SupportsWaitForIdleTimeoutOption.java | 2 +- .../SupportsWdaEventloopIdleDelayOption.java | 2 +- .../wda/SupportsXcodeCertificateOptions.java | 2 +- .../java_client/mac/options/Mac2Options.java | 4 +- .../pagefactory/AppiumElementLocator.java | 4 +- .../pagefactory/AppiumFieldDecorator.java | 2 +- .../pagefactory/OverrideWidgetReader.java | 35 ++++++----- .../pagefactory/ThrowableUtil.java | 3 + .../java_client/pagefactory/WithTimeout.java | 4 ++ .../pagefactory/bys/ContentMappedBy.java | 59 +++++++++++++------ .../bys/builder/AppiumByBuilder.java | 22 +++---- .../pagefactory/bys/builder/Strategies.java | 2 +- .../utils/WebDriverUnpackUtility.java | 3 + .../appium/java_client/proxy/Interceptor.java | 3 + .../remote/AppiumCommandExecutor.java | 2 +- .../options/SupportsOrientationOption.java | 2 +- .../remote/options/SupportsProxyOption.java | 2 +- .../options/UnhandledPromptBehavior.java | 2 +- .../local/AppiumDriverLocalService.java | 19 +++--- .../service/local/AppiumServiceBuilder.java | 4 +- .../service/local/ListOutputStream.java | 2 + .../windows/options/WindowsOptions.java | 10 ++-- 46 files changed, 188 insertions(+), 106 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index 297e9a9b3..7e067287d 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -25,6 +25,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Objects; public abstract class AppiumBy extends By implements Remotable { @@ -253,6 +254,27 @@ protected ByIosNsPredicate(String locatorString) { super("-ios predicate string", locatorString, "iOSNsPredicate"); } } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + AppiumBy appiumBy = (AppiumBy) o; + return Objects.equals(remoteParameters, appiumBy.remoteParameters) + && Objects.equals(locatorName, appiumBy.locatorName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), remoteParameters, locatorName); + } } diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 0a41f17b6..7abfef612 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -17,6 +17,8 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; @@ -26,6 +28,7 @@ import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class CommandExecutionHelper { @Nullable diff --git a/src/main/java/io/appium/java_client/ErrorCodesMobile.java b/src/main/java/io/appium/java_client/ErrorCodesMobile.java index 6e7e66d69..473c014d9 100644 --- a/src/main/java/io/appium/java_client/ErrorCodesMobile.java +++ b/src/main/java/io/appium/java_client/ErrorCodesMobile.java @@ -43,6 +43,7 @@ public class ErrorCodesMobile extends ErrorCodes { * @param statusCode The status code to convert. * @return The exception type that corresponds to the provided status */ + @Override public Class getExceptionType(int statusCode) { switch (statusCode) { case NO_SUCH_CONTEXT: @@ -60,6 +61,7 @@ public Class getExceptionType(int statusCode) { * @return The exception type that corresponds to the provided error message or {@code null} if * there are no matching mobile exceptions. */ + @Override public Class getExceptionType(String message) { for (Map.Entry entry : statusToState.entrySet()) { if (message.contains(entry.getValue())) { @@ -75,6 +77,7 @@ public Class getExceptionType(String message) { * @param thrown The thrown error. * @return The corresponding status code for the given thrown error. */ + @Override public int toStatusCode(Throwable thrown) { if (thrown instanceof NoSuchContextException) { return NO_SUCH_CONTEXT; diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 45f1c285d..d61f85b19 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -75,7 +75,7 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions Map args = ImmutableMap.builder() .put("appPath", appPath) .putAll(Optional.ofNullable(options).map( - (opts) -> ImmutableMap.of("options", opts.build()) + opts -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); CommandExecutionHelper.execute( @@ -169,7 +169,7 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - (opts) -> ImmutableMap.of("options", opts.build()) + opts -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); //noinspection RedundantCast @@ -214,7 +214,7 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - (opts) -> ImmutableMap.of("options", opts.build()) + opts -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); CommandExecutionHelper.execute( @@ -290,7 +290,7 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - (opts) -> ImmutableMap.of("options", opts.build()) + opts -> ImmutableMap.of("options", opts.build()) ).orElseGet(ImmutableMap::of)) .build(); //noinspection RedundantCast diff --git a/src/main/java/io/appium/java_client/ScreenshotState.java b/src/main/java/io/appium/java_client/ScreenshotState.java index 8c88397e5..1747a1e41 100644 --- a/src/main/java/io/appium/java_client/ScreenshotState.java +++ b/src/main/java/io/appium/java_client/ScreenshotState.java @@ -175,7 +175,7 @@ private ScreenshotState checkState(Function checkerFunc, Durati * @throws ScreenshotComparisonError if {@link #remember()} method has not been invoked yet */ public ScreenshotState verifyChanged(Duration timeout, double minScore) { - return checkState((x) -> x < minScore, timeout); + return checkState(x -> x < minScore, timeout); } /** @@ -190,7 +190,7 @@ public ScreenshotState verifyChanged(Duration timeout, double minScore) { * @throws ScreenshotComparisonError if {@link #remember()} method has not been invoked yet */ public ScreenshotState verifyNotChanged(Duration timeout, double minScore) { - return checkState((x) -> x >= minScore, timeout); + return checkState(x -> x >= minScore, timeout); } /** diff --git a/src/main/java/io/appium/java_client/Setting.java b/src/main/java/io/appium/java_client/Setting.java index b5b84ca16..cc1f44cd1 100644 --- a/src/main/java/io/appium/java_client/Setting.java +++ b/src/main/java/io/appium/java_client/Setting.java @@ -65,6 +65,7 @@ public enum Setting { this.name = name; } + @Override public String toString() { return this.name; } diff --git a/src/main/java/io/appium/java_client/android/options/app/IntentOptions.java b/src/main/java/io/appium/java_client/android/options/app/IntentOptions.java index e651a4b91..67f09f2b5 100644 --- a/src/main/java/io/appium/java_client/android/options/app/IntentOptions.java +++ b/src/main/java/io/appium/java_client/android/options/app/IntentOptions.java @@ -261,7 +261,7 @@ public IntentOptions withEi(Map ei) { private Map convertMapValues(Map map, Function converter) { return map.entrySet().stream() .collect(Collectors.toMap( - Map.Entry::getKey, (entry) -> converter.apply(String.valueOf(entry.getValue()))) + Map.Entry::getKey, entry -> converter.apply(String.valueOf(entry.getValue()))) ); } @@ -272,7 +272,7 @@ private Map convertMapValues(Map map, Function> getEi() { Optional> value = getOptionValue("ei"); - return value.map((v) -> convertMapValues(v, Integer::parseInt)); + return value.map(v -> convertMapValues(v, Integer::parseInt)); } /** @@ -292,7 +292,7 @@ public IntentOptions withEl(Map el) { */ public Optional> getEl() { Optional> value = getOptionValue("el"); - return value.map((v) -> convertMapValues(v, Long::parseLong)); + return value.map(v -> convertMapValues(v, Long::parseLong)); } /** @@ -312,7 +312,7 @@ public IntentOptions withEf(Map ef) { */ public Optional> getEf() { Optional> value = getOptionValue("ef"); - return value.map((v) -> convertMapValues(v, Float::parseFloat)); + return value.map(v -> convertMapValues(v, Float::parseFloat)); } /** @@ -356,7 +356,7 @@ public Optional> getEcn() { private static Map mergeValues(Map map) { return map.entrySet().stream() .collect( - Collectors.toMap(Map.Entry::getKey, (entry) -> ((List) entry.getValue()).stream() + Collectors.toMap(Map.Entry::getKey, entry -> ((List) entry.getValue()).stream() .map(String::valueOf) .collect(Collectors.joining(","))) ); diff --git a/src/main/java/io/appium/java_client/android/options/app/SupportsActivityOptionsOption.java b/src/main/java/io/appium/java_client/android/options/app/SupportsActivityOptionsOption.java index 393ee51c5..59d5fe520 100644 --- a/src/main/java/io/appium/java_client/android/options/app/SupportsActivityOptionsOption.java +++ b/src/main/java/io/appium/java_client/android/options/app/SupportsActivityOptionsOption.java @@ -48,6 +48,6 @@ default T setActivityOptions(ActivityOptions options) { default Optional getActivityOptions() { //noinspection unchecked return Optional.ofNullable(getCapability(ACTIVITY_OPTIONS_OPTION)) - .map((v) -> new ActivityOptions((Map) v)); + .map(v -> new ActivityOptions((Map) v)); } } diff --git a/src/main/java/io/appium/java_client/android/options/app/SupportsIntentOptionsOption.java b/src/main/java/io/appium/java_client/android/options/app/SupportsIntentOptionsOption.java index 91c2b49a2..3c0fab894 100644 --- a/src/main/java/io/appium/java_client/android/options/app/SupportsIntentOptionsOption.java +++ b/src/main/java/io/appium/java_client/android/options/app/SupportsIntentOptionsOption.java @@ -48,6 +48,6 @@ default T setIntentOptions(IntentOptions options) { default Optional getIntentOptions() { //noinspection unchecked return Optional.ofNullable(getCapability(INTENT_OPTIONS_OPTION)) - .map((v) -> new IntentOptions((Map) v)); + .map(v -> new IntentOptions((Map) v)); } } diff --git a/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdArgsOption.java b/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdArgsOption.java index 7843393e4..bca9866c0 100644 --- a/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdArgsOption.java +++ b/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdArgsOption.java @@ -56,7 +56,7 @@ default T setAvdArgs(String args) { default Optional, String>> getAvdArgs() { //noinspection unchecked return Optional.ofNullable(getCapability(AVD_ARGS_OPTION)) - .map((v) -> v instanceof List + .map(v -> v instanceof List ? Either.left((List) v) : Either.right(String.valueOf(v)) ); diff --git a/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdEnvOption.java b/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdEnvOption.java index e6d083aee..9fadb6532 100644 --- a/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdEnvOption.java +++ b/src/main/java/io/appium/java_client/android/options/avd/SupportsAvdEnvOption.java @@ -45,6 +45,6 @@ default T setAvdEnv(Map env) { default Optional> getAvdEnv() { //noinspection unchecked return Optional.ofNullable(getCapability(AVD_ENV_OPTION)) - .map((v) -> (Map) v); + .map(v -> (Map) v); } } diff --git a/src/main/java/io/appium/java_client/android/options/localization/SupportsAppLocaleOption.java b/src/main/java/io/appium/java_client/android/options/localization/SupportsAppLocaleOption.java index 40447d749..d8fafba02 100644 --- a/src/main/java/io/appium/java_client/android/options/localization/SupportsAppLocaleOption.java +++ b/src/main/java/io/appium/java_client/android/options/localization/SupportsAppLocaleOption.java @@ -51,6 +51,6 @@ default T setAppLocale(AppLocale locale) { default Optional getAppLocale() { //noinspection unchecked return Optional.ofNullable(getCapability(APP_LOCALE_OPTION)) - .map((v) -> new AppLocale((Map) v)); + .map(v -> new AppLocale((Map) v)); } } diff --git a/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java b/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java index 2044f0bd1..24adc5675 100644 --- a/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java +++ b/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java @@ -51,7 +51,7 @@ private EspressoBuildConfig assignToolsVersionsField(String name, Object value) private Optional getToolsVersionsFieldValue(String name) { Optional> toolsVersionsOptional = getOptionValue(TOOLS_VERSION); //noinspection unchecked - return toolsVersionsOptional.map((v) -> (R) v.getOrDefault(name, null)); + return toolsVersionsOptional.map(v -> (R) v.getOrDefault(name, null)); } /** diff --git a/src/main/java/io/appium/java_client/android/options/server/SupportsEspressoBuildConfigOption.java b/src/main/java/io/appium/java_client/android/options/server/SupportsEspressoBuildConfigOption.java index 93eb19831..a916ec35b 100644 --- a/src/main/java/io/appium/java_client/android/options/server/SupportsEspressoBuildConfigOption.java +++ b/src/main/java/io/appium/java_client/android/options/server/SupportsEspressoBuildConfigOption.java @@ -61,7 +61,7 @@ default T setEspressoBuildConfig(EspressoBuildConfig config) { default Optional> getEspressoBuildConfig() { return Optional.ofNullable(getCapability(ESPRESSO_BUILD_CONFIG_OPTION)) .map(String::valueOf) - .map((v) -> v.trim().startsWith("{") + .map(v -> v.trim().startsWith("{") ? Either.left(new EspressoBuildConfig(v)) : Either.right(v) ); diff --git a/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java b/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java index d9e95f065..204967bca 100644 --- a/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java +++ b/src/main/java/io/appium/java_client/chromium/options/SupportsBuildCheckOption.java @@ -33,11 +33,11 @@ public interface SupportsBuildCheckOption> extends * Unless disable build check preference has been user-set, the capability * is not present because the default value is false. * - * @param BuildCheckDisabled flag for --disable-build-check. + * @param buildCheckDisabled flag for --disable-build-check. * @return self instance for chaining. */ - default T setBuildCheckDisabled(boolean BuildCheckDisabled) { - return amend(DISABLE_BUILD_CHECK, BuildCheckDisabled); + default T setBuildCheckDisabled(boolean buildCheckDisabled) { + return amend(DISABLE_BUILD_CHECK, buildCheckDisabled); } /** 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 256a1636a..510f64b8e 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -65,7 +65,7 @@ public List getMultiple() { //noinspection unchecked List> multiple = (List>) getCommandResult().get(MULTIPLE); return multiple.stream() - .map((m) -> new OccurrenceMatchingResult(m, false)) + .map(m -> new OccurrenceMatchingResult(m, false)) .collect(Collectors.toList()); } } 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 3645a7520..5280b8e17 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -16,6 +16,8 @@ package io.appium.java_client.internal; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.openqa.selenium.Capabilities; import javax.annotation.Nullable; @@ -26,6 +28,7 @@ import java.util.List; import java.util.function.Function; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class CapabilityHelpers { public static final String APPIUM_PREFIX = "appium:"; 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 fd9ef73c1..83bfb7995 100644 --- a/src/main/java/io/appium/java_client/internal/Config.java +++ b/src/main/java/io/appium/java_client/internal/Config.java @@ -58,7 +58,7 @@ public T getValue(String key, Class valueType) { * @throws ClassCastException if the retrieved value cannot be cast to `valueType` type */ public Optional getOptionalValue(String key, Class valueType) { - final Properties cachedProps = cache.computeIfAbsent(configName, (k) -> { + final Properties cachedProps = cache.computeIfAbsent(configName, k -> { try (InputStream configFileStream = getClass().getClassLoader().getResourceAsStream(configName)) { final Properties p = new Properties(); p.load(configFileStream); diff --git a/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java index cbaa7c796..abe16a170 100644 --- a/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java +++ b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java @@ -16,19 +16,22 @@ package io.appium.java_client.internal; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.openqa.selenium.WebDriverException; import java.lang.reflect.Field; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ReflectionHelpers { /** * Sets the given value to a private instance field. * - * @param cls The target class or a superclass. - * @param target Target instance. + * @param cls The target class or a superclass. + * @param target Target instance. * @param fieldName Target field name. - * @param newValue The value to be set. + * @param newValue The value to be set. * @return The same instance for chaining. */ public static T setPrivateFieldValue(Class cls, T target, String fieldName, Object newValue) { @@ -45,8 +48,8 @@ public static T setPrivateFieldValue(Class cls, T target, String fieldNam /** * Fetches the value of a private instance field. * - * @param cls The target class or a superclass. - * @param target Target instance. + * @param cls The target class or a superclass. + * @param target Target instance. * @param fieldName Target field name. * @param fieldType Field type. * @return The retrieved field value. diff --git a/src/main/java/io/appium/java_client/internal/SessionHelpers.java b/src/main/java/io/appium/java_client/internal/SessionHelpers.java index b3b9f0eca..c028bdfb5 100644 --- a/src/main/java/io/appium/java_client/internal/SessionHelpers.java +++ b/src/main/java/io/appium/java_client/internal/SessionHelpers.java @@ -16,7 +16,9 @@ package io.appium.java_client.internal; +import lombok.AccessLevel; import lombok.Data; +import lombok.NoArgsConstructor; import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.WebDriverException; @@ -25,6 +27,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class SessionHelpers { private static final Pattern SESSION = Pattern.compile("/session/([^/]+)"); diff --git a/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java b/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java index 0afd1a9a8..4461985c1 100644 --- a/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java +++ b/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java @@ -62,7 +62,7 @@ default T setCommandTimeouts(Duration timeout) { default Optional> getCommandTimeouts() { return Optional.ofNullable(getCapability(COMMAND_TIMEOUTS_OPTION)) .map(String::valueOf) - .map((v) -> v.trim().startsWith("{") + .map(v -> v.trim().startsWith("{") ? Either.left(new CommandTimeouts(v)) : Either.right(toDuration(v)) ); diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsPermissionsOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsPermissionsOption.java index e3a4b36c2..3f55a335c 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsPermissionsOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsPermissionsOption.java @@ -44,6 +44,6 @@ default T setPermissions(Permissions permissions) { */ default Optional getPermissions() { return Optional.ofNullable(getCapability(PERMISSIONS_OPTION)) - .map((v) -> new Permissions(String.valueOf(v))); + .map(v -> new Permissions(String.valueOf(v))); } } diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorPasteboardAutomaticSyncOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorPasteboardAutomaticSyncOption.java index 7f99af2e0..665c4d368 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorPasteboardAutomaticSyncOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorPasteboardAutomaticSyncOption.java @@ -47,6 +47,6 @@ default T setSimulatorPasteboardAutomaticSync(PasteboardSyncState state) { */ default Optional getSimulatorPasteboardAutomaticSync() { return Optional.ofNullable(getCapability(SIMULATOR_PASTEBOARD_AUTOMATIC_SYNC)) - .map((v) -> PasteboardSyncState.valueOf(String.valueOf(v).toUpperCase())); + .map(v -> PasteboardSyncState.valueOf(String.valueOf(v).toUpperCase())); } } diff --git a/src/main/java/io/appium/java_client/ios/options/wda/ProcessArguments.java b/src/main/java/io/appium/java_client/ios/options/wda/ProcessArguments.java index c5ed74d22..08b06f9f8 100644 --- a/src/main/java/io/appium/java_client/ios/options/wda/ProcessArguments.java +++ b/src/main/java/io/appium/java_client/ios/options/wda/ProcessArguments.java @@ -49,8 +49,8 @@ public ProcessArguments(Map env) { */ public Map toMap() { Map result = new HashMap<>(); - Optional.ofNullable(args).ifPresent((v) -> result.put("args", v)); - Optional.ofNullable(env).ifPresent((v) -> result.put("env", v)); + Optional.ofNullable(args).ifPresent(v -> result.put("args", v)); + Optional.ofNullable(env).ifPresent(v -> result.put("env", v)); return Collections.unmodifiableMap(result); } } diff --git a/src/main/java/io/appium/java_client/ios/options/wda/SupportsWaitForIdleTimeoutOption.java b/src/main/java/io/appium/java_client/ios/options/wda/SupportsWaitForIdleTimeoutOption.java index 0d9ffda35..f9dd2401a 100644 --- a/src/main/java/io/appium/java_client/ios/options/wda/SupportsWaitForIdleTimeoutOption.java +++ b/src/main/java/io/appium/java_client/ios/options/wda/SupportsWaitForIdleTimeoutOption.java @@ -53,6 +53,6 @@ default T setWaitForIdleTimeout(Duration timeout) { default Optional getWaitForIdleTimeout() { return Optional.ofNullable(getCapability(WAIT_FOR_IDLE_TIMEOUT_OPTION)) .map(CapabilityHelpers::toDouble) - .map((d) -> toDuration((long) (d * 1000.0))); + .map(d -> toDuration((long) (d * 1000.0))); } } diff --git a/src/main/java/io/appium/java_client/ios/options/wda/SupportsWdaEventloopIdleDelayOption.java b/src/main/java/io/appium/java_client/ios/options/wda/SupportsWdaEventloopIdleDelayOption.java index 4d35bb5c4..3d48703b5 100644 --- a/src/main/java/io/appium/java_client/ios/options/wda/SupportsWdaEventloopIdleDelayOption.java +++ b/src/main/java/io/appium/java_client/ios/options/wda/SupportsWdaEventloopIdleDelayOption.java @@ -54,6 +54,6 @@ default T setWdaEventloopIdleDelay(Duration duration) { default Optional getWdaEventloopIdleDelay() { return Optional.ofNullable(getCapability(WDA_EVENTLOOP_IDLE_DELAY_OPTION)) .map(CapabilityHelpers::toDouble) - .map((d) -> toDuration((long) (d * 1000.0))); + .map(d -> toDuration((long) (d * 1000.0))); } } diff --git a/src/main/java/io/appium/java_client/ios/options/wda/SupportsXcodeCertificateOptions.java b/src/main/java/io/appium/java_client/ios/options/wda/SupportsXcodeCertificateOptions.java index fcecbab8c..45d437195 100644 --- a/src/main/java/io/appium/java_client/ios/options/wda/SupportsXcodeCertificateOptions.java +++ b/src/main/java/io/appium/java_client/ios/options/wda/SupportsXcodeCertificateOptions.java @@ -51,6 +51,6 @@ default Optional getXcodeCertificate() { String orgId = (String) getCapability(XCODE_ORG_ID_OPTION); String signingId = (String) getCapability(XCODE_SIGNING_ID_OPTION); return Optional.ofNullable(orgId) - .map((x) -> new XcodeCertificate(orgId, signingId)); + .map(x -> new XcodeCertificate(orgId, signingId)); } } diff --git a/src/main/java/io/appium/java_client/mac/options/Mac2Options.java b/src/main/java/io/appium/java_client/mac/options/Mac2Options.java index 37e31575a..5325ee284 100644 --- a/src/main/java/io/appium/java_client/mac/options/Mac2Options.java +++ b/src/main/java/io/appium/java_client/mac/options/Mac2Options.java @@ -83,7 +83,7 @@ public Mac2Options setPrerun(AppleScriptData script) { public Optional getPrerun() { //noinspection unchecked return Optional.ofNullable(getCapability(PRERUN_OPTION)) - .map((v) -> new AppleScriptData((Map) v)); + .map(v -> new AppleScriptData((Map) v)); } /** @@ -108,6 +108,6 @@ public Mac2Options setPostrun(AppleScriptData script) { public Optional getPostrun() { //noinspection unchecked return Optional.ofNullable(getCapability(POSTRUN_OPTION)) - .map((v) -> new AppleScriptData((Map) v)); + .map(v -> new AppleScriptData((Map) v)); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java index 3aba5bcdc..9e148a2c7 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -43,7 +43,7 @@ class AppiumElementLocator implements CacheableLocator { - private static final String exceptionMessageIfElementNotFound = "Can't locate an element by this strategy: %s"; + private static final String EXCEPTION_MESSAGE_IF_ELEMENT_NOT_FOUND = "Can't locate an element by this strategy: %s"; private final boolean shouldCache; private final By by; @@ -163,7 +163,7 @@ public WebElement findElement() { } return result; } catch (TimeoutException | StaleElementReferenceException e) { - throw new NoSuchElementException(format(exceptionMessageIfElementNotFound, bySearching.toString()), e); + throw new NoSuchElementException(format(EXCEPTION_MESSAGE_IF_ELEMENT_NOT_FOUND, bySearching.toString()), e); } } 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 c2f11f473..2d5daaa03 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -170,7 +170,7 @@ protected boolean isDecoratableList(Field field) { ? Arrays.asList(((TypeVariable) listType).getBounds()) : Collections.emptyList(); return availableElementClasses.stream() - .anyMatch((webElClass) -> webElClass.equals(listType) || bounds.contains(webElClass)); + .anyMatch(webElClass -> webElClass.equals(listType) || bounds.contains(webElClass)); } }; } diff --git a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java index be25f1c31..9f8b59c17 100644 --- a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java +++ b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java @@ -17,6 +17,8 @@ package io.appium.java_client.pagefactory; import io.appium.java_client.pagefactory.bys.ContentType; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; @@ -29,6 +31,7 @@ import static io.appium.java_client.remote.MobilePlatform.IOS; import static io.appium.java_client.remote.MobilePlatform.WINDOWS; +@NoArgsConstructor(access = AccessLevel.PRIVATE) class OverrideWidgetReader { private static final Class EMPTY = Widget.class; private static final String HTML = "html"; @@ -38,15 +41,15 @@ class OverrideWidgetReader { @SuppressWarnings("unchecked") private static Class getConvenientClass(Class declaredClass, - AnnotatedElement annotatedElement, String method) { + AnnotatedElement annotatedElement, String method) { Class convenientClass; OverrideWidget overrideWidget = annotatedElement.getAnnotation(OverrideWidget.class); try { if (overrideWidget == null || (convenientClass = - (Class) OverrideWidget.class - .getDeclaredMethod(method).invoke(overrideWidget)) - .equals(EMPTY)) { + (Class) OverrideWidget.class + .getDeclaredMethod(method).invoke(overrideWidget)) + .equals(EMPTY)) { convenientClass = declaredClass; } } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { @@ -55,9 +58,9 @@ private static Class getConvenientClass(Class getConvenientClass(Class getDefaultOrHTMLWidgetClass( - Class declaredClass, AnnotatedElement annotatedElement) { + Class declaredClass, AnnotatedElement annotatedElement) { return getConvenientClass(declaredClass, annotatedElement, HTML); } static Class getMobileNativeWidgetClass(Class declaredClass, - AnnotatedElement annotatedElement, String platform) { + AnnotatedElement annotatedElement, String platform) { String transformedPlatform = String.valueOf(platform).toUpperCase().trim(); if (ANDROID.equalsIgnoreCase(transformedPlatform)) { @@ -89,26 +92,26 @@ static Class getMobileNativeWidgetClass(Class getConstructorOfADefaultOrHTMLWidget( - Class declaredClass, AnnotatedElement annotatedElement) { + Class declaredClass, AnnotatedElement annotatedElement) { Class clazz = - getDefaultOrHTMLWidgetClass(declaredClass, annotatedElement); + getDefaultOrHTMLWidgetClass(declaredClass, annotatedElement); return findConvenientConstructor(clazz); } private static Constructor getConstructorOfAMobileNativeWidgets( - Class declaredClass, AnnotatedElement annotatedElement, String platform) { + Class declaredClass, AnnotatedElement annotatedElement, String platform) { Class clazz = - getMobileNativeWidgetClass(declaredClass, annotatedElement, platform); + getMobileNativeWidgetClass(declaredClass, annotatedElement, platform); return findConvenientConstructor(clazz); } protected static Map> read( - Class declaredClass, AnnotatedElement annotatedElement, String platform) { + Class declaredClass, AnnotatedElement annotatedElement, String platform) { Map> result = new HashMap<>(); result.put(ContentType.HTML_OR_DEFAULT, - getConstructorOfADefaultOrHTMLWidget(declaredClass, annotatedElement)); + getConstructorOfADefaultOrHTMLWidget(declaredClass, annotatedElement)); result.put(ContentType.NATIVE_MOBILE_SPECIFIC, - getConstructorOfAMobileNativeWidgets(declaredClass, annotatedElement, platform)); + getConstructorOfAMobileNativeWidgets(declaredClass, annotatedElement, platform)); return result; } } diff --git a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java index c025a53dd..f5b310702 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java +++ b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java @@ -16,11 +16,14 @@ package io.appium.java_client.pagefactory; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.openqa.selenium.InvalidSelectorException; import org.openqa.selenium.StaleElementReferenceException; import java.lang.reflect.InvocationTargetException; +@NoArgsConstructor(access = AccessLevel.PRIVATE) class ThrowableUtil { private static final String INVALID_SELECTOR_PATTERN = "Invalid locator strategy:"; diff --git a/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java b/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java index 0b95a50c1..0727bc2b3 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java +++ b/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java @@ -16,6 +16,9 @@ package io.appium.java_client.pagefactory; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -43,6 +46,7 @@ */ ChronoUnit chronoUnit(); + @NoArgsConstructor(access = AccessLevel.PRIVATE) class DurationBuilder { static Duration build(WithTimeout withTimeout) { return Duration.of(withTimeout.time(), withTimeout.chronoUnit()); diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java index fe7edf9b9..30bf58456 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java @@ -1,18 +1,18 @@ /* -* 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. -*/ + * 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.pagefactory.bys; @@ -23,6 +23,7 @@ import javax.annotation.Nonnull; import java.util.List; import java.util.Map; +import java.util.Objects; import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; @@ -37,6 +38,7 @@ public ContentMappedBy(Map map) { /** * This method sets required content type for the further searching. + * * @param type required content type {@link ContentType} * @return self-reference. */ @@ -46,15 +48,38 @@ public By useContent(@Nonnull ContentType type) { return this; } - @Override public WebElement findElement(SearchContext context) { + @Override + public WebElement findElement(SearchContext context) { return context.findElement(map.get(currentContent)); } - @Override public List findElements(SearchContext context) { + @Override + public List findElements(SearchContext context) { return context.findElements(map.get(currentContent)); } - @Override public String toString() { + @Override + public String toString() { return map.get(currentContent).toString(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + ContentMappedBy that = (ContentMappedBy) o; + return Objects.equals(map, that.map) && currentContent == that.currentContent; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), map, currentContent); + } } 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 330362140..0e7146a0c 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 @@ -39,10 +39,10 @@ import static io.appium.java_client.remote.MobilePlatform.WINDOWS; /** - * It is the basic handler of Appium-specific page object annotations + * It is the basic handler of Appium-specific page object annotations. * About the Page Object design pattern please read these documents: - * - https://code.google.com/p/selenium/wiki/PageObjects - * - https://code.google.com/p/selenium/wiki/PageFactory + * - Selenium Page Object models + * - Selenium Page Factory */ public abstract class AppiumByBuilder extends AbstractAnnotations { protected static final Class[] DEFAULT_ANNOTATION_METHOD_ARGUMENTS = new Class[]{}; @@ -76,7 +76,7 @@ private static Method[] prepareAnnotationMethods(Class ann List targetAnnotationMethodNamesList = getMethodNames(annotation.getDeclaredMethods()); targetAnnotationMethodNamesList.removeAll(METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ); return targetAnnotationMethodNamesList.stream() - .map((methodName) -> { + .map(methodName -> { try { return annotation.getMethod(methodName, DEFAULT_ANNOTATION_METHOD_ARGUMENTS); } catch (NoSuchMethodException | SecurityException e) { @@ -87,13 +87,13 @@ private static Method[] prepareAnnotationMethods(Class ann private static String getFilledValue(Annotation mobileBy) { return Stream.of(prepareAnnotationMethods(mobileBy.getClass())) - .filter((method) -> String.class == method.getReturnType()) - .filter((method) -> { + .filter(method -> String.class == method.getReturnType()) + .filter(method -> { try { Object strategyParameter = method.invoke(mobileBy); return strategyParameter != null && !String.valueOf(strategyParameter).isEmpty(); } catch (IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { + | InvocationTargetException e) { throw new RuntimeException(e); } }) @@ -107,9 +107,9 @@ private static String getFilledValue(Annotation mobileBy) { private static By getMobileBy(Annotation annotation, String valueName) { return Stream.of(Strategies.values()) - .filter((strategy) -> strategy.returnValueName().equals(valueName)) + .filter(strategy -> strategy.returnValueName().equals(valueName)) .findFirst() - .map((strategy) -> strategy.getBy(annotation)) + .map(strategy -> strategy.getBy(annotation)) .orElseThrow(() -> new IllegalArgumentException( String.format("@%s: There is an unknown strategy %s", annotation.getClass().getSimpleName(), valueName) @@ -118,14 +118,14 @@ private static By getMobileBy(Annotation annotation, String valueName) { private static T getComplexMobileBy(Annotation[] annotations, Class requiredByClass) { By[] byArray = Stream.of(annotations) - .map((annotation) -> getMobileBy(annotation, getFilledValue(annotation))) + .map(annotation -> getMobileBy(annotation, getFilledValue(annotation))) .toArray(By[]::new); try { Constructor c = requiredByClass.getConstructor(By[].class); Object[] values = new Object[]{byArray}; return c.newInstance(values); } catch (InvocationTargetException | NoSuchMethodException | InstantiationException - | IllegalAccessException e) { + | IllegalAccessException e) { throw new RuntimeException(e); } } 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 a353e0257..452a8cda0 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 @@ -135,7 +135,7 @@ enum Strategies { } static List strategiesNames() { - return Stream.of(values()).map((s) -> s.valueName).collect(Collectors.toList()); + return Stream.of(values()).map(s -> s.valueName).collect(Collectors.toList()); } private static String getValue(Annotation annotation, Strategies strategy) { diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 53ba1506c..9df129930 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -18,6 +18,8 @@ import io.appium.java_client.HasBrowserCheck; import io.appium.java_client.pagefactory.bys.ContentType; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.openqa.selenium.ContextAware; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -31,6 +33,7 @@ import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index dce2fd807..aca474044 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -16,6 +16,8 @@ package io.appium.java_client.proxy; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.implementation.bind.annotation.RuntimeType; @@ -29,6 +31,7 @@ import java.util.UUID; import java.util.concurrent.Callable; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class Interceptor { private static final Logger logger = LoggerFactory.getLogger(Interceptor.class); 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 0085f457d..ee0e5a396 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -180,7 +180,7 @@ private Response createSession(Command command) throws IOException { } ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession( - getClient().with((httpHandler) -> (req) -> { + getClient().with(httpHandler -> req -> { req.setHeader(HttpHeaders.USER_AGENT, AppiumUserAgentFilter.buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); diff --git a/src/main/java/io/appium/java_client/remote/options/SupportsOrientationOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsOrientationOption.java index 3bc8b16e0..be2dd1a5d 100644 --- a/src/main/java/io/appium/java_client/remote/options/SupportsOrientationOption.java +++ b/src/main/java/io/appium/java_client/remote/options/SupportsOrientationOption.java @@ -42,7 +42,7 @@ default T setOrientation(ScreenOrientation orientation) { */ default Optional getOrientation() { return Optional.ofNullable(getCapability(ORIENTATION_OPTION)) - .map((v) -> v instanceof ScreenOrientation + .map(v -> v instanceof ScreenOrientation ? (ScreenOrientation) v : ScreenOrientation.valueOf((String.valueOf(v)).toUpperCase()) ); diff --git a/src/main/java/io/appium/java_client/remote/options/SupportsProxyOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsProxyOption.java index cb3de55e7..d69be4d2b 100644 --- a/src/main/java/io/appium/java_client/remote/options/SupportsProxyOption.java +++ b/src/main/java/io/appium/java_client/remote/options/SupportsProxyOption.java @@ -45,7 +45,7 @@ default T setProxy(Proxy proxy) { default Optional getProxy() { return Optional.ofNullable(getCapability(PROXY_OPTION)) .map(String::valueOf) - .map((v) -> new Gson().fromJson(v, Map.class)) + .map(v -> new Gson().fromJson(v, Map.class)) .map(Proxy::new); } } diff --git a/src/main/java/io/appium/java_client/remote/options/UnhandledPromptBehavior.java b/src/main/java/io/appium/java_client/remote/options/UnhandledPromptBehavior.java index 0068dfe42..3aa5f4add 100644 --- a/src/main/java/io/appium/java_client/remote/options/UnhandledPromptBehavior.java +++ b/src/main/java/io/appium/java_client/remote/options/UnhandledPromptBehavior.java @@ -38,7 +38,7 @@ public String toString() { */ public static UnhandledPromptBehavior fromString(String value) { return Arrays.stream(values()) - .filter((v) -> v.toString().equals(value)) + .filter(v -> v.toString().equals(value)) .findFirst() .orElseThrow(() -> new IllegalArgumentException( String.format("Unhandled prompt behavior '%s' is not supported. " 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 08f289289..f37b89e29 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 @@ -145,15 +145,15 @@ public boolean isRunning() { } private void ping(Duration timeout) throws UrlChecker.TimeoutException, MalformedURLException { - URL url = getUrl(); - String host = url.getHost(); + URL baseURL = getUrl(); + String host = baseURL.getHost(); // The operating system will block direct access to the universal broadcast IP address if (host.equals(BROADCAST_IP4_ADDRESS)) { - url = replaceHost(url, BROADCAST_IP4_ADDRESS, "127.0.0.1"); + baseURL = replaceHost(baseURL, BROADCAST_IP4_ADDRESS, "127.0.0.1"); } else if (host.equals(BROADCAST_IP6_ADDRESS)) { - url = replaceHost(url, BROADCAST_IP6_ADDRESS, "::1"); + baseURL = replaceHost(baseURL, BROADCAST_IP6_ADDRESS, "::1"); } - URL status = addSuffix(url, "/status"); + URL status = addSuffix(baseURL, "/status"); new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); } @@ -163,6 +163,7 @@ private void ping(Duration timeout) throws UrlChecker.TimeoutException, Malforme * @throws AppiumServerHasNotBeenStartedLocallyException If an error occurs while spawning the child process. * @see #stop() */ + @Override public void start() throws AppiumServerHasNotBeenStartedLocallyException { lock.lock(); try { @@ -182,7 +183,7 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } catch (Exception e) { final Optional output = Optional.ofNullable(process) .map(CommandLine::getStdOut) - .filter((o) -> !StringUtils.isBlank(o)); + .filter(o -> !StringUtils.isBlank(o)); destroyProcess(); List errorLines = new ArrayList<>(); errorLines.add("The local appium server has not been started"); @@ -197,7 +198,7 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { String.format("Node.js executable path: %s", nodeJSExec.getAbsolutePath()) ); errorLines.add(String.format("Arguments: %s", nodeJSArgs)); - output.ifPresent((o) -> errorLines.add(String.format("Output: %s", o))); + output.ifPresent(o -> errorLines.add(String.format("Output: %s", o))); throw new AppiumServerHasNotBeenStartedLocallyException( StringUtils.joinWith("\n", errorLines), e ); @@ -302,8 +303,8 @@ public void addOutPutStream(OutputStream outputStream) { */ public void addOutPutStreams(List outputStreams) { checkNotNull(outputStreams, "outputStreams parameter is NULL!"); - for (OutputStream stream : outputStreams) { - addOutPutStream(stream); + for (OutputStream outputStream : outputStreams) { + addOutPutStream(outputStream); } } 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 0e354d441..774399a9c 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 @@ -81,9 +81,9 @@ public final class AppiumServiceBuilder private String ipAddress = BROADCAST_IP4_ADDRESS; private Capabilities capabilities; private boolean autoQuoteCapabilitiesOnWindows = false; - private static final Function APPIUM_JS_NOT_EXIST_ERROR = (fullPath) -> String.format( + 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) -> + private static final Function NODE_JS_NOT_EXIST_ERROR = fullPath -> String.format("The main NodeJS executable does not exist at '%s'", fullPath.getAbsolutePath()); private static final List PATH_CAPABILITIES = ImmutableList.of(AndroidMobileCapabilityType.KEYSTORE_PATH, diff --git a/src/main/java/io/appium/java_client/service/local/ListOutputStream.java b/src/main/java/io/appium/java_client/service/local/ListOutputStream.java index 89820f70a..7173963ad 100644 --- a/src/main/java/io/appium/java_client/service/local/ListOutputStream.java +++ b/src/main/java/io/appium/java_client/service/local/ListOutputStream.java @@ -53,12 +53,14 @@ Optional remove(OutputStream stream) { } } + @Override public void flush() throws IOException { for (OutputStream stream : streams) { stream.flush(); } } + @Override public void close() throws IOException { for (OutputStream stream : streams) { stream.close(); diff --git a/src/main/java/io/appium/java_client/windows/options/WindowsOptions.java b/src/main/java/io/appium/java_client/windows/options/WindowsOptions.java index 8a1f0eb8f..fd4d125b0 100644 --- a/src/main/java/io/appium/java_client/windows/options/WindowsOptions.java +++ b/src/main/java/io/appium/java_client/windows/options/WindowsOptions.java @@ -28,7 +28,7 @@ import java.util.Optional; /** - * https://github.com/appium/appium-windows-driver#usage + * https://github.com/appium/appium-windows-driver#usage */ public class WindowsOptions extends BaseOptions implements SupportsAppOption, @@ -65,7 +65,7 @@ private void setCommonOptions() { * each key must be a valid PowerShell script or command to be * executed prior to the WinAppDriver session startup. * See - * https://github.com/appium/appium-windows-driver#power-shell-commands-execution + * https://github.com/appium/appium-windows-driver#power-shell-commands-execution * for more details. * * @param script E.g. {script: 'Get-Process outlook -ErrorAction SilentlyContinue'}. @@ -83,7 +83,7 @@ public WindowsOptions setPrerun(PowerShellData script) { public Optional getPrerun() { //noinspection unchecked return Optional.ofNullable(getCapability(PRERUN_OPTION)) - .map((v) -> new PowerShellData((Map) v)); + .map(v -> new PowerShellData((Map) v)); } /** @@ -91,7 +91,7 @@ public Optional getPrerun() { * each key must be a valid PowerShell script or command to be * executed after an WinAppDriver session is finished. * See - * https://github.com/appium/appium-windows-driver#power-shell-commands-execution + * https://github.com/appium/appium-windows-driver#power-shell-commands-execution * for more details. * * @param script E.g. {script: 'Get-Process outlook -ErrorAction SilentlyContinue'}. @@ -109,6 +109,6 @@ public WindowsOptions setPostrun(PowerShellData script) { public Optional getPostrun() { //noinspection unchecked return Optional.ofNullable(getCapability(POSTRUN_OPTION)) - .map((v) -> new PowerShellData((Map) v)); + .map(v -> new PowerShellData((Map) v)); } } From 7157251e9ec6de0566e9bb10a9b8dd9582d5e041 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 15:30:15 +0300 Subject: [PATCH 143/314] build(deps): Bump org.projectlombok:lombok from 1.18.28 to 1.18.30 (#2022) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.28 to 1.18.30. - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.28...v1.18.30) --- 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 1021b18b2..958e67905 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.28' - annotationProcessor 'org.projectlombok:lombok:1.18.28' + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' api ('org.seleniumhq.selenium:selenium-api') { version { From 0a2bfee430435921637677e78f9abc9e210e2f1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:47:50 +0300 Subject: [PATCH 144/314] build(deps): Bump org.seleniumhq.selenium:selenium-bom (#2024) Bumps [org.seleniumhq.selenium:selenium-bom](https://github.com/SeleniumHQ/selenium) from 4.12.1 to 4.13.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/commits/selenium-4.13.0) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-bom 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 958e67905..44cd0c8d1 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.12.1') + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.13.0') testImplementation 'org.seleniumhq.selenium:selenium-api' testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' testImplementation 'org.seleniumhq.selenium:selenium-support' From 0ac4b4236319f74b20c846dfda3af4acf2fe5eb7 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Wed, 27 Sep 2023 10:40:44 -0700 Subject: [PATCH 145/314] fix: class of proxy method in AppiumClientConfig (#2026) --- src/main/java/io/appium/java_client/AppiumClientConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index b25a3032d..c8e123fc9 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -147,7 +147,7 @@ public AppiumClientConfig withRetries() { @Override - public ClientConfig proxy(Proxy proxy) { + public AppiumClientConfig proxy(Proxy proxy) { ClientConfig clientConfig = super.proxy(proxy); return buildAppiumClientConfig(clientConfig, directConnect); } From e7482971479935ecd304ad0e231c68b90b7243f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:09:51 +0300 Subject: [PATCH 146/314] build(deps): Bump commons-io:commons-io from 2.13.0 to 2.14.0 (#2027) Bumps commons-io:commons-io from 2.13.0 to 2.14.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 44cd0c8d1..ff247ab7f 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.13.0' - implementation 'commons-io:commons-io:2.13.0' + implementation 'commons-io:commons-io:2.14.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' From a5a25b51a08062e96b0189e8a2aece031fdc06da Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 3 Oct 2023 10:50:50 +0300 Subject: [PATCH 147/314] docs: Add Selenium 4.13.0 to compatibility matrix (#2025) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dacbe1340..97ecb5017 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client --------------------|----------------- - `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004) + `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` From f875c3b86aa0f14e27a91d0a3d1f8e51d02e9054 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 3 Oct 2023 11:32:26 +0300 Subject: [PATCH 148/314] chore: Automate more static code checks (#2028) --- config/checkstyle/appium-style.xml | 10 ++++++++++ .../java/io/appium/java_client/AppiumDriver.java | 6 +++--- .../appium/java_client/CommandExecutionHelper.java | 6 +++--- src/main/java/io/appium/java_client/LogsEvents.java | 4 ++-- .../java/io/appium/java_client/MobileCommand.java | 3 ++- .../android/AndroidMobileCommandHelper.java | 3 +-- .../java_client/internal/CapabilityHelpers.java | 6 +++--- .../java/io/appium/java_client/internal/Config.java | 4 ++-- .../java_client/internal/ReflectionHelpers.java | 6 +++--- .../appium/java_client/internal/SessionHelpers.java | 6 +++--- .../pagefactory/AppiumFieldDecorator.java | 4 ++-- .../pagefactory/DefaultElementByBuilder.java | 2 +- .../pagefactory/OverrideWidgetReader.java | 6 +++--- .../java_client/pagefactory/ThrowableUtil.java | 6 +++--- .../java_client/pagefactory/WidgetInterceptor.java | 2 +- .../pagefactory/WidgetListInterceptor.java | 2 +- .../appium/java_client/pagefactory/WithTimeout.java | 7 +++---- .../pagefactory/utils/WebDriverUnpackUtility.java | 6 +++--- .../io/appium/java_client/proxy/Interceptor.java | 12 ++++++------ .../remote/options/BaseMapOptionData.java | 8 ++++---- .../java_client/remote/options/BaseOptions.java | 2 +- .../java/io/appium/java_client/TestResources.java | 3 +++ src/test/java/io/appium/java_client/TestUtils.java | 3 +++ .../java_client/android/AndroidDriverTest.java | 7 +++---- .../android/AndroidLogcatListenerTest.java | 2 +- .../appium/java_client/android/BaseAndroidTest.java | 1 + .../appium/java_client/android/BaseEspressoTest.java | 1 + .../drivers/options/OptionsBuildingTest.java | 2 +- .../java/io/appium/java_client/ios/BaseIOSTest.java | 1 + .../java/io/appium/java_client/ios/IOSAlertTest.java | 4 ++-- .../java_client/ios/IOSSyslogListenerTest.java | 2 +- 31 files changed, 77 insertions(+), 60 deletions(-) diff --git a/config/checkstyle/appium-style.xml b/config/checkstyle/appium-style.xml index 2feb5eeff..630949b66 100755 --- a/config/checkstyle/appium-style.xml +++ b/config/checkstyle/appium-style.xml @@ -128,6 +128,7 @@

Read appium-chromium-driver + * for more details on how to configure and use it.

+ @@ -198,6 +199,12 @@ + + + + + + @@ -208,4 +215,7 @@ + + + diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index ead0a05d0..749a0204d 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -70,7 +70,7 @@ public class AppiumDriver extends RemoteWebDriver implements CanRememberExtensionPresence, HasSettings { - private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); + private static final ErrorHandler ERROR_HANDLER = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters private final URL remoteAddress; protected final RemoteLocationContext locationContext; @@ -89,7 +89,7 @@ public AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, capabilities); this.executeMethod = new AppiumExecutionMethod(this); locationContext = new RemoteLocationContext(executeMethod); - super.setErrorHandler(errorHandler); + super.setErrorHandler(ERROR_HANDLER); this.remoteAddress = executor.getAddressOfRemoteServer(); } @@ -167,7 +167,7 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa setCommandExecutor(executor); this.executeMethod = new AppiumExecutionMethod(this); locationContext = new RemoteLocationContext(executeMethod); - super.setErrorHandler(errorHandler); + super.setErrorHandler(ERROR_HANDLER); this.remoteAddress = executor.getAddressOfRemoteServer(); setSessionId(sessionAddress.getId()); diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 7abfef612..692f7cf0f 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -17,8 +17,6 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; @@ -28,9 +26,11 @@ import static org.openqa.selenium.remote.DriverCommand.EXECUTE_SCRIPT; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class CommandExecutionHelper { + private CommandExecutionHelper() { + } + @Nullable public static T execute( ExecutesMethod executesMethod, Map.Entry> keyValuePair diff --git a/src/main/java/io/appium/java_client/LogsEvents.java b/src/main/java/io/appium/java_client/LogsEvents.java index a29d9eecf..33f50d2db 100644 --- a/src/main/java/io/appium/java_client/LogsEvents.java +++ b/src/main/java/io/appium/java_client/LogsEvents.java @@ -63,8 +63,8 @@ default ServerEvents getEvents() { .stream() .map((Map cmd) -> new CommandEvent( (String) cmd.get("cmd"), - ((Long) cmd.get("startTime")), - ((Long) cmd.get("endTime")) + (Long) cmd.get("startTime"), + (Long) cmd.get("endTime") )) .collect(Collectors.toList()); diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 721c854a7..d61a3ef01 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -38,6 +38,7 @@ * Most of these commands are platform-specific obsolete things and should eventually be replaced with * calls to corresponding `mobile:` extensions, so we don't abuse non-w3c APIs */ +@SuppressWarnings({"checkstyle:HideUtilityClassConstructor", "checkstyle:ConstantName"}) public class MobileCommand { //General @Deprecated @@ -434,7 +435,7 @@ public static ImmutableMap prepareArguments(String[] params, Object[] values) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (int i = 0; i < params.length; i++) { - if (!StringUtils.isBlank(params[i]) && (values[i] != null)) { + if (!StringUtils.isBlank(params[i]) && values[i] != null) { builder.put(params[i], values[i]); } } diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index 1bed116d7..562eb9172 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -214,8 +214,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { String intentAction, String intentCategory, String intentFlags, String optionalIntentArguments, boolean stopApp) throws IllegalArgumentException { - checkArgument((!StringUtils.isBlank(appPackage) - && !StringUtils.isBlank(appActivity)), + checkArgument(!StringUtils.isBlank(appPackage) && !StringUtils.isBlank(appActivity), String.format("'%s' and '%s' are required.", "appPackage", "appActivity")); String targetWaitPackage = !StringUtils.isBlank(appWaitPackage) ? appWaitPackage : ""; 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 5280b8e17..855a40cdb 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -16,8 +16,6 @@ package io.appium.java_client.internal; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import org.openqa.selenium.Capabilities; import javax.annotation.Nullable; @@ -28,10 +26,12 @@ import java.util.List; import java.util.function.Function; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public class CapabilityHelpers { public static final String APPIUM_PREFIX = "appium:"; + private CapabilityHelpers() { + } + /** * Helper that is used for capability values retrieval. * Supports both prefixed W3C and "classic" capability names. 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 83bfb7995..4413f28ab 100644 --- a/src/main/java/io/appium/java_client/internal/Config.java +++ b/src/main/java/io/appium/java_client/internal/Config.java @@ -11,7 +11,7 @@ public class Config { private static Config mainInstance = null; private static final String MAIN_CONFIG = "main.properties"; - private static final Map cache = new ConcurrentHashMap<>(); + private static final Map CACHE = new ConcurrentHashMap<>(); private final String configName; /** @@ -58,7 +58,7 @@ public T getValue(String key, Class valueType) { * @throws ClassCastException if the retrieved value cannot be cast to `valueType` type */ public Optional getOptionalValue(String key, Class valueType) { - final Properties cachedProps = cache.computeIfAbsent(configName, k -> { + final Properties cachedProps = CACHE.computeIfAbsent(configName, k -> { try (InputStream configFileStream = getClass().getClassLoader().getResourceAsStream(configName)) { final Properties p = new Properties(); p.load(configFileStream); diff --git a/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java index abe16a170..10375e2ad 100644 --- a/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java +++ b/src/main/java/io/appium/java_client/internal/ReflectionHelpers.java @@ -16,15 +16,15 @@ package io.appium.java_client.internal; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import org.openqa.selenium.WebDriverException; import java.lang.reflect.Field; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public class ReflectionHelpers { + private ReflectionHelpers() { + } + /** * Sets the given value to a private instance field. * diff --git a/src/main/java/io/appium/java_client/internal/SessionHelpers.java b/src/main/java/io/appium/java_client/internal/SessionHelpers.java index c028bdfb5..51371dbd1 100644 --- a/src/main/java/io/appium/java_client/internal/SessionHelpers.java +++ b/src/main/java/io/appium/java_client/internal/SessionHelpers.java @@ -16,9 +16,7 @@ package io.appium.java_client.internal; -import lombok.AccessLevel; import lombok.Data; -import lombok.NoArgsConstructor; import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.WebDriverException; @@ -27,10 +25,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public class SessionHelpers { private static final Pattern SESSION = Pattern.compile("/session/([^/]+)"); + private SessionHelpers() { + } + @Data public static class SessionAddress { private final URL serverUrl; private final String id; 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 2d5daaa03..b0c8cd428 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -62,7 +62,7 @@ */ public class AppiumFieldDecorator implements FieldDecorator { - private static final List> availableElementClasses = ImmutableList.of( + private static final List> AVAILABLE_ELEMENT_CLASSES = ImmutableList.of( WebElement.class, RemoteWebElement.class ); @@ -169,7 +169,7 @@ protected boolean isDecoratableList(Field field) { List bounds = (listType instanceof TypeVariable) ? Arrays.asList(((TypeVariable) listType).getBounds()) : Collections.emptyList(); - return availableElementClasses.stream() + return AVAILABLE_ELEMENT_CLASSES.stream() .anyMatch(webElClass -> webElClass.equals(listType) || bounds.contains(webElClass)); } }; 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 23195e5ce..5cb10c9de 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -186,7 +186,7 @@ protected By buildMobileNativeBy() { @Override public boolean isLookupCached() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); - return (annotatedElement.getAnnotation(CacheLookup.class) != null); + return annotatedElement.getAnnotation(CacheLookup.class) != null; } private By returnMappedBy(By byDefault, By nativeAppBy) { diff --git a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java index 9f8b59c17..e08f413dd 100644 --- a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java +++ b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java @@ -17,8 +17,6 @@ package io.appium.java_client.pagefactory; import io.appium.java_client.pagefactory.bys.ContentType; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; @@ -31,7 +29,6 @@ import static io.appium.java_client.remote.MobilePlatform.IOS; import static io.appium.java_client.remote.MobilePlatform.WINDOWS; -@NoArgsConstructor(access = AccessLevel.PRIVATE) class OverrideWidgetReader { private static final Class EMPTY = Widget.class; private static final String HTML = "html"; @@ -39,6 +36,9 @@ class OverrideWidgetReader { private static final String IOS_XCUIT_AUTOMATION = "iOSXCUITAutomation"; private static final String WINDOWS_AUTOMATION = "windowsAutomation"; + private OverrideWidgetReader() { + } + @SuppressWarnings("unchecked") private static Class getConvenientClass(Class declaredClass, AnnotatedElement annotatedElement, String method) { diff --git a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java index f5b310702..af09676f7 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java +++ b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java @@ -16,17 +16,17 @@ package io.appium.java_client.pagefactory; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import org.openqa.selenium.InvalidSelectorException; import org.openqa.selenium.StaleElementReferenceException; import java.lang.reflect.InvocationTargetException; -@NoArgsConstructor(access = AccessLevel.PRIVATE) class ThrowableUtil { private static final String INVALID_SELECTOR_PATTERN = "Invalid locator strategy:"; + private ThrowableUtil() { + } + protected static boolean isInvalidSelectorRootCause(Throwable e) { if (e == null) { return false; diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java index d00c6e3c9..bfc358bd8 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java @@ -67,7 +67,7 @@ protected Object getObject(WebElement element, Method method, Object[] args) thr ContentType type = getCurrentContentType(element); WebElement cachedElement = cachedElementReference == null ? null : cachedElementReference.get(); if (cachedElement == null || !cachedInstances.containsKey(type) - || (locator != null && !((CacheableLocator) locator).isLookUpCached()) + || locator != null && !((CacheableLocator) locator).isLookUpCached() ) { cachedElementReference = new WeakReference<>(element); diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java index 031a0e624..ff9983f8c 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java @@ -71,7 +71,7 @@ protected Object getObject(List elements, Method method, Object[] ar .filter(Objects::nonNull) .collect(Collectors.toList()); if (cachedElements.size() != cachedWidgets.size() - || (locator != null && !((CacheableLocator) locator).isLookUpCached())) { + || locator != null && !((CacheableLocator) locator).isLookUpCached()) { cachedWidgets.clear(); cachedElementReferences.clear(); diff --git a/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java b/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java index 0727bc2b3..0b04dadf2 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java +++ b/src/main/java/io/appium/java_client/pagefactory/WithTimeout.java @@ -16,9 +16,6 @@ package io.appium.java_client.pagefactory; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -46,8 +43,10 @@ */ ChronoUnit chronoUnit(); - @NoArgsConstructor(access = AccessLevel.PRIVATE) class DurationBuilder { + private DurationBuilder() { + } + static Duration build(WithTimeout withTimeout) { return Duration.of(withTimeout.time(), withTimeout.chronoUnit()); } diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 9df129930..6d31fcc5a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -18,8 +18,6 @@ import io.appium.java_client.HasBrowserCheck; import io.appium.java_client.pagefactory.bys.ContentType; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import org.openqa.selenium.ContextAware; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -33,10 +31,12 @@ import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; + private WebDriverUnpackUtility() { + } + /** * This method extract an instance of {@link WebDriver} from the given {@link SearchContext}. * @param searchContext is an instance of {@link SearchContext}. It may be the instance of diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index aca474044..fa4d72405 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -16,8 +16,6 @@ package io.appium.java_client.proxy; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.implementation.bind.annotation.RuntimeType; @@ -31,9 +29,11 @@ import java.util.UUID; import java.util.concurrent.Callable; -@NoArgsConstructor(access = AccessLevel.PRIVATE) public class Interceptor { - private static final Logger logger = LoggerFactory.getLogger(Interceptor.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Interceptor.class); + + private Interceptor() { + } /** * A magic method used to wrap public method calls in classes @@ -64,7 +64,7 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - logger.atError() + LOGGER.atError() .addArgument(() -> self.getClass().getName()) .addArgument(method::getName) .log("Got an unexpected error in beforeCall listener of {}.{} method", e); @@ -110,7 +110,7 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - logger.atError() + LOGGER.atError() .addArgument(() -> self.getClass().getName()) .addArgument(method::getName) .log("Got an unexpected error in afterCall listener of {}.{} method", e); diff --git a/src/main/java/io/appium/java_client/remote/options/BaseMapOptionData.java b/src/main/java/io/appium/java_client/remote/options/BaseMapOptionData.java index a7b75e3d7..dc5ada3a5 100644 --- a/src/main/java/io/appium/java_client/remote/options/BaseMapOptionData.java +++ b/src/main/java/io/appium/java_client/remote/options/BaseMapOptionData.java @@ -26,7 +26,7 @@ public abstract class BaseMapOptionData> { private Map options; - private static final Gson gson = new Gson(); + private static final Gson GSON = new Gson(); public BaseMapOptionData() { } @@ -37,7 +37,7 @@ public BaseMapOptionData(Map options) { public BaseMapOptionData(String json) { //noinspection unchecked - this((Map) gson.fromJson(json, Map.class)); + this((Map) GSON.fromJson(json, Map.class)); } /** @@ -78,11 +78,11 @@ public Map toMap() { } public JsonObject toJson() { - return gson.toJsonTree(toMap()).getAsJsonObject(); + return GSON.toJsonTree(toMap()).getAsJsonObject(); } @Override public String toString() { - return gson.toJson(toMap()); + return GSON.toJson(toMap()); } } diff --git a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java index 7ef52b7e4..dff4f5c44 100644 --- a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java +++ b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java @@ -96,7 +96,7 @@ public Platform getPlatformName() { } try { - return Platform.fromString((String.valueOf(cap))); + return Platform.fromString(String.valueOf(cap)); } catch (WebDriverException e) { return null; } diff --git a/src/test/java/io/appium/java_client/TestResources.java b/src/test/java/io/appium/java_client/TestResources.java index 9d188fb58..6928cdcaf 100644 --- a/src/test/java/io/appium/java_client/TestResources.java +++ b/src/test/java/io/appium/java_client/TestResources.java @@ -5,6 +5,9 @@ import static io.appium.java_client.TestUtils.resourcePathToLocalPath; public class TestResources { + private TestResources() { + } + public static Path apiDemosApk() { return resourcePathToLocalPath("apps/ApiDemos-debug.apk"); } diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index f2ed2792e..6a968a4e1 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -19,6 +19,9 @@ import java.util.function.Supplier; public class TestUtils { + private TestUtils() { + } + public static String getLocalIp4Address() throws SocketException, UnknownHostException { // https://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java try (final DatagramSocket socket = new DatagramSocket()) { diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index 45f048272..095866e89 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -220,7 +220,7 @@ public void runAppInBackgroundTest() { long time = System.currentTimeMillis(); driver.runAppInBackground(Duration.ofSeconds(4)); long timeAfter = System.currentTimeMillis(); - assert (timeAfter - time > 3000); + assert timeAfter - time > 3000; } @Test @@ -237,9 +237,8 @@ public void testApplicationsManagement() throws InterruptedException { @Test public void pullFileTest() { - byte[] data = - driver.pullFile("/data/system/users/userlist.xml"); - assert (data.length > 0); + byte[] data = driver.pullFile("/data/system/users/userlist.xml"); + assert data.length > 0; } @Test diff --git a/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java b/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java index e7a40383c..618da2e32 100644 --- a/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java @@ -16,7 +16,7 @@ public void verifyLogcatListenerCanBeAssigned() { final Semaphore messageSemaphore = new Semaphore(1); final Duration timeout = Duration.ofSeconds(15); - driver.addLogcatMessagesListener((msg) -> messageSemaphore.release()); + driver.addLogcatMessagesListener(msg -> messageSemaphore.release()); driver.addLogcatConnectionListener(() -> System.out.println("Connected to the web socket")); driver.addLogcatDisconnectionListener(() -> System.out.println("Disconnected from the web socket")); driver.addLogcatErrorsListener(Throwable::printStackTrace); 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 28c1c95f4..21142e631 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -25,6 +25,7 @@ import static io.appium.java_client.TestResources.apiDemosApk; +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") public class BaseAndroidTest { public static final String APP_ID = "io.appium.android.apis"; protected static final int PORT = 4723; 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 cfc690021..d312587bd 100644 --- a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java +++ b/src/test/java/io/appium/java_client/android/BaseEspressoTest.java @@ -24,6 +24,7 @@ import static io.appium.java_client.TestResources.apiDemosApk; +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") public class BaseEspressoTest { private static AppiumDriverLocalService service; diff --git a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java index 04574448c..7a22ffd93 100644 --- a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java +++ b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java @@ -67,7 +67,7 @@ public void canBuildXcuiTestOptions() throws MalformedURLException { assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); assertEquals(new URL("http://localhost:8000/"), options.getWdaBaseUrl().orElse(null)); assertNotNull(options.getPermissions() - .map((v) -> v.getAppPermissions("com.apple.MobileSafari")) + .map(v -> v.getAppPermissions("com.apple.MobileSafari")) .orElse(null)); assertEquals(10L, (long) options.getSafariSocketChunkSize().orElse(0)); assertEquals(1L, options.getCommandTimeouts().orElse(null).left() 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 fe5764e7b..cc571b6fa 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -22,6 +22,7 @@ import java.time.Duration; +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") public class BaseIOSTest { protected static AppiumDriverLocalService service; 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 7e1fb6528..7d91e596a 100644 --- a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAlertTest.java @@ -38,16 +38,16 @@ public class IOSAlertTest extends AppIOSTest { private static final Duration ALERT_TIMEOUT = Duration.ofSeconds(5); private static final int CLICK_RETRIES = 2; + private static final String IOS_AUTOMATION_TEXT = "show alert"; private final WebDriverWait waiter = new WebDriverWait(driver, ALERT_TIMEOUT); - private static final String iOSAutomationText = "show alert"; private void ensureAlertPresence() { int retry = 0; // CI might not be performant enough, so we need to retry while (true) { try { - driver.findElement(AppiumBy.accessibilityId(iOSAutomationText)).click(); + driver.findElement(AppiumBy.accessibilityId(IOS_AUTOMATION_TEXT)).click(); } catch (WebDriverException e) { // ignore } diff --git a/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java b/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java index 34429e5df..b2c4c96bd 100644 --- a/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java @@ -16,7 +16,7 @@ public void verifySyslogListenerCanBeAssigned() { final Semaphore messageSemaphore = new Semaphore(1); final Duration timeout = Duration.ofSeconds(15); - driver.addSyslogMessagesListener((msg) -> messageSemaphore.release()); + driver.addSyslogMessagesListener(msg -> messageSemaphore.release()); driver.addSyslogConnectionListener(() -> System.out.println("Connected to the web socket")); driver.addSyslogDisconnectionListener(() -> System.out.println("Disconnected from the web socket")); driver.addSyslogErrorsListener(Throwable::printStackTrace); From c0088b8f25a0734fc3b8622a3a03d727e2387a0a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 6 Oct 2023 13:04:32 +0300 Subject: [PATCH 149/314] chore: Bump Gradle from `8.3` to `8.4` (#2029) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index ff247ab7f..f6b39cebd 100644 --- a/build.gradle +++ b/build.gradle @@ -189,7 +189,7 @@ signing { } wrapper { - gradleVersion = '8.3' + gradleVersion = '8.4' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d11cdd907..8838ba97b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5..1aa94a426 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From 8386bd28f024a22f07a650651780fd3e24db0cae Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 6 Oct 2023 16:41:15 +0200 Subject: [PATCH 150/314] chore: Limit the maximum selenium version (#2031) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f6b39cebd..f2269fc6a 100644 --- a/build.gradle +++ b/build.gradle @@ -34,19 +34,19 @@ dependencies { api ('org.seleniumhq.selenium:selenium-api') { version { - strictly "[${seleniumVersion}, 5.0)" + strictly "[${seleniumVersion}, 4.14)" prefer "${seleniumVersion}" } } api ('org.seleniumhq.selenium:selenium-remote-driver') { version { - strictly "[${seleniumVersion}, 5.0)" + strictly "[${seleniumVersion}, 4.14)" prefer "${seleniumVersion}" } } api ('org.seleniumhq.selenium:selenium-support') { version { - strictly "[${seleniumVersion}, 5.0)" + strictly "[${seleniumVersion}, 4.14)" prefer "${seleniumVersion}" } } From f838c7116d12d0368c21680b6d2399ebac76e138 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 6 Oct 2023 17:25:27 +0200 Subject: [PATCH 151/314] chore: Remove the obsolete commons-validator dependency (#2032) --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index f2269fc6a..a2286e1e7 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ dependencies { } } implementation 'com.google.code.gson:gson:2.10.1' - implementation 'commons-validator:commons-validator:1.7' implementation 'org.apache.commons:commons-lang3:3.13.0' implementation 'commons-io:commons-io:2.14.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" From c6f21546d9d10945d58105317466c04d6df4c4d3 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Sun, 8 Oct 2023 23:34:50 +0530 Subject: [PATCH 152/314] Release 8.6.0 and update release notes (#2033) --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1920ce1..607a44c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_8.6.0_ + +- **[BUG FIX]** + - Exclude abstract methods from proxy matching. [#1937](https://github.com/appium/java-client/pull/1937) + - AppiumClientConfig#readTimeout to call super.readTimeout. [#1959](https://github.com/appium/java-client/pull/1959) + - Use weak references to elements inside of interceptor objects. [#1981](https://github.com/appium/java-client/pull/1981) + - Correct spelling and semantic mistakes in method naming. [#1970](https://github.com/appium/java-client/pull/1970) + - Change scope of selenium-support dependency to compile. [#2019](https://github.com/appium/java-client/pull/2019) + - Fix Code style issues to match Java standards. [#2017](https://github.com/appium/java-client/pull/2017) + - class of proxy method in AppiumClientConfig. [#2026](https://github.com/appium/java-client/pull/2026) +- **[ENHANCEMENTS]** + - Mark Windows page object annotations as deprecated. [#1938](https://github.com/appium/java-client/pull/1938) + - Deprecate obsolete capabilities constants. [#1961](https://github.com/appium/java-client/pull/1961) + - patch AutomationName with Chromium. [#1993](https://github.com/appium/java-client/pull/1993) + - Implementation of Chromium driver plus capabilities. [#2003](https://github.com/appium/java-client/pull/2003) +- **[REFACTOR]** + - Increase server start timeout for iOS tests. [#1983](https://github.com/appium/java-client/pull/1983) + - Fix Android test: --base-path arg must start with /. [#1952](https://github.com/appium/java-client/pull/1952) + - Added fixes for No service provider found for `io.appium.java_client.events.api.Listener`. [#1975](https://github.com/appium/java-client/pull/1975) + - Run tests against latest Selenium release. [#1978](https://github.com/appium/java-client/pull/1978) + - Use server releases from the main branch for testing. [#1994](https://github.com/appium/java-client/pull/1994) + - Remove obsolete API calls from tests. [#2006](https://github.com/appium/java-client/pull/2006) + - Automate more static code checks. [#2028](https://github.com/appium/java-client/pull/2028) + - Limit the maximum selenium version to 4.14. [#2031](https://github.com/appium/java-client/pull/2031) + - Remove the obsolete commons-validator dependency. [#2032](https://github.com/appium/java-client/pull/2032) +- **[DOCUMENTATION]** + - Add the latest versions of clients to the compatibility matrix. [#1935](https://github.com/appium/java-client/pull/1935) + - Added correct url path for the latest appium documentation. [#1974](https://github.com/appium/java-client/pull/1974) + - Add Selenium 4.11.0, 4.12.0, 4.12.1 & 4.13.0 to compatibility matrix. [#1986](https://github.com/appium/java-client/pull/1986) & [#1999](https://github.com/appium/java-client/pull/1999) & [#2002](https://github.com/appium/java-client/pull/2025) & [#1986](https://github.com/appium/java-client/pull/2025) + - Add known compatibility issue for Selenium 4.12.1. [#2008](https://github.com/appium/java-client/pull/2008) +- **[DEPENDENCY UPDATES]** + - `org.owasp.dependencycheck` was updated to 8.4.0. + - `org.junit.jupiter:junit-jupiter` was updated to 5.10.0. + - `commons-io:commons-io` was updated to 2.14.0. + - `checkstyle` was updated to 10.12.1. + - `org.apache.commons:commons-lang3` was updated to 3.13.0. + - `gradle` was updated to 8.4.0. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.5.3. + - `org.seleniumhq.selenium:selenium-bom` was updated to 4.13.0. + - `org.projectlombok:lombok` was updated to 1.18.30. + *8.5.1* - **[BUG FIX]** - Use correct exception type for fallback at file/folder pulling. [#1912](https://github.com/appium/java-client/pull/1912) diff --git a/gradle.properties b/gradle.properties index e7f91060b..8542862b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.9.1 # Please increment the value in a release -appiumClient.version=8.5.1 +appiumClient.version=8.6.0 From 39c2526775d01ef7224557c9a1587cc97274c58a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 12 Oct 2023 20:34:02 +0200 Subject: [PATCH 153/314] refactor!: Update selenium dependencies to v4.14 (#2036) BREAKING CHANGE: The library is now only compatible to Java11 and above BREAKING CHANGE: The previously deprecated MobileBy class has been removed. Use AppiumBy instead BREAKING CHANGE: The previously deprecated launchApp, resetApp and closeApp methods have been removed. Use extension methods instead BREAKING CHANGE: The previously deprecated WindowsBy class and related location strategies have been removed BREAKING CHANGE: The previously deprecated ByAll class has been removed in favour of the Selenium one BREAKING CHANGE: The previously deprecated AndroidMobileCapabilityType interface has been removed. Use driver options instead BREAKING CHANGE: The previously deprecated IOSMobileCapabilityType interface has been removed. Use driver options instead BREAKING CHANGE: The previously deprecated MobileCapabilityType interface has been removed. Use driver options instead BREAKING CHANGE: The previously deprecated MobileOptions class has been removed. Use driver options instead BREAKING CHANGE: The previously deprecated YouiEngineCapabilityType interface has been removed. Use driver options instead BREAKING CHANGE: Removed several misspelled methods. Use properly spelled alternatives instead BREAKING CHANGE: Removed startActivity method from AndroidDriver. Use 'mobile: startActivity' extension method instead BREAKING CHANGE: Removed the previously deprecated APPIUM constant from the AutomationName interface BREAKING CHANGE: Removed the previously deprecated PRE_LAUNCH value from the GeneralServerFlag enum BREAKING CHANGE: Moved AppiumUserAgentFilter class to io.appium.java_client.internal.filters package Removed dependencies to Apache Commons libraries --- .github/workflows/gradle.yml | 11 +- .github/workflows/publish.yml | 2 +- build.gradle | 27 +- gradle.properties | 2 +- .../java/io/appium/java_client/AppiumBy.java | 6 +- .../java_client/AppiumClientConfig.java | 28 +- .../io/appium/java_client/AppiumDriver.java | 38 +- .../io/appium/java_client/ComparesImages.java | 16 +- .../appium/java_client/HasBrowserCheck.java | 8 +- .../java_client/HidesKeyboardWithKeyName.java | 15 - .../java/io/appium/java_client/MobileBy.java | 348 ------------ .../io/appium/java_client/MobileCommand.java | 5 +- .../io/appium/java_client/PushesFiles.java | 4 +- .../SupportsLegacyAppManagement.java | 59 -- .../appium/java_client/android/Activity.java | 6 +- .../java_client/android/AndroidDriver.java | 18 +- .../android/AndroidMobileCommandHelper.java | 66 --- .../java_client/android/StartsActivity.java | 29 - .../SupportsShowChromedriverLogOption.java | 21 - .../filters/AppiumIdempotencyFilter.java | 38 ++ .../filters}/AppiumUserAgentFilter.java | 3 +- .../io/appium/java_client/ios/IOSDriver.java | 4 +- .../SupportsCustomSslCertOption.java | 10 - .../SupportsSimulatorTracePointerOption.java | 10 - .../pagefactory/AppiumFieldDecorator.java | 6 +- .../pagefactory/DefaultElementByBuilder.java | 5 - .../pagefactory/HowToUseLocators.java | 10 - .../java_client/pagefactory/WindowsBy.java | 74 --- .../pagefactory/WindowsFindAll.java | 51 -- .../pagefactory/WindowsFindBy.java | 88 --- .../pagefactory/WindowsFindByAllSet.java | 26 - .../pagefactory/WindowsFindByChainSet.java | 26 - .../pagefactory/WindowsFindBySet.java | 42 -- .../pagefactory/WindowsFindBys.java | 49 -- .../pagefactory/bys/builder/ByAll.java | 65 --- .../pagefactory/bys/builder/Strategies.java | 21 - .../pagefactory/utils/ProxyFactory.java | 16 +- .../utils/WebDriverUnpackUtility.java | 5 +- .../io/appium/java_client/proxy/Helpers.java | 1 - .../appium/java_client/proxy/Interceptor.java | 20 +- .../proxy/ProxyListenersContainer.java | 54 +- .../remote/AndroidMobileCapabilityType.java | 495 ----------------- .../remote/AppiumCommandExecutor.java | 14 +- .../java_client/remote/AutomationName.java | 2 - .../remote/IOSMobileCapabilityType.java | 400 -------------- .../remote/MobileCapabilityType.java | 142 ----- .../java_client/remote/MobileOptions.java | 512 ------------------ .../remote/YouiEngineCapabilityType.java | 16 - .../local/AppiumDriverLocalService.java | 6 +- .../service/local/AppiumServiceBuilder.java | 30 +- .../local/flags/GeneralServerFlag.java | 9 - .../java_client/ws/StringWebSocketClient.java | 33 +- .../android/ExecuteCDPCommandTest.java | 11 +- .../internal/AppiumUserAgentFilterTest.java | 2 +- .../java_client/internal/ConfigTest.java | 2 +- .../java_client/remote/MobileOptionsTest.java | 110 ---- .../local/StartingAppLocallyAndroidTest.java | 21 +- .../local/StartingAppLocallyIosTest.java | 7 +- 58 files changed, 239 insertions(+), 2906 deletions(-) delete mode 100644 src/main/java/io/appium/java_client/MobileBy.java delete mode 100644 src/main/java/io/appium/java_client/SupportsLegacyAppManagement.java create mode 100644 src/main/java/io/appium/java_client/internal/filters/AppiumIdempotencyFilter.java rename src/main/java/io/appium/java_client/{ => internal/filters}/AppiumUserAgentFilter.java (98%) delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsBy.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java delete mode 100644 src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java delete mode 100644 src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java delete mode 100644 src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java delete mode 100644 src/main/java/io/appium/java_client/remote/MobileCapabilityType.java delete mode 100644 src/main/java/io/appium/java_client/remote/MobileOptions.java delete mode 100644 src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java delete mode 100644 src/test/java/io/appium/java_client/remote/MobileOptionsTest.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d427bec19..a7094028a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,6 +14,10 @@ on: - 'docs/**' - '*.md' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: ANDROID_SDK_VERSION: 28 ANDROID_EMU_NAME: test @@ -27,14 +31,13 @@ jobs: strategy: matrix: include: - # TODO: add new LTS Java ( 21 ) once it's released - - java: 8 - platform: macos-latest - e2e-tests: android - java: 11 # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available platform: macos-12 e2e-tests: ios + - java: 15 + platform: macos-latest + e2e-tests: android - java: 17 platform: ubuntu-latest fail-fast: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7e95af207..71837dd3c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ jobs: - name: Set up Java uses: actions/setup-java@v3 with: - java-version: '8' + java-version: '11' distribution: 'zulu' cache: 'gradle' - name: Publish package diff --git a/build.gradle b/build.gradle index a2286e1e7..05e17653f 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,8 @@ repositories { } java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 withJavadocJar() withSourcesJar() } @@ -34,25 +34,23 @@ dependencies { api ('org.seleniumhq.selenium:selenium-api') { version { - strictly "[${seleniumVersion}, 4.14)" + strictly "[${seleniumVersion}, 5.0)" prefer "${seleniumVersion}" } } api ('org.seleniumhq.selenium:selenium-remote-driver') { version { - strictly "[${seleniumVersion}, 4.14)" + strictly "[${seleniumVersion}, 5.0)" prefer "${seleniumVersion}" } } api ('org.seleniumhq.selenium:selenium-support') { version { - strictly "[${seleniumVersion}, 4.14)" + strictly "[${seleniumVersion}, 5.0)" prefer "${seleniumVersion}" } } implementation 'com.google.code.gson:gson:2.10.1' - implementation 'org.apache.commons:commons-lang3:3.13.0' - implementation 'commons-io:commons-io:2.14.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' @@ -93,16 +91,13 @@ tasks.withType(JacocoReport) { } jacocoTestReport.dependsOn test -// Checkstyle requires Java 11 starting from 10.0 -if (JavaVersion.current().isJava11Compatible()) { - apply plugin: 'checkstyle' +apply plugin: 'checkstyle' - checkstyle { - toolVersion = '10.12.1' - configFile = configDirectory.file('appium-style.xml').get().getAsFile() - showViolations = true - ignoreFailures = false - } +checkstyle { + toolVersion = '10.12.1' + configFile = configDirectory.file('appium-style.xml').get().getAsFile() + showViolations = true + ignoreFailures = false } javadoc { diff --git a/gradle.properties b/gradle.properties index 8542862b9..9b917685b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.9.1 +selenium.version=4.14.1 # Please increment the value in a release appiumClient.version=8.6.0 diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index 7e067287d..21e11d08e 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -16,8 +16,8 @@ package io.appium.java_client; +import com.google.common.base.Preconditions; import lombok.Getter; -import org.apache.commons.lang3.Validate; import org.openqa.selenium.By; import org.openqa.selenium.By.Remotable; import org.openqa.selenium.SearchContext; @@ -27,13 +27,15 @@ import java.util.List; import java.util.Objects; +import static com.google.common.base.Strings.isNullOrEmpty; + public abstract class AppiumBy extends By implements Remotable { @Getter private final Parameters remoteParameters; private final String locatorName; protected AppiumBy(String selector, String locatorString, String locatorName) { - Validate.notBlank(locatorString, "Must supply a not empty locator value."); + Preconditions.checkArgument(!isNullOrEmpty(locatorString), "Must supply a not empty locator value."); this.remoteParameters = new Parameters(selector, locatorString); this.locatorName = locatorName; } diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index c8e123fc9..bb77b4ec9 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -16,11 +16,15 @@ package io.appium.java_client; +import io.appium.java_client.internal.filters.AppiumIdempotencyFilter; +import io.appium.java_client.internal.filters.AppiumUserAgentFilter; import org.openqa.selenium.Credentials; import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.Filter; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; @@ -33,7 +37,10 @@ public class AppiumClientConfig extends ClientConfig { private final boolean directConnect; - private static final Filter DEFAULT_FILTER = new AppiumUserAgentFilter(); + private static final Filter DEFAULT_FILTERS = new AppiumUserAgentFilter() + .andThen(new AppiumIdempotencyFilter()); + + private static final String DEFAULT_HTTP_VERSION = "HTTP_1_1"; private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(10); @@ -49,6 +56,7 @@ public class AppiumClientConfig extends ClientConfig { * {@link org.openqa.selenium.remote.http.HttpResponse}. * @param proxy The client proxy preference. * @param credentials Credentials used for authenticating http requests + * @param sslContext SSL context (if present) * @param directConnect If directConnect is enabled. */ protected AppiumClientConfig( @@ -56,10 +64,12 @@ protected AppiumClientConfig( Duration connectionTimeout, Duration readTimeout, Filter filters, - Proxy proxy, - Credentials credentials, + @Nullable Proxy proxy, + @Nullable Credentials credentials, + @Nullable SSLContext sslContext, + @Nullable String version, Boolean directConnect) { - super(baseUri, connectionTimeout, readTimeout, filters, proxy, credentials); + super(baseUri, connectionTimeout, readTimeout, filters, proxy, credentials, sslContext, version); this.directConnect = Require.nonNull("Direct Connect", directConnect); } @@ -73,9 +83,11 @@ public static AppiumClientConfig defaultConfig() { null, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT, - DEFAULT_FILTER, + DEFAULT_FILTERS, + null, null, null, + DEFAULT_HTTP_VERSION, false); } @@ -92,6 +104,8 @@ public static AppiumClientConfig fromClientConfig(ClientConfig clientConfig) { clientConfig.filter(), clientConfig.proxy(), clientConfig.credentials(), + clientConfig.sslContext(), + clientConfig.version(), false); } @@ -103,6 +117,8 @@ private AppiumClientConfig buildAppiumClientConfig(ClientConfig clientConfig, Bo clientConfig.filter(), clientConfig.proxy(), clientConfig.credentials(), + clientConfig.sslContext(), + clientConfig.version(), directConnect); } @@ -175,6 +191,8 @@ public AppiumClientConfig directConnect(boolean directConnect) { this.filter(), this.proxy(), this.credentials(), + this.sslContext(), + this.version(), directConnect ); } diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 749a0204d..28d0bd529 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -23,10 +23,10 @@ import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.AppiumNewSessionCommandPayload; import io.appium.java_client.remote.AppiumW3CHttpCommandCodec; -import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.options.BaseOptions; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; +import lombok.Getter; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.MutableCapabilities; @@ -53,10 +53,10 @@ import java.util.Map; import java.util.Set; +import static com.google.common.base.Strings.isNullOrEmpty; import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; -import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME; -import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; +import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; /** * Default Appium driver implementation. @@ -72,6 +72,7 @@ public class AppiumDriver extends RemoteWebDriver implements private static final ErrorHandler ERROR_HANDLER = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters + @Getter private final URL remoteAddress; protected final RemoteLocationContext locationContext; private final ExecuteMethod executeMethod; @@ -154,7 +155,7 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa RemoteWebDriver.class, this, "capabilities", new ImmutableCapabilities( ImmutableMap.of( PLATFORM_NAME, platformName, - APPIUM_PREFIX + AUTOMATION_NAME, automationName + APPIUM_PREFIX + AUTOMATION_NAME_OPTION, automationName ) ) ); @@ -177,8 +178,7 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa * Changes platform name if it is not set and returns merged capabilities. * * @param originalCapabilities the given {@link Capabilities}. - * @param defaultName a {@link MobileCapabilityType#PLATFORM_NAME} value which has - * to be set up + * @param defaultName a platformName value which has to be set up * @return {@link Capabilities} with changed platform name value or the original capabilities */ protected static Capabilities ensurePlatformName( @@ -192,17 +192,16 @@ protected static Capabilities ensurePlatformName( * Changes automation name if it is not set and returns merged capabilities. * * @param originalCapabilities the given {@link Capabilities}. - * @param defaultName a {@link MobileCapabilityType#AUTOMATION_NAME} value which has - * to be set up + * @param defaultName a platformName value which has to be set up * @return {@link Capabilities} with changed mobile automation name value or the original capabilities */ protected static Capabilities ensureAutomationName( Capabilities originalCapabilities, String defaultName) { String currentAutomationName = CapabilityHelpers.getCapability( - originalCapabilities, AUTOMATION_NAME, String.class); - if (isBlank(currentAutomationName)) { + originalCapabilities, AUTOMATION_NAME_OPTION, String.class); + if (isNullOrEmpty(currentAutomationName)) { String capabilityName = originalCapabilities.getCapabilityNames() - .contains(AUTOMATION_NAME) ? AUTOMATION_NAME : APPIUM_PREFIX + AUTOMATION_NAME; + .contains(AUTOMATION_NAME_OPTION) ? AUTOMATION_NAME_OPTION : APPIUM_PREFIX + AUTOMATION_NAME_OPTION; return originalCapabilities.merge(new ImmutableCapabilities(capabilityName, defaultName)); } return originalCapabilities; @@ -213,8 +212,7 @@ protected static Capabilities ensureAutomationName( * and returns merged capabilities. * * @param originalCapabilities the given {@link Capabilities}. - * @param defaultPlatformName a {@link MobileCapabilityType#PLATFORM_NAME} value which has - * to be set up + * @param defaultPlatformName a platformName value which has to be set up * @param defaultAutomationName The default automation name to set up for this class * @return {@link Capabilities} with changed platform/automation name value or the original capabilities */ @@ -265,10 +263,6 @@ public void addCommand(HttpMethod httpMethod, String url, String methodName) { ((AppiumCommandExecutor) getCommandExecutor()).refreshAdditionalCommands(); } - public URL getRemoteAddress() { - return remoteAddress; - } - @Override protected void startSession(Capabilities capabilities) { Response response = execute(new AppiumNewSessionCommandPayload(capabilities)); @@ -293,7 +287,7 @@ protected void startSession(Capabilities capabilities) { // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version rawCapabilities.remove("platform"); if (rawCapabilities.containsKey(CapabilityType.BROWSER_NAME) - && isBlank((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { + && isNullOrEmpty((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { rawCapabilities.remove(CapabilityType.BROWSER_NAME); } MutableCapabilities returnedCapabilities = new BaseOptions<>(rawCapabilities); @@ -346,4 +340,10 @@ public AppiumDriver markExtensionAbsence(String extName) { absentExtensionNames.add(extName); return this; } + + protected HttpClient getHttpClient() { + return ReflectionHelpers.getPrivateFieldValue( + HttpCommandExecutor.class, getCommandExecutor(), "client", HttpClient.class + ); + } } diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 4aeefec6a..1b29c35cf 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -23,11 +23,11 @@ import io.appium.java_client.imagecomparison.OccurrenceMatchingResult; import io.appium.java_client.imagecomparison.SimilarityMatchingOptions; import io.appium.java_client.imagecomparison.SimilarityMatchingResult; -import org.apache.commons.io.FileUtils; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.Base64; import java.util.Map; @@ -93,8 +93,8 @@ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2) thr */ default FeaturesMatchingResult matchImagesFeatures(File image1, File image2, @Nullable FeaturesMatchingOptions options) throws IOException { - return matchImagesFeatures(Base64.getEncoder().encode(FileUtils.readFileToByteArray(image1)), - Base64.getEncoder().encode(FileUtils.readFileToByteArray(image2)), options); + return matchImagesFeatures(Base64.getEncoder().encode(Files.readAllBytes(image1.toPath())), + Base64.getEncoder().encode(Files.readAllBytes(image2.toPath())), options); } /** @@ -160,8 +160,8 @@ default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partia default OccurrenceMatchingResult findImageOccurrence(File fullImage, File partialImage, @Nullable OccurrenceMatchingOptions options) throws IOException { - return findImageOccurrence(Base64.getEncoder().encode(FileUtils.readFileToByteArray(fullImage)), - Base64.getEncoder().encode(FileUtils.readFileToByteArray(partialImage)), options); + return findImageOccurrence(Base64.getEncoder().encode(Files.readAllBytes(fullImage.toPath())), + Base64.getEncoder().encode(Files.readAllBytes(partialImage.toPath())), options); } /** @@ -227,7 +227,7 @@ default SimilarityMatchingResult getImagesSimilarity(File image1, File image2) t default SimilarityMatchingResult getImagesSimilarity(File image1, File image2, @Nullable SimilarityMatchingOptions options) throws IOException { - return getImagesSimilarity(Base64.getEncoder().encode(FileUtils.readFileToByteArray(image1)), - Base64.getEncoder().encode(FileUtils.readFileToByteArray(image2)), options); + return getImagesSimilarity(Base64.getEncoder().encode(Files.readAllBytes(image1.toPath())), + Base64.getEncoder().encode(Files.readAllBytes(image2.toPath())), options); } -} \ No newline at end of file +} diff --git a/src/main/java/io/appium/java_client/HasBrowserCheck.java b/src/main/java/io/appium/java_client/HasBrowserCheck.java index 6b00a8d5d..c5cd62cdb 100644 --- a/src/main/java/io/appium/java_client/HasBrowserCheck.java +++ b/src/main/java/io/appium/java_client/HasBrowserCheck.java @@ -7,8 +7,7 @@ import org.openqa.selenium.remote.CapabilityType; import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static com.google.common.base.Strings.isNullOrEmpty; public interface HasBrowserCheck extends ExecutesMethod, HasCapabilities { /** @@ -19,7 +18,7 @@ public interface HasBrowserCheck extends ExecutesMethod, HasCapabilities { default boolean isBrowser() { String browserName = CapabilityHelpers.getCapability(getCapabilities(), CapabilityType.BROWSER_NAME, String.class); - if (!isBlank(browserName)) { + if (!isNullOrEmpty(browserName)) { try { return checkNotNull( CommandExecutionHelper.executeScript(this, "return !!window.navigator;") @@ -32,7 +31,8 @@ default boolean isBrowser() { return false; } try { - return !containsIgnoreCase(((ContextAware) this).getContext(), "NATIVE_APP"); + var context = ((ContextAware) this).getContext(); + return context != null && !context.toUpperCase().contains("NATIVE_APP"); } catch (WebDriverException e) { return false; } diff --git a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java index 35d7a7b0c..4223ab814 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java +++ b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java @@ -45,19 +45,4 @@ default void hideKeyboard(String keyName) { CommandExecutionHelper.execute(markExtensionAbsence(extName), hideKeyboardCommand(keyName)); } } - - /** - * Hides the keyboard if it is showing. Hiding the keyboard often - * depends on the way an app is implemented, no single strategy always - * works. - * - * @param strategy HideKeyboardStrategy. - * @param keyName a String, representing the text displayed on the button of the - * keyboard you want to press. For example: "Done". - * @deprecated This API is deprecated and will be removed in the future. - */ - @Deprecated - default void hideKeyboard(String strategy, String keyName) { - CommandExecutionHelper.execute(this, hideKeyboardCommand(strategy, keyName)); - } } diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java deleted file mode 100644 index 902baa249..000000000 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ /dev/null @@ -1,348 +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.By; - -import java.io.Serializable; - -/** - * Appium locating strategies. - * - * @deprecated Use {@link AppiumBy} instead. - */ -@SuppressWarnings("serial") -@Deprecated -public abstract class MobileBy extends AppiumBy { - - protected MobileBy(String selector, String locatorString, String locatorName) { - super(selector, locatorString, locatorName); - } - - /** - * Refer to https://developer.android.com/training/testing/ui-automator - * - * @param uiautomatorText is Android UIAutomator string - * @return an instance of {@link ByAndroidUIAutomator} - * @deprecated Use {@link AppiumBy#androidUIAutomator(String)} instead. - */ - @Deprecated - public static By AndroidUIAutomator(final String uiautomatorText) { - return new ByAndroidUIAutomator(uiautomatorText); - } - - /** - * About Android accessibility - * https://developer.android.com/intl/ru/training/accessibility/accessible-app.html - * About iOS accessibility - * https://developer.apple.com/library/ios/documentation/UIKit/Reference/ - * UIAccessibilityIdentification_Protocol/index.html - * - * @param accessibilityId id is a convenient UI automation accessibility Id. - * @return an instance of {@link ByAndroidUIAutomator} - * @deprecated Use {@link AppiumBy#accessibilityId(String)} instead. - */ - @Deprecated - public static By AccessibilityId(final String accessibilityId) { - return new ByAccessibilityId(accessibilityId); - } - - /** - * This locator strategy is available in XCUITest Driver mode. - * - * @param iOSClassChainString is a valid class chain locator string. - * See - * the documentation for more details - * @return an instance of {@link ByIosClassChain} - * @deprecated Use {@link AppiumBy#iOSClassChain(String)} instead. - */ - @Deprecated - public static By iOSClassChain(final String iOSClassChainString) { - return new ByIosClassChain(iOSClassChainString); - } - - /** - * This locator strategy is only available in Espresso Driver mode. - * - * @param dataMatcherString is a valid json string detailing hamcrest matcher for Espresso onData(). - * See - * the documentation for more details - * @return an instance of {@link ByAndroidDataMatcher} - * @deprecated Use {@link AppiumBy#androidDataMatcher(String)} instead. - */ - @Deprecated - 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 ByAndroidViewMatcher} - * @deprecated Use {@link AppiumBy#androidViewMatcher(String)} instead. - */ - @Deprecated - public static By androidViewMatcher(final String viewMatcherString) { - return new ByAndroidViewMatcher(viewMatcherString); - } - - /** - * This locator strategy is available in XCUITest Driver mode. - * - * @param iOSNsPredicateString is an iOS NsPredicate String - * @return an instance of {@link ByIosNsPredicate} - * @deprecated Use {@link AppiumBy#iOSNsPredicateString(String)} instead. - */ - @Deprecated - public static By iOSNsPredicateString(final String iOSNsPredicateString) { - return new ByIosNsPredicate(iOSNsPredicateString); - } - - /** - * The Windows UIAutomation selector. - * - * @param windowsAutomation The element name in the Windows UIAutomation selector - * @return an instance of {@link MobileBy.ByWindowsAutomation} - * @deprecated Not supported on the server side. - */ - @Deprecated - public static By windowsAutomation(final String windowsAutomation) { - return new ByWindowsAutomation(windowsAutomation); - } - - /** - * This locator strategy is available in Espresso Driver mode. - * - * @param tag is an view tag string - * @return an instance of {@link ByAndroidViewTag} - * @since Appium 1.8.2 beta - * @deprecated Use {@link AppiumBy#androidViewTag(String)} instead. - */ - @Deprecated - public static By AndroidViewTag(final String tag) { - return new ByAndroidViewTag(tag); - } - - /** - * This locator strategy is available only if OpenCV libraries and - * NodeJS bindings are installed on the server machine. - * - * @param b64Template base64-encoded template image string. Supported image formats are the same - * as for OpenCV library. - * @return an instance of {@link ByImage} - * @see - * The documentation on Image Comparison Features - * @see - * The settings available for lookup fine-tuning - * @since Appium 1.8.2 - * @deprecated Use {@link AppiumBy#image(String)} instead. - */ - @Deprecated - public static By image(final String b64Template) { - return new ByImage(b64Template); - } - - /** - * 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 an instance of {@link ByCustom} - * @since Appium 1.9.2 - * @deprecated Use {@link AppiumBy#custom(String)} instead. - */ - @Deprecated - public static By custom(final String selector) { - return new ByCustom(selector); - } - - /** - * Refer to https://developer.android.com/training/testing/ui-automator - * - * @deprecated Use {@link AppiumBy.ByAndroidUIAutomator} instead. - */ - @Deprecated - public static class ByAndroidUIAutomator extends AppiumBy.ByAndroidUIAutomator { - - public ByAndroidUIAutomator(String uiautomatorText) { - super(uiautomatorText); - } - - @Override public String toString() { - return "By.AndroidUIAutomator: " + getRemoteParameters().value(); - } - } - - /** - * About Android accessibility - * https://developer.android.com/intl/ru/training/accessibility/accessible-app.html - * About iOS accessibility - * https://developer.apple.com/library/ios/documentation/UIKit/Reference/ - * UIAccessibilityIdentification_Protocol/index.html - * @deprecated Use {@link AppiumBy.ByAccessibilityId} instead. - */ - @Deprecated - public static class ByAccessibilityId extends AppiumBy.ByAccessibilityId { - - public ByAccessibilityId(String accessibilityId) { - super(accessibilityId); - } - - @Override public String toString() { - return "By.AccessibilityId: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is available in XCUITest Driver mode. - * See - * the documentation for more details - * @deprecated Use {@link AppiumBy.ByIosClassChain} instead. - */ - @Deprecated - public static class ByIosClassChain extends AppiumBy.ByIosClassChain { - - protected ByIosClassChain(String locatorString) { - super(locatorString); - } - - @Override public String toString() { - return "By.IosClassChain: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is only available in Espresso Driver mode. - * See - * the documentation for more details - * @deprecated Use {@link AppiumBy.ByAndroidDataMatcher} instead. - */ - @Deprecated - public static class ByAndroidDataMatcher extends AppiumBy.ByAndroidDataMatcher { - - protected ByAndroidDataMatcher(String locatorString) { - super(locatorString); - } - - @Override public String toString() { - return "By.AndroidDataMatcher: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is only available in Espresso Driver mode. - * See - * the documentation for more details - * @deprecated Use {@link AppiumBy.ByAndroidViewMatcher} instead. - */ - @Deprecated - public static class ByAndroidViewMatcher extends AppiumBy.ByAndroidViewMatcher { - - protected ByAndroidViewMatcher(String locatorString) { - super(locatorString); - } - - @Override public String toString() { - return "By.AndroidViewMatcher: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is available in XCUITest Driver mode. - * @deprecated Use {@link AppiumBy.ByIosNsPredicate} instead. - */ - @Deprecated - public static class ByIosNsPredicate extends AppiumBy.ByIosNsPredicate { - - protected ByIosNsPredicate(String locatorString) { - super(locatorString); - } - - @Override public String toString() { - return "By.IosNsPredicate: " + getRemoteParameters().value(); - } - } - - /** - * The Windows UIAutomation selector. - * @deprecated Not supported on the server side. - */ - @Deprecated - public static class ByWindowsAutomation extends MobileBy implements Serializable { - - protected ByWindowsAutomation(String locatorString) { - super("-windows uiautomation", locatorString, "windowsAutomation"); - } - - @Override public String toString() { - return "By.windowsAutomation: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is available only if OpenCV libraries and - * NodeJS bindings are installed on the server machine. - * @deprecated Use {@link AppiumBy.ByImage} instead. - */ - @Deprecated - public static class ByImage extends AppiumBy.ByImage { - - protected ByImage(String b64Template) { - super(b64Template); - } - - @Override public String toString() { - return "By.Image: " + getRemoteParameters().value(); - } - } - - /** - * This type of locator requires the use of the 'customFindModules' capability and a - * separately-installed element finding plugin. - * @deprecated Use {@link AppiumBy.ByCustom} instead. - */ - @Deprecated - public static class ByCustom extends AppiumBy.ByCustom { - - protected ByCustom(String selector) { - super(selector); - } - - @Override public String toString() { - return "By.Custom: " + getRemoteParameters().value(); - } - } - - /** - * This locator strategy is available in Espresso Driver mode. - * @deprecated Use {@link AppiumBy.ByAndroidViewTag} instead. - */ - @Deprecated - public static class ByAndroidViewTag extends AppiumBy.ByAndroidViewTag { - - public ByAndroidViewTag(String tag) { - super(tag); - } - - @Override public String toString() { - return "By.AndroidViewTag: " + getRemoteParameters().value(); - } - } -} diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index d61a3ef01..878c13c4d 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -21,7 +21,6 @@ import io.appium.java_client.imagecomparison.ComparisonMode; import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; import io.appium.java_client.screenrecording.BaseStopScreenRecordingOptions; -import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.http.HttpMethod; @@ -32,6 +31,8 @@ import java.util.HashMap; import java.util.Map; +import static com.google.common.base.Strings.isNullOrEmpty; + /** * The repository of mobile commands defined in the Mobile JSON * wire protocol. @@ -435,7 +436,7 @@ public static ImmutableMap prepareArguments(String[] params, Object[] values) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (int i = 0; i < params.length; i++) { - if (!StringUtils.isBlank(params[i]) && values[i] != null) { + if (!isNullOrEmpty(params[i]) && values[i] != null) { builder.put(params[i], values[i]); } } diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index 1bfd7f8eb..b91aada90 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -17,12 +17,12 @@ package io.appium.java_client; import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.FileUtils; import org.openqa.selenium.UnsupportedCommandException; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Base64; import static io.appium.java_client.MobileCommand.pushFileCommand; @@ -59,7 +59,7 @@ default void pushFile(String remotePath, byte[] base64Data) { * @throws IOException when there are problems with a file on current file system */ default void pushFile(String remotePath, File file) throws IOException { - pushFile(remotePath, Base64.getEncoder().encode(FileUtils.readFileToByteArray(file))); + pushFile(remotePath, Base64.getEncoder().encode(Files.readAllBytes(file.toPath()))); } } diff --git a/src/main/java/io/appium/java_client/SupportsLegacyAppManagement.java b/src/main/java/io/appium/java_client/SupportsLegacyAppManagement.java deleted file mode 100644 index 7be14ec6d..000000000 --- a/src/main/java/io/appium/java_client/SupportsLegacyAppManagement.java +++ /dev/null @@ -1,59 +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 static io.appium.java_client.MobileCommand.CLOSE_APP; -import static io.appium.java_client.MobileCommand.LAUNCH_APP; -import static io.appium.java_client.MobileCommand.RESET; - -@Deprecated -public interface SupportsLegacyAppManagement extends ExecutesMethod { - /** - * Launches the app, which was provided in the capabilities at session creation, - * and (re)starts the session. - * - * @deprecated This method is deprecated and will be removed. - * See https://github.com/appium/appium/issues/15807 - */ - @Deprecated - default void launchApp() { - execute(LAUNCH_APP); - } - - /** - * Resets the currently running app together with the session. - * - * @deprecated This method is deprecated and will be removed. - * See https://github.com/appium/appium/issues/15807 - */ - @Deprecated - default void resetApp() { - execute(RESET); - } - - /** - * Close the app which was provided in the capabilities at session creation - * and quits the session. - * - * @deprecated This method is deprecated and will be removed. - * See https://github.com/appium/appium/issues/15807 - */ - @Deprecated - default void closeApp() { - execute(CLOSE_APP); - } -} 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 41a17dc8c..34821f8d4 100644 --- a/src/main/java/io/appium/java_client/android/Activity.java +++ b/src/main/java/io/appium/java_client/android/Activity.java @@ -4,7 +4,7 @@ import lombok.experimental.Accessors; import static com.google.common.base.Preconditions.checkArgument; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static com.google.common.base.Strings.isNullOrEmpty; /** * This is a simple POJO class to support the {@link StartsActivity}. @@ -29,9 +29,9 @@ public class Activity { * @param appActivity The value for the app activity. */ public Activity(String appPackage, String appActivity) { - checkArgument(!isBlank(appPackage), + checkArgument(!isNullOrEmpty(appPackage), "App package should be defined as not empty or null string"); - checkArgument(!isBlank(appActivity), + checkArgument(!isNullOrEmpty(appActivity), "App activity should be defined as not empty or null string"); this.appPackage = appPackage; this.appActivity = appActivity; 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 27b3bd5cf..584dde1a7 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -29,7 +29,6 @@ import io.appium.java_client.PerformsTouchActions; import io.appium.java_client.PullsFiles; import io.appium.java_client.PushesFiles; -import io.appium.java_client.SupportsLegacyAppManagement; import io.appium.java_client.android.connection.HasNetworkConnection; import io.appium.java_client.android.geolocation.SupportsExtendedGeolocationCommands; import io.appium.java_client.android.nativekey.PressesKey; @@ -50,8 +49,6 @@ import java.net.URL; -import static io.appium.java_client.android.AndroidMobileCommandHelper.endTestCoverageCommand; - /** * Android driver implementation. */ @@ -65,7 +62,6 @@ public class AndroidDriver extends AppiumDriver implements HasDeviceTime, PullsFiles, InteractsWithApps, - SupportsLegacyAppManagement, HasAppStrings, HasNetworkConnection, PushesFiles, @@ -250,18 +246,6 @@ public AndroidDriver(URL remoteSessionAddress, String automationName) { super(remoteSessionAddress, ANDROID_PLATFORM, automationName); } - /** - * Get test-coverage data. - * - * @param intent intent to broadcast. - * @param path path to .ec file. - * @deprecated This API will be removed - */ - @Deprecated - public void endTestCoverage(String intent, String path) { - CommandExecutionHelper.execute(this, endTestCoverageCommand(intent, path)); - } - @Override public AndroidBatteryInfo getBatteryInfo() { return new AndroidBatteryInfo(CommandExecutionHelper.executeScript(this, "mobile: batteryInfo")); @@ -275,7 +259,7 @@ public RemoteLocationContext getLocationContext() { @Override public synchronized StringWebSocketClient getLogcatClient() { if (logcatClient == null) { - logcatClient = new StringWebSocketClient(); + logcatClient = new StringWebSocketClient(getHttpClient()); } return logcatClient; } diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index 562eb9172..a3b31f716 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -18,14 +18,11 @@ import com.google.common.collect.ImmutableMap; import io.appium.java_client.MobileCommand; -import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.remote.RemoteWebElement; import java.util.AbstractMap; import java.util.Map; -import static com.google.common.base.Preconditions.checkArgument; - /** * This util class helps to prepare parameters of Android-specific JSONWP * commands. @@ -52,22 +49,6 @@ public class AndroidMobileCommandHelper extends MobileCommand { return new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()); } - /** - * This method forms a {@link Map} of parameters for the ending of the test coverage. - * - * @param intent intent to broadcast. - * @param path path to .ec file. - * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. - */ - @Deprecated - public static Map.Entry> endTestCoverageCommand(String intent, - String path) { - String[] parameters = new String[] {"intent", "path"}; - Object[] values = new Object[] {intent, path}; - return new AbstractMap.SimpleEntry<>( - END_TEST_COVERAGE, prepareArguments(parameters, values)); - } - /** * returns the information type of the system state which is supported to read * as like cpu, memory, network traffic, and battery. @@ -192,53 +173,6 @@ public class AndroidMobileCommandHelper extends MobileCommand { SET_NETWORK_CONNECTION, prepareArguments(parameters, values)); } - /** - * This method forms a {@link Map} of parameters for the activity starting. - * - * @param appPackage The package containing the activity. [Required] - * @param appActivity The activity to start. [Required] - * @param appWaitPackage Automation will begin after this package starts. [Optional] - * @param appWaitActivity Automation will begin after this activity starts. [Optional] - * @param intentAction Intent action which will be used to start activity [Optional] - * @param intentCategory Intent category which will be used to start activity [Optional] - * @param intentFlags Flags that will be used to start activity [Optional] - * @param optionalIntentArguments Additional intent arguments that will be used to - * start activity [Optional] - * @param stopApp Stop app on reset or not - * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. - * @throws IllegalArgumentException when any required argument is empty - */ - @Deprecated - public static Map.Entry> startActivityCommand(String appPackage, - String appActivity, String appWaitPackage, String appWaitActivity, - String intentAction, String intentCategory, String intentFlags, - String optionalIntentArguments, boolean stopApp) throws IllegalArgumentException { - - checkArgument(!StringUtils.isBlank(appPackage) && !StringUtils.isBlank(appActivity), - String.format("'%s' and '%s' are required.", "appPackage", "appActivity")); - - String targetWaitPackage = !StringUtils.isBlank(appWaitPackage) ? appWaitPackage : ""; - String targetWaitActivity = !StringUtils.isBlank(appWaitActivity) ? appWaitActivity : ""; - String targetIntentAction = !StringUtils.isBlank(intentAction) ? intentAction : ""; - String targetIntentCategory = !StringUtils.isBlank(intentCategory) ? intentCategory : ""; - String targetIntentFlags = !StringUtils.isBlank(intentFlags) ? intentFlags : ""; - String targetOptionalIntentArguments = !StringUtils.isBlank(optionalIntentArguments) - ? optionalIntentArguments : ""; - - ImmutableMap parameters = ImmutableMap - .builder().put("appPackage", appPackage) - .put("appActivity", appActivity) - .put("appWaitPackage", targetWaitPackage) - .put("appWaitActivity", targetWaitActivity) - .put("dontStopAppOnReset", !stopApp) - .put("intentAction", targetIntentAction) - .put("intentCategory", targetIntentCategory) - .put("intentFlags", targetIntentFlags) - .put("optionalIntentArguments", targetOptionalIntentArguments) - .build(); - return new AbstractMap.SimpleEntry<>(START_ACTIVITY, parameters); - } - /** * This method forms a {@link Map} of parameters for the toggling of location services. * diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index f1d538881..d4cb4258a 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -28,37 +28,8 @@ import static io.appium.java_client.MobileCommand.CURRENT_ACTIVITY; import static io.appium.java_client.MobileCommand.GET_CURRENT_PACKAGE; -import static io.appium.java_client.android.AndroidMobileCommandHelper.startActivityCommand; public interface StartsActivity extends ExecutesMethod, CanRememberExtensionPresence { - /** - * This method should start arbitrary activity during a test. If the activity belongs to - * another application, that application is started and the activity is opened. - *

- * Usage: - *

- *
-     *     {@code
-     *     Activity activity = new Activity("app package goes here", "app activity goes here");
-     *     activity.setWaitAppPackage("app wait package goes here");
-     *     activity.setWaitAppActivity("app wait activity goes here");
-     *     driver.startActivity(activity);
-     *     }
-     * 
- * - * @param activity The {@link Activity} object - * @deprecated Use 'mobile: startActivity' extension instead - */ - @Deprecated - default void startActivity(Activity activity) { - CommandExecutionHelper.execute(this, - startActivityCommand(activity.getAppPackage(), activity.getAppActivity(), - activity.getAppWaitPackage(), activity.getAppWaitActivity(), - activity.getIntentAction(), activity.getIntentCategory(), activity.getIntentFlags(), - activity.getOptionalIntentArguments(), activity.isStopApp()) - ); - } - /** * Get the current activity being run on the mobile device. * diff --git a/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java b/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java index bd86a041e..ef2b6f301 100644 --- a/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java +++ b/src/main/java/io/appium/java_client/android/options/context/SupportsShowChromedriverLogOption.java @@ -49,17 +49,6 @@ default T setShowChromedriverLog(boolean value) { return amend(SHOW_CHROMEDRIVER_LOG_OPTION, value); } - /** - * If set to true then all the output from chromedriver binary will be - * forwarded to the Appium server log. false by default. - * - * @deprecated Use {@link SupportsShowChromedriverLogOption#setShowChromedriverLog(boolean)} instead. - */ - @Deprecated - default T setDhowChromedriverLog(boolean value) { - return setShowChromedriverLog(value); - } - /** * Get whether to forward chromedriver output to the Appium server log. * @@ -70,14 +59,4 @@ default Optional doesShowChromedriverLog() { toSafeBoolean(getCapability(SHOW_CHROMEDRIVER_LOG_OPTION)) ); } - - /** - * Get whether to forward chromedriver output to the Appium server log. - * - * @deprecated Use {@link SupportsShowChromedriverLogOption#doesShowChromedriverLog()} (boolean)} instead. - */ - @Deprecated - default Optional doesDhowChromedriverLog() { - return doesShowChromedriverLog(); - } } diff --git a/src/main/java/io/appium/java_client/internal/filters/AppiumIdempotencyFilter.java b/src/main/java/io/appium/java_client/internal/filters/AppiumIdempotencyFilter.java new file mode 100644 index 000000000..9a8f8156b --- /dev/null +++ b/src/main/java/io/appium/java_client/internal/filters/AppiumIdempotencyFilter.java @@ -0,0 +1,38 @@ +/* + * 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.internal.filters; + +import org.openqa.selenium.remote.http.Filter; +import org.openqa.selenium.remote.http.HttpHandler; +import org.openqa.selenium.remote.http.HttpMethod; + +import java.util.UUID; + +public class AppiumIdempotencyFilter implements Filter { + // https://github.com/appium/appium-base-driver/pull/400 + private static final String IDEMPOTENCY_KEY_HEADER = "X-Idempotency-Key"; + + @Override + public HttpHandler apply(HttpHandler next) { + return req -> { + if (req.getMethod() == HttpMethod.POST && req.getUri().endsWith("/session")) { + req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); + } + return next.execute(req); + }; + } +} diff --git a/src/main/java/io/appium/java_client/AppiumUserAgentFilter.java b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java similarity index 98% rename from src/main/java/io/appium/java_client/AppiumUserAgentFilter.java rename to src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java index a36da9d08..50e9449e0 100644 --- a/src/main/java/io/appium/java_client/AppiumUserAgentFilter.java +++ b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.appium.java_client; +package io.appium.java_client.internal.filters; import com.google.common.annotations.VisibleForTesting; import com.google.common.net.HttpHeaders; @@ -82,7 +82,6 @@ public static String buildUserAgent(@Nullable String userAgent) { @Override public HttpHandler apply(HttpHandler next) { - return req -> { req.setHeader(HttpHeaders.USER_AGENT, buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); return next.execute(req); 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 2b735d649..33c048710 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -29,7 +29,6 @@ import io.appium.java_client.PerformsTouchActions; import io.appium.java_client.PullsFiles; import io.appium.java_client.PushesFiles; -import io.appium.java_client.SupportsLegacyAppManagement; import io.appium.java_client.battery.HasBattery; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.SupportsContextSwitching; @@ -64,7 +63,6 @@ public class IOSDriver extends AppiumDriver implements HasDeviceTime, PullsFiles, InteractsWithApps, - SupportsLegacyAppManagement, HasAppStrings, PerformsTouchActions, HidesKeyboardWithKeyName, @@ -290,7 +288,7 @@ public RemoteLocationContext getLocationContext() { @Override public synchronized StringWebSocketClient getSyslogClient() { if (syslogClient == null) { - syslogClient = new StringWebSocketClient(); + syslogClient = new StringWebSocketClient(getHttpClient()); } return syslogClient; } diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java index e5ceefef2..af501a76e 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsCustomSslCertOption.java @@ -37,16 +37,6 @@ default T setCustomSSLCert(String cert) { return amend(CUSTOM_SSLCERT_OPTION, cert); } - /** - * Get the SSL certificate content. - * - * @deprecated use {@link SupportsCustomSslCertOption#getCustomSSLCert()} instead - */ - @Deprecated - default Optional setCustomSSLCert() { - return getCustomSSLCert(); - } - /** * Get the SSL certificate content. * diff --git a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java index 3958de502..d7a1d115e 100644 --- a/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java +++ b/src/main/java/io/appium/java_client/ios/options/simulator/SupportsSimulatorTracePointerOption.java @@ -58,14 +58,4 @@ default T setSimulatorTracePointer(boolean value) { default Optional doesSimulatorTracePointer() { return Optional.ofNullable(toSafeBoolean(getCapability(SIMULATOR_TRACE_POINTER_OPTION))); } - - /** - * Get whether to highlight pointer moves in the Simulator window. - * - * @deprecated use {@link SupportsSimulatorTracePointerOption#doesSimulatorTracePointer()} instead - */ - @Deprecated - default Optional doesSimulatorTracePointerd() { - return doesSimulatorTracePointer(); - } } 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 b0c8cd428..63208ef1f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -20,7 +20,6 @@ import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import io.appium.java_client.remote.MobileCapabilityType; import org.openqa.selenium.Capabilities; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.SearchContext; @@ -49,6 +48,7 @@ import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; +import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; import static java.time.Duration.ofSeconds; /** @@ -88,7 +88,7 @@ public AppiumFieldDecorator(SearchContext context, Duration duration) { if (wd instanceof HasCapabilities) { Capabilities caps = ((HasCapabilities) wd).getCapabilities(); this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, MobileCapabilityType.AUTOMATION_NAME, String.class); + this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); } else { this.platform = null; this.automation = null; @@ -123,7 +123,7 @@ public AppiumFieldDecorator(SearchContext context) { if (wd instanceof HasCapabilities) { Capabilities caps = ((HasCapabilities) wd).getCapabilities(); this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, MobileCapabilityType.AUTOMATION_NAME, String.class); + this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); } else { this.platform = null; this.automation = null; 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 5cb10c9de..ab4e29274 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -175,11 +175,6 @@ protected By buildMobileNativeBy() { getBys(iOSXCUITFindBy.class, iOSXCUITFindBys.class, iOSXCUITFindAll.class)); } - if (isWindows()) { - return buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::windowsAutomation).orElse(null), - getBys(WindowsFindBy.class, WindowsFindBys.class, WindowsFindAll.class)); - } - return null; } diff --git a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java index 2e091064f..cdeb9da1e 100644 --- a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java +++ b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java @@ -33,16 +33,6 @@ */ LocatorGroupStrategy androidAutomation() default LocatorGroupStrategy.CHAIN; - /** - * The strategy which defines how to use locators which are described by the - * {@link WindowsFindBy} annotation. These annotations can define the chained searching - * or the searching by all possible locators. - * - * @return the strategy which defines how to use locators which are described by the - * {@link WindowsFindBy} annotation - */ - LocatorGroupStrategy windowsAutomation() default LocatorGroupStrategy.CHAIN; - /** * The strategy which defines how to use locators which are described by the * {@link iOSXCUITFindBy} annotation. These annotations can define the chained searching diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java deleted file mode 100644 index 3c599c326..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java +++ /dev/null @@ -1,74 +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.pagefactory; - -/** - * Used to build a complex Windows automation locator. - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -public @interface WindowsBy { - - /** - * It is a Windows automator string. - * - * @return a Windows automator string - */ - String windowsAutomation() default ""; - - /** - * It an UI automation accessibility Id which is a convenient to Windows. - * - * @return an UI automation accessibility Id - */ - String accessibility() default ""; - - /** - * It is an id of the target element. - * - * @return an id of the target element - */ - String id() default ""; - - /** - * It is a className of the target element. - * - * @return a className of the target element - */ - String className() default ""; - - /** - * It is a desired element tag. - * - * @return a desired element tag - */ - String tagName() default ""; - - /** - * It is a xpath to the target element. - * - * @return a xpath to the target element - */ - String xpath() default ""; - - /** - * Priority of the searching. Higher number means lower priority. - * - * @return priority of the searching - */ - int priority() default 0; -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java deleted file mode 100644 index ac1d0d0a5..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.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.pagefactory; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series - * of {@link WindowsBy} tags - * It will then search for all elements that match any criteria. Note that elements - * are not guaranteed to be in document order. - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Retention(RUNTIME) @Target({FIELD, TYPE}) -@Repeatable(WindowsFindByAllSet.class) -public @interface WindowsFindAll { - /** - * It is a set of {@link WindowsBy} strategies which may be used to find the target element. - * - * @return a collection of strategies which may be used to find the target element - */ - WindowsBy[] value(); - - /** - * Priority of the searching. Higher number means lower priority. - * - * @return priority of the searching - */ - int priority() default 0; -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java deleted file mode 100644 index f1c67be98..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java +++ /dev/null @@ -1,88 +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.pagefactory; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Used to mark a field on a Page Object to indicate an alternative mechanism for locating the - * element or a list of elements. Used in conjunction with - * {@link org.openqa.selenium.support.PageFactory} - * this allows users to quickly and easily create PageObjects. - * using Windows automation selectors, accessibility, id, name, class name, tag and xpath - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Retention(RUNTIME) @Target({FIELD, TYPE}) -@Repeatable(WindowsFindBySet.class) -public @interface WindowsFindBy { - - /** - * It is a Windows automator string. - * - * @return a Windows automator string - */ - String windowsAutomation() default ""; - - /** - * It an UI automation accessibility Id which is a convenient to Windows. - * - * @return an UI automation accessibility Id - */ - String accessibility() default ""; - - /** - * It is an id of the target element. - * - * @return an id of the target element - */ - String id() default ""; - - /** - * It is a className of the target element. - * - * @return a className of the target element - */ - String className() default ""; - - /** - * It is a desired element tag. - * - * @return a desired element tag - */ - String tagName() default ""; - - /** - * It is a xpath to the target element. - * - * @return a xpath to the target element - */ - String xpath() default ""; - - /** - * Priority of the searching. Higher number means lower priority. - * - * @return priority of the searching - */ - int priority() default 0; -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java deleted file mode 100644 index f93e00d92..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.appium.java_client.pagefactory; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Defines set of chained/possible locators. Each one locator - * should be defined with {@link WindowsFindAll} - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Target(value = {TYPE, FIELD}) -@Retention(value = RUNTIME) -public @interface WindowsFindByAllSet { - /** - * An array of which builds a sequence of the chained searching for elements or a set of possible locators. - * - * @return an array of {@link WindowsFindAll} which builds a sequence of - * the chained searching for elements or a set of possible locators - */ - WindowsFindAll[] value(); -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java deleted file mode 100644 index b8a3e9f23..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.appium.java_client.pagefactory; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Defines set of chained/possible locators. Each one locator - * should be defined with {@link WindowsFindBys} - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Target(value = {TYPE, FIELD}) -@Retention(value = RUNTIME) -public @interface WindowsFindByChainSet { - /** - * An array of which builds a sequence of the chained searching for elements or a set of possible locators. - * - * @return an array of {@link WindowsFindBys} which builds a sequence of - * the chained searching for elements or a set of possible locators - */ - WindowsFindBys[] value(); -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java deleted file mode 100644 index e953d63a1..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java +++ /dev/null @@ -1,42 +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.pagefactory; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Defines set of chained/possible locators. Each one locator - * should be defined with {@link WindowsFindBy} - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Target(value = {TYPE, FIELD}) -@Retention(value = RUNTIME) -public @interface WindowsFindBySet { - /** - * An array ofwhich builds a sequence of the chained searching for elements or a set of possible locators. - * - * @return an array of {@link WindowsFindBy} which builds a sequence of - * the chained searching for elements or a set of possible locators - */ - WindowsFindBy[] value(); -} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java deleted file mode 100644 index 358a77ae8..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java +++ /dev/null @@ -1,49 +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.pagefactory; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Used to mark a field on a Page Object to indicate that lookup should use - * a series of {@link WindowsBy} tags. - * @deprecated This annotation is deprecated and will be removed. - */ -@Deprecated -@Retention(RUNTIME) @Target({FIELD, TYPE}) -@Repeatable(WindowsFindByChainSet.class) -public @interface WindowsFindBys { - /** - * It is a set of {@link WindowsBy} strategies which build the chain of the searching for the target element. - * - * @return a collection of strategies which build the chain of the searching for the target element - */ - WindowsBy[] value(); - - /** - * Priority of the searching. Higher number means lower priority. - * - * @return priority of the searching - */ - int priority() default 0; -} diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java deleted file mode 100644 index 4999c294d..000000000 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByAll.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.appium.java_client.pagefactory.bys.builder; - -import org.openqa.selenium.By; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.SearchContext; -import org.openqa.selenium.WebElement; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Mechanism used to locate elements within a document using a series of lookups. This class will - * find all DOM elements that matches any of the locators in sequence, e.g. - *
- * driver.findElements(new ByAll(by1, by2))
- * 
- * will find all elements that match by1 and then all elements that match by2. - * This means that the list of elements returned may not be in document order. - * - * @deprecated Use {@link org.openqa.selenium.support.pagefactory.ByAll} - */ -@Deprecated -public class ByAll extends org.openqa.selenium.support.pagefactory.ByAll { - - private final List bys; - - private Function> getSearchingFunction(By by) { - return input -> { - try { - return Optional.of(input.findElement(by)); - } catch (NoSuchElementException e) { - return Optional.empty(); - } - }; - } - - /** - * Finds all elements that matches any of the locators in sequence. - * - * @param bys is a set of {@link By} which forms the all possible searching. - */ - public ByAll(By[] bys) { - super(bys); - checkNotNull(bys); - - this.bys = Arrays.asList(bys); - - checkArgument(!this.bys.isEmpty(), "By array should not be empty"); - } - - @Override - public WebElement findElement(SearchContext context) { - return bys.stream() - .map(by -> getSearchingFunction(by).apply(context)) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst() - .orElseThrow(() -> new NoSuchElementException("Cannot locate an element using " + toString())); - } -} 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 452a8cda0..590db8278 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 @@ -17,7 +17,6 @@ package io.appium.java_client.pagefactory.bys.builder; import io.appium.java_client.AppiumBy; -import io.appium.java_client.MobileBy; import io.appium.java_client.pagefactory.AndroidBy; import io.appium.java_client.pagefactory.AndroidFindBy; import org.openqa.selenium.By; @@ -40,16 +39,6 @@ enum Strategies { return super.getBy(annotation); } }, - /** - * This has been deprecated due to misspelling. - * @deprecated Use {@link Strategies#BYACCESSIBILITY} instead. - */ - @Deprecated - BYACCESSABILITY("accessibility") { - @Override By getBy(Annotation annotation) { - return AppiumBy.accessibilityId(getValue(annotation, this)); - } - }, BYACCESSIBILITY("accessibility") { @Override By getBy(Annotation annotation) { return AppiumBy.accessibilityId(getValue(annotation, this)); @@ -93,16 +82,6 @@ enum Strategies { .partialLinkText(getValue(annotation, this)); } }, - /** - * The Windows UIAutomation strategy. - * @deprecated Not supported on the server side. - */ - @Deprecated - BYWINDOWSAUTOMATION("windowsAutomation") { - @Override By getBy(Annotation annotation) { - return MobileBy.windowsAutomation(getValue(annotation, this)); - } - }, BY_CLASS_CHAIN("iOSClassChain") { @Override By getBy(Annotation annotation) { return AppiumBy 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 7cec1ad07..3cf725bd6 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 @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import static io.appium.java_client.proxy.Helpers.OBJECT_METHOD_NAMES; @@ -36,17 +37,26 @@ * proxy object here. */ public final class ProxyFactory { - private static final Set NON_PROXYABLE_METHODS = setWithout(OBJECT_METHOD_NAMES, "toString"); + private static final Set NON_PROXYABLE_METHODS = setWith( + setWithout(OBJECT_METHOD_NAMES, "toString"), + "iterator" + ); - @SuppressWarnings("unchecked") + @SafeVarargs private static Set setWithout(@SuppressWarnings("SameParameterValue") Set source, T... items) { Set result = new HashSet<>(source); Arrays.asList(items).forEach(result::remove); return Collections.unmodifiableSet(result); } + @SafeVarargs + private static Set setWith(@SuppressWarnings("SameParameterValue") Set source, T... items) { + Set result = new HashSet<>(source); + result.addAll(List.of(items)); + return Collections.unmodifiableSet(result); + } + private ProxyFactory() { - super(); } /** diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 6d31fcc5a..01cc25a7e 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -29,7 +29,6 @@ import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; import static java.util.Optional.ofNullable; -import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; @@ -91,8 +90,8 @@ public static ContentType getCurrentContentType(SearchContext context) { if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser ContextAware contextAware = (ContextAware) driver; - String currentContext = contextAware.getContext(); - if (containsIgnoreCase(currentContext, NATIVE_APP_PATTERN)) { + var currentContext = contextAware.getContext(); + if (currentContext != null && currentContext.toUpperCase().contains(NATIVE_APP_PATTERN)) { return NATIVE_MOBILE_SPECIFIC; } } diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 2dd5912ca..6ed19b7a4 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -35,7 +35,6 @@ import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; public class Helpers { - public static final Set OBJECT_METHOD_NAMES = Stream.of(Object.class.getMethods()) .map(Method::getName) .collect(Collectors.toSet()); diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index fa4d72405..b8417e14b 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -39,9 +39,9 @@ private Interceptor() { * A magic method used to wrap public method calls in classes * patched by ByteBuddy and acting as proxies. * - * @param self The reference to the original instance. - * @param method The reference to the original method. - * @param args The reference to method args. + * @param self The reference to the original instance. + * @param method The reference to the original method. + * @param args The reference to method args. * @param callable The reference to the non-patched callable to avoid call recursion. * @return Either the original method result or the patched one. */ @@ -64,10 +64,9 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - LOGGER.atError() - .addArgument(() -> self.getClass().getName()) - .addArgument(method::getName) - .log("Got an unexpected error in beforeCall listener of {}.{} method", e); + LOGGER.atError().log("Got an unexpected error in beforeCall listener of {}.{} method", + self.getClass().getName(), method.getName(), e + ); } }); @@ -110,10 +109,9 @@ public static Object intercept( } catch (NotImplementedException e) { // ignore } catch (Exception e) { - LOGGER.atError() - .addArgument(() -> self.getClass().getName()) - .addArgument(method::getName) - .log("Got an unexpected error in afterCall listener of {}.{} method", e); + LOGGER.atError().log("Got an unexpected error in afterCall listener of {}.{} method", + self.getClass().getName(), method.getName(), e + ); } }); return endResult; diff --git a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java index 4b314b02b..8ba4d734b 100644 --- a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java +++ b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java @@ -16,14 +16,20 @@ package io.appium.java_client.proxy; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Semaphore; +@NoArgsConstructor(access = AccessLevel.PRIVATE) class ProxyListenersContainer { private static ProxyListenersContainer INSTANCE; @@ -35,31 +41,14 @@ public static synchronized ProxyListenersContainer getInstance() { } private final Semaphore listenersGuard = new Semaphore(1); - // Previously WeakHashMap has been used because of O(1) lookup performance, although - // we had to change it to a list, which has O(N). The reason for that is that - // maps implicitly call `hashCode` API on instances, which might not always - // work as expected for arbitrary proxies - private final List, Collection>> listeners = new LinkedList<>(); + private final List, Collection>> listenerPairs = new LinkedList<>(); + @Getter + @AllArgsConstructor private static class Pair { private final K key; - private final V value; - - public Pair(K key, V value) { - this.key = key; - this.value = value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - } - - private ProxyListenersContainer() { + @Setter + private V value; } /** @@ -78,24 +67,23 @@ public T setListeners(T proxyInstance, Collection listen try { int i = 0; boolean wasInstancePresent = false; - while (i < this.listeners.size()) { - Pair, Collection> pair = this.listeners.get(i); + while (i < listenerPairs.size()) { + var pair = listenerPairs.get(i); Object key = pair.getKey().get(); if (key == null) { // The instance has been garbage-collected - this.listeners.remove(i); + listenerPairs.remove(i); continue; } if (key == proxyInstance) { - pair.getValue().clear(); - pair.getValue().addAll(listeners); + pair.setValue(List.copyOf(listeners)); wasInstancePresent = true; } i++; } if (!wasInstancePresent) { - this.listeners.add(new Pair<>(new WeakReference<>(proxyInstance), new HashSet<>(listeners))); + listenerPairs.add(new Pair<>(new WeakReference<>(proxyInstance), List.copyOf(listeners))); } } finally { listenersGuard.release(); @@ -116,13 +104,13 @@ public Collection getListeners(Object proxyInstance) { } try { int i = 0; - Collection result = Collections.emptySet(); - while (i < listeners.size()) { - Pair, Collection> pair = listeners.get(i); + Collection result = Collections.emptyList(); + while (i < listenerPairs.size()) { + var pair = listenerPairs.get(i); Object key = pair.getKey().get(); if (key == null) { // The instance has been garbage-collected - listeners.remove(i); + listenerPairs.remove(i); continue; } diff --git a/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java deleted file mode 100644 index 6b698d004..000000000 --- a/src/main/java/io/appium/java_client/remote/AndroidMobileCapabilityType.java +++ /dev/null @@ -1,495 +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.remote; - -import org.openqa.selenium.remote.CapabilityType; - -/** - * The list of Android-specific capabilities.
- * Read:
- * - * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#android-only - * - * @deprecated This interface will be removed. Use Options instead. - */ -@Deprecated -public interface AndroidMobileCapabilityType extends CapabilityType { - - /** - * Activity name for the Android activity you want to launch from your package. - * This often needs to be preceded by a {@code .} (e.g., {@code .MainActivity} - * instead of {@code MainActivity}). By default this capability is received from the package - * manifest (action: android.intent.action.MAIN , category: android.intent.category.LAUNCHER) - */ - String APP_ACTIVITY = "appActivity"; - - /** - * Java package of the Android app you want to run. By default this capability is received - * from the package manifest ({@literal @}package attribute value) - */ - String APP_PACKAGE = "appPackage"; - - /** - * Activity name/names, comma separated, for the Android activity you want to wait for. - * By default the value of this capability is the same as for {@code appActivity}. - * You must set it to the very first focused application activity name in case it is different - * from the one which is set as {@code appActivity} if your capability has {@code appActivity} - * and {@code appPackage}. You can also use wildcards ({@code *}). - */ - String APP_WAIT_ACTIVITY = "appWaitActivity"; - - /** - * Java package of the Android app you want to wait for. - * By default the value of this capability is the same as for {@code appActivity} - */ - String APP_WAIT_PACKAGE = "appWaitPackage"; - - /** - * Timeout in milliseconds used to wait for the appWaitActivity to launch (default 20000). - * @since 1.6.0 - */ - String APP_WAIT_DURATION = "appWaitDuration"; - - /** - * Timeout in seconds while waiting for device to become ready. - */ - String DEVICE_READY_TIMEOUT = "deviceReadyTimeout"; - - /** - * Allow to install a test package which has {@code android:testOnly="true"} in the manifest. - * {@code false} by default - */ - String ALLOW_TEST_PACKAGES = "allowTestPackages"; - - /** - * Fully qualified instrumentation class. Passed to -w in adb shell - * am instrument -e coverage true -w. - */ - String ANDROID_COVERAGE = "androidCoverage"; - - /** - * A broadcast action implemented by yourself which is used to dump coverage into file system. - * Passed to -a in adb shell am broadcast -a - */ - String ANDROID_COVERAGE_END_INTENT = "androidCoverageEndIntent"; - - /** - * (Chrome and webview only) Enable Chromedriver's performance logging (default false). - * - * @deprecated move to {@link MobileCapabilityType#ENABLE_PERFORMANCE_LOGGING} - */ - @Deprecated - String ENABLE_PERFORMANCE_LOGGING = "enablePerformanceLogging"; - - /** - * Timeout in seconds used to wait for a device to become ready after booting. - */ - String ANDROID_DEVICE_READY_TIMEOUT = "androidDeviceReadyTimeout"; - - /** - * Port used to connect to the ADB server (default 5037). - */ - String ADB_PORT = "adbPort"; - - /** - * Devtools socket name. Needed only when tested app is a Chromium embedding browser. - * The socket is open by the browser and Chromedriver connects to it as a devtools client. - */ - String ANDROID_DEVICE_SOCKET = "androidDeviceSocket"; - - /** - * Timeout in milliseconds used to wait for an apk to install to the device. Defaults to `90000`. - * @since 1.6.0 - */ - String ANDROID_INSTALL_TIMEOUT = "androidInstallTimeout"; - - /** - * The name of the directory on the device in which the apk will be push before install. - * Defaults to {@code /data/local/tmp} - * @since 1.6.5 - */ - String ANDROID_INSTALL_PATH = "androidInstallPath"; - - /** - * Name of avd to launch. - */ - String AVD = "avd"; - - /** - * How long to wait in milliseconds for an avd to launch and connect to - * ADB (default 120000). - * @since 0.18.0 - */ - String AVD_LAUNCH_TIMEOUT = "avdLaunchTimeout"; - - /** - * How long to wait in milliseconds for an avd to finish its - * boot animations (default 120000). - * @since 0.18.0 - */ - String AVD_READY_TIMEOUT = "avdReadyTimeout"; - - /** - * Additional emulator arguments used when launching an avd. - */ - String AVD_ARGS = "avdArgs"; - - /** - * Use a custom keystore to sign apks, default false. - */ - String USE_KEYSTORE = "useKeystore"; - - /** - * Path to custom keystore, default ~/.android/debug.keystore. - */ - String KEYSTORE_PATH = "keystorePath"; - - /** - * Password for custom keystore. - */ - String KEYSTORE_PASSWORD = "keystorePassword"; - - /** - * Alias for key. - */ - String KEY_ALIAS = "keyAlias"; - - /** - * Password for key. - */ - String KEY_PASSWORD = "keyPassword"; - - /** - * The absolute local path to webdriver executable (if Chromium embedder provides - * its own webdriver, it should be used instead of original chromedriver - * bundled with Appium). - */ - String CHROMEDRIVER_EXECUTABLE = "chromedriverExecutable"; - - /** - * An array of arguments to be passed to the chromedriver binary when it's run by Appium. - * By default no CLI args are added beyond what Appium uses internally (such as {@code --url-base}, {@code --port}, - * {@code --adb-port}, and {@code --log-path}. - * @since 1.12.0 - */ - String CHROMEDRIVER_ARGS = "chromedriverArgs"; - - /** - * The absolute path to a directory to look for Chromedriver executables in, for automatic discovery of compatible - * Chromedrivers. Ignored if {@code chromedriverUseSystemExecutable} is {@code true} - * @since 1.8.0 - */ - String CHROMEDRIVER_EXECUTABLE_DIR = "chromedriverExecutableDir"; - - /** - * The absolute path to a file which maps Chromedriver versions to the minimum Chrome that it supports. - * Ignored if {@code chromedriverUseSystemExecutable} is {@code true} - * @since 1.8.0 - */ - String CHROMEDRIVER_CHROME_MAPPING_FILE = "chromedriverChromeMappingFile"; - - /** - * If true, bypasses automatic Chromedriver configuration and uses the version that comes downloaded with Appium. - * Ignored if {@code chromedriverExecutable} is set. Defaults to {@code false} - * @since 1.9.0 - */ - String CHROMEDRIVER_USE_SYSTEM_EXECUTABLE = "chromedriverUseSystemExecutable"; - - /** - * Numeric port to start Chromedriver on. Note that use of this capability is discouraged as it will cause undefined - * behavior in case there are multiple webviews present. By default Appium will find a free port. - */ - String CHROMEDRIVER_PORT = "chromedriverPort"; - - /** - * A list of valid ports for Appium to use for communication with Chromedrivers. This capability supports multiple - * webview scenarios. The form of this capability is an array of numeric ports, where array items can themselves be - * arrays of length 2, where the first element is the start of an inclusive range and the second is the end. - * By default, Appium will use any free port. - * @since 1.13.0 - */ - String CHROMEDRIVER_PORTS = "chromedriverPorts"; - - /** - * Sets the chromedriver flag {@code --disable-build-check} for Chrome webview tests. - * @since 1.11.0 - */ - String CHROMEDRIVER_DISABLE_BUILD_CHECK = "chromedriverDisableBuildCheck"; - - /** - * Amount of time to wait for Webview context to become active, in ms. Defaults to 2000. - * @since 1.5.2 - */ - String AUTO_WEBVIEW_TIMEOUT = "autoWebviewTimeout"; - - /** - * Intent action which will be used to start activity - * (default android.intent.action.MAIN). - */ - String INTENT_ACTION = "intentAction"; - - /** - * Intent category which will be used to start - * activity (default android.intent.category.LAUNCHER). - */ - String INTENT_CATEGORY = "intentCategory"; - - /** - * Flags that will be used to start activity (default 0x10200000). - */ - String INTENT_FLAGS = "intentFlags"; - - /** - * Additional intent arguments that will be used to start activity. See - * - * Intent arguments. - */ - String OPTIONAL_INTENT_ARGUMENTS = "optionalIntentArguments"; - - /** - * Doesn't stop the process of the app under test, before starting the app using adb. - * If the app under test is created by another anchor app, setting this false, - * allows the process of the anchor app to be still alive, during the start of - * the test app using adb. In other words, with dontStopAppOnReset set to true, - * we will not include the -S flag in the adb shell am start call. - * With this capability omitted or set to false, we include the -S flag. Default false - * @since 1.4.0 - */ - String DONT_STOP_APP_ON_RESET = "dontStopAppOnReset"; - - /** - * Enable Unicode input, default false. - * @since 1.2.0 - */ - String UNICODE_KEYBOARD = "unicodeKeyboard"; - - /** - * Reset keyboard to its original state, after running Unicode tests with - * unicodeKeyboard capability. Ignored if used alone. Default false - */ - String RESET_KEYBOARD = "resetKeyboard"; - - /** - * Skip checking and signing of app with debug keys, will work only with - * UiAutomator and not with selendroid, default false. - * @since 1.2.2 - */ - String NO_SIGN = "noSign"; - - /** - * Calls the setCompressedLayoutHierarchy() uiautomator function. - * This capability can speed up test execution, since Accessibility commands will run - * faster ignoring some elements. The ignored elements will not be findable, - * which is why this capability has also been implemented as a toggle-able - * setting as well as a capability. Defaults to false. - */ - String IGNORE_UNIMPORTANT_VIEWS = "ignoreUnimportantViews"; - - /** - * Disables android watchers that watch for application not responding and application crash, - * this will reduce cpu usage on android device/emulator. This capability will work only with - * UiAutomator and not with selendroid, default false. - * @since 1.4.0 - */ - String DISABLE_ANDROID_WATCHERS = "disableAndroidWatchers"; - - /** - * Allows passing chromeOptions capability for ChromeDriver. - * For more information see - * - * chromeOptions. - */ - String CHROME_OPTIONS = "chromeOptions"; - - /** - * Kill ChromeDriver session when moving to a non-ChromeDriver webview. - * Defaults to false - */ - String RECREATE_CHROME_DRIVER_SESSIONS = "recreateChromeDriverSessions"; - - /** - * In a web context, use native (adb) method for taking a screenshot, rather than proxying - * to ChromeDriver, default false. - * @since 1.5.3 - */ - String NATIVE_WEB_SCREENSHOT = "nativeWebScreenshot"; - - /** - * The name of the directory on the device in which the screenshot will be put. - * Defaults to /data/local/tmp. - * @since 1.6.0 - */ - String ANDROID_SCREENSHOT_PATH = "androidScreenshotPath"; - - /** - * Set the network speed emulation. Specify the maximum network upload and download speeds. Defaults to {@code full} - */ - String NETWORK_SPEED = "networkSpeed"; - - /** - * Toggle gps location provider for emulators before starting the session. By default the emulator will have this - * option enabled or not according to how it has been provisioned. - */ - String GPS_ENABLED = "gpsEnabled"; - - /** - * Set this capability to {@code true} to run the Emulator headless when device display is not needed to be visible. - * {@code false} is the default value. isHeadless is also support for iOS, check XCUITest-specific capabilities. - */ - String IS_HEADLESS = "isHeadless"; - - /** - * Timeout in milliseconds used to wait for adb command execution. Defaults to {@code 20000} - */ - String ADB_EXEC_TIMEOUT = "adbExecTimeout"; - - /** - * Sets the locale script. - * @since 1.10.0 - */ - String LOCALE_SCRIPT = "localeScript"; - - /** - * Skip device initialization which includes i.a.: installation and running of Settings app or setting of - * permissions. Can be used to improve startup performance when the device was already used for automation and - * it's prepared for the next automation. Defaults to {@code false} - * @since 1.11.0 - */ - String SKIP_DEVICE_INITIALIZATION = "skipDeviceInitialization"; - - /** - * Have Appium automatically determine which permissions your app requires and - * grant them to the app on install. Defaults to {@code false}. If noReset is {@code true}, this capability doesn't - * work. - */ - String AUTO_GRANT_PERMISSIONS = "autoGrantPermissions"; - - /** - * Allow for correct handling of orientation on landscape-oriented devices. - * Set to {@code true} to basically flip the meaning of {@code PORTRAIT} and {@code LANDSCAPE}. - * Defaults to {@code false}. - * @since 1.6.4 - */ - String ANDROID_NATURAL_ORIENTATION = "androidNaturalOrientation"; - - /** - * {@code systemPort} used to connect to - * appium-uiautomator2-server or - * appium-espresso-driver. - * The default is {@code 8200} in general and selects one port from {@code 8200} to {@code 8299} - * for appium-uiautomator2-server, it is {@code 8300} from {@code 8300} to {@code 8399} for - * appium-espresso-driver. When you run tests in parallel, you must adjust the port to avoid conflicts. Read - * - * Parallel Testing Setup Guide for more details. - */ - String SYSTEM_PORT = "systemPort"; - - /** - * Optional remote ADB server host. - * @since 1.7.0 - */ - String REMOTE_ADB_HOST = "remoteAdbHost"; - - /** - * Skips unlock during session creation. Defaults to {@code false} - */ - String SKIP_UNLOCK = "skipUnlock"; - - /** - * Unlock the target device with particular lock pattern instead of just waking up the device with a helper app. - * It works with {@code unlockKey} capability. Defaults to undefined. {@code fingerprint} is available only for - * Android 6.0+ and emulators. - * Read unlock doc in - * android driver. - */ - String UNLOCK_TYPE = "unlockType"; - - /** - * A key pattern to unlock used by {@code unlockType}. - */ - String UNLOCK_KEY = "unlockKey"; - - /** - * Initializing the app under test automatically. - * Appium does not launch the app under test if this is {@code false}. Defaults to {@code true} - */ - String AUTO_LAUNCH = "autoLaunch"; - - /** - * Skips to start capturing logcat. It might improve performance such as network. - * Log related commands will not work. Defaults to {@code false}. - * @since 1.12.0 - */ - String SKIP_LOGCAT_CAPTURE = "skipLogcatCapture"; - - /** - * A package, list of packages or * to uninstall package/s before installing apks for test. - * {@code '*'} uninstall all of thrid-party packages except for packages which is necessary for Appium to test such - * as {@code io.appium.settings} or {@code io.appium.uiautomator2.server} since Appium already contains the logic to - * manage them. - * @since 1.12.0 - */ - String UNINSTALL_OTHER_PACKAGES = "uninstallOtherPackages"; - - /** - * Set device animation scale zero if the value is {@code true}. After session is complete, Appium restores the - * animation scale to it's original value. Defaults to {@code false} - * @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"; -} 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 ee0e5a396..d7dd90683 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -18,9 +18,7 @@ import com.google.common.base.Supplier; import com.google.common.base.Throwables; -import com.google.common.net.HttpHeaders; import io.appium.java_client.AppiumClientConfig; -import io.appium.java_client.AppiumUserAgentFilter; import io.appium.java_client.internal.ReflectionHelpers; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; @@ -48,7 +46,6 @@ import java.net.URL; import java.util.Map; import java.util.Optional; -import java.util.UUID; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Throwables.throwIfUnchecked; @@ -56,8 +53,6 @@ import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; 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; @@ -179,14 +174,7 @@ private Response createSession(Command command) throws IOException { throw new SessionNotCreatedException("Session already exists"); } - ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession( - getClient().with(httpHandler -> req -> { - req.setHeader(HttpHeaders.USER_AGENT, - AppiumUserAgentFilter.buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); - req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); - return httpHandler.execute(req); - }), command - ); + ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession(getClient(), command); Dialect dialect = result.getDialect(); if (!(dialect.getCommandCodec() instanceof W3CHttpCommandCodec)) { throw new SessionNotCreatedException("Only W3C sessions are supported. " 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 4ee53ea1a..7df904c4d 100644 --- a/src/main/java/io/appium/java_client/remote/AutomationName.java +++ b/src/main/java/io/appium/java_client/remote/AutomationName.java @@ -18,8 +18,6 @@ public interface AutomationName { // Officially supported drivers - @Deprecated - String APPIUM = "Appium"; // https://github.com/appium/appium-xcuitest-driver String IOS_XCUI_TEST = "XCuiTest"; // https://github.com/appium/appium-uiautomator2-driver diff --git a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java deleted file mode 100644 index b8b517132..000000000 --- a/src/main/java/io/appium/java_client/remote/IOSMobileCapabilityType.java +++ /dev/null @@ -1,400 +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.remote; - -import org.openqa.selenium.remote.CapabilityType; - -/** - * The list of iOS-specific capabilities.
- * Read:
- * - * https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md#ios-only - *
- * and
- * - * https://appium.github.io/appium-xcuitest-driver/latest/capabilities/ - * - * @deprecated This interface will be removed. Use Options instead. - */ -@Deprecated -public interface IOSMobileCapabilityType extends CapabilityType { - - /** - * (Sim-only) Calendar format to set for the iOS Simulator. - */ - String CALENDAR_FORMAT = "calendarFormat"; - - /** - * Bundle ID of the app under test. Useful for starting an app on a real device - * or for using other caps which require the bundle ID during test startup. - * To run a test on a real device using the bundle ID, - * you may omit the 'app' capability, but you must provide 'udid'. - */ - String BUNDLE_ID = "bundleId"; - - /** - * (Sim-only) Force location services to be either on or off. - * Default is to keep current sim setting. - */ - String LOCATION_SERVICES_ENABLED = "locationServicesEnabled"; - - /** - * (Sim-only) Set location services to be authorized or not authorized for app via plist, - * so that location services alert doesn't pop up. Default is to keep current sim - * setting. Note that if you use this setting you MUST also use the bundleId - * capability to send in your app's bundle ID. - */ - String LOCATION_SERVICES_AUTHORIZED = "locationServicesAuthorized"; - - /** - * Accept all iOS alerts automatically if they pop up. - * This includes privacy access permission alerts - * (e.g., location, contacts, photos). Default is false. - */ - String AUTO_ACCEPT_ALERTS = "autoAcceptAlerts"; - - /** - * Dismiss all iOS alerts automatically if they pop up. - * This includes privacy access permission alerts (e.g., - * location, contacts, photos). Default is false. - */ - String AUTO_DISMISS_ALERTS = "autoDismissAlerts"; - - /** - * Use native intruments lib (ie disable instruments-without-delay). - */ - String NATIVE_INSTRUMENTS_LIB = "nativeInstrumentsLib"; - - /** - * Enable "real", non-javascript-based web taps in Safari. - * Default: false. - * Warning: depending on viewport size/ratio this might not accurately tap an element. - */ - String NATIVE_WEB_TAP = "nativeWebTap"; - - /** - * (Sim-only) (>= 8.1) Initial safari url, default is a local welcome page. - */ - String SAFARI_INITIAL_URL = "safariInitialUrl"; - - /** - * (Sim-only) Allow javascript to open new windows in Safari. Default keeps current sim - * setting. - */ - String SAFARI_ALLOW_POPUPS = "safariAllowPopups"; - - /** - * (Sim-only) Prevent Safari from showing a fraudulent website warning. - * Default keeps current sim setting. - */ - String SAFARI_IGNORE_FRAUD_WARNING = "safariIgnoreFraudWarning"; - - /** - * (Sim-only) Whether Safari should allow links to open in new windows. - * Default keeps current sim setting. - */ - String SAFARI_OPEN_LINKS_IN_BACKGROUND = "safariOpenLinksInBackground"; - - /** - * (Sim-only) Whether to keep keychains (Library/Keychains) when appium - * session is started/finished. - */ - String KEEP_KEY_CHAINS = "keepKeyChains"; - - /** - * Where to look for localizable strings. Default en.lproj. - */ - String LOCALIZABLE_STRINGS_DIR = "localizableStringsDir"; - - /** - * Arguments to pass to the AUT using instruments. - */ - String PROCESS_ARGUMENTS = "processArguments"; - - /** - * The delay, in ms, between keystrokes sent to an element when typing. - */ - String INTER_KEY_DELAY = "interKeyDelay"; - - /** - * Whether to show any logs captured from a device in the appium logs. Default false. - */ - String SHOW_IOS_LOG = "showIOSLog"; - - /** - * strategy to use to type test into a test field. Simulator default: oneByOne. - * Real device default: grouped. - */ - String SEND_KEY_STRATEGY = "sendKeyStrategy"; - - /** - * Max timeout in sec to wait for a screenshot to be generated. default: 10. - */ - String SCREENSHOT_WAIT_TIMEOUT = "screenshotWaitTimeout"; - - /** - * The ios automation script used to determined if the app has been launched, - * by default the system wait for the page source not to be empty. - * The result must be a boolean. - */ - String WAIT_FOR_APP_SCRIPT = "waitForAppScript"; - - /** - * Number of times to send connection message to remote debugger, to get webview. - * Default: 8. - */ - String WEBVIEW_CONNECT_RETRIES = "webviewConnectRetries"; - - /** - * The display name of the application under test. Used to automate backgrounding - * the app in iOS 9+. - */ - String APP_NAME = "appName"; - - /** - * (Sim only) Add an SSL certificate to IOS Simulator. - */ - String CUSTOM_SSL_CERT = "customSSLCert"; - - /** - * The desired capability to specify a length for tapping, if the regular - * tap is too long for the app under test. The XCUITest specific capability. - * - * @deprecated This capability is not being used. - */ - @Deprecated - String TAP_WITH_SHORT_PRESS_DURATION = "tapWithShortPressDuration"; - - /** - * Simulator scale factor. - * This is useful to have if the default resolution of simulated device is - * greater than the actual display resolution. So you can scale the simulator - * to see the whole device screen without scrolling. - * This capability only works below Xcode9. - */ - String SCALE_FACTOR = "scaleFactor"; - - /** - * This value if specified, will be used to forward traffic from Mac - * host to real ios devices over USB. Default value is same as port - * number used by WDA on device. - * eg: 8100 - */ - String WDA_LOCAL_PORT = "wdaLocalPort"; - - /** - * Whether to display the output of the Xcode command - * used to run the tests.If this is true, - * there will be lots of extra logging at startup. Defaults to false - */ - String SHOW_XCODE_LOG = "showXcodeLog"; - - /** - * Time in milliseconds to pause between installing the application - * and starting WebDriverAgent on the device. Used particularly for larger applications. - * Defaults to 0 - */ - String IOS_INSTALL_PAUSE = "iosInstallPause"; - - /** - * Full path to an optional Xcode configuration file that - * specifies the code signing identity - * and team for running the WebDriverAgent on the real device. - * e.g., /path/to/myconfig.xcconfig - */ - String XCODE_CONFIG_FILE = "xcodeConfigFile"; - - /** - * Password for unlocking keychain specified in keychainPath. - */ - String KEYCHAIN_PASSWORD = "keychainPassword"; - - /** - * Skips the build phase of running the WDA app. - * Building is then the responsibility of the user. - * Only works for Xcode 8+. Defaults to false - */ - String USE_PREBUILT_WDA = "usePrebuiltWDA"; - - /** - * Sets read only permissons to Attachments subfolder of WebDriverAgent - * root inside Xcode's DerivedData. - * This is necessary to prevent XCTest framework from - * creating tons of unnecessary screenshots and logs, - * which are impossible to shutdown using programming - * interfaces provided by Apple - * - * @deprecated This capability was deleted at Appium 1.14.0 - */ - @Deprecated - String PREVENT_WDAATTACHMENTS = "preventWDAAttachments"; - - /** - * Appium will connect to an existing WebDriverAgent, - * instance at this URL instead of starting a new one. - * eg : http://localhost:8100 - */ - String WEB_DRIVER_AGENT_URL = "webDriverAgentUrl"; - - /** - * Full path to the private development key exported - * from the system keychain. Used in conjunction - * with keychainPassword when testing on real devices. - * e.g., /path/to/MyPrivateKey.p12 - */ - String KEYCHAIN_PATH = "keychainPath"; - - /** - * If {@code true}, forces uninstall of any existing WebDriverAgent app on device. - * Set it to {@code true} if you want to apply different startup options for WebDriverAgent for each session. - * Although, it is only guaranteed to work stable on Simulator. Real devices require WebDriverAgent - * client to run for as long as possible without reinstall/restart to avoid issues like - * - * https://github.com/facebook/WebDriverAgent/issues/507. - * The {@code false} value (the default behaviour since driver version 2.35.0) will try to detect currently - * running WDA listener executed by previous testing session(s) and reuse it if possible, which is - * highly recommended for real device testing and to speed up suites of multiple tests in general. - * A new WDA session will be triggered at the default URL (http://localhost:8100) if WDA is not - * listening and {@code webDriverAgentUrl} capability is not set. The negative/unset value of {@code useNewWDA} - * capability has no effect prior to xcuitest driver version 2.35.0. - */ - String USE_NEW_WDA = "useNewWDA"; - - /** - * Time, in ms, to wait for WebDriverAgent to be pingable. Defaults to 60000ms. - */ - String WDA_LAUNCH_TIMEOUT = "wdaLaunchTimeout"; - - /** - * Timeout, in ms, for waiting for a response from WebDriverAgent. Defaults to 240000ms. - */ - String WDA_CONNECTION_TIMEOUT = "wdaConnectionTimeout"; - - /** - * Apple developer team identifier string. - * Must be used in conjunction with xcodeSigningId to take effect. - * e.g., JWL241K123 - */ - String XCODE_ORG_ID = "xcodeOrgId"; - - /** - * String representing a signing certificate. - * Must be used in conjunction with xcodeOrgId. - * This is usually just iPhone Developer, so the default (if not included) is iPhone Developer - */ - String XCODE_SIGNING_ID = "xcodeSigningId"; - - /** - * Bundle id to update WDA to before building and launching on real devices. - * This bundle id must be associated with a valid provisioning profile. - * e.g., io.appium.WebDriverAgentRunner. - */ - String UPDATE_WDA_BUNDLEID = "updatedWDABundleId"; - - /** - * By default application installation is not skipped if newer or the same version of this app is already present on - * the device under test. Setting this option to {@code false} will enforce Appium to always skip the current - * application build. Defaults to {@code true}. - * - * @since 4.19.0 - */ - String ENFORCE_APP_INSTALL = "enforceAppInstall"; - - /** - * Whether to perform reset on test session finish (false) or not (true). - * Keeping this variable set to true and Simulator running - * (the default behaviour since version 1.6.4) may significantly shorten the - * duration of test session initialization. - * Defaults to true. - */ - String RESET_ON_SESSION_START_ONLY = "resetOnSessionStartOnly"; - - /** - * Custom timeout(s) in milliseconds for WDA backend commands execution. - * This might be useful if WDA backend freezes unexpectedly or requires - * too much time to fail and blocks automated test execution. - * The value is expected to be of type string and can either contain - * max milliseconds to wait for each WDA command to be executed before - * terminating the session forcefully or a valid JSON string, - * where keys are internal Appium command names (you can find these in logs, - * look for "Executing command 'command_name'" records) and values are - * timeouts in milliseconds. You can also set the 'default' key to assign - * the timeout for all other commands not explicitly enumerated as JSON keys. - */ - String COMMAND_TIMEOUTS = "commandTimeouts"; - - /** - * Number of times to try to build and launch WebDriverAgent onto the device. - * Defaults to 2. - */ - String WDA_STARTUP_RETRIES = "wdaStartupRetries"; - - /** - * Time, in ms, to wait between tries to build and launch WebDriverAgent. - * Defaults to 10000ms. - */ - String WDA_STARTUP_RETRY_INTERVAL = "wdaStartupRetryInterval"; - - /** - * Set this option to true in order to enable hardware keyboard in Simulator. - * It is set to false by default, because this helps to workaround some XCTest bugs. - */ - String CONNECT_HARDWARE_KEYBOARD = "connectHardwareKeyboard"; - - /** - * Maximum frequency of keystrokes for typing and clear. - * If your tests are failing because of typing errors, you may want to adjust this. - * Defaults to 60 keystrokes per minute. - */ - String MAX_TYPING_FREQUENCY = "maxTypingFrequency"; - - /** - * Use native methods for determining visibility of elements. - * In some cases this takes a long time. - * Setting this capability to false will cause the system to use the position - * and size of elements to make sure they are visible on the screen. - * This can, however, lead to false results in some situations. - * Defaults to false, except iOS 9.3, where it defaults to true. - */ - String SIMPLE_ISVISIBLE_CHECK = "simpleIsVisibleCheck"; - - /** - * Use SSL to download dependencies for WebDriverAgent. Defaults to false. - */ - String USE_CARTHAGE_SSL = "useCarthageSsl"; - - /** - * Use default proxy for test management within WebDriverAgent. - * Setting this to false sometimes helps with socket hangup problems. - * Defaults to true. - */ - String SHOULD_USE_SINGLETON_TESTMANAGER = "shouldUseSingletonTestManager"; - - /** - * Set this to true if you want to start ios_webkit_debug proxy server - * automatically for accessing webviews on iOS. - * The capatibility only works for real device automation. - * Defaults to false. - */ - String START_IWDP = "startIWDP"; - - /** - * Enrolls simulator for touch id. Defaults to false. - */ - String ALLOW_TOUCHID_ENROLL = "allowTouchIdEnroll"; - -} diff --git a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java deleted file mode 100644 index 44cc27214..000000000 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ /dev/null @@ -1,142 +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.remote; - -import org.openqa.selenium.remote.CapabilityType; - -/** - * The list of common capabilities.
- * Read:
- * - * https://appium.io/docs/en/latest/guides/caps/#appium-capabilities - * - * @deprecated This interface will be removed. Use Options instead. - */ -@Deprecated -public interface MobileCapabilityType extends CapabilityType { - - /** - * Which automation engine to use. - */ - String AUTOMATION_NAME = "automationName"; - - /** - * Mobile OS version. - */ - String PLATFORM_VERSION = "platformVersion"; - - /** - * The kind of mobile device or emulator to use. - */ - String DEVICE_NAME = "deviceName"; - - /** - * How long (in seconds) Appium will wait for a new command from the - * client before assuming the client quit and ending the session. - */ - String NEW_COMMAND_TIMEOUT = "newCommandTimeout"; - - /** - * The absolute local path or remote http URL to a {@code .ipa} file (IOS), - * {@code .app} folder (IOS Simulator), {@code .apk} file (Android) or {@code .apks} file (Android App Bundle), - * or a {@code .zip} file containing one of these (for .app, the .app folder must be the root of the zip file). - * Appium will attempt to install this app binary on the appropriate device first. - * Note that this capability is not required for Android if you specify {@code appPackage} - * and {@code appActivity} capabilities (see below). Incompatible with {@code browserName}. See - * - * here - * about {@code .apks} file. - */ - String APP = "app"; - - /** - * Unique device identifier of the connected physical device. - */ - String UDID = "udid"; - - - /** - * Language to set for iOS (XCUITest driver only) and Android. - */ - String LANGUAGE = "language"; - - /** - * Locale to set for iOS (XCUITest driver only) and Android. - * {@code fr_CA} format for iOS. {@code CA} format (country name abbreviation) for Android - */ - String LOCALE = "locale"; - - /** - * (Sim/Emu-only) start in a certain orientation. - */ - String ORIENTATION = "orientation"; - - /** - * Move directly into Webview context. Default false. - */ - String AUTO_WEBVIEW = "autoWebview"; - - /** - * Don't reset app state before this session. See - * - * here - * for more detail. - */ - String NO_RESET = "noReset"; - - /** - * Perform a complete reset. See - * - * here - * for more detail. - */ - String FULL_RESET = "fullReset"; - - /** - * The desired capability which specifies whether to delete any generated files at - * the end of a session (see iOS and Android entries for particulars). - */ - String CLEAR_SYSTEM_FILES = "clearSystemFiles"; - - /** - * Enable or disable the reporting of the timings for various Appium-internal events - * (e.g., the start and end of each command, etc.). Defaults to {@code false}. - * To enable, use {@code true}. The timings are then reported as {@code events} property on response - * to querying the current session. See the - * - * event timing docs for the structure of this response. - */ - String EVENT_TIMINGS = "eventTimings"; - - /** - * (Web and webview only) Enable ChromeDriver's (on Android) - * 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"; -} diff --git a/src/main/java/io/appium/java_client/remote/MobileOptions.java b/src/main/java/io/appium/java_client/remote/MobileOptions.java deleted file mode 100644 index 98bd098b7..000000000 --- a/src/main/java/io/appium/java_client/remote/MobileOptions.java +++ /dev/null @@ -1,512 +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.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; - -/** - * Use the specific options class for your driver, - * for example XCUITestOptions or UiAutomator2Options. - * - * @param The child class for a proper chaining. - */ -@Deprecated -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) { - super(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); - } - - /** - * 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); - } - - @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; - } -} diff --git a/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java b/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java deleted file mode 100644 index 1657103a6..000000000 --- a/src/main/java/io/appium/java_client/remote/YouiEngineCapabilityType.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.appium.java_client.remote; - -import org.openqa.selenium.remote.CapabilityType; - -/** - * The list of youiengine-specific capabilities. - * - * @deprecated This interface will be removed. Use Options instead. - */ -@Deprecated -public interface YouiEngineCapabilityType extends CapabilityType { - /** - * IP address of the app to execute commands against. - */ - String APP_ADDRESS = "youiEngineAppAddress"; -} 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 f37b89e29..fa2ed6584 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 @@ -19,7 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import io.appium.java_client.internal.ReflectionHelpers; import lombok.SneakyThrows; -import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.net.UrlChecker; import org.openqa.selenium.os.CommandLine; import org.openqa.selenium.remote.service.DriverService; @@ -47,6 +46,7 @@ import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS; import static org.slf4j.event.Level.DEBUG; @@ -183,7 +183,7 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } catch (Exception e) { final Optional output = Optional.ofNullable(process) .map(CommandLine::getStdOut) - .filter(o -> !StringUtils.isBlank(o)); + .filter(o -> !isNullOrEmpty(o)); destroyProcess(); List errorLines = new ArrayList<>(); errorLines.add("The local appium server has not been started"); @@ -200,7 +200,7 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { errorLines.add(String.format("Arguments: %s", nodeJSArgs)); output.ifPresent(o -> errorLines.add(String.format("Output: %s", o))); throw new AppiumServerHasNotBeenStartedLocallyException( - StringUtils.joinWith("\n", errorLines), e + String.join("\n", errorLines), e ); } } finally { 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 774399a9c..a8879cc36 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 @@ -19,16 +19,14 @@ import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.appium.java_client.android.options.context.SupportsChromedriverExecutableOption; +import io.appium.java_client.android.options.signing.SupportsKeystoreOptions; import io.appium.java_client.internal.ReflectionHelpers; -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.options.SupportsAppOption; import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.appium.java_client.service.local.flags.ServerArgument; import lombok.SneakyThrows; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.os.ExecutableFinder; @@ -52,6 +50,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; public final class AppiumServiceBuilder @@ -86,8 +85,11 @@ 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()); - private static final List PATH_CAPABILITIES = ImmutableList.of(AndroidMobileCapabilityType.KEYSTORE_PATH, - AndroidMobileCapabilityType.CHROMEDRIVER_EXECUTABLE, MobileCapabilityType.APP); + private static final List PATH_CAPABILITIES = List.of( + SupportsChromedriverExecutableOption.CHROMEDRIVER_EXECUTABLE_OPTION, + SupportsKeystoreOptions.KEYSTORE_PATH_OPTION, + SupportsAppOption.APP_OPTION + ); public AppiumServiceBuilder() { usingPort(DEFAULT_APPIUM_PORT); @@ -142,14 +144,14 @@ private static File findNpm() { private static File findMainScript() { File npm = findNpm(); - List cmdLine = SystemUtils.IS_OS_WINDOWS + List cmdLine = System.getProperty("os.name").toLowerCase().contains("win") // npm is a batch script, so on windows we need to use cmd.exe in order to execute it ? Arrays.asList("cmd.exe", "/c", String.format("\"%s\" root -g", npm.getAbsolutePath())) : Arrays.asList(npm.getAbsolutePath(), "root", "-g"); ProcessBuilder pb = new ProcessBuilder(cmdLine); String nodeModulesRoot; try { - nodeModulesRoot = IOUtils.toString(pb.start().getInputStream(), StandardCharsets.UTF_8).trim(); + nodeModulesRoot = new String(pb.start().getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim(); } catch (IOException e) { throw new InvalidServerInstanceException( "Cannot retrieve the path to the folder where NodeJS modules are located", e); @@ -282,10 +284,10 @@ public AppiumServiceBuilder withIPAddress(String ipAddress) { @Nullable private static File loadPathFromEnv(String envVarName) { String fullPath = System.getProperty(envVarName); - if (StringUtils.isBlank(fullPath)) { + if (isNullOrEmpty(fullPath)) { fullPath = System.getenv(envVarName); } - return StringUtils.isBlank(fullPath) ? null : new File(fullPath); + return isNullOrEmpty(fullPath) ? null : new File(fullPath); } private void loadPathToMainScript() { @@ -362,7 +364,7 @@ protected ImmutableList createArgs() { argList.add("--port"); argList.add(String.valueOf(getPort())); - if (StringUtils.isBlank(ipAddress)) { + if (isNullOrEmpty(ipAddress)) { ipAddress = BROADCAST_IP4_ADDRESS; } argList.add("--address"); @@ -378,12 +380,12 @@ protected ImmutableList createArgs() { for (Map.Entry entry : entries) { String argument = entry.getKey(); String value = entry.getValue(); - if (StringUtils.isBlank(argument) || value == null) { + if (isNullOrEmpty(argument) || value == null) { continue; } argList.add(argument); - if (!StringUtils.isBlank(value)) { + if (!isNullOrEmpty(value)) { argList.add(value); } } 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 19b69c38c..8bf2b9679 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 @@ -38,15 +38,6 @@ public enum GeneralServerFlag implements ServerArgument { * Enables session override (clobbering). Default: false */ SESSION_OVERRIDE("--session-override"), - /** - * Pre-launch the application before allowing the first session - * (Requires –app and, for Android, –app-pkg and –app-activity). - * Default: false - * - * @deprecated This argument has been removed from Appium 2.0 - */ - @Deprecated - PRE_LAUNCH("--pre-launch"), /** * The message log level to be shown. * Sample: --log-level debug 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 99f16d8d3..33c5d8aa6 100644 --- a/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java +++ b/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java @@ -16,16 +16,16 @@ package io.appium.java_client.ws; -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; import javax.annotation.Nullable; +import java.lang.ref.WeakReference; import java.net.URI; -import java.time.Duration; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -38,6 +38,12 @@ public class StringWebSocketClient implements WebSocket.Listener, private volatile boolean isListening = false; + private final WeakReference httpClient; + + public StringWebSocketClient(HttpClient httpClient) { + this.httpClient = new WeakReference<>(httpClient); + } + private URI endpoint; private void setEndpoint(URI endpoint) { @@ -64,26 +70,31 @@ public void connect(URI endpoint) { return; } - 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); + Objects.requireNonNull(httpClient.get()).openSocket(request, this); onOpen(); setEndpoint(endpoint); } + /** + * The callback method invoked on websocket opening. + */ public void onOpen() { - getConnectionHandlers().forEach(Runnable::run); - isListening = true; + try { + getConnectionHandlers().forEach(Runnable::run); + } finally { + isListening = true; + } } @Override public void onClose(int code, String reason) { - getDisconnectionHandlers().forEach(Runnable::run); - isListening = false; + try { + getDisconnectionHandlers().forEach(Runnable::run); + } finally { + isListening = false; + } } @Override diff --git a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java index 259dd0d4d..1e0bff096 100644 --- a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java +++ b/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java @@ -16,16 +16,15 @@ package io.appium.java_client.android; +import io.appium.java_client.android.options.UiAutomator2Options; 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.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; @@ -53,11 +52,9 @@ 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); + driver = new AndroidDriver(service.getUrl(), new UiAutomator2Options() + .withBrowserName(MobileBrowserType.CHROME) + .setDeviceName("Android Emulator")); //This time out is set because test can be run on slow Android SDK emulator PageFactory.initElements(new AppiumFieldDecorator(driver, ofSeconds(5)), this); } diff --git a/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java index 7b1eec03a..3d90540a4 100644 --- a/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java +++ b/src/test/java/io/appium/java_client/internal/AppiumUserAgentFilterTest.java @@ -1,6 +1,6 @@ package io.appium.java_client.internal; -import io.appium.java_client.AppiumUserAgentFilter; +import io.appium.java_client.internal.filters.AppiumUserAgentFilter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/src/test/java/io/appium/java_client/internal/ConfigTest.java b/src/test/java/io/appium/java_client/internal/ConfigTest.java index 46651eac6..f509a4d23 100644 --- a/src/test/java/io/appium/java_client/internal/ConfigTest.java +++ b/src/test/java/io/appium/java_client/internal/ConfigTest.java @@ -1,6 +1,6 @@ package io.appium.java_client.internal; -import io.appium.java_client.AppiumUserAgentFilter; +import io.appium.java_client.internal.filters.AppiumUserAgentFilter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; diff --git a/src/test/java/io/appium/java_client/remote/MobileOptionsTest.java b/src/test/java/io/appium/java_client/remote/MobileOptionsTest.java deleted file mode 100644 index 13faeb583..000000000 --- a/src/test/java/io/appium/java_client/remote/MobileOptionsTest.java +++ /dev/null @@ -1,110 +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.remote; - -import org.junit.jupiter.api.Test; -import org.openqa.selenium.MutableCapabilities; -import org.openqa.selenium.ScreenOrientation; - -import java.net.MalformedURLException; -import java.net.URL; -import java.time.Duration; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class MobileOptionsTest { - private MobileOptions mobileOptions = new MobileOptions<>(); - - @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()); - } -} diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java index 36518f4ed..157077f88 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java @@ -19,7 +19,6 @@ import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.remote.AutomationName; -import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.github.bonigarcia.wdm.WebDriverManager; @@ -29,10 +28,14 @@ import java.time.Duration; import static io.appium.java_client.TestResources.apiDemosApk; +import static io.appium.java_client.remote.options.SupportsAppOption.APP_OPTION; +import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; +import static io.appium.java_client.remote.options.SupportsDeviceNameOption.DEVICE_NAME_OPTION; import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; class StartingAppLocallyAndroidTest { @@ -46,11 +49,11 @@ void startingAndroidAppWithCapabilitiesOnlyTest() { Capabilities caps = driver.getCapabilities(); assertTrue(MobilePlatform.ANDROID.equalsIgnoreCase( - String.valueOf(caps.getCapability(MobileCapabilityType.PLATFORM_NAME))) + String.valueOf(caps.getCapability(PLATFORM_NAME))) ); - assertEquals(AutomationName.ANDROID_UIAUTOMATOR2, caps.getCapability(MobileCapabilityType.AUTOMATION_NAME)); - assertNotNull(caps.getCapability(MobileCapabilityType.DEVICE_NAME)); - assertEquals(apiDemosApk().toAbsolutePath().toString(), caps.getCapability(MobileCapabilityType.APP)); + assertEquals(AutomationName.ANDROID_UIAUTOMATOR2, caps.getCapability(AUTOMATION_NAME_OPTION)); + assertNotNull(caps.getCapability(DEVICE_NAME_OPTION)); + assertEquals(apiDemosApk().toAbsolutePath().toString(), caps.getCapability(APP_OPTION)); } finally { driver.quit(); } @@ -70,9 +73,9 @@ void startingAndroidAppWithCapabilitiesAndServiceTest() { Capabilities caps = driver.getCapabilities(); assertTrue(MobilePlatform.ANDROID.equalsIgnoreCase( - String.valueOf(caps.getCapability(MobileCapabilityType.PLATFORM_NAME))) + String.valueOf(caps.getCapability(PLATFORM_NAME))) ); - assertNotNull(caps.getCapability(MobileCapabilityType.DEVICE_NAME)); + assertNotNull(caps.getCapability(DEVICE_NAME_OPTION)); } finally { driver.quit(); } @@ -105,9 +108,9 @@ void startingAndroidAppWithCapabilitiesAndFlagsOnServerSideTest() { Capabilities caps = driver.getCapabilities(); assertTrue(MobilePlatform.ANDROID.equalsIgnoreCase( - String.valueOf(caps.getCapability(MobileCapabilityType.PLATFORM_NAME))) + String.valueOf(caps.getCapability(PLATFORM_NAME))) ); - assertNotNull(caps.getCapability(MobileCapabilityType.DEVICE_NAME)); + assertNotNull(caps.getCapability(DEVICE_NAME_OPTION)); } finally { driver.quit(); } diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java index 3ce6bd9af..5041231ec 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java +++ b/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java @@ -20,7 +20,6 @@ import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.ios.options.XCUITestOptions; import io.appium.java_client.remote.AutomationName; -import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.flags.GeneralServerFlag; import org.junit.jupiter.api.Test; @@ -28,10 +27,12 @@ import org.openqa.selenium.Platform; import static io.appium.java_client.TestResources.uiCatalogAppZip; +import static io.appium.java_client.remote.options.SupportsDeviceNameOption.DEVICE_NAME_OPTION; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; class StartingAppLocallyIosTest { @Test @@ -72,9 +73,9 @@ void startingIOSAppWithCapabilitiesAndServiceTest() { IOSDriver driver = new IOSDriver(builder, options); try { Capabilities caps = driver.getCapabilities(); - assertTrue(caps.getCapability(MobileCapabilityType.PLATFORM_NAME) + assertTrue(caps.getCapability(PLATFORM_NAME) .toString().equalsIgnoreCase(MobilePlatform.IOS)); - assertNotNull(caps.getCapability(MobileCapabilityType.DEVICE_NAME)); + assertNotNull(caps.getCapability(DEVICE_NAME_OPTION)); } finally { driver.quit(); } From 269972643a0ca095b9e42fd7131319458384082f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 13 Oct 2023 13:25:13 +0200 Subject: [PATCH 154/314] docs: Add 8 to 9 migration guide (#2039) --- README.md | 10 +++++-- docs/v8-to-v9-migration-guide.md | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 docs/v8-to-v9-migration-guide.md diff --git a/README.md b/README.md index 97ecb5017..892af6456 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,15 @@ This is the Java language bindings for writing Appium Tests that conform to [WebDriver Protocol](https://w3c.github.io/webdriver/) -## v8 Migration +## v8 to v9 Migration + +Since v9 the client only supports Java 11 and above. +Follow the [v8 to v9 Migration Guide](./docs/v8-to-v9-migration-guide.md) in order to streamline the migration process. + +## v7 to v8 Migration Since version 8 Appium Java Client had several major changes, which might require to -update your client code. Make sure to follow the [v7 to v8 Migration Guide](https://github.com/appium/java-client/blob/master/docs/v7-to-v8-migration-guide.md) +update your client code. Make sure to follow the [v7 to v8 Migration Guide](./docs/v7-to-v8-migration-guide.md) in order to streamline the migration process. ## Add Appium java client to your test framework @@ -90,6 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client --------------------|----------------- + `9.0.0` | `4.14.1` `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` diff --git a/docs/v8-to-v9-migration-guide.md b/docs/v8-to-v9-migration-guide.md new file mode 100644 index 000000000..a0b57cd35 --- /dev/null +++ b/docs/v8-to-v9-migration-guide.md @@ -0,0 +1,46 @@ +This is the list of main changes between major versions 8 and 9 of Appium +java client. This list should help you to successfully migrate your +existing automated tests codebase. + + +## The support for Java compilers below version 11 has been dropped + +- The minimum supported Java version is now 11. The library won't work +with Java compilers below this version. + +## The minimum supported Selenium version is set to 4.14.1 + +- Selenium versions below 4.14.1 won't work with Appium java client 9+. +Check the [Compatibility Matrix](../README.md#compatibility-matrix) for more +details about versions compatibility. + +## Removed previously deprecated items + +- `MobileBy` class has been removed. Use +[AppiumBy](../src/main/java/io/appium/java_client/AppiumBy.java) instead +- `launchApp`, `resetApp` and `closeApp` methods along with their +`SupportsLegacyAppManagement` container. +Use [the corresponding extension methods](https://github.com/appium/appium/issues/15807) instead. +- `WindowsBy` class and related location strategies. +- `ByAll` class has been removed in favour of the same class from Selenium lib. +- `AndroidMobileCapabilityType` interface. Use +[UIAutomator2 driver options](../src/main/java/io/appium/java_client/android/options/UiAutomator2Options.java) +or [Espresso driver options](../src/main/java/io/appium/java_client/android/options/EspressoOptions.java) instead. +- `IOSMobileCapabilityType` interface. Use +[XCUITest driver options](../src/main/java/io/appium/java_client/ios/options/XCUITestOptions.java) instead. +- `MobileCapabilityType` interface. Use +[driver options](../src/main/java/io/appium/java_client/remote/options/BaseOptions.java) instead. +- `MobileOptions` class. Use +[driver options](../src/main/java/io/appium/java_client/remote/options/BaseOptions.java) instead. +- `YouiEngineCapabilityType` interface. Use +[driver options](../src/main/java/io/appium/java_client/remote/options/BaseOptions.java) instead. +- Several misspelled methods. Use properly spelled alternatives instead. +- `startActivity` method from AndroidDriver. Use +[mobile: startActivity](https://github.com/appium/appium-uiautomator2-driver#mobile-startactivity) +extension method instead. +- `APPIUM` constant from the AutomationName interface. It is not needed anymore. +- `PRE_LAUNCH` value from the GeneralServerFlag enum. It is not needed anymore. + +## Moved items + +- `AppiumUserAgentFilter` class to `io.appium.java_client.internal.filters` package. From d7b3be4264b530ca66bc44dcf3ef99c8b372de5d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 13 Oct 2023 14:59:16 +0300 Subject: [PATCH 155/314] ci: Use LTS Java in CI (#2040) --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a7094028a..5d15dfbc2 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -35,10 +35,10 @@ jobs: # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available platform: macos-12 e2e-tests: ios - - java: 15 + - java: 17 platform: macos-latest e2e-tests: android - - java: 17 + - java: 21 platform: ubuntu-latest fail-fast: false From 0701528838f38f103ff303e095e30c422487c749 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 13 Oct 2023 19:00:00 +0300 Subject: [PATCH 156/314] test: Align Selenium version in test dependencies (#2042) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 05e17653f..a5c3c2681 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.13.0') + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.14.1') testImplementation 'org.seleniumhq.selenium:selenium-api' testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' testImplementation 'org.seleniumhq.selenium:selenium-support' From 37ad22457bd68c2a34fa79a8ffc4e8c6275c7b8f Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Sat, 14 Oct 2023 16:24:25 +0530 Subject: [PATCH 157/314] release: v9.0.0 and update release notes (#2041) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607a44c86..a16add346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.0.0_ +- **[DOCUMENTATION]** + - Add 8 to 9 migration guide. [#2039](https://github.com/appium/java-client/pull/2039) +- **[BREAKING CHANGE]** [#2036](https://github.com/appium/java-client/pull/2036) + - Set minimum Java version to 11. + - The previously deprecated MobileBy class has been removed. Use AppiumBy instead. + - The previously deprecated launchApp, resetApp and closeApp methods have been removed. Use extension methods instead. + - The previously deprecated WindowsBy class and related location strategies have been removed. + - The previously deprecated ByAll class has been removed in favour of the Selenium one. + - The previously deprecated AndroidMobileCapabilityType interface has been removed. Use driver options instead + - The previously deprecated IOSMobileCapabilityType interface has been removed. Use driver options instead + - The previously deprecated MobileCapabilityType interface has been removed. Use driver options instead + - The previously deprecated MobileOptions class has been removed. Use driver options instead + - The previously deprecated YouiEngineCapabilityType interface has been removed. Use driver options instead + - Removed several misspelled methods. Use properly spelled alternatives instead + - Removed startActivity method from AndroidDriver. Use 'mobile: startActivity' extension method instead + - Removed the previously deprecated APPIUM constant from the AutomationName interface + - Removed the previously deprecated PRE_LAUNCH value from the GeneralServerFlag enum + - Moved AppiumUserAgentFilter class to io.appium.java_client.internal.filters package +- **[REFACTOR]** + - Align Selenium version in test dependencies. [#2042](https://github.com/appium/java-client/pull/2042) +- **[DEPENDENCY CHANGE]** + - Removed dependencies to Apache Commons libraries. + _8.6.0_ - **[BUG FIX]** diff --git a/gradle.properties b/gradle.properties index 9b917685b..6e923bc15 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.14.1 # Please increment the value in a release -appiumClient.version=8.6.0 +appiumClient.version=9.0.0 From 72c33e0f26456cfacec18a51fba72239941a02cf Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 16 Oct 2023 08:33:25 +0200 Subject: [PATCH 158/314] docs: Update gesture shortcut links --- docs/v7-to-v8-migration-guide.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/v7-to-v8-migration-guide.md b/docs/v7-to-v8-migration-guide.md index a0ab9d256..b3be7def0 100644 --- a/docs/v7-to-v8-migration-guide.md +++ b/docs/v7-to-v8-migration-guide.md @@ -86,8 +86,27 @@ or the corresponding extension methods for the driver (if available). Check - https://www.youtube.com/watch?v=oAJ7jwMNFVU - https://appiumpro.com/editions/30-ios-specific-touch-action-methods - - https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/android/android-mobile-gestures.md - - https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/ios/ios-xctest-mobile-gestures.md + - Android gesture shortcuts: + * [mobile: longClickGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-longclickgesture) + * [mobile: doubleClickGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-doubleclickgesture) + * [mobile: clickGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-clickgesture) + * [mobile: dragGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-draggesture) + * [mobile: flingGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-flinggesture) + * [mobile: pinchOpenGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-pinchopengesture) + * [mobile: pinchCloseGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-pinchclosegesture) + * [mobile: swipeGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-swipegesture) + * [mobile: scrollGesture](https://github.com/appium/appium-uiautomator2-driver/blob/master/docs/android-mobile-gestures.md#mobile-scrollgesture) + - iOS gesture shortcuts: + * [mobile: swipe](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-swipe) + * [mobile: scroll](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-scroll) + * [mobile: pinch](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-pinch) + * [mobile: doubleTap](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-doubletap) + * [mobile: touchAndHold](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-touchandhold) + * [mobile: twoFingerTap](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-twofingertap) + * [mobile: tap](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-tap) + * [mobile: dragFromToForDuration](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-dragfromtoforduration) + * [mobile: dragFromToWithVelocity](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-dragfromtowithvelocity) + * [mobile: scrollToElement](https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-scrolltoelement) - https://appiumpro.com/editions/29-automating-complex-gestures-with-the-w3c-actions-api for more details on how to properly apply W3C Actions to your automation context. From 10a3f627515f7db904e11c921dae1dd49453ce9d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 18 Oct 2023 04:29:22 +0300 Subject: [PATCH 159/314] docs: Add missing clients versions to compatibility matrix (#2049) --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 892af6456..c79742f12 100644 --- a/README.md +++ b/README.md @@ -93,13 +93,14 @@ dependencies { ``` ### Compatibility Matrix - Appium Java Client | Selenium client ---------------------|----------------- - `9.0.0` | `4.14.1` - `8.5.0`, `8.5.1` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` - `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` - `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` - `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` + Appium Java Client | Selenium client +---------------------------|----------------- + `9.0.0` | `4.14.1` + N/A | `4.14.0` + `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` + `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` + `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` + `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` #### Why is it so complicated? From a9240e38bfae96069043dec7b43dec53024f0de2 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 18 Oct 2023 12:31:26 +0300 Subject: [PATCH 160/314] refactor: Use Java 9+ APIs instead of outdated/3rd-party APIs (#2048) --- .../io/appium/java_client/AppiumDriver.java | 3 +- .../java_client/AppiumExecutionMethod.java | 3 +- .../java_client/CommandExecutionHelper.java | 4 +- .../appium/java_client/ErrorCodesMobile.java | 5 +- .../appium/java_client/ExecuteCDPCommand.java | 4 +- .../java_client/ExecutesDriverScript.java | 4 +- .../io/appium/java_client/HasAppStrings.java | 20 ++-- .../appium/java_client/HasBrowserCheck.java | 4 +- .../io/appium/java_client/HasDeviceTime.java | 4 +- .../java_client/HasOnScreenKeyboard.java | 6 +- .../java_client/HidesKeyboardWithKeyName.java | 9 +- .../appium/java_client/InteractsWithApps.java | 57 ++++++----- .../io/appium/java_client/LocksDevice.java | 10 +- .../io/appium/java_client/LogsEvents.java | 3 +- .../io/appium/java_client/MobileCommand.java | 82 ++++++++-------- .../appium/java_client/MultiTouchAction.java | 9 +- .../io/appium/java_client/PullsFiles.java | 21 ++-- .../io/appium/java_client/PushesFiles.java | 4 +- .../appium/java_client/ScreenshotState.java | 6 +- .../io/appium/java_client/TouchAction.java | 21 ++-- .../android/AndroidMobileCommandHelper.java | 95 +++++++++---------- .../AndroidStartScreenRecordingOptions.java | 6 +- .../android/AuthenticatesByFinger.java | 5 +- .../android/CanReplaceElementValue.java | 7 +- .../android/HasAndroidClipboard.java | 17 ++-- .../HasSupportedPerformanceDataType.java | 4 +- .../java_client/android/StartsActivity.java | 7 +- .../android/SupportsGpsStateManagement.java | 4 +- .../SupportsNetworkStateManagement.java | 15 ++- .../SupportsSpecialEmulatorCommands.java | 31 +++--- .../AndroidInstallApplicationOptions.java | 14 +-- .../AndroidRemoveApplicationOptions.java | 8 +- .../AndroidTerminateApplicationOptions.java | 6 +- .../connection/HasNetworkConnection.java | 11 +-- .../geolocation/AndroidGeoLocation.java | 6 +- .../SupportsExtendedGeolocationCommands.java | 10 +- .../android/nativekey/PressesKey.java | 5 +- .../options/server/EspressoBuildConfig.java | 2 +- .../java_client/clipboard/HasClipboard.java | 20 ++-- .../driverscripts/ScriptOptions.java | 8 +- .../BaseComparisonOptions.java | 2 +- .../FeaturesMatchingOptions.java | 6 +- .../OccurrenceMatchingOptions.java | 6 +- .../java_client/ios/HasIOSClipboard.java | 6 +- .../io/appium/java_client/ios/IOSDriver.java | 5 +- .../ios/IOSMobileCommandHelper.java | 12 +-- .../ios/IOSStartScreenRecordingOptions.java | 8 +- .../java_client/ios/PerformsTouchID.java | 7 +- .../pagefactory/AppiumFieldDecorator.java | 3 +- .../pagefactory/bys/ContentMappedBy.java | 4 +- .../pagefactory/bys/builder/ByChained.java | 13 ++- .../io/appium/java_client/proxy/Helpers.java | 3 +- .../remote/AppiumCommandExecutor.java | 16 ++-- .../AppiumNewSessionCommandPayload.java | 6 +- .../remote/SupportsContextSwitching.java | 10 +- .../java_client/remote/SupportsRotation.java | 3 +- .../BaseScreenRecordingOptions.java | 4 +- .../BaseStartScreenRecordingOptions.java | 4 +- .../ScreenRecordingUploadOptions.java | 16 ++-- .../local/AppiumDriverLocalService.java | 12 +-- .../service/local/AppiumServiceBuilder.java | 6 +- .../java_client/touch/LongPressOptions.java | 4 +- .../appium/java_client/touch/WaitOptions.java | 4 +- .../touch/offset/ElementOption.java | 4 +- .../AndroidAbilityToUseSupplierTest.java | 6 +- .../android/AndroidDataMatcherTest.java | 8 +- .../android/AndroidDriverTest.java | 6 +- .../java_client/android/AndroidTouchTest.java | 6 +- .../android/AndroidViewMatcherTest.java | 8 +- .../java_client/android/BaseAndroidTest.java | 5 +- .../java_client/android/ClipboardTest.java | 7 +- .../android/OpenNotificationsTest.java | 4 +- .../drivers/options/OptionsBuildingTest.java | 10 +- .../events/stubs/EmptyWebDriver.java | 3 +- .../events/stubs/StubWebElement.java | 3 +- .../appium/java_client/ios/IOSDriverTest.java | 8 +- .../widget/tests/AbstractStubWebDriver.java | 3 +- .../widget/tests/DefaultStubWidget.java | 3 +- .../widget/tests/StubWebElement.java | 11 +-- .../service/local/ServerBuilderTest.java | 4 +- 80 files changed, 387 insertions(+), 432 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 28d0bd529..f4721dbf5 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.internal.ReflectionHelpers; import io.appium.java_client.internal.SessionHelpers; @@ -153,7 +152,7 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa super(); ReflectionHelpers.setPrivateFieldValue( RemoteWebDriver.class, this, "capabilities", new ImmutableCapabilities( - ImmutableMap.of( + Map.of( PLATFORM_NAME, platformName, APPIUM_PREFIX + AUTOMATION_NAME_OPTION, automationName ) diff --git a/src/main/java/io/appium/java_client/AppiumExecutionMethod.java b/src/main/java/io/appium/java_client/AppiumExecutionMethod.java index 9b9f79cb9..34a848f79 100644 --- a/src/main/java/io/appium/java_client/AppiumExecutionMethod.java +++ b/src/main/java/io/appium/java_client/AppiumExecutionMethod.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.remote.ExecuteMethod; import org.openqa.selenium.remote.Response; @@ -40,7 +39,7 @@ public Object execute(String commandName, Map parameters) { Response response; if (parameters == null || parameters.isEmpty()) { - response = driver.execute(commandName, ImmutableMap.of()); + response = driver.execute(commandName, Map.of()); } else { response = driver.execute(commandName, parameters); } diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 692f7cf0f..7cb011677 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -16,11 +16,9 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; -import java.util.AbstractMap; import java.util.Collections; import java.util.Map; @@ -66,7 +64,7 @@ public static T executeScript(ExecutesMethod executesMethod, String scriptNa public static T executeScript( ExecutesMethod executesMethod, String scriptName, @Nullable Map args ) { - return execute(executesMethod, new AbstractMap.SimpleEntry<>(EXECUTE_SCRIPT, ImmutableMap.of( + return execute(executesMethod, Map.entry(EXECUTE_SCRIPT, Map.of( "script", scriptName, "args", (args == null || args.isEmpty()) ? Collections.emptyList() : Collections.singletonList(args) ))); diff --git a/src/main/java/io/appium/java_client/ErrorCodesMobile.java b/src/main/java/io/appium/java_client/ErrorCodesMobile.java index 473c014d9..c70514b0f 100644 --- a/src/main/java/io/appium/java_client/ErrorCodesMobile.java +++ b/src/main/java/io/appium/java_client/ErrorCodesMobile.java @@ -17,7 +17,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.ErrorCodes; @@ -32,9 +31,7 @@ public class ErrorCodesMobile extends ErrorCodes { public static final int NO_SUCH_CONTEXT = 35; - private static Map statusToState = - ImmutableMap.builder().put(NO_SUCH_CONTEXT, "No such context found") - .build(); + private static Map statusToState = Map.of(NO_SUCH_CONTEXT, "No such context found"); /** * Returns the exception type that corresponds to the given {@code statusCode}. All unrecognized diff --git a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java index 8cf09f357..5af5e1f83 100644 --- a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java +++ b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java @@ -24,8 +24,8 @@ 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; +import static java.util.Objects.requireNonNull; public interface ExecuteCDPCommand extends ExecutesMethod { @@ -40,7 +40,7 @@ public interface ExecuteCDPCommand extends ExecutesMethod { */ default Map executeCdpCommand(String command, @Nullable Map params) { Map data = new HashMap<>(); - data.put("cmd", checkNotNull(command)); + data.put("cmd", requireNonNull(command)); data.put("params", params == null ? Collections.emptyMap() : params); Response response = execute(EXECUTE_GOOGLE_CDP_COMMAND, data); //noinspection unchecked diff --git a/src/main/java/io/appium/java_client/ExecutesDriverScript.java b/src/main/java/io/appium/java_client/ExecutesDriverScript.java index 997b061a2..1ffebedb9 100644 --- a/src/main/java/io/appium/java_client/ExecutesDriverScript.java +++ b/src/main/java/io/appium/java_client/ExecutesDriverScript.java @@ -24,8 +24,8 @@ import java.util.HashMap; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.EXECUTE_DRIVER_SCRIPT; +import static java.util.Objects.requireNonNull; public interface ExecutesDriverScript extends ExecutesMethod { @@ -46,7 +46,7 @@ public interface ExecutesDriverScript extends ExecutesMethod { */ default ScriptValue executeDriverScript(String script, @Nullable ScriptOptions options) { Map data = new HashMap<>(); - data.put("script", checkNotNull(script)); + data.put("script", requireNonNull(script)); if (options != null) { data.putAll(options.build()); } diff --git a/src/main/java/io/appium/java_client/HasAppStrings.java b/src/main/java/io/appium/java_client/HasAppStrings.java index 439e08bcd..1224b26f9 100644 --- a/src/main/java/io/appium/java_client/HasAppStrings.java +++ b/src/main/java/io/appium/java_client/HasAppStrings.java @@ -16,14 +16,11 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.UnsupportedCommandException; -import java.util.AbstractMap; import java.util.Map; import static io.appium.java_client.MobileCommand.GET_STRINGS; -import static io.appium.java_client.MobileCommand.prepareArguments; public interface HasAppStrings extends ExecutesMethod, CanRememberExtensionPresence { /** @@ -52,14 +49,14 @@ default Map getAppStringMap() { default Map getAppStringMap(String language) { final String extName = "mobile: getAppStrings"; try { - return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "language", language )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments("language", language)) + Map.entry(GET_STRINGS, Map.of("language", language)) ); } } @@ -75,18 +72,17 @@ default Map getAppStringMap(String language) { */ default Map getAppStringMap(String language, String stringFile) { final String extName = "mobile: getAppStrings"; + Map args = Map.of( + "language", language, + "stringFile", stringFile + ); try { - return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( - "language", language, - "stringFile", stringFile - )); + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - String[] parameters = new String[]{"language", "stringFile"}; - Object[] values = new Object[]{language, stringFile}; return CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GET_STRINGS, prepareArguments(parameters, values)) + Map.entry(GET_STRINGS, args) ); } } diff --git a/src/main/java/io/appium/java_client/HasBrowserCheck.java b/src/main/java/io/appium/java_client/HasBrowserCheck.java index c5cd62cdb..76094b5ca 100644 --- a/src/main/java/io/appium/java_client/HasBrowserCheck.java +++ b/src/main/java/io/appium/java_client/HasBrowserCheck.java @@ -6,8 +6,8 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Objects.requireNonNull; public interface HasBrowserCheck extends ExecutesMethod, HasCapabilities { /** @@ -20,7 +20,7 @@ default boolean isBrowser() { CapabilityType.BROWSER_NAME, String.class); if (!isNullOrEmpty(browserName)) { try { - return checkNotNull( + return requireNonNull( CommandExecutionHelper.executeScript(this, "return !!window.navigator;") ); } catch (WebDriverException ign) { diff --git a/src/main/java/io/appium/java_client/HasDeviceTime.java b/src/main/java/io/appium/java_client/HasDeviceTime.java index f29e3f3f1..e450f28f1 100644 --- a/src/main/java/io/appium/java_client/HasDeviceTime.java +++ b/src/main/java/io/appium/java_client/HasDeviceTime.java @@ -16,7 +16,7 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; +import java.util.Map; public interface HasDeviceTime extends ExecutesMethod { @@ -32,7 +32,7 @@ public interface HasDeviceTime extends ExecutesMethod { */ default String getDeviceTime(String format) { return CommandExecutionHelper.executeScript( - this, "mobile: getDeviceTime", ImmutableMap.of("format", format) + this, "mobile: getDeviceTime", Map.of("format", format) ); } diff --git a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java index d54c4a91f..b242d2b01 100644 --- a/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java +++ b/src/main/java/io/appium/java_client/HasOnScreenKeyboard.java @@ -2,8 +2,8 @@ import org.openqa.selenium.UnsupportedCommandException; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.isKeyboardShownCommand; +import static java.util.Objects.requireNonNull; public interface HasOnScreenKeyboard extends ExecutesMethod, CanRememberExtensionPresence { @@ -16,10 +16,10 @@ public interface HasOnScreenKeyboard extends ExecutesMethod, CanRememberExtensio default boolean isKeyboardShown() { final String extName = "mobile: isKeyboardShown"; try { - return checkNotNull(CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName)); + return requireNonNull(CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName)); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return checkNotNull( + return requireNonNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), isKeyboardShownCommand()) ); } diff --git a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java index 4223ab814..c2a84bb11 100644 --- a/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java +++ b/src/main/java/io/appium/java_client/HidesKeyboardWithKeyName.java @@ -16,10 +16,11 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.UnsupportedCommandException; +import java.util.List; +import java.util.Map; + import static io.appium.java_client.MobileCommand.hideKeyboardCommand; public interface HidesKeyboardWithKeyName extends HidesKeyboard { @@ -37,8 +38,8 @@ public interface HidesKeyboardWithKeyName extends HidesKeyboard { default void hideKeyboard(String keyName) { final String extName = "mobile: hideKeyboard"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( - "keys", ImmutableList.of(keyName) + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( + "keys", List.of(keyName) )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index d61f85b19..e254f6aa6 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -28,12 +28,10 @@ import javax.annotation.Nullable; import java.time.Duration; -import java.util.AbstractMap; import java.util.Collections; import java.util.Map; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.ACTIVATE_APP; import static io.appium.java_client.MobileCommand.INSTALL_APP; import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; @@ -41,6 +39,7 @@ import static io.appium.java_client.MobileCommand.REMOVE_APP; import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; import static io.appium.java_client.MobileCommand.TERMINATE_APP; +import static java.util.Objects.requireNonNull; @SuppressWarnings({"rawtypes", "unchecked"}) public interface InteractsWithApps extends ExecutesMethod, CanRememberExtensionPresence { @@ -75,11 +74,11 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions Map args = ImmutableMap.builder() .put("appPath", appPath) .putAll(Optional.ofNullable(options).map( - opts -> ImmutableMap.of("options", opts.build()) - ).orElseGet(ImmutableMap::of)) + opts -> Map.of("options", opts.build()) + ).orElseGet(Map::of)) .build(); CommandExecutionHelper.execute( - markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(INSTALL_APP, args) + markExtensionAbsence(extName), Map.entry(INSTALL_APP, args) ); } } @@ -93,18 +92,18 @@ default void installApp(String appPath, @Nullable BaseInstallApplicationOptions default boolean isAppInstalled(String bundleId) { final String extName = "mobile: isAppInstalled"; try { - return checkNotNull( - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + return requireNonNull( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "bundleId", bundleId, "appId", bundleId )) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback - return checkNotNull( + return requireNonNull( CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId)) + Map.entry(IS_APP_INSTALLED, Map.of("bundleId", bundleId)) ) ); } @@ -121,16 +120,14 @@ default boolean isAppInstalled(String bundleId) { default void runAppInBackground(Duration duration) { final String extName = "mobile: backgroundApp"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "seconds", duration.toMillis() / 1000.0 )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(RUN_APP_IN_BACKGROUND, ImmutableMap.of( - "seconds", duration.toMillis() / 1000.0) - ) + Map.entry(RUN_APP_IN_BACKGROUND, Map.of("seconds", duration.toMillis() / 1000.0)) ); } } @@ -161,7 +158,7 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption .put("appId", bundleId) .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); - return checkNotNull( + return requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { @@ -169,14 +166,14 @@ default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOption Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - opts -> ImmutableMap.of("options", opts.build()) - ).orElseGet(ImmutableMap::of)) + opts -> Map.of("options", opts.build()) + ).orElseGet(Map::of)) .build(); //noinspection RedundantCast - return checkNotNull( + return requireNonNull( (Boolean) CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(REMOVE_APP, args) + Map.entry(REMOVE_APP, args) ) ); } @@ -214,11 +211,11 @@ default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptio Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - opts -> ImmutableMap.of("options", opts.build()) - ).orElseGet(ImmutableMap::of)) + opts -> Map.of("options", opts.build()) + ).orElseGet(Map::of)) .build(); CommandExecutionHelper.execute( - markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(ACTIVATE_APP, args) + markExtensionAbsence(extName), Map.entry(ACTIVATE_APP, args) ); } } @@ -233,10 +230,10 @@ default ApplicationState queryAppState(String bundleId) { final String extName = "mobile: queryAppState"; try { return ApplicationState.ofCode( - checkNotNull( + requireNonNull( CommandExecutionHelper.executeScript( assertExtensionExists(extName), - extName, ImmutableMap.of( + extName, Map.of( "bundleId", bundleId, "appId", bundleId ) @@ -246,10 +243,10 @@ default ApplicationState queryAppState(String bundleId) { } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback return ApplicationState.ofCode( - checkNotNull( + requireNonNull( CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId)) + Map.entry(QUERY_APP_STATE, Map.of("bundleId", bundleId)) ) ) ); @@ -282,7 +279,7 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication .put("appId", bundleId) .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) .build(); - return checkNotNull( + return requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { @@ -290,13 +287,13 @@ default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplication Map args = ImmutableMap.builder() .put("bundleId", bundleId) .putAll(Optional.ofNullable(options).map( - opts -> ImmutableMap.of("options", opts.build()) - ).orElseGet(ImmutableMap::of)) + opts -> Map.of("options", opts.build()) + ).orElseGet(Map::of)) .build(); //noinspection RedundantCast - return checkNotNull( + return requireNonNull( (Boolean) CommandExecutionHelper.execute( - markExtensionAbsence(extName), new AbstractMap.SimpleEntry<>(TERMINATE_APP, args) + markExtensionAbsence(extName), Map.entry(TERMINATE_APP, args) ) ); } diff --git a/src/main/java/io/appium/java_client/LocksDevice.java b/src/main/java/io/appium/java_client/LocksDevice.java index 60ba88ffc..bd818b21b 100644 --- a/src/main/java/io/appium/java_client/LocksDevice.java +++ b/src/main/java/io/appium/java_client/LocksDevice.java @@ -16,15 +16,15 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.UnsupportedCommandException; import java.time.Duration; +import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.getIsDeviceLockedCommand; import static io.appium.java_client.MobileCommand.lockDeviceCommand; import static io.appium.java_client.MobileCommand.unlockDeviceCommand; +import static java.util.Objects.requireNonNull; public interface LocksDevice extends ExecutesMethod, CanRememberExtensionPresence { @@ -47,7 +47,7 @@ default void lockDevice() { default void lockDevice(Duration duration) { final String extName = "mobile: lock"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "seconds", duration.getSeconds() )); } catch (UnsupportedCommandException e) { @@ -82,12 +82,12 @@ default void unlockDevice() { default boolean isDeviceLocked() { final String extName = "mobile: isLocked"; try { - return checkNotNull( + return requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName) ); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - return checkNotNull( + return requireNonNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), getIsDeviceLockedCommand()) ); } diff --git a/src/main/java/io/appium/java_client/LogsEvents.java b/src/main/java/io/appium/java_client/LogsEvents.java index 33f50d2db..6ae80bc0c 100644 --- a/src/main/java/io/appium/java_client/LogsEvents.java +++ b/src/main/java/io/appium/java_client/LogsEvents.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.serverevents.CommandEvent; import io.appium.java_client.serverevents.CustomEvent; import io.appium.java_client.serverevents.ServerEvents; @@ -41,7 +40,7 @@ public interface LogsEvents extends ExecutesMethod { * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script */ default void logEvent(CustomEvent event) { - execute(LOG_EVENT, ImmutableMap.of("vendor", event.getVendor(), "event", event.getEventName())); + execute(LOG_EVENT, Map.of("vendor", event.getVendor(), "event", event.getEventName())); } /** diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 878c13c4d..ee08c289a 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -27,9 +27,9 @@ import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import static com.google.common.base.Strings.isNullOrEmpty; @@ -389,8 +389,7 @@ public static AppiumCommandInfo deleteC(String url) { */ @Deprecated public static Map.Entry> hideKeyboardCommand(String keyName) { - return new AbstractMap.SimpleEntry<>( - HIDE_KEYBOARD, prepareArguments("keyName", keyName)); + return Map.entry(HIDE_KEYBOARD, Map.of("keyName", keyName)); } /** @@ -405,10 +404,10 @@ public static AppiumCommandInfo deleteC(String url) { @Deprecated public static Map.Entry> hideKeyboardCommand(String strategy, String keyName) { - String[] parameters = new String[]{"strategy", "key"}; - Object[] values = new Object[]{strategy, keyName}; - return new AbstractMap.SimpleEntry<>( - HIDE_KEYBOARD, prepareArguments(parameters, values)); + return Map.entry(HIDE_KEYBOARD, Map.of( + "strategy", strategy, + "key", keyName + )); } /** @@ -417,7 +416,9 @@ public static AppiumCommandInfo deleteC(String url) { * @param param is a parameter name. * @param value is the parameter value. * @return built {@link ImmutableMap}. + * @deprecated Use {@link Map#of(Object, Object)} */ + @Deprecated public static ImmutableMap prepareArguments(String param, Object value) { ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -431,7 +432,9 @@ public static ImmutableMap prepareArguments(String param, * @param params is the array with parameter names. * @param values is the array with parameter values. * @return built {@link ImmutableMap}. + * @deprecated Use {@link Map#of(Object, Object, Object, Object)} */ + @Deprecated public static ImmutableMap prepareArguments(String[] params, Object[] values) { ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -452,8 +455,7 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> pressKeyCodeCommand(int key) { - return new AbstractMap.SimpleEntry<>( - PRESS_KEY_CODE, prepareArguments("keycode", key)); + return Map.entry(PRESS_KEY_CODE, Map.of("keycode", key)); } /** @@ -467,10 +469,10 @@ public static ImmutableMap prepareArguments(String[] params, @Deprecated public static Map.Entry> pressKeyCodeCommand(int key, Integer metastate) { - String[] parameters = new String[]{"keycode", "metastate"}; - Object[] values = new Object[]{key, metastate}; - return new AbstractMap.SimpleEntry<>( - PRESS_KEY_CODE, prepareArguments(parameters, values)); + return Map.entry(PRESS_KEY_CODE, Map.of( + "keycode", key, + "metastate", metastate + )); } /** @@ -482,8 +484,7 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> longPressKeyCodeCommand(int key) { - return new AbstractMap.SimpleEntry<>( - LONG_PRESS_KEY_CODE, prepareArguments("keycode", key)); + return Map.entry(LONG_PRESS_KEY_CODE, Map.of("keycode", key)); } /** @@ -497,10 +498,10 @@ public static ImmutableMap prepareArguments(String[] params, @Deprecated public static Map.Entry> longPressKeyCodeCommand(int key, Integer metastate) { - String[] parameters = new String[]{"keycode", "metastate"}; - Object[] values = new Object[]{key, metastate}; - return new AbstractMap.SimpleEntry<>( - LONG_PRESS_KEY_CODE, prepareArguments(parameters, values)); + return Map.entry(LONG_PRESS_KEY_CODE, Map.of( + "keycode", key, + "metastate", metastate + )); } /** @@ -512,8 +513,7 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> lockDeviceCommand(Duration duration) { - return new AbstractMap.SimpleEntry<>( - LOCK, prepareArguments("seconds", duration.getSeconds())); + return Map.entry(LOCK, Map.of("seconds", duration.getSeconds())); } /** @@ -524,7 +524,7 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> unlockDeviceCommand() { - return new AbstractMap.SimpleEntry<>(UNLOCK, ImmutableMap.of()); + return Map.entry(UNLOCK, Map.of()); } /** @@ -535,19 +535,19 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> getIsDeviceLockedCommand() { - return new AbstractMap.SimpleEntry<>(IS_LOCKED, ImmutableMap.of()); + return Map.entry(IS_LOCKED, Map.of()); } public static Map.Entry> getSettingsCommand() { - return new AbstractMap.SimpleEntry<>(GET_SETTINGS, ImmutableMap.of()); + return Map.entry(GET_SETTINGS, Map.of()); } public static Map.Entry> setSettingsCommand(String setting, Object value) { - return setSettingsCommand(prepareArguments(setting, value)); + return setSettingsCommand(Map.of(setting, value)); } public static Map.Entry> setSettingsCommand(Map settings) { - return new AbstractMap.SimpleEntry<>(SET_SETTINGS, prepareArguments("settings", settings)); + return Map.entry(SET_SETTINGS, Map.of("settings", settings)); } /** @@ -560,19 +560,18 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> pushFileCommand(String remotePath, byte[] base64Data) { - String[] parameters = new String[]{"path", "data"}; - Object[] values = new Object[]{remotePath, new String(base64Data, StandardCharsets.UTF_8)}; - return new AbstractMap.SimpleEntry<>(PUSH_FILE, prepareArguments(parameters, values)); + return Map.entry(PUSH_FILE, Map.of( + "path", remotePath, + "data", new String(base64Data, StandardCharsets.UTF_8) + )); } public static Map.Entry> startRecordingScreenCommand(BaseStartScreenRecordingOptions opts) { - return new AbstractMap.SimpleEntry<>(START_RECORDING_SCREEN, - prepareArguments("options", opts.build())); + return Map.entry(START_RECORDING_SCREEN, Map.of("options", opts.build())); } public static Map.Entry> stopRecordingScreenCommand(BaseStopScreenRecordingOptions opts) { - return new AbstractMap.SimpleEntry<>(STOP_RECORDING_SCREEN, - prepareArguments("options", opts.build())); + return Map.entry(STOP_RECORDING_SCREEN, Map.of("options", opts.build())); } /** @@ -587,15 +586,12 @@ public static ImmutableMap prepareArguments(String[] params, public static Map.Entry> compareImagesCommand(ComparisonMode mode, byte[] img1Data, byte[] img2Data, @Nullable BaseComparisonOptions options) { - String[] parameters = options == null - ? new String[]{"mode", "firstImage", "secondImage"} - : new String[]{"mode", "firstImage", "secondImage", "options"}; - Object[] values = options == null - ? new Object[]{mode.toString(), new String(img1Data, StandardCharsets.UTF_8), - new String(img2Data, StandardCharsets.UTF_8)} - : new Object[]{mode.toString(), new String(img1Data, StandardCharsets.UTF_8), - new String(img2Data, StandardCharsets.UTF_8), options.build()}; - return new AbstractMap.SimpleEntry<>(COMPARE_IMAGES, prepareArguments(parameters, values)); + ImmutableMap.Builder argsBuilder = ImmutableMap.builder() + .put("mode", mode.toString()) + .put("firstImage", new String(img1Data, StandardCharsets.UTF_8)) + .put("secondImage", new String(img2Data, StandardCharsets.UTF_8)); + Optional.ofNullable(options).ifPresent(opts -> argsBuilder.put("options", options.build())); + return Map.entry(COMPARE_IMAGES, argsBuilder.build()); } /** @@ -606,6 +602,6 @@ public static ImmutableMap prepareArguments(String[] params, */ @Deprecated public static Map.Entry> isKeyboardShownCommand() { - return new AbstractMap.SimpleEntry<>(IS_KEYBOARD_SHOWN, ImmutableMap.of()); + return Map.entry(IS_KEYBOARD_SHOWN, Map.of()); } } diff --git a/src/main/java/io/appium/java_client/MultiTouchAction.java b/src/main/java/io/appium/java_client/MultiTouchAction.java index 1549fbdf5..0a133756e 100644 --- a/src/main/java/io/appium/java_client/MultiTouchAction.java +++ b/src/main/java/io/appium/java_client/MultiTouchAction.java @@ -17,7 +17,6 @@ package io.appium.java_client; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.util.List; import java.util.Map; @@ -89,10 +88,10 @@ public MultiTouchAction perform() { } protected Map> getParameters() { - ImmutableList touchActions = actions.build(); - return ImmutableMap.of("actions", - touchActions.stream().map(touchAction -> - touchAction.getParameters().get("actions")).collect(toList())); + return Map.of("actions", + actions.build().stream().map(touchAction -> + touchAction.getParameters().get("actions")).collect(toList()) + ); } /** diff --git a/src/main/java/io/appium/java_client/PullsFiles.java b/src/main/java/io/appium/java_client/PullsFiles.java index c6d4e0898..3c1e7ccff 100644 --- a/src/main/java/io/appium/java_client/PullsFiles.java +++ b/src/main/java/io/appium/java_client/PullsFiles.java @@ -16,16 +16,15 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.UnsupportedCommandException; import java.nio.charset.StandardCharsets; -import java.util.AbstractMap; import java.util.Base64; +import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.PULL_FILE; import static io.appium.java_client.MobileCommand.PULL_FOLDER; +import static java.util.Objects.requireNonNull; public interface PullsFiles extends ExecutesMethod, CanRememberExtensionPresence { @@ -45,16 +44,16 @@ default byte[] pullFile(String remotePath) { final String extName = "mobile: pullFile"; String base64String; try { - base64String = checkNotNull( + base64String = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, - ImmutableMap.of("remotePath", remotePath) + Map.of("remotePath", remotePath) ) ); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - base64String = checkNotNull( + base64String = requireNonNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(PULL_FILE, ImmutableMap.of("path", remotePath)) + Map.entry(PULL_FILE, Map.of("path", remotePath)) ) ); } @@ -77,16 +76,16 @@ default byte[] pullFolder(String remotePath) { final String extName = "mobile: pullFolder"; String base64String; try { - base64String = checkNotNull( + base64String = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, - ImmutableMap.of("remotePath", remotePath) + Map.of("remotePath", remotePath) ) ); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback - base64String = checkNotNull( + base64String = requireNonNull( CommandExecutionHelper.execute(markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(PULL_FOLDER, ImmutableMap.of("path", remotePath)) + Map.entry(PULL_FOLDER, Map.of("path", remotePath)) ) ); } diff --git a/src/main/java/io/appium/java_client/PushesFiles.java b/src/main/java/io/appium/java_client/PushesFiles.java index b91aada90..a30bf0d3b 100644 --- a/src/main/java/io/appium/java_client/PushesFiles.java +++ b/src/main/java/io/appium/java_client/PushesFiles.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.UnsupportedCommandException; import java.io.File; @@ -24,6 +23,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Base64; +import java.util.Map; import static io.appium.java_client.MobileCommand.pushFileCommand; @@ -41,7 +41,7 @@ public interface PushesFiles extends ExecutesMethod, CanRememberExtensionPresenc default void pushFile(String remotePath, byte[] base64Data) { final String extName = "mobile: pushFile"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "remotePath", remotePath, "payload", new String(base64Data, StandardCharsets.UTF_8) )); diff --git a/src/main/java/io/appium/java_client/ScreenshotState.java b/src/main/java/io/appium/java_client/ScreenshotState.java index 1747a1e41..f51add334 100644 --- a/src/main/java/io/appium/java_client/ScreenshotState.java +++ b/src/main/java/io/appium/java_client/ScreenshotState.java @@ -31,7 +31,7 @@ import java.util.function.Function; import java.util.function.Supplier; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; @Accessors(chain = true) @@ -81,7 +81,7 @@ public class ScreenshotState { * @param stateProvider lambda function, which returns a screenshot for further comparison */ public ScreenshotState(ComparesImages comparator, Supplier stateProvider) { - this.comparator = checkNotNull(comparator); + this.comparator = requireNonNull(comparator); this.stateProvider = stateProvider; } @@ -110,7 +110,7 @@ public ScreenshotState remember() { * @return self instance for chaining */ public ScreenshotState remember(BufferedImage customInitialState) { - this.previousScreenshot = checkNotNull(customInitialState); + this.previousScreenshot = requireNonNull(customInitialState); return this; } diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index 98abeaafb..be76d1610 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -28,8 +28,8 @@ import java.util.List; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.builder; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; /** @@ -58,7 +58,7 @@ public class TouchAction> implements PerformsActions private PerformsTouchActions performsTouchActions; public TouchAction(PerformsTouchActions performsTouchActions) { - this.performsTouchActions = checkNotNull(performsTouchActions); + this.performsTouchActions = requireNonNull(performsTouchActions); parameterBuilder = builder(); } @@ -204,9 +204,9 @@ public T perform() { * @return A map of parameters for this touch action to pass as part of mjsonwp. */ protected Map> getParameters() { - List actionList = parameterBuilder.build(); - return ImmutableMap.of("actions", actionList.stream() - .map(ActionParameter::getParameterMap).collect(toList())); + return Map.of("actions", + parameterBuilder.build().stream().map(ActionParameter::getParameterMap).collect(toList()) + ); } /** @@ -233,17 +233,18 @@ public ActionParameter(String actionName) { } public ActionParameter(String actionName, ActionOptions opts) { - checkNotNull(opts); + requireNonNull(opts); this.actionName = actionName; optionsBuilder = ImmutableMap.builder(); //noinspection unchecked optionsBuilder.putAll(opts.build()); } - public ImmutableMap getParameterMap() { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("action", actionName).put("options", optionsBuilder.build()); - return builder.build(); + public Map getParameterMap() { + return Map.of( + "action", actionName, + "options", optionsBuilder.build() + ); } } } diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index a3b31f716..50a7256a5 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -16,11 +16,9 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.MobileCommand; import org.openqa.selenium.remote.RemoteWebElement; -import java.util.AbstractMap; import java.util.Map; /** @@ -36,7 +34,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> currentActivityCommand() { - return new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()); + return Map.entry(CURRENT_ACTIVITY, Map.of()); } /** @@ -46,7 +44,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> currentPackageCommand() { - return new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()); + return Map.entry(GET_CURRENT_PACKAGE, Map.of()); } /** @@ -57,7 +55,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * */ public static Map.Entry> getSupportedPerformanceDataTypesCommand() { - return new AbstractMap.SimpleEntry<>(GET_SUPPORTED_PERFORMANCE_DATA_TYPES, ImmutableMap.of()); + return Map.entry(GET_SUPPORTED_PERFORMANCE_DATA_TYPES, Map.of()); } /** @@ -90,10 +88,11 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ public static Map.Entry> getPerformanceDataCommand( String packageName, String dataType, int dataReadTimeout) { - String[] parameters = new String[] {"packageName", "dataType", "dataReadTimeout"}; - Object[] values = new Object[] {packageName, dataType, dataReadTimeout}; - return new AbstractMap.SimpleEntry<>( - GET_PERFORMANCE_DATA, prepareArguments(parameters, values)); + return Map.entry(GET_PERFORMANCE_DATA, Map.of( + "packageName", packageName, + "dataType", dataType, + "dataReadTimeout", dataReadTimeout + )); } /** @@ -103,7 +102,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> getDisplayDensityCommand() { - return new AbstractMap.SimpleEntry<>(GET_DISPLAY_DENSITY, ImmutableMap.of()); + return Map.entry(GET_DISPLAY_DENSITY, Map.of()); } /** @@ -113,7 +112,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> getNetworkConnectionCommand() { - return new AbstractMap.SimpleEntry<>(GET_NETWORK_CONNECTION, ImmutableMap.of()); + return Map.entry(GET_NETWORK_CONNECTION, Map.of()); } /** @@ -124,7 +123,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> getSystemBarsCommand() { - return new AbstractMap.SimpleEntry<>(GET_SYSTEM_BARS, ImmutableMap.of()); + return Map.entry(GET_SYSTEM_BARS, Map.of()); } /** @@ -134,7 +133,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> isLockedCommand() { - return new AbstractMap.SimpleEntry<>(IS_LOCKED, ImmutableMap.of()); + return Map.entry(IS_LOCKED, Map.of()); } /** @@ -145,8 +144,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> fingerPrintCommand(int fingerPrintId) { - return new AbstractMap.SimpleEntry<>(FINGER_PRINT, - prepareArguments("fingerprintId", fingerPrintId)); + return Map.entry(FINGER_PRINT, Map.of("fingerprintId", fingerPrintId)); } /** @@ -156,7 +154,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> openNotificationsCommand() { - return new AbstractMap.SimpleEntry<>(OPEN_NOTIFICATIONS, ImmutableMap.of()); + return Map.entry(OPEN_NOTIFICATIONS, Map.of()); } /** @@ -167,10 +165,10 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> setConnectionCommand(long bitMask) { - String[] parameters = new String[] {"name", "parameters"}; - Object[] values = new Object[] {"network_connection", ImmutableMap.of("type", bitMask)}; - return new AbstractMap.SimpleEntry<>( - SET_NETWORK_CONNECTION, prepareArguments(parameters, values)); + return Map.entry(SET_NETWORK_CONNECTION, Map.of( + "name", "network_connection", + "parameters", Map.of("type", bitMask) + )); } /** @@ -180,7 +178,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> toggleLocationServicesCommand() { - return new AbstractMap.SimpleEntry<>(TOGGLE_LOCATION_SERVICES, ImmutableMap.of()); + return Map.entry(TOGGLE_LOCATION_SERVICES, Map.of()); } /** @@ -190,7 +188,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> unlockCommand() { - return new AbstractMap.SimpleEntry<>(UNLOCK, ImmutableMap.of()); + return Map.entry(UNLOCK, Map.of()); } @@ -205,12 +203,10 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> replaceElementValueCommand( RemoteWebElement remoteWebElement, String value) { - String[] parameters = new String[] {"id", "value"}; - Object[] values = - new Object[] {remoteWebElement.getId(), value}; - - return new AbstractMap.SimpleEntry<>( - REPLACE_VALUE, prepareArguments(parameters, values)); + return Map.entry(REPLACE_VALUE, Map.of( + "id", remoteWebElement.getId(), + "value", value + )); } /** @@ -225,12 +221,10 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> sendSMSCommand( String phoneNumber, String message) { - ImmutableMap parameters = ImmutableMap - .builder().put("phoneNumber", phoneNumber) - .put("message", message) - .build(); - - return new AbstractMap.SimpleEntry<>(SEND_SMS, parameters); + return Map.entry(SEND_SMS, Map.of( + "phoneNumber", phoneNumber, + "message", message + )); } /** @@ -245,9 +239,10 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> gsmCallCommand( String phoneNumber, GsmCallActions gsmCallActions) { - String[] parameters = new String[] {"phoneNumber", "action"}; - Object[] values = new Object[]{phoneNumber, gsmCallActions.name().toLowerCase()}; - return new AbstractMap.SimpleEntry<>(GSM_CALL, prepareArguments(parameters, values)); + return Map.entry(GSM_CALL, Map.of( + "phoneNumber", phoneNumber, + "action", gsmCallActions.name().toLowerCase() + )); } /** @@ -261,11 +256,11 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> gsmSignalStrengthCommand( GsmSignalStrength gsmSignalStrength) { - return new AbstractMap.SimpleEntry<>(GSM_SIGNAL, - prepareArguments( + return Map.entry(GSM_SIGNAL, + Map.of( // https://github.com/appium/appium/issues/12234 - new String[] {"signalStrengh", "signalStrength" }, - new Object[] {gsmSignalStrength.ordinal(), gsmSignalStrength.ordinal()} + "signalStrengh", gsmSignalStrength.ordinal(), + "signalStrength", gsmSignalStrength.ordinal() )); } @@ -280,8 +275,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> gsmVoiceCommand( GsmVoiceState gsmVoiceState) { - return new AbstractMap.SimpleEntry<>(GSM_VOICE, - prepareArguments("state", gsmVoiceState.name().toLowerCase())); + return Map.entry(GSM_VOICE, Map.of("state", gsmVoiceState.name().toLowerCase())); } /** @@ -295,8 +289,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> networkSpeedCommand( NetworkSpeed networkSpeed) { - return new AbstractMap.SimpleEntry<>(NETWORK_SPEED, - prepareArguments("netspeed", networkSpeed.name().toLowerCase())); + return Map.entry(NETWORK_SPEED, Map.of("netspeed", networkSpeed.name().toLowerCase())); } /** @@ -310,8 +303,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> powerCapacityCommand( int percent) { - return new AbstractMap.SimpleEntry<>(POWER_CAPACITY, - prepareArguments("percent", percent)); + return Map.entry(POWER_CAPACITY, Map.of("percent", percent)); } /** @@ -325,8 +317,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { @Deprecated public static Map.Entry> powerACCommand( PowerACState powerACState) { - return new AbstractMap.SimpleEntry<>(POWER_AC_STATE, - prepareArguments("state", powerACState.name().toLowerCase())); + return Map.entry(POWER_AC_STATE, Map.of("state", powerACState.name().toLowerCase())); } /** @@ -336,7 +327,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> toggleWifiCommand() { - return new AbstractMap.SimpleEntry<>(TOGGLE_WIFI, ImmutableMap.of()); + return Map.entry(TOGGLE_WIFI, Map.of()); } /** @@ -346,7 +337,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> toggleAirplaneCommand() { - return new AbstractMap.SimpleEntry<>(TOGGLE_AIRPLANE_MODE, ImmutableMap.of()); + return Map.entry(TOGGLE_AIRPLANE_MODE, Map.of()); } /** @@ -356,6 +347,6 @@ public class AndroidMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> toggleDataCommand() { - return new AbstractMap.SimpleEntry<>(TOGGLE_DATA, ImmutableMap.of()); + return Map.entry(TOGGLE_DATA, Map.of()); } } 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 2ef6cdc29..14c575208 100644 --- a/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java @@ -107,9 +107,9 @@ public AndroidStartScreenRecordingOptions withTimeLimit(Duration timeLimit) { public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); builder.putAll(super.build()); - ofNullable(bitRate).map(x -> builder.put("bitRate", x)); - ofNullable(videoSize).map(x -> builder.put("videoSize", x)); - ofNullable(isBugReportEnabled).map(x -> builder.put("bugReport", x)); + ofNullable(bitRate).ifPresent(x -> builder.put("bitRate", x)); + ofNullable(videoSize).ifPresent(x -> builder.put("videoSize", x)); + ofNullable(isBugReportEnabled).ifPresent(x -> builder.put("bugReport", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java index 178ec4206..611fb30ed 100644 --- a/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java +++ b/src/main/java/io/appium/java_client/android/AuthenticatesByFinger.java @@ -1,11 +1,12 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; +import java.util.Map; + import static io.appium.java_client.android.AndroidMobileCommandHelper.fingerPrintCommand; public interface AuthenticatesByFinger extends ExecutesMethod, CanRememberExtensionPresence { @@ -18,7 +19,7 @@ public interface AuthenticatesByFinger extends ExecutesMethod, CanRememberExtens default void fingerPrint(int fingerPrintId) { final String extName = "mobile: fingerprint"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "fingerprintId", fingerPrintId )); } catch (UnsupportedCommandException e) { diff --git a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java index 2b48a9e29..447377633 100644 --- a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java +++ b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java @@ -1,6 +1,5 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; @@ -8,7 +7,7 @@ import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.remote.RemoteWebElement; -import java.util.AbstractMap; +import java.util.Map; public interface CanReplaceElementValue extends ExecutesMethod, CanRememberExtensionPresence { /** @@ -24,7 +23,7 @@ public interface CanReplaceElementValue extends ExecutesMethod, CanRememberExten default void replaceElementValue(RemoteWebElement element, String value) { final String extName = "mobile: replaceValue"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "elementId", element.getId(), "text", value )); @@ -32,7 +31,7 @@ default void replaceElementValue(RemoteWebElement element, String value) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(MobileCommand.REPLACE_VALUE, ImmutableMap.of( + Map.entry(MobileCommand.REPLACE_VALUE, Map.of( "id", element.getId(), "text", value, "value", value diff --git a/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java b/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java index 40fc24a83..49a657898 100644 --- a/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java +++ b/src/main/java/io/appium/java_client/android/HasAndroidClipboard.java @@ -21,12 +21,11 @@ import io.appium.java_client.clipboard.HasClipboard; import java.nio.charset.StandardCharsets; -import java.util.AbstractMap; import java.util.Base64; +import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; -import static io.appium.java_client.MobileCommand.prepareArguments; +import static java.util.Objects.requireNonNull; public interface HasAndroidClipboard extends HasClipboard { /** @@ -37,11 +36,13 @@ public interface HasAndroidClipboard extends HasClipboard { * @param base64Content base64-encoded content to be set. */ default void setClipboard(String label, ClipboardContentType contentType, byte[] base64Content) { - String[] parameters = new String[]{"content", "contentType", "label"}; - Object[] values = new Object[]{new String(checkNotNull(base64Content), StandardCharsets.UTF_8), - contentType.name().toLowerCase(), checkNotNull(label)}; - CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(SET_CLIPBOARD, - prepareArguments(parameters, values))); + CommandExecutionHelper.execute(this, Map.entry(SET_CLIPBOARD, + Map.of( + "content", new String(requireNonNull(base64Content), StandardCharsets.UTF_8), + "contentType", contentType.name().toLowerCase(), + "label", requireNonNull(label) + ) + )); } /** diff --git a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java index ee6a3bce8..9a175d14c 100644 --- a/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java +++ b/src/main/java/io/appium/java_client/android/HasSupportedPerformanceDataType.java @@ -1,12 +1,12 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; import java.util.List; +import java.util.Map; import static io.appium.java_client.android.AndroidMobileCommandHelper.getPerformanceDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.getSupportedPerformanceDataTypesCommand; @@ -63,7 +63,7 @@ default List getSupportedPerformanceDataTypes() { default List> getPerformanceData(String packageName, String dataType, int dataReadTimeout) { final String extName = "mobile: getPerformanceData"; try { - return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "packageName", packageName, "dataType", dataType )); diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index d4cb4258a..9f56d7b1a 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -16,7 +16,6 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; @@ -24,7 +23,7 @@ import javax.annotation.Nullable; -import java.util.AbstractMap; +import java.util.Map; import static io.appium.java_client.MobileCommand.CURRENT_ACTIVITY; import static io.appium.java_client.MobileCommand.GET_CURRENT_PACKAGE; @@ -44,7 +43,7 @@ default String currentActivity() { // TODO: Remove the fallback return CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(CURRENT_ACTIVITY, ImmutableMap.of()) + Map.entry(CURRENT_ACTIVITY, Map.of()) ); } } @@ -63,7 +62,7 @@ default String getCurrentPackage() { // TODO: Remove the fallback return CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GET_CURRENT_PACKAGE, ImmutableMap.of()) + Map.entry(GET_CURRENT_PACKAGE, Map.of()) ); } } diff --git a/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java index 14854412e..5c16bb293 100644 --- a/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsGpsStateManagement.java @@ -5,8 +5,8 @@ import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleLocationServicesCommand; +import static java.util.Objects.requireNonNull; public interface SupportsGpsStateManagement extends ExecutesMethod, CanRememberExtensionPresence { @@ -30,7 +30,7 @@ default void toggleLocationServices() { * @return true if GPS service is enabled. */ default boolean isLocationServicesEnabled() { - return checkNotNull( + return requireNonNull( CommandExecutionHelper.executeScript(this, "mobile: isGpsEnabled") ); } diff --git a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java index 8f4dfa246..2992e5847 100644 --- a/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java +++ b/src/main/java/io/appium/java_client/android/SupportsNetworkStateManagement.java @@ -1,6 +1,5 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; @@ -8,10 +7,10 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleAirplaneCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleDataCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.toggleWifiCommand; +import static java.util.Objects.requireNonNull; public interface SupportsNetworkStateManagement extends ExecutesMethod, CanRememberExtensionPresence { @@ -21,10 +20,10 @@ public interface SupportsNetworkStateManagement extends ExecutesMethod, CanRemem default void toggleWifi() { final String extName = "mobile: setConnectivity"; try { - Map result = checkNotNull( + Map result = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, Map.of( "wifi", !((Boolean) result.get("wifi")) )); } catch (UnsupportedCommandException e) { @@ -40,10 +39,10 @@ default void toggleWifi() { default void toggleAirplaneMode() { final String extName = "mobile: setConnectivity"; try { - Map result = checkNotNull( + Map result = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, Map.of( "airplaneMode", !((Boolean) result.get("airplaneMode")) )); } catch (UnsupportedCommandException e) { @@ -59,10 +58,10 @@ default void toggleAirplaneMode() { default void toggleData() { final String extName = "mobile: setConnectivity"; try { - Map result = checkNotNull( + Map result = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), "mobile: getConnectivity") ); - CommandExecutionHelper.executeScript(this, extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(this, extName, Map.of( "data", !((Boolean) result.get("data")) )); } catch (UnsupportedCommandException e) { diff --git a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java index 025f00b05..c7b9accaf 100644 --- a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java +++ b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java @@ -1,12 +1,11 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; -import java.util.AbstractMap; +import java.util.Map; import static io.appium.java_client.MobileCommand.GSM_CALL; import static io.appium.java_client.MobileCommand.GSM_SIGNAL; @@ -27,7 +26,7 @@ public interface SupportsSpecialEmulatorCommands extends ExecutesMethod, CanReme default void sendSMS(String phoneNumber, String message) { final String extName = "mobile: sendSms"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "phoneNumber", phoneNumber, "message", message )); @@ -35,7 +34,7 @@ default void sendSMS(String phoneNumber, String message) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(SEND_SMS, ImmutableMap.of( + Map.entry(SEND_SMS, Map.of( "phoneNumber", phoneNumber, "message", message )) @@ -52,7 +51,7 @@ default void sendSMS(String phoneNumber, String message) { default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { final String extName = "mobile: gsmCall"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "phoneNumber", phoneNumber, "action", gsmCallAction.toString().toLowerCase() )); @@ -60,7 +59,7 @@ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GSM_CALL, ImmutableMap.of( + Map.entry(GSM_CALL, Map.of( "phoneNumber", phoneNumber, "action", gsmCallAction.toString().toLowerCase() )) @@ -76,14 +75,14 @@ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallAction) { default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { final String extName = "mobile: gsmSignal"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "strength", gsmSignalStrength.ordinal() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GSM_SIGNAL, ImmutableMap.of( + Map.entry(GSM_SIGNAL, Map.of( "signalStrengh", gsmSignalStrength.ordinal(), "signalStrength", gsmSignalStrength.ordinal() )) @@ -99,14 +98,14 @@ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { default void setGsmVoice(GsmVoiceState gsmVoiceState) { final String extName = "mobile: gsmVoice"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "state", gsmVoiceState.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(GSM_VOICE, ImmutableMap.of( + Map.entry(GSM_VOICE, Map.of( "state", gsmVoiceState.name().toLowerCase() )) ); @@ -121,14 +120,14 @@ default void setGsmVoice(GsmVoiceState gsmVoiceState) { default void setNetworkSpeed(NetworkSpeed networkSpeed) { final String extName = "mobile: networkSpeed"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "speed", networkSpeed.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(NETWORK_SPEED, ImmutableMap.of( + Map.entry(NETWORK_SPEED, Map.of( "netspeed", networkSpeed.name().toLowerCase() )) ); @@ -143,14 +142,14 @@ default void setNetworkSpeed(NetworkSpeed networkSpeed) { default void setPowerCapacity(int percent) { final String extName = "mobile: powerCapacity"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "percent", percent )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(POWER_CAPACITY, ImmutableMap.of( + Map.entry(POWER_CAPACITY, Map.of( "percent", percent )) ); @@ -165,14 +164,14 @@ default void setPowerCapacity(int percent) { default void setPowerAC(PowerACState powerACState) { final String extName = "mobile: powerAC"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "state", powerACState.toString().toLowerCase() )); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(POWER_AC_STATE, ImmutableMap.of( + Map.entry(POWER_AC_STATE, Map.of( "state", powerACState.name().toLowerCase() )) ); diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java index 67f2e09db..27ce3e4a6 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java @@ -23,7 +23,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class AndroidInstallApplicationOptions extends @@ -64,7 +64,7 @@ public AndroidInstallApplicationOptions withReplaceDisabled() { * @return self instance for chaining. */ public AndroidInstallApplicationOptions withTimeout(Duration timeout) { - checkArgument(!checkNotNull(timeout).isNegative(), "The timeout value cannot be negative"); + checkArgument(!requireNonNull(timeout).isNegative(), "The timeout value cannot be negative"); this.timeout = timeout; return this; } @@ -139,11 +139,11 @@ public AndroidInstallApplicationOptions withGrantPermissionsDisabled() { @Override public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(replace).map(x -> builder.put("replace", x)); - ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); - ofNullable(allowTestPackages).map(x -> builder.put("allowTestPackages", x)); - ofNullable(useSdcard).map(x -> builder.put("useSdcard", x)); - ofNullable(grantPermissions).map(x -> builder.put("grantPermissions", x)); + ofNullable(replace).ifPresent(x -> builder.put("replace", x)); + ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); + ofNullable(allowTestPackages).ifPresent(x -> builder.put("allowTestPackages", x)); + ofNullable(useSdcard).ifPresent(x -> builder.put("useSdcard", x)); + ofNullable(grantPermissions).ifPresent(x -> builder.put("grantPermissions", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java index 2a4899f13..9783d9fda 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java @@ -23,7 +23,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class AndroidRemoveApplicationOptions extends @@ -39,7 +39,7 @@ public class AndroidRemoveApplicationOptions extends * @return self instance for chaining. */ public AndroidRemoveApplicationOptions withTimeout(Duration timeout) { - checkArgument(!checkNotNull(timeout).isNegative(), + checkArgument(!requireNonNull(timeout).isNegative(), "The timeout value cannot be negative"); this.timeout = timeout; return this; @@ -69,8 +69,8 @@ public AndroidRemoveApplicationOptions withKeepDataDisabled() { @Override public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); - ofNullable(keepData).map(x -> builder.put("keepData", x)); + ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); + ofNullable(keepData).ifPresent(x -> builder.put("keepData", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java index 0e11e569d..c9a0a02d8 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java @@ -23,7 +23,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class AndroidTerminateApplicationOptions extends @@ -38,7 +38,7 @@ public class AndroidTerminateApplicationOptions extends * @return self instance for chaining. */ public AndroidTerminateApplicationOptions withTimeout(Duration timeout) { - checkArgument(!checkNotNull(timeout).isNegative(), "The timeout value cannot be negative"); + checkArgument(!requireNonNull(timeout).isNegative(), "The timeout value cannot be negative"); this.timeout = timeout; return this; } @@ -46,7 +46,7 @@ public AndroidTerminateApplicationOptions withTimeout(Duration timeout) { @Override public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); + ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java index 0774c74a2..03d1381f0 100644 --- a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java +++ b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java @@ -16,7 +16,6 @@ package io.appium.java_client.android.connection; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; @@ -24,9 +23,9 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.android.AndroidMobileCommandHelper.getNetworkConnectionCommand; import static io.appium.java_client.android.AndroidMobileCommandHelper.setConnectionCommand; +import static java.util.Objects.requireNonNull; public interface HasNetworkConnection extends ExecutesMethod, CanRememberExtensionPresence { @@ -39,7 +38,7 @@ public interface HasNetworkConnection extends ExecutesMethod, CanRememberExtensi default ConnectionState setConnection(ConnectionState connection) { final String extName = "mobile: setConnectivity"; try { - CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, ImmutableMap.of( + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "wifi", connection.isWiFiEnabled(), "data", connection.isDataEnabled(), "airplaneMode", connection.isAirplaneModeEnabled() @@ -48,7 +47,7 @@ default ConnectionState setConnection(ConnectionState connection) { } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return new ConnectionState( - checkNotNull( + requireNonNull( CommandExecutionHelper.execute( markExtensionAbsence(extName), setConnectionCommand(connection.getBitMask()) @@ -66,7 +65,7 @@ default ConnectionState setConnection(ConnectionState connection) { default ConnectionState getConnection() { final String extName = "mobile: getConnectivity"; try { - Map result = checkNotNull( + Map result = requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName) ); return new ConnectionState( @@ -77,7 +76,7 @@ default ConnectionState getConnection() { } catch (UnsupportedCommandException e) { // TODO: Remove the fallback return new ConnectionState( - checkNotNull( + requireNonNull( CommandExecutionHelper.execute( markExtensionAbsence(extName), getNetworkConnectionCommand() 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 f04a41fe2..7cd53e698 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 @@ -117,9 +117,9 @@ public AndroidGeoLocation withSpeed(double speed) { 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)); + ofNullable(altitude).ifPresent(x -> builder.put("altitude", x)); + ofNullable(satellites).ifPresent(x -> builder.put("satellites", x)); + ofNullable(speed).ifPresent(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 index 3587ad07e..d1ccf8e2c 100644 --- a/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java +++ b/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java @@ -16,12 +16,11 @@ 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; +import java.util.Map; public interface SupportsExtendedGeolocationCommands extends ExecutesMethod { @@ -32,9 +31,8 @@ public interface SupportsExtendedGeolocationCommands extends ExecutesMethod { * @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)); + CommandExecutionHelper.execute(this, Map.entry(DriverCommand.SET_LOCATION, + Map.of("location", location.build()) + )); } } diff --git a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java index b262096c0..641087050 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java +++ b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java @@ -22,7 +22,6 @@ import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; -import java.util.AbstractMap; import java.util.Map; import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; @@ -43,7 +42,7 @@ default void pressKey(KeyEvent keyEvent) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(PRESS_KEY_CODE, keyEvent.build()) + Map.entry(PRESS_KEY_CODE, keyEvent.build()) ); } } @@ -65,7 +64,7 @@ default void longPressKey(KeyEvent keyEvent) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - new AbstractMap.SimpleEntry<>(LONG_PRESS_KEY_CODE, keyEvent.build()) + Map.entry(LONG_PRESS_KEY_CODE, keyEvent.build()) ); } } diff --git a/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java b/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java index 24adc5675..4cbc571da 100644 --- a/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java +++ b/src/main/java/io/appium/java_client/android/options/server/EspressoBuildConfig.java @@ -42,7 +42,7 @@ private EspressoBuildConfig assignToolsVersionsField(String name, Object value) Optional> toolsVersionsOptional = getOptionValue(TOOLS_VERSION); Map toolsVersions = toolsVersionsOptional.orElseGet(HashMap::new); toolsVersions.put(name, value); - if (!toolsVersionsOptional.isPresent()) { + if (toolsVersionsOptional.isEmpty()) { assignOptionValue(TOOLS_VERSION, toolsVersions); } return this; diff --git a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java index 637a35caf..a6ae3327c 100644 --- a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java +++ b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java @@ -20,13 +20,12 @@ import io.appium.java_client.ExecutesMethod; import java.nio.charset.StandardCharsets; -import java.util.AbstractMap; import java.util.Base64; +import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.MobileCommand.GET_CLIPBOARD; import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; -import static io.appium.java_client.MobileCommand.prepareArguments; +import static java.util.Objects.requireNonNull; public interface HasClipboard extends ExecutesMethod { /** @@ -36,11 +35,12 @@ public interface HasClipboard extends ExecutesMethod { * @param base64Content base64-encoded content to be set. */ default void setClipboard(ClipboardContentType contentType, byte[] base64Content) { - String[] parameters = new String[]{"content", "contentType"}; - Object[] values = new Object[]{new String(checkNotNull(base64Content), StandardCharsets.UTF_8), - contentType.name().toLowerCase()}; - CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(SET_CLIPBOARD, - prepareArguments(parameters, values))); + CommandExecutionHelper.execute(this, Map.entry(SET_CLIPBOARD, + Map.of( + "content", new String(requireNonNull(base64Content), StandardCharsets.UTF_8), + "contentType", contentType.name().toLowerCase() + ) + )); } /** @@ -50,8 +50,8 @@ default void setClipboard(ClipboardContentType contentType, byte[] base64Content * @return the actual content of the clipboard as base64-encoded string or an empty string if the clipboard is empty */ default String getClipboard(ClipboardContentType contentType) { - return CommandExecutionHelper.execute(this, new AbstractMap.SimpleEntry<>(GET_CLIPBOARD, - prepareArguments("contentType", contentType.name().toLowerCase()))); + return CommandExecutionHelper.execute(this, Map.entry(GET_CLIPBOARD, + Map.of("contentType", contentType.name().toLowerCase()))); } /** diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java index 15d7ddf36..11c4f650a 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -20,7 +20,7 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; @@ -35,7 +35,7 @@ public class ScriptOptions { * @return self instance for chaining */ public ScriptOptions withScriptType(ScriptType type) { - this.scriptType = checkNotNull(type); + this.scriptType = requireNonNull(type); return this; } @@ -59,8 +59,8 @@ public ScriptOptions withTimeout(long timeoutMs) { */ public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(scriptType).map(x -> builder.put("type", x.name().toLowerCase())); - ofNullable(timeoutMs).map(x -> builder.put("timeout", x)); + ofNullable(scriptType).ifPresent(x -> builder.put("type", x.name().toLowerCase())); + ofNullable(timeoutMs).ifPresent(x -> builder.put("timeout", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java index 2563055cc..b91974009 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java @@ -46,7 +46,7 @@ public T withEnabledVisualization() { */ public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(visualize).map(x -> builder.put("visualize", x)); + ofNullable(visualize).ifPresent(x -> builder.put("visualize", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java index 5e72c6591..42e4cd976 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java @@ -70,9 +70,9 @@ public FeaturesMatchingOptions withGoodMatchesFactor(int factor) { public Map build() { final ImmutableMap.Builder builder = ImmutableMap.builder(); builder.putAll(super.build()); - ofNullable(detectorName).map(x -> builder.put("detectorName", x)); - ofNullable(matchFunc).map(x -> builder.put("matchFunc", x)); - ofNullable(goodMatchesFactor).map(x -> builder.put("goodMatchesFactor", x)); + ofNullable(detectorName).ifPresent(x -> builder.put("detectorName", x)); + ofNullable(matchFunc).ifPresent(x -> builder.put("matchFunc", x)); + ofNullable(goodMatchesFactor).ifPresent(x -> builder.put("goodMatchesFactor", x)); return builder.build(); } } 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 818c9f2da..d75e1d7e1 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java @@ -68,9 +68,9 @@ public OccurrenceMatchingOptions withMatchNeighbourThreshold(int threshold) { 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)); + ofNullable(threshold).ifPresent(x -> builder.put("threshold", x)); + ofNullable(matchNeighbourThreshold).ifPresent(x -> builder.put("matchNeighbourThreshold", x)); + ofNullable(multiple).ifPresent(x -> builder.put("multiple", x)); return builder.build(); } } diff --git a/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java b/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java index f1dd7300e..32f4c9df4 100644 --- a/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java +++ b/src/main/java/io/appium/java_client/ios/HasIOSClipboard.java @@ -29,7 +29,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; public interface HasIOSClipboard extends HasClipboard { /** @@ -40,7 +40,7 @@ public interface HasIOSClipboard extends HasClipboard { */ default void setClipboardImage(BufferedImage img) throws IOException { try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) { - ImageIO.write(checkNotNull(img), "png", os); + ImageIO.write(requireNonNull(img), "png", os); setClipboard(ClipboardContentType.IMAGE, Base64 .getMimeEncoder() .encode(os.toByteArray())); @@ -68,7 +68,7 @@ default BufferedImage getClipboardImage() throws IOException { default void setClipboardUrl(URL url) { setClipboard(ClipboardContentType.URL, Base64 .getMimeEncoder() - .encode(checkNotNull(url).toString().getBytes(StandardCharsets.UTF_8))); + .encode(requireNonNull(url).toString().getBytes(StandardCharsets.UTF_8))); } /** 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 33c048710..995c196a6 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -49,8 +49,7 @@ import org.openqa.selenium.remote.http.HttpClient; import java.net.URL; - -import static io.appium.java_client.MobileCommand.prepareArguments; +import java.util.Map; /** * iOS driver implementation. @@ -275,7 +274,7 @@ class IOSAlert implements Alert { } @Override public void sendKeys(String keysToSend) { - execute(DriverCommand.SET_ALERT_VALUE, prepareArguments("value", keysToSend)); + execute(DriverCommand.SET_ALERT_VALUE, Map.of("value", keysToSend)); } } diff --git a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java index 390079d19..ebdddaedc 100644 --- a/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/ios/IOSMobileCommandHelper.java @@ -16,10 +16,8 @@ package io.appium.java_client.ios; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.MobileCommand; -import java.util.AbstractMap; import java.util.Map; @Deprecated @@ -32,8 +30,8 @@ public class IOSMobileCommandHelper extends MobileCommand { * @deprecated this helper is deprecated and will be removed in future versions. */ @Deprecated - public static Map.Entry> shakeCommand() { - return new AbstractMap.SimpleEntry<>(SHAKE, ImmutableMap.of()); + public static Map.Entry> shakeCommand() { + return Map.entry(SHAKE, Map.of()); } /** @@ -45,8 +43,7 @@ public class IOSMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> touchIdCommand(boolean match) { - return new AbstractMap.SimpleEntry<>( - TOUCH_ID, prepareArguments("match", match)); + return Map.entry(TOUCH_ID, Map.of("match", match)); } /** @@ -59,7 +56,6 @@ public class IOSMobileCommandHelper extends MobileCommand { */ @Deprecated public static Map.Entry> toggleTouchIdEnrollmentCommand(boolean enabled) { - return new AbstractMap.SimpleEntry<>( - TOUCH_ID_ENROLLMENT, prepareArguments("enabled", enabled)); + return Map.entry(TOUCH_ID_ENROLLMENT, Map.of("enabled", enabled)); } } 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 a86dabac7..b0673a5e0 100644 --- a/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java @@ -23,7 +23,7 @@ import java.time.Duration; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class IOSStartScreenRecordingOptions @@ -56,7 +56,7 @@ public IOSStartScreenRecordingOptions withUploadOptions(ScreenRecordingUploadOpt * @return self instance for chaining. */ public IOSStartScreenRecordingOptions withVideoType(String videoType) { - this.videoType = checkNotNull(videoType); + this.videoType = requireNonNull(videoType); return this; } @@ -72,7 +72,7 @@ public enum VideoQuality { * @return self instance for chaining. */ public IOSStartScreenRecordingOptions withVideoQuality(VideoQuality videoQuality) { - this.videoQuality = checkNotNull(videoQuality).name().toLowerCase(); + this.videoQuality = requireNonNull(videoQuality).name().toLowerCase(); return this; } @@ -98,7 +98,7 @@ public IOSStartScreenRecordingOptions withFps(int fps) { * @return self instance for chaining. */ public IOSStartScreenRecordingOptions withVideoScale(String videoScale) { - this.videoScale = checkNotNull(videoScale); + this.videoScale = requireNonNull(videoScale); return this; } diff --git a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java index af574bd2f..5829808bd 100644 --- a/src/main/java/io/appium/java_client/ios/PerformsTouchID.java +++ b/src/main/java/io/appium/java_client/ios/PerformsTouchID.java @@ -16,10 +16,11 @@ package io.appium.java_client.ios; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import java.util.Map; + public interface PerformsTouchID extends ExecutesMethod { /** @@ -29,7 +30,7 @@ public interface PerformsTouchID extends ExecutesMethod { * @param match If true, simulates a successful fingerprint scan. If false, simulates a failed fingerprint scan. */ default void performTouchID(boolean match) { - CommandExecutionHelper.executeScript(this, "mobile: sendBiometricMatch", ImmutableMap.of( + CommandExecutionHelper.executeScript(this, "mobile: sendBiometricMatch", Map.of( "type", "touchId", "match", match )); @@ -44,7 +45,7 @@ default void performTouchID(boolean match) { * Multiple calls of the method with the same argument value have no effect. */ default void toggleTouchIDEnrollment(boolean enabled) { - CommandExecutionHelper.executeScript(this, "mobile: enrollBiometric", ImmutableMap.of( + CommandExecutionHelper.executeScript(this, "mobile: enrollBiometric", Map.of( "isEnabled", enabled )); } 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 63208ef1f..67af93096 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -16,7 +16,6 @@ package io.appium.java_client.pagefactory; -import com.google.common.collect.ImmutableList; import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; @@ -62,7 +61,7 @@ */ public class AppiumFieldDecorator implements FieldDecorator { - private static final List> AVAILABLE_ELEMENT_CLASSES = ImmutableList.of( + private static final List> AVAILABLE_ELEMENT_CLASSES = List.of( WebElement.class, RemoteWebElement.class ); diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java index 30bf58456..4bb1b1f4d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java @@ -25,8 +25,8 @@ import java.util.Map; import java.util.Objects; -import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; +import static java.util.Objects.requireNonNull; public class ContentMappedBy extends By { private final Map map; @@ -43,7 +43,7 @@ public ContentMappedBy(Map map) { * @return self-reference. */ public By useContent(@Nonnull ContentType type) { - checkNotNull(type); + requireNonNull(type); currentContent = type; return this; } diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java index 9a4196f23..2f44463ec 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java @@ -16,7 +16,6 @@ package io.appium.java_client.pagefactory.bys.builder; -import io.appium.java_client.functions.AppiumFunction; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.SearchContext; @@ -25,14 +24,15 @@ import org.openqa.selenium.support.ui.FluentWait; import java.util.Optional; +import java.util.function.Function; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; public class ByChained extends org.openqa.selenium.support.pagefactory.ByChained { private final By[] bys; - private static AppiumFunction getSearchingFunction(By by) { + private static Function getSearchingFunction(By by) { return input -> { try { if (input == null) { @@ -51,8 +51,7 @@ private static AppiumFunction getSearchingFunction(By * @param bys is a set of {@link By} which forms the chain of the searching. */ public ByChained(By[] bys) { - super(bys); - checkNotNull(bys); + super(requireNonNull(bys)); if (bys.length == 0) { throw new IllegalArgumentException("By array should not be empty"); } @@ -61,7 +60,7 @@ public ByChained(By[] bys) { @Override public WebElement findElement(SearchContext context) { - AppiumFunction searchingFunction = null; + Function searchingFunction = null; for (By by: bys) { searchingFunction = Optional.ofNullable(searchingFunction != null @@ -71,7 +70,7 @@ public WebElement findElement(SearchContext context) { FluentWait waiting = new FluentWait<>(context); try { - checkNotNull(searchingFunction); + requireNonNull(searchingFunction); return waiting.until(searchingFunction); } catch (TimeoutException e) { throw new NoSuchElementException("Cannot locate an element using " + this); diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 6ed19b7a4..9543073a8 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Objects.requireNonNull; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; public class Helpers { @@ -108,7 +109,7 @@ public static T createProxy( ) ); Preconditions.checkArgument(!listeners.isEmpty(), "The collection of listeners must not be empty"); - Preconditions.checkArgument(cls != null, "Class must not be null"); + requireNonNull(cls, "Class must not be null"); Preconditions.checkArgument(!cls.isInterface(), "Class must not be an interface"); ElementMatcher.Junction matcher = ElementMatchers.isPublic(); 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 d7dd90683..d3310f478 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -16,7 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.base.Supplier; import com.google.common.base.Throwables; import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.internal.ReflectionHelpers; @@ -47,8 +46,8 @@ import java.util.Map; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Throwables.throwIfUnchecked; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; @@ -85,14 +84,14 @@ public AppiumCommandExecutor( public AppiumCommandExecutor(Map additionalCommands, DriverService service, HttpClient.Factory httpClientFactory) { - this(additionalCommands, checkNotNull(service), httpClientFactory, - AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(service).getUrl())); + this(additionalCommands, requireNonNull(service), httpClientFactory, + AppiumClientConfig.defaultConfig().baseUrl(requireNonNull(service).getUrl())); } public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer, HttpClient.Factory httpClientFactory) { this(additionalCommands, null, httpClientFactory, - AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(addressOfRemoteServer))); + AppiumClientConfig.defaultConfig().baseUrl(requireNonNull(addressOfRemoteServer))); } public AppiumCommandExecutor(Map additionalCommands, AppiumClientConfig appiumClientConfig) { @@ -101,13 +100,13 @@ public AppiumCommandExecutor(Map additionalCommands, Appium public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer) { this(additionalCommands, null, HttpClient.Factory.createDefault(), - AppiumClientConfig.defaultConfig().baseUrl(checkNotNull(addressOfRemoteServer))); + AppiumClientConfig.defaultConfig().baseUrl(requireNonNull(addressOfRemoteServer))); } public AppiumCommandExecutor(Map additionalCommands, URL addressOfRemoteServer, AppiumClientConfig appiumClientConfig) { this(additionalCommands, null, HttpClient.Factory.createDefault(), - appiumClientConfig.baseUrl(checkNotNull(addressOfRemoteServer))); + appiumClientConfig.baseUrl(requireNonNull(addressOfRemoteServer))); } public AppiumCommandExecutor(Map additionalCommands, DriverService service) { @@ -246,8 +245,7 @@ public Response execute(Command command) throws WebDriverException { } return new WebDriverException("The appium server has accidentally died!", rootCause); - }).orElseGet((Supplier) () -> - new WebDriverException(rootCause.getMessage(), rootCause)); + }).orElseGet(() -> new WebDriverException(rootCause.getMessage(), rootCause)); } throwIfUnchecked(t); throw new WebDriverException(t); diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java index a57170d6c..5f7655c2d 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -17,13 +17,13 @@ package io.appium.java_client.remote; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import io.appium.java_client.remote.options.BaseOptions; import org.openqa.selenium.Capabilities; import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.CommandPayload; import java.util.Map; +import java.util.Set; import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; @@ -50,8 +50,8 @@ private static Map makeW3CSafe(Capabilities possiblyInvalidCapab * @param capabilities User-provided capabilities. */ public AppiumNewSessionCommandPayload(Capabilities capabilities) { - super(NEW_SESSION, ImmutableMap.of( - "capabilities", ImmutableSet.of(makeW3CSafe(capabilities)), + super(NEW_SESSION, Map.of( + "capabilities", Set.of(makeW3CSafe(capabilities)), "desiredCapabilities", capabilities )); } diff --git a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java index 7f5f79956..b5c7e4b3b 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java +++ b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java @@ -16,7 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.NoSuchContextException; import org.openqa.selenium.ContextAware; @@ -28,9 +27,10 @@ import javax.annotation.Nullable; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; public interface SupportsContextSwitching extends WebDriver, ContextAware, ExecutesMethod { /** @@ -40,9 +40,9 @@ public interface SupportsContextSwitching extends WebDriver, ContextAware, Execu * @return self instance for chaining. */ default WebDriver context(String name) { - checkNotNull(name, "Must supply a context name"); + requireNonNull(name, "Must supply a context name"); try { - execute(DriverCommand.SWITCH_TO_CONTEXT, ImmutableMap.of("name", name)); + execute(DriverCommand.SWITCH_TO_CONTEXT, Map.of("name", name)); return this; } catch (WebDriverException e) { throw new NoSuchContextException(e.getMessage(), e); @@ -55,7 +55,7 @@ default WebDriver context(String name) { * @return List list of context names. */ default Set getContextHandles() { - Response response = execute(DriverCommand.GET_CONTEXT_HANDLES, ImmutableMap.of()); + Response response = execute(DriverCommand.GET_CONTEXT_HANDLES, Map.of()); Object value = response.getValue(); try { //noinspection unchecked diff --git a/src/main/java/io/appium/java_client/remote/SupportsRotation.java b/src/main/java/io/appium/java_client/remote/SupportsRotation.java index 6e1af3a58..4e8078707 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsRotation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsRotation.java @@ -16,7 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.DeviceRotation; import org.openqa.selenium.ScreenOrientation; @@ -44,7 +43,7 @@ default void rotate(DeviceRotation rotation) { default void rotate(ScreenOrientation orientation) { execute(DriverCommand.SET_SCREEN_ORIENTATION, - ImmutableMap.of("orientation", orientation.value().toUpperCase())); + Map.of("orientation", orientation.value().toUpperCase())); } /** 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 308a9b05a..e4e3797ad 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java @@ -20,7 +20,7 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public abstract class BaseScreenRecordingOptions> { @@ -34,7 +34,7 @@ public abstract class BaseScreenRecordingOptions> @@ -36,7 +36,7 @@ public abstract class BaseStartScreenRecordingOptions formFields) { - this.formFields = checkNotNull(formFields); + this.formFields = requireNonNull(formFields); return this; } @@ -112,7 +112,7 @@ public ScreenRecordingUploadOptions withFormFields(Map formField * @return self instance for chaining. */ public ScreenRecordingUploadOptions withHeaders(Map headers) { - this.headers = checkNotNull(headers); + this.headers = requireNonNull(headers); return this; } 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 fa2ed6584..0bd223034 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 @@ -45,10 +45,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS; +import static java.util.Objects.requireNonNull; import static org.slf4j.event.Level.DEBUG; import static org.slf4j.event.Level.INFO; @@ -291,7 +291,7 @@ public String getStdOut() { * that is ready to accept server output */ public void addOutPutStream(OutputStream outputStream) { - checkNotNull(outputStream, "outputStream parameter is NULL!"); + requireNonNull(outputStream, "outputStream parameter is NULL!"); stream.add(outputStream); } @@ -302,7 +302,7 @@ public void addOutPutStream(OutputStream outputStream) { * that are ready to accept server output */ public void addOutPutStreams(List outputStreams) { - checkNotNull(outputStreams, "outputStreams parameter is NULL!"); + requireNonNull(outputStreams, "outputStreams parameter is NULL!"); for (OutputStream outputStream : outputStreams) { addOutPutStream(outputStream); } @@ -314,7 +314,7 @@ public void addOutPutStreams(List outputStreams) { * @return the outputStream has been removed if it is present */ public Optional removeOutPutStream(OutputStream outputStream) { - checkNotNull(outputStream, "outputStream parameter is NULL!"); + requireNonNull(outputStream, "outputStream parameter is NULL!"); return stream.remove(outputStream); } @@ -399,7 +399,7 @@ public void enableDefaultSlf4jLoggingOfOutputData() { * available. */ public void addSlf4jLogMessageConsumer(BiConsumer slf4jLogMessageConsumer) { - checkNotNull(slf4jLogMessageConsumer, "slf4jLogMessageConsumer parameter is NULL!"); + requireNonNull(slf4jLogMessageConsumer, "slf4jLogMessageConsumer parameter is NULL!"); addLogMessageConsumer(logMessage -> { slf4jLogMessageConsumer.accept(logMessage, parseSlf4jContextFromLogMessage(logMessage)); }); @@ -433,7 +433,7 @@ static Slf4jLogMessageContext parseSlf4jContextFromLogMessage(String logMessage) * @param consumer Consumer block to be executed when a log message is available. */ public void addLogMessageConsumer(Consumer consumer) { - checkNotNull(consumer, "consumer parameter is NULL!"); + requireNonNull(consumer, "consumer parameter is NULL!"); addOutPutStream(new OutputStream() { private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 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 a8879cc36..a8690c78b 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 @@ -49,8 +49,8 @@ import java.util.function.Function; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; +import static java.util.Objects.requireNonNull; import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; public final class AppiumServiceBuilder @@ -229,7 +229,7 @@ public AppiumServiceBuilder withArgument(ServerArgument argument, String value) } private static String sanitizeBasePath(String basePath) { - basePath = checkNotNull(basePath).trim(); + basePath = requireNonNull(basePath).trim(); checkArgument( !basePath.isEmpty(), "Given base path is not valid - blank or empty values are not allowed for base path" @@ -357,7 +357,7 @@ private String capabilitiesToCmdlineArg() { } @Override - protected ImmutableList createArgs() { + protected List createArgs() { List argList = new ArrayList<>(); loadPathToMainScript(); argList.add(appiumJS.getAbsolutePath()); diff --git a/src/main/java/io/appium/java_client/touch/LongPressOptions.java b/src/main/java/io/appium/java_client/touch/LongPressOptions.java index ac8600b49..9f9104b71 100644 --- a/src/main/java/io/appium/java_client/touch/LongPressOptions.java +++ b/src/main/java/io/appium/java_client/touch/LongPressOptions.java @@ -22,7 +22,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class LongPressOptions extends AbstractOptionCombinedWithPosition { @@ -45,7 +45,7 @@ public static LongPressOptions longPressOptions() { * @return this instance for chaining. */ public LongPressOptions withDuration(Duration duration) { - checkNotNull(duration); + requireNonNull(duration); checkArgument(duration.toMillis() >= 0, "Duration value should be greater or equal to zero"); this.duration = duration; diff --git a/src/main/java/io/appium/java_client/touch/WaitOptions.java b/src/main/java/io/appium/java_client/touch/WaitOptions.java index 81aedd674..29d869b07 100644 --- a/src/main/java/io/appium/java_client/touch/WaitOptions.java +++ b/src/main/java/io/appium/java_client/touch/WaitOptions.java @@ -20,8 +20,8 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static java.time.Duration.ofMillis; +import static java.util.Objects.requireNonNull; public class WaitOptions extends ActionOptions { protected Duration duration = ofMillis(0); @@ -44,7 +44,7 @@ public static WaitOptions waitOptions(Duration duration) { * @return this instance for chaining. */ public WaitOptions withDuration(Duration duration) { - checkNotNull(duration); + requireNonNull(duration); checkArgument(duration.toMillis() >= 0, "Duration value should be greater or equal to zero"); this.duration = duration; 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 05b4684a3..16cf65f70 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 @@ -8,7 +8,7 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; public class ElementOption extends PointOption { @@ -82,7 +82,7 @@ public ElementOption withCoordinates(int xOffset, int yOffset) { * @return self-reference */ public ElementOption withElement(WebElement element) { - checkNotNull(element); + requireNonNull(element); checkArgument(true, "Element should be an instance of the class which " + "extends org.openqa.selenium.remote.RemoteWebElement", element instanceof RemoteWebElement); diff --git a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java index 21389d54a..5db7921a7 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java @@ -1,6 +1,5 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import io.appium.java_client.functions.ActionSupplier; import io.appium.java_client.touch.offset.ElementOption; @@ -10,6 +9,7 @@ import org.openqa.selenium.WebElement; import java.util.List; +import java.util.Map; import static io.appium.java_client.TestUtils.getCenter; import static io.appium.java_client.touch.WaitOptions.waitOptions; @@ -59,8 +59,8 @@ public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { } @Test public void verticalSwipingWithSupplier() throws Exception { - driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); driver.findElement(AppiumBy.accessibilityId("Views")).click(); Point originalLocation = driver.findElement(AppiumBy.accessibilityId("Gallery")).getLocation(); diff --git a/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java b/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java index 3db8ade05..83d8eabdf 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java @@ -16,8 +16,6 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.Test; import org.openqa.selenium.json.Json; @@ -25,6 +23,8 @@ import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -37,9 +37,9 @@ public void testFindByDataMatcher() { .elementToBeClickable(AppiumBy.accessibilityId("Graphics"))); driver.findElement(AppiumBy.accessibilityId("Graphics")).click(); - String selector = new Json().toJson(ImmutableMap.of( + String selector = new Json().toJson(Map.of( "name", "hasEntry", - "args", ImmutableList.of("title", "Sweep") + "args", List.of("title", "Sweep") )); assertNotNull(wait.until(ExpectedConditions diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index 095866e89..e1ff99d2e 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -16,7 +16,6 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; @@ -29,6 +28,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; +import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -149,8 +149,8 @@ public void isAppNotInstalledTest() { @Test public void closeAppTest() { - driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); assertEquals(".ApiDemos", driver.currentActivity()); } diff --git a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java index 84cb15753..c24ed20fa 100644 --- a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java @@ -1,6 +1,5 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; @@ -11,6 +10,7 @@ import org.openqa.selenium.WebElement; import java.util.List; +import java.util.Map; import static io.appium.java_client.TestUtils.getCenter; import static io.appium.java_client.touch.LongPressOptions.longPressOptions; @@ -26,8 +26,8 @@ public class AndroidTouchTest extends BaseAndroidTest { @BeforeEach public void setUp() { - driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); } @Test public void dragNDropByElementTest() { diff --git a/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java b/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java index 852723694..80b60ab28 100644 --- a/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java @@ -16,8 +16,6 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.AppiumBy; import org.junit.jupiter.api.Test; import org.openqa.selenium.json.Json; @@ -25,6 +23,8 @@ import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -32,9 +32,9 @@ public class AndroidViewMatcherTest extends BaseEspressoTest { @Test public void testFindByViewMatcher() { - String selector = new Json().toJson(ImmutableMap.of( + String selector = new Json().toJson(Map.of( "name", "withText", - "args", ImmutableList.of("Animation"), + "args", List.of("Animation"), "class", "androidx.test.espresso.matcher.ViewMatchers" )); final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); 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 21142e631..e14343d75 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -16,13 +16,14 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import java.util.Map; + import static io.appium.java_client.TestResources.apiDemosApk; @SuppressWarnings("checkstyle:HideUtilityClassConstructor") @@ -65,7 +66,7 @@ public class BaseAndroidTest { public static void startActivity(String name) { driver.executeScript( "mobile: startActivity", - ImmutableMap.of( + Map.of( "component", String.format("%s/%s", APP_ID, name) ) ); diff --git a/src/test/java/io/appium/java_client/android/ClipboardTest.java b/src/test/java/io/appium/java_client/android/ClipboardTest.java index 7b576bcc2..8de3bda5c 100644 --- a/src/test/java/io/appium/java_client/android/ClipboardTest.java +++ b/src/test/java/io/appium/java_client/android/ClipboardTest.java @@ -16,17 +16,18 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.assertEquals; public class ClipboardTest extends BaseAndroidTest { @BeforeEach public void setUp() { - driver.executeScript("mobile: terminateApp", ImmutableMap.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", ImmutableMap.of("appId", APP_ID)); + driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); + driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); } @Test public void verifySetAndGetClipboardText() { diff --git a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java index 1c5848934..08bddc736 100644 --- a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java +++ b/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java @@ -1,12 +1,12 @@ package io.appium.java_client.android; -import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.openqa.selenium.By.xpath; @@ -14,7 +14,7 @@ public class OpenNotificationsTest extends BaseAndroidTest { @Test public void openNotification() { - driver.executeScript("mobile: terminateApp", ImmutableMap.of( + driver.executeScript("mobile: terminateApp", Map.of( "appId", APP_ID )); driver.openNotifications(); diff --git a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java index 7a22ffd93..4ab700ca3 100644 --- a/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java +++ b/src/test/java/io/appium/java_client/drivers/options/OptionsBuildingTest.java @@ -16,8 +16,6 @@ package io.appium.java_client.drivers.options; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.android.options.EspressoOptions; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.android.options.localization.AppLocale; @@ -42,6 +40,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -60,7 +60,7 @@ public void canBuildXcuiTestOptions() throws MalformedURLException { .setWdaBaseUrl("http://localhost:8000/") .setPermissions(new Permissions() .withAppPermissions("com.apple.MobileSafari", - ImmutableMap.of("calendar", "YES"))) + Map.of("calendar", "YES"))) .setSafariSocketChunkSize(10) .setCommandTimeouts(new CommandTimeouts() .withCommandTimeout("yolo", Duration.ofSeconds(1))); @@ -105,7 +105,7 @@ public void canBuildEspressoOptions() { .withLanguage("zh") .withVariant("hans")) .setEspressoBuildConfig(new EspressoBuildConfig() - .withAdditionalAppDependencies(ImmutableList.of( + .withAdditionalAppDependencies(List.of( "com.dep1:1.2.3", "com.dep2:1.2.3" )) @@ -154,7 +154,7 @@ public void canBuildGeckoOptions() { assertEquals(AutomationName.GECKO, options.getAutomationName().orElse(null)); options.setNewCommandTimeout(Duration.ofSeconds(10)) .setVerbosity(Verbosity.TRACE) - .setMozFirefoxOptions(ImmutableMap.of( + .setMozFirefoxOptions(Map.of( "profile", "yolo" )); assertEquals(Duration.ofSeconds(10), options.getNewCommandTimeout().orElse(null)); diff --git a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java index 9b6af0820..c07df5b68 100644 --- a/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java +++ b/src/test/java/io/appium/java_client/events/stubs/EmptyWebDriver.java @@ -16,7 +16,6 @@ package io.appium.java_client.events.stubs; -import com.google.common.collect.ImmutableList; import org.openqa.selenium.Alert; import org.openqa.selenium.By; import org.openqa.selenium.Capabilities; @@ -46,7 +45,7 @@ public EmptyWebDriver() { } private static List createStubList() { - return ImmutableList.of(new StubWebElement(), new StubWebElement()); + return List.of(new StubWebElement(), new StubWebElement()); } public WebDriver context(String name) { diff --git a/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java b/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java index 5a50dc58a..a84708083 100644 --- a/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java +++ b/src/test/java/io/appium/java_client/events/stubs/StubWebElement.java @@ -16,7 +16,6 @@ package io.appium.java_client.events.stubs; -import com.google.common.collect.ImmutableList; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.OutputType; @@ -33,7 +32,7 @@ public StubWebElement() { } private static List createStubSubElementList() { - return new ArrayList<>(ImmutableList.of(new StubWebElement(), new StubWebElement())); + return new ArrayList<>(List.of(new StubWebElement(), new StubWebElement())); } public void click() { 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 6ead922b8..b5d718eb8 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -16,7 +16,6 @@ package io.appium.java_client.ios; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -30,6 +29,7 @@ import org.openqa.selenium.remote.http.HttpMethod; import java.time.Duration; +import java.util.Map; import static io.appium.java_client.TestUtils.waitUntilTrue; import static org.hamcrest.MatcherAssert.assertThat; @@ -71,7 +71,7 @@ public void addCustomCommandWithElementIdTest() { String.format("/session/%s/appium/element/%s/value", driver.getSessionId(), ((RemoteWebElement) intA).getId()), "setNewValue"); final Response setNewValue = driver.execute("setNewValue", - ImmutableMap.of("id", ((RemoteWebElement) intA).getId(), "text", "8")); + Map.of("id", ((RemoteWebElement) intA).getId(), "text", "8")); assertNotNull(setNewValue.getSessionId()); } @@ -82,8 +82,8 @@ public void getDeviceTimeTest() { } @Test public void resetTest() { - driver.executeScript("mobile: terminateApp", ImmutableMap.of("bundleId", BUNDLE_ID)); - driver.executeScript("mobile: activateApp", ImmutableMap.of("bundleId", BUNDLE_ID)); + driver.executeScript("mobile: terminateApp", Map.of("bundleId", BUNDLE_ID)); + driver.executeScript("mobile: activateApp", Map.of("bundleId", BUNDLE_ID)); } @Disabled diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java index 6f7dbb1c9..636d33c4e 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/AbstractStubWebDriver.java @@ -17,7 +17,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; -import static com.google.common.collect.ImmutableList.of; import static io.appium.java_client.remote.AutomationName.ANDROID_UIAUTOMATOR2; import static io.appium.java_client.remote.AutomationName.IOS_XCUI_TEST; import static io.appium.java_client.remote.MobilePlatform.ANDROID; @@ -61,7 +60,7 @@ public String getTitle() { @Override public List findElements(By by) { - return of(new StubWebElement(this, by), new StubWebElement(this, by)); + return List.of(new StubWebElement(this, by), new StubWebElement(this, by)); } @Override diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java index 0e6bc08b7..5977646d7 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java @@ -1,6 +1,5 @@ package io.appium.java_client.pagefactory_tests.widget.tests; -import com.google.common.collect.ImmutableList; import io.appium.java_client.pagefactory.Widget; import org.openqa.selenium.WebElement; @@ -16,7 +15,7 @@ public T getSubWidget() { } public List getSubWidgets() { - return ImmutableList.of(); + return List.of(); } @Override diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java index 43f0864ac..94fd5a8db 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/StubWebElement.java @@ -12,16 +12,15 @@ import java.util.List; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.of; +import static java.util.Objects.requireNonNull; public class StubWebElement implements WebElement, WrapsDriver { private final WebDriver driver; private final By by; public StubWebElement(WebDriver driver, By by) { - this.driver = checkNotNull(driver); - this.by = checkNotNull(by); + this.driver = requireNonNull(driver); + this.by = requireNonNull(by); } @Override @@ -70,8 +69,8 @@ public String getText() { } @Override - public List findElements(By by) { - return of(new StubWebElement(driver, by), new StubWebElement(driver, by)); + public List findElements(By by) { + return List.of(new StubWebElement(driver, by), new StubWebElement(driver, by)); } @Override 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 aae940553..9822e9417 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,6 +1,5 @@ package io.appium.java_client.service.local; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.android.options.UiAutomator2Options; import io.github.bonigarcia.wdm.WebDriverManager; import org.junit.jupiter.api.AfterEach; @@ -14,6 +13,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static io.appium.java_client.TestResources.apiDemosApk; import static io.appium.java_client.TestUtils.getLocalIp4Address; @@ -179,7 +179,7 @@ void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { .amend("unixPath", "/selenium/app.apk") .amend("quotes", "\"'") .setChromeOptions( - ImmutableMap.of("env", ImmutableMap.of("test", "value"), "val2", 0) + Map.of("env", Map.of("test", "value"), "val2", 0) ); service = new AppiumServiceBuilder() From fac489f8cec390fbd3b6df7de28cc5e135a69a63 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 22 Oct 2023 19:29:12 +0200 Subject: [PATCH 161/314] fix: Properly unwrap driver instance if the ContextAware object is deeply nested (#2052) --- .../pagefactory/AppiumFieldDecorator.java | 47 ++++++------ .../utils/WebDriverUnpackUtility.java | 72 +++++++++++-------- 2 files changed, 65 insertions(+), 54 deletions(-) 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 67af93096..280cd02d1 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -19,7 +19,6 @@ import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import org.openqa.selenium.Capabilities; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -46,7 +45,7 @@ import java.util.Map; import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackObjectFromSearchContext; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; import static java.time.Duration.ofSeconds; @@ -82,23 +81,15 @@ public class AppiumFieldDecorator implements FieldDecorator { * @param duration is a desired duration of the waiting for an element presence. */ public AppiumFieldDecorator(SearchContext context, Duration duration) { - WebDriver wd = unpackWebDriverFromSearchContext(context); - this.webDriverReference = wd == null ? null : new WeakReference<>(wd); - if (wd instanceof HasCapabilities) { - Capabilities caps = ((HasCapabilities) wd).getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); - } else { - this.platform = null; - this.automation = null; - } - + this.webDriverReference = unpackObjectFromSearchContext(context, WebDriver.class) + .map(WeakReference::new).orElse(null); + this.platform = readStringCapability(context, CapabilityType.PLATFORM_NAME); + this.automation = readStringCapability(context, AUTOMATION_NAME_OPTION); this.duration = duration; defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( context, duration, new DefaultElementByBuilder(platform, automation) )); - widgetLocatorFactory = new AppiumElementLocatorFactory( context, duration, new WidgetByBuilder(platform, automation) ); @@ -117,28 +108,32 @@ public AppiumFieldDecorator(SearchContext context) { * @param duration is a desired duration of the waiting for an element presence. */ AppiumFieldDecorator(WeakReference contextReference, Duration duration) { - WebDriver wd = unpackWebDriverFromSearchContext(contextReference.get()); - this.webDriverReference = wd == null ? null : new WeakReference<>(wd); - if (wd instanceof HasCapabilities) { - Capabilities caps = ((HasCapabilities) wd).getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); - } else { - this.platform = null; - this.automation = null; - } - + var cr = contextReference.get(); + this.webDriverReference = unpackObjectFromSearchContext(cr, WebDriver.class) + .map(WeakReference::new).orElse(null); + this.platform = readStringCapability(cr, CapabilityType.PLATFORM_NAME); + this.automation = readStringCapability(cr, AUTOMATION_NAME_OPTION); this.duration = duration; defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( contextReference, duration, new DefaultElementByBuilder(platform, automation) )); - widgetLocatorFactory = new AppiumElementLocatorFactory( contextReference, duration, new WidgetByBuilder(platform, automation) ); } + @Nullable + private String readStringCapability(SearchContext searchContext, String capName) { + if (searchContext == null) { + return null; + } + return unpackObjectFromSearchContext(searchContext, HasCapabilities.class) + .map(HasCapabilities::getCapabilities) + .map(caps -> CapabilityHelpers.getCapability(caps, capName, String.class)) + .orElse(null); + } + private DefaultFieldDecorator createFieldDecorator(ElementLocatorFactory factory) { return new DefaultFieldDecorator(factory) { @Override diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 01cc25a7e..190f9c4ae 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -26,9 +26,10 @@ import javax.annotation.Nullable; +import java.util.Optional; + import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; -import static java.util.Optional.ofNullable; public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; @@ -37,35 +38,53 @@ private WebDriverUnpackUtility() { } /** - * This method extract an instance of {@link WebDriver} from the given {@link SearchContext}. + * This method extracts an instance of the given interface from the given {@link SearchContext}. + * It is expected that the {@link SearchContext} itself or the object it wraps implements it. + * * @param searchContext is an instance of {@link SearchContext}. It may be the instance of * {@link WebDriver} or {@link org.openqa.selenium.WebElement} or some other * user's extension/implementation. * Note: if you want to use your own implementation then it should implement * {@link WrapsDriver} or {@link WrapsElement} - * @return the instance of {@link WebDriver}. - * Note: if the given {@link SearchContext} is not - * {@link WebDriver} and it doesn't implement - * {@link WrapsDriver} or {@link WrapsElement} then this method returns null. - * + * @param cls interface whose instance is going to be extracted. + * @return Either an instance of the given interface or Optional.empty(). */ - @Nullable - public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext) { - if (searchContext instanceof WebDriver) { - return (WebDriver) searchContext; + public static Optional unpackObjectFromSearchContext(@Nullable SearchContext searchContext, Class cls) { + if (searchContext == null) { + return Optional.empty(); } + if (cls.isAssignableFrom(searchContext.getClass())) { + return Optional.of(cls.cast(searchContext)); + } if (searchContext instanceof WrapsDriver) { - return unpackWebDriverFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver()); + return unpackObjectFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver(), cls); } - // Search context it is not only WebDriver. WebElement is search context too. // RemoteWebElement implements WrapsDriver if (searchContext instanceof WrapsElement) { - return unpackWebDriverFromSearchContext(((WrapsElement) searchContext).getWrappedElement()); + return unpackObjectFromSearchContext(((WrapsElement) searchContext).getWrappedElement(), cls); } - return null; + return Optional.empty(); + } + + /** + * This method extract an instance of {@link WebDriver} from the given {@link SearchContext}. + * @param searchContext is an instance of {@link SearchContext}. It may be the instance of + * {@link WebDriver} or {@link org.openqa.selenium.WebElement} or some other + * user's extension/implementation. + * Note: if you want to use your own implementation then it should implement + * {@link WrapsDriver} or {@link WrapsElement} + * @return the instance of {@link WebDriver}. + * Note: if the given {@link SearchContext} is not + * {@link WebDriver} and it doesn't implement + * {@link WrapsDriver} or {@link WrapsElement} then this method returns null. + * + */ + @Nullable + public static WebDriver unpackWebDriverFromSearchContext(@Nullable SearchContext searchContext) { + return unpackObjectFromSearchContext(searchContext, WebDriver.class).orElse(null); } /** @@ -83,20 +102,17 @@ public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchCon * {@link SearchContext} instance doesn't implement {@link ContextAware} and {@link WrapsDriver} */ public static ContentType getCurrentContentType(SearchContext context) { - return ofNullable(unpackWebDriverFromSearchContext(context)).map(driver -> { - if (driver instanceof HasBrowserCheck && !((HasBrowserCheck) driver).isBrowser()) { - return NATIVE_MOBILE_SPECIFIC; - } + var browserCheckHolder = unpackObjectFromSearchContext(context, HasBrowserCheck.class); + if (browserCheckHolder.filter(hbc -> !hbc.isBrowser()).isPresent()) { + return NATIVE_MOBILE_SPECIFIC; + } - if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser - ContextAware contextAware = (ContextAware) driver; - var currentContext = contextAware.getContext(); - if (currentContext != null && currentContext.toUpperCase().contains(NATIVE_APP_PATTERN)) { - return NATIVE_MOBILE_SPECIFIC; - } - } + var contextAware = unpackObjectFromSearchContext(context, ContextAware.class); + if (contextAware.map(ContextAware::getContext) + .filter(c -> c.toUpperCase().contains(NATIVE_APP_PATTERN)).isPresent()) { + return NATIVE_MOBILE_SPECIFIC; + } - return HTML_OR_DEFAULT; - }).orElse(HTML_OR_DEFAULT); + return HTML_OR_DEFAULT; } } From 735e9fbddc89cd5ffc383bd6163b9f8f92cc9717 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 20:21:20 +0300 Subject: [PATCH 162/314] build(deps): Bump org.owasp.dependencycheck from 8.4.0 to 8.4.2 (#2053) Bumps org.owasp.dependencycheck from 8.4.0 to 8.4.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 a5c3c2681..6db928d7a 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.4.0' + id 'org.owasp.dependencycheck' version '8.4.2' id 'com.github.johnrengelman.shadow' version '8.1.1' } From e7b2436875619993980d9eee61446c72222f72e4 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Oct 2023 09:18:25 +0300 Subject: [PATCH 163/314] chore: Optimize and clean up Gradle build script (#2056) --- build.gradle | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 6db928d7a..874bbf21b 100644 --- a/build.gradle +++ b/build.gradle @@ -67,12 +67,6 @@ dependencies { testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } -ext { - Sources = fileTree("$buildDir/src/main/java").include('**/*.java') - Tests = fileTree("$buildDir/src/test/java").include('**/*.java') - Docs = file("$buildDir/doc") -} - dependencyCheck { failBuildOnCVSS=22 } @@ -81,7 +75,7 @@ jacoco { toolVersion = '0.8.8' } -tasks.withType(JacocoReport) { +tasks.withType(JacocoReport).configureEach { description = 'Generate Jacoco coverage reports after running tests' sourceSets sourceSets.main reports { @@ -209,7 +203,7 @@ test { finalizedBy jacocoTestReport } -task xcuiTest( type: Test ) { +tasks.register('xcuiTest', Test) { useJUnitPlatform() testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' @@ -224,7 +218,7 @@ task xcuiTest( type: Test ) { } } -task uiAutomationTest( type: Test ) { +tasks.register('uiAutomationTest', Test) { useJUnitPlatform() testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' From 2df9f5229ba58c1fada1533a0f8d438d4ceddad4 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 25 Oct 2023 14:03:26 +0300 Subject: [PATCH 164/314] build: Enable Checkstyle SingleSpaceSeparator check (#2057) --- config/checkstyle/appium-style.xml | 1 + .../java_client/android/AndroidMobileCommandHelper.java | 6 +++--- .../android/SupportsSpecialEmulatorCommands.java | 2 +- .../android/connection/HasNetworkConnection.java | 2 +- .../io/appium/java_client/functions/AppiumFunction.java | 2 +- .../ios/options/other/SupportsCommandTimeoutsOption.java | 2 +- .../io/appium/java_client/serverevents/ServerEvents.java | 2 +- .../io/appium/java_client/android/AndroidSearchingTest.java | 4 ++-- .../appium/java_client/pagefactory_tests/TimeoutTest.java | 2 +- 9 files changed, 12 insertions(+), 11 deletions(-) diff --git a/config/checkstyle/appium-style.xml b/config/checkstyle/appium-style.xml index 630949b66..b7473e937 100755 --- a/config/checkstyle/appium-style.xml +++ b/config/checkstyle/appium-style.xml @@ -63,6 +63,7 @@
+ diff --git a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java index 50a7256a5..8c294c4c0 100644 --- a/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java +++ b/src/main/java/io/appium/java_client/android/AndroidMobileCommandHelper.java @@ -91,7 +91,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { return Map.entry(GET_PERFORMANCE_DATA, Map.of( "packageName", packageName, "dataType", dataType, - "dataReadTimeout", dataReadTimeout + "dataReadTimeout", dataReadTimeout )); } @@ -201,7 +201,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { * @return a key-value pair. The key is the command name. The value is a {@link Map} command arguments. */ @Deprecated - public static Map.Entry> replaceElementValueCommand( + public static Map.Entry> replaceElementValueCommand( RemoteWebElement remoteWebElement, String value) { return Map.entry(REPLACE_VALUE, Map.of( "id", remoteWebElement.getId(), @@ -241,7 +241,7 @@ public class AndroidMobileCommandHelper extends MobileCommand { String phoneNumber, GsmCallActions gsmCallActions) { return Map.entry(GSM_CALL, Map.of( "phoneNumber", phoneNumber, - "action", gsmCallActions.name().toLowerCase() + "action", gsmCallActions.name().toLowerCase() )); } diff --git a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java index c7b9accaf..c60d8eaf9 100644 --- a/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java +++ b/src/main/java/io/appium/java_client/android/SupportsSpecialEmulatorCommands.java @@ -34,7 +34,7 @@ default void sendSMS(String phoneNumber, String message) { // TODO: Remove the fallback CommandExecutionHelper.execute( markExtensionAbsence(extName), - Map.entry(SEND_SMS, Map.of( + Map.entry(SEND_SMS, Map.of( "phoneNumber", phoneNumber, "message", message )) diff --git a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java index 03d1381f0..a00693af3 100644 --- a/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java +++ b/src/main/java/io/appium/java_client/android/connection/HasNetworkConnection.java @@ -71,7 +71,7 @@ default ConnectionState getConnection() { return new ConnectionState( ((boolean) result.get("wifi") ? ConnectionState.WIFI_MASK : 0) | ((boolean) result.get("data") ? ConnectionState.DATA_MASK : 0) - | ((boolean) result.get("airplaneMode") ? ConnectionState.AIRPLANE_MODE_MASK : 0) + | ((boolean) result.get("airplaneMode") ? ConnectionState.AIRPLANE_MODE_MASK : 0) ); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback diff --git a/src/main/java/io/appium/java_client/functions/AppiumFunction.java b/src/main/java/io/appium/java_client/functions/AppiumFunction.java index de9069d37..cd621a803 100644 --- a/src/main/java/io/appium/java_client/functions/AppiumFunction.java +++ b/src/main/java/io/appium/java_client/functions/AppiumFunction.java @@ -30,7 +30,7 @@ * @param The return type */ @FunctionalInterface -public interface AppiumFunction extends Function, java.util.function.Function { +public interface AppiumFunction extends Function, java.util.function.Function { @Override default AppiumFunction compose(java.util.function.Function before) { Objects.requireNonNull(before); diff --git a/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java b/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java index 4461985c1..d19e6272f 100644 --- a/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java +++ b/src/main/java/io/appium/java_client/ios/options/other/SupportsCommandTimeoutsOption.java @@ -62,7 +62,7 @@ default T setCommandTimeouts(Duration timeout) { default Optional> getCommandTimeouts() { return Optional.ofNullable(getCapability(COMMAND_TIMEOUTS_OPTION)) .map(String::valueOf) - .map(v -> v.trim().startsWith("{") + .map(v -> v.trim().startsWith("{") ? Either.left(new CommandTimeouts(v)) : Either.right(toDuration(v)) ); diff --git a/src/main/java/io/appium/java_client/serverevents/ServerEvents.java b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java index 283aa3b66..624dd1707 100644 --- a/src/main/java/io/appium/java_client/serverevents/ServerEvents.java +++ b/src/main/java/io/appium/java_client/serverevents/ServerEvents.java @@ -14,7 +14,7 @@ public class ServerEvents { public final List events; public final String jsonData; - public void save(Path output) throws IOException { + public void save(Path output) throws IOException { Files.write(output, this.jsonData.getBytes()); } } \ No newline at end of file diff --git a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java index 64390962d..fb9275943 100644 --- a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java @@ -33,12 +33,12 @@ public void setup() { startActivity(".ApiDemos"); } - @Test public void findByAccessibilityIdTest() { + @Test public void findByAccessibilityIdTest() { assertNotEquals(driver.findElement(AppiumBy.accessibilityId("Graphics")).getText(), null); assertEquals(driver.findElements(AppiumBy.accessibilityId("Graphics")).size(), 1); } - @Test public void findByAndroidUIAutomatorTest() { + @Test public void findByAndroidUIAutomatorTest() { assertNotEquals(driver .findElement(AppiumBy .androidUIAutomator("new UiSelector().clickable(true)")).getText(), null); diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java index 1ae9157a3..a9d44562d 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java @@ -69,7 +69,7 @@ private static long getPerformanceDiff(long expectedMs, Runnable runnable) { long startMark = currentTimeMillis(); runnable.run(); long endMark = currentTimeMillis(); - return abs(expectedMs - (endMark - startMark)); + return abs(expectedMs - (endMark - startMark)); } private static String assertionMessage(Duration expectedDuration) { From 6ca79e4ceea7b5f80ca7cac0b15fa251bb313fff Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 26 Oct 2023 12:04:54 +0300 Subject: [PATCH 165/314] refactor: Migrate to new Selenium API for process management (#2054) --- .../local/AppiumDriverLocalService.java | 71 +++++-------------- 1 file changed, 17 insertions(+), 54 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 0bd223034..45f611eab 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 @@ -17,10 +17,9 @@ package io.appium.java_client.service.local; import com.google.common.annotations.VisibleForTesting; -import io.appium.java_client.internal.ReflectionHelpers; import lombok.SneakyThrows; import org.openqa.selenium.net.UrlChecker; -import org.openqa.selenium.os.CommandLine; +import org.openqa.selenium.os.ExternalProcess; import org.openqa.selenium.remote.service.DriverService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +48,7 @@ import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS; import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; import static org.slf4j.event.Level.DEBUG; import static org.slf4j.event.Level.INFO; @@ -70,7 +70,7 @@ public final class AppiumDriverLocalService extends DriverService { private final URL url; private String basePath; - private CommandLine process = null; + private ExternalProcess process = null; AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort, Duration startupTimeout, @@ -126,7 +126,7 @@ public URL getUrl() { public boolean isRunning() { lock.lock(); try { - if (process == null || !process.isRunning()) { + if (process == null || !process.isAlive()) { return false; } @@ -172,17 +172,15 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } try { - process = new CommandLine( - this.nodeJSExec.getCanonicalPath(), - nodeJSArgs.toArray(new String[]{}) - ); - process.setEnvironmentVariables(nodeJSEnvironment); - process.copyOutputTo(stream); - process.executeAsync(); + ExternalProcess.Builder processBuilder = ExternalProcess.builder() + .command(this.nodeJSExec.getCanonicalPath(), nodeJSArgs) + .copyOutputTo(stream); + nodeJSEnvironment.forEach(processBuilder::environment); + process = processBuilder.start(); ping(startupTimeout); } catch (Exception e) { - final Optional output = Optional.ofNullable(process) - .map(CommandLine::getStdOut) + final Optional output = ofNullable(process) + .map(ExternalProcess::getOutput) .filter(o -> !isNullOrEmpty(o)); destroyProcess(); List errorLines = new ArrayList<>(); @@ -227,47 +225,16 @@ public void stop() { } } - /** - * 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 == null || !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 { - Object osProcess = ReflectionHelpers.getPrivateFieldValue( - process.getClass(), process, "process", Object.class - ); - Object watchdog = ReflectionHelpers.getPrivateFieldValue( - osProcess.getClass(), osProcess, "executeWatchdog", Object.class - ); - Process nativeProcess = ReflectionHelpers.getPrivateFieldValue( - watchdog.getClass(), watchdog, "process", Process.class - ); - 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 + * This method waits up to `DESTROY_TIMEOUT` seconds for the Appium service * to exit gracefully. */ private void destroyProcess() { - destroyProcess(DESTROY_TIMEOUT); + if (process == null || !process.isAlive()) { + return; + } + process.shutdown(DESTROY_TIMEOUT); } /** @@ -277,11 +244,7 @@ private void destroyProcess() { */ @Nullable public String getStdOut() { - if (process != null) { - return process.getStdOut(); - } - - return null; + return ofNullable(process).map(ExternalProcess::getOutput).orElse(null); } /** From a6ebbeb7957bcb116714e76a5a357d50e02bd51d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 26 Oct 2023 12:06:12 +0300 Subject: [PATCH 166/314] refactor: Deprecate custom functional interfaces (#2055) --- .../io/appium/java_client/functions/ActionSupplier.java | 6 ++++++ .../io/appium/java_client/functions/AppiumFunction.java | 2 ++ .../io/appium/java_client/functions/ExpectedCondition.java | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/main/java/io/appium/java_client/functions/ActionSupplier.java b/src/main/java/io/appium/java_client/functions/ActionSupplier.java index 9554bf6ff..67ea8b888 100644 --- a/src/main/java/io/appium/java_client/functions/ActionSupplier.java +++ b/src/main/java/io/appium/java_client/functions/ActionSupplier.java @@ -20,6 +20,12 @@ import java.util.function.Supplier; +/** + * Represents a supplier of actions. + * + * @deprecated Use {@link Supplier} instead + */ +@Deprecated @FunctionalInterface public interface ActionSupplier> extends Supplier { } diff --git a/src/main/java/io/appium/java_client/functions/AppiumFunction.java b/src/main/java/io/appium/java_client/functions/AppiumFunction.java index cd621a803..e23dcb298 100644 --- a/src/main/java/io/appium/java_client/functions/AppiumFunction.java +++ b/src/main/java/io/appium/java_client/functions/AppiumFunction.java @@ -28,7 +28,9 @@ * * @param The input type * @param The return type + * @deprecated Use {@link java.util.function.Function} instead */ +@Deprecated @FunctionalInterface public interface AppiumFunction extends Function, java.util.function.Function { diff --git a/src/main/java/io/appium/java_client/functions/ExpectedCondition.java b/src/main/java/io/appium/java_client/functions/ExpectedCondition.java index 885952525..926577c53 100644 --- a/src/main/java/io/appium/java_client/functions/ExpectedCondition.java +++ b/src/main/java/io/appium/java_client/functions/ExpectedCondition.java @@ -23,7 +23,9 @@ * with {@link java.util.function.Function}. * * @param The return type + * @deprecated Use {@link org.openqa.selenium.support.ui.ExpectedCondition} instead */ +@Deprecated @FunctionalInterface public interface ExpectedCondition extends org.openqa.selenium.support.ui.ExpectedCondition, AppiumFunction { From 5ab1117d16b78e8a13bb42a2b6b10d0c54b0af55 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 1 Nov 2023 22:35:11 +0100 Subject: [PATCH 167/314] ci: Skip Android emulator caching (#2065) --- .github/workflows/gradle.yml | 48 ++++++++++-------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5d15dfbc2..f5f601dd4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -5,25 +5,26 @@ on: branches: - master paths-ignore: - - 'docs/**' - - '*.md' + - 'docs/**' + - '*.md' pull_request: branches: - master paths-ignore: - - 'docs/**' - - '*.md' + - 'docs/**' + - '*.md' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: - ANDROID_SDK_VERSION: 28 + ANDROID_SDK_VERSION: "28" ANDROID_EMU_NAME: test - XCODE_VERSION: 14.2 + ANDROID_EMU_TARGET: default + XCODE_VERSION: "14.2" IOS_DEVICE_NAME: iPhone 12 - IOS_PLATFORM_VERSION: 16.2 + IOS_PLATFORM_VERSION: "16.2" jobs: build: @@ -59,7 +60,7 @@ jobs: run: ./gradlew clean build - name: Install Node.js - if: ${{ matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' }} + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' uses: actions/setup-node@v3 with: node-version: 'lts/*' @@ -71,50 +72,28 @@ jobs: - name: Install UIA2 driver if: matrix.e2e-tests == 'android' run: appium driver install uiautomator2 - - - name: AVD cache - if: matrix.e2e-tests == 'android' - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ env.ANDROID_SDK_VERSION }} - - - name: Generate AVD snapshot for caching - if: matrix.e2e-tests == 'android' && steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ env.ANDROID_SDK_VERSION }} - avd-name: ${{ env.ANDROID_EMU_NAME }} - force-avd-creation: false - script: echo "Generated AVD snapshot for caching." - - name: Run Android E2E tests if: matrix.e2e-tests == 'android' uses: reactivecircus/android-emulator-runner@v2 with: + script: ./gradlew uiAutomationTest api-level: ${{ env.ANDROID_SDK_VERSION }} avd-name: ${{ env.ANDROID_EMU_NAME }} - force-avd-creation: false - emulator-options: -no-snapshot -delay-adb - script: ./gradlew uiAutomationTest + sdcard-path-or-size: 1500M + disable-spellchecker: true + target: ${{ env.ANDROID_EMU_TARGET }} - name: Select Xcode if: matrix.e2e-tests == 'ios' uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "${{ env.XCODE_VERSION }}" - - name: Install XCUITest driver if: matrix.e2e-tests == 'ios' run: appium driver install xcuitest - - name: Prebuild XCUITest driver if: matrix.e2e-tests == 'ios' run: appium driver run xcuitest build-wda - - name: Prepare iOS simulator if: matrix.e2e-tests == 'ios' run: | @@ -122,7 +101,6 @@ jobs: target_sim_id=$(xcrun simctl list devices available | grep "$IOS_DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) open -Fn "/Applications/Xcode_$XCODE_VERSION.app/Contents/Developer/Applications/Simulator.app" xcrun simctl bootstatus $target_sim_id -b - - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' run: ./gradlew xcuiTest From 9748d77457fca034de0bead993462e802ad97a68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 08:20:56 +0300 Subject: [PATCH 168/314] build(deps): Bump org.seleniumhq.selenium:selenium-bom (#2064) Bumps [org.seleniumhq.selenium:selenium-bom](https://github.com/SeleniumHQ/selenium) from 4.14.1 to 4.15.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Commits](https://github.com/SeleniumHQ/selenium/commits/selenium-4.15.0) --- updated-dependencies: - dependency-name: org.seleniumhq.selenium:selenium-bom 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 874bbf21b..20647239f 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.14.1') + testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.15.0') testImplementation 'org.seleniumhq.selenium:selenium-api' testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' testImplementation 'org.seleniumhq.selenium:selenium-support' From c17f77d467befcfbaccf23ae839cc9e7354f8a91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 23:37:07 +0300 Subject: [PATCH 169/314] build(deps): Bump actions/setup-node from 3 to 4 (#2060) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node 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> --- .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 f5f601dd4..2ee43a743 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -61,7 +61,7 @@ jobs: - name: Install Node.js if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' From 89046d543e4e8016727105c7fc00fadd91f12901 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:06:09 +0200 Subject: [PATCH 170/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2068) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.5.3 to 5.6.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-5.5.3...webdrivermanager-5.6.1) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager 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 20647239f..67cb55f98 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.5.3') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.1') { exclude group: 'org.seleniumhq.selenium' } testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.15.0') From 5298e9c7be7893e1c879245f6e6bb5133f3a5fa5 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 6 Nov 2023 17:50:50 +0100 Subject: [PATCH 171/314] chore: Introduce better constructor argument validation for the AppiumFieldDecorator class (#2070) --- .../pagefactory/AppiumFieldDecorator.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 280cd02d1..05fa41a42 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -30,6 +30,7 @@ import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; import org.openqa.selenium.support.pagefactory.FieldDecorator; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; @@ -44,6 +45,7 @@ import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackObjectFromSearchContext; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; @@ -81,8 +83,7 @@ public class AppiumFieldDecorator implements FieldDecorator { * @param duration is a desired duration of the waiting for an element presence. */ public AppiumFieldDecorator(SearchContext context, Duration duration) { - this.webDriverReference = unpackObjectFromSearchContext(context, WebDriver.class) - .map(WeakReference::new).orElse(null); + this.webDriverReference = requireWebDriverReference(context); this.platform = readStringCapability(context, CapabilityType.PLATFORM_NAME); this.automation = readStringCapability(context, AUTOMATION_NAME_OPTION); this.duration = duration; @@ -109,8 +110,7 @@ public AppiumFieldDecorator(SearchContext context) { */ AppiumFieldDecorator(WeakReference contextReference, Duration duration) { var cr = contextReference.get(); - this.webDriverReference = unpackObjectFromSearchContext(cr, WebDriver.class) - .map(WeakReference::new).orElse(null); + this.webDriverReference = requireWebDriverReference(cr); this.platform = readStringCapability(cr, CapabilityType.PLATFORM_NAME); this.automation = readStringCapability(cr, AUTOMATION_NAME_OPTION); this.duration = duration; @@ -123,6 +123,22 @@ contextReference, duration, new WidgetByBuilder(platform, automation) ); } + @Nonnull + private static WeakReference requireWebDriverReference(SearchContext searchContext) { + var wd = unpackObjectFromSearchContext( + checkNotNull(searchContext, "The provided search context cannot be null"), + WebDriver.class + ); + return wd.map(WeakReference::new) + .orElseThrow(() -> new IllegalArgumentException( + String.format( + "No driver implementing %s interface could be extracted from the %s instance. " + + "Is the provided search context valid?", + WebDriver.class.getName(), searchContext.getClass().getName() + ) + )); + } + @Nullable private String readStringCapability(SearchContext searchContext, String capName) { if (searchContext == null) { From 4914ab86dc826f860488e7e30b5e0e6fc754216a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 7 Nov 2023 10:01:23 +0200 Subject: [PATCH 172/314] build(deps): Bump JaCoCo from `0.8.8` to `0.8.11` (#2071) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67cb55f98..b1b3b8843 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencyCheck { } jacoco { - toolVersion = '0.8.8' + toolVersion = '0.8.11' } tasks.withType(JacocoReport).configureEach { From 482748c912acfbcb51192bd8b91e4198e28e6d06 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 7 Nov 2023 12:27:56 +0100 Subject: [PATCH 173/314] fix: Update hashing and iteration logic of page object items (#2067) --- .github/workflows/gradle.yml | 11 ++-- build.gradle | 50 +++++++++++-------- .../pagefactory/bys/builder/ByChained.java | 12 ++--- .../InterceptorOfAListOfElements.java | 4 +- .../InterceptorOfASingleElement.java | 16 +++++- .../pagefactory/utils/ProxyFactory.java | 5 +- .../AndroidPageObjectTest.java | 25 +++++++++- 7 files changed, 86 insertions(+), 37 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 2ee43a743..282526a5f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -57,7 +57,12 @@ jobs: cache: 'gradle' - name: Build with Gradle - run: ./gradlew clean build + run: | + latest_snapshot=$(curl -sf https://oss.sonatype.org/content/repositories/snapshots/org/seleniumhq/selenium/selenium-api/ | \ + python -c "import sys,re; print(re.findall(r'\d+\.\d+\.\d+-SNAPSHOT', sys.stdin.read())[-1])") + echo ">>> $latest_snapshot" + echo "latest_snapshot=$latest_snapshot" >> "$GITHUB_ENV" + ./gradlew clean build -PisCI -Pselenium.version=$latest_snapshot - name: Install Node.js if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' @@ -76,7 +81,7 @@ jobs: if: matrix.e2e-tests == 'android' uses: reactivecircus/android-emulator-runner@v2 with: - script: ./gradlew uiAutomationTest + script: ./gradlew uiAutomationTest -PisCI -Pselenium.version=$latest_snapshot api-level: ${{ env.ANDROID_SDK_VERSION }} avd-name: ${{ env.ANDROID_EMU_NAME }} sdcard-path-or-size: 1500M @@ -103,4 +108,4 @@ jobs: xcrun simctl bootstatus $target_sim_id -b - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' - run: ./gradlew xcuiTest + run: ./gradlew xcuiTest -PisCI -Pselenium.version=$latest_snapshot diff --git a/build.gradle b/build.gradle index b1b3b8843..603ab21e7 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,15 @@ plugins { repositories { mavenCentral() + + if (project.hasProperty("isCI")) { + maven { + url uri('https://oss.sonatype.org/content/repositories/snapshots/') + mavenContent { + snapshotsOnly() + } + } + } } java { @@ -32,22 +41,28 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' - api ('org.seleniumhq.selenium:selenium-api') { - version { - strictly "[${seleniumVersion}, 5.0)" - prefer "${seleniumVersion}" + if (project.hasProperty("isCI")) { + api "org.seleniumhq.selenium:selenium-api:${seleniumVersion}" + api "org.seleniumhq.selenium:selenium-remote-driver:${seleniumVersion}" + api "org.seleniumhq.selenium:selenium-support:${seleniumVersion}" + } else { + api('org.seleniumhq.selenium:selenium-api') { + version { + strictly "[${seleniumVersion}, 5.0)" + prefer "${seleniumVersion}" + } } - } - api ('org.seleniumhq.selenium:selenium-remote-driver') { - version { - strictly "[${seleniumVersion}, 5.0)" - prefer "${seleniumVersion}" + api('org.seleniumhq.selenium:selenium-remote-driver') { + version { + strictly "[${seleniumVersion}, 5.0)" + prefer "${seleniumVersion}" + } } - } - api ('org.seleniumhq.selenium:selenium-support') { - version { - strictly "[${seleniumVersion}, 5.0)" - prefer "${seleniumVersion}" + api('org.seleniumhq.selenium:selenium-support') { + version { + strictly "[${seleniumVersion}, 5.0)" + prefer "${seleniumVersion}" + } } } implementation 'com.google.code.gson:gson:2.10.1' @@ -59,11 +74,7 @@ dependencies { testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.1') { exclude group: 'org.seleniumhq.selenium' } - testImplementation platform(group: 'org.seleniumhq.selenium', name: 'selenium-bom', version: '4.15.0') - testImplementation 'org.seleniumhq.selenium:selenium-api' - testImplementation 'org.seleniumhq.selenium:selenium-remote-driver' - testImplementation 'org.seleniumhq.selenium:selenium-support' - testImplementation 'org.seleniumhq.selenium:selenium-chrome-driver' + testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } @@ -228,7 +239,6 @@ tasks.register('uiAutomationTest', Test) { includeTestsMatching 'io.appium.java_client.android.OpenNotificationsTest' includeTestsMatching '*.AndroidAppStringsTest' includeTestsMatching '*.pagefactory_tests.widget.tests.android.*' - includeTestsMatching '*.pagefactory_tests.widget.tests.AndroidPageObjectTest' includeTestsMatching 'io.appium.java_client.service.local.StartingAppLocallyAndroidTest' includeTestsMatching 'io.appium.java_client.service.local.ServerBuilderTest' includeTestsMatching 'io.appium.java_client.service.local.ThreadSafetyTest' diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java index 2f44463ec..b92d2eb10 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java @@ -61,17 +61,15 @@ public ByChained(By[] bys) { @Override public WebElement findElement(SearchContext context) { Function searchingFunction = null; - for (By by: bys) { - searchingFunction = Optional.ofNullable(searchingFunction != null - ? searchingFunction.andThen(getSearchingFunction(by)) : null).orElse(getSearchingFunction(by)); + searchingFunction = Optional.ofNullable(searchingFunction) + .map(sf -> sf.andThen(getSearchingFunction(by))) + .orElseGet(() -> getSearchingFunction(by)); } - - FluentWait waiting = new FluentWait<>(context); + requireNonNull(searchingFunction); try { - requireNonNull(searchingFunction); - return waiting.until(searchingFunction); + return new FluentWait<>(context).until(searchingFunction); } catch (TimeoutException e) { throw new NoSuchElementException("Cannot locate an element using " + this); } diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java index 62e1442aa..fc35f9992 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java @@ -39,11 +39,11 @@ protected abstract Object getObject( @Override public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { - if (locator == null || Object.class.equals(method.getDeclaringClass())) { + if (locator == null || Object.class == method.getDeclaringClass()) { return original.call(); } - List realElements = new ArrayList<>(locator.findElements()); + final var realElements = new ArrayList<>(locator.findElements()); return getObject(realElements, method, args); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java index 7eea82233..738f49823 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java @@ -20,11 +20,13 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.WrapsDriver; +import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Method; +import java.util.Objects; import java.util.concurrent.Callable; public abstract class InterceptorOfASingleElement implements MethodCallListener { @@ -42,6 +44,15 @@ public InterceptorOfASingleElement( protected abstract Object getObject(WebElement element, Method method, Object[] args) throws Throwable; + private static boolean areElementsEqual(Object we1, Object we2) { + if (!(we1 instanceof RemoteWebElement) || !(we2 instanceof RemoteWebElement)) { + return false; + } + + return we1 == we2 + || (Objects.equals(((RemoteWebElement) we1).getId(), ((RemoteWebElement) we2).getId())); + } + @Override public Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { if (locator == null) { @@ -52,7 +63,7 @@ public Object call(Object obj, Method method, Object[] args, Callable origina return locator.toString(); } - if (Object.class.equals(method.getDeclaringClass())) { + if (Object.class == method.getDeclaringClass()) { return original.call(); } @@ -62,6 +73,9 @@ public Object call(Object obj, Method method, Object[] args, Callable origina } WebElement realElement = locator.findElement(); + if ("equals".equals(method.getName()) && args.length == 1) { + return areElementsEqual(realElement, args[0]); + } return getObject(realElement, method, args); } } 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 3cf725bd6..9e33276e5 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 @@ -37,9 +37,8 @@ * proxy object here. */ public final class ProxyFactory { - private static final Set NON_PROXYABLE_METHODS = setWith( - setWithout(OBJECT_METHOD_NAMES, "toString"), - "iterator" + private static final Set NON_PROXYABLE_METHODS = setWithout( + OBJECT_METHOD_NAMES, "toString", "equals", "hashCode" ); @SafeVarargs diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java index abcd92c80..68e89ddb6 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java @@ -24,6 +24,7 @@ import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.HowToUseLocators; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; @@ -34,16 +35,19 @@ import org.openqa.selenium.support.PageFactory; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; import static java.time.Duration.ofSeconds; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +@SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) public class AndroidPageObjectTest extends BaseAndroidTest { private boolean populated = false; @@ -149,6 +153,10 @@ public class AndroidPageObjectTest extends BaseAndroidTest { @FindBy(id = "fakeId") private List fakeElements; + @FindBy(className = "android.widget.TextView") + @CacheLookup + private List cachedViews; + @CacheLookup @FindBy(className = "android.widget.TextView") private WebElement cached; @@ -343,8 +351,22 @@ public class AndroidPageObjectTest extends BaseAndroidTest { assertNotEquals(ArrayList.class, fakeElements.getClass()); } - @Test public void checkCached() { + @Test public void checkCachedElements() { assertEquals(((RemoteWebElement) cached).getId(), ((RemoteWebElement) cached).getId()); + assertEquals(cached.hashCode(), cached.hashCode()); + //noinspection SimplifiableAssertion,EqualsWithItself + assertTrue(cached.equals(cached)); + } + + @Test public void checkCachedLists() { + assertEquals(cachedViews.hashCode(), cachedViews.hashCode()); + //noinspection SimplifiableAssertion,EqualsWithItself + assertTrue(cachedViews.equals(cachedViews)); + } + + @Test public void checkListHashing() { + assertFalse(cachedViews.isEmpty()); + assertEquals(cachedViews.size(), new HashSet<>(cachedViews).size()); } @Test @@ -364,6 +386,7 @@ public void checkThatElementSearchingThrowsExpectedExceptionIfChainedLocatorIsIn assertNotEquals(0, androidElementsViewFoundByMixedSearching.size()); } + @Disabled("FIXME") @Test public void checkMixedElementSearching2() { assertNotNull(androidElementViewFoundByMixedSearching2.getAttribute("text")); } From a8737ab7860ffc3ede3c2a73f0fa5cfca9d0c134 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:09:31 +0200 Subject: [PATCH 174/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.10.0 to 5.10.1 (#2069) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 603ab21e7..efe9265c2 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.1') { From 9be1936fbd541ee07d793e68f60889fd8a21c334 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:26:26 +0200 Subject: [PATCH 175/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2074) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.6.1 to 5.6.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.6.1...webdrivermanager-5.6.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 efe9265c2..e8939419a 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.1') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.2') { exclude group: 'org.seleniumhq.selenium' } testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" From b082b379b6d782197a90710e3b5a95ea8b27f631 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Thu, 16 Nov 2023 13:20:12 -0800 Subject: [PATCH 176/314] chore: add toString as AppiumClientConfig (#2076) --- .../java_client/AppiumClientConfig.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index bb77b4ec9..e6ebfd0bb 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -205,4 +205,28 @@ public AppiumClientConfig directConnect(boolean directConnect) { public boolean isDirectConnectEnabled() { return directConnect; } + + @Override + public String toString() { + return "AppiumClientConfig{" + + "baseUri=" + + this.baseUri() + + ", connectionTimeout=" + + this.connectionTimeout() + + ", readTimeout=" + + this.readTimeout() + + ", filters=" + + this.filter() + + ", proxy=" + + this.proxy() + + ", credentials=" + + this.credentials() + + ", sslcontext=" + + this.sslContext() + + ", version=" + + this.version() + + ", directConnect=" + + this.directConnect + + '}'; + } } From 2bd448009ef78fc36df7fc45f95e4a2e8cdae024 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 17 Nov 2023 16:02:57 +0100 Subject: [PATCH 177/314] chore: Perform listeners cleanup periodically (#2077) --- .../proxy/ProxyListenersContainer.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java index 8ba4d734b..4d93ebefb 100644 --- a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java +++ b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java @@ -16,21 +16,23 @@ package io.appium.java_client.proxy; -import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import java.lang.ref.WeakReference; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.Semaphore; -@NoArgsConstructor(access = AccessLevel.PRIVATE) class ProxyListenersContainer { + private static final Duration LISTENERS_CLEANUP_INTERVAL = Duration.ofMinutes(1); + private static ProxyListenersContainer INSTANCE; public static synchronized ProxyListenersContainer getInstance() { @@ -40,6 +42,21 @@ public static synchronized ProxyListenersContainer getInstance() { return INSTANCE; } + private ProxyListenersContainer() { + var task = new TimerTask() { + @Override + public void run() { + getListeners(null); + } + }; + // Listeners are cleaned up lazily, e.g. every time getListeners API + // is called we also remove garbage-collected items. Although, due to an + // unpredictable nature of the garbage collector and no guarantees about the + // frequency of getListeners API calls we schedule the below loop to be executed every + // minute, and make sure there are no extra references to obsolete listeners + new Timer().scheduleAtFixedRate(task, 0, LISTENERS_CLEANUP_INTERVAL.toMillis()); + } + private final Semaphore listenersGuard = new Semaphore(1); private final List, Collection>> listenerPairs = new LinkedList<>(); @@ -55,7 +72,7 @@ private static class Pair { * Assign listeners for the particular proxied instance. * * @param proxyInstance The proxied instance. - * @param listeners Collection of listeners. + * @param listeners Collection of listeners. * @return The same given instance. */ public T setListeners(T proxyInstance, Collection listeners) { From 18ea9f4337ed330bbba2cbd932b44a16833b31a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Nov 2023 19:28:03 +0200 Subject: [PATCH 178/314] build(deps): Bump org.owasp.dependencycheck from 8.4.2 to 8.4.3 (#2078) Bumps org.owasp.dependencycheck from 8.4.2 to 8.4.3. --- 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 e8939419a..ac6aa6d80 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.4.2' + id 'org.owasp.dependencycheck' version '8.4.3' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 5e2a6efde8995f97a07442751c54a26d6ca09d49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:46:21 +0200 Subject: [PATCH 179/314] build(deps): Bump org.owasp.dependencycheck from 8.4.3 to 9.0.1 (#2079) Bumps org.owasp.dependencycheck from 8.4.3 to 9.0.1. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck 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 ac6aa6d80..ccb08f4bc 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '8.4.3' + id 'org.owasp.dependencycheck' version '9.0.1' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 5cd439ae63af9dbadc54e16cece123457bfd5c10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:13:07 +0200 Subject: [PATCH 180/314] build(deps): Bump actions/setup-java from 3 to 4 (#2081) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-java 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> --- .github/workflows/gradle.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 282526a5f..c293b7b40 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -50,7 +50,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 71837dd3c..96fcfce38 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '11' distribution: 'zulu' From fa56ff7941b71f6556043a3709d2aa5ebb191adb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:24:11 +0200 Subject: [PATCH 181/314] build(deps): Bump org.owasp.dependencycheck from 9.0.1 to 9.0.2 (#2080) Bumps org.owasp.dependencycheck from 9.0.1 to 9.0.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 ccb08f4bc..280c67fab 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.1' + id 'org.owasp.dependencycheck' version '9.0.2' id 'com.github.johnrengelman.shadow' version '8.1.1' } From b2ee385dd1f151c76ea15ecdf5eeea167e7fde18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:40:36 +0200 Subject: [PATCH 182/314] build(deps): Bump org.owasp.dependencycheck from 9.0.2 to 9.0.4 (#2082) Bumps org.owasp.dependencycheck from 9.0.2 to 9.0.4. --- 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 280c67fab..c13ac9254 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.2' + id 'org.owasp.dependencycheck' version '9.0.4' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 1d22f0c59d2dd70189cf8075914494c4f3b382fe Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 14 Dec 2023 08:05:50 +0200 Subject: [PATCH 183/314] fix: Use JDK 11 to build Jitpack snapshots (#2083) --- jitpack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jitpack.yml b/jitpack.yml index 8e5135fbb..e5b145a9d 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,4 +1,4 @@ jdk: - - openjdk8 + - openjdk11 install: - ./gradlew clean build publishToMavenLocal -PsigningDisabled=true From 4ab6b5397cc52bc5f078791a91fc0972b3309efa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:58:41 +0200 Subject: [PATCH 184/314] build(deps): Bump org.owasp.dependencycheck from 9.0.4 to 9.0.6 (#2084) Bumps org.owasp.dependencycheck from 9.0.4 to 9.0.6. --- 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 c13ac9254..69120dcbf 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.4' + id 'org.owasp.dependencycheck' version '9.0.6' id 'com.github.johnrengelman.shadow' version '8.1.1' } From f8080a2205029591da3fa4d6779a11d56146c308 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 11:37:25 +0200 Subject: [PATCH 185/314] build(deps): Bump org.owasp.dependencycheck from 9.0.6 to 9.0.7 (#2087) Bumps org.owasp.dependencycheck from 9.0.6 to 9.0.7. --- 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 69120dcbf..9ad264d04 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.6' + id 'org.owasp.dependencycheck' version '9.0.7' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 2a39582d6f1a27859de785c9ef68c27b3212333b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 15:49:02 +0300 Subject: [PATCH 186/314] build(deps): Bump slf4jVersion from 2.0.9 to 2.0.10 (#2091) Bumps `slf4jVersion` from 2.0.9 to 2.0.10. Updates `org.slf4j:slf4j-api` from 2.0.9 to 2.0.10 Updates `org.slf4j:slf4j-simple` from 2.0.9 to 2.0.10 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 9ad264d04..f37e5d5ea 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.9' + slf4jVersion = '2.0.10' } dependencies { From f2537947db3bc4e21499f14b8ed95ffcc6b54136 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 15:49:29 +0300 Subject: [PATCH 187/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2092) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.6.2 to 5.6.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.6.2...webdrivermanager-5.6.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 f37e5d5ea..43c00e5ed 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.2') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.3') { exclude group: 'org.seleniumhq.selenium' } testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" From 3ab5bf92f028432b0daecec5492b2db1238057c4 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sun, 7 Jan 2024 23:21:12 +0300 Subject: [PATCH 188/314] chore: Add non-W3C endpoints removed from Selenium client (#2093) --- .../io/appium/java_client/MobileCommand.java | 18 ++++++++++++++++++ .../remote/SupportsContextSwitching.java | 8 ++++---- .../java_client/remote/SupportsRotation.java | 10 +++++----- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index ee08c289a..15704ec40 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -178,6 +178,15 @@ public class MobileCommand { protected static final String GET_ALLSESSION; protected static final String EXECUTE_GOOGLE_CDP_COMMAND; + public static final String GET_SCREEN_ORIENTATION = "getScreenOrientation"; + public static final String SET_SCREEN_ORIENTATION = "setScreenOrientation"; + public static final String GET_SCREEN_ROTATION = "getScreenRotation"; + public static final String SET_SCREEN_ROTATION = "setScreenRotation"; + + public static final String GET_CONTEXT_HANDLES = "getContextHandles"; + public static final String GET_CURRENT_CONTEXT_HANDLE = "getCurrentContextHandle"; + public static final String SWITCH_TO_CONTEXT = "switchToContext"; + public static final Map commandRepository; static { @@ -347,6 +356,15 @@ public class MobileCommand { 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")); + + commandRepository.put(GET_SCREEN_ORIENTATION, getC("/session/:sessionId/orientation")); + commandRepository.put(SET_SCREEN_ORIENTATION, postC("/session/:sessionId/orientation")); + commandRepository.put(GET_SCREEN_ROTATION, getC("/session/:sessionId/rotation")); + commandRepository.put(SET_SCREEN_ROTATION, postC("/session/:sessionId/rotation")); + + commandRepository.put(GET_CONTEXT_HANDLES, getC("/session/:sessionId/contexts")); + commandRepository.put(GET_CURRENT_CONTEXT_HANDLE, getC("/session/:sessionId/context")); + commandRepository.put(SWITCH_TO_CONTEXT, postC("/session/:sessionId/context")); } /** diff --git a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java index b5c7e4b3b..b126e4fa1 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java +++ b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java @@ -17,11 +17,11 @@ package io.appium.java_client.remote; import io.appium.java_client.ExecutesMethod; +import io.appium.java_client.MobileCommand; import io.appium.java_client.NoSuchContextException; import org.openqa.selenium.ContextAware; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; @@ -42,7 +42,7 @@ public interface SupportsContextSwitching extends WebDriver, ContextAware, Execu default WebDriver context(String name) { requireNonNull(name, "Must supply a context name"); try { - execute(DriverCommand.SWITCH_TO_CONTEXT, Map.of("name", name)); + execute(MobileCommand.SWITCH_TO_CONTEXT, Map.of("name", name)); return this; } catch (WebDriverException e) { throw new NoSuchContextException(e.getMessage(), e); @@ -55,7 +55,7 @@ default WebDriver context(String name) { * @return List list of context names. */ default Set getContextHandles() { - Response response = execute(DriverCommand.GET_CONTEXT_HANDLES, Map.of()); + Response response = execute(MobileCommand.GET_CONTEXT_HANDLES, Map.of()); Object value = response.getValue(); try { //noinspection unchecked @@ -75,7 +75,7 @@ default Set getContextHandles() { @Nullable default String getContext() { String contextName = - String.valueOf(execute(DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue()); + String.valueOf(execute(MobileCommand.GET_CURRENT_CONTEXT_HANDLE).getValue()); return "null".equalsIgnoreCase(contextName) ? null : contextName; } } diff --git a/src/main/java/io/appium/java_client/remote/SupportsRotation.java b/src/main/java/io/appium/java_client/remote/SupportsRotation.java index 4e8078707..74397a3e0 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsRotation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsRotation.java @@ -17,10 +17,10 @@ package io.appium.java_client.remote; import io.appium.java_client.ExecutesMethod; +import io.appium.java_client.MobileCommand; import org.openqa.selenium.DeviceRotation; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.Response; import java.util.Map; @@ -32,17 +32,17 @@ public interface SupportsRotation extends WebDriver, ExecutesMethod { * @return The rotation value. */ default DeviceRotation rotation() { - Response response = execute(DriverCommand.GET_SCREEN_ROTATION); + Response response = execute(MobileCommand.GET_SCREEN_ROTATION); //noinspection unchecked return new DeviceRotation((Map) response.getValue()); } default void rotate(DeviceRotation rotation) { - execute(DriverCommand.SET_SCREEN_ROTATION, rotation.parameters()); + execute(MobileCommand.SET_SCREEN_ROTATION, rotation.parameters()); } default void rotate(ScreenOrientation orientation) { - execute(DriverCommand.SET_SCREEN_ORIENTATION, + execute(MobileCommand.SET_SCREEN_ORIENTATION, Map.of("orientation", orientation.value().toUpperCase())); } @@ -52,7 +52,7 @@ default void rotate(ScreenOrientation orientation) { * @return The orientation value. */ default ScreenOrientation getOrientation() { - Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION); + Response response = execute(MobileCommand.GET_SCREEN_ORIENTATION); String orientation = String.valueOf(response.getValue()); return ScreenOrientation.valueOf(orientation.toUpperCase()); } From 2ec056e442addb268de668bf63689fbcb0b6f478 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sun, 7 Jan 2024 23:21:39 +0300 Subject: [PATCH 189/314] docs: Add recent versions of Selenium client to compatibility matrix (#2095) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c79742f12..a20f242cd 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client ---------------------------|----------------- - `9.0.0` | `4.14.1` + `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` From 3c35a42f7fc7d2d4f48072783b6ae86c885f2fa9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 8 Jan 2024 00:07:17 +0300 Subject: [PATCH 190/314] chore: bump Gradle from `8.4` to `8.5` (#2094) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 43c00e5ed..42711ef5d 100644 --- a/build.gradle +++ b/build.gradle @@ -188,7 +188,7 @@ signing { } wrapper { - gradleVersion = '8.4' + gradleVersion = '8.5' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8838ba97b..e6aba2515 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 43bbd488ffafe43eb354a7df1a031b72aeea2043 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:06:49 +0200 Subject: [PATCH 191/314] build(deps): Bump org.owasp.dependencycheck from 9.0.7 to 9.0.8 (#2096) Bumps org.owasp.dependencycheck from 9.0.7 to 9.0.8. --- 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 42711ef5d..9088c3820 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.7' + id 'org.owasp.dependencycheck' version '9.0.8' id 'com.github.johnrengelman.shadow' version '8.1.1' } From c4b4b27f039ee8b521cb35d1e28e0a26671285f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:01:52 +0200 Subject: [PATCH 192/314] build(deps): Bump slf4jVersion from 2.0.10 to 2.0.11 (#2099) Bumps `slf4jVersion` from 2.0.10 to 2.0.11. Updates `org.slf4j:slf4j-api` from 2.0.10 to 2.0.11 Updates `org.slf4j:slf4j-simple` from 2.0.10 to 2.0.11 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 9088c3820..54db6c5fb 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.10' + slf4jVersion = '2.0.11' } dependencies { From 6ac3d9b8e64c617181fcf8cf24fced5144de3096 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 15 Jan 2024 21:18:05 +0200 Subject: [PATCH 193/314] chore: Add non-W3C Location-management endpoints deprecated in Selenium client (#2098) --- .../io/appium/java_client/AppiumDriver.java | 1 + .../java/io/appium/java_client/Location.java | 58 +++++++++++++++++ .../io/appium/java_client/MobileCommand.java | 6 ++ .../java_client/android/AndroidDriver.java | 9 +++ .../SupportsExtendedGeolocationCommands.java | 4 +- .../io/appium/java_client/ios/IOSDriver.java | 9 +++ .../java_client/remote/SupportsLocation.java | 65 ++++++++++++++++++- 7 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/appium/java_client/Location.java diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index f4721dbf5..6d66639c9 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -73,6 +73,7 @@ public class AppiumDriver extends RemoteWebDriver implements // frequently used command parameters @Getter private final URL remoteAddress; + @Deprecated(forRemoval = true) protected final RemoteLocationContext locationContext; private final ExecuteMethod executeMethod; private final Set absentExtensionNames = new HashSet<>(); diff --git a/src/main/java/io/appium/java_client/Location.java b/src/main/java/io/appium/java_client/Location.java new file mode 100644 index 000000000..336c09797 --- /dev/null +++ b/src/main/java/io/appium/java_client/Location.java @@ -0,0 +1,58 @@ +/* + * 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 lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import javax.annotation.Nullable; + +/** + * Represents the physical location. + */ +@Getter +@ToString +@EqualsAndHashCode +public class Location { + private final double latitude; + private final double longitude; + @Nullable private final Double altitude; + + /** + * Create {@link Location} with latitude, longitude and altitude values. + * + * @param latitude latitude value. + * @param longitude longitude value. + * @param altitude altitude value (can be null). + */ + public Location(double latitude, double longitude, @Nullable Double altitude) { + this.latitude = latitude; + this.longitude = longitude; + this.altitude = altitude; + } + + /** + * Create {@link Location} with latitude and longitude values. + * + * @param latitude latitude value. + * @param longitude longitude value. + */ + public Location(double latitude, double longitude) { + this(latitude, longitude, null); + } +} diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 15704ec40..6f7eafb34 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -187,6 +187,9 @@ public class MobileCommand { public static final String GET_CURRENT_CONTEXT_HANDLE = "getCurrentContextHandle"; public static final String SWITCH_TO_CONTEXT = "switchToContext"; + public static final String GET_LOCATION = "getLocation"; + public static final String SET_LOCATION = "setLocation"; + public static final Map commandRepository; static { @@ -365,6 +368,9 @@ public class MobileCommand { commandRepository.put(GET_CONTEXT_HANDLES, getC("/session/:sessionId/contexts")); commandRepository.put(GET_CURRENT_CONTEXT_HANDLE, getC("/session/:sessionId/context")); commandRepository.put(SWITCH_TO_CONTEXT, postC("/session/:sessionId/context")); + + commandRepository.put(GET_LOCATION, getC("/session/:sessionId/location")); + commandRepository.put(SET_LOCATION, postC("/session/:sessionId/location")); } /** 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 584dde1a7..dfd9a879a 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -251,7 +251,16 @@ public AndroidBatteryInfo getBatteryInfo() { return new AndroidBatteryInfo(CommandExecutionHelper.executeScript(this, "mobile: batteryInfo")); } + /** + * Provides the location context. + * + * @return instance of {@link RemoteLocationContext} + * @deprecated This method, {@link org.openqa.selenium.html5.LocationContext} and {@link RemoteLocationContext} + * interface are deprecated, use {@link #getLocation()} and + * {@link #setLocation(io.appium.java_client.Location)} instead. + */ @Override + @Deprecated(forRemoval = true) public RemoteLocationContext getLocationContext() { return locationContext; } 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 index d1ccf8e2c..0472a5bab 100644 --- a/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java +++ b/src/main/java/io/appium/java_client/android/geolocation/SupportsExtendedGeolocationCommands.java @@ -18,7 +18,7 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; -import org.openqa.selenium.remote.DriverCommand; +import io.appium.java_client.MobileCommand; import java.util.Map; @@ -31,7 +31,7 @@ public interface SupportsExtendedGeolocationCommands extends ExecutesMethod { * @param location The location object to set. */ default void setLocation(AndroidGeoLocation location) { - CommandExecutionHelper.execute(this, Map.entry(DriverCommand.SET_LOCATION, + CommandExecutionHelper.execute(this, Map.entry(MobileCommand.SET_LOCATION, Map.of("location", location.build()) )); } 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 995c196a6..6cc48469e 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -279,7 +279,16 @@ class IOSAlert implements Alert { } + /** + * Provides the location context. + * + * @return instance of {@link RemoteLocationContext} + * @deprecated This method, {@link org.openqa.selenium.html5.LocationContext} and {@link RemoteLocationContext} + * interface are deprecated, use {@link #getLocation()} and + * {@link #setLocation(io.appium.java_client.Location)} instead. + */ @Override + @Deprecated(forRemoval = true) public RemoteLocationContext getLocationContext() { return locationContext; } diff --git a/src/main/java/io/appium/java_client/remote/SupportsLocation.java b/src/main/java/io/appium/java_client/remote/SupportsLocation.java index b2211dfda..504f418b6 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsLocation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsLocation.java @@ -16,19 +16,80 @@ package io.appium.java_client.remote; +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.CommandExecutionHelper; +import io.appium.java_client.ExecutesMethod; +import io.appium.java_client.MobileCommand; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.html5.Location; import org.openqa.selenium.html5.LocationContext; import org.openqa.selenium.remote.html5.RemoteLocationContext; -public interface SupportsLocation extends WebDriver, LocationContext { - public RemoteLocationContext getLocationContext(); +import java.util.Map; +import java.util.Optional; +public interface SupportsLocation extends WebDriver, ExecutesMethod, LocationContext { + + /** + * Provides the location context. + * + * @return instance of {@link RemoteLocationContext} + * @deprecated This method, {@link LocationContext} and {@link RemoteLocationContext} interface are deprecated, use + * {@link #getLocation()} and {@link #setLocation(io.appium.java_client.Location)} instead. + */ + @Deprecated(forRemoval = true) + RemoteLocationContext getLocationContext(); + + /** + * Gets the current device's geolocation coordinates. + * + * @return A {@link Location} containing the location information. Returns null if the location is not available + * @deprecated This method and whole {@link LocationContext} interface are deprecated, use {@link #getLocation()} + * instead. + */ + @Deprecated(forRemoval = true) default Location location() { return getLocationContext().location(); } + /** + * Gets the current device's geolocation coordinates. + * + * @return A {@link Location} containing the location information. Throws {@link WebDriverException} if the + * location is not available. + */ + default io.appium.java_client.Location getLocation() { + Map result = CommandExecutionHelper.execute(this, MobileCommand.GET_LOCATION); + return new io.appium.java_client.Location( + result.get("latitude").doubleValue(), + result.get("longitude").doubleValue(), + Optional.ofNullable(result.get("altitude")).map(Number::doubleValue).orElse(null) + ); + } + + /** + * Sets the current device's geolocation coordinates. + * + * @param location A {@link Location} containing the new location information. + * @deprecated This method and whole {@link LocationContext} interface are deprecated, use + * {@link #setLocation(io.appium.java_client.Location)} instead. + */ + @Deprecated(forRemoval = true) default void setLocation(Location location) { getLocationContext().setLocation(location); } + + /** + * Sets the current device's geolocation coordinates. + * + * @param location A {@link Location} containing the new location information. + */ + default void setLocation(io.appium.java_client.Location location) { + ImmutableMap.Builder locationParameters = ImmutableMap.builder(); + locationParameters.put("latitude", location.getLatitude()); + locationParameters.put("longitude", location.getLongitude()); + Optional.ofNullable(location.getAltitude()).ifPresent(altitude -> locationParameters.put("altitude", altitude)); + execute(MobileCommand.SET_LOCATION, Map.of("location", locationParameters)); + } } From a2e839decc61f8378b32892cba9a73239608e35a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 21 Jan 2024 17:40:33 +0100 Subject: [PATCH 194/314] fix: Assign method call listeners directly to the proxy instance (#2102) --- .../proxy/HasMethodCallListeners.java | 35 +++++ .../io/appium/java_client/proxy/Helpers.java | 12 +- .../appium/java_client/proxy/Interceptor.java | 45 +++--- .../java_client/proxy/MethodCallListener.java | 8 +- .../proxy/ProxyListenersContainer.java | 145 ------------------ 5 files changed, 74 insertions(+), 171 deletions(-) create mode 100644 src/main/java/io/appium/java_client/proxy/HasMethodCallListeners.java delete mode 100644 src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java diff --git a/src/main/java/io/appium/java_client/proxy/HasMethodCallListeners.java b/src/main/java/io/appium/java_client/proxy/HasMethodCallListeners.java new file mode 100644 index 000000000..b5807f71b --- /dev/null +++ b/src/main/java/io/appium/java_client/proxy/HasMethodCallListeners.java @@ -0,0 +1,35 @@ +/* + * 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.proxy; + +public interface HasMethodCallListeners { + /** + * The setter is dynamically created by ByteBuddy to store + * method call listeners on the instrumented proxy instance. + * + * @param methodCallListeners Array of method call listeners assigned to the proxy instance. + */ + void setMethodCallListeners(MethodCallListener[] methodCallListeners); + + /** + * The getter is dynamically created by ByteBuddy to access + * method call listeners on the instrumented proxy instance. + * + * @return Array of method call listeners assigned the proxy instance. + */ + MethodCallListener[] getMethodCallListeners(); +} diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 9543073a8..1b0c7a6d4 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -19,7 +19,9 @@ import com.google.common.base.Preconditions; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; @@ -118,16 +120,18 @@ public static T createProxy( .subclass(cls) .method(extraMethodMatcher == null ? matcher : matcher.and(extraMethodMatcher)) .intercept(MethodDelegation.to(Interceptor.class)) + // https://github.com/raphw/byte-buddy/blob/2caef35c172897cbdd21d163c55305a64649ce41/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java#L346 + .defineField("methodCallListeners", MethodCallListener[].class, Visibility.PRIVATE) + .implement(HasMethodCallListeners.class).intercept(FieldAccessor.ofBeanProperty()) .make() .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .asSubclass(cls); try { - return ProxyListenersContainer.getInstance().setListeners( - cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs)), - listeners - ); + T result = cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs)); + ((HasMethodCallListeners) result).setMethodCallListeners(listeners.toArray(MethodCallListener[]::new)); + return result; } catch (SecurityException | ReflectiveOperationException e) { throw new IllegalStateException(String.format("Unable to create a proxy of %s", cls.getName()), e); } diff --git a/src/main/java/io/appium/java_client/proxy/Interceptor.java b/src/main/java/io/appium/java_client/proxy/Interceptor.java index b8417e14b..f4ece1668 100644 --- a/src/main/java/io/appium/java_client/proxy/Interceptor.java +++ b/src/main/java/io/appium/java_client/proxy/Interceptor.java @@ -25,10 +25,10 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.Method; -import java.util.Collection; -import java.util.UUID; import java.util.concurrent.Callable; +import static io.appium.java_client.proxy.MethodCallListener.UNSET; + public class Interceptor { private static final Logger LOGGER = LoggerFactory.getLogger(Interceptor.class); @@ -37,7 +37,9 @@ private Interceptor() { /** * A magic method used to wrap public method calls in classes - * patched by ByteBuddy and acting as proxies. + * patched by ByteBuddy and acting as proxies. The performance + * of this method is mission-critical as it gets called upon + * every invocation of any method of the proxied class. * * @param self The reference to the original instance. * @param method The reference to the original method. @@ -53,12 +55,12 @@ public static Object intercept( @AllArguments Object[] args, @SuperCall Callable callable ) throws Throwable { - Collection listeners = ProxyListenersContainer.getInstance().getListeners(self); - if (listeners.isEmpty()) { + var listeners = ((HasMethodCallListeners) self).getMethodCallListeners(); + if (listeners == null || listeners.length == 0) { return callable.call(); } - listeners.forEach(listener -> { + for (var listener : listeners) { try { listener.beforeCall(self, method, args); } catch (NotImplementedException e) { @@ -68,32 +70,39 @@ public static Object intercept( self.getClass().getName(), method.getName(), e ); } - }); + } - final UUID noResult = UUID.randomUUID(); - Object result = noResult; - for (MethodCallListener listener : listeners) { + Object result = UNSET; + for (var listener : listeners) { try { result = listener.call(self, method, args, callable); - break; + if (result != UNSET) { + break; + } } catch (NotImplementedException e) { // ignore } catch (Exception e) { try { - return listener.onError(self, method, args, e); + result = listener.onError(self, method, args, e); + if (result != UNSET) { + return result; + } } catch (NotImplementedException ignore) { // ignore } throw e; } } - if (noResult.equals(result)) { + if (UNSET == result) { try { result = callable.call(); } catch (Exception e) { - for (MethodCallListener listener : listeners) { + for (var listener : listeners) { try { - return listener.onError(self, method, args, e); + result = listener.onError(self, method, args, e); + if (result != UNSET) { + return result; + } } catch (NotImplementedException ignore) { // ignore } @@ -102,8 +111,8 @@ public static Object intercept( } } - final Object endResult = result == noResult ? null : result; - listeners.forEach(listener -> { + final Object endResult = result == UNSET ? null : result; + for (var listener : listeners) { try { listener.afterCall(self, method, args, endResult); } catch (NotImplementedException e) { @@ -113,7 +122,7 @@ public static Object intercept( self.getClass().getName(), method.getName(), e ); } - }); + } return endResult; } } diff --git a/src/main/java/io/appium/java_client/proxy/MethodCallListener.java b/src/main/java/io/appium/java_client/proxy/MethodCallListener.java index f0cc1be7a..7dfb5b299 100644 --- a/src/main/java/io/appium/java_client/proxy/MethodCallListener.java +++ b/src/main/java/io/appium/java_client/proxy/MethodCallListener.java @@ -17,9 +17,11 @@ package io.appium.java_client.proxy; import java.lang.reflect.Method; +import java.util.UUID; import java.util.concurrent.Callable; public interface MethodCallListener { + UUID UNSET = UUID.randomUUID(); /** * The callback to be invoked before any public method of the proxy is called. @@ -31,7 +33,6 @@ public interface MethodCallListener { * @param args Array of method arguments */ default void beforeCall(Object obj, Method method, Object[] args) { - throw new NotImplementedException(); } /** @@ -48,7 +49,7 @@ default void beforeCall(Object obj, Method method, Object[] args) { * @return The type of the returned result should be castable to the returned type of the original method. */ default Object call(Object obj, Method method, Object[] args, Callable original) throws Throwable { - throw new NotImplementedException(); + return UNSET; } /** @@ -61,7 +62,6 @@ default Object call(Object obj, Method method, Object[] args, Callable origin * @param args Array of method arguments */ default void afterCall(Object obj, Method method, Object[] args, Object result) { - throw new NotImplementedException(); } /** @@ -77,6 +77,6 @@ default void afterCall(Object obj, Method method, Object[] args, Object result) * type of the returned argument could be cast to the returned type of the original method. */ default Object onError(Object obj, Method method, Object[] args, Throwable e) throws Throwable { - throw new NotImplementedException(); + return UNSET; } } diff --git a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java b/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java deleted file mode 100644 index 4d93ebefb..000000000 --- a/src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java +++ /dev/null @@ -1,145 +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.proxy; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -import java.lang.ref.WeakReference; -import java.time.Duration; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.Semaphore; - -class ProxyListenersContainer { - private static final Duration LISTENERS_CLEANUP_INTERVAL = Duration.ofMinutes(1); - - private static ProxyListenersContainer INSTANCE; - - public static synchronized ProxyListenersContainer getInstance() { - if (INSTANCE == null) { - INSTANCE = new ProxyListenersContainer(); - } - return INSTANCE; - } - - private ProxyListenersContainer() { - var task = new TimerTask() { - @Override - public void run() { - getListeners(null); - } - }; - // Listeners are cleaned up lazily, e.g. every time getListeners API - // is called we also remove garbage-collected items. Although, due to an - // unpredictable nature of the garbage collector and no guarantees about the - // frequency of getListeners API calls we schedule the below loop to be executed every - // minute, and make sure there are no extra references to obsolete listeners - new Timer().scheduleAtFixedRate(task, 0, LISTENERS_CLEANUP_INTERVAL.toMillis()); - } - - private final Semaphore listenersGuard = new Semaphore(1); - private final List, Collection>> listenerPairs = new LinkedList<>(); - - @Getter - @AllArgsConstructor - private static class Pair { - private final K key; - @Setter - private V value; - } - - /** - * Assign listeners for the particular proxied instance. - * - * @param proxyInstance The proxied instance. - * @param listeners Collection of listeners. - * @return The same given instance. - */ - public T setListeners(T proxyInstance, Collection listeners) { - try { - listenersGuard.acquire(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - try { - int i = 0; - boolean wasInstancePresent = false; - while (i < listenerPairs.size()) { - var pair = listenerPairs.get(i); - Object key = pair.getKey().get(); - if (key == null) { - // The instance has been garbage-collected - listenerPairs.remove(i); - continue; - } - - if (key == proxyInstance) { - pair.setValue(List.copyOf(listeners)); - wasInstancePresent = true; - } - i++; - } - if (!wasInstancePresent) { - listenerPairs.add(new Pair<>(new WeakReference<>(proxyInstance), List.copyOf(listeners))); - } - } finally { - listenersGuard.release(); - } - return proxyInstance; - } - - /** - * Fetches listeners for the particular proxied instance. - * - * @param proxyInstance The proxied instance. - */ - public Collection getListeners(Object proxyInstance) { - try { - listenersGuard.acquire(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - try { - int i = 0; - Collection result = Collections.emptyList(); - while (i < listenerPairs.size()) { - var pair = listenerPairs.get(i); - Object key = pair.getKey().get(); - if (key == null) { - // The instance has been garbage-collected - listenerPairs.remove(i); - continue; - } - - if (key == proxyInstance) { - result = pair.getValue(); - } - i++; - } - return result; - } finally { - listenersGuard.release(); - } - } - -} From c83013f340e06500e3bb5bcc9fd8d4b5ca2ee969 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:22:09 +0200 Subject: [PATCH 195/314] build(deps): Bump org.owasp.dependencycheck from 9.0.8 to 9.0.9 (#2103) Bumps org.owasp.dependencycheck from 9.0.8 to 9.0.9. --- 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 54db6c5fb..3596d3e28 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.8' + id 'org.owasp.dependencycheck' version '9.0.9' id 'com.github.johnrengelman.shadow' version '8.1.1' } From a0b0f61db5cc5c9c91b0914dc53510538e318124 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 26 Jan 2024 16:04:54 +0100 Subject: [PATCH 196/314] docs: Describe the release procedure (#2104) * docs: Describe the release procedure * Add note about gradle * Address comments --- docs/release.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/release.md diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 000000000..ec4927a75 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,25 @@ +# Appium Java Client Release Procedure + +This document describes the process of releasing this client to the Maven repository. +Its target auditory is project maintainers. + +## Release Steps + +1. Update the [Changelog](../CHANGELOG.md) for the given version based on previous commits. +1. Bump the `appiumClient.version` number in [gradle.properties](../gradle.properties). +1. Create a pull request to approve the changelog and version bump. +1. Merge the pull request after it is approved. +1. Create and push a new repository tag. The tag name should look like + `v..`. +1. Open [Sonatype](https://oss.sonatype.org/#welcome) in your browser. +1. Login to Nexus using 1Password credentials. Ask Appium maintainers + if you need access to the team's 1Password vault. +1. Navigate to `Staging Repositories`. +1. Select the corresponding release and click `Close`. +1. Wait until checks are completed. +1. Click `Release`. +1. After the new release is published, it becomes available in + [Maven Central](https://repo1.maven.org/maven2/io/appium/java-client/) + within 30 minutes. Once artifacts are in Maven Central, it normally + takes 1-2 hours before they appear in + [search results](https://central.sonatype.com/artifact/io.appium/java-client). From 38c96a271a2b509d15b959a2215d7f4a0c5d472a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 26 Jan 2024 21:49:19 +0200 Subject: [PATCH 197/314] release: v9.1.0 and update release notes (#2106) --- CHANGELOG.md | 23 +++++++++++++++++++++++ gradle.properties | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a16add346..9b3f9b0d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.1.0_ +- **[ENHANCEMENTS]** + - Introduce better constructor argument validation for the `AppiumFieldDecorator` class. [#2070](https://github.com/appium/java-client/pull/2070) + - Add `toString` to `AppiumClientConfig`. [#2076](https://github.com/appium/java-client/pull/2076) + - Perform listeners cleanup periodically. [#2077](https://github.com/appium/java-client/pull/2077) + - Add non-W3C context, orientation and rotation management endpoints removed from Selenium client. [#2093](https://github.com/appium/java-client/pull/2093) + - Add non-W3C Location-management endpoints deprecated in Selenium client. [#2098](https://github.com/appium/java-client/pull/2098) +- **[BUG FIX]** + - Properly unwrap driver instance if the `ContextAware` object is deeply nested. [#2052](https://github.com/appium/java-client/pull/2052) + - Update hashing and iteration logic of page object items. [#2067](https://github.com/appium/java-client/pull/2067) + - Assign method call listeners directly to the proxy instance. [#2102](https://github.com/appium/java-client/pull/2102) + - Use JDK 11 to build Jitpack snapshots. [#2083](https://github.com/appium/java-client/pull/2083) +- **[DEPRECATION]** + - Deprecate custom functional interfaces. [#2055](https://github.com/appium/java-client/pull/2055) +- **[REFACTOR]** + - Use Java 9+ APIs instead of outdated/3rd-party APIs. [#2048](https://github.com/appium/java-client/pull/2048) + - Migrate to new Selenium API for process management. [#2054](https://github.com/appium/java-client/pull/2054) +- **[DEPENDENCY CHANGE]** + - Bump minimum supported Selenium version from `4.14.1` to `4.17.0`. + - Bump SLF4J from `2.0.9` to `2.0.11`. [#2091](https://github.com/appium/java-client/pull/2091), [#2099](https://github.com/appium/java-client/pull/2099) +- **[DOCUMENTATION]** + - Describe the release procedure. [#2104](https://github.com/appium/java-client/pull/2104) + _9.0.0_ - **[DOCUMENTATION]** - Add 8 to 9 migration guide. [#2039](https://github.com/appium/java-client/pull/2039) diff --git a/gradle.properties b/gradle.properties index 6e923bc15..16c8cc317 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.14.1 +selenium.version=4.17.0 # Please increment the value in a release -appiumClient.version=9.0.0 +appiumClient.version=9.1.0 From d90df884ecc1802dadd3a2b3cebb81cc0d8397b2 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 27 Jan 2024 14:40:04 +0100 Subject: [PATCH 198/314] docs: Update release steps (#2107) --- docs/release.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/release.md b/docs/release.md index ec4927a75..f2a6c1ae6 100644 --- a/docs/release.md +++ b/docs/release.md @@ -10,12 +10,16 @@ Its target auditory is project maintainers. 1. Create a pull request to approve the changelog and version bump. 1. Merge the pull request after it is approved. 1. Create and push a new repository tag. The tag name should look like - `v..`. + `v..`. +1. Create a new [Release](https://github.com/appium/java-client/releases/new) in GitHub. + Paste the above changelist into the release notes. Make sure the name of the new release + matches to the name of the above tag. 1. Open [Sonatype](https://oss.sonatype.org/#welcome) in your browser. 1. Login to Nexus using 1Password credentials. Ask Appium maintainers - if you need access to the team's 1Password vault. + if you need access to the team's 1Password vault. 1. Navigate to `Staging Repositories`. -1. Select the corresponding release and click `Close`. +1. Select the corresponding release and click `Close`. Note that it may take + some minutes until Sonatype picks up the GitHub release. 1. Wait until checks are completed. 1. Click `Release`. 1. After the new release is published, it becomes available in From 4623e1138ca19c388ac2f636a30a4320c00b7fb5 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 30 Jan 2024 14:09:13 +0200 Subject: [PATCH 199/314] fix: Set correct geolocation coordinates of the current device (#2109) --- .../java/io/appium/java_client/remote/SupportsLocation.java | 2 +- .../java/io/appium/java_client/android/AndroidDriverTest.java | 4 ++-- src/test/java/io/appium/java_client/ios/IOSDriverTest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/appium/java_client/remote/SupportsLocation.java b/src/main/java/io/appium/java_client/remote/SupportsLocation.java index 504f418b6..45d81f36c 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsLocation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsLocation.java @@ -90,6 +90,6 @@ default void setLocation(io.appium.java_client.Location location) { locationParameters.put("latitude", location.getLatitude()); locationParameters.put("longitude", location.getLongitude()); Optional.ofNullable(location.getAltitude()).ifPresent(altitude -> locationParameters.put("altitude", altitude)); - execute(MobileCommand.SET_LOCATION, Map.of("location", locationParameters)); + execute(MobileCommand.SET_LOCATION, Map.of("location", locationParameters.build())); } } diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index e1ff99d2e..76753d75d 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -16,12 +16,12 @@ package io.appium.java_client.android; +import io.appium.java_client.Location; import io.appium.java_client.appmanagement.ApplicationState; import org.apache.commons.io.FileUtils; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.openqa.selenium.ScreenOrientation; -import org.openqa.selenium.html5.Location; import java.io.File; import java.time.Duration; @@ -192,7 +192,7 @@ public void toggleLocationServicesTest() { @Test public void geolocationTest() { - Location location = new Location(45, 45, 100); + Location location = new Location(45, 45, 100.0); driver.setLocation(location); } 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 b5d718eb8..438178e36 100644 --- a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.ios; +import io.appium.java_client.Location; import io.appium.java_client.appmanagement.ApplicationState; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -23,7 +24,6 @@ import org.openqa.selenium.By; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.WebElement; -import org.openqa.selenium.html5.Location; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.http.HttpMethod; @@ -88,7 +88,7 @@ public void getDeviceTimeTest() { @Disabled @Test public void geolocationTest() { - Location location = new Location(45, 45, 100); + Location location = new Location(45, 45, 100.0); try { driver.setLocation(location); } catch (Exception e) { From 38636226d1a07e2ee849cfa4e8f0625152085f72 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 2 Feb 2024 14:25:51 +0200 Subject: [PATCH 200/314] docs: add Appium Java client `9.1.0` to Compatibility Matrix (#2110) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a20f242cd..08bf82124 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client ---------------------------|----------------- + `9.1.0` | `4.17.0` `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` From 3f211fa095a30883193f40afefefc4243b830bce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:37:14 +0200 Subject: [PATCH 201/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.10.1 to 5.10.2 (#2113) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.1 to 5.10.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 3596d3e28..487e02e64 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.3') { From 2e6a74b4fb0dd6ff6f6feb0608491e517b803243 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:37:34 +0200 Subject: [PATCH 202/314] build(deps): Bump gradle/wrapper-validation-action from 1 to 2 (#2114) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action 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> --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index f6851f4b1..531000b79 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -24,4 +24,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v1 + - uses: gradle/wrapper-validation-action@v2 From b7ec2f3fe25e2db964455d8296cd1bbcfc948b39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:57:28 +0300 Subject: [PATCH 203/314] build(deps): Bump slf4jVersion from 2.0.11 to 2.0.12 (#2115) Bumps `slf4jVersion` from 2.0.11 to 2.0.12. Updates `org.slf4j:slf4j-api` from 2.0.11 to 2.0.12 Updates `org.slf4j:slf4j-simple` from 2.0.11 to 2.0.12 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 487e02e64..ae9e02882 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.11' + slf4jVersion = '2.0.12' } dependencies { From 36752de5736da21a896631ca5a7f0724be0983c4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 16 Feb 2024 16:52:13 +0100 Subject: [PATCH 204/314] test: Remove obsolete tests (#2120) --- .../appium/java_client/ios/IOSTouchTest.java | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 src/test/java/io/appium/java_client/ios/IOSTouchTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java deleted file mode 100644 index 38e66359f..000000000 --- a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package io.appium.java_client.ios; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.MultiTouchAction; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.openqa.selenium.By; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.WebDriverWait; - -import java.time.Duration; - -import static io.appium.java_client.ios.touch.IOSPressOptions.iosPressOptions; -import static io.appium.java_client.touch.TapOptions.tapOptions; -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 org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; - -@TestMethodOrder(MethodOrderer.MethodName.class) -public class IOSTouchTest extends AppIOSTest { - - @Test - public void tapTest() { - WebElement intA = driver.findElement(By.id("IntegerA")); - WebElement intB = driver.findElement(By.id("IntegerB")); - intA.clear(); - intB.clear(); - intA.sendKeys("2"); - intB.sendKeys("4"); - - WebElement e = driver.findElement(AppiumBy.accessibilityId("ComputeSumButton")); - new IOSTouchAction(driver).tap(tapOptions().withElement(element(e))).perform(); - assertEquals(driver.findElement(By.xpath("//*[@name = \"Answer\"]")).getText(), "6"); - } - - @Test - public void touchWithPressureTest() { - WebElement intA = driver.findElement(By.id("IntegerA")); - WebElement intB = driver.findElement(By.id("IntegerB")); - intA.clear(); - intB.clear(); - intA.sendKeys("2"); - intB.sendKeys("4"); - - WebElement e = driver.findElement(AppiumBy.accessibilityId("ComputeSumButton")); - new IOSTouchAction(driver) - .press(iosPressOptions() - .withElement(element(e)) - .withPressure(1)) - .waitAction(waitOptions(ofMillis(100))) - .release() - .perform(); - assertEquals(driver.findElement(By.xpath("//*[@name = \"Answer\"]")).getText(), "6"); - } - - @Test public void multiTouchTest() { - WebElement e = driver.findElement(AppiumBy.accessibilityId("ComputeSumButton")); - WebElement e2 = driver.findElement(AppiumBy.accessibilityId("show alert")); - - 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(); - - WebDriverWait waiting = new WebDriverWait(driver, Duration.ofSeconds(10)); - assertNotNull(waiting.until(alertIsPresent())); - driver.switchTo().alert().accept(); - } - - @Test public void doubleTapTest() { - WebElement firstField = driver.findElement(By.id("IntegerA")); - firstField.sendKeys("2"); - - IOSTouchAction iosTouchAction = new IOSTouchAction(driver); - iosTouchAction.doubleTap(element(firstField)); - WebElement editingMenu = driver.findElement(AppiumBy.className("XCUIElementTypeTextField")); - assertNotNull(editingMenu); - } -} From 63bcbf5d6bd823aeef81f47e4ac302bf37b212ca Mon Sep 17 00:00:00 2001 From: AlessandroMiccoli <57630383+AlessandroMiccoli@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:53:56 +0000 Subject: [PATCH 205/314] feat: add pollDelay mechanism into AppiumFluentWait (#2116) --- .../appium/java_client/AppiumFluentWait.java | 90 ++++++++++++++----- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 9061600a0..6361a5652 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -29,12 +29,16 @@ import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; public class AppiumFluentWait extends FluentWait { private Function pollingStrategy = null; + private static final Duration DEFAULT_POLL_DELAY_DURATION = Duration.ZERO; + private Duration pollDelay = DEFAULT_POLL_DELAY_DURATION; + public static class IterationInfo { /** * The current iteration number. @@ -98,6 +102,18 @@ public AppiumFluentWait(T input, Clock clock, Sleeper sleeper) { super(input, clock, sleeper); } + /** + * Sets how long to wait before starting to evaluate condition to be true. + * The default pollDelay is {@link #DEFAULT_POLL_DELAY_DURATION}. + * + * @param pollDelay The pollDelay duration. + * @return A self reference. + */ + public AppiumFluentWait withPollDelay(Duration pollDelay) { + this.pollDelay = pollDelay; + return this; + } + private B getPrivateFieldValue(String fieldName, Class fieldType) { return ReflectionHelpers.getPrivateFieldValue(FluentWait.class, this, fieldName, fieldType); } @@ -200,10 +216,19 @@ public AppiumFluentWait withPollingStrategy(Function */ @Override public V until(Function isTrue) { - final Instant start = getClock().instant(); - final Instant end = getClock().instant().plus(getTimeout()); - long iterationNumber = 1; + final var start = getClock().instant(); + // Adding pollDelay to end instant will allow to verify the condition for the expected timeout duration. + final var end = start.plus(getTimeout()).plus(pollDelay); + + return performIteration(isTrue, start, end); + } + + private V performIteration(Function isTrue, Instant start, Instant end) { + var iterationNumber = 1; Throwable lastException; + + sleepInterruptibly(pollDelay); + while (true) { try { V value = isTrue.apply(getInput()); @@ -222,32 +247,51 @@ public V until(Function isTrue) { // Check the timeout after evaluating the function to ensure conditions // with a zero timeout can succeed. if (end.isBefore(getClock().instant())) { - String message = getMessageSupplier() != null ? getMessageSupplier().get() : null; - - String timeoutMessage = String.format( - "Expected condition failed: %s (tried for %d second(s) with %s interval)", - message == null ? "waiting for " + isTrue : message, - getTimeout().getSeconds(), getInterval()); - throw timeoutException(timeoutMessage, lastException); + handleTimeoutException(lastException, isTrue); } - try { - Duration interval = getInterval(); - if (pollingStrategy != null) { - final IterationInfo info = new IterationInfo(iterationNumber, - Duration.between(start, getClock().instant()), getTimeout(), - interval); - interval = pollingStrategy.apply(info); - } - getSleeper().sleep(interval); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new WebDriverException(e); - } + var interval = getIntervalWithPollingStrategy(start, iterationNumber); + sleepInterruptibly(interval); + ++iterationNumber; } } + private void handleTimeoutException(Throwable lastException, Function isTrue) { + var message = Optional.ofNullable(getMessageSupplier()) + .map(Supplier::get) + .orElseGet(() -> "waiting for " + isTrue); + + var timeoutMessage = String.format( + "Expected condition failed: %s (tried for %s ms with an interval of %s ms)", + message, + getTimeout().toMillis(), + getInterval().toMillis() + ); + + throw timeoutException(timeoutMessage, lastException); + } + + private Duration getIntervalWithPollingStrategy(Instant start, long iterationNumber) { + var interval = getInterval(); + return Optional.ofNullable(pollingStrategy) + .map(strategy -> strategy.apply(new IterationInfo( + iterationNumber, + Duration.between(start, getClock().instant()), getTimeout(), interval))) + .orElse(interval); + } + + private void sleepInterruptibly(Duration duration) { + try { + if (!duration.isZero() && !duration.isNegative()) { + getSleeper().sleep(duration); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new WebDriverException(e); + } + } + protected Throwable propagateIfNotIgnored(Throwable e) { for (Class ignoredException : getIgnoredExceptions()) { if (ignoredException.isInstance(e)) { From 6075c9d6d5757708e9f09af53c98b738ba08dd28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:50:42 +0200 Subject: [PATCH 206/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2125) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.6.3 to 5.7.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-5.6.3...webdrivermanager-5.7.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager 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 ae9e02882..0eb9997c9 100644 --- a/build.gradle +++ b/build.gradle @@ -71,7 +71,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.6.3') { + testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.7.0') { exclude group: 'org.seleniumhq.selenium' } testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" From 7ba6ef91c3769f212a229dc8d43af3725db4a4a6 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 4 Mar 2024 15:46:59 +0100 Subject: [PATCH 207/314] fix: Always release annotated element reference from the builder instance (#2128) --- .../AppiumElementLocatorFactory.java | 21 ++++++++++++------- .../pagefactory/WidgetByBuilder.java | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 787e34e9e..77b3120fc 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -19,7 +19,6 @@ import io.appium.java_client.pagefactory.bys.builder.AppiumByBuilder; import io.appium.java_client.pagefactory.locator.CacheableElementLocatorFactory; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; import javax.annotation.Nullable; @@ -90,12 +89,18 @@ public CacheableLocator createLocator(AnnotatedElement annotatedElement) { customDuration = duration; } builder.setAnnotated(annotatedElement); - By byResult = builder.buildBy(); - return ofNullable(byResult) - .map(by -> searchContextReference != null - ? new AppiumElementLocator(searchContextReference, by, builder.isLookupCached(), customDuration) - : new AppiumElementLocator(searchContext, by, builder.isLookupCached(), customDuration) - ) - .orElse(null); + try { + return ofNullable(builder.buildBy()) + .map(by -> { + var isLookupCached = builder.isLookupCached(); + return searchContextReference != null + ? new AppiumElementLocator(searchContextReference, by, isLookupCached, customDuration) + : new AppiumElementLocator(searchContext, by, isLookupCached, customDuration); + }) + .orElse(null); + } finally { + // unleak element reference after the locator is built + builder.setAnnotated(null); + } } } diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java index 905844e4a..b87996357 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java @@ -51,7 +51,7 @@ private static Class getClassFromAListField(Field field) { @SuppressWarnings("unchecked") private By getByFromDeclaredClass(WhatIsNeeded whatIsNeeded) { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); - Field field = Field.class.cast(annotatedElement); + Field field = (Field) annotatedElement; Class declaredClass; By result = null; From 6144fb2cae2948c2abe9d792375baa4136c8e941 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 4 Mar 2024 15:47:14 +0100 Subject: [PATCH 208/314] fix: Cache dynamic proxy classes created by ByteBuddy (#2129) --- .../io/appium/java_client/proxy/Helpers.java | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 1b0c7a6d4..af724ae43 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -17,6 +17,7 @@ package io.appium.java_client.proxy; import com.google.common.base.Preconditions; +import lombok.Value; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Visibility; @@ -31,6 +32,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -42,6 +45,13 @@ public class Helpers { .map(Method::getName) .collect(Collectors.toSet()); + // Each proxy class created by ByteBuddy gets automatically cached by the + // given class loader. It is important to have this cache here in order to improve + // the performance and to avoid extensive memory usage for our case, where + // the amount of instrumented proxy classes we create is low in comparison to the amount + // of proxy instances. + private static final ConcurrentMap> CACHED_PROXY_CLASSES = new ConcurrentHashMap<>(); + private Helpers() { } @@ -104,32 +114,35 @@ public static T createProxy( Collection listeners, @Nullable ElementMatcher extraMethodMatcher ) { - Preconditions.checkArgument(constructorArgs.length == constructorArgTypes.length, - String.format( - "Constructor arguments array length %d must be equal to the types array length %d", - constructorArgs.length, constructorArgTypes.length - ) - ); - Preconditions.checkArgument(!listeners.isEmpty(), "The collection of listeners must not be empty"); - requireNonNull(cls, "Class must not be null"); - Preconditions.checkArgument(!cls.isInterface(), "Class must not be an interface"); + var signature = ProxyClassSignature.of(cls, constructorArgTypes, extraMethodMatcher); + var proxyClass = CACHED_PROXY_CLASSES.computeIfAbsent(signature, k -> { + Preconditions.checkArgument(constructorArgs.length == constructorArgTypes.length, + String.format( + "Constructor arguments array length %d must be equal to the types array length %d", + constructorArgs.length, constructorArgTypes.length + ) + ); + Preconditions.checkArgument(!listeners.isEmpty(), "The collection of listeners must not be empty"); + requireNonNull(cls, "Class must not be null"); + Preconditions.checkArgument(!cls.isInterface(), "Class must not be an interface"); - ElementMatcher.Junction matcher = ElementMatchers.isPublic(); - //noinspection resource - Class proxy = new ByteBuddy() - .subclass(cls) - .method(extraMethodMatcher == null ? matcher : matcher.and(extraMethodMatcher)) - .intercept(MethodDelegation.to(Interceptor.class)) - // https://github.com/raphw/byte-buddy/blob/2caef35c172897cbdd21d163c55305a64649ce41/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java#L346 - .defineField("methodCallListeners", MethodCallListener[].class, Visibility.PRIVATE) - .implement(HasMethodCallListeners.class).intercept(FieldAccessor.ofBeanProperty()) - .make() - .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER) - .getLoaded() - .asSubclass(cls); + ElementMatcher.Junction matcher = ElementMatchers.isPublic(); + //noinspection resource + return new ByteBuddy() + .subclass(cls) + .method(extraMethodMatcher == null ? matcher : matcher.and(extraMethodMatcher)) + .intercept(MethodDelegation.to(Interceptor.class)) + // https://github.com/raphw/byte-buddy/blob/2caef35c172897cbdd21d163c55305a64649ce41/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java#L346 + .defineField("methodCallListeners", MethodCallListener[].class, Visibility.PRIVATE) + .implement(HasMethodCallListeners.class).intercept(FieldAccessor.ofBeanProperty()) + .make() + .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER) + .getLoaded() + .asSubclass(cls); + }); try { - T result = cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs)); + T result = cls.cast(proxyClass.getConstructor(constructorArgTypes).newInstance(constructorArgs)); ((HasMethodCallListeners) result).setMethodCallListeners(listeners.toArray(MethodCallListener[]::new)); return result; } catch (SecurityException | ReflectiveOperationException e) { @@ -201,4 +214,11 @@ public static T createProxy( ) { return createProxy(cls, constructorArgs, constructorArgTypes, Collections.singletonList(listener)); } + + @Value(staticConstructor = "of") + private static class ProxyClassSignature { + Class cls; + Class[] constructorArgTypes; + ElementMatcher extraMethodMatcher; + } } From 41769295f6af68da7721be8cfd3a59ee01fd01d4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 10 Mar 2024 13:52:37 +0100 Subject: [PATCH 209/314] chore: Make server startup error messages more useful (#2130) --- .../local/AppiumDriverLocalService.java | 117 ++++++++----- .../AppiumServerAvailabilityChecker.java | 158 ++++++++++++++++++ ...rverHasNotBeenStartedLocallyException.java | 14 +- 3 files changed, 241 insertions(+), 48 deletions(-) create mode 100644 src/main/java/io/appium/java_client/service/local/AppiumServerAvailabilityChecker.java 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 45f611eab..666f2ba06 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 @@ -17,8 +17,8 @@ package io.appium.java_client.service.local; import com.google.common.annotations.VisibleForTesting; +import lombok.Getter; import lombok.SneakyThrows; -import org.openqa.selenium.net.UrlChecker; import org.openqa.selenium.os.ExternalProcess; import org.openqa.selenium.remote.service.DriverService; import org.slf4j.Logger; @@ -30,14 +30,12 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -67,7 +65,9 @@ public final class AppiumDriverLocalService extends DriverService { 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 AppiumServerAvailabilityChecker availabilityChecker = new AppiumServerAvailabilityChecker(); private final URL url; + @Getter private String basePath; private ExternalProcess process = null; @@ -97,10 +97,6 @@ public AppiumDriverLocalService withBasePath(String basePath) { return this; } - public String getBasePath() { - return this.basePath; - } - @SneakyThrows private static URL addSuffix(URL url, String suffix) { return url.toURI().resolve("." + (suffix.startsWith("/") ? suffix : "/" + suffix)).toURL(); @@ -131,36 +127,40 @@ public boolean isRunning() { } try { - ping(IS_RUNNING_PING_TIMEOUT); - return true; - } catch (UrlChecker.TimeoutException e) { + return ping(IS_RUNNING_PING_TIMEOUT); + } catch (AppiumServerAvailabilityChecker.ConnectionTimeout + | AppiumServerAvailabilityChecker.ConnectionError e) { return false; - } catch (MalformedURLException e) { - throw new AppiumServerHasNotBeenStartedLocallyException(e.getMessage(), e); + } catch (InterruptedException e) { + throw new RuntimeException(e); } } finally { lock.unlock(); } + } + private boolean ping(Duration timeout) throws InterruptedException { + var baseURL = fixBroadcastAddresses(getUrl()); + var statusUrl = addSuffix(baseURL, "/status"); + return availabilityChecker.waitUntilAvailable(statusUrl, timeout); } - private void ping(Duration timeout) throws UrlChecker.TimeoutException, MalformedURLException { - URL baseURL = getUrl(); - String host = baseURL.getHost(); + private URL fixBroadcastAddresses(URL url) { + var host = url.getHost(); // The operating system will block direct access to the universal broadcast IP address if (host.equals(BROADCAST_IP4_ADDRESS)) { - baseURL = replaceHost(baseURL, BROADCAST_IP4_ADDRESS, "127.0.0.1"); - } else if (host.equals(BROADCAST_IP6_ADDRESS)) { - baseURL = replaceHost(baseURL, BROADCAST_IP6_ADDRESS, "::1"); + return replaceHost(url, BROADCAST_IP4_ADDRESS, "127.0.0.1"); + } + if (host.equals(BROADCAST_IP6_ADDRESS)) { + return replaceHost(url, BROADCAST_IP6_ADDRESS, "::1"); } - URL status = addSuffix(baseURL, "/status"); - new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); + return url; } /** * Starts the defined appium server. * - * @throws AppiumServerHasNotBeenStartedLocallyException If an error occurs while spawning the child process. + * @throws AppiumServerHasNotBeenStartedLocallyException If an error occurs on Appium server startup. * @see #stop() */ @Override @@ -172,40 +172,75 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } try { - ExternalProcess.Builder processBuilder = ExternalProcess.builder() + var processBuilder = ExternalProcess.builder() .command(this.nodeJSExec.getCanonicalPath(), nodeJSArgs) .copyOutputTo(stream); nodeJSEnvironment.forEach(processBuilder::environment); process = processBuilder.start(); + } catch (IOException e) { + throw new AppiumServerHasNotBeenStartedLocallyException(e); + } + + var didPingSucceed = false; + try { ping(startupTimeout); - } catch (Exception e) { - final Optional output = ofNullable(process) - .map(ExternalProcess::getOutput) - .filter(o -> !isNullOrEmpty(o)); - destroyProcess(); - List errorLines = new ArrayList<>(); - errorLines.add("The local appium server has not been started"); - errorLines.add(String.format("Reason: %s", e.getMessage())); - if (e instanceof UrlChecker.TimeoutException) { - errorLines.add(String.format( - "Consider increasing the server startup timeout value (currently %sms)", - startupTimeout.toMillis() - )); - } - errorLines.add( - String.format("Node.js executable path: %s", nodeJSExec.getAbsolutePath()) - ); - errorLines.add(String.format("Arguments: %s", nodeJSArgs)); - output.ifPresent(o -> errorLines.add(String.format("Output: %s", o))); + didPingSucceed = true; + } catch (AppiumServerAvailabilityChecker.ConnectionTimeout + | AppiumServerAvailabilityChecker.ConnectionError e) { + var errorLines = new ArrayList<>(generateDetailedErrorMessagePrefix(e)); + errorLines.addAll(retrieveServerDebugInfo()); throw new AppiumServerHasNotBeenStartedLocallyException( String.join("\n", errorLines), e ); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + if (!didPingSucceed) { + destroyProcess(); + } } } finally { lock.unlock(); } } + private List generateDetailedErrorMessagePrefix(RuntimeException e) { + var errorLines = new ArrayList(); + if (e instanceof AppiumServerAvailabilityChecker.ConnectionTimeout) { + errorLines.add(String.format( + "Appium HTTP server is not listening at %s after %s ms timeout. " + + "Consider increasing the server startup timeout value and " + + "check the server log for possible error messages occurrences.", getUrl(), + ((AppiumServerAvailabilityChecker.ConnectionTimeout) e).getTimeout().toMillis() + )); + } else if (e instanceof AppiumServerAvailabilityChecker.ConnectionError) { + var connectionError = (AppiumServerAvailabilityChecker.ConnectionError) e; + var statusCode = connectionError.getResponseCode(); + var statusUrl = connectionError.getStatusUrl(); + var payload = connectionError.getPayload(); + errorLines.add(String.format( + "Appium HTTP server has started and is listening although we were " + + "unable to get an OK response from %s. Make sure both the client " + + "and the server use the same base path '%s' and check the server log for possible " + + "error messages occurrences.", statusUrl, Optional.ofNullable(basePath).orElse("/") + )); + errorLines.add(String.format("Response status code: %s", statusCode)); + payload.ifPresent(p -> errorLines.add(String.format("Response payload: %s", p))); + } + return errorLines; + } + + private List retrieveServerDebugInfo() { + var result = new ArrayList(); + result.add(String.format("Node.js executable path: %s", nodeJSExec.getAbsolutePath())); + result.add(String.format("Arguments: %s", nodeJSArgs)); + ofNullable(process) + .map(ExternalProcess::getOutput) + .filter(o -> !isNullOrEmpty(o)) + .ifPresent(o -> result.add(String.format("Server log: %s", o))); + return result; + } + /** * Stops this service is it is currently running. This method will attempt to block until the * server has been fully shutdown. diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServerAvailabilityChecker.java b/src/main/java/io/appium/java_client/service/local/AppiumServerAvailabilityChecker.java new file mode 100644 index 000000000..2876c3707 --- /dev/null +++ b/src/main/java/io/appium/java_client/service/local/AppiumServerAvailabilityChecker.java @@ -0,0 +1,158 @@ +/* + * 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.service.local; + +import lombok.Getter; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.Duration; +import java.time.Instant; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +public class AppiumServerAvailabilityChecker { + private static final Duration CONNECT_TIMEOUT = Duration.ofMillis(500); + private static final Duration READ_TIMEOUT = Duration.ofSeconds(1); + private static final Duration MAX_POLL_INTERVAL = Duration.ofMillis(320); + private static final Duration MIN_POLL_INTERVAL = Duration.ofMillis(10); + + /** + * Verifies a possibility of establishing a connection + * to a running Appium server. + * + * @param serverStatusUrl The URL of /status endpoint. + * @param timeout Wait timeout. If the server responds with non-200 error + * code then we are not going to retry, but throw an exception + * immediately. + * @return true in case of success + * @throws InterruptedException If the API is interrupted + * @throws ConnectionTimeout If it is not possible to successfully open + * an HTTP connection to the server's /status endpoint. + * @throws ConnectionError If an HTTP connection was opened successfully, + * but non-200 error code was received. + */ + public boolean waitUntilAvailable(URL serverStatusUrl, Duration timeout) throws InterruptedException { + var interval = MIN_POLL_INTERVAL; + var start = Instant.now(); + IOException lastError = null; + while (Duration.between(start, Instant.now()).compareTo(timeout) <= 0) { + HttpURLConnection connection = null; + try { + connection = connectToUrl(serverStatusUrl); + return checkResponse(connection); + } catch (IOException e) { + lastError = e; + } finally { + Optional.ofNullable(connection).ifPresent(HttpURLConnection::disconnect); + } + //noinspection BusyWait + Thread.sleep(interval.toMillis()); + interval = interval.compareTo(MAX_POLL_INTERVAL) >= 0 ? interval : interval.multipliedBy(2); + } + throw new ConnectionTimeout(timeout, lastError); + } + + private HttpURLConnection connectToUrl(URL url) throws IOException { + var connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout((int) CONNECT_TIMEOUT.toMillis()); + connection.setReadTimeout((int) READ_TIMEOUT.toMillis()); + connection.connect(); + return connection; + } + + private boolean checkResponse(HttpURLConnection connection) throws IOException { + var responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + return true; + } + var is = responseCode < HttpURLConnection.HTTP_BAD_REQUEST + ? connection.getInputStream() + : connection.getErrorStream(); + throw new ConnectionError(connection.getURL(), responseCode, is); + } + + @Getter + public static class ConnectionError extends RuntimeException { + private static final int MAX_PAYLOAD_LEN = 1024; + + private final URL statusUrl; + private final int responseCode; + private final Optional payload; + + /** + * Thrown on server connection errors. + * + * @param statusUrl Appium server status URL. + * @param responseCode The response code received from the URL above. + * @param body The response body stream received from the URL above. + */ + public ConnectionError(URL statusUrl, int responseCode, InputStream body) { + super(ConnectionError.class.getSimpleName()); + this.statusUrl = statusUrl; + this.responseCode = responseCode; + this.payload = readResponseStreamSafely(body); + } + + private static Optional readResponseStreamSafely(InputStream is) { + try (var br = new BufferedReader(new InputStreamReader(is))) { + var result = new LinkedList(); + String currentLine; + var payloadSize = 0L; + while ((currentLine = br.readLine()) != null) { + result.addFirst(currentLine); + payloadSize += currentLine.length(); + while (payloadSize > MAX_PAYLOAD_LEN && result.size() > 1) { + payloadSize -= result.removeLast().length(); + } + } + var s = abbreviate(result); + return s.isEmpty() ? Optional.empty() : Optional.of(s); + } catch (IOException e) { + return Optional.empty(); + } + } + + private static String abbreviate(List filo) { + var result = String.join("\n", filo).trim(); + return result.length() > MAX_PAYLOAD_LEN + ? "…" + result.substring(0, MAX_PAYLOAD_LEN) + : result; + } + } + + @Getter + public static class ConnectionTimeout extends RuntimeException { + private final Duration timeout; + + /** + * Thrown on server timeout errors. + * + * @param timeout Timeout value. + * @param cause Timeout cause. + */ + public ConnectionTimeout(Duration timeout, Throwable cause) { + super(ConnectionTimeout.class.getSimpleName(), cause); + this.timeout = timeout; + } + } +} diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServerHasNotBeenStartedLocallyException.java b/src/main/java/io/appium/java_client/service/local/AppiumServerHasNotBeenStartedLocallyException.java index 664e6a602..9c0afb248 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServerHasNotBeenStartedLocallyException.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServerHasNotBeenStartedLocallyException.java @@ -16,16 +16,16 @@ package io.appium.java_client.service.local; - public class AppiumServerHasNotBeenStartedLocallyException extends RuntimeException { + public AppiumServerHasNotBeenStartedLocallyException(String message, Throwable cause) { + super(message, cause); + } - private static final long serialVersionUID = 1L; - - public AppiumServerHasNotBeenStartedLocallyException(String messege, Throwable t) { - super(messege, t); + public AppiumServerHasNotBeenStartedLocallyException(String message) { + super(message); } - public AppiumServerHasNotBeenStartedLocallyException(String messege) { - super(messege); + public AppiumServerHasNotBeenStartedLocallyException(Throwable cause) { + super(cause); } } From 36a865326abe316fb4a719b63724cb6a086034d8 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 14 Mar 2024 18:18:17 +0200 Subject: [PATCH 210/314] release: v9.2.0 (#2131) --- CHANGELOG.md | 13 +++++++++++++ README.md | 24 ++++++++++++------------ gradle.properties | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b3f9b0d5..62b862e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.2.0_ +- **[ENHANCEMENTS]** + - Incorporate poll delay mechanism into `AppiumFluentWait` [#2116](https://github.com/appium/java-client/pull/2116) (Closes [#2111](https://github.com/appium/java-client/pull/2111)) + - Make server startup error messages more useful [#2130](https://github.com/appium/java-client/pull/2130) +- **[BUG FIX]** + - Set correct geolocation coordinates of the current device [#2109](https://github.com/appium/java-client/pull/2109) (Fixes [#2108](https://github.com/appium/java-client/pull/2108)) + - Always release annotated element reference from the builder instance [#2128](https://github.com/appium/java-client/pull/2128) + - Cache dynamic proxy classes created by ByteBuddy [#2129](https://github.com/appium/java-client/pull/2129) (Fixes [#2119](https://github.com/appium/java-client/pull/2119)) +- **[DEPENDENCY CHANGE]** + - Bump SLF4J from `2.0.11` to `2.0.12` [#2115](https://github.com/appium/java-client/pull/2115) +- **[DOCUMENTATION]** + - Improve release steps [#2107](https://github.com/appium/java-client/pull/2107) + _9.1.0_ - **[ENHANCEMENTS]** - Introduce better constructor argument validation for the `AppiumFieldDecorator` class. [#2070](https://github.com/appium/java-client/pull/2070) diff --git a/README.md b/README.md index 08bf82124..a20c5218e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Follow the [v8 to v9 Migration Guide](./docs/v8-to-v9-migration-guide.md) in ord ## v7 to v8 Migration -Since version 8 Appium Java Client had several major changes, which might require to +Since version 8 Appium Java Client had several major changes, which might require to update your client code. Make sure to follow the [v7 to v8 Migration Guide](./docs/v7-to-v8-migration-guide.md) in order to streamline the migration process. @@ -21,7 +21,7 @@ in order to streamline the migration process. ### Stable -#### Maven +#### Maven Add the following to pom.xml: @@ -62,19 +62,19 @@ Add the following to pom.xml: ``` Add the dependency: - + ```xml com.github.appium java-client latest commit ID from master branch -``` +``` #### Gradle Add the JitPack repository to your build file. Add it in your root build.gradle at the end of repositories: - + ```groovy allprojects { repositories { @@ -85,7 +85,7 @@ allprojects { ``` Add the dependency: - + ```groovy dependencies { implementation 'com.github.appium:java-client:latest commit id from master branch' @@ -95,7 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client ---------------------------|----------------- - `9.1.0` | `4.17.0` + `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` @@ -126,7 +126,7 @@ Appium java client has dedicated classes to support the following Appium drivers - [Gecko](https://github.com/appium/appium-geckodriver): [GeckoDriver](src/main/java/io/appium/java_client/gecko/GeckoDriver.java) - [Mac2](https://github.com/appium/appium-mac2-driver): [Mac2Driver](src/main/java/io/appium/java_client/mac/Mac2Driver.java) -To automate other platforms that are not listed above you could use +To automate other platforms that are not listed above you could use [AppiumDriver](src/main/java/io/appium/java_client/AppiumDriver.java) or its custom derivatives. Appium java client is built on top of Selenium and implements same interfaces that the foundation @@ -225,7 +225,7 @@ try { Check the corresponding driver's READMEs to know the list of capabilities and features it supports. -You could find much more code examples by checking client's +You could find much more code examples by checking client's [unit and integration tests](src/test/java/io/appium/java_client). ## Troubleshooting @@ -235,7 +235,7 @@ You could find much more code examples by checking client's Appium Java client uses reflective access to private members of other modules to ensure proper functionality of several features, like Page Object model. If you get a runtime exception and `InaccessibleObjectException` is present in -the stacktrace, and your Java runtime is at version 16 or higher, then consider following +the stacktrace, and your Java runtime is at version 16 or higher, then consider following [Oracle's tutorial](https://docs.oracle.com/en/java/javase/16/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B) and/or checking [existing issues](https://github.com/appium/java-client/search?q=InaccessibleObjectException&type=issues) for possible solutions. Basically, the idea there would be to explicitly allow @@ -251,9 +251,9 @@ framework code rather than run separately by a script or manually. Depending on the way the server process is started it may or may not inherit the currently active shell environment. That is why you may still receive errors about variables presence even though these variables ar actually defined for your command line interpreter. -Again, there is no universal solution to that, as there are many ways to spin up a new +Again, there is no universal solution to that, as there are many ways to spin up a new server process. Consider checking the [Appium Environment Troubleshooting](docs/environment.md) -document for more information on how to debug and fix process environment issues. +document for more information on how to debug and fix process environment issues. ## Changelog diff --git a/gradle.properties b/gradle.properties index 16c8cc317..fb33c2c1a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.17.0 # Please increment the value in a release -appiumClient.version=9.1.0 +appiumClient.version=9.2.0 From c7dfd5d159b13dbf7d0d2e06811cc9d087592097 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:42:11 +0200 Subject: [PATCH 211/314] build(deps): Bump org.owasp.dependencycheck from 9.0.9 to 9.0.10 (#2133) Bumps org.owasp.dependencycheck from 9.0.9 to 9.0.10. --- 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 0eb9997c9..3e8bd75ab 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.9' + id 'org.owasp.dependencycheck' version '9.0.10' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 79c6c0ce87cabc8ca5cfd3f047083a43203f5915 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:09:57 +0200 Subject: [PATCH 212/314] build(deps): Bump org.projectlombok:lombok from 1.18.30 to 1.18.32 (#2135) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.30 to 1.18.32. - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.30...v1.18.32) --- 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 3e8bd75ab..1372dae1f 100644 --- a/build.gradle +++ b/build.gradle @@ -38,8 +38,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.30' - annotationProcessor 'org.projectlombok:lombok:1.18.30' + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' if (project.hasProperty("isCI")) { api "org.seleniumhq.selenium:selenium-api:${seleniumVersion}" From 75e726229c899014b3af0aacce047b095e353a24 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 26 Mar 2024 23:20:43 +0400 Subject: [PATCH 213/314] refactor: Replace private usages of Guava Collections API with Java Collections API (#2136) --- .../appium/java_client/ExecuteCDPCommand.java | 3 +- .../appium/java_client/InteractsWithApps.java | 85 +++++++------------ .../io/appium/java_client/MobileCommand.java | 13 +-- .../appium/java_client/MultiTouchAction.java | 19 ++--- .../io/appium/java_client/TouchAction.java | 56 +++++------- .../AndroidStartScreenRecordingOptions.java | 14 +-- .../AndroidInstallApplicationOptions.java | 17 ++-- .../AndroidRemoveApplicationOptions.java | 11 +-- .../AndroidTerminateApplicationOptions.java | 9 +- .../geolocation/AndroidGeoLocation.java | 24 +++--- .../android/nativekey/KeyEvent.java | 17 ++-- .../android/nativekey/PressesKey.java | 8 +- .../driverscripts/ScriptOptions.java | 12 +-- .../BaseComparisonOptions.java | 10 +-- .../FeaturesMatchingOptions.java | 15 ++-- .../OccurrenceMatchingOptions.java | 15 ++-- .../ios/IOSStartScreenRecordingOptions.java | 18 ++-- .../java_client/ios/IOSTouchAction.java | 6 +- .../mac/Mac2StartScreenRecordingOptions.java | 20 ++--- .../AppiumNewSessionCommandPayload.java | 4 +- .../java_client/remote/SupportsLocation.java | 6 +- .../BaseScreenRecordingOptions.java | 7 +- .../BaseStartScreenRecordingOptions.java | 13 ++- .../ScreenRecordingUploadOptions.java | 22 ++--- .../service/local/AppiumServiceBuilder.java | 4 +- .../WindowsStartScreenRecordingOptions.java | 20 ++--- 26 files changed, 201 insertions(+), 247 deletions(-) diff --git a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java index 5af5e1f83..8b9f18317 100644 --- a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java +++ b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import org.openqa.selenium.remote.Response; import javax.annotation.Nullable; @@ -44,7 +43,7 @@ default Map executeCdpCommand(String command, @Nullable Map) response.getValue()); + return Collections.unmodifiableMap((Map) response.getValue()); } /** diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index e254f6aa6..9fe25dc24 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -16,7 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.ApplicationState; import io.appium.java_client.appmanagement.BaseActivateApplicationOptions; import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; @@ -28,9 +27,8 @@ import javax.annotation.Nullable; import java.time.Duration; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Optional; import static io.appium.java_client.MobileCommand.ACTIVATE_APP; import static io.appium.java_client.MobileCommand.INSTALL_APP; @@ -40,6 +38,7 @@ import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; import static io.appium.java_client.MobileCommand.TERMINATE_APP; import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; @SuppressWarnings({"rawtypes", "unchecked"}) public interface InteractsWithApps extends ExecutesMethod, CanRememberExtensionPresence { @@ -63,23 +62,17 @@ default void installApp(String appPath) { default void installApp(String appPath, @Nullable BaseInstallApplicationOptions options) { final String extName = "mobile: installApp"; try { - Map args = ImmutableMap.builder() - .put("app", appPath) - .put("appPath", appPath) - .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) - .build(); + var args = new HashMap(); + args.put("app", appPath); + args.put("appPath", appPath); + ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback - Map args = ImmutableMap.builder() - .put("appPath", appPath) - .putAll(Optional.ofNullable(options).map( - opts -> Map.of("options", opts.build()) - ).orElseGet(Map::of)) - .build(); - CommandExecutionHelper.execute( - markExtensionAbsence(extName), Map.entry(INSTALL_APP, args) - ); + var args = new HashMap(); + args.put("appPath", appPath); + ofNullable(options).map(BaseOptions::build).ifPresent(opts -> args.put("options", opts)); + CommandExecutionHelper.execute(markExtensionAbsence(extName), Map.entry(INSTALL_APP, args)); } } @@ -153,22 +146,18 @@ default boolean removeApp(String bundleId) { default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOptions options) { final String extName = "mobile: removeApp"; try { - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .put("appId", bundleId) - .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) - .build(); + var args = new HashMap(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); return requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .putAll(Optional.ofNullable(options).map( - opts -> Map.of("options", opts.build()) - ).orElseGet(Map::of)) - .build(); + var args = new HashMap(); + args.put("bundleId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(opts -> args.put("options", opts)); //noinspection RedundantCast return requireNonNull( (Boolean) CommandExecutionHelper.execute( @@ -200,23 +189,17 @@ default void activateApp(String bundleId) { default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptions options) { final String extName = "mobile: activateApp"; try { - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .put("appId", bundleId) - .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) - .build(); + var args = new HashMap(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .putAll(Optional.ofNullable(options).map( - opts -> Map.of("options", opts.build()) - ).orElseGet(Map::of)) - .build(); - CommandExecutionHelper.execute( - markExtensionAbsence(extName), Map.entry(ACTIVATE_APP, args) - ); + var args = new HashMap(); + args.put("bundleId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(opts -> args.put("options", opts)); + CommandExecutionHelper.execute(markExtensionAbsence(extName), Map.entry(ACTIVATE_APP, args)); } } @@ -274,22 +257,18 @@ default boolean terminateApp(String bundleId) { default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplicationOptions options) { final String extName = "mobile: terminateApp"; try { - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .put("appId", bundleId) - .putAll(Optional.ofNullable(options).map(BaseOptions::build).orElseGet(Collections::emptyMap)) - .build(); + var args = new HashMap(); + args.put("bundleId", bundleId); + args.put("appId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(args::putAll); return requireNonNull( CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args) ); } catch (UnsupportedCommandException | InvalidArgumentException e) { // TODO: Remove the fallback - Map args = ImmutableMap.builder() - .put("bundleId", bundleId) - .putAll(Optional.ofNullable(options).map( - opts -> Map.of("options", opts.build()) - ).orElseGet(Map::of)) - .build(); + var args = new HashMap(); + args.put("bundleId", bundleId); + ofNullable(options).map(BaseOptions::build).ifPresent(opts -> args.put("options", opts)); //noinspection RedundantCast return requireNonNull( (Boolean) CommandExecutionHelper.execute( diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 6f7eafb34..029c1abb7 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -610,12 +611,12 @@ public static ImmutableMap prepareArguments(String[] params, public static Map.Entry> compareImagesCommand(ComparisonMode mode, byte[] img1Data, byte[] img2Data, @Nullable BaseComparisonOptions options) { - ImmutableMap.Builder argsBuilder = ImmutableMap.builder() - .put("mode", mode.toString()) - .put("firstImage", new String(img1Data, StandardCharsets.UTF_8)) - .put("secondImage", new String(img2Data, StandardCharsets.UTF_8)); - Optional.ofNullable(options).ifPresent(opts -> argsBuilder.put("options", options.build())); - return Map.entry(COMPARE_IMAGES, argsBuilder.build()); + var args = new HashMap(); + args.put("mode", mode.toString()); + args.put("firstImage", new String(img1Data, StandardCharsets.UTF_8)); + args.put("secondImage", new String(img2Data, StandardCharsets.UTF_8)); + Optional.ofNullable(options).ifPresent(opts -> args.put("options", options.build())); + return Map.entry(COMPARE_IMAGES, Collections.unmodifiableMap(args)); } /** diff --git a/src/main/java/io/appium/java_client/MultiTouchAction.java b/src/main/java/io/appium/java_client/MultiTouchAction.java index 0a133756e..d82b47b1f 100644 --- a/src/main/java/io/appium/java_client/MultiTouchAction.java +++ b/src/main/java/io/appium/java_client/MultiTouchAction.java @@ -16,8 +16,7 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableList; - +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -53,12 +52,12 @@ @Deprecated public class MultiTouchAction implements PerformsActions { - private ImmutableList.Builder actions; + private List actions; private PerformsTouchActions performsTouchActions; public MultiTouchAction(PerformsTouchActions performsTouchActions) { this.performsTouchActions = performsTouchActions; - actions = ImmutableList.builder(); + actions = new ArrayList<>(); } /** @@ -76,21 +75,19 @@ public MultiTouchAction add(TouchAction action) { * Perform the multi-touch action on the mobile performsTouchActions. */ public MultiTouchAction perform() { - List touchActions = actions.build(); - checkArgument(touchActions.size() > 0, + checkArgument(!actions.isEmpty(), "MultiTouch action must have at least one TouchAction added before it can be performed"); - if (touchActions.size() > 1) { + if (actions.size() > 1) { performsTouchActions.performMultiTouchAction(this); return this; } //android doesn't like having multi-touch actions with only a single TouchAction... - performsTouchActions.performTouchAction(touchActions.get(0)); + performsTouchActions.performTouchAction(actions.get(0)); return this; } protected Map> getParameters() { return Map.of("actions", - actions.build().stream().map(touchAction -> - touchAction.getParameters().get("actions")).collect(toList()) + actions.stream().map(touchAction -> touchAction.getParameters().get("actions")).collect(toList()) ); } @@ -100,7 +97,7 @@ protected Map> getParameters() { * @return this MultiTouchAction, for possible segmented-touches. */ protected MultiTouchAction clearActions() { - actions = ImmutableList.builder(); + actions = new ArrayList<>(); return this; } } diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index be76d1610..6f6621f0b 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -16,8 +16,6 @@ package io.appium.java_client; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.touch.ActionOptions; import io.appium.java_client.touch.LongPressOptions; import io.appium.java_client.touch.TapOptions; @@ -25,10 +23,12 @@ import io.appium.java_client.touch.offset.ElementOption; import io.appium.java_client.touch.offset.PointOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import static com.google.common.collect.ImmutableList.builder; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -54,12 +54,12 @@ @Deprecated public class TouchAction> implements PerformsActions { - protected ImmutableList.Builder parameterBuilder; + protected List parameters; private PerformsTouchActions performsTouchActions; public TouchAction(PerformsTouchActions performsTouchActions) { this.performsTouchActions = requireNonNull(performsTouchActions); - parameterBuilder = builder(); + parameters = new ArrayList<>(); } /** @@ -69,7 +69,7 @@ public TouchAction(PerformsTouchActions performsTouchActions) { * @return this TouchAction, for chaining. */ public T press(PointOption pressOptions) { - parameterBuilder.add(new ActionParameter("press", pressOptions)); + parameters.add(new ActionParameter("press", pressOptions)); //noinspection unchecked return (T) this; } @@ -80,8 +80,7 @@ public T press(PointOption pressOptions) { * @return this TouchAction, for chaining. */ public T release() { - ActionParameter action = new ActionParameter("release"); - parameterBuilder.add(action); + parameters.add(new ActionParameter("release")); //noinspection unchecked return (T) this; } @@ -98,8 +97,7 @@ public T release() { * @return this TouchAction, for chaining. */ public T moveTo(PointOption moveToOptions) { - ActionParameter action = new ActionParameter("moveTo", moveToOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("moveTo", moveToOptions)); return (T) this; } @@ -110,8 +108,7 @@ public T moveTo(PointOption moveToOptions) { * @return this TouchAction, for chaining. */ public T tap(TapOptions tapOptions) { - ActionParameter action = new ActionParameter("tap", tapOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("tap", tapOptions)); return (T) this; } @@ -122,8 +119,7 @@ public T tap(TapOptions tapOptions) { * @return this TouchAction, for chaining. */ public T tap(PointOption tapOptions) { - ActionParameter action = new ActionParameter("tap", tapOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("tap", tapOptions)); return (T) this; } @@ -133,8 +129,7 @@ public T tap(PointOption tapOptions) { * @return this TouchAction, for chaining. */ public T waitAction() { - ActionParameter action = new ActionParameter("wait"); - parameterBuilder.add(action); + parameters.add(new ActionParameter("wait")); //noinspection unchecked return (T) this; } @@ -146,8 +141,7 @@ public T waitAction() { * @return this TouchAction, for chaining. */ public T waitAction(WaitOptions waitOptions) { - ActionParameter action = new ActionParameter("wait", waitOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("wait", waitOptions)); //noinspection unchecked return (T) this; } @@ -159,8 +153,7 @@ public T waitAction(WaitOptions waitOptions) { * @return this TouchAction, for chaining. */ public T longPress(LongPressOptions longPressOptions) { - ActionParameter action = new ActionParameter("longPress", longPressOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("longPress", longPressOptions)); //noinspection unchecked return (T) this; } @@ -172,8 +165,7 @@ public T longPress(LongPressOptions longPressOptions) { * @return this TouchAction, for chaining. */ public T longPress(PointOption longPressOptions) { - ActionParameter action = new ActionParameter("longPress", longPressOptions); - parameterBuilder.add(action); + parameters.add(new ActionParameter("longPress", longPressOptions)); //noinspection unchecked return (T) this; } @@ -182,8 +174,7 @@ public T longPress(PointOption longPressOptions) { * Cancel this action, if it was partially completed by the performsTouchActions. */ public void cancel() { - ActionParameter action = new ActionParameter("cancel"); - parameterBuilder.add(action); + parameters.add(new ActionParameter("cancel")); this.perform(); } @@ -205,7 +196,7 @@ public T perform() { */ protected Map> getParameters() { return Map.of("actions", - parameterBuilder.build().stream().map(ActionParameter::getParameterMap).collect(toList()) + parameters.stream().map(ActionParameter::getParameterMap).collect(toList()) ); } @@ -215,7 +206,7 @@ protected Map> getParameters() { * @return this TouchAction, for possible segmented-touches. */ protected T clearParameters() { - parameterBuilder = builder(); + parameters = new ArrayList<>(); //noinspection unchecked return (T) this; } @@ -224,26 +215,25 @@ protected T clearParameters() { * Just holds values to eventually return the parameters required for the mjsonwp. */ protected class ActionParameter { - private String actionName; - private ImmutableMap.Builder optionsBuilder; + private final String actionName; + private final Map options; public ActionParameter(String actionName) { this.actionName = actionName; - optionsBuilder = ImmutableMap.builder(); + options = new HashMap<>(); } public ActionParameter(String actionName, ActionOptions opts) { + this(actionName); requireNonNull(opts); - this.actionName = actionName; - optionsBuilder = ImmutableMap.builder(); //noinspection unchecked - optionsBuilder.putAll(opts.build()); + options.putAll(opts.build()); } public Map getParameterMap() { return Map.of( "action", actionName, - "options", optionsBuilder.build() + "options", Collections.unmodifiableMap(options) ); } } 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 14c575208..dd6ee2b2f 100644 --- a/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/android/AndroidStartScreenRecordingOptions.java @@ -16,11 +16,12 @@ package io.appium.java_client.android; -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.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -105,11 +106,10 @@ public AndroidStartScreenRecordingOptions withTimeLimit(Duration timeLimit) { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(super.build()); - ofNullable(bitRate).ifPresent(x -> builder.put("bitRate", x)); - ofNullable(videoSize).ifPresent(x -> builder.put("videoSize", x)); - ofNullable(isBugReportEnabled).ifPresent(x -> builder.put("bugReport", x)); - return builder.build(); + var map = new HashMap<>(super.build()); + ofNullable(bitRate).ifPresent(x -> map.put("bitRate", x)); + ofNullable(videoSize).ifPresent(x -> map.put("videoSize", x)); + ofNullable(isBugReportEnabled).ifPresent(x -> map.put("bugReport", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java index 27ce3e4a6..216641b84 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java @@ -16,10 +16,11 @@ package io.appium.java_client.android.appmanagement; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -138,12 +139,12 @@ public AndroidInstallApplicationOptions withGrantPermissionsDisabled() { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(replace).ifPresent(x -> builder.put("replace", x)); - ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); - ofNullable(allowTestPackages).ifPresent(x -> builder.put("allowTestPackages", x)); - ofNullable(useSdcard).ifPresent(x -> builder.put("useSdcard", x)); - ofNullable(grantPermissions).ifPresent(x -> builder.put("grantPermissions", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(replace).ifPresent(x -> map.put("replace", x)); + ofNullable(timeout).ifPresent(x -> map.put("timeout", x.toMillis())); + ofNullable(allowTestPackages).ifPresent(x -> map.put("allowTestPackages", x)); + ofNullable(useSdcard).ifPresent(x -> map.put("useSdcard", x)); + ofNullable(grantPermissions).ifPresent(x -> map.put("grantPermissions", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java index 9783d9fda..fe68a0073 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java @@ -16,10 +16,11 @@ package io.appium.java_client.android.appmanagement; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -68,9 +69,9 @@ public AndroidRemoveApplicationOptions withKeepDataDisabled() { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); - ofNullable(keepData).ifPresent(x -> builder.put("keepData", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(timeout).ifPresent(x -> map.put("timeout", x.toMillis())); + ofNullable(keepData).ifPresent(x -> map.put("keepData", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java index c9a0a02d8..b683c5a8f 100644 --- a/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java @@ -16,10 +16,11 @@ package io.appium.java_client.android.appmanagement; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -45,8 +46,8 @@ public AndroidTerminateApplicationOptions withTimeout(Duration timeout) { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(timeout).ifPresent(x -> builder.put("timeout", x.toMillis())); - return builder.build(); + var map = new HashMap(); + ofNullable(timeout).ifPresent(x -> map.put("timeout", x.toMillis())); + return Collections.unmodifiableMap(map); } } 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 7cd53e698..f8e2d24c7 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 @@ -16,8 +16,8 @@ package io.appium.java_client.android.geolocation; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -110,16 +110,14 @@ public AndroidGeoLocation withSpeed(double speed) { * @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).ifPresent(x -> builder.put("altitude", x)); - ofNullable(satellites).ifPresent(x -> builder.put("satellites", x)); - ofNullable(speed).ifPresent(x -> builder.put("speed", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(longitude).map(x -> map.put("longitude", x)).orElseThrow(() -> new IllegalArgumentException( + "A valid 'longitude' must be provided")); + ofNullable(latitude).map(x -> map.put("latitude", x)).orElseThrow(() -> new IllegalArgumentException( + "A valid 'latitude' must be provided")); + ofNullable(altitude).ifPresent(x -> map.put("altitude", x)); + ofNullable(satellites).ifPresent(x -> map.put("satellites", x)); + ofNullable(speed).ifPresent(x -> map.put("speed", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java index 8bf0a99e2..3d695e4c3 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java +++ b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java @@ -16,8 +16,8 @@ package io.appium.java_client.android.nativekey; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -82,13 +82,12 @@ public KeyEvent withFlag(KeyEventFlag keyEventFlag) { * @throws IllegalStateException if key code is not set */ public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - final int keyCode = ofNullable(this.keyCode) - .orElseThrow(() -> new IllegalStateException("The key code must be set")); - builder.put("keycode", keyCode); - ofNullable(this.metaState).ifPresent(x -> builder.put("metastate", x)); - ofNullable(this.flags).ifPresent(x -> builder.put("flags", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(this.keyCode).map(x -> map.put("keycode", x)).orElseThrow(() -> new IllegalStateException( + "The key code must be set")); + ofNullable(this.metaState).ifPresent(x -> map.put("metastate", x)); + ofNullable(this.flags).ifPresent(x -> map.put("flags", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java index 641087050..af633a1d9 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java +++ b/src/main/java/io/appium/java_client/android/nativekey/PressesKey.java @@ -16,12 +16,12 @@ package io.appium.java_client.android.nativekey; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import org.openqa.selenium.UnsupportedCommandException; +import java.util.HashMap; import java.util.Map; import static io.appium.java_client.MobileCommand.LONG_PRESS_KEY_CODE; @@ -55,10 +55,8 @@ default void pressKey(KeyEvent keyEvent) { default void longPressKey(KeyEvent keyEvent) { final String extName = "mobile: pressKey"; try { - Map args = ImmutableMap.builder() - .putAll(keyEvent.build()) - .put("isLongPress", true) - .build(); + var args = new HashMap<>(keyEvent.build()); + args.put("isLongPress", true); CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); } catch (UnsupportedCommandException e) { // TODO: Remove the fallback diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java index 11c4f650a..41b5cd78e 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -16,8 +16,8 @@ package io.appium.java_client.driverscripts; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Objects.requireNonNull; @@ -58,9 +58,9 @@ public ScriptOptions withTimeout(long timeoutMs) { * @return The map containing the provided options */ public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(scriptType).ifPresent(x -> builder.put("type", x.name().toLowerCase())); - ofNullable(timeoutMs).ifPresent(x -> builder.put("timeout", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(scriptType).ifPresent(x -> map.put("type", x.name().toLowerCase())); + ofNullable(timeoutMs).ifPresent(x -> map.put("timeout", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java index b91974009..bd20f884a 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/BaseComparisonOptions.java @@ -16,8 +16,8 @@ package io.appium.java_client.imagecomparison; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -45,8 +45,8 @@ public T withEnabledVisualization() { * @return comparison options mapping. */ public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(visualize).ifPresent(x -> builder.put("visualize", x)); - return builder.build(); + var map = new HashMap(); + ofNullable(visualize).ifPresent(x -> map.put("visualize", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java index 42e4cd976..3fd56517c 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingOptions.java @@ -16,8 +16,8 @@ package io.appium.java_client.imagecomparison; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -68,11 +68,10 @@ public FeaturesMatchingOptions withGoodMatchesFactor(int factor) { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(super.build()); - ofNullable(detectorName).ifPresent(x -> builder.put("detectorName", x)); - ofNullable(matchFunc).ifPresent(x -> builder.put("matchFunc", x)); - ofNullable(goodMatchesFactor).ifPresent(x -> builder.put("goodMatchesFactor", x)); - return builder.build(); + var map = new HashMap<>(super.build()); + ofNullable(detectorName).ifPresent(x -> map.put("detectorName", x)); + ofNullable(matchFunc).ifPresent(x -> map.put("matchFunc", x)); + ofNullable(goodMatchesFactor).ifPresent(x -> map.put("goodMatchesFactor", x)); + return Collections.unmodifiableMap(map); } } 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 d75e1d7e1..314a237dc 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingOptions.java @@ -16,8 +16,8 @@ package io.appium.java_client.imagecomparison; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -66,11 +66,10 @@ public OccurrenceMatchingOptions withMatchNeighbourThreshold(int threshold) { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(super.build()); - ofNullable(threshold).ifPresent(x -> builder.put("threshold", x)); - ofNullable(matchNeighbourThreshold).ifPresent(x -> builder.put("matchNeighbourThreshold", x)); - ofNullable(multiple).ifPresent(x -> builder.put("multiple", x)); - return builder.build(); + var map = new HashMap<>(super.build()); + ofNullable(threshold).ifPresent(x -> map.put("threshold", x)); + ofNullable(matchNeighbourThreshold).ifPresent(x -> map.put("matchNeighbourThreshold", x)); + ofNullable(multiple).ifPresent(x -> map.put("multiple", x)); + return Collections.unmodifiableMap(map); } } 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 b0673a5e0..a608cae0f 100644 --- a/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/ios/IOSStartScreenRecordingOptions.java @@ -16,11 +16,12 @@ package io.appium.java_client.ios; -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.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Objects.requireNonNull; @@ -133,13 +134,12 @@ public IOSStartScreenRecordingOptions withVideoFilters(String filters) { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(super.build()); - ofNullable(videoType).map(x -> builder.put("videoType", x)); - ofNullable(videoQuality).map(x -> builder.put("videoQuality", x)); - ofNullable(videoScale).map(x -> builder.put("videoScale", x)); - ofNullable(videoFilters).map(x -> builder.put("videoFilters", x)); - ofNullable(fps).map(x -> builder.put("videoFps", x)); - return builder.build(); + var map = new HashMap<>(super.build()); + ofNullable(videoType).map(x -> map.put("videoType", x)); + ofNullable(videoQuality).map(x -> map.put("videoQuality", x)); + ofNullable(videoScale).map(x -> map.put("videoScale", x)); + ofNullable(videoFilters).map(x -> map.put("videoFilters", x)); + ofNullable(fps).map(x -> map.put("videoFps", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/ios/IOSTouchAction.java b/src/main/java/io/appium/java_client/ios/IOSTouchAction.java index 01a98f9e5..aca87955d 100644 --- a/src/main/java/io/appium/java_client/ios/IOSTouchAction.java +++ b/src/main/java/io/appium/java_client/ios/IOSTouchAction.java @@ -48,9 +48,7 @@ public IOSTouchAction(PerformsTouchActions performsTouchActions) { * @return self-reference */ public IOSTouchAction doubleTap(PointOption doubleTapOption) { - ActionParameter action = new ActionParameter("doubleTap", - doubleTapOption); - parameterBuilder.add(action); + parameters.add(new ActionParameter("doubleTap", doubleTapOption)); return this; } @@ -61,7 +59,7 @@ public IOSTouchAction doubleTap(PointOption doubleTapOption) { * @return this TouchAction, for chaining. */ public IOSTouchAction press(IOSPressOptions pressOptions) { - parameterBuilder.add(new ActionParameter("press", pressOptions)); + parameters.add(new ActionParameter("press", pressOptions)); return this; } } diff --git a/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java index 45c573126..342598b62 100644 --- a/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/mac/Mac2StartScreenRecordingOptions.java @@ -16,10 +16,11 @@ 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.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -139,14 +140,13 @@ public Mac2StartScreenRecordingOptions withTimeLimit(Duration 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(); + var map = new HashMap<>(super.build()); + ofNullable(fps).map(x -> map.put("fps", x)); + ofNullable(preset).map(x -> map.put("preset", x)); + ofNullable(videoFilter).map(x -> map.put("videoFilter", x)); + ofNullable(captureClicks).map(x -> map.put("captureClicks", x)); + ofNullable(captureCursor).map(x -> map.put("captureCursor", x)); + ofNullable(deviceId).map(x -> map.put("deviceId", x)); + return Collections.unmodifiableMap(map); } } diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java index 5f7655c2d..63fc663a5 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -16,7 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.remote.options.BaseOptions; import org.openqa.selenium.Capabilities; import org.openqa.selenium.internal.Require; @@ -24,6 +23,7 @@ import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; @@ -37,7 +37,7 @@ public class AppiumNewSessionCommandPayload extends CommandPayload { private static Map makeW3CSafe(Capabilities possiblyInvalidCapabilities) { return Require.nonNull("Capabilities", possiblyInvalidCapabilities) .asMap().entrySet().stream() - .collect(ImmutableMap.toImmutableMap( + .collect(Collectors.toUnmodifiableMap( entry -> BaseOptions.toW3cName(entry.getKey()), Map.Entry::getValue )); diff --git a/src/main/java/io/appium/java_client/remote/SupportsLocation.java b/src/main/java/io/appium/java_client/remote/SupportsLocation.java index 45d81f36c..91e12f0fa 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsLocation.java +++ b/src/main/java/io/appium/java_client/remote/SupportsLocation.java @@ -16,7 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.collect.ImmutableMap; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.MobileCommand; @@ -26,6 +25,7 @@ import org.openqa.selenium.html5.LocationContext; import org.openqa.selenium.remote.html5.RemoteLocationContext; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -86,10 +86,10 @@ default void setLocation(Location location) { * @param location A {@link Location} containing the new location information. */ default void setLocation(io.appium.java_client.Location location) { - ImmutableMap.Builder locationParameters = ImmutableMap.builder(); + var locationParameters = new HashMap(); locationParameters.put("latitude", location.getLatitude()); locationParameters.put("longitude", location.getLongitude()); Optional.ofNullable(location.getAltitude()).ifPresent(altitude -> locationParameters.put("altitude", altitude)); - execute(MobileCommand.SET_LOCATION, Map.of("location", locationParameters.build())); + execute(MobileCommand.SET_LOCATION, Map.of("location", locationParameters)); } } 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 e4e3797ad..fd75dc2d6 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseScreenRecordingOptions.java @@ -16,8 +16,6 @@ package io.appium.java_client.screenrecording; -import com.google.common.collect.ImmutableMap; - import java.util.Map; import static java.util.Objects.requireNonNull; @@ -46,9 +44,6 @@ protected T withUploadOptions(ScreenRecordingUploadOptions uploadOptions) { * @return arguments mapping. */ public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - //noinspection unchecked - ofNullable(uploadOptions).map(x -> builder.putAll(x.build())); - return builder.build(); + return ofNullable(uploadOptions).map(ScreenRecordingUploadOptions::build).orElseGet(Map::of); } } diff --git a/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java index 1ce5af766..55716b622 100644 --- a/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/BaseStartScreenRecordingOptions.java @@ -16,9 +16,9 @@ package io.appium.java_client.screenrecording; -import com.google.common.collect.ImmutableMap; - import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Objects.requireNonNull; @@ -66,10 +66,9 @@ public T disableForcedRestart() { @Override public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(super.build()); - ofNullable(timeLimit).map(x -> builder.put("timeLimit", x.getSeconds())); - ofNullable(forceRestart).map(x -> builder.put("forceRestart", x)); - return builder.build(); + var map = new HashMap<>(super.build()); + ofNullable(timeLimit).map(x -> map.put("timeLimit", x.getSeconds())); + ofNullable(forceRestart).map(x -> map.put("forceRestart", x)); + return Collections.unmodifiableMap(map); } } 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 97115a253..e018b47ea 100644 --- a/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java +++ b/src/main/java/io/appium/java_client/screenrecording/ScreenRecordingUploadOptions.java @@ -16,8 +16,8 @@ package io.appium.java_client.screenrecording; -import com.google.common.collect.ImmutableMap; - +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Objects.requireNonNull; @@ -123,14 +123,14 @@ public ScreenRecordingUploadOptions withHeaders(Map headers) { * @return arguments mapping. */ public Map build() { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - ofNullable(remotePath).map(x -> builder.put("remotePath", x)); - 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(); + var map = new HashMap(); + ofNullable(remotePath).map(x -> map.put("remotePath", x)); + ofNullable(user).map(x -> map.put("user", x)); + ofNullable(pass).map(x -> map.put("pass", x)); + ofNullable(method).map(x -> map.put("method", x)); + ofNullable(fileFieldName).map(x -> map.put("fileFieldName", x)); + ofNullable(formFields).map(x -> map.put("formFields", x)); + ofNullable(headers).map(x -> map.put("headers", x)); + return Collections.unmodifiableMap(map); } } 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 a8690c78b..ad3729e77 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 @@ -16,7 +16,6 @@ package io.appium.java_client.service.local; -import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.appium.java_client.android.options.context.SupportsChromedriverExecutableOption; @@ -42,6 +41,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -395,7 +395,7 @@ protected List createArgs() { argList.add(capabilitiesToCmdlineArg()); } - return new ImmutableList.Builder().addAll(argList).build(); + return Collections.unmodifiableList(argList); } @Override diff --git a/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java b/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java index ff90a08f2..8f5d5bc72 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java +++ b/src/main/java/io/appium/java_client/windows/WindowsStartScreenRecordingOptions.java @@ -16,10 +16,11 @@ 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.Collections; +import java.util.HashMap; import java.util.Map; import static java.util.Optional.ofNullable; @@ -138,14 +139,13 @@ public WindowsStartScreenRecordingOptions withTimeLimit(Duration 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(); + var map = new HashMap<>(super.build()); + ofNullable(fps).map(x -> map.put("fps", x)); + ofNullable(preset).map(x -> map.put("preset", x)); + ofNullable(videoFilter).map(x -> map.put("videoFilter", x)); + ofNullable(captureClicks).map(x -> map.put("captureClicks", x)); + ofNullable(captureCursor).map(x -> map.put("captureCursor", x)); + ofNullable(audioInput).map(x -> map.put("audioInput", x)); + return Collections.unmodifiableMap(map); } } From a60a7cc9b48f0f08e83c85a3b38dd7b4b1c374a3 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 27 Mar 2024 09:00:10 +0400 Subject: [PATCH 214/314] build: Restore execution of lost tests (#2139) --- build.gradle | 2 ++ src/test/java/io/appium/java_client/TestUtils.java | 7 ++++++- .../pagefactory_tests/DesktopBrowserCompatibilityTest.java | 3 ++- .../appium/java_client/pagefactory_tests/TimeoutTest.java | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 1372dae1f..0b3860f17 100644 --- a/build.gradle +++ b/build.gradle @@ -207,6 +207,8 @@ test { includeTestsMatching 'io.appium.java_client.drivers.options.*' includeTestsMatching 'io.appium.java_client.events.*' includeTestsMatching 'io.appium.java_client.internal.*' + includeTestsMatching 'io.appium.java_client.pagefactory_tests.DesktopBrowserCompatibilityTest' + includeTestsMatching 'io.appium.java_client.pagefactory_tests.TimeoutTest' includeTestsMatching 'io.appium.java_client.proxy.*' includeTestsMatching 'io.appium.java_client.remote.*' includeTestsMatching 'io.appium.java_client.touch.*' diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index 6a968a4e1..aaf254f75 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -10,6 +10,7 @@ import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; +import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; import java.nio.file.Files; @@ -35,7 +36,11 @@ public static Path resourcePathToLocalPath(String resourcePath) { if (url == null) { throw new IllegalArgumentException(String.format("Cannot find the '%s' resource", resourcePath)); } - return Paths.get(url.getPath()); + try { + return Paths.get(url.toURI()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } } public static String resourceAsString(String resourcePath) { 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 aeefba86e..0a890dc67 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 @@ -26,6 +26,7 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBys; import org.openqa.selenium.support.PageFactory; @@ -58,7 +59,7 @@ public class DesktopBrowserCompatibilityTest { } @Test public void chromeTest() { - WebDriver driver = new ChromeDriver(); + WebDriver driver = new ChromeDriver(new ChromeOptions().addArguments("--headless=new")); try { PageFactory.initElements(new AppiumFieldDecorator(driver, ofSeconds(15)), this); driver.get(helloAppiumHtml().toUri().toString()); diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java index a9d44562d..32d23c874 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/TimeoutTest.java @@ -25,6 +25,7 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.support.FindAll; import org.openqa.selenium.support.FindBy; @@ -86,7 +87,7 @@ public static void beforeAll() { * The setting up. */ @BeforeEach public void setUp() { - driver = new ChromeDriver(); + driver = new ChromeDriver(new ChromeOptions().addArguments("--headless=new")); timeOutDuration = DEFAULT_WAITING_TIMEOUT; initElements(new AppiumFieldDecorator(driver, timeOutDuration), this); } From 60d5f24828baadd7c6edbf97652e2dccef08189c Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 27 Mar 2024 23:51:21 +0400 Subject: [PATCH 215/314] chore: Bump minimum Selenium version to `4.19.0` (#2142) --- README.md | 1 + gradle.properties | 2 +- .../remote/AppiumProtocolHandshake.java | 50 ++++++------------- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a20c5218e..b0ee499da 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client ---------------------------|----------------- + | `4.19.0` `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` diff --git a/gradle.properties b/gradle.properties index fb33c2c1a..306b374dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.17.0 +selenium.version=4.19.0 # Please increment the value in a release appiumClient.version=9.2.0 diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 0f42b9a51..f92a3632d 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -16,8 +16,6 @@ package io.appium.java_client.remote; -import com.google.common.io.CountingOutputStream; -import com.google.common.io.FileBackedOutputStream; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.SessionNotCreatedException; @@ -28,21 +26,17 @@ import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.NewSessionPayload; import org.openqa.selenium.remote.ProtocolHandshake; +import org.openqa.selenium.remote.http.Contents; import org.openqa.selenium.remote.http.HttpHandler; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; +import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -import java.util.function.Supplier; import java.util.stream.Stream; -import static java.nio.charset.StandardCharsets.UTF_8; - @SuppressWarnings("UnstableApiUsage") public class AppiumProtocolHandshake extends ProtocolHandshake { private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable destination) { @@ -108,34 +102,22 @@ public Result createSession(HttpHandler client, Command command) throws IOExcept } @Override - public Either createSession( - HttpHandler client, NewSessionPayload payload) throws IOException { - int threshold = (int) Math.min(Runtime.getRuntime().freeMemory() / 10, Integer.MAX_VALUE); - FileBackedOutputStream os = new FileBackedOutputStream(threshold, true); + public Either createSession(HttpHandler client, NewSessionPayload payload) { - try (CountingOutputStream counter = new CountingOutputStream(os); - Writer writer = new OutputStreamWriter(counter, UTF_8)) { - writeJsonPayload(payload, writer); + StringWriter stringWriter = new StringWriter(); + writeJsonPayload(payload, stringWriter); - Supplier contentSupplier = () -> { - try { - return os.asByteSource().openBufferedStream(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - try { - Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod( - "createSession", HttpHandler.class, Supplier.class, long.class - ); - createSessionMethod.setAccessible(true); - //noinspection unchecked - return (Either) createSessionMethod.invoke( - this, client, contentSupplier, counter.getCount() - ); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new WebDriverException(e); - } + try { + Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod( + "createSession", HttpHandler.class, Contents.Supplier.class + ); + createSessionMethod.setAccessible(true); + //noinspection unchecked + return (Either) createSessionMethod.invoke( + this, client, Contents.utf8String(stringWriter.toString()) + ); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new WebDriverException(e); } } } From b510eaa3c283aec16d7b303c90aa2685dcfe401c Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 00:38:29 +0400 Subject: [PATCH 216/314] build: bump Gradle from `8.5` to `8.7` (#2140) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 0b3860f17..2956300e8 100644 --- a/build.gradle +++ b/build.gradle @@ -188,7 +188,7 @@ signing { } wrapper { - gradleVersion = '8.5' + gradleVersion = '8.7' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 zcmX7vV`H6d(}mmEwr$(CZQE$vU^m*aZQE(=WXEZ2+l}qF_w)XN>&rEBu9;)4>7EB0 zo(HR^Mh47P)@z^^pH!4#b(O8!;$>N+S+v5K5f8RrQ+Qv0_oH#e!pI2>yt4ij>fI9l zW&-hsVAQg%dpn3NRy$kb_vbM2sr`>bZ48b35m{D=OqX;p8A${^Dp|W&J5mXvUl#_I zN!~GCBUzj~C%K?<7+UZ_q|L)EGG#_*2Zzko-&Kck)Qd2%CpS3{P1co1?$|Sj1?E;PO z7alI9$X(MDly9AIEZ-vDLhpAKd1x4U#w$OvBtaA{fW9)iD#|AkMrsSaNz(69;h1iM1#_ z?u?O_aKa>vk=j;AR&*V-p3SY`CI}Uo%eRO(Dr-Te<99WQhi>y&l%UiS%W2m(d#woD zW?alFl75!1NiUzVqgqY98fSQNjhX3uZ&orB08Y*DFD;sjIddWoJF;S_@{Lx#SQk+9 zvSQ-620z0D7cy8-u_7u?PqYt?R0m2k%PWj%V(L|MCO(@3%l&pzEy7ijNv(VXU9byn z@6=4zL|qk*7!@QWd9imT9i%y}1#6+%w=s%WmsHbw@{UVc^?nL*GsnACaLnTbr9A>B zK)H-$tB`>jt9LSwaY+4!F1q(YO!E7@?SX3X-Ug4r($QrmJnM8m#;#LN`kE>?<{vbCZbhKOrMpux zTU=02hy${;n&ikcP8PqufhT9nJU>s;dyl;&~|Cs+o{9pCu{cRF+0{iyuH~6=tIZXVd zR~pJBC3Hf-g%Y|bhTuGyd~3-sm}kaX5=T?p$V?48h4{h2;_u{b}8s~Jar{39PnL7DsXpxcX#3zx@f9K zkkrw9s2*>)&=fLY{=xeIYVICff2Id5cc*~l7ztSsU@xuXYdV1(lLGZ5)?mXyIDf1- zA7j3P{C5s?$Y-kg60&XML*y93zrir8CNq*EMx)Kw)XA(N({9t-XAdX;rjxk`OF%4-0x?ne@LlBQMJe5+$Ir{Oj`@#qe+_-z!g5qQ2SxKQy1ex_x^Huj%u+S@EfEPP-70KeL@7@PBfadCUBt%`huTknOCj{ z;v?wZ2&wsL@-iBa(iFd)7duJTY8z-q5^HR-R9d*ex2m^A-~uCvz9B-1C$2xXL#>ow z!O<5&jhbM&@m=l_aW3F>vjJyy27gY}!9PSU3kITbrbs#Gm0gD?~Tub8ZFFK$X?pdv-%EeopaGB#$rDQHELW!8bVt`%?&>0 zrZUQ0!yP(uzVK?jWJ8^n915hO$v1SLV_&$-2y(iDIg}GDFRo!JzQF#gJoWu^UW0#? z*OC-SPMEY!LYY*OO95!sv{#-t!3Z!CfomqgzFJld>~CTFKGcr^sUai5s-y^vI5K={ z)cmQthQuKS07e8nLfaIYQ5f}PJQqcmokx?%yzFH*`%k}RyXCt1Chfv5KAeMWbq^2MNft;@`hMyhWg50(!jdAn;Jyx4Yt)^^DVCSu?xRu^$*&&=O6#JVShU_N3?D)|$5pyP8A!f)`| z>t0k&S66T*es5(_cs>0F=twYJUrQMqYa2HQvy)d+XW&rai?m;8nW9tL9Ivp9qi2-` zOQM<}D*g`28wJ54H~1U!+)vQh)(cpuf^&8uteU$G{9BUhOL| zBX{5E1**;hlc0ZAi(r@)IK{Y*ro_UL8Ztf8n{Xnwn=s=qH;fxkK+uL zY)0pvf6-iHfX+{F8&6LzG;&d%^5g`_&GEEx0GU=cJM*}RecV-AqHSK@{TMir1jaFf&R{@?|ieOUnmb?lQxCN!GnAqcii9$ z{a!Y{Vfz)xD!m2VfPH=`bk5m6dG{LfgtA4ITT?Sckn<92rt@pG+sk>3UhTQx9ywF3 z=$|RgTN<=6-B4+UbYWxfQUOe8cmEDY3QL$;mOw&X2;q9x9qNz3J97)3^jb zdlzkDYLKm^5?3IV>t3fdWwNpq3qY;hsj=pk9;P!wVmjP|6Dw^ez7_&DH9X33$T=Q{>Nl zv*a*QMM1-2XQ)O=3n@X+RO~S`N13QM81^ZzljPJIFBh%x<~No?@z_&LAl)ap!AflS zb{yFXU(Uw(dw%NR_l7%eN2VVX;^Ln{I1G+yPQr1AY+0MapBnJ3k1>Zdrw^3aUig*! z?xQe8C0LW;EDY(qe_P!Z#Q^jP3u$Z3hQpy^w7?jI;~XTz0ju$DQNc4LUyX}+S5zh> zGkB%~XU+L?3pw&j!i|x6C+RyP+_XYNm9`rtHpqxvoCdV_MXg847oHhYJqO+{t!xxdbsw4Ugn($Cwkm^+36&goy$vkaFs zrH6F29eMPXyoBha7X^b+N*a!>VZ<&Gf3eeE+Bgz7PB-6X7 z_%2M~{sTwC^iQVjH9#fVa3IO6E4b*S%M;#WhHa^L+=DP%arD_`eW5G0<9Tk=Ci?P@ z6tJXhej{ZWF=idj32x7dp{zmQY;;D2*11&-(~wifGXLmD6C-XR=K3c>S^_+x!3OuB z%D&!EOk;V4Sq6eQcE{UEDsPMtED*;qgcJU^UwLwjE-Ww54d73fQ`9Sv%^H>juEKmxN+*aD=0Q+ZFH1_J(*$~9&JyUJ6!>(Nj zi3Z6zWC%Yz0ZjX>thi~rH+lqv<9nkI3?Ghn7@!u3Ef){G(0Pvwnxc&(YeC=Kg2-7z zr>a^@b_QClXs?Obplq@Lq-l5>W);Y^JbCYk^n8G`8PzCH^rnY5Zk-AN6|7Pn=oF(H zxE#8LkI;;}K7I^UK55Z)c=zn7OX_XVgFlEGSO}~H^y|wd7piw*b1$kA!0*X*DQ~O` z*vFvc5Jy7(fFMRq>XA8Tq`E>EF35{?(_;yAdbO8rrmrlb&LceV%;U3haVV}Koh9C| zTZnR0a(*yN^Hp9u*h+eAdn)d}vPCo3k?GCz1w>OOeme(Mbo*A7)*nEmmUt?eN_vA; z=~2}K_}BtDXJM-y5fn^v>QQo+%*FdZQFNz^j&rYhmZHgDA-TH47#Wjn_@iH4?6R{J z%+C8LYIy>{3~A@|y4kN8YZZp72F8F@dOZWp>N0-DyVb4UQd_t^`P)zsCoygL_>>x| z2Hyu7;n(4G&?wCB4YVUIVg0K!CALjRsb}&4aLS|}0t`C}orYqhFe7N~h9XQ_bIW*f zGlDCIE`&wwyFX1U>}g#P0xRRn2q9%FPRfm{-M7;}6cS(V6;kn@6!$y06lO>8AE_!O z{|W{HEAbI0eD$z9tQvWth7y>qpTKQ0$EDsJkQxAaV2+gE28Al8W%t`Pbh zPl#%_S@a^6Y;lH6BfUfZNRKwS#x_keQ`;Rjg@qj zZRwQXZd-rWngbYC}r6X)VCJ-=D54A+81%(L*8?+&r7(wOxDSNn!t(U}!;5|sjq zc5yF5$V!;%C#T+T3*AD+A({T)#p$H_<$nDd#M)KOLbd*KoW~9E19BBd-UwBX1<0h9 z8lNI&7Z_r4bx;`%5&;ky+y7PD9F^;Qk{`J@z!jJKyJ|s@lY^y!r9p^75D)_TJ6S*T zLA7AA*m}Y|5~)-`cyB+lUE9CS_`iB;MM&0fX**f;$n($fQ1_Zo=u>|n~r$HvkOUK(gv_L&@DE0b4#ya{HN)8bNQMl9hCva zi~j0v&plRsp?_zR zA}uI4n;^_Ko5`N-HCw_1BMLd#OAmmIY#ol4M^UjLL-UAat+xA+zxrFqKc@V5Zqan_ z+LoVX-Ub2mT7Dk_ z<+_3?XWBEM84@J_F}FDe-hl@}x@v-s1AR{_YD!_fMgagH6s9uyi6pW3gdhauG>+H? zi<5^{dp*5-9v`|m*ceT&`Hqv77oBQ+Da!=?dDO&9jo;=JkzrQKx^o$RqAgzL{ zjK@n)JW~lzxB>(o(21ibI}i|r3e;17zTjdEl5c`Cn-KAlR7EPp84M@!8~CywES-`mxKJ@Dsf6B18_!XMIq$Q3rTDeIgJ3X zB1)voa#V{iY^ju>*Cdg&UCbx?d3UMArPRHZauE}c@Fdk;z85OcA&Th>ZN%}=VU%3b9={Q(@M4QaeuGE(BbZ{U z?WPDG+sjJSz1OYFpdImKYHUa@ELn%n&PR9&I7B$<-c3e|{tPH*u@hs)Ci>Z@5$M?lP(#d#QIz}~()P7mt`<2PT4oHH}R&#dIx4uq943D8gVbaa2&FygrSk3*whGr~Jn zR4QnS@83UZ_BUGw;?@T zo5jA#potERcBv+dd8V$xTh)COur`TQ^^Yb&cdBcesjHlA3O8SBeKrVj!-D3+_p6%P zP@e{|^-G-C(}g+=bAuAy8)wcS{$XB?I=|r=&=TvbqeyXiuG43RR>R72Ry7d6RS;n^ zO5J-QIc@)sz_l6%Lg5zA8cgNK^GK_b-Z+M{RLYk5=O|6c%!1u6YMm3jJg{TfS*L%2 zA<*7$@wgJ(M*gyTzz8+7{iRP_e~(CCbGB}FN-#`&1ntct@`5gB-u6oUp3#QDxyF8v zOjxr}pS{5RpK1l7+l(bC)0>M;%7L?@6t}S&a zx0gP8^sXi(g2_g8+8-1~hKO;9Nn%_S%9djd*;nCLadHpVx(S0tixw2{Q}vOPCWvZg zjYc6LQ~nIZ*b0m_uN~l{&2df2*ZmBU8dv`#o+^5p>D5l%9@(Y-g%`|$%nQ|SSRm0c zLZV)45DS8d#v(z6gj&6|ay@MP23leodS8-GWIMH8_YCScX#Xr)mbuvXqSHo*)cY9g z#Ea+NvHIA)@`L+)T|f$Etx;-vrE3;Gk^O@IN@1{lpg&XzU5Eh3!w;6l=Q$k|%7nj^ z|HGu}c59-Ilzu^w<93il$cRf@C(4Cr2S!!E&7#)GgUH@py?O;Vl&joXrep=2A|3Vn zH+e$Ctmdy3B^fh%12D$nQk^j|v=>_3JAdKPt2YVusbNW&CL?M*?`K1mK*!&-9Ecp~>V1w{EK(429OT>DJAV21fG z=XP=%m+0vV4LdIi#(~XpaUY$~fQ=xA#5?V%xGRr_|5WWV=uoG_Z&{fae)`2~u{6-p zG>E>8j({w7njU-5Lai|2HhDPntQ(X@yB z9l?NGoKB5N98fWrkdN3g8ox7Vic|gfTF~jIfXkm|9Yuu-p>v3d{5&hC+ZD%mh|_=* zD5v*u(SuLxzX~owH!mJQi%Z=ALvdjyt9U6baVY<88B>{HApAJ~>`buHVGQd%KUu(d z5#{NEKk6Vy08_8*E(?hqZe2L?P2$>!0~26N(rVzB9KbF&JQOIaU{SumX!TsYzR%wB z<5EgJXDJ=1L_SNCNZcBWBNeN+Y`)B%R(wEA?}Wi@mp(jcw9&^1EMSM58?68gwnXF` zzT0_7>)ep%6hid-*DZ42eU)tFcFz7@bo=<~CrLXpNDM}tv*-B(ZF`(9^RiM9W4xC%@ZHv=>w(&~$Wta%)Z;d!{J;e@z zX1Gkw^XrHOfYHR#hAU=G`v43E$Iq}*gwqm@-mPac0HOZ0 zVtfu7>CQYS_F@n6n#CGcC5R%4{+P4m7uVlg3axX}B(_kf((>W?EhIO&rQ{iUO$16X zv{Abj3ZApUrcar7Ck}B1%RvnR%uocMlKsRxV9Qqe^Y_5C$xQW@9QdCcF%W#!zj;!xWc+0#VQ*}u&rJ7)zc+{vpw+nV?{tdd&Xs`NV zKUp|dV98WbWl*_MoyzM0xv8tTNJChwifP!9WM^GD|Mkc75$F;j$K%Y8K@7?uJjq-w zz*|>EH5jH&oTKlIzueAN2926Uo1OryC|CmkyoQZABt#FtHz)QmQvSX35o`f z<^*5XXxexj+Q-a#2h4(?_*|!5Pjph@?Na8Z>K%AAjNr3T!7RN;7c)1SqAJfHY|xAV z1f;p%lSdE8I}E4~tRH(l*rK?OZ>mB4C{3e%E-bUng2ymerg8?M$rXC!D?3O}_mka? zm*Y~JMu+_F7O4T;#nFv)?Ru6 z92r|old*4ZB$*6M40B;V&2w->#>4DEu0;#vHSgXdEzm{+VS48 z7U1tVn#AnQ3z#gP26$!dmS5&JsXsrR>~rWA}%qd{92+j zu+wYAqrJYOA%WC9nZ>BKH&;9vMSW_59z5LtzS4Q@o5vcrWjg+28#&$*8SMYP z!l5=|p@x6YnmNq>23sQ(^du5K)TB&K8t{P`@T4J5cEFL@qwtsCmn~p>>*b=37y!kB zn6x{#KjM{S9O_otGQub*K)iIjtE2NfiV~zD2x{4r)IUD(Y8%r`n;#)ujIrl8Sa+L{ z>ixGoZJ1K@;wTUbRRFgnltN_U*^EOJS zRo4Y+S`cP}e-zNtdl^S5#%oN#HLjmq$W^(Y6=5tM#RBK-M14RO7X(8Gliy3+&9fO; zXn{60%0sWh1_g1Z2r0MuGwSGUE;l4TI*M!$5dm&v9pO7@KlW@j_QboeDd1k9!7S)jIwBza-V#1)(7ht|sjY}a19sO!T z2VEW7nB0!zP=Sx17-6S$r=A)MZikCjlQHE)%_Ka|OY4+jgGOw=I3CM`3ui^=o0p7u z?xujpg#dRVZCg|{%!^DvoR*~;QBH8ia6%4pOh<#t+e_u!8gjuk_Aic=|*H24Yq~Wup1dTRQs0nlZOy+30f16;f7EYh*^*i9hTZ`h`015%{i|4 z?$7qC3&kt#(jI#<76Biz=bl=k=&qyaH>foM#zA7}N`Ji~)-f-t&tR4^do)-5t?Hz_Q+X~S2bZx{t+MEjwy3kGfbv(ij^@;=?H_^FIIu*HP_7mpV)NS{MY-Rr7&rvWo@Wd~{Lt!8|66rq`GdGu% z@<(<7bYcZKCt%_RmTpAjx=TNvdh+ZiLkMN+hT;=tC?%vQQGc7WrCPIYZwYTW`;x|N zrlEz1yf95FiloUU^(onr3A3>+96;;6aL?($@!JwiQ2hO|^i)b4pCJ7-y&a~B#J`#FO!3uBp{5GBvM2U@K85&o0q~6#LtppE&cVY z3Bv{xQ-;i}LN-60B2*1suMd=Fi%Y|7@52axZ|b=Wiwk^5eg{9X4}(q%4D5N5_Gm)` zg~VyFCwfkIKW(@@ZGAlTra6CO$RA_b*yz#){B82N7AYpQ9)sLQfhOAOMUV7$0|d$=_y&jl>va$3u-H z_+H*|UXBPLe%N2Ukwu1*)kt!$Y>(IH3`YbEt; znb1uB*{UgwG{pQnh>h@vyCE!6B~!k}NxEai#iY{$!_w54s5!6jG9%pr=S~3Km^EEA z)sCnnau+ZY)(}IK#(3jGGADw8V7#v~<&y5cF=5_Ypkrs3&7{}%(4KM7) zuSHVqo~g#1kzNwXc39%hL8atpa1Wd#V^uL=W^&E)fvGivt)B!M)?)Y#Ze&zU6O_I?1wj)*M;b*dE zqlcwgX#eVuZj2GKgBu@QB(#LHMd`qk<08i$hG1@g1;zD*#(9PHjVWl*5!;ER{Q#A9 zyQ%fu<$U?dOW=&_#~{nrq{RRyD8upRi}c-m!n)DZw9P>WGs>o1vefI}ujt_`O@l#Z z%xnOt4&e}LlM1-0*dd?|EvrAO-$fX8i{aTP^2wsmSDd!Xc9DxJB=x1}6|yM~QQPbl z0xrJcQNtWHgt*MdGmtj%x6SWYd?uGnrx4{m{6A9bYx`m z$*UAs@9?3s;@Jl19%$!3TxPlCkawEk12FADYJClt0N@O@Pxxhj+Kk(1jK~laR0*KGAc7%C4nI^v2NShTc4#?!p{0@p0T#HSIRndH;#Ts0YECtlSR}~{Uck+keoJq6iH)(Zc~C!fBe2~4(Wd> zR<4I1zMeW$<0xww(@09!l?;oDiq zk8qjS9Lxv$<5m#j(?4VLDgLz;8b$B%XO|9i7^1M;V{aGC#JT)c+L=BgCfO5k>CTlI zOlf~DzcopV29Dajzt*OcYvaUH{UJPaD$;spv%>{y8goE+bDD$~HQbON>W*~JD`;`- zZEcCPSdlCvANe z=?|+e{6AW$f(H;BND>uy1MvQ`pri>SafK5bK!YAE>0URAW9RS8#LWUHBOc&BNQ9T+ zJpg~Eky!u!9WBk)!$Z?!^3M~o_VPERYnk1NmzVYaGH;1h+;st==-;jzF~2LTn+x*k zvywHZg7~=aiJe=OhS@U>1fYGvT1+jsAaiaM;) zay2xsMKhO+FIeK?|K{G4SJOEt*eX?!>K8jpsZWW8c!X|JR#v(1+Ey5NM^TB1n|_40 z@Db2gH}PNT+3YEyqXP8U@)`E|Xat<{K5K;eK7O0yV72m|b!o43!e-!P>iW>7-9HN7 zmmc7)JX0^lPzF#>$#D~nU^3f!~Q zQWly&oZEb1847&czU;dg?=dS>z3lJkADL1innNtE(f?~OxM`%A_PBp?Lj;zDDomf$ z;|P=FTmqX|!sHO6uIfCmh4Fbgw@`DOn#`qAPEsYUiBvUlw zevH{)YWQu>FPXU$%1!h*2rtk_J}qNkkq+StX8Wc*KgG$yH#p-kcD&)%>)Yctb^JDB zJe>=!)5nc~?6hrE_3n^_BE<^;2{}&Z>Dr)bX>H{?kK{@R)`R5lnlO6yU&UmWy=d03 z*(jJIwU3l0HRW1PvReOb|MyZT^700rg8eFp#p<3Et%9msiCxR+jefK%x81+iN0=hG z;<`^RUVU+S)Iv-*5y^MqD@=cp{_cP4`s=z)Ti3!Bf@zCmfpZTwf|>|0t^E8R^s`ad z5~tA?0x7OM{*D;zb6bvPu|F5XpF11`U5;b*$p zNAq7E6c=aUnq>}$JAYsO&=L^`M|DdSSp5O4LA{|tO5^8%Hf1lqqo)sj=!aLNKn9(3 zvKk($N`p`f&u+8e^Z-?uc2GZ_6-HDQs@l%+pWh!|S9+y3!jrr3V%cr{FNe&U6(tYs zLto$0D+2}K_9kuxgFSeQ!EOXjJtZ$Pyl_|$mPQ9#fES=Sw8L% zO7Jij9cscU)@W+$jeGpx&vWP9ZN3fLDTp zaYM$gJD8ccf&g>n?a56X=y zec%nLN`(dVCpSl9&pJLf2BN;cR5F0Nn{(LjGe7RjFe7efp3R_2JmHOY#nWEc2TMhMSj5tBf-L zlxP3sV`!?@!mRnDTac{35I7h@WTfRjRiFw*Q*aD8)n)jdkJC@)jD-&mzAdK6Kqdct8P}~dqixq;n zjnX!pb^;5*Rr?5ycT7>AB9)RED^x+DVDmIbHKjcDv2lHK;apZOc=O@`4nJ;k|iikKk66v4{zN#lmSn$lh z_-Y3FC)iV$rFJH!#mNqWHF-DtSNbI)84+VLDWg$ph_tkKn_6+M1RZ!)EKaRhY={el zG-i@H!fvpH&4~$5Q+zHU(Ub=;Lzcrc3;4Cqqbr$O`c5M#UMtslK$3r+Cuz>xKl+xW?`t2o=q`1djXC=Q6`3C${*>dm~I{ z(aQH&Qd{{X+&+-4{epSL;q%n$)NOQ7kM}ea9bA++*F+t$2$%F!U!U}(&y7Sd0jQMV zkOhuJ$+g7^kb<`jqFiq(y1-~JjP13J&uB=hfjH5yAArMZx?VzW1~>tln~d5pt$uWR~TM!lIg+D)prR zocU0N2}_WTYpU`@Bsi1z{$le`dO{-pHFQr{M}%iEkX@0fv!AGCTcB90@e|slf#unz z*w4Cf>(^XI64l|MmWih1g!kwMJiifdt4C<5BHtaS%Ra>~3IFwjdu;_v*7BL|fPu+c zNp687`{}e@|%)5g4U*i=0zlSWXzz=YcZ*&Bg zr$r(SH0V5a%oHh*t&0y%R8&jDI=6VTWS_kJ!^WN!ET@XfEHYG-T1jJsDd`yEgh!^* z+!P62=v`R2=TBVjt=h}|JIg7N^RevZuyxyS+jsk>=iLA52Ak+7L?2$ZDUaWdi1PgB z_;*Uae_n&7o27ewV*y(wwK~8~tU<#Np6UUIx}zW6fR&dKiPq|$A{BwG_-wVfkm+EP zxHU@m`im3cD#fH63>_X`Il-HjZN_hqOVMG;(#7RmI13D-s_>41l|vDH1BglPsNJ+p zTniY{Hwoief+h%C^|@Syep#722=wmcTR7awIzimAcye?@F~f|n<$%=rM+Jkz9m>PF70$)AK@|h_^(zn?!;={;9Zo7{ zBI7O?6!J2Ixxk;XzS~ScO9{K1U9swGvR_d+SkromF040|Slk%$)M;9O_8h0@WPe4= z%iWM^ust8w$(NhO)7*8uq+9CycO$3m-l}O70sBi<4=j0CeE_&3iRUWJkDM$FIfrkR zHG2|hVh3?Nt$fdI$W?<|Qq@#hjDijk@7eUr1&JHYI>(_Q4^3$+Zz&R)Z`WqhBIvjo zX#EbA8P0Qla-yACvt)%oAVHa#kZi3Y8|(IOp_Z6J-t{)98*OXQ#8^>vTENsV@(M}^ z(>8BXw`{+)BfyZB!&85hT0!$>7$uLgp9hP9M7v=5@H`atsri1^{1VDxDqizj46-2^ z?&eA9udH#BD|QY2B7Zr$l;NJ-$L!u8G{MZoX)~bua5J=0p_JnM`$(D4S!uF}4smWq zVo%kQ~C~X?cWCH zo4s#FqJ)k|D{c_ok+sZ8`m2#-Uk8*o)io`B+WTD0PDA!G`DjtibftJXhPVjLZj~g& z=MM9nF$7}xvILx}BhM;J-Xnz0=^m1N2`Mhn6@ct+-!ijIcgi6FZ*oIPH(tGYJ2EQ0 z{;cjcc>_GkAlWEZ2zZLA_oa-(vYBp7XLPbHCBcGH$K9AK6nx}}ya%QB2=r$A;11*~ z_wfru1SkIQ0&QUqd)%eAY^FL!G;t@7-prQ|drDn#yDf%Uz8&kGtrPxKv?*TqkC(}g zUx10<;3Vhnx{gpWXM8H zKc0kkM~gIAts$E!X-?3DWG&^knj4h(q5(L;V81VWyC@_71oIpXfsb0S(^Js#N_0E} zJ%|XX&EeVPyu}? zz~(%slTw+tcY3ZMG$+diC8zed=CTN}1fB`RXD_v2;{evY z@MCG$l9Az+F()8*SqFyrg3jrN7k^x3?;A?L&>y{ZUi$T8!F7Dv8s}}4r9+Wo0h^m= zAob@CnJ;IR-{|_D;_w)? zcH@~&V^(}Ag}%A90);X2AhDj(-YB>$>GrW1F4C*1S5`u@N{T|;pYX1;E?gtBbPvS* zlv3r#rw2KCmLqX0kGT8&%#A6Sc(S>apOHtfn+UdYiN4qPawcL{Sb$>&I)Ie>Xs~ej z7)a=-92!sv-A{-7sqiG-ysG0k&beq6^nX1L!Fs$JU#fsV*CbsZqBQ|y z{)}zvtEwO%(&mIG|L?qs2Ou1rqTZHV@H+sm8Nth(+#dp0DW4VXG;;tCh`{BpY)THY z_10NNWpJuzCG%Q@#Aj>!v7Eq8eI6_JK3g2CsB2jz)2^bWiM{&U8clnV7<2?Qx5*k_ zl9B$P@LV7Sani>Xum{^yJ6uYxM4UHnw4zbPdM|PeppudXe}+OcX z!nr!xaUA|xYtA~jE|436iL&L={H3e}H`M1;2|pLG)Z~~Ug9X%_#D!DW>w}Es!D{=4 zxRPBf5UWm2{}D>Em;v43miQ~2{>%>O*`wA{7j;yh;*DV=C-bs;3p{AD;>VPcn>E;V zLgtw|Y{|Beo+_ABz`lofH+cdf33LjIf!RdcW~wWgmsE%2yCQGbst4TS_t%6nS8a+m zFEr<|9TQzQC@<(yNN9GR4S$H-SA?xiLIK2O2>*w-?cdzNPsG4D3&%$QOK{w)@Dk}W z|3_Z>U`XBu7j6Vc=es(tz}c7k4al1$cqDW4a~|xgE9zPX(C`IsN(QwNomzsBOHqjd zi{D|jYSv5 zC>6#uB~%#!!*?zXW`!yHWjbjwm!#eo3hm;>nJ!<`ZkJamE6i>>WqkoTpbm(~b%G_v z`t3Z#ERips;EoA_0c?r@WjEP|ulD+hue5r8946Sd0kuBD$A!=dxigTZn)u3>U;Y8l zX9j(R*(;;i&HrB&M|Xnitzf@><3#)aKy=bFCf5Hz@_);{nlL?J!U>%fL$Fk~Ocs3& zB@-Ek%W>h9#$QIYg07&lS_CG3d~LrygXclO!Ws-|PxMsn@n{?77wCaq?uj`dd7lllDCGd?ed&%5k{RqUhiN1u&?uz@Fq zNkv_4xmFcl?vs>;emR1R<$tg;*Ayp@rl=ik z=x2Hk zJqsM%++e|*+#camAiem6f;3-khtIgjYmNL0x|Mz|y{r{6<@_&a7^1XDyE>v*uo!qF zBq^I8PiF#w<-lFvFx9xKoi&0j)4LX~rWsK$%3hr@ebDv^($$T^4m4h#Q-(u*Mbt6F zE%y0Fvozv=WAaTj6EWZ)cX{|9=AZDvPQuq>2fUkU(!j1GmdgeYLX`B0BbGK(331ME zu3yZ3jQ@2)WW5!C#~y}=q5Av=_;+hNi!%gmY;}~~e!S&&^{4eJuNQ2kud%Olf8TRI zW-Dze987Il<^!hCO{AR5tLW{F1WLuZ>nhPjke@CSnN zzoW{m!+PSCb7byUf-1b;`{0GU^zg7b9c!7ueJF`>L;|akVzb&IzoLNNEfxp7b7xMN zKs9QG6v@t7X)yYN9}3d4>*ROMiK-Ig8(Do$3UI&E}z!vcH2t(VIk-cLyC-Y%`)~>Ce23A=dQsc<( ziy;8MmHki+5-(CR8$=lRt{(9B9W59Pz|z0^;`C!q<^PyE$KXt!KibFH*xcB9V%xTD zn;YlZ*tTukwr$(mWMka@|8CW-J8!zCXI{P1-&=wSvZf&%9SZ7m`1&2^nV#D z6T*)`Mz3wGUC69Fg0Xk!hwY}ykk!TE%mr57TLX*U4ygwvM^!#G`HYKLIN>gT;?mo% zAxGgzSnm{}vRG}K)8n(XjG#d+IyAFnozhk|uwiey(p@ zu>j#n4C|Mhtd=0G?Qn5OGh{{^MWR)V*geNY8d)py)@5a85G&_&OSCx4ASW8g&AEXa zC}^ET`eORgG*$$Q1L=9_8MCUO4Mr^1IA{^nsB$>#Bi(vN$l8+p(U^0dvN_{Cu-UUm zQyJc!8>RWp;C3*2dGp49QVW`CRR@no(t+D|@nl138lu@%c1VCy3|v4VoKZ4AwnnjF z__8f$usTzF)TQ$sQ^|#(M}-#0^3Ag%A0%5vA=KK$37I`RY({kF-z$(P50pf3_20YTr%G@w+bxE_V+Tt^YHgrlu$#wjp7igF!=o8e2rqCs|>XM9+M7~TqI&fcx z=pcX6_MQQ{TIR6a0*~xdgFvs<2!yaA1F*4IZgI!)xnzJCwsG&EElg_IpFbrT}nr)UQy}GiK;( zDlG$cksync34R3J^FqJ=={_y9x_pcd%$B*u&vr7^ItxqWFIAkJgaAQiA)pioK1JQ| zYB_6IUKc$UM*~f9{Xzw*tY$pUglV*?BDQuhsca*Fx!sm`9y`V&?lVTH%%1eJ74#D_ z7W+@8@7LAu{aq)sPys{MM~;`k>T%-wPA)E2QH7(Z4XEUrQ5YstG`Uf@w{n_Oc!wem z7=8z;k$N{T74B*zVyJI~4d60M09FYG`33;Wxh=^Ixhs69U_SG_deO~_OUO1s9K-8p z5{HmcXAaKqHrQ@(t?d@;63;Pnj2Kk<;Hx=kr>*Ko`F*l){%GVDj5nkohSU)B&5Vrc zo0u%|b%|VITSB)BXTRPQC=Bv=qplloSI#iKV#~z#t#q*jcS`3s&w-z^m--CYDI7n2 z%{LHFZ*(1u4DvhES|Dc*n%JL8%8?h7boNf|qxl8D)np@5t~VORwQn)TuSI07b-T=_ zo8qh+0yf|-6=x;Ra$w&WeVZhUO%3v6Ni*}i&sby3s_(?l5Er{K9%0_dE<`7^>8mLr zZ|~l#Bi@5}8{iZ$(d9)!`}@2~#sA~?uH|EbrJQcTw|ssG)MSJJIF96-_gf&* zy~I&$m6e0nnLz^M2;G|IeUk?s+afSZ){10*P~9W%RtYeSg{Nv5FG<2QaWpj?d`;}<4( z>V1i|wNTpH`jJtvTD0C3CTws410U9HS_%Ti2HaB~%^h6{+$@5`K9}T=eQL;dMZ?=Y zX^z?B3ZU_!E^OW%Z*-+t&B-(kLmDwikb9+F9bj;NFq-XHRB=+L)Rew{w|7p~7ph{#fRT}}K zWA)F7;kJBCk^aFILnkV^EMs=B~#qh*RG2&@F|x2$?7QTX_T6qL?i$c6J*-cNQC~E6dro zR)CGIoz;~V?=>;(NF4dihkz~Koqu}VNPE9^R{L@e6WkL{fK84H?C*uvKkO(!H-&y( zq|@B~juu*x#J_i3gBrS0*5U*%NDg+Ur9euL*5QaF^?-pxxieMM6k_xAP;S}sfKmIa zj(T6o{4RfARHz25YWzv=QaJ4P!O$LHE(L~6fB89$`6+olZR!#%y?_v+Cf+g)5#!ZM zkabT-y%v|ihYuV}Y%-B%pxL264?K%CXlbd_s<GY5BG*`kYQjao$QHiC_qPk5uE~AO+F=eOtTWJ1vm*cU(D5kvs3kity z$IYG{$L<8|&I>|WwpCWo5K3!On`)9PIx(uWAq>bSQTvSW`NqgprBIuV^V>C~?+d(w$ZXb39Vs`R=BX;4HISfN^qW!{4 z^amy@Nqw6oqqobiNlxzxU*z2>2Q;9$Cr{K;*&l!;Y??vi^)G|tefJG9utf|~4xh=r3UjmRlADyLC*i`r+m;$7?7*bL!oR4=yU<8<-3XVA z%sAb`xe&4RV(2vj+1*ktLs<&m~mGJ@RuJ)1c zLxZyjg~*PfOeAm8R>7e&#FXBsfU_?azU=uxBm=E6z7FSr7J>{XY z1qUT>dh`X(zHRML_H-7He^P_?148AkDqrb>;~1M-k+xHVy>;D7p!z=XBgxMGQX2{* z-xMCOwS33&K^~3%#k`eIjKWvNe1f3y#}U4;J+#-{;=Xne^6+eH@eGJK#i|`~dgV5S zdn%`RHBsC!=9Q=&=wNbV#pDv6rgl?k1wM03*mN`dQBT4K%uRoyoH{e=ZL5E*`~X|T zbKG9aWI}7NGTQtjc3BYDTY3LbkgBNSHG$5xVx8gc@dEuJqT~QPBD=Scf53#kZzZ6W zM^$vkvMx+-0$6R^{{hZ2qLju~e85Em>1nDcRN3-Mm7x;87W#@RSIW9G>TT6Q{4e~b z8DN%n83FvXWdpr|I_8TaMv~MCqq0TA{AXYO-(~l=ug42gpMUvOjG_pWSEdDJ2Bxqz z!em;9=7y3HW*XUtK+M^)fycd8A6Q@B<4biGAR)r%gQf>lWI%WmMbij;un)qhk$bff zQxb{&L;`-1uvaCE7Fm*83^0;!QA5-zeSvKY}WjbwE68)jqnOmj^CTBHaD zvK6}Mc$a39b~Y(AoS|$%ePoHgMjIIux?;*;=Y|3zyfo)^fM=1GBbn7NCuKSxp1J|z zC>n4!X_w*R8es1ofcPrD>%e=E*@^)7gc?+JC@mJAYsXP;10~gZv0!Egi~){3mjVzs z^PrgddFewu>Ax_G&tj-!L=TuRl0FAh#X0gtQE#~}(dSyPO=@7yd zNC6l_?zs_u5&x8O zQ|_JvKf!WHf43F0R%NQwGQi-Dy7~PGZ@KRKMp?kxlaLAV=X{UkKgaTu2!qzPi8aJ z-;n$}unR?%uzCkMHwb56T%IUV)h>qS(XiuRLh3fdlr!Cri|{fZf0x9GVYUOlsKgxLA7vHrkpQddcSsg4JfibzpB zwR!vYiL)7%u8JG7^x@^px(t-c_Xt|9Dm)C@_zGeW_3nMLZBA*9*!fLTV$Uf1a0rDt zJI@Z6pdB9J(a|&T_&AocM2WLNB;fpLnlOFtC9yE6cb39?*1@wy8UgruTtX?@=<6YW zF%82|(F7ANWQ`#HPyPqG6~ggFlhJW#R>%p@fzrpL^K)Kbwj(@#7s97r`)iJ{&-ToR z$7(mQI@~;lwY+8dSKP~0G|#sjL2lS0LQP3Oe=>#NZ|JKKYd6s6qwe#_6Xz_^L4PJ5TM_|#&~zy= zabr|kkr3Osj;bPz`B0s;c&kzzQ2C8|tC9tz;es~zr{hom8bT?t$c|t;M0t2F{xI;G z`0`ADc_nJSdT`#PYCWu4R0Rmbk#PARx(NBfdU>8wxzE(`jA}atMEsaG6zy8^^nCu| z9_tLj90r-&Xc~+p%1vyt>=q_hQsDYB&-hPj(-OGxFpesWm;A(Lh>UWy4SH9&+mB(A z2jkTQ2C&o(Q4wC_>|c()M8_kF?qKhNB+PW6__;U+?ZUoDp2GNr<|*j(CC*#v0{L2E zgVBw6|3c(~V4N*WgJsO(I3o>8)EO5;p7Xg8yU&%rZ3QSRB6Ig6MK7Wn5r+xo2V}fM z0QpfDB9^xJEi}W*Fv6>=p4%@eP`K5k%kCE0YF2Eu5L!DM1ZY7wh`kghC^NwxrL}90dRXjQx=H>8 zOWP@<+C!tcw8EL8aCt9{|4aT+x|70i6m*LP*lhp;kGr5f#OwRy`(60LK@rd=to5yk^%N z6MTSk)7)#!cGDV@pbQ>$N8i2rAD$f{8T{QM+|gaj^sBt%24UJGF4ufrG1_Ag$Rn?c zzICg9`ICT>9N_2vqvVG#_lf9IEd%G5gJ_!j)1X#d^KUJBkE9?|K03AEe zo>5Rql|WuUU=LhLRkd&0rH4#!!>sMg@4Wr=z2|}dpOa`4c;_DqN{3Pj`AgSnc;h%# z{ny1lK%7?@rwZO(ZACq#8mL)|vy8tO0d1^4l;^e?hU+zuH%-8Y^5YqM9}sRzr-XC0 zPzY1l($LC-yyy*1@eoEANoTLQAZ2lVto2r7$|?;PPQX`}rbxPDH-a$8ez@J#v0R5n z7P*qT3aHj02*cK)WzZmoXkw?e3XNu&DkElGZ0Nk~wBti%yLh+l2DYx&U1lD_NW_Yt zGN>yOF?u%ksMW?^+~2&p@NoPzk`T)8qifG_owD>@iwI3@u^Y;Mqaa!2DGUKi{?U3d z|Efe=CBc!_ZDoa~LzZr}%;J|I$dntN24m4|1(#&Tw0R}lP`a`?uT;>szf^0mDJx3u z6IJvpeOpS$OV!Xw21p>Xu~MZ(Nas5Iim-#QSLIYSNhYgx1V!AR>b zf5b7O`ITTvW5z%X8|7>&BeEs8~J1i47l;`7Y#MUMReQ4z!IL1rh8UauKNPG?7rV_;#Y zG*6Vrt^SsTMOpV7mkui}l_S8UNOBcYi+DzcMF>YKrs3*(q5fwVCr;_zO?gpGx*@%O zl`KOwYMSUs4e&}eM#FhB3(RIDJ9ZRn6NN{2Nf+ z2jcz%-u6IPq{n7N3wLH{9c+}4G(NyZa`UmDr5c-SPgj0Sy$VN#Vxxr;kF>-P;5k!w zuAdrP(H+v{Dybn78xM6^*Ym@UGxx?L)m}WY#R>6M2zXnPL_M9#h($ECz^+(4HmKN7 zA>E;`AEqouHJd7pegrq4zkk>kHh`TEb`^(_ea;v{?MW3Sr^FXegkqAQPM-h^)$#Jn z?bKbnXR@k~%*?q`TPL=sD8C+n^I#08(}d$H(@Y;3*{~nv4RLZLw`v=1M0-%j>CtT( zTp#U03GAv{RFAtj4vln4#E4eLOvt zs;=`m&{S@AJbcl1q^39VOtmN^Zm(*x(`(SUgF(=6#&^7oA8T_ojX>V5sJx@*cV|29 z)6_%P6}e}`58Sd;LY2cWv~w}fer&_c1&mlY0`YNNk9q=TRg@Khc5E$N`aYng=!afD z@ewAv^jl$`U5;q4OxFM4ab%X_Jv>V!98w$8ZN*`D-)0S7Y^6xW$pQ%g3_lEmW9Ef^ zGmFsQw`E!ATjDvy@%mdcqrD-uiKB}!)ZRwpZRmyu+x|RUXS+oQ*_jIZKAD~U=3B|t zz>9QQr91qJihg9j9rWHww{v@+SYBzCfc0kI=4Gr{ZLcC~mft^EkJ`CMl?8fZ z3G4ix71=2dQ`5QuTOYA0(}f`@`@U<#K?1TI(XO9c*()q!Hf}JUCaUmg#y?ffT9w1g zc)e=JcF-9J`hK{0##K#A>m^@ZFx!$g09WSBdc8O^IdP&JE@O{i0&G!Ztvt{L4q%x& zGE2s!RVi6ZN9)E*(c33HuMf7#X2*VPVThdmrVz-Fyqxcs&aI4DvP#bfW={h$9>K0HsBTUf z2&!G;( z^oOVIYJv~OM=-i`6=r4Z1*hC8Fcf3rI9?;a_rL*nr@zxwKNlxf(-#Kgn@C~4?BdKk zYvL?QcQeDwwR5_S(`sn&{PL6FYxwb-qSh_rUUo{Yi-GZz5rZotG4R<+!PfsGg`MVtomw z5kzOZJrh(#rMR_87KeP0Q=#^5~r_?y1*kN?3Fq% zvnzHw$r!w|Soxz8Nbx2d&{!#w$^Hua%fx!xUbc2SI-<{h>e2I;$rJL)4)hnT5cx^* zIq#+{3;Leun3Xo=C(XVjt_z)F#PIoAw%SqJ=~DMQeB zNWQ={d|1qtlDS3xFik}#j*8%DG0<^6fW~|NGL#P_weHnJ(cYEdJtI9#1-Pa8M}(r{ zwnPJB_qB?IqZw5h!hRwW2WIEb?&F<52Ruxpr77O2K>=t*3&Z@=5(c^Uy&JSph}{Q^ z0Tl|}gt=&vK;Rb9Tx{{jUvhtmF>;~k$8T7kp;EV`C!~FKW|r$n^d6=thh`)^uYgBd zydgnY9&mm$?B@pKK+_QreOm?wnl5l}-wA$RZCZukfC$slxbqv9uKq0o^QeSID96{Rm^084kZ)*`P zk))V~+<4-_7d6<~)PL%!+%JP`Dn23vUpH47h~xnA=B_a}rLy|7U-f0W+fH`{wnyh2 zD$JYdXuygeP5&OAqpl2)BZ|X){~G;E|7{liYf%AZFmXXyA@32qLA)tuuQz`n^iH1Y z=)pAzxK$jw0Xq?7`M`=kN2WeQFhz)p;QhjbKg#SB zP~_Vqo0SGbc5Q;v4Q7vm6_#iT+p9B>%{s`8H}r|hAL5I8Q|ceJAL*eruzD8~_m>fg26HvLpik&#{3Zd#|1C_>l&-RW2nBBzSO zQ3%G{nI*T}jBjr%3fjG*&G#ruH^ioDM>0 zb0vSM8ML?tPU*y%aoCq;V%x%~!W*HaebuDn9qeT*vk0%X>fq-4zrrQf{Uq5zI1rEy zjQ@V|Cp~$AoBu=VgnVl@Yiro>ZF{uB=5)~i1rZzmDTIzLBy`8Too!#Z4nE$Z{~uB( z_=o=gKuhVpy&`}-c&f%**M&(|;2iy+nZy2Su}GOAH_GT9z`!ogwn$+Bi&1ZhtPF zVS&LO5#Bq}cew$kvE7*t8W^{{7&7WaF{upy0mj*K&xbnXvSP9V$6m6cesHGC!&Us36ld9f*Pn8gbJb3`PPT|ZG zri2?uIu09i>6Y-0-8sREOU?WaGke0+rHPb^sp;*E{Z5P7kFJ@RiLZTO`cN2mRR#Nz zxjJ##Nk+Uy-2N-8K_@576L(kJ>$UhP+)|w!SQHkkz+e62*hpzyfmY4eQLZtZUhEdG zIZluDOoPDlt5#iw+2epC3vEATfok^?SDT`TzBwtgKjY z>ZImbO)i~T=IYAfw$3j2mF1Cj*_yqK(qw(U^r-!gcUKvWQrDG@E{lEyWDWOPtA9v{ z5($&mxw{nZWo_Ov??S#Bo1;+YwVfx%M23|o$24Hdf^&4hQeV=Cffa5MMYOu2NZLSC zQ4UxWvn+8%YVGDg(Y*1iHbUyT^=gP*COcE~QkU|&6_3h z-GOS6-@o9+Vd(D7x#NYt{Bvx2`P&ZuCx#^l0bR89Hr6Vm<||c3Waq(KO0eZ zH(|B;X}{FaZ8_4yyWLdK!G_q9AYZcoOY}Jlf3R;%oR5dwR(rk7NqyF%{r>F4s^>li z`R~-fh>YIAC1?%!O?mxLx!dq*=%IRCj;vXX628aZ;+^M0CDFUY0Rc<1P5e(OVX8n- z*1UOrX{J}b2N)6m5&_xw^WSN=Lp$I$T>f8K6|J_bj%ZsIYKNs1$TFt!RuCWF48;98`7D(XPVnk+~~i=U$} zR#;!ZRo4eVqlDxjDeE^3+8)bzG_o~VRwdxqvD^HNh#@o>1My$0*Y_`wfQ$y}az|Uz zM47oEaYNTH?J^w9EVNnvfmmbV+GHDe)Kf;$^@6?9DrSHnk@*{PuJ>ra|9KO!qQ-Fp zNNcZB4ZdAI>jEh@3Mt(E1Fy!^gH-Zx6&lr8%=duIgI^~gC{Q;4yoe;#F7B`w9daIe z{(I;y)=)anc;C;)#P`8H6~iAG_q-4rPJb(6rn4pjclGi6$_L79sFAj#CTv;t@94S6 zz`Id7?k!#3JItckcwOf?sj=Xr6oKvAyt1=jiWN@XBFoW6dw_+c9O9x2i4or?*~8f& zm<>yzc6Aw_E-gsGAa`6`cjK~k^TJt(^`E1^_h)5(8)1kzAsBxjd4+!hJ&&T!qklDN z`?j#za=(^wRCvEI75uE^K#IBe5!5g2XW}|lUqAmdmIQb7xJtP}G9^(=!V`ZS_7#RZ zjXq#Cekw>fE*YS-?Qea|7~H?)bbLK;G&(~%!B@H`o#LYAuu6;-c~jFfjY7GKZ|9~{ zE!`!d@@rhY_@5fDbuQ8gRI~R_vs4%fR5$?yot4hDPJ28k_Wzmc^0yzwMr#*(OXq@g zRUgQmJA?E>3GO=5N8iWIfBP{&QM%!Oa*iwTlbd0Fbm*QCX>oRb*2XfG-=Bz1Qz0$v zn#X!2C!LqE601LEMq;X7`P*5nurdKZAmmsI-zZ|rTH;AFxNDyZ_#hN2m4W(|YB64E z470#yh$;8QzsdA;6vbNvc95HLvZvyT4{C>F(fwy&izvNDuvfO1Z;`Ss#4a_c6pm*{0t|_i9z{@84^lffQa5zG4<{(+p5-S z^>lG-^GJR#V>;5f3~y%n=`U_jBp~WgB0cp;Lx5VZYPYCH&(evw#}AYRlGJ>vcoeVr z3%#-QUBgeH!GB>XLw;rT&oMI9ynP;leDwh4O2uM!oIWo&Qxk{^9#nX&^3GJ z(U~5{S9aw@yHH^yuQGso=~*JOC9Zdi6(TFP+IddkfK5Eu9q;+F9?PPNAe-O;;P_Aa zPJ{Dqa1gQb%dZ|0I{#B0(z|r(qq!A4CxlW92-LwXFjYfOzAT1DDK`9rm4AB~l&oVv zi6_{)M9L1%JP}i52y@`!T9RB~!CRel53wl?amNHqcuElq%hn)|#BPvW5_m51RVb|? zXQ&B*eAD}}QamG>o{?i~usG5X6IDa3+Xkb8w%7;C8|Cln70biA+ZH}fxkH^Wei$vZPnuqIT!Mmy26;mLfU z3Bbv4M^vvMlz-I+46=g>0^wWkmA!hlYj*I!%it^x9Kx(d{L|+L{rW?Y#hLHWJfd5X z>B=Swk8=;mRtIz}Hr3NE_garb5W*!7fnNM{+m2_>!cHZZlNEeof~7M#FBEQ+f&gJ3 z^zv*t?XV)jQi%0-Ra|ISiW-fx)DsK-> zI}Fv%uee$#-1PKJwr=lU89eh=M{>Nk7IlJ)U33U)lLW+OOU%A|9-Lf;`@c*+vX{W2 z{{?0QoP!#?8=5%yL=fP%iF+?n$0#iHz`P;1{Ra6iwr=V7v^8;NoLJ5)QxIyIx>ur?lMwV=mBo0BA?28kMow8SX=Ax5L%S~x4+EQi#Ig`(ht%)D(F#Pa!)SiHy&PvUp32=VtAsR|6|NZR@jkad zX^aEgojf9(-)rNOZ=NVA&a;6Cljkb=H-bY9m^_I)`pBHB16QW)sU27zF13ypefeATJc1Wzy39GrKF{UntHsIU59AdXp?j{eh2R)IbU&omd zk6(qzvE@hve1yM6dgkbz>5HDR&MD~yi$yymQ}?b;RfL$N-#l7(u?T^Wlu+Q;fo|jd zBe^jzGMHY(2=5l?bEIh+zgE$1TEQ&!p3fH;AW`P?W5Hkj3eJnT>dqg! zf~}A*SZU5HHDCbdywQ^l_PqssHRlrySYN=`hAv2sVrtcF!`kyEu%XeeRUTJU7vB%h zY0*)N$mLo6d=tJfe}IPIeiH~>AKwCpkn&WEfYgl?3anq5#-F$6$v-(G_j0*S9mdsn zg@ek_ut4(?+JP_9-n`YqoD(gAz+Ttm1#t za96D}oQR(o=e8wwes19_(p4g(A1vSGwPAp~Hh3hh!fc>u{1E^+^}AzwilFVf6^vbL zc&NnRs`u)N-P|Cu4()yTiuE{j_V&=K?iP!IUBf~ei2}~_KBvUAlXa;R#Wl`gOBtJ$Y5(L))@`riLB)v*r>9*8VfmQt<72?+fdwP{BA@?_qo>mN7yzICUCaeG(+>Rb~8wg~6U(P)NlDLuhQgjbC}=)HuZgC}0Z-qLX4lJ7^)8~!!*qP0=~`Y_(A z{@15*ZevZSI^s|OnpCeCwLXf#tgbq8y~R*GB5anmZ;_N!+-3>!wu@NBFCNJ$#y?{? zMI!?s*=_xA;V&aX)ROxzVW8*de+&P#2zucA|8mksdgCXBsZ*TM=%{L1Tk5LB_*^@&S?O=ot{h)1xRVSn27&Tk8>rF|6ruzYb;Nq) z;qvlmrP^SL$mhe4Ai)xpl6Wx&y;z8o!7-+6$qj;ZLXvfR71I@w(R|6lyuP6v-lP&r z@KK-TEmGQfMmk1c0^fd7!^si}T%b5a2%>T-Drh|^Cf z$}qxIv@zxbmJ#qjK6Q_aGDe{ciVT20V1lW52Xs!}x(4_j)sUXYdm4 zwYC9FOa;X*c*LxL;xE5ov?|?^7gWXyALy_D2GvDo-8%0-Y%9TkkO_Tcr2qIUg3(OC z%3wt?hyn*+e^z%(~2#!2dvMFa$mzgwk1I1X;naFMjXSbnmZ!zd%7u)=cgi z*0&@Scrl&BDfU(9Pks8#;!~v~r7~DN{G6WE&_;7i{{a*?oiCao(l%2ruxX0fAt69e2vLgL%Mf_)!*(Tz zNKW>sW@YB2vBfP>C&L|-pq)Uq^PsG_THu;8iEcqafO?0k$IQp1KyWyOoTxwmKvlc^ zO9$%Tt8;%qQxwy5;CsJ)V}a7I6}SvQ%0_H53Kcqx=m83fIzpLSGgfVe^SPdc*xPdciI5dg}#{Etv$e<)gGD=qm0v=!aN@*?$s zLhzD%4w{vf-g6FHQjG9XyC+4=bewb?Mz%!u8%oP{G9{UJFTLTcCi3R(=Nm&t&Sl(? zr>pj?=ECdDVa}-g%`LF^1EY@>7d}%VhYpKFSDPH)D(zB+gPe1m7E}W>TiW=8L0&(D&YG=0<&7G4Bu{;-#Ud;-1%Ta9V}U6fyK1YX z`Rq|i-X(loPZ)M$H%m@j7bGx>uj~y=0)!t#dc|c}+hT%~Sq>fefez0Ul|jOJHta~u zx7*mV6~Jpt(FkY(pQN91>aFk7VS%Sa^oLaq$*)W?fy`xuFJgH<2s=!Rz}_(qdmdF~ zlr2f=)q_vpi8X;Jq>5^$GweJ{iS`Khw2f)fsvKpgh;U~13a+9 zfaw}UuGiBy;q10pI^Avb#X3D=k_r(T{N;-xA)OM}2Py5L##<96NU*Sr7GQqhfrPej z?;B$Bt_sTxuSAPXfTSC{zr?@$$0iHxC@z*5F52j*PG87hh`0w3At8jPf*rjNE~_Gj z2)fjeUFJ(#l9uWuw&5#@13|AQ1;pdA?EL4YKq0JDR5T8I?aWGxI=J9}vdyH;gQ@iE z>+UnC2iwT0f80-VuE^bY!N@(}9?bOXyy%rTqSNDN4rO4Zt#(kZwcGgTp&3((F+nsd ze~B)%K6oP4WX_w1>|QImC;9q zy}4p+s%^Too2(gE>yo%+yY#F{)phtmNqsJPVQQ0lGR|H9q>aA&AtU4M+EZ%`xvQLb zbigBOc`dL}&j3er?EOI`!W)N#>+uwp_!h^5FspaEylq!e(FPY-6T3~WeNmZ<$?Y6y z-!bM1kD7ZF8xl+Pi6fiv1?)q%`aNxn#pK%)ct||L&Xnf8Gu&3g;Of{B8Pt=u`e+Mn zA(DmU#3cF#Nr7W;X0V4ksFHMcNDAf4G&D8VjLeZ^|5-f$>_|71>P3xuu)?4NJed*w z6GR_RB5HQLzT(h+`Y?-3esxeue{-Q%b+!&o>IJ!#=}#_&q+hwJga>fkt(*(WdoN5vSta z#$mMN6}YzYRpaBZ)j)EL91-oL1(|d(>%UclsTUOyXyWM&(hNqLwqtn`!E>HJM{ zh>M~xa1@*U^cwx-k5QjePr5=B6u*jpJ)C0{C?f7Yga+I^4$TleyX$x&jm9z@c!?cC z<2kY7)p^+W{AXd@l1C09_yB*TG|yzb96BYk z8Wpj81vB>zcR+qM4m~A44w1n7$fxB$-?MV}S?Fh}c_|2FXg`cZ?750i;Cdl-_nGK# zta)h)6!*AsQ-z8caSh)%5JY>_yCeJs~FpAzdY8 zF@SU_hN#~ip5I;UACFzx1v0yf{j97l&)e-=`d#1Kp6A(Kj&HC!%vK!wEdK3HFJ?|6 za;WwUczZ+&<$g!Td^48@lJtfW@doXL#jY6)dK_RDCQAZ}l&OdD+?Yl5-bqpsHZR^( zF{u_cR(x>u(c4i5f(^8!h6CV0#ZxRFhLlunWiGDLO6yoRb(wV<(P^8=fOU7Hp{AHE z;Yg%kg@6&tL3Z*IrbkDeQ$%rbalVP39D@LVrC2xSavnTp%PorXPf1DVzHyqjDsDnS zL=mv0a2s60bHKGQM)ue>npH0SCp;XtZFUzm?R-x7D*(PxMmuJ4J*K2eY&ebe0yQHe zVG&*qe{pot{PM^xQv`H_rn2FcYOrEN+I#uX^1`Id%J$;Hi2cNCU!0Hlc0TjxLzkss zHxmC;hQBu5U4J0XflWM;{uH`_47Sg)QyZ{8D&T0;bdc3{^^<=q7P?C_2E-}PQn>*= z2T5q^J|Q_2+x%Qt`i3m6=6V$)BxIx{2KAFkMb#q`iMCD|L>+}_dYVA$wBr1Zr}YOF z^MMGO@PHGGh>g|^yF`PvvtDwN@kxt?ClLcG<+murHMz1Asj!$l=b)4{d}SqOJ}>Y< zSeAyP@ZEcpx`ayIdp>{--UVLYC_cZZURh_!4u2(*#x@Tk(QJa}4BqqZ$6%LhF-HB~ zAcc?$I6KP}IxANcAteEBX$Ys?T=JB|Fnd3*UAO0mYAXCgWf~?7Z_G7G5`H4;S^QKK zG*2l75vI@DHQC*es>6&|r^#RHKRQ5rwv_l4`!(!I3%)Z$P1fnZ8N@27zyg}54ElO%SjQ_4uujX)4ta@Gz2)_>4b~vX|rhRIH-eqdD zL)xaEpW3K|a>daQRRR*_$W>rWOsW-IE4VQl3L$3}=-PFU)s@XG&9+DFivH-;2&w~$ES_nJZJH!?1mO!CnP)Jb{mW9=f`bDpo^PI6i4|YurK)Q1 z^Ys1oHRdr!$X4RuyR%kgp!a*Lz*_AAoJ$EVAdsNCoPA^VZE1pGO@D3UStACE+%vs6 z$io@E>DmB|3VV~GbOt2oc+K;t zdn3gaFvYz;vRN-+2+Qk{8|O}e86nVck)fZn3sg$j#dLVham{yGkc$I#!HF7mRS%f* z!+NdzG49K(qaO^SBlp@K@D?|^rAq;8{*@kRc4sYSNQmoy7@_RS_ksWl2T_38h2A)# ziU2WXWD03(NqS&Mu*?0-iK8X_Z3w`}c7MPv0qZ7iM|L3xdTnR{y!7{#82$}uJCiGT zqa=8<9L05hu6 z1N+2n7OzT{NEf?gS@eq7@buCDFe9mAxY%THo^b@BHckKK>jg6{@)>n z43cPs%$Qi0iwyZ+{C491>FRu5+6baJ{&XXXC@Sp+b!QE|{7_d?lm5K=B z)myKEcxjFm74+drF|JCYcxdY%ASig#YoRBRUV7An7f-%rqj%PHECbxh#5476cEq@NQL?dI6gUqvS@w zq!WmD(aR0{NxItAZCKDCVw=Zu{9WGDu^i?2g zLerPiOU*HSaXg^3CdOX^F6c9MiHINP339N%)a96`^Z-c#&EogcxMSYo0Cb4{-}q1( zRrJine`P|6WRkm8u4Ja1QRYq$AR>b7tugd#EsT-VmXN-t!TYjZy}i!uKi6$u>EJ?w zvdHZg+hp+5ree?>fdJAX)5#Wtm#2M-{~2jfX2{G`)?D6UD1MevdeeU;;HCi}AtJr( SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6aba2515..e7646dead 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 48f0d4febdfe7dcb52a5ccfd0906b0f095bd3f7d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 01:44:23 +0400 Subject: [PATCH 217/314] build: Bump Checkstyle from `10.12.1` to `10.14.2` (#2132) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2956300e8..15768b583 100644 --- a/build.gradle +++ b/build.gradle @@ -99,7 +99,7 @@ jacocoTestReport.dependsOn test apply plugin: 'checkstyle' checkstyle { - toolVersion = '10.12.1' + toolVersion = '10.14.2' configFile = configDirectory.file('appium-style.xml').get().getAsFile() showViolations = true ignoreFailures = false From 5f9ba620b4d05ca002a30f97a5bf3e82ff62df13 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 01:44:57 +0400 Subject: [PATCH 218/314] refactor: Remove usages of Guava's `@VisibleForTesting` (#2138) --- .../filters/AppiumUserAgentFilter.java | 4 +-- .../local/AppiumDriverLocalService.java | 4 +-- .../internal/AppiumUserAgentFilterTest.java | 26 +++++++------------ 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java index 50e9449e0..7c68cfad1 100644 --- a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java +++ b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java @@ -16,7 +16,6 @@ package io.appium.java_client.internal.filters; -import com.google.common.annotations.VisibleForTesting; import com.google.common.net.HttpHeaders; import io.appium.java_client.internal.Config; import org.openqa.selenium.remote.http.AddSeleniumUserAgent; @@ -55,8 +54,7 @@ private static String buildUserAgentHeaderValue(@Nonnull String previousUA) { * @return whether the given User Agent includes Appium UA * like by this filter. */ - @VisibleForTesting - public static boolean containsAppiumName(@Nullable String userAgent) { + private static boolean containsAppiumName(@Nullable String userAgent) { return userAgent != null && userAgent.toLowerCase().contains(USER_AGENT_PREFIX.toLowerCase()); } 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 666f2ba06..8026300ad 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 @@ -16,7 +16,6 @@ package io.appium.java_client.service.local; -import com.google.common.annotations.VisibleForTesting; import lombok.Getter; import lombok.SneakyThrows; import org.openqa.selenium.os.ExternalProcess; @@ -403,8 +402,7 @@ public void addSlf4jLogMessageConsumer(BiConsumer userAgentParams() { - return Stream.of( - Arguments.of("selenium/4.5.0 (java mac)", false), - Arguments.of("appium/8.2.0 (selenium/4.5.0 (java mac))", true), - Arguments.of("APPIUM/8.2.0 (selenium/4.5.0 (java mac))", true), - Arguments.of("something (Appium/8.2.0 (selenium/4.5.0 (java mac)))", true), - Arguments.of("something (appium/8.2.0 (selenium/4.5.0 (java mac)))", true) - ); - } - @ParameterizedTest - @MethodSource("userAgentParams") - void validUserAgentIfContainsAppiumName(String userAgent, boolean expected) { - assertEquals(AppiumUserAgentFilter.containsAppiumName(userAgent), expected); + @ValueSource(strings = { + "appium/8.2.0 (selenium/4.5.0 (java mac))", + "APPIUM/8.2.0 (selenium/4.5.0 (java mac))", + "something (Appium/8.2.0 (selenium/4.5.0 (java mac)))", + "something (appium/8.2.0 (selenium/4.5.0 (java mac)))" + }) + void validUserAgentIfContainsAppiumName(String userAgent) { + assertEquals(AppiumUserAgentFilter.buildUserAgent(userAgent), userAgent); } @Test From f0aad8a5dc6c088b5981577cda8e2dc40b525340 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 12:10:41 +0400 Subject: [PATCH 219/314] release: v9.2.1 (#2143) --- CHANGELOG.md | 9 +++++++++ README.md | 2 +- gradle.properties | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62b862e69..d1f7f1714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.2.1_ +- **[REFACTOR]** + - Replace private usages of Guava Collections API with Java Collections API [#2136](https://github.com/appium/java-client/pull/2136) + - Remove usages of Guava's `@VisibleForTesting` annotation [#2138](https://github.com/appium/java-client/pull/2138). Previously opened internal API marked with `@VisibleForTesting` annotation is private now: + - `io.appium.java_client.internal.filters.AppiumUserAgentFilter#containsAppiumName` + - `io.appium.java_client.service.local.AppiumDriverLocalService#parseSlf4jContextFromLogMessage` +- **[DEPENDENCY CHANGE]** + - Bump minimum supported Selenium version from `4.17.0` to `4.19.0` [#2142](https://github.com/appium/java-client/pull/2142) + _9.2.0_ - **[ENHANCEMENTS]** - Incorporate poll delay mechanism into `AppiumFluentWait` [#2116](https://github.com/appium/java-client/pull/2116) (Closes [#2111](https://github.com/appium/java-client/pull/2111)) diff --git a/README.md b/README.md index b0ee499da..11084ab61 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client ---------------------------|----------------- - | `4.19.0` + `9.2.1` | `4.19.0` `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` diff --git a/gradle.properties b/gradle.properties index 306b374dd..2e3f2130e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.19.0 # Please increment the value in a release -appiumClient.version=9.2.0 +appiumClient.version=9.2.1 From bc39a230f216a594b6f1b0e428a47db5eb68e225 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 20:15:23 +0400 Subject: [PATCH 220/314] fix: Fix building of Android geo location parameters (#2146) --- build.gradle | 1 + .../geolocation/AndroidGeoLocation.java | 10 ++-- .../geolocation/AndroidGeoLocationTest.java | 59 +++++++++++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/test/java/io/appium/java_client/android/geolocation/AndroidGeoLocationTest.java diff --git a/build.gradle b/build.gradle index 15768b583..4197aa34a 100644 --- a/build.gradle +++ b/build.gradle @@ -204,6 +204,7 @@ test { testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' filter { + includeTestsMatching 'io.appium.java_client.android.geolocation.*' includeTestsMatching 'io.appium.java_client.drivers.options.*' includeTestsMatching 'io.appium.java_client.events.*' includeTestsMatching 'io.appium.java_client.internal.*' 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 f8e2d24c7..37d878642 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 @@ -111,10 +111,12 @@ public AndroidGeoLocation withSpeed(double speed) { */ public Map build() { var map = new HashMap(); - ofNullable(longitude).map(x -> map.put("longitude", x)).orElseThrow(() -> new IllegalArgumentException( - "A valid 'longitude' must be provided")); - ofNullable(latitude).map(x -> map.put("latitude", x)).orElseThrow(() -> new IllegalArgumentException( - "A valid 'latitude' must be provided")); + ofNullable(longitude).ifPresentOrElse(x -> map.put("longitude", x), () -> { + throw new IllegalArgumentException("A valid 'longitude' must be provided"); + }); + ofNullable(latitude).ifPresentOrElse(x -> map.put("latitude", x), () -> { + throw new IllegalArgumentException("A valid 'latitude' must be provided"); + }); ofNullable(altitude).ifPresent(x -> map.put("altitude", x)); ofNullable(satellites).ifPresent(x -> map.put("satellites", x)); ofNullable(speed).ifPresent(x -> map.put("speed", x)); diff --git a/src/test/java/io/appium/java_client/android/geolocation/AndroidGeoLocationTest.java b/src/test/java/io/appium/java_client/android/geolocation/AndroidGeoLocationTest.java new file mode 100644 index 000000000..47746c42a --- /dev/null +++ b/src/test/java/io/appium/java_client/android/geolocation/AndroidGeoLocationTest.java @@ -0,0 +1,59 @@ +package io.appium.java_client.android.geolocation; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class AndroidGeoLocationTest { + + @Test + void shouldThrowExceptionWhenLatitudeIsNotSet() { + var androidGeoLocation = new AndroidGeoLocation().withLongitude(24.105078); + + var exception = assertThrows(IllegalArgumentException.class, androidGeoLocation::build); + + assertEquals("A valid 'latitude' must be provided", exception.getMessage()); + } + + @Test + void shouldThrowExceptionWhenLongitudeIsNotSet() { + var androidGeoLocation = new AndroidGeoLocation().withLatitude(56.946285); + + var exception = assertThrows(IllegalArgumentException.class, androidGeoLocation::build); + + assertEquals("A valid 'longitude' must be provided", exception.getMessage()); + } + + @Test + void shodBuildMinimalParameters() { + var androidGeoLocation = new AndroidGeoLocation() + .withLongitude(24.105078) + .withLatitude(56.946285); + + assertParameters(androidGeoLocation.build(), 24.105078, 56.946285, null, null, null); + } + + @Test + void shodBuildFullParameters() { + var androidGeoLocation = new AndroidGeoLocation() + .withLongitude(24.105078) + .withLatitude(56.946285) + .withAltitude(7) + .withSpeed(1.5) + .withSatellites(12); + + assertParameters(androidGeoLocation.build(), 24.105078, 56.946285, 7.0, 1.5, 12); + } + + private static void assertParameters(Map actualParams, double longitude, double latitude, + Double altitude, Double speed, Integer satellites) { + assertEquals(longitude, actualParams.get("longitude")); + assertEquals(latitude, actualParams.get("latitude")); + assertEquals(altitude, actualParams.get("altitude")); + assertEquals(speed, actualParams.get("speed")); + assertEquals(satellites, actualParams.get("satellites")); + } +} From 3dfe243c7de29b13191594d472f9686289f496d1 Mon Sep 17 00:00:00 2001 From: Artur Lomako Date: Thu, 28 Mar 2024 18:03:22 +0100 Subject: [PATCH 221/314] fix: Fix building of Android key event parameters (#2145) Co-authored-by: Valery Yatsynovich --- .../io/appium/java_client/android/nativekey/KeyEvent.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java index 3d695e4c3..984c34cbf 100644 --- a/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java +++ b/src/main/java/io/appium/java_client/android/nativekey/KeyEvent.java @@ -83,8 +83,9 @@ public KeyEvent withFlag(KeyEventFlag keyEventFlag) { */ public Map build() { var map = new HashMap(); - ofNullable(this.keyCode).map(x -> map.put("keycode", x)).orElseThrow(() -> new IllegalStateException( - "The key code must be set")); + ofNullable(this.keyCode).ifPresentOrElse(x -> map.put("keycode", x), () -> { + throw new IllegalStateException("The key code must be set"); + }); ofNullable(this.metaState).ifPresent(x -> map.put("metastate", x)); ofNullable(this.flags).ifPresent(x -> map.put("flags", x)); return Collections.unmodifiableMap(map); From 51170c10bc3e1dede1ac033b0f9bc3a3f3497598 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 22:31:41 +0400 Subject: [PATCH 222/314] test: Add unit tests for Android `KeyEvent` (#2147) --- .../android/nativekey/KeyEventTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/java/io/appium/java_client/android/nativekey/KeyEventTest.java diff --git a/src/test/java/io/appium/java_client/android/nativekey/KeyEventTest.java b/src/test/java/io/appium/java_client/android/nativekey/KeyEventTest.java new file mode 100644 index 000000000..707da9bfa --- /dev/null +++ b/src/test/java/io/appium/java_client/android/nativekey/KeyEventTest.java @@ -0,0 +1,48 @@ +package io.appium.java_client.android.nativekey; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class KeyEventTest { + + @Test + void shouldThrowExceptionWhenKeyCodeIsNotSet() { + var keyEvent = new KeyEvent(); + + var exception = assertThrows(IllegalStateException.class, keyEvent::build); + + assertEquals("The key code must be set", exception.getMessage()); + } + + @Test + void shouldBuildMinimalParameters() { + var keyEvent = new KeyEvent().withKey(AndroidKey.A); + + Map params = keyEvent.build(); + + assertParameters(params, AndroidKey.A, null, null); + } + + @Test + void shouldBuildFullParameters() { + var keyEvent = new KeyEvent() + .withKey(AndroidKey.A) + .withMetaModifier(KeyEventMetaModifier.CAP_LOCKED) + .withFlag(KeyEventFlag.KEEP_TOUCH_MODE); + + Map params = keyEvent.build(); + + assertParameters(params, AndroidKey.A, KeyEventMetaModifier.CAP_LOCKED.getValue(), + KeyEventFlag.KEEP_TOUCH_MODE.getValue()); + } + + private static void assertParameters(Map params, AndroidKey key, Integer metastate, Integer flags) { + assertEquals(key.getCode(), params.get("keycode")); + assertEquals(metastate, params.get("metastate")); + assertEquals(flags, params.get("flags")); + } +} From a5ed8d3d6d8daadecfeeccb36e8b6e137abfe5b4 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 28 Mar 2024 23:43:27 +0400 Subject: [PATCH 223/314] release: v9.2.2 (#2148) --- CHANGELOG.md | 5 +++++ README.md | 20 ++++++++++---------- gradle.properties | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f7f1714..92823038c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.2.2_ +- **[BUG FIX]** + - fix: Fix building of Android key event parameters [#2145](https://github.com/appium/java-client/pull/2145) + - fix: Fix building of Android geo location parameters [#2146](https://github.com/appium/java-client/pull/2146) + _9.2.1_ - **[REFACTOR]** - Replace private usages of Guava Collections API with Java Collections API [#2136](https://github.com/appium/java-client/pull/2136) diff --git a/README.md b/README.md index 11084ab61..189592d79 100644 --- a/README.md +++ b/README.md @@ -93,16 +93,16 @@ dependencies { ``` ### Compatibility Matrix - Appium Java Client | Selenium client ----------------------------|----------------- - `9.2.1` | `4.19.0` - `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` - `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` - N/A | `4.14.0` - `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` - `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` - `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` - `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` + Appium Java Client | Selenium client +----------------------------------------------------------------------------------|----------------- + `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2` | `4.19.0` + `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` + `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` + N/A | `4.14.0` + `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` + `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` + `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` + `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` #### Why is it so complicated? diff --git a/gradle.properties b/gradle.properties index 2e3f2130e..b67a7114b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.19.0 # Please increment the value in a release -appiumClient.version=9.2.1 +appiumClient.version=9.2.2 From bcf39238cad72a24b55aaafaec6003e5cd11910e Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 29 Mar 2024 19:44:34 +0400 Subject: [PATCH 224/314] refactor: Replace Guava `HttpHeaders` with Selenium `HttpHeader` (#2151) --- .../java_client/internal/filters/AppiumUserAgentFilter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java index 7c68cfad1..d4f1842ce 100644 --- a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java +++ b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java @@ -16,11 +16,11 @@ package io.appium.java_client.internal.filters; -import com.google.common.net.HttpHeaders; import io.appium.java_client.internal.Config; import org.openqa.selenium.remote.http.AddSeleniumUserAgent; import org.openqa.selenium.remote.http.Filter; import org.openqa.selenium.remote.http.HttpHandler; +import org.openqa.selenium.remote.http.HttpHeader; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -81,7 +81,9 @@ public static String buildUserAgent(@Nullable String userAgent) { @Override public HttpHandler apply(HttpHandler next) { return req -> { - req.setHeader(HttpHeaders.USER_AGENT, buildUserAgent(req.getHeader(HttpHeaders.USER_AGENT))); + var originalUserAgentHeader = req.getHeader(HttpHeader.UserAgent.getName()); + var newUserAgentHeader = buildUserAgent(originalUserAgentHeader); + req.setHeader(HttpHeader.UserAgent.getName(), newUserAgentHeader); return next.execute(req); }; } From 1330a6bf485ec0f436f8add1c703c8bfdce513b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 22:51:40 +0300 Subject: [PATCH 225/314] build(deps): Bump org.owasp.dependencycheck from 9.0.10 to 9.1.0 (#2152) Bumps org.owasp.dependencycheck from 9.0.10 to 9.1.0. --- 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 4197aa34a..e3c633ce6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.0.10' + id 'org.owasp.dependencycheck' version '9.1.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 284918d6fe18ddf13c48e5a833a702dc0dbb9576 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 4 Apr 2024 18:08:45 +0400 Subject: [PATCH 226/314] test: Re-group tests into Gradle suites (#2150) --- .github/workflows/gradle.yml | 4 +- build.gradle | 150 +++++++++++------- .../AndroidAbilityToUseSupplierTest.java | 0 .../android/AndroidAppStringsTest.java | 0 .../android/AndroidConnectionTest.java | 0 .../android/AndroidContextTest.java | 0 .../android/AndroidDataMatcherTest.java | 0 .../android/AndroidDriverTest.java | 0 .../android/AndroidElementTest.java | 0 .../android/AndroidFunctionTest.java | 0 .../android/AndroidLogcatListenerTest.java | 0 .../android/AndroidScreenRecordTest.java | 0 .../android/AndroidSearchingTest.java | 0 .../java_client/android/AndroidTouchTest.java | 0 .../android/AndroidViewMatcherTest.java | 0 .../java_client/android/BaseAndroidTest.java | 4 +- .../java_client/android/BaseEspressoTest.java | 4 +- .../java_client/android/BatteryTest.java | 0 .../java_client/android/ClipboardTest.java | 0 .../android/ExecuteCDPCommandTest.java | 0 .../android/ExecuteDriverScriptTest.java | 0 .../java_client/android/FingerPrintTest.java | 0 .../android/ImagesComparisonTest.java | 0 .../java_client/android/KeyCodeTest.java | 0 .../java_client/android/LogEventTest.java | 0 .../android/OpenNotificationsTest.java | 0 .../java_client/android/SettingTest.java | 0 .../java_client/android/TestResources.java | 12 ++ .../java_client/android/UIAutomator2Test.java | 0 .../AndroidPageObjectTest.java | 0 .../MobileBrowserCompatibilityTest.java | 0 .../service/local/ServerBuilderTest.java | 27 ++-- .../local/StartingAppLocallyAndroidTest.java | 10 +- .../service/local/ThreadSafetyTest.java | 0 .../resources}/ApiDemos-debug.apk | Bin .../resources}/main.js | 0 .../io/appium/java_client/ios/AppIOSTest.java | 10 +- .../appium/java_client/ios/BaseIOSTest.java | 0 .../java_client/ios/BaseIOSWebViewTest.java | 10 +- .../java_client/ios/BaseSafariTest.java | 0 .../appium/java_client/ios/ClipboardTest.java | 0 .../appium/java_client/ios/IOSAlertTest.java | 0 .../java_client/ios/IOSAppStringsTest.java | 0 .../java_client/ios/IOSContextTest.java | 0 .../appium/java_client/ios/IOSDriverTest.java | 0 .../java_client/ios/IOSElementTest.java | 0 .../ios/IOSNativeWebTapSettingTest.java | 0 .../java_client/ios/IOSScreenRecordTest.java | 0 .../java_client/ios/IOSSearchingTest.java | 0 .../ios/IOSSyslogListenerTest.java | 0 .../java_client/ios/IOSWebViewTest.java | 0 .../java_client/ios/ImagesComparisonTest.java | 0 .../appium/java_client/ios/RotationTest.java | 0 .../appium/java_client/ios/SettingTest.java | 0 .../pagefactory_tests/XCUITModeTest.java | 0 .../local/StartingAppLocallyIosTest.java | 12 +- .../resources}/TestApp.app.zip | Bin .../resources}/UICatalog.app.zip | Bin .../apps => e2eIosTest/resources}/vodqa.zip | Bin .../io/appium/java_client/TestResources.java | 34 ---- .../java/io/appium/java_client/TestUtils.java | 14 +- .../DesktopBrowserCompatibilityTest.java | 6 +- src/test/resources/apps/IntentExample.apk | Bin 50008 -> 0 bytes 63 files changed, 150 insertions(+), 147 deletions(-) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidAppStringsTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidConnectionTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidContextTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidDataMatcherTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidDriverTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidElementTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidFunctionTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidLogcatListenerTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidScreenRecordTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidSearchingTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidTouchTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/AndroidViewMatcherTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/BaseAndroidTest.java (94%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/BaseEspressoTest.java (93%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/BatteryTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/ClipboardTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/ExecuteCDPCommandTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/ExecuteDriverScriptTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/FingerPrintTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/ImagesComparisonTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/KeyCodeTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/LogEventTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/OpenNotificationsTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/SettingTest.java (100%) create mode 100644 src/e2eAndroidTest/java/io/appium/java_client/android/TestResources.java rename src/{test => e2eAndroidTest}/java/io/appium/java_client/android/UIAutomator2Test.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java (100%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/service/local/ServerBuilderTest.java (92%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java (92%) rename src/{test => e2eAndroidTest}/java/io/appium/java_client/service/local/ThreadSafetyTest.java (100%) rename src/{test/resources/apps => e2eAndroidTest/resources}/ApiDemos-debug.apk (100%) rename src/{test/java/io/appium/java_client/service/local => e2eAndroidTest/resources}/main.js (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/AppIOSTest.java (73%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/BaseIOSTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/BaseIOSWebViewTest.java (92%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/BaseSafariTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/ClipboardTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSAlertTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSAppStringsTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSContextTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSDriverTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSElementTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSScreenRecordTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSSearchingTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSSyslogListenerTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/IOSWebViewTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/ImagesComparisonTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/RotationTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/ios/SettingTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java (100%) rename src/{test => e2eIosTest}/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java (92%) rename src/{test/resources/apps => e2eIosTest/resources}/TestApp.app.zip (100%) rename src/{test/resources/apps => e2eIosTest/resources}/UICatalog.app.zip (100%) rename src/{test/resources/apps => e2eIosTest/resources}/vodqa.zip (100%) delete mode 100644 src/test/java/io/appium/java_client/TestResources.java delete mode 100644 src/test/resources/apps/IntentExample.apk diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c293b7b40..9a5972673 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -81,7 +81,7 @@ jobs: if: matrix.e2e-tests == 'android' uses: reactivecircus/android-emulator-runner@v2 with: - script: ./gradlew uiAutomationTest -PisCI -Pselenium.version=$latest_snapshot + script: ./gradlew e2eAndroidTest -PisCI -Pselenium.version=$latest_snapshot api-level: ${{ env.ANDROID_SDK_VERSION }} avd-name: ${{ env.ANDROID_EMU_NAME }} sdcard-path-or-size: 1500M @@ -108,4 +108,4 @@ jobs: xcrun simctl bootstatus $target_sim_id -b - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' - run: ./gradlew xcuiTest -PisCI -Pselenium.version=$latest_snapshot + run: ./gradlew e2eIosTest -PisCI -Pselenium.version=$latest_snapshot diff --git a/build.gradle b/build.gradle index e3c633ce6..da8dd2da3 100644 --- a/build.gradle +++ b/build.gradle @@ -67,15 +67,6 @@ dependencies { } implementation 'com.google.code.gson:gson:2.10.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation (group: 'io.github.bonigarcia', name: 'webdrivermanager', version: '5.7.0') { - exclude group: 'org.seleniumhq.selenium' - } - testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } dependencyCheck { @@ -199,51 +190,104 @@ processResources { ] } -test { - useJUnitPlatform() - testLogging.showStandardStreams = true - testLogging.exceptionFormat = 'full' - filter { - includeTestsMatching 'io.appium.java_client.android.geolocation.*' - includeTestsMatching 'io.appium.java_client.drivers.options.*' - includeTestsMatching 'io.appium.java_client.events.*' - includeTestsMatching 'io.appium.java_client.internal.*' - includeTestsMatching 'io.appium.java_client.pagefactory_tests.DesktopBrowserCompatibilityTest' - includeTestsMatching 'io.appium.java_client.pagefactory_tests.TimeoutTest' - includeTestsMatching 'io.appium.java_client.proxy.*' - includeTestsMatching 'io.appium.java_client.remote.*' - includeTestsMatching 'io.appium.java_client.touch.*' - } - finalizedBy jacocoTestReport -} +testing { + suites { + configureEach { + useJUnitJupiter() + dependencies { + implementation 'org.junit.jupiter:junit-jupiter:5.10.2' + runtimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.hamcrest:hamcrest:2.2' + runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" + } + targets.configureEach { + testTask.configure { + testLogging { + showStandardStreams = true + exceptionFormat = 'full' + } + } + } + } -tasks.register('xcuiTest', Test) { - useJUnitPlatform() - testLogging.showStandardStreams = true - testLogging.exceptionFormat = 'full' - filter { - includeTestsMatching 'io.appium.java_client.ios.*' - includeTestsMatching '*.pagefactory_tests.XCUITModeTest' - includeTestsMatching '*.pagefactory_tests.widget.tests.combined.*' - includeTestsMatching '*.pagefactory_tests.widget.tests.ios.*' - includeTestsMatching 'io.appium.java_client.service.local.StartingAppLocallyIosTest' - exclude '**/IOSScreenRecordTest.class' - exclude '**/ImagesComparisonTest.class' - } -} + test { + dependencies { + implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" + implementation('io.github.bonigarcia:webdrivermanager:5.7.0') { + exclude group: 'org.seleniumhq.selenium' + } + } + targets.configureEach { + testTask.configure { + finalizedBy jacocoTestReport + } + } + } + + e2eIosTest(JvmTestSuite) { + sources { + java { + srcDirs = ['src/e2eIosTest/java'] + } + } + dependencies { + implementation project() + implementation(sourceSets.test.output) + implementation('org.apache.commons:commons-lang3:3.14.0') + } -tasks.register('uiAutomationTest', Test) { - useJUnitPlatform() - testLogging.showStandardStreams = true - testLogging.exceptionFormat = 'full' - filter { - includeTestsMatching 'io.appium.java_client.android.SettingTest' - includeTestsMatching 'io.appium.java_client.android.ClipboardTest' - includeTestsMatching 'io.appium.java_client.android.OpenNotificationsTest' - includeTestsMatching '*.AndroidAppStringsTest' - includeTestsMatching '*.pagefactory_tests.widget.tests.android.*' - includeTestsMatching 'io.appium.java_client.service.local.StartingAppLocallyAndroidTest' - includeTestsMatching 'io.appium.java_client.service.local.ServerBuilderTest' - includeTestsMatching 'io.appium.java_client.service.local.ThreadSafetyTest' + targets.configureEach { + testTask.configure { + shouldRunAfter(test) + filter { + exclude '**/IOSScreenRecordTest.class' + exclude '**/ImagesComparisonTest.class' + } + } + } + } + + e2eAndroidTest(JvmTestSuite) { + sources { + java { + srcDirs = ['src/e2eAndroidTest/java'] + } + } + dependencies { + implementation project() + implementation(sourceSets.test.output) + implementation('io.github.bonigarcia:webdrivermanager:5.7.0') { + exclude group: 'org.seleniumhq.selenium' + } + } + + targets.configureEach { + testTask.configure { + shouldRunAfter(test) + filter { + // The following tests fail and should be reviewed/fixed + exclude '**/AndroidAbilityToUseSupplierTest.class' + exclude '**/AndroidConnectionTest.class' + exclude '**/AndroidContextTest.class' + exclude '**/AndroidDataMatcherTest.class' + exclude '**/AndroidDriverTest.class' + exclude '**/AndroidElementTest.class' + exclude '**/AndroidFunctionTest.class' + exclude '**/AndroidSearchingTest.class' + exclude '**/AndroidTouchTest.class' + exclude '**/AndroidViewMatcherTest.class' + exclude '**/ExecuteCDPCommandTest.class' + exclude '**/ExecuteDriverScriptTest.class' + exclude '**/FingerPrintTest.class' + exclude '**/ImagesComparisonTest.class' + exclude '**/KeyCodeTest.class' + exclude '**/LogEventTest.class' + exclude '**/UIAutomator2Test.class' + exclude '**/AndroidPageObjectTest.class' + exclude '**/MobileBrowserCompatibilityTest.class' + } + } + } + } } } diff --git a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAppStringsTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidAppStringsTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAppStringsTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidConnectionTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidConnectionTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidConnectionTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidConnectionTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidContextTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidContextTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidDataMatcherTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidDataMatcherTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidDataMatcherTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidDriverTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidDriverTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidDriverTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidElementTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidElementTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidElementTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidElementTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidFunctionTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidFunctionTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidLogcatListenerTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidLogcatListenerTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidLogcatListenerTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidScreenRecordTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidScreenRecordTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidScreenRecordTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidSearchingTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidSearchingTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidSearchingTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidSearchingTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidTouchTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java diff --git a/src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidViewMatcherTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/AndroidViewMatcherTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/AndroidViewMatcherTest.java diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java similarity index 94% rename from src/test/java/io/appium/java_client/android/BaseAndroidTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java index e14343d75..347304cf5 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java @@ -24,8 +24,6 @@ import java.util.Map; -import static io.appium.java_client.TestResources.apiDemosApk; - @SuppressWarnings("checkstyle:HideUtilityClassConstructor") public class BaseAndroidTest { public static final String APP_ID = "io.appium.android.apis"; @@ -46,7 +44,7 @@ public class BaseAndroidTest { UiAutomator2Options options = new UiAutomator2Options() .setDeviceName("Android Emulator") - .setApp(apiDemosApk().toAbsolutePath().toString()) + .setApp(TestResources.API_DEMOS_APK.toString()) .eventTimings(); driver = new AndroidDriver(service.getUrl(), options); } diff --git a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseEspressoTest.java similarity index 93% rename from src/test/java/io/appium/java_client/android/BaseEspressoTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/BaseEspressoTest.java index d312587bd..f26469cb8 100644 --- a/src/test/java/io/appium/java_client/android/BaseEspressoTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseEspressoTest.java @@ -22,8 +22,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import static io.appium.java_client.TestResources.apiDemosApk; - @SuppressWarnings("checkstyle:HideUtilityClassConstructor") public class BaseEspressoTest { @@ -44,7 +42,7 @@ public class BaseEspressoTest { EspressoOptions options = new EspressoOptions() .setDeviceName("Android Emulator") - .setApp(apiDemosApk().toAbsolutePath().toString()) + .setApp(TestResources.API_DEMOS_APK.toString()) .eventTimings(); driver = new AndroidDriver(service.getUrl(), options); } diff --git a/src/test/java/io/appium/java_client/android/BatteryTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/BatteryTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/BatteryTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/BatteryTest.java diff --git a/src/test/java/io/appium/java_client/android/ClipboardTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/ClipboardTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/ClipboardTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/ClipboardTest.java diff --git a/src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/ExecuteCDPCommandTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/ExecuteCDPCommandTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/ExecuteCDPCommandTest.java diff --git a/src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/ExecuteDriverScriptTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/ExecuteDriverScriptTest.java diff --git a/src/test/java/io/appium/java_client/android/FingerPrintTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/FingerPrintTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/FingerPrintTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/FingerPrintTest.java diff --git a/src/test/java/io/appium/java_client/android/ImagesComparisonTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/ImagesComparisonTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/ImagesComparisonTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/ImagesComparisonTest.java diff --git a/src/test/java/io/appium/java_client/android/KeyCodeTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/KeyCodeTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/KeyCodeTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/KeyCodeTest.java diff --git a/src/test/java/io/appium/java_client/android/LogEventTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/LogEventTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/LogEventTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/LogEventTest.java diff --git a/src/test/java/io/appium/java_client/android/OpenNotificationsTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/OpenNotificationsTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/OpenNotificationsTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/OpenNotificationsTest.java diff --git a/src/test/java/io/appium/java_client/android/SettingTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/SettingTest.java similarity index 100% rename from src/test/java/io/appium/java_client/android/SettingTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/SettingTest.java diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/TestResources.java b/src/e2eAndroidTest/java/io/appium/java_client/android/TestResources.java new file mode 100644 index 000000000..149a72a4a --- /dev/null +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/TestResources.java @@ -0,0 +1,12 @@ +package io.appium.java_client.android; + +import io.appium.java_client.TestUtils; + +import java.nio.file.Path; + +public class TestResources { + public static final Path API_DEMOS_APK = TestUtils.resourcePathToAbsolutePath("ApiDemos-debug.apk"); + + private TestResources() { + } +} diff --git a/src/test/java/io/appium/java_client/android/UIAutomator2Test.java b/src/e2eAndroidTest/java/io/appium/java_client/android/UIAutomator2Test.java similarity index 100% rename from src/test/java/io/appium/java_client/android/UIAutomator2Test.java rename to src/e2eAndroidTest/java/io/appium/java_client/android/UIAutomator2Test.java diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java b/src/e2eAndroidTest/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java similarity index 100% rename from src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java b/src/e2eAndroidTest/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java similarity index 100% rename from src/test/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/pagefactory_tests/MobileBrowserCompatibilityTest.java diff --git a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java b/src/e2eAndroidTest/java/io/appium/java_client/service/local/ServerBuilderTest.java similarity index 92% rename from src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/service/local/ServerBuilderTest.java index 9822e9417..d482284b2 100644 --- a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/service/local/ServerBuilderTest.java @@ -1,5 +1,7 @@ package io.appium.java_client.service.local; +import io.appium.java_client.TestUtils; +import io.appium.java_client.android.TestResources; import io.appium.java_client.android.options.UiAutomator2Options; import io.github.bonigarcia.wdm.WebDriverManager; import org.junit.jupiter.api.AfterEach; @@ -15,7 +17,6 @@ import java.util.List; import java.util.Map; -import static io.appium.java_client.TestResources.apiDemosApk; import static io.appium.java_client.TestUtils.getLocalIp4Address; import static io.appium.java_client.service.local.AppiumDriverLocalService.buildDefaultService; import static io.appium.java_client.service.local.AppiumServiceBuilder.APPIUM_PATH; @@ -27,7 +28,6 @@ import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static java.lang.System.getProperty; import static java.lang.System.setProperty; -import static java.nio.file.FileSystems.getDefault; import static java.util.Arrays.asList; import static java.util.Optional.ofNullable; import static java.util.concurrent.TimeUnit.SECONDS; @@ -49,14 +49,10 @@ class ServerBuilderTest { */ private static final String PATH_TO_APPIUM_NODE_IN_PROPERTIES = getProperty(APPIUM_PATH); - private static final Path ROOT_TEST_PATH = getDefault().getPath("src") - .resolve("test").resolve("java").resolve("io").resolve("appium").resolve("java_client"); - /** * This is the path to the stub main.js file */ - private static final Path PATH_T0_TEST_MAIN_JS = ROOT_TEST_PATH - .resolve("service").resolve("local").resolve("main.js"); + private static final Path PATH_T0_TEST_MAIN_JS = TestUtils.resourcePathToAbsolutePath("main.js"); private static String testIP; private AppiumDriverLocalService service; @@ -111,18 +107,14 @@ void checkAbilityToStartDefaultService() { @Test void checkAbilityToFindNodeDefinedInProperties() { - File definedNode = PATH_T0_TEST_MAIN_JS.toFile(); - setProperty(APPIUM_PATH, definedNode.getAbsolutePath()); - assertThat(new AppiumServiceBuilder().createArgs().get(0), is(definedNode.getAbsolutePath())); + setProperty(APPIUM_PATH, PATH_T0_TEST_MAIN_JS.toString()); + assertThat(new AppiumServiceBuilder().createArgs().get(0), is(PATH_T0_TEST_MAIN_JS.toString())); } @Test void checkAbilityToUseNodeDefinedExplicitly() { - File mainJS = PATH_T0_TEST_MAIN_JS.toFile(); - AppiumServiceBuilder builder = new AppiumServiceBuilder() - .withAppiumJS(mainJS); - assertThat(builder.createArgs().get(0), - is(mainJS.getAbsolutePath())); + AppiumServiceBuilder builder = new AppiumServiceBuilder().withAppiumJS(PATH_T0_TEST_MAIN_JS.toFile()); + assertThat(builder.createArgs().get(0), is(PATH_T0_TEST_MAIN_JS.toString())); } @Test @@ -156,7 +148,7 @@ void checkAbilityToStartServiceUsingCapabilities() { .setNewCommandTimeout(Duration.ofSeconds(60)) .setAppPackage("io.appium.android.apis") .setAppActivity(".view.WebView1") - .setApp(apiDemosApk().toAbsolutePath().toString()) + .setApp(TestResources.API_DEMOS_APK.toString()) .setChromedriverExecutable(chromeManager.getDownloadedDriverPath()); service = new AppiumServiceBuilder().withCapabilities(options).build(); @@ -166,14 +158,13 @@ void checkAbilityToStartServiceUsingCapabilities() { @Test void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { - File app = ROOT_TEST_PATH.resolve("ApiDemos-debug.apk").toFile(); UiAutomator2Options options = new UiAutomator2Options() .fullReset() .setNewCommandTimeout(Duration.ofSeconds(60)) .setAppPackage("io.appium.android.apis") .setAppActivity(".view.WebView1") - .setApp(app.getAbsolutePath()) + .setApp(TestResources.API_DEMOS_APK.toString()) .setChromedriverExecutable(chromeManager.getDownloadedDriverPath()) .amend("winPath", "C:\\selenium\\app.apk") .amend("unixPath", "/selenium/app.apk") diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java b/src/e2eAndroidTest/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java similarity index 92% rename from src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java index 157077f88..77a7cc585 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/service/local/StartingAppLocallyAndroidTest.java @@ -17,6 +17,7 @@ package io.appium.java_client.service.local; import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.android.TestResources; import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.MobilePlatform; @@ -27,7 +28,6 @@ import java.time.Duration; -import static io.appium.java_client.TestResources.apiDemosApk; import static io.appium.java_client.remote.options.SupportsAppOption.APP_OPTION; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; import static io.appium.java_client.remote.options.SupportsDeviceNameOption.DEVICE_NAME_OPTION; @@ -44,7 +44,7 @@ void startingAndroidAppWithCapabilitiesOnlyTest() { AndroidDriver driver = new AndroidDriver(new UiAutomator2Options() .setDeviceName("Android Emulator") .autoGrantPermissions() - .setApp(apiDemosApk().toAbsolutePath().toString())); + .setApp(TestResources.API_DEMOS_APK.toString())); try { Capabilities caps = driver.getCapabilities(); @@ -53,7 +53,7 @@ void startingAndroidAppWithCapabilitiesOnlyTest() { ); assertEquals(AutomationName.ANDROID_UIAUTOMATOR2, caps.getCapability(AUTOMATION_NAME_OPTION)); assertNotNull(caps.getCapability(DEVICE_NAME_OPTION)); - assertEquals(apiDemosApk().toAbsolutePath().toString(), caps.getCapability(APP_OPTION)); + assertEquals(TestResources.API_DEMOS_APK.toString(), caps.getCapability(APP_OPTION)); } finally { driver.quit(); } @@ -68,7 +68,7 @@ void startingAndroidAppWithCapabilitiesAndServiceTest() { AndroidDriver driver = new AndroidDriver(builder, new UiAutomator2Options() .setDeviceName("Android Emulator") .autoGrantPermissions() - .setApp(apiDemosApk().toAbsolutePath().toString())); + .setApp(TestResources.API_DEMOS_APK.toString())); try { Capabilities caps = driver.getCapabilities(); @@ -88,7 +88,7 @@ void startingAndroidAppWithCapabilitiesAndFlagsOnServerSideTest() { .fullReset() .autoGrantPermissions() .setNewCommandTimeout(Duration.ofSeconds(60)) - .setApp(apiDemosApk().toAbsolutePath().toString()); + .setApp(TestResources.API_DEMOS_APK.toString()); WebDriverManager chromeManager = chromedriver(); chromeManager.setup(); diff --git a/src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java b/src/e2eAndroidTest/java/io/appium/java_client/service/local/ThreadSafetyTest.java similarity index 100% rename from src/test/java/io/appium/java_client/service/local/ThreadSafetyTest.java rename to src/e2eAndroidTest/java/io/appium/java_client/service/local/ThreadSafetyTest.java diff --git a/src/test/resources/apps/ApiDemos-debug.apk b/src/e2eAndroidTest/resources/ApiDemos-debug.apk similarity index 100% rename from src/test/resources/apps/ApiDemos-debug.apk rename to src/e2eAndroidTest/resources/ApiDemos-debug.apk diff --git a/src/test/java/io/appium/java_client/service/local/main.js b/src/e2eAndroidTest/resources/main.js similarity index 100% rename from src/test/java/io/appium/java_client/service/local/main.js rename to src/e2eAndroidTest/resources/main.js diff --git a/src/test/java/io/appium/java_client/ios/AppIOSTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java similarity index 73% rename from src/test/java/io/appium/java_client/ios/AppIOSTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java index b049a15bb..25574d727 100644 --- a/src/test/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java @@ -1,26 +1,26 @@ package io.appium.java_client.ios; +import io.appium.java_client.TestUtils; import io.appium.java_client.ios.options.XCUITestOptions; import org.junit.jupiter.api.BeforeAll; import org.openqa.selenium.SessionNotCreatedException; import java.time.Duration; -import static io.appium.java_client.TestResources.testAppZip; - public class AppIOSTest extends BaseIOSTest { + protected static final String BUNDLE_ID = "io.appium.TestApp"; - public static final String BUNDLE_ID = "io.appium.TestApp"; + private static final String TEST_APP_ZIP = TestUtils.resourcePathToAbsolutePath("TestApp.app.zip").toString(); @BeforeAll - public static void beforeClass() throws Exception { + public static void beforeClass() { startAppiumServer(); XCUITestOptions options = new XCUITestOptions() .setPlatformVersion(PLATFORM_VERSION) .setDeviceName(DEVICE_NAME) .setCommandTimeouts(Duration.ofSeconds(240)) - .setApp(testAppZip().toAbsolutePath().toString()) + .setApp(TEST_APP_ZIP) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT); try { driver = new IOSDriver(service.getUrl(), options); diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/BaseIOSTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSTest.java diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java similarity index 92% rename from src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index c461ded8a..f975d3c5b 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -16,29 +16,29 @@ package io.appium.java_client.ios; +import io.appium.java_client.TestUtils; import io.appium.java_client.ios.options.XCUITestOptions; import org.junit.jupiter.api.BeforeAll; import org.openqa.selenium.SessionNotCreatedException; -import java.io.IOException; 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 String VODQA_ZIP = TestUtils.resourcePathToAbsolutePath("vodqa.zip").toString(); + private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(1); private static final Duration WEB_VIEW_DETECT_DURATION = Duration.ofSeconds(15); @BeforeAll - public static void beforeClass() throws IOException { + public static void beforeClass() { startAppiumServer(); XCUITestOptions options = new XCUITestOptions() .setDeviceName(DEVICE_NAME) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT) .setCommandTimeouts(Duration.ofSeconds(240)) - .setApp(vodQaAppZip().toAbsolutePath().toString()); + .setApp(VODQA_ZIP); Supplier createDriver = () -> new IOSDriver(service.getUrl(), options); try { driver = createDriver.get(); diff --git a/src/test/java/io/appium/java_client/ios/BaseSafariTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/BaseSafariTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java diff --git a/src/test/java/io/appium/java_client/ios/ClipboardTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/ClipboardTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/ClipboardTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/ClipboardTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSAlertTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSAlertTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSAlertTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSAlertTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSAppStringsTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSAppStringsTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSAppStringsTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSContextTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSContextTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSDriverTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSDriverTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSDriverTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSElementTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSElementTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSElementTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSElementTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSScreenRecordTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSScreenRecordTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSScreenRecordTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSSearchingTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSSearchingTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSSearchingTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSSearchingTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSSyslogListenerTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSSyslogListenerTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSSyslogListenerTest.java diff --git a/src/test/java/io/appium/java_client/ios/IOSWebViewTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/IOSWebViewTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java diff --git a/src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/ImagesComparisonTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/ImagesComparisonTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/ImagesComparisonTest.java diff --git a/src/test/java/io/appium/java_client/ios/RotationTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/RotationTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/RotationTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/RotationTest.java diff --git a/src/test/java/io/appium/java_client/ios/SettingTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/SettingTest.java similarity index 100% rename from src/test/java/io/appium/java_client/ios/SettingTest.java rename to src/e2eIosTest/java/io/appium/java_client/ios/SettingTest.java diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java b/src/e2eIosTest/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java similarity index 100% rename from src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java rename to src/e2eIosTest/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java diff --git a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java b/src/e2eIosTest/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java similarity index 92% rename from src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java rename to src/e2eIosTest/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java index 5041231ec..b262e93af 100644 --- a/src/test/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/service/local/StartingAppLocallyIosTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.service.local; +import io.appium.java_client.TestUtils; import io.appium.java_client.ios.BaseIOSTest; import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.ios.options.XCUITestOptions; @@ -26,7 +27,6 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; -import static io.appium.java_client.TestResources.uiCatalogAppZip; import static io.appium.java_client.remote.options.SupportsDeviceNameOption.DEVICE_NAME_OPTION; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -35,12 +35,14 @@ import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; class StartingAppLocallyIosTest { + private static final String UI_CATALOG_ZIP = TestUtils.resourcePathToAbsolutePath("UICatalog.app.zip").toString(); + @Test void startingIOSAppWithCapabilitiesOnlyTest() { XCUITestOptions options = new XCUITestOptions() .setPlatformVersion(BaseIOSTest.PLATFORM_VERSION) .setDeviceName(BaseIOSTest.DEVICE_NAME) - .setApp(uiCatalogAppZip().toAbsolutePath().toString()) + .setApp(UI_CATALOG_ZIP) .setWdaLaunchTimeout(BaseIOSTest.WDA_LAUNCH_TIMEOUT); IOSDriver driver = new IOSDriver(options); try { @@ -50,7 +52,7 @@ void startingIOSAppWithCapabilitiesOnlyTest() { assertEquals(Platform.IOS, caps.getPlatformName()); assertNotNull(caps.getDeviceName().orElse(null)); assertEquals(BaseIOSTest.PLATFORM_VERSION, caps.getPlatformVersion().orElse(null)); - assertEquals(uiCatalogAppZip().toAbsolutePath().toString(), caps.getApp().orElse(null)); + assertEquals(UI_CATALOG_ZIP, caps.getApp().orElse(null)); } finally { driver.quit(); } @@ -62,7 +64,7 @@ void startingIOSAppWithCapabilitiesAndServiceTest() { XCUITestOptions options = new XCUITestOptions() .setPlatformVersion(BaseIOSTest.PLATFORM_VERSION) .setDeviceName(BaseIOSTest.DEVICE_NAME) - .setApp(uiCatalogAppZip().toAbsolutePath().toString()) + .setApp(UI_CATALOG_ZIP) .setWdaLaunchTimeout(BaseIOSTest.WDA_LAUNCH_TIMEOUT); AppiumServiceBuilder builder = new AppiumServiceBuilder() @@ -89,7 +91,7 @@ void startingIOSAppWithCapabilitiesAndFlagsOnServerSideTest() { .setWdaLaunchTimeout(BaseIOSTest.WDA_LAUNCH_TIMEOUT); XCUITestOptions clientOptions = new XCUITestOptions() - .setApp(uiCatalogAppZip().toAbsolutePath().toString()); + .setApp(UI_CATALOG_ZIP); AppiumServiceBuilder builder = new AppiumServiceBuilder() .withArgument(GeneralServerFlag.SESSION_OVERRIDE) diff --git a/src/test/resources/apps/TestApp.app.zip b/src/e2eIosTest/resources/TestApp.app.zip similarity index 100% rename from src/test/resources/apps/TestApp.app.zip rename to src/e2eIosTest/resources/TestApp.app.zip diff --git a/src/test/resources/apps/UICatalog.app.zip b/src/e2eIosTest/resources/UICatalog.app.zip similarity index 100% rename from src/test/resources/apps/UICatalog.app.zip rename to src/e2eIosTest/resources/UICatalog.app.zip diff --git a/src/test/resources/apps/vodqa.zip b/src/e2eIosTest/resources/vodqa.zip similarity index 100% rename from src/test/resources/apps/vodqa.zip rename to src/e2eIosTest/resources/vodqa.zip diff --git a/src/test/java/io/appium/java_client/TestResources.java b/src/test/java/io/appium/java_client/TestResources.java deleted file mode 100644 index 6928cdcaf..000000000 --- a/src/test/java/io/appium/java_client/TestResources.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.appium.java_client; - -import java.nio.file.Path; - -import static io.appium.java_client.TestUtils.resourcePathToLocalPath; - -public class TestResources { - private 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 aaf254f75..1d650777c 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -6,14 +6,12 @@ import org.openqa.selenium.WebElement; import javax.annotation.Nullable; -import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; @@ -31,26 +29,18 @@ public static String getLocalIp4Address() throws SocketException, UnknownHostExc } } - public static Path resourcePathToLocalPath(String resourcePath) { + public static Path resourcePathToAbsolutePath(String resourcePath) { URL url = ClassLoader.getSystemResource(resourcePath); if (url == null) { throw new IllegalArgumentException(String.format("Cannot find the '%s' resource", resourcePath)); } try { - return Paths.get(url.toURI()); + return Paths.get(url.toURI()).toAbsolutePath(); } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } - public static String resourceAsString(String resourcePath) { - try { - return new String(Files.readAllBytes(resourcePathToLocalPath(resourcePath))); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public static void waitUntilTrue(Supplier func, Duration timeout, Duration interval) { long started = System.currentTimeMillis(); RuntimeException lastError = null; 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 0a890dc67..40c672ae7 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 io.appium.java_client.TestUtils; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.pagefactory.AndroidFindBy; import io.appium.java_client.pagefactory.AppiumFieldDecorator; @@ -33,7 +34,6 @@ import java.util.List; -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; @@ -41,6 +41,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; public class DesktopBrowserCompatibilityTest { + private static final String HELLO_APPIUM_HTML = + TestUtils.resourcePathToAbsolutePath("html/hello appium - saved page.htm").toUri().toString(); @HowToUseLocators(iOSXCUITAutomation = ALL_POSSIBLE) @AndroidFindBy(className = "someClass") @@ -62,7 +64,7 @@ public class DesktopBrowserCompatibilityTest { WebDriver driver = new ChromeDriver(new ChromeOptions().addArguments("--headless=new")); try { PageFactory.initElements(new AppiumFieldDecorator(driver, ofSeconds(15)), this); - driver.get(helloAppiumHtml().toUri().toString()); + driver.get(HELLO_APPIUM_HTML); assertNotEquals(0, foundLinks.size()); assertNotEquals(0, main.size()); assertNull(trap1); diff --git a/src/test/resources/apps/IntentExample.apk b/src/test/resources/apps/IntentExample.apk deleted file mode 100644 index 196ea90940af607535d0cf5c57e0c22c1d3e4af2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50008 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Ž)U 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 C0*Da+ From cfb815694fefc2d5d3a3fbfcae2de16402dd7ad6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:50:16 +0300 Subject: [PATCH 227/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2155) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.7.0 to 5.8.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-5.7.0...webdrivermanager-5.8.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index da8dd2da3..9a981364b 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.7.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.8.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -256,7 +256,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.7.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.8.0') { exclude group: 'org.seleniumhq.selenium' } } From 989153fd4a985b5472174376a8911a57fa2df184 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:26:31 +0300 Subject: [PATCH 228/314] build(deps): Bump gradle/wrapper-validation-action from 2 to 3 (#2159) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 2 to 3. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v2...v3) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action 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> --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 531000b79..e3df8193e 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -24,4 +24,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v2 + - uses: gradle/wrapper-validation-action@v3 From fc03b6cb9e3e79dc0e0bf41fc6e271080586b436 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:51:12 +0300 Subject: [PATCH 229/314] build(deps): Bump slf4jVersion from 2.0.12 to 2.0.13 (#2158) Bumps `slf4jVersion` from 2.0.12 to 2.0.13. Updates `org.slf4j:slf4j-api` from 2.0.12 to 2.0.13 Updates `org.slf4j:slf4j-simple` from 2.0.12 to 2.0.13 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 9a981364b..719b1921e 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.12' + slf4jVersion = '2.0.13' } dependencies { From 57e431c3c1fc3f77f41e6973be02eac833f30404 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 16 Apr 2024 08:17:48 +0300 Subject: [PATCH 230/314] ci: Use non-deprecated GH action for Gradle wrapper validation (#2160) https://github.com/gradle/wrapper-validation-action/releases/tag/v3.3.0: As of v3 this action has been deprecated by gradle/actions/wrapper-validation. Any workflow that uses gradle/wrapper-validation-action@v3 will transparently delegate to gradle/actions/wrapper-validation@v3. --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index e3df8193e..72a15d717 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -24,4 +24,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v3 + - uses: gradle/actions/wrapper-validation@v3 From 496417f296df2176132cd6a5c6178ba278249b10 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 5 May 2024 07:28:21 +0200 Subject: [PATCH 231/314] ci: Update the flow for Android e2e tests (#2166) --- .github/workflows/gradle.yml | 11 +++++++++-- .../io/appium/java_client/ios/BaseSafariTest.java | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 9a5972673..c83581927 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -37,7 +37,7 @@ jobs: platform: macos-12 e2e-tests: ios - java: 17 - platform: macos-latest + platform: ubuntu-latest e2e-tests: android - java: 21 platform: ubuntu-latest @@ -49,6 +49,13 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Enable KVM group perms + if: matrix.e2e-tests == 'android' + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v4 with: @@ -84,8 +91,8 @@ jobs: script: ./gradlew e2eAndroidTest -PisCI -Pselenium.version=$latest_snapshot api-level: ${{ env.ANDROID_SDK_VERSION }} avd-name: ${{ env.ANDROID_EMU_NAME }} - sdcard-path-or-size: 1500M disable-spellchecker: true + disable-animations: true target: ${{ env.ANDROID_EMU_TARGET }} - name: Select Xcode diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java index 654c4e58b..d35c76357 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java @@ -21,8 +21,10 @@ import org.junit.jupiter.api.BeforeAll; import java.io.IOException; +import java.time.Duration; public class BaseSafariTest extends BaseIOSTest { + private static final Duration WEBVIEW_CONNECT_TIMEOUT = Duration.ofSeconds(30); @BeforeAll public static void beforeClass() throws IOException { startAppiumServer(); @@ -31,6 +33,7 @@ public class BaseSafariTest extends BaseIOSTest { .withBrowserName(MobileBrowserType.SAFARI) .setDeviceName(DEVICE_NAME) .setPlatformVersion(PLATFORM_VERSION) + .setWebviewConnectTimeout(WEBVIEW_CONNECT_TIMEOUT) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT); driver = new IOSDriver(service.getUrl(), options); } From 6f83f1d223f306c6bd6b14b2ac20a286e446ac00 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 9 May 2024 09:35:30 +0200 Subject: [PATCH 232/314] ci: Bump conventional-pr-action to v3 --- .github/workflows/pr-title.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index f3387333b..393ca3bd6 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -8,8 +8,8 @@ jobs: name: https://www.conventionalcommits.org runs-on: ubuntu-latest steps: - - uses: beemojs/conventional-pr-action@v2 + - uses: beemojs/conventional-pr-action@v3 with: - config-preset: angular + config-preset: angular env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 3efeaebc8964423a8a1619305f343d2ec7128149 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 15 May 2024 07:26:22 +0200 Subject: [PATCH 233/314] fix: Properly represent FeaturesMatchingResult model if `multiple` option is enabled (#2170) --- .../io/appium/java_client/ComparesImages.java | 3 +- .../imagecomparison/ComparisonResult.java | 17 +- .../FeaturesMatchingResult.java | 12 +- .../OccurrenceMatchingResult.java | 150 ++++++++++++++++-- .../SimilarityMatchingResult.java | 7 +- 5 files changed, 153 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 1b29c35cf..5a9a58b1c 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -126,8 +126,7 @@ default OccurrenceMatchingResult findImageOccurrence(byte[] fullImage, byte[] pa @Nullable OccurrenceMatchingOptions options) { Object response = CommandExecutionHelper.execute(this, compareImagesCommand(ComparisonMode.MATCH_TEMPLATE, fullImage, partialImage, options)); - //noinspection unchecked - return new OccurrenceMatchingResult((Map) response); + return new OccurrenceMatchingResult(response); } /** 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 49e487b59..0fba408d4 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/ComparisonResult.java @@ -16,8 +16,6 @@ package io.appium.java_client.imagecomparison; -import lombok.AccessLevel; -import lombok.Getter; import org.openqa.selenium.Point; import org.openqa.selenium.Rectangle; @@ -32,12 +30,17 @@ public abstract class ComparisonResult { private static final String VISUALIZATION = "visualization"; - @Getter(AccessLevel.PROTECTED) private final Map commandResult; + protected final Object commandResult; - public ComparisonResult(Map commandResult) { + public ComparisonResult(Object commandResult) { this.commandResult = commandResult; } + protected Map getResultAsMap() { + //noinspection unchecked + return (Map) commandResult; + } + /** * Verifies if the corresponding property is present in the commend result * and throws an exception if not. @@ -45,7 +48,7 @@ public ComparisonResult(Map commandResult) { * @param propertyName the actual property name to be verified for presence */ protected void verifyPropertyPresence(String propertyName) { - if (!commandResult.containsKey(propertyName)) { + if (!getResultAsMap().containsKey(propertyName)) { throw new IllegalStateException( String.format("There is no '%s' attribute in the resulting command output %s. " + "Did you set the options properly?", propertyName, commandResult)); @@ -59,13 +62,13 @@ protected void verifyPropertyPresence(String propertyName) { */ public byte[] getVisualization() { verifyPropertyPresence(VISUALIZATION); - return ((String) getCommandResult().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8); + return ((String) getResultAsMap().get(VISUALIZATION)).getBytes(StandardCharsets.UTF_8); } /** * Stores visualization image into the given file. * - * @param destination file to save image. + * @param destination File path to save the image to. * @throws IOException On file system I/O error. */ public void storeVisualization(File destination) throws IOException { diff --git a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java index 2ba90c7dd..0a983e50a 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/FeaturesMatchingResult.java @@ -43,7 +43,7 @@ public FeaturesMatchingResult(Map input) { */ public int getCount() { verifyPropertyPresence(COUNT); - return ((Long) getCommandResult().get(COUNT)).intValue(); + return ((Long) getResultAsMap().get(COUNT)).intValue(); } /** @@ -56,7 +56,7 @@ public int getCount() { */ public int getTotalCount() { verifyPropertyPresence(TOTAL_COUNT); - return ((Long) getCommandResult().get(TOTAL_COUNT)).intValue(); + return ((Long) getResultAsMap().get(TOTAL_COUNT)).intValue(); } /** @@ -67,7 +67,7 @@ public int getTotalCount() { public List getPoints1() { verifyPropertyPresence(POINTS1); //noinspection unchecked - return ((List>) getCommandResult().get(POINTS1)).stream() + return ((List>) getResultAsMap().get(POINTS1)).stream() .map(ComparisonResult::mapToPoint) .collect(Collectors.toList()); } @@ -80,7 +80,7 @@ public List getPoints1() { public Rectangle getRect1() { verifyPropertyPresence(RECT1); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT1)); + return mapToRect((Map) getResultAsMap().get(RECT1)); } /** @@ -91,7 +91,7 @@ public Rectangle getRect1() { public List getPoints2() { verifyPropertyPresence(POINTS2); //noinspection unchecked - return ((List>) getCommandResult().get(POINTS2)).stream() + return ((List>) getResultAsMap().get(POINTS2)).stream() .map(ComparisonResult::mapToPoint) .collect(Collectors.toList()); } @@ -104,6 +104,6 @@ public List getPoints2() { public Rectangle getRect2() { verifyPropertyPresence(RECT2); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT2)); + return mapToRect((Map) getResultAsMap().get(RECT2)); } } 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 510f64b8e..7b0266f23 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/OccurrenceMatchingResult.java @@ -18,34 +18,131 @@ import org.openqa.selenium.Rectangle; +import java.io.File; +import java.io.IOException; 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 static final String SCORE = "score"; - private final boolean isAtRoot; + private final boolean hasMultiple; - public OccurrenceMatchingResult(Map input) { - this(input, true); + public OccurrenceMatchingResult(Object input) { + super(input); + hasMultiple = input instanceof List; } - private OccurrenceMatchingResult(Map input, boolean isAtRoot) { - super(input); - this.isAtRoot = isAtRoot; + /** + * Check whether the current instance contains multiple matches. + * + * @return True or false. + */ + public boolean hasMultiple() { + return hasMultiple; } /** - * Returns rectangle of partial image occurrence. + * Returns rectangle of the partial image occurrence. * * @return The region of the partial image occurrence on the full image. */ public Rectangle getRect() { + if (hasMultiple) { + return getRect(0); + } verifyPropertyPresence(RECT); //noinspection unchecked - return mapToRect((Map) getCommandResult().get(RECT)); + return mapToRect((Map) getResultAsMap().get(RECT)); + } + + /** + * Returns rectangle of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return Matching rectangle. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public Rectangle getRect(int matchIndex) { + return getMatch(matchIndex).getRect(); + } + + /** + * Returns the score of the partial image occurrence. + * + * @return Matching score in range 0..1. + */ + public double getScore() { + if (hasMultiple) { + return getScore(0); + } + verifyPropertyPresence(SCORE); + var value = getResultAsMap().get(SCORE); + if (value instanceof Long) { + return ((Long) value).doubleValue(); + } + return (Double) value; + } + + /** + * Returns the score of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return Matching score in range 0..1. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public double getScore(int matchIndex) { + return getMatch(matchIndex).getScore(); + } + + /** + * Returns the visualization of the matching result. + * + * @return The visualization of the matching result represented as base64-encoded PNG image. + */ + @Override + public byte[] getVisualization() { + return hasMultiple ? getVisualization(0) : super.getVisualization(); + } + + /** + * Returns the visualization of the partial image occurrence for the given match index. + * + * @param matchIndex Match index. + * @return The visualization of the matching result represented as base64-encoded PNG image. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public byte[] getVisualization(int matchIndex) { + return getMatch(matchIndex).getVisualization(); + } + + /** + * Stores visualization image into the given file. + * + * @param destination File path to save the image to. + * @throws IOException On file system I/O error. + */ + @Override + public void storeVisualization(File destination) throws IOException { + if (hasMultiple) { + getMatch(0).storeVisualization(destination); + } else { + super.storeVisualization(destination); + } + } + + /** + * Stores visualization image into the given file. + * + * @param matchIndex Match index. + * @param destination File path to save the image to. + * @throws IOException On file system I/O error. + * @throws IllegalStateException If the current instance does not represent multiple matches. + */ + public void storeVisualization(int matchIndex, File destination) throws IOException { + getMatch(matchIndex).storeVisualization(destination); } /** @@ -54,18 +151,37 @@ public Rectangle getRect() { * * @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. + * @throws IllegalStateException If the current instance does not represent multiple matches. */ public List getMultiple() { - if (!isAtRoot) { - throw new IllegalStateException("Only the root match could contain multiple submatches"); - } - verifyPropertyPresence(MULTIPLE); + return getMultipleMatches(false); + } + private List getMultipleMatches(boolean throwIfEmpty) { + if (!hasMultiple) { + throw new IllegalStateException(String.format( + "This %s does not represent multiple matches. Did you set options properly?", + getClass().getSimpleName() + )); + } //noinspection unchecked - List> multiple = (List>) getCommandResult().get(MULTIPLE); - return multiple.stream() - .map(m -> new OccurrenceMatchingResult(m, false)) + var matches = ((List>) commandResult).stream() + .map(OccurrenceMatchingResult::new) .collect(Collectors.toList()); + if (matches.isEmpty() && throwIfEmpty) { + throw new IllegalStateException("Zero matches have been found. Try the lookup with different options."); + } + return matches; + } + + private OccurrenceMatchingResult getMatch(int index) { + var matches = getMultipleMatches(true); + if (index < 0 || index >= matches.size()) { + throw new IndexOutOfBoundsException(String.format( + "The match #%s does not exist. The total number of found matches is %s", + index, matches.size() + )); + } + return matches.get(index); } } diff --git a/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java b/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java index 50c388ead..0806e7b53 100644 --- a/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java +++ b/src/main/java/io/appium/java_client/imagecomparison/SimilarityMatchingResult.java @@ -33,10 +33,9 @@ public SimilarityMatchingResult(Map input) { */ public double getScore() { verifyPropertyPresence(SCORE); - //noinspection unchecked - if (getCommandResult().get(SCORE) instanceof Long) { - return ((Long) getCommandResult().get(SCORE)).doubleValue(); + if (getResultAsMap().get(SCORE) instanceof Long) { + return ((Long) getResultAsMap().get(SCORE)).doubleValue(); } - return (double) getCommandResult().get(SCORE); + return (double) getResultAsMap().get(SCORE); } } From 73a438752de816d90826501142809c8c807aa614 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 15 May 2024 09:07:20 +0200 Subject: [PATCH 234/314] fix: Use current class loader for the ByteBuddy wrapper (#2172) --- src/main/java/io/appium/java_client/proxy/Helpers.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index af724ae43..d162c3ed5 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -136,7 +136,7 @@ public static T createProxy( .defineField("methodCallListeners", MethodCallListener[].class, Visibility.PRIVATE) .implement(HasMethodCallListeners.class).intercept(FieldAccessor.ofBeanProperty()) .make() - .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER) + .load(Helpers.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded() .asSubclass(cls); }); From 56dc41186d466c80d2e038c9b56fac98144bcdb7 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 15 May 2024 09:07:33 +0200 Subject: [PATCH 235/314] fix: Correct extension name for `mobile: replaceElementValue` (#2171) --- .../io/appium/java_client/android/CanReplaceElementValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java index 447377633..3c42f5c35 100644 --- a/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java +++ b/src/main/java/io/appium/java_client/android/CanReplaceElementValue.java @@ -21,7 +21,7 @@ public interface CanReplaceElementValue extends ExecutesMethod, CanRememberExten * off from the typed text). */ default void replaceElementValue(RemoteWebElement element, String value) { - final String extName = "mobile: replaceValue"; + final String extName = "mobile: replaceElementValue"; try { CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, Map.of( "elementId", element.getId(), From b1f8bc9e529906bea353e7bb3bd2c9eabc767e45 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 16 May 2024 09:45:39 +0200 Subject: [PATCH 236/314] fix: Deprecate AppiumProtocolHandshake class (#2173) --- .../io/appium/java_client/AppiumDriver.java | 41 +++---- .../remote/AppiumCommandExecutor.java | 2 +- .../AppiumNewSessionCommandPayload.java | 6 + .../remote/AppiumProtocolHandshake.java | 107 +----------------- 4 files changed, 35 insertions(+), 121 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 6d66639c9..c8d660e9f 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -20,7 +20,6 @@ import io.appium.java_client.internal.ReflectionHelpers; import io.appium.java_client.internal.SessionHelpers; import io.appium.java_client.remote.AppiumCommandExecutor; -import io.appium.java_client.remote.AppiumNewSessionCommandPayload; import io.appium.java_client.remote.AppiumW3CHttpCommandCodec; import io.appium.java_client.remote.options.BaseOptions; import io.appium.java_client.service.local.AppiumDriverLocalService; @@ -50,11 +49,13 @@ import java.util.Collections; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import static com.google.common.base.Strings.isNullOrEmpty; import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; +import static java.util.Collections.singleton; import static org.openqa.selenium.remote.CapabilityType.PLATFORM_NAME; /** @@ -265,25 +266,27 @@ public void addCommand(HttpMethod httpMethod, String url, String methodName) { @Override protected void startSession(Capabilities capabilities) { - Response response = execute(new AppiumNewSessionCommandPayload(capabilities)); - if (response == null) { - throw new SessionNotCreatedException( - "The underlying command executor returned a null response."); - } - - Object responseValue = response.getValue(); - if (responseValue == null) { - throw new SessionNotCreatedException( - "The underlying command executor returned a response without payload: " - + response); - } - if (!(responseValue instanceof Map)) { - throw new SessionNotCreatedException( - "The underlying command executor returned a response with a non well formed payload: " - + response); - } + var response = Optional.ofNullable( + execute(DriverCommand.NEW_SESSION(singleton(capabilities))) + ).orElseThrow(() -> new SessionNotCreatedException( + "The underlying command executor returned a null response." + )); + + var rawCapabilities = Optional.ofNullable(response.getValue()) + .map(value -> { + if (!(value instanceof Map)) { + throw new SessionNotCreatedException(String.format( + "The underlying command executor returned a response " + + "with a non well formed payload: %s", response) + ); + } + //noinspection unchecked + return (Map) value; + }) + .orElseThrow(() -> new SessionNotCreatedException( + "The underlying command executor returned a response without payload: " + response) + ); - @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version rawCapabilities.remove("platform"); if (rawCapabilities.containsKey(CapabilityType.BROWSER_NAME) 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 d3310f478..118f0ff81 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -173,7 +173,7 @@ private Response createSession(Command command) throws IOException { throw new SessionNotCreatedException("Session already exists"); } - ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession(getClient(), command); + var result = new ProtocolHandshake().createSession(getClient(), command); Dialect dialect = result.getDialect(); if (!(dialect.getCommandCodec() instanceof W3CHttpCommandCodec)) { throw new SessionNotCreatedException("Only W3C sessions are supported. " diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java index 63fc663a5..31635dabb 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -27,6 +27,12 @@ import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; +/** + * This class is deprecated and will be removed. + * + * @deprecated Use CommandPayload instead. + */ +@Deprecated public class AppiumNewSessionCommandPayload extends CommandPayload { /** * Appends "appium:" prefix to all non-prefixed non-standard capabilities. diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index f92a3632d..ef2f659da 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -16,108 +16,13 @@ package io.appium.java_client.remote; -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.ImmutableCapabilities; -import org.openqa.selenium.SessionNotCreatedException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.internal.Either; -import org.openqa.selenium.json.Json; -import org.openqa.selenium.json.JsonOutput; -import org.openqa.selenium.remote.Command; -import org.openqa.selenium.remote.NewSessionPayload; import org.openqa.selenium.remote.ProtocolHandshake; -import org.openqa.selenium.remote.http.Contents; -import org.openqa.selenium.remote.http.HttpHandler; -import java.io.IOException; -import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -@SuppressWarnings("UnstableApiUsage") +/** + * This class is deprecated and should be removed. + * + * @deprecated Use ProtocolHandshake instead. + */ +@Deprecated public class AppiumProtocolHandshake extends ProtocolHandshake { - private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable destination) { - try (JsonOutput json = new Json().newOutput(destination)) { - json.beginObject(); - - json.name("capabilities"); - json.beginObject(); - - json.name("firstMatch"); - json.beginArray(); - json.beginObject(); - json.endObject(); - json.endArray(); - - json.name("alwaysMatch"); - try { - Method getW3CMethod = NewSessionPayload.class.getDeclaredMethod("getW3C"); - getW3CMethod.setAccessible(true); - //noinspection unchecked,resource - ((Stream>) getW3CMethod.invoke(srcPayload)) - .findFirst() - .map(json::write) - .orElseGet(() -> { - json.beginObject(); - json.endObject(); - return null; - }); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new WebDriverException(e); - } - - json.endObject(); // Close "capabilities" object - - try { - Method writeMetaDataMethod = NewSessionPayload.class.getDeclaredMethod( - "writeMetaData", JsonOutput.class); - writeMetaDataMethod.setAccessible(true); - writeMetaDataMethod.invoke(srcPayload, json); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new WebDriverException(e); - } - - json.endObject(); - } - } - - @Override - public Result createSession(HttpHandler client, Command command) throws IOException { - //noinspection unchecked - Capabilities desired = ((Set>) command.getParameters().get("capabilities")) - .stream() - .findAny() - .map(ImmutableCapabilities::new) - .orElseGet(ImmutableCapabilities::new); - try (NewSessionPayload payload = NewSessionPayload.create(desired)) { - Either result = createSession(client, payload); - if (result.isRight()) { - return result.right(); - } - throw result.left(); - } - } - - @Override - public Either createSession(HttpHandler client, NewSessionPayload payload) { - - StringWriter stringWriter = new StringWriter(); - writeJsonPayload(payload, stringWriter); - - try { - Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod( - "createSession", HttpHandler.class, Contents.Supplier.class - ); - createSessionMethod.setAccessible(true); - //noinspection unchecked - return (Either) createSessionMethod.invoke( - this, client, Contents.utf8String(stringWriter.toString()) - ); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new WebDriverException(e); - } - } } From 6b8e28d96973f73dc770662f7c3fbc0a586bef45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 14:39:35 +0300 Subject: [PATCH 237/314] build(deps): Bump com.google.code.gson:gson from 2.10.1 to 2.11.0 (#2175) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10.1 to 2.11.0. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10.1...gson-parent-2.11.0) --- updated-dependencies: - dependency-name: com.google.code.gson:gson 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 719b1921e..30e5abd59 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { } } } - implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.google.code.gson:gson:2.11.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" } From a67dd969a6252806a14d9adaf09d78f2147fba8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 14:43:20 +0300 Subject: [PATCH 238/314] build(deps): Bump org.owasp.dependencycheck from 9.1.0 to 9.2.0 (#2176) Bumps org.owasp.dependencycheck from 9.1.0 to 9.2.0. --- 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 30e5abd59..54057da0c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.1.0' + id 'org.owasp.dependencycheck' version '9.2.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 127a70f3449e7a8e174a65812d1fedb639056731 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 28 May 2024 19:57:56 +0300 Subject: [PATCH 239/314] release: v9.2.3 (#2177) --- CHANGELOG.md | 15 +++++++++++++++ README.md | 20 ++++++++++---------- gradle.properties | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92823038c..44838a04f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.2.3_ +- **[BUG FIX]** + - Properly represent `FeaturesMatchingResult` model if `multiple` option is enabled [#2170](https://github.com/appium/java-client/pull/2170) + - Use current class loader for the ByteBuddy wrapper [#2172](https://github.com/appium/java-client/pull/2172) \ + This fixes errors like `NoClassDefFoundError: org/openqa/selenium/remote/RemoteWebElement`, `NoClassDefFoundError: io/appium/java_client/proxy/HasMethodCallListeners` when `PageFactory` is used. + - Correct extension name for `mobile: replaceElementValue` [#2171](https://github.com/appium/java-client/pull/2171) +- **[DEPRECATION]** + - Deprecate `AppiumProtocolHandshake` class [#2173](https://github.com/appium/java-client/pull/2173) \ + The original `ProtocolHandshake` class only supports W3C protocol now. There is no need to hack it anymore. +- **[REFACTOR]** + - Replace Guava `HttpHeaders` with Selenium `HttpHeader` [#2151](https://github.com/appium/java-client/pull/2151) +- **[DEPENDENCY CHANGE]** + - Bump SLF4J from `2.0.12` to `2.0.13` [#2158](https://github.com/appium/java-client/pull/2158) + - Bump Gson from `2.10.1` to `2.11.0` [#2175](https://github.com/appium/java-client/pull/2175) + _9.2.2_ - **[BUG FIX]** - fix: Fix building of Android key event parameters [#2145](https://github.com/appium/java-client/pull/2145) diff --git a/README.md b/README.md index 189592d79..6b7f4988e 100644 --- a/README.md +++ b/README.md @@ -93,16 +93,16 @@ dependencies { ``` ### Compatibility Matrix - Appium Java Client | Selenium client -----------------------------------------------------------------------------------|----------------- - `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2` | `4.19.0` - `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` - `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` - N/A | `4.14.0` - `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` - `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` - `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` - `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` + Appium Java Client | Selenium client +-------------------------------------------------------------------------------------------|----------------- + `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2`, `9.2.3` | `4.19.0`, `4.19.1`, `4.20.0`, `4.21.0` + `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` + `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` + N/A | `4.14.0` + `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` + `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` + `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` + `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` #### Why is it so complicated? diff --git a/gradle.properties b/gradle.properties index b67a7114b..44154a53b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.19.0 # Please increment the value in a release -appiumClient.version=9.2.2 +appiumClient.version=9.2.3 From e62b3951cb48bea076d104a3a3d0b6588e618587 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 28 May 2024 19:03:42 +0200 Subject: [PATCH 240/314] ci: Bump iOS version (#2167) --- .github/workflows/gradle.yml | 22 +++++++++---------- build.gradle | 1 + .../java_client/ios/BaseIOSWebViewTest.java | 6 +++-- .../java_client/ios/BaseSafariTest.java | 3 +-- .../ios/IOSNativeWebTapSettingTest.java | 2 -- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c83581927..e8006e8df 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,9 +22,10 @@ env: ANDROID_SDK_VERSION: "28" ANDROID_EMU_NAME: test ANDROID_EMU_TARGET: default - XCODE_VERSION: "14.2" - IOS_DEVICE_NAME: iPhone 12 - IOS_PLATFORM_VERSION: "16.2" + # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md + XCODE_VERSION: "15.4" + IOS_DEVICE_NAME: iPhone 15 + IOS_PLATFORM_VERSION: "17.5" jobs: build: @@ -34,7 +35,7 @@ jobs: include: - java: 11 # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available - platform: macos-12 + platform: macos-14 e2e-tests: ios - java: 17 platform: ubuntu-latest @@ -100,19 +101,18 @@ jobs: uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "${{ env.XCODE_VERSION }}" + - name: Prepare iOS simulator + if: matrix.e2e-tests == 'ios' + uses: futureware-tech/simulator-action@v3 + with: + model: "${{ env.IOS_DEVICE_NAME }}" + os_version: "${{ env.IOS_PLATFORM_VERSION }}" - name: Install XCUITest driver if: matrix.e2e-tests == 'ios' run: appium driver install xcuitest - name: Prebuild XCUITest driver if: matrix.e2e-tests == 'ios' run: appium driver run xcuitest build-wda - - name: Prepare iOS simulator - if: matrix.e2e-tests == 'ios' - run: | - xcrun simctl list - target_sim_id=$(xcrun simctl list devices available | grep "$IOS_DEVICE_NAME (" | cut -d "(" -f2 | cut -d ")" -f1) - open -Fn "/Applications/Xcode_$XCODE_VERSION.app/Contents/Developer/Applications/Simulator.app" - xcrun simctl bootstatus $target_sim_id -b - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' run: ./gradlew e2eIosTest -PisCI -Pselenium.version=$latest_snapshot diff --git a/build.gradle b/build.gradle index 54057da0c..b4f39d1fa 100644 --- a/build.gradle +++ b/build.gradle @@ -242,6 +242,7 @@ testing { filter { exclude '**/IOSScreenRecordTest.class' exclude '**/ImagesComparisonTest.class' + exclude '**/IOSNativeWebTapSettingTest.class' } } } diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index f975d3c5b..752a0c539 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -27,17 +27,19 @@ public class BaseIOSWebViewTest extends BaseIOSTest { private static final String VODQA_ZIP = TestUtils.resourcePathToAbsolutePath("vodqa.zip").toString(); - private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(1); - private static final Duration WEB_VIEW_DETECT_DURATION = Duration.ofSeconds(15); + private static final Duration WEB_VIEW_DETECT_INTERVAL = Duration.ofSeconds(2); + private static final Duration WEB_VIEW_DETECT_DURATION = Duration.ofSeconds(30); @BeforeAll public static void beforeClass() { startAppiumServer(); XCUITestOptions options = new XCUITestOptions() + .setPlatformVersion(PLATFORM_VERSION) .setDeviceName(DEVICE_NAME) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT) .setCommandTimeouts(Duration.ofSeconds(240)) + .setShowIosLog(true) .setApp(VODQA_ZIP); Supplier createDriver = () -> new IOSDriver(service.getUrl(), options); try { diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java index d35c76357..710f5dbf1 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/BaseSafariTest.java @@ -20,13 +20,12 @@ import io.appium.java_client.remote.MobileBrowserType; import org.junit.jupiter.api.BeforeAll; -import java.io.IOException; import java.time.Duration; public class BaseSafariTest extends BaseIOSTest { private static final Duration WEBVIEW_CONNECT_TIMEOUT = Duration.ofSeconds(30); - @BeforeAll public static void beforeClass() throws IOException { + @BeforeAll public static void beforeClass() { startAppiumServer(); XCUITestOptions options = new XCUITestOptions() diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java index 922dbb7cd..8c1bc3fee 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/IOSNativeWebTapSettingTest.java @@ -1,6 +1,5 @@ package io.appium.java_client.ios; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -14,7 +13,6 @@ public class IOSNativeWebTapSettingTest extends BaseSafariTest { @Test - @Disabled("https://github.com/appium/appium/issues/17014") public void nativeWebTapSettingTest() { assertTrue(driver.isBrowser()); driver.get("https://saucelabs.com/test/guinea-pig"); From c8e13e19746abd8a3d0ee71eed299a442e65c57c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:14:13 +0300 Subject: [PATCH 241/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2187) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.8.0 to 5.9.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-5.8.0...webdrivermanager-5.9.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b4f39d1fa..c1fdd6d81 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.8.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -257,7 +257,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.8.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.0') { exclude group: 'org.seleniumhq.selenium' } } From 409d167d6e923b058b79f8e127e665aa882b8536 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2024 08:08:03 +0200 Subject: [PATCH 242/314] chore: Add mobile: replacements to clipboards API wrappers (#2188) --- .../java_client/CommandExecutionHelper.java | 2 +- .../java_client/clipboard/HasClipboard.java | 33 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index 7cb011677..e64470b38 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -62,7 +62,7 @@ public static T executeScript(ExecutesMethod executesMethod, String scriptNa */ @Nullable public static T executeScript( - ExecutesMethod executesMethod, String scriptName, @Nullable Map args + ExecutesMethod executesMethod, String scriptName, @Nullable Map args ) { return execute(executesMethod, Map.entry(EXECUTE_SCRIPT, Map.of( "script", scriptName, diff --git a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java index a6ae3327c..24384da13 100644 --- a/src/main/java/io/appium/java_client/clipboard/HasClipboard.java +++ b/src/main/java/io/appium/java_client/clipboard/HasClipboard.java @@ -16,8 +16,10 @@ package io.appium.java_client.clipboard; +import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.openqa.selenium.UnsupportedCommandException; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -27,20 +29,25 @@ import static io.appium.java_client.MobileCommand.SET_CLIPBOARD; import static java.util.Objects.requireNonNull; -public interface HasClipboard extends ExecutesMethod { +public interface HasClipboard extends ExecutesMethod, CanRememberExtensionPresence { /** * Set the content of device's clipboard. * - * @param contentType one of supported content types. + * @param contentType one of supported content types. * @param base64Content base64-encoded content to be set. */ default void setClipboard(ClipboardContentType contentType, byte[] base64Content) { - CommandExecutionHelper.execute(this, Map.entry(SET_CLIPBOARD, - Map.of( - "content", new String(requireNonNull(base64Content), StandardCharsets.UTF_8), - "contentType", contentType.name().toLowerCase() - ) - )); + final String extName = "mobile: setClipboard"; + var args = Map.of( + "content", new String(requireNonNull(base64Content), StandardCharsets.UTF_8), + "contentType", contentType.name().toLowerCase() + ); + try { + CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + CommandExecutionHelper.execute(this, Map.entry(SET_CLIPBOARD, args)); + } } /** @@ -50,8 +57,14 @@ default void setClipboard(ClipboardContentType contentType, byte[] base64Content * @return the actual content of the clipboard as base64-encoded string or an empty string if the clipboard is empty */ default String getClipboard(ClipboardContentType contentType) { - return CommandExecutionHelper.execute(this, Map.entry(GET_CLIPBOARD, - Map.of("contentType", contentType.name().toLowerCase()))); + final String extName = "mobile: getClipboard"; + var args = Map.of("contentType", contentType.name().toLowerCase()); + try { + return CommandExecutionHelper.executeScript(assertExtensionExists(extName), extName, args); + } catch (UnsupportedCommandException e) { + // TODO: Remove the fallback + return CommandExecutionHelper.execute(this, Map.entry(GET_CLIPBOARD, args)); + } } /** From 9333f955ce9208e7daf79c12be7e7b9a662027bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:24:07 +0300 Subject: [PATCH 243/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2189) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.9.0 to 5.9.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-5.9.0...webdrivermanager-5.9.1) --- 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index c1fdd6d81..955239afd 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.9.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.1') { exclude group: 'org.seleniumhq.selenium' } } @@ -257,7 +257,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.9.0') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.1') { exclude group: 'org.seleniumhq.selenium' } } From d8a31a48e119254a601e8820e7964836db53bef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:24:31 +0300 Subject: [PATCH 244/314] build(deps): Bump org.projectlombok:lombok from 1.18.32 to 1.18.34 (#2191) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.32 to 1.18.34. - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.32...v1.18.34) --- 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 955239afd..f98ed2764 100644 --- a/build.gradle +++ b/build.gradle @@ -38,8 +38,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.32' - annotationProcessor 'org.projectlombok:lombok:1.18.32' + compileOnly 'org.projectlombok:lombok:1.18.34' + annotationProcessor 'org.projectlombok:lombok:1.18.34' if (project.hasProperty("isCI")) { api "org.seleniumhq.selenium:selenium-api:${seleniumVersion}" From 969248542bf32c35273ce7cb425e3f5ae108fe05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:40:42 +0300 Subject: [PATCH 245/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.10.2 to 5.10.3 (#2190) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 f98ed2764..7afb2ddcf 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.10.2' + implementation 'org.junit.jupiter:junit-jupiter:5.10.3' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:2.2' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From f821c1982aecdbe3e11a22300d405242ea9934cc Mon Sep 17 00:00:00 2001 From: Saikat <41847480+sailfishdev@users.noreply.github.com> Date: Mon, 8 Jul 2024 02:48:38 +0530 Subject: [PATCH 246/314] docs: Update README.md (#2193) --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6b7f4988e..edbb5cc83 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ This is the Java language bindings for writing Appium Tests that conform to [Web ## v8 to v9 Migration Since v9 the client only supports Java 11 and above. -Follow the [v8 to v9 Migration Guide](./docs/v8-to-v9-migration-guide.md) in order to streamline the migration process. +Follow the [v8 to v9 Migration Guide](./docs/v8-to-v9-migration-guide.md) to streamline the migration process. ## v7 to v8 Migration Since version 8 Appium Java Client had several major changes, which might require to update your client code. Make sure to follow the [v7 to v8 Migration Guide](./docs/v7-to-v8-migration-guide.md) -in order to streamline the migration process. +to streamline the migration process. ## Add Appium java client to your test framework @@ -46,7 +46,7 @@ dependencies { ### Beta/Snapshots -Java client project is available to use even before it is officially published to maven central. Refer [jitpack.io](https://jitpack.io/#appium/java-client) +Java client project is available to use even before it is officially published to Maven Central. Refer [jitpack.io](https://jitpack.io/#appium/java-client) #### Maven @@ -73,7 +73,7 @@ Add the dependency: #### Gradle -Add the JitPack repository to your build file. Add it in your root build.gradle at the end of repositories: +Add the JitPack repository to your build file. Add it to your root build.gradle at the end of repositories: ```groovy allprojects { @@ -107,11 +107,11 @@ dependencies { #### Why is it so complicated? Selenium client does not follow [Semantic Versioning](https://semver.org/), so breaking changes might be introduced -even in patches, which requires Appium team to update Java client in response. +even in patches, which requires the Appium team to update the Java client in response. #### How to pin Selenium dependencies? -Appium Java Client declares Selenium dependencies using open version range which is handled in differently by different +Appium Java Client declares Selenium dependencies using an open version range which is handled differently by different build tools. Sometimes users may want to pin used Selenium dependencies for [various reasons](https://github.com/appium/java-client/issues/1823). Follow the [Transitive Dependencies Management article](docs/transitive-dependencies-management.md) for more information about establishing a fixed Selenium version for your Java test framework. @@ -130,10 +130,10 @@ Appium java client has dedicated classes to support the following Appium drivers To automate other platforms that are not listed above you could use [AppiumDriver](src/main/java/io/appium/java_client/AppiumDriver.java) or its custom derivatives. -Appium java client is built on top of Selenium and implements same interfaces that the foundation +Appium java client is built on top of Selenium and implements the same interfaces that the foundation [RemoteWebDriver](https://github.com/SeleniumHQ/selenium/blob/trunk/java/src/org/openqa/selenium/remote/RemoteWebDriver.java) -does. However, Selenium lib is mostly focused on web browsers automation while -Appium is universal and covers wide range of possible platforms, e.g. mobile and desktop +does. However, Selenium lib is mostly focused on web browser automation while +Appium is universal and covers a wide range of possible platforms, e.g. mobile and desktop operating systems, IOT devices, etc. Thus, the foundation `AppiumDriver` class in this package extends `RemoteWebDriver` with additional features, and makes it more flexible, so it is not so strictly focused on web-browser related operations. @@ -161,7 +161,7 @@ using [AppiumServiceBuilder](src/main/java/io/appium/java_client/service/local/A **Note** -> AppiumDriverLocalService does not support the server management on non-local hosts +> AppiumDriverLocalService does not support server management on non-local hosts ## Usage Examples @@ -169,14 +169,14 @@ using [AppiumServiceBuilder](src/main/java/io/appium/java_client/service/local/A ```java UiAutomator2Options options = new UiAutomator2Options() - .setUdid('123456') + .setUdid("123456") .setApp("/home/myapp.apk"); AndroidDriver driver = new AndroidDriver( // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub new URL("http://127.0.0.1:4723/"), options ); try { - WebElement el = driver.findElement(AppiumBy.xpath, "//Button"); + WebElement el = driver.findElement(AppiumBy.xpath("//Button")); el.click(); driver.getPageSource(); } finally { @@ -188,14 +188,14 @@ try { ```java XCUITestOptions options = new XCUITestOptions() - .setUdid('123456') + .setUdid("123456") .setApp("/home/myapp.ipa"); IOSDriver driver = new IOSDriver( // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub new URL("http://127.0.0.1:4723/"), options ); try { - WebElement el = driver.findElement(AppiumBy.accessibilityId, "myId"); + WebElement el = driver.findElement(AppiumBy.accessibilityId("myId")); el.click(); driver.getPageSource(); } finally { @@ -216,7 +216,7 @@ AppiumDriver driver = new AppiumDriver( new URL("http://127.0.0.1:4723/"), options ); try { - WebElement el = driver.findElement(AppiumBy.className, "myClass"); + WebElement el = driver.findElement(AppiumBy.className("myClass")); el.click(); driver.getPageSource(); } finally { @@ -226,7 +226,7 @@ try { Check the corresponding driver's READMEs to know the list of capabilities and features it supports. -You could find much more code examples by checking client's +You can find many more code examples by checking client's [unit and integration tests](src/test/java/io/appium/java_client). ## Troubleshooting @@ -234,24 +234,24 @@ You could find much more code examples by checking client's ### InaccessibleObjectException is thrown in runtime if Java 16+ is used Appium Java client uses reflective access to private members of other modules -to ensure proper functionality of several features, like Page Object model. +to ensure proper functionality of several features, like the Page Object model. If you get a runtime exception and `InaccessibleObjectException` is present in -the stacktrace, and your Java runtime is at version 16 or higher, then consider following +the stack trace and your Java runtime is at version 16 or higher, then consider the following [Oracle's tutorial](https://docs.oracle.com/en/java/javase/16/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B) and/or checking [existing issues](https://github.com/appium/java-client/search?q=InaccessibleObjectException&type=issues) -for possible solutions. Basically, the idea there would be to explicitly allow +for possible solutions. The idea there would be to explicitly allow access for particular modules using `--add-exports/--add-opens` command line arguments. Another possible, but weakly advised solution, would be to downgrade Java to version 15 or lower. -### Issues related to environment variables presence or to their values +### Issues related to environment variables' presence or to their values -Such issues are usually the case when Appium server is started directly from your +Such issues are usually the case when the Appium server is started directly from your framework code rather than run separately by a script or manually. Depending on the way the server process is started it may or may not inherit the currently -active shell environment. That is why you may still receive errors about variables -presence even though these variables ar actually defined for your command line interpreter. +active shell environment. That is why you may still receive errors about the variables' +presence even though these variables are defined for your command line interpreter. Again, there is no universal solution to that, as there are many ways to spin up a new server process. Consider checking the [Appium Environment Troubleshooting](docs/environment.md) document for more information on how to debug and fix process environment issues. From b2c7d872544a3bbca174c25fdddd8774cefdf04d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:39:18 +0300 Subject: [PATCH 247/314] build(deps): Bump org.owasp.dependencycheck from 9.2.0 to 10.0.2 (#2195) Bumps org.owasp.dependencycheck from 9.2.0 to 10.0.2. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck 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 7afb2ddcf..8de5f95be 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '9.2.0' + id 'org.owasp.dependencycheck' version '10.0.2' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 8a637591fb3747eda528b9edf8509499fd22d27b Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Wed, 10 Jul 2024 07:51:21 +0300 Subject: [PATCH 248/314] feat: Add ability to use secure WebSocket to listen Logcat messages (#2182) --- .../android/ListensToLogcatMessages.java | 20 +++++++++---------- .../ios/ListensToSyslogMessages.java | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java index 8d16bf7de..d5051da0e 100644 --- a/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java +++ b/src/main/java/io/appium/java_client/android/ListensToLogcatMessages.java @@ -19,10 +19,12 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.SessionId; import java.net.URI; -import java.net.URISyntaxException; +import java.net.URL; import java.util.function.Consumer; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; @@ -36,7 +38,7 @@ public interface ListensToLogcatMessages extends ExecutesMethod { * is assigned to the default port (4723). */ default void startLogcatBroadcast() { - startLogcatBroadcast("127.0.0.1", DEFAULT_APPIUM_PORT); + startLogcatBroadcast("127.0.0.1"); } /** @@ -56,15 +58,13 @@ default void startLogcatBroadcast(String host) { * @param port the port of the host where Appium server is running */ default void startLogcatBroadcast(String host, int port) { + var remoteWebDriver = (RemoteWebDriver) this; + URL serverUrl = ((HttpCommandExecutor) remoteWebDriver.getCommandExecutor()).getAddressOfRemoteServer(); + var scheme = "https".equals(serverUrl.getProtocol()) ? "wss" : "ws"; CommandExecutionHelper.executeScript(this, "mobile: startLogsBroadcast"); - final URI endpointUri; - try { - endpointUri = new URI(String.format("ws://%s:%s/ws/session/%s/appium/device/logcat", - host, port, ((RemoteWebDriver) this).getSessionId())); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - getLogcatClient().connect(endpointUri); + SessionId sessionId = remoteWebDriver.getSessionId(); + var endpoint = String.format("%s://%s:%s/ws/session/%s/appium/device/logcat", scheme, host, port, sessionId); + getLogcatClient().connect(URI.create(endpoint)); } /** diff --git a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java index ef0bd3f9d..98a75158a 100644 --- a/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java +++ b/src/main/java/io/appium/java_client/ios/ListensToSyslogMessages.java @@ -19,10 +19,12 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; import io.appium.java_client.ws.StringWebSocketClient; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.SessionId; import java.net.URI; -import java.net.URISyntaxException; +import java.net.URL; import java.util.function.Consumer; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; @@ -37,7 +39,7 @@ public interface ListensToSyslogMessages extends ExecutesMethod { * is assigned to the default port (4723). */ default void startSyslogBroadcast() { - startSyslogBroadcast("localhost", DEFAULT_APPIUM_PORT); + startSyslogBroadcast("localhost"); } /** @@ -57,15 +59,13 @@ default void startSyslogBroadcast(String host) { * @param port the port of the host where Appium server is running */ default void startSyslogBroadcast(String host, int port) { + var remoteWebDriver = (RemoteWebDriver) this; + URL serverUrl = ((HttpCommandExecutor) remoteWebDriver.getCommandExecutor()).getAddressOfRemoteServer(); + var scheme = "https".equals(serverUrl.getProtocol()) ? "wss" : "ws"; CommandExecutionHelper.executeScript(this, "mobile: startLogsBroadcast"); - final URI endpointUri; - try { - endpointUri = new URI(String.format("ws://%s:%s/ws/session/%s/appium/device/syslog", - host, port, ((RemoteWebDriver) this).getSessionId())); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - getSyslogClient().connect(endpointUri); + SessionId sessionId = remoteWebDriver.getSessionId(); + var endpoint = String.format("%s://%s:%s/ws/session/%s/appium/device/syslog", scheme, host, port, sessionId); + getSyslogClient().connect(URI.create(endpoint)); } /** From 0e24ef1d3e55a6ac8960db70e10d1633e8d33ca8 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 13 Jul 2024 09:16:33 +0200 Subject: [PATCH 249/314] chore: Deprecate obsolete TouchAction helpers (#2199) --- .../AndroidAbilityToUseSupplierTest.java | 71 ------- .../java_client/android/AndroidTouchTest.java | 193 ------------------ .../java/io/appium/java_client/AppiumBy.java | 26 +-- .../pagefactory/bys/ContentMappedBy.java | 23 +-- .../java_client/touch/ActionOptions.java | 1 + .../java_client/touch/LongPressOptions.java | 1 + .../appium/java_client/touch/TapOptions.java | 1 + .../appium/java_client/touch/WaitOptions.java | 1 + .../AbstractOptionCombinedWithPosition.java | 1 + .../touch/offset/ElementOption.java | 1 + .../java_client/touch/offset/PointOption.java | 1 + .../java_client/touch/DummyElement.java | 103 ---------- .../java_client/touch/FailsWithMatcher.java | 43 ---- .../java_client/touch/TouchOptionsTests.java | 96 --------- 14 files changed, 11 insertions(+), 551 deletions(-) delete mode 100644 src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java delete mode 100644 src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java delete mode 100644 src/test/java/io/appium/java_client/touch/DummyElement.java delete mode 100644 src/test/java/io/appium/java_client/touch/FailsWithMatcher.java delete mode 100644 src/test/java/io/appium/java_client/touch/TouchOptionsTests.java diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java deleted file mode 100644 index 5db7921a7..000000000 --- a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package io.appium.java_client.android; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.functions.ActionSupplier; -import io.appium.java_client.touch.offset.ElementOption; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.Point; -import org.openqa.selenium.WebElement; - -import java.util.List; -import java.util.Map; - -import static io.appium.java_client.TestUtils.getCenter; -import static io.appium.java_client.touch.WaitOptions.waitOptions; -import static io.appium.java_client.touch.offset.ElementOption.element; -import static java.time.Duration.ofSeconds; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { - - private final ActionSupplier horizontalSwipe = () -> { - driver.findElement(By.id("io.appium.android.apis:id/gallery")); - - WebElement gallery = driver.findElement(By.id("io.appium.android.apis:id/gallery")); - List images = gallery.findElements(AppiumBy.className("android.widget.ImageView")); - Point location = gallery.getLocation(); - Point center = getCenter(gallery, location); - - ElementOption pressOption = element(images.get(2),-10,center.y - location.y); - ElementOption moveOption = element(gallery, 10,center.y - location.y); - - return new AndroidTouchAction(driver) - .press(pressOption) - .waitAction(waitOptions(ofSeconds(2))) - .moveTo(moveOption) - .release(); - }; - - private final ActionSupplier verticalSwiping = () -> - new AndroidTouchAction(driver) - .press(element(driver.findElement(AppiumBy.accessibilityId("Gallery")))) - - .waitAction(waitOptions(ofSeconds(2))) - - .moveTo(element(driver.findElement(AppiumBy.accessibilityId("Auto Complete")))) - .release(); - - @Test public void horizontalSwipingWithSupplier() { - startActivity(".view.Gallery1"); - WebElement gallery = driver.findElement(By.id("io.appium.android.apis:id/gallery")); - List images = gallery.findElements(AppiumBy.className("android.widget.ImageView")); - int originalImageCount = images.size(); - - horizontalSwipe.get().perform(); - - assertNotEquals(originalImageCount, - gallery.findElements(AppiumBy.className("android.widget.ImageView")).size()); - } - - @Test public void verticalSwipingWithSupplier() throws Exception { - driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); - driver.findElement(AppiumBy.accessibilityId("Views")).click(); - - Point originalLocation = driver.findElement(AppiumBy.accessibilityId("Gallery")).getLocation(); - verticalSwiping.get().perform(); - Thread.sleep(5000); - assertNotEquals(originalLocation, driver.findElement(AppiumBy.accessibilityId("Gallery")).getLocation()); - } -} diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java deleted file mode 100644 index c24ed20fa..000000000 --- a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidTouchTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package io.appium.java_client.android; - -import io.appium.java_client.AppiumBy; -import io.appium.java_client.MultiTouchAction; -import io.appium.java_client.TouchAction; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.Point; -import org.openqa.selenium.WebElement; - -import java.util.List; -import java.util.Map; - -import static io.appium.java_client.TestUtils.getCenter; -import static io.appium.java_client.touch.LongPressOptions.longPressOptions; -import static io.appium.java_client.touch.TapOptions.tapOptions; -import static io.appium.java_client.touch.WaitOptions.waitOptions; -import static io.appium.java_client.touch.offset.ElementOption.element; -import static io.appium.java_client.touch.offset.PointOption.point; -import static java.time.Duration.ofSeconds; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; - -public class AndroidTouchTest extends BaseAndroidTest { - - @BeforeEach - public void setUp() { - driver.executeScript("mobile: terminateApp", Map.of("appId", APP_ID)); - driver.executeScript("mobile: activateApp", Map.of("appId", APP_ID)); - } - - @Test public void dragNDropByElementTest() { - startActivity(".view.DragAndDropDemo"); - WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); - WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); - - WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); - assertEquals("Drag text not empty", "", dragText.getText()); - - TouchAction dragNDrop = new TouchAction(driver) - .longPress(element(dragDot1)) - .moveTo(element(dragDot3)) - .release(); - dragNDrop.perform(); - assertNotEquals("Drag text empty", "", dragText.getText()); - } - - @Test public void dragNDropByElementAndDurationTest() { - startActivity(".view.DragAndDropDemo"); - WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); - WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); - - WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); - assertEquals("Drag text not empty", "", dragText.getText()); - - TouchAction dragNDrop = new TouchAction(driver) - .longPress(longPressOptions() - .withElement(element(dragDot1)) - .withDuration(ofSeconds(2))) - .moveTo(element(dragDot3)) - .release(); - dragNDrop.perform(); - assertNotEquals("Drag text empty", "", dragText.getText()); - } - - @Test public void dragNDropByCoordinatesTest() { - startActivity(".view.DragAndDropDemo"); - WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); - WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); - - WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); - assertEquals("Drag text not empty", "", dragText.getText()); - - Point center1 = getCenter(dragDot1); - Point center2 = getCenter(dragDot3); - - TouchAction dragNDrop = new TouchAction(driver) - .longPress(point(center1.x, center1.y)) - .moveTo(point(center2.x, center2.y)) - .release(); - dragNDrop.perform(); - assertNotEquals("Drag text empty", "", dragText.getText()); - } - - @Test public void dragNDropByCoordinatesAndDurationTest() { - startActivity(".view.DragAndDropDemo"); - WebElement dragDot1 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_1")); - WebElement dragDot3 = driver.findElement(By.id("io.appium.android.apis:id/drag_dot_3")); - - WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); - assertEquals("Drag text not empty", "", dragText.getText()); - - Point center1 = getCenter(dragDot1); - Point center2 = getCenter(dragDot3); - - TouchAction dragNDrop = new TouchAction(driver) - .longPress(longPressOptions() - .withPosition(point(center1.x, center1.y)) - .withDuration(ofSeconds(2))) - .moveTo(point(center2.x, center2.y)) - .release(); - dragNDrop.perform(); - assertNotEquals("Drag text empty", "", dragText.getText()); - } - - @Test public void pressByCoordinatesTest() { - startActivity(".view.Buttons1"); - Point point = driver.findElement(By.id("io.appium.android.apis:id/button_toggle")).getLocation(); - new TouchAction(driver) - .press(point(point.x + 20, point.y + 30)) - .waitAction(waitOptions(ofSeconds(1))) - .release() - .perform(); - assertEquals("ON", driver.findElement(By.id("io.appium.android.apis:id/button_toggle")).getText()); - } - - @Test public void pressByElementTest() { - startActivity(".view.Buttons1"); - new TouchAction(driver) - .press(element(driver.findElement(By.id("io.appium.android.apis:id/button_toggle")))) - .waitAction(waitOptions(ofSeconds(1))) - .release() - .perform(); - assertEquals("ON", driver.findElement(By.id("io.appium.android.apis:id/button_toggle")).getText()); - } - - @Test public void tapActionTestByElement() throws Exception { - startActivity(".view.ChronometerDemo"); - WebElement chronometer = driver.findElement(By.id("io.appium.android.apis:id/chronometer")); - - TouchAction startStop = new TouchAction(driver) - .tap(tapOptions().withElement(element(driver.findElement(By.id("io.appium.android.apis:id/start"))))) - .waitAction(waitOptions(ofSeconds(2))) - .tap(tapOptions().withElement(element(driver.findElement(By.id("io.appium.android.apis:id/stop"))))); - - startStop.perform(); - - String time = chronometer.getText(); - assertNotEquals(time, "Initial format: 00:00"); - Thread.sleep(2500); - assertEquals(time, chronometer.getText()); - } - - @Test public void tapActionTestByCoordinates() throws Exception { - startActivity(".view.ChronometerDemo"); - WebElement chronometer = driver.findElement(By.id("io.appium.android.apis:id/chronometer")); - - Point center1 = getCenter(driver.findElement(By.id("io.appium.android.apis:id/start"))); - - TouchAction startStop = new TouchAction(driver) - .tap(point(center1.x, center1.y)) - .tap(element(driver.findElement(By.id("io.appium.android.apis:id/stop")), 5, 5)); - startStop.perform(); - - String time = chronometer.getText(); - assertNotEquals(time, "Initial format: 00:00"); - Thread.sleep(2500); - assertEquals(time, chronometer.getText()); - } - - @Test public void horizontalSwipingTest() { - startActivity(".view.Gallery1"); - - WebElement gallery = driver.findElement(By.id("io.appium.android.apis:id/gallery")); - List images = gallery.findElements(AppiumBy.className("android.widget.ImageView")); - int originalImageCount = images.size(); - Point location = gallery.getLocation(); - Point center = getCenter(gallery); - - TouchAction swipe = new TouchAction(driver) - .press(element(images.get(2),-10, center.y - location.y)) - .waitAction(waitOptions(ofSeconds(2))) - .moveTo(element(gallery,10,center.y - location.y)) - .release(); - swipe.perform(); - assertNotEquals(originalImageCount, - gallery.findElements(AppiumBy.className("android.widget.ImageView")).size()); - } - - @Test public void multiTouchTest() { - startActivity(".view.Buttons1"); - TouchAction press = new TouchAction(driver) - .press(element(driver.findElement(By.id("io.appium.android.apis:id/button_toggle")))) - .waitAction(waitOptions(ofSeconds(1))) - .release(); - new MultiTouchAction(driver) - .add(press) - .perform(); - assertEquals("ON", driver.findElement(By.id("io.appium.android.apis:id/button_toggle")).getText()); - } - -} diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index 21e11d08e..cb374831e 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -17,6 +17,7 @@ package io.appium.java_client; import com.google.common.base.Preconditions; +import lombok.EqualsAndHashCode; import lombok.Getter; import org.openqa.selenium.By; import org.openqa.selenium.By.Remotable; @@ -25,10 +26,10 @@ import java.io.Serializable; import java.util.List; -import java.util.Objects; import static com.google.common.base.Strings.isNullOrEmpty; +@EqualsAndHashCode(callSuper = true) public abstract class AppiumBy extends By implements Remotable { @Getter private final Parameters remoteParameters; @@ -256,27 +257,4 @@ protected ByIosNsPredicate(String locatorString) { super("-ios predicate string", locatorString, "iOSNsPredicate"); } } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - AppiumBy appiumBy = (AppiumBy) o; - return Objects.equals(remoteParameters, appiumBy.remoteParameters) - && Objects.equals(locatorName, appiumBy.locatorName); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), remoteParameters, locatorName); - } } - - diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java index 4bb1b1f4d..6c0c0f99f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java @@ -16,6 +16,7 @@ package io.appium.java_client.pagefactory.bys; +import lombok.EqualsAndHashCode; import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebElement; @@ -23,11 +24,11 @@ import javax.annotation.Nonnull; import java.util.List; import java.util.Map; -import java.util.Objects; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; import static java.util.Objects.requireNonNull; +@EqualsAndHashCode(callSuper = true) public class ContentMappedBy extends By { private final Map map; private ContentType currentContent = NATIVE_MOBILE_SPECIFIC; @@ -62,24 +63,4 @@ public List findElements(SearchContext context) { public String toString() { return map.get(currentContent).toString(); } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - ContentMappedBy that = (ContentMappedBy) o; - return Objects.equals(map, that.map) && currentContent == that.currentContent; - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), map, currentContent); - } } diff --git a/src/main/java/io/appium/java_client/touch/ActionOptions.java b/src/main/java/io/appium/java_client/touch/ActionOptions.java index 2673142e4..2514a92a5 100644 --- a/src/main/java/io/appium/java_client/touch/ActionOptions.java +++ b/src/main/java/io/appium/java_client/touch/ActionOptions.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.Map; +@Deprecated public abstract class ActionOptions> { /** * This method is automatically called before building diff --git a/src/main/java/io/appium/java_client/touch/LongPressOptions.java b/src/main/java/io/appium/java_client/touch/LongPressOptions.java index 9f9104b71..56d2334fb 100644 --- a/src/main/java/io/appium/java_client/touch/LongPressOptions.java +++ b/src/main/java/io/appium/java_client/touch/LongPressOptions.java @@ -25,6 +25,7 @@ import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; +@Deprecated public class LongPressOptions extends AbstractOptionCombinedWithPosition { protected Duration duration = null; diff --git a/src/main/java/io/appium/java_client/touch/TapOptions.java b/src/main/java/io/appium/java_client/touch/TapOptions.java index 620df8286..7dee99fae 100644 --- a/src/main/java/io/appium/java_client/touch/TapOptions.java +++ b/src/main/java/io/appium/java_client/touch/TapOptions.java @@ -23,6 +23,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.util.Optional.ofNullable; +@Deprecated public class TapOptions extends AbstractOptionCombinedWithPosition { private Integer tapsCount = null; diff --git a/src/main/java/io/appium/java_client/touch/WaitOptions.java b/src/main/java/io/appium/java_client/touch/WaitOptions.java index 29d869b07..11eb0ccdc 100644 --- a/src/main/java/io/appium/java_client/touch/WaitOptions.java +++ b/src/main/java/io/appium/java_client/touch/WaitOptions.java @@ -23,6 +23,7 @@ import static java.time.Duration.ofMillis; import static java.util.Objects.requireNonNull; +@Deprecated public class WaitOptions extends ActionOptions { protected Duration duration = ofMillis(0); diff --git a/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java index d10a05682..194228eea 100644 --- a/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java +++ b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java @@ -6,6 +6,7 @@ import static java.util.Optional.ofNullable; +@Deprecated public abstract class AbstractOptionCombinedWithPosition> extends ActionOptions> { private ActionOptions positionOption; 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 16cf65f70..ac5d577a7 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 @@ -11,6 +11,7 @@ import static java.util.Objects.requireNonNull; import static java.util.Optional.ofNullable; +@Deprecated public class ElementOption extends PointOption { private String elementId; diff --git a/src/main/java/io/appium/java_client/touch/offset/PointOption.java b/src/main/java/io/appium/java_client/touch/offset/PointOption.java index 9bad51609..a45d59f9c 100644 --- a/src/main/java/io/appium/java_client/touch/offset/PointOption.java +++ b/src/main/java/io/appium/java_client/touch/offset/PointOption.java @@ -7,6 +7,7 @@ import static java.util.Optional.ofNullable; +@Deprecated public class PointOption> extends ActionOptions { protected Point coordinates; diff --git a/src/test/java/io/appium/java_client/touch/DummyElement.java b/src/test/java/io/appium/java_client/touch/DummyElement.java deleted file mode 100644 index 62e1fbb12..000000000 --- a/src/test/java/io/appium/java_client/touch/DummyElement.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.appium.java_client.touch; - -import org.openqa.selenium.By; -import org.openqa.selenium.Dimension; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.Point; -import org.openqa.selenium.Rectangle; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.remote.RemoteWebElement; - -import java.util.List; - -public class DummyElement extends RemoteWebElement { - @Override - public void click() { - // dummy - } - - @Override - public void submit() { - // dummy - } - - @Override - public void sendKeys(CharSequence... charSequences) { - // dummy - } - - @Override - public void clear() { - // dummy - } - - @Override - public String getTagName() { - return ""; - } - - @Override - public String getAttribute(String s) { - return ""; - } - - @Override - public boolean isSelected() { - return false; - } - - @Override - public boolean isEnabled() { - return false; - } - - @Override - public String getText() { - return ""; - } - - @Override - public List findElements(By by) { - return null; - } - - @Override - public WebElement findElement(By by) { - return null; - } - - @Override - public boolean isDisplayed() { - return false; - } - - @Override - public Point getLocation() { - return null; - } - - @Override - public Dimension getSize() { - return null; - } - - @Override - public Rectangle getRect() { - return null; - } - - @Override - public String getCssValue(String s) { - return ""; - } - - @Override - public X getScreenshotAs(OutputType outputType) { - return null; - } - - @Override - public String getId() { - return "123"; - } -} diff --git a/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java b/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java deleted file mode 100644 index 2d79fcfec..000000000 --- a/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.appium.java_client.touch; - -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import static org.hamcrest.core.AllOf.allOf; -import static org.hamcrest.core.IsInstanceOf.instanceOf; - -public final class FailsWithMatcher extends TypeSafeMatcher { - - private final Matcher matcher; - - private FailsWithMatcher(final Matcher matcher) { - this.matcher = matcher; - } - - public static Matcher failsWith( - final Class throwableType) { - return new FailsWithMatcher<>(instanceOf(throwableType)); - } - - public static Matcher failsWith( - final Class throwableType, final Matcher throwableMatcher) { - return new FailsWithMatcher(allOf(instanceOf(throwableType), throwableMatcher)); - } - - @Override - protected boolean matchesSafely(final Runnable runnable) { - try { - runnable.run(); - return false; - } catch (final Throwable ex) { - return matcher.matches(ex); - } - } - - @Override - public void describeTo(final Description description) { - description.appendText("fails with ").appendDescriptionOf(matcher); - } - -} diff --git a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java deleted file mode 100644 index a68bc3fa6..000000000 --- a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java +++ /dev/null @@ -1,96 +0,0 @@ -package io.appium.java_client.touch; - -import io.appium.java_client.touch.offset.ElementOption; -import io.appium.java_client.touch.offset.PointOption; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.Point; -import org.openqa.selenium.remote.RemoteWebElement; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.appium.java_client.touch.FailsWithMatcher.failsWith; -import static io.appium.java_client.touch.LongPressOptions.longPressOptions; -import static io.appium.java_client.touch.TapOptions.tapOptions; -import static io.appium.java_client.touch.WaitOptions.waitOptions; -import static io.appium.java_client.touch.offset.ElementOption.element; -import static io.appium.java_client.touch.offset.PointOption.point; -import static java.time.Duration.ofMillis; -import static java.time.Duration.ofSeconds; -import static org.hamcrest.CoreMatchers.everyItem; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.in; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class TouchOptionsTests { - private static final RemoteWebElement DUMMY_ELEMENT = new DummyElement(); - - @Test - public void invalidEmptyPointOptionsShouldFailOnBuild() { - assertThrows(IllegalArgumentException.class, - () -> new PointOption<>().build()); - } - - @Test - public void invalidEmptyElementOptionsShouldFailOnBuild() { - assertThrows(IllegalArgumentException.class, - () -> new ElementOption().build()); - } - - @Test - public void invalidOptionsArgumentsShouldFailOnAltering() { - final List invalidOptions = new ArrayList<>(); - invalidOptions.add(() -> waitOptions(ofMillis(-1))); - invalidOptions.add(() -> new ElementOption().withCoordinates(new Point(0, 0)).withElement(null)); - invalidOptions.add(() -> new WaitOptions().withDuration(null)); - invalidOptions.add(() -> tapOptions().withTapsCount(-1)); - invalidOptions.add(() -> longPressOptions().withDuration(null)); - invalidOptions.add(() -> longPressOptions().withDuration(ofMillis(-1))); - for (Runnable item : invalidOptions) { - assertThat(item, failsWith(RuntimeException.class)); - } - } - - @Test - public void longPressOptionsShouldBuildProperly() { - final Map actualOpts = longPressOptions() - .withElement(element(DUMMY_ELEMENT).withCoordinates(0, 0)) - .withDuration(ofMillis(1)) - .build(); - final Map expectedOpts = new HashMap<>(); - expectedOpts.put("element", DUMMY_ELEMENT.getId()); - expectedOpts.put("x", 0); - expectedOpts.put("y", 0); - expectedOpts.put("duration", 1L); - assertThat(actualOpts.entrySet(), everyItem(is(in(expectedOpts.entrySet())))); - assertThat(expectedOpts.entrySet(), everyItem(is(in(actualOpts.entrySet())))); - } - - @Test - public void tapOptionsShouldBuildProperly() { - final Map actualOpts = tapOptions() - .withPosition(point(new Point(0, 0))) - .withTapsCount(2) - .build(); - final Map expectedOpts = new HashMap<>(); - expectedOpts.put("x", 0); - expectedOpts.put("y", 0); - expectedOpts.put("count", 2); - assertThat(actualOpts.entrySet(), everyItem(is(in(expectedOpts.entrySet())))); - assertThat(expectedOpts.entrySet(), everyItem(is(in(actualOpts.entrySet())))); - } - - @Test - public void waitOptionsShouldBuildProperly() { - final Map actualOpts = new WaitOptions() - .withDuration(ofSeconds(1)) - .build(); - final Map expectedOpts = new HashMap<>(); - expectedOpts.put("ms", 1000L); - assertThat(actualOpts.entrySet(), everyItem(is(in(expectedOpts.entrySet())))); - assertThat(expectedOpts.entrySet(), everyItem(is(in(actualOpts.entrySet())))); - } -} From bb4ee2d15316b09ad3e88401482b651e58d6e7fd Mon Sep 17 00:00:00 2001 From: Sudharsan Selvaraj Date: Sun, 21 Jul 2024 02:30:10 +0530 Subject: [PATCH 250/314] feat: Add locator types supported by flutter integration driver (#2201) --- .../java/io/appium/java_client/AppiumBy.java | 125 ++++++++++++++++-- 1 file changed, 113 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index cb374831e..164c7d3e7 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -32,7 +32,8 @@ @EqualsAndHashCode(callSuper = true) public abstract class AppiumBy extends By implements Remotable { - @Getter private final Parameters remoteParameters; + @Getter + private final Parameters remoteParameters; private final String locatorName; protected AppiumBy(String selector, String locatorString, String locatorName) { @@ -41,15 +42,18 @@ protected AppiumBy(String selector, String locatorString, String locatorName) { this.locatorName = locatorName; } - @Override public List findElements(SearchContext context) { + @Override + public List findElements(SearchContext context) { return context.findElements(this); } - @Override public WebElement findElement(SearchContext context) { + @Override + public WebElement findElement(SearchContext context) { return context.findElement(this); } - @Override public String toString() { + @Override + public String toString() { return String.format("%s.%s: %s", AppiumBy.class.getSimpleName(), locatorName, remoteParameters.value()); } @@ -59,6 +63,7 @@ protected AppiumBy(String selector, String locatorString, String locatorName) { * About iOS accessibility * https://developer.apple.com/library/ios/documentation/UIKit/Reference/ * UIAccessibilityIdentification_Protocol/index.html + * * @param accessibilityId id is a convenient UI automation accessibility Id. * @return an instance of {@link AppiumBy.ByAndroidUIAutomator} */ @@ -68,9 +73,10 @@ public static By accessibilityId(final String accessibilityId) { /** * This locator strategy is only available in Espresso Driver mode. + * * @param dataMatcherString is a valid json string detailing hamcrest matcher for Espresso onData(). - * See - * the documentation for more details + * See + * the documentation for more details * @return an instance of {@link AppiumBy.ByAndroidDataMatcher} */ public static By androidDataMatcher(final String dataMatcherString) { @@ -79,6 +85,7 @@ public static By androidDataMatcher(final String dataMatcherString) { /** * Refer to https://developer.android.com/training/testing/ui-automator + * * @param uiautomatorText is Android UIAutomator string * @return an instance of {@link AppiumBy.ByAndroidUIAutomator} */ @@ -88,9 +95,10 @@ public static By androidUIAutomator(final String uiautomatorText) { /** * 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 + * See + * the documentation for more details * @return an instance of {@link AppiumBy.ByAndroidViewMatcher} */ public static By androidViewMatcher(final String viewMatcherString) { @@ -99,9 +107,10 @@ public static By androidViewMatcher(final String viewMatcherString) { /** * This locator strategy is available in Espresso Driver mode. - * @since Appium 1.8.2 beta + * * @param tag is a view tag string * @return an instance of {@link ByAndroidViewTag} + * @since Appium 1.8.2 beta */ public static By androidViewTag(final String tag) { return new ByAndroidViewTag(tag); @@ -110,6 +119,7 @@ public static By androidViewTag(final String tag) { /** * 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} */ @@ -120,6 +130,7 @@ public static By className(final String selector) { /** * For IOS the element name. * For Android it is the resource identifier. + * * @param selector element id * @return an instance of {@link ById} */ @@ -130,6 +141,7 @@ public static By id(final String selector) { /** * For IOS the element name. * For Android it is the resource identifier. + * * @param selector element id * @return an instance of {@link ByName} */ @@ -153,14 +165,14 @@ public static By custom(final String selector) { * This locator strategy is available only if OpenCV libraries and * Node.js bindings are installed on the server machine. * + * @param b64Template base64-encoded template image string. Supported image formats are the same + * as for OpenCV library. + * @return an instance of {@link ByImage} * @see * The documentation on Image Comparison Features * @see * The settings available for lookup fine-tuning * @since Appium 1.8.2 - * @param b64Template base64-encoded template image string. Supported image formats are the same - * as for OpenCV library. - * @return an instance of {@link ByImage} */ public static By image(final String b64Template) { return new ByImage(b64Template); @@ -168,6 +180,7 @@ public static By image(final String b64Template) { /** * This locator strategy is available in XCUITest Driver mode. + * * @param iOSClassChainString is a valid class chain locator string. * See * the documentation for more details @@ -179,6 +192,7 @@ public static By iOSClassChain(final String iOSClassChainString) { /** * This locator strategy is available in XCUITest Driver mode. + * * @param iOSNsPredicateString is an iOS NsPredicate String * @return an instance of {@link AppiumBy.ByIosNsPredicate} */ @@ -186,6 +200,56 @@ public static By iOSNsPredicateString(final String iOSNsPredicateString) { return new ByIosNsPredicate(iOSNsPredicateString); } + /** + * This locator strategy is available in FlutterIntegration Driver mode. + * + * @param selector is the value defined to the key attribute of the flutter element + * @return an instance of {@link AppiumBy.ByFlutterKey} + */ + public static By flutterKey(final String selector) { + return new ByFlutterKey(selector); + } + + /** + * This locator strategy is available in FlutterIntegration Driver mode. + * + * @param selector is the Type of widget mounted in the app tree + * @return an instance of {@link AppiumBy.ByFlutterType} + */ + public static By flutterType(final String selector) { + return new ByFlutterType(selector); + } + + /** + * This locator strategy is available in FlutterIntegration Driver mode. + * + * @param selector is the text that is present on the widget + * @return an instance of {@link AppiumBy.ByFlutterText} + */ + public static By flutterText(final String selector) { + return new ByFlutterText(selector); + } + + /** + * This locator strategy is available in FlutterIntegration Driver mode. + * + * @param selector is the text that is partially present on the widget + * @return an instance of {@link AppiumBy.ByFlutterTextContaining} + */ + public static By flutterTextContaining(final String selector) { + return new ByFlutterTextContaining(selector); + } + + /** + * This locator strategy is available in FlutterIntegration Driver mode. + * + * @param semanticsLabel represents the value assigned to the label attribute of semantics element + * @return an instance of {@link AppiumBy.ByFlutterSemanticsLabel} + */ + public static By flutterSemanticsLabel(final String semanticsLabel) { + return new ByFlutterSemanticsLabel(semanticsLabel); + } + public static class ByAccessibilityId extends AppiumBy implements Serializable { public ByAccessibilityId(String accessibilityId) { super("accessibility id", accessibilityId, "accessibilityId"); @@ -257,4 +321,41 @@ protected ByIosNsPredicate(String locatorString) { super("-ios predicate string", locatorString, "iOSNsPredicate"); } } + + public abstract static class FlutterBy extends AppiumBy { + protected FlutterBy(String selector, String locatorString, String locatorName) { + super(selector, locatorString, locatorName); + } + } + + public static class ByFlutterType extends FlutterBy implements Serializable { + protected ByFlutterType(String locatorString) { + super("-flutter type", locatorString, "flutterType"); + } + } + + public static class ByFlutterKey extends FlutterBy implements Serializable { + protected ByFlutterKey(String locatorString) { + super("-flutter key", locatorString, "flutterKey"); + } + } + + public static class ByFlutterSemanticsLabel extends FlutterBy implements Serializable { + protected ByFlutterSemanticsLabel(String locatorString) { + super("-flutter semantics label", locatorString, "flutterSemanticsLabel"); + } + } + + public static class ByFlutterText extends FlutterBy implements Serializable { + protected ByFlutterText(String locatorString) { + super("-flutter text", locatorString, "flutterText"); + } + } + + public static class ByFlutterTextContaining extends FlutterBy implements Serializable { + protected ByFlutterTextContaining(String locatorString) { + super("-flutter text containing", locatorString, "flutterTextContaining"); + } + } + } From 7f28bfbcf051ec0b3d53533280b1236ca2a2838f Mon Sep 17 00:00:00 2001 From: Sudharsan Selvaraj Date: Tue, 23 Jul 2024 06:20:12 +0530 Subject: [PATCH 251/314] feat: add support for FlutterAndroidDriver (#2203) --- .github/workflows/gradle.yml | 48 +++++++--- build.gradle | 23 ++++- .../java_client/android/BaseFlutterTest.java | 82 +++++++++++++++++ .../java_client/android/CommandTest.java | 62 +++++++++++++ .../java_client/android/FinderTests.java | 54 ++++++++++++ .../java/io/appium/java_client/AppiumBy.java | 10 +-- .../flutter/CanExecuteFlutterScripts.java | 20 +++++ .../SupportsScrollingOfFlutterElements.java | 17 ++++ .../SupportsWaitingForFlutterElements.java | 25 ++++++ .../flutter/android/FlutterAndroidDriver.java | 72 +++++++++++++++ .../commands/FlutterCommandParameter.java | 25 ++++++ .../flutter/commands/ScrollParameter.java | 87 +++++++++++++++++++ .../flutter/commands/WaitParameter.java | 38 ++++++++ .../java_client/remote/AutomationName.java | 2 + 14 files changed, 544 insertions(+), 21 deletions(-) create mode 100644 src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java create mode 100644 src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java create mode 100644 src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java create mode 100644 src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java create mode 100644 src/main/java/io/appium/java_client/flutter/SupportsScrollingOfFlutterElements.java create mode 100644 src/main/java/io/appium/java_client/flutter/SupportsWaitingForFlutterElements.java create mode 100644 src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/FlutterCommandParameter.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e8006e8df..8cdab0ef7 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -26,6 +26,7 @@ env: XCODE_VERSION: "15.4" IOS_DEVICE_NAME: iPhone 15 IOS_PLATFORM_VERSION: "17.5" + FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk" jobs: build: @@ -33,25 +34,28 @@ jobs: strategy: matrix: include: - - java: 11 - # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available - platform: macos-14 - e2e-tests: ios - - java: 17 - platform: ubuntu-latest - e2e-tests: android - - java: 21 - platform: ubuntu-latest + - java: 11 + # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available + platform: macos-14 + e2e-tests: ios + - java: 17 + platform: ubuntu-latest + e2e-tests: android + - java: 17 + platform: ubuntu-latest + e2e-tests: flutter-android + - java: 21 + platform: ubuntu-latest fail-fast: false runs-on: ${{ matrix.platform }} - name: JDK ${{ matrix.java }} - ${{ matrix.platform }} + name: JDK ${{ matrix.java }} - ${{ matrix.platform }} ${{ matrix.e2e-tests }} steps: - uses: actions/checkout@v4 - name: Enable KVM group perms - if: matrix.e2e-tests == 'android' + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'flutter-android' run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules @@ -73,18 +77,23 @@ jobs: ./gradlew clean build -PisCI -Pselenium.version=$latest_snapshot - name: Install Node.js - if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-android' uses: actions/setup-node@v4 with: node-version: 'lts/*' - name: Install Appium - if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-android' run: npm install --location=global appium - name: Install UIA2 driver - if: matrix.e2e-tests == 'android' + if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'flutter-android' run: appium driver install uiautomator2 + + - name: Install Flutter Integration driver + if: matrix.e2e-tests == 'flutter-android' + run: appium driver install appium-flutter-integration-driver --source npm + - name: Run Android E2E tests if: matrix.e2e-tests == 'android' uses: reactivecircus/android-emulator-runner@v2 @@ -96,6 +105,17 @@ jobs: disable-animations: true target: ${{ env.ANDROID_EMU_TARGET }} + - name: Run Flutter Android E2E tests + if: matrix.e2e-tests == 'flutter-android' + uses: reactivecircus/android-emulator-runner@v2 + with: + script: ./gradlew e2eFlutterTest -Pplatform="android" -Pselenium.version=$latest_snapshot -PisCI -PflutterApp=${{ env.FLUTTER_ANDROID_APP }} + api-level: ${{ env.ANDROID_SDK_VERSION }} + avd-name: ${{ env.ANDROID_EMU_NAME }} + disable-spellchecker: true + disable-animations: true + target: ${{ env.ANDROID_EMU_TARGET }} + - name: Select Xcode if: matrix.e2e-tests == 'ios' uses: maxim-lobanov/setup-xcode@v1 diff --git a/build.gradle b/build.gradle index 8de5f95be..3488703d7 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { } dependencyCheck { - failBuildOnCVSS=22 + failBuildOnCVSS = 22 } jacoco { @@ -185,7 +185,7 @@ wrapper { processResources { filter ReplaceTokens, tokens: [ - 'selenium.version': seleniumVersion, + 'selenium.version' : seleniumVersion, 'appiumClient.version': appiumClientVersion ] } @@ -290,5 +290,24 @@ testing { } } } + + e2eFlutterTest(JvmTestSuite) { + sources { + java { + srcDirs = ['src/e2eFlutterTest/java'] + } + } + dependencies { + implementation project() + implementation(sourceSets.test.output) + } + + targets.configureEach { + testTask.configure { + shouldRunAfter(test) + systemProperties project.properties.subMap(["platform", "flutterApp"]) + } + } + } } } diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java new file mode 100644 index 000000000..e18112d4a --- /dev/null +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java @@ -0,0 +1,82 @@ +package io.appium.java_client.android; + +import io.appium.java_client.AppiumBy; +import io.appium.java_client.android.options.UiAutomator2Options; +import io.appium.java_client.flutter.android.FlutterAndroidDriver; +import io.appium.java_client.flutter.commands.ScrollParameter; +import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import io.appium.java_client.service.local.AppiumServiceBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.openqa.selenium.By; +import org.openqa.selenium.InvalidArgumentException; +import org.openqa.selenium.WebElement; + +import java.net.MalformedURLException; +import java.util.Optional; + +class BaseFlutterTest { + + private static final boolean IS_ANDROID = Optional + .ofNullable(System.getProperty("platform")) + .orElse("android") + .equalsIgnoreCase("android"); + private static final String APP_ID = IS_ANDROID + ? "com.example.appium_testing_app" : "com.example.appiumTestingApp"; + protected static final int PORT = 4723; + + private static AppiumDriverLocalService service; + protected static FlutterAndroidDriver driver; + protected static final By LOGIN_BUTTON = AppiumBy.flutterText("Login"); + + /** + * initialization. + */ + @BeforeAll + public static void beforeClass() { + service = new AppiumServiceBuilder() + .withIPAddress("127.0.0.1") + .usingPort(PORT) + .build(); + service.start(); + } + + @BeforeEach + public void startSession() throws MalformedURLException { + if (IS_ANDROID) { + // TODO: update it with FlutterDriverOptions once implemented + UiAutomator2Options options = new UiAutomator2Options() + .setAutomationName(AutomationName.FLUTTER_INTEGRATION) + .setApp(System.getProperty("flutterApp")) + .eventTimings(); + driver = new FlutterAndroidDriver(service.getUrl(), options); + } else { + throw new InvalidArgumentException( + "Currently flutter driver implementation only supports android platform"); + } + } + + @AfterEach + public void stopSession() { + if (driver != null) { + driver.quit(); + } + } + + @AfterAll + public static void afterClass() { + if (service.isRunning()) { + service.stop(); + } + } + + public void openScreen(String screenTitle) { + ScrollParameter scrollOptions = new ScrollParameter( + AppiumBy.flutterText(screenTitle), ScrollParameter.ScrollDirection.DOWN); + WebElement element = driver.scrollTillVisible(scrollOptions); + element.click(); + } +} diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java new file mode 100644 index 000000000..80f32002d --- /dev/null +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java @@ -0,0 +1,62 @@ +package io.appium.java_client.android; + +import io.appium.java_client.AppiumBy; +import io.appium.java_client.flutter.commands.ScrollParameter; +import io.appium.java_client.flutter.commands.WaitParameter; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebElement; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CommandTest extends BaseFlutterTest { + + private static final AppiumBy.FlutterBy MESSAGE_FIELD = AppiumBy.flutterKey("message_field"); + private static final AppiumBy.FlutterBy TOGGLE_BUTTON = AppiumBy.flutterKey("toggle_button"); + + @Test + public void testWaitCommand() { + WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); + loginButton.click(); + openScreen("Lazy Loading"); + + WebElement messageField = driver.findElement(MESSAGE_FIELD); + WebElement toggleButton = driver.findElement(TOGGLE_BUTTON); + + assertEquals(messageField.getText(), "Hello world"); + toggleButton.click(); + assertEquals(messageField.getText(), "Hello world"); + + WaitParameter waitParameter = new WaitParameter().setLocator(MESSAGE_FIELD); + + driver.waitForInVisible(waitParameter); + assertEquals(0, driver.findElements(MESSAGE_FIELD).size()); + toggleButton.click(); + driver.waitForVisible(waitParameter); + assertEquals(1, driver.findElements(MESSAGE_FIELD).size()); + assertEquals(messageField.getText(), "Hello world"); + } + + @Test + public void testScrollTillVisibleCommand() { + WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); + loginButton.click(); + openScreen("Vertical Swiping"); + + WebElement firstElement = driver.scrollTillVisible(new ScrollParameter(AppiumBy.flutterText("Java"))); + assertTrue(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); + + WebElement lastElement = driver.scrollTillVisible(new ScrollParameter(AppiumBy.flutterText("Protractor"))); + assertTrue(Boolean.parseBoolean(lastElement.getAttribute("displayed"))); + assertFalse(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); + + firstElement = driver.scrollTillVisible( + new ScrollParameter(AppiumBy.flutterText("Java"), + ScrollParameter.ScrollDirection.UP) + ); + assertTrue(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); + assertFalse(Boolean.parseBoolean(lastElement.getAttribute("displayed"))); + } + +} diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java b/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java new file mode 100644 index 000000000..dc2361869 --- /dev/null +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java @@ -0,0 +1,54 @@ +package io.appium.java_client.android; + +import io.appium.java_client.AppiumBy; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebElement; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class FinderTests extends BaseFlutterTest { + + @Test + public void testFlutterByKey() { + WebElement userNameField = driver.findElement(AppiumBy.flutterKey("username_text_field")); + assertEquals("admin", userNameField.getText()); + userNameField.clear(); + driver.findElement(AppiumBy.flutterKey("username_text_field")).sendKeys("admin123"); + assertEquals("admin123", userNameField.getText()); + } + + @Test + public void testFlutterByType() { + WebElement loginButton = driver.findElement(AppiumBy.flutterType("ElevatedButton")); + assertEquals(loginButton.findElement(AppiumBy.flutterType("Text")).getText(), "Login"); + } + + @Test + public void testFlutterText() { + WebElement loginButton = driver.findElement(AppiumBy.flutterText("Login")); + assertEquals(loginButton.getText(), "Login"); + loginButton.click(); + + assertEquals(1, driver.findElements(AppiumBy.flutterText("Slider")).size()); + } + + @Test + public void testFlutterTextContaining() { + WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); + loginButton.click(); + assertEquals(driver.findElement(AppiumBy.flutterTextContaining("Vertical")).getText(), + "Vertical Swiping"); + } + + @Test + public void testFlutterSemanticsLabel() { + WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); + loginButton.click(); + openScreen("Lazy Loading"); + + WebElement messageField = driver.findElement(AppiumBy.flutterSemanticsLabel("message_field")); + assertEquals(messageField.getText(), + "Hello world"); + } +} diff --git a/src/main/java/io/appium/java_client/AppiumBy.java b/src/main/java/io/appium/java_client/AppiumBy.java index 164c7d3e7..aca01fea7 100644 --- a/src/main/java/io/appium/java_client/AppiumBy.java +++ b/src/main/java/io/appium/java_client/AppiumBy.java @@ -206,7 +206,7 @@ public static By iOSNsPredicateString(final String iOSNsPredicateString) { * @param selector is the value defined to the key attribute of the flutter element * @return an instance of {@link AppiumBy.ByFlutterKey} */ - public static By flutterKey(final String selector) { + public static FlutterBy flutterKey(final String selector) { return new ByFlutterKey(selector); } @@ -216,7 +216,7 @@ public static By flutterKey(final String selector) { * @param selector is the Type of widget mounted in the app tree * @return an instance of {@link AppiumBy.ByFlutterType} */ - public static By flutterType(final String selector) { + public static FlutterBy flutterType(final String selector) { return new ByFlutterType(selector); } @@ -226,7 +226,7 @@ public static By flutterType(final String selector) { * @param selector is the text that is present on the widget * @return an instance of {@link AppiumBy.ByFlutterText} */ - public static By flutterText(final String selector) { + public static FlutterBy flutterText(final String selector) { return new ByFlutterText(selector); } @@ -236,7 +236,7 @@ public static By flutterText(final String selector) { * @param selector is the text that is partially present on the widget * @return an instance of {@link AppiumBy.ByFlutterTextContaining} */ - public static By flutterTextContaining(final String selector) { + public static FlutterBy flutterTextContaining(final String selector) { return new ByFlutterTextContaining(selector); } @@ -246,7 +246,7 @@ public static By flutterTextContaining(final String selector) { * @param semanticsLabel represents the value assigned to the label attribute of semantics element * @return an instance of {@link AppiumBy.ByFlutterSemanticsLabel} */ - public static By flutterSemanticsLabel(final String semanticsLabel) { + public static FlutterBy flutterSemanticsLabel(final String semanticsLabel) { return new ByFlutterSemanticsLabel(semanticsLabel); } diff --git a/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java b/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java new file mode 100644 index 000000000..f8eaf3af4 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java @@ -0,0 +1,20 @@ +package io.appium.java_client.flutter; + +import io.appium.java_client.flutter.commands.FlutterCommandParameter; +import org.openqa.selenium.JavascriptExecutor; + +public interface CanExecuteFlutterScripts extends JavascriptExecutor { + + /** + * Executes a Flutter-specific script using JavascriptExecutor. + * + * @param scriptName The name of the Flutter script to execute. + * @param parameter The parameters for the Flutter command. + * @return The result of executing the script. + */ + default Object executeFlutterCommand(String scriptName, FlutterCommandParameter parameter) { + String commandName = String.format("flutter: %s", scriptName); + return executeScript(commandName, parameter.toJson()); + } + +} diff --git a/src/main/java/io/appium/java_client/flutter/SupportsScrollingOfFlutterElements.java b/src/main/java/io/appium/java_client/flutter/SupportsScrollingOfFlutterElements.java new file mode 100644 index 000000000..25a734cf7 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/SupportsScrollingOfFlutterElements.java @@ -0,0 +1,17 @@ +package io.appium.java_client.flutter; + +import io.appium.java_client.flutter.commands.ScrollParameter; +import org.openqa.selenium.WebElement; + +public interface SupportsScrollingOfFlutterElements extends CanExecuteFlutterScripts { + + /** + * Scrolls to make an element visible on the screen. + * + * @param parameter The parameters for scrolling, specifying element details. + * @return The WebElement that was scrolled to. + */ + default WebElement scrollTillVisible(ScrollParameter parameter) { + return (WebElement) executeFlutterCommand("scrollTillVisible", parameter); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/SupportsWaitingForFlutterElements.java b/src/main/java/io/appium/java_client/flutter/SupportsWaitingForFlutterElements.java new file mode 100644 index 000000000..521f75cc8 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/SupportsWaitingForFlutterElements.java @@ -0,0 +1,25 @@ +package io.appium.java_client.flutter; + +import io.appium.java_client.flutter.commands.WaitParameter; + +public interface SupportsWaitingForFlutterElements extends CanExecuteFlutterScripts { + + /** + * Waits for an element to become visible on the screen. + * + * @param parameter The parameters for waiting, specifying timeout and element details. + */ + default void waitForVisible(WaitParameter parameter) { + executeFlutterCommand("waitForVisible", parameter); + } + + /** + * Waits for an element to become absent on the screen. + * + * @param parameter The parameters for waiting, specifying timeout and element details. + */ + default void waitForInVisible(WaitParameter parameter) { + executeFlutterCommand("waitForAbsent", parameter); + } + +} diff --git a/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java b/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java new file mode 100644 index 000000000..f7be154ff --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java @@ -0,0 +1,72 @@ +package io.appium.java_client.flutter.android; + +import io.appium.java_client.AppiumClientConfig; +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.flutter.SupportsScrollingOfFlutterElements; +import io.appium.java_client.flutter.SupportsWaitingForFlutterElements; +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.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URL; + +/** + * Custom AndroidDriver implementation with additional Flutter-specific capabilities. + */ +public class FlutterAndroidDriver extends AndroidDriver implements + SupportsWaitingForFlutterElements, + SupportsScrollingOfFlutterElements { + + public FlutterAndroidDriver(HttpCommandExecutor executor, Capabilities capabilities) { + super(executor, capabilities); + } + + public FlutterAndroidDriver(URL remoteAddress, Capabilities capabilities) { + super(remoteAddress, capabilities); + } + + public FlutterAndroidDriver(URL remoteAddress, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(remoteAddress, httpClientFactory, capabilities); + } + + public FlutterAndroidDriver(AppiumDriverLocalService service, Capabilities capabilities) { + super(service, capabilities); + } + + public FlutterAndroidDriver( + AppiumDriverLocalService service, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(service, httpClientFactory, capabilities); + } + + public FlutterAndroidDriver(AppiumServiceBuilder builder, Capabilities capabilities) { + super(builder, capabilities); + } + + public FlutterAndroidDriver( + AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(builder, httpClientFactory, capabilities); + } + + public FlutterAndroidDriver(HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(httpClientFactory, capabilities); + } + + public FlutterAndroidDriver(ClientConfig clientConfig, Capabilities capabilities) { + super(clientConfig, capabilities); + } + + public FlutterAndroidDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, capabilities); + } + + public FlutterAndroidDriver(Capabilities capabilities) { + super(capabilities); + } + + public FlutterAndroidDriver(URL remoteSessionAddress, String automationName) { + super(remoteSessionAddress, automationName); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/FlutterCommandParameter.java b/src/main/java/io/appium/java_client/flutter/commands/FlutterCommandParameter.java new file mode 100644 index 000000000..ddd2d74f6 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/FlutterCommandParameter.java @@ -0,0 +1,25 @@ +package io.appium.java_client.flutter.commands; + +import io.appium.java_client.AppiumBy; +import org.openqa.selenium.By; + +import java.util.Map; + +public abstract class FlutterCommandParameter { + + /** + * Parses an Appium Flutter locator into a Map representation suitable for Flutter Integration Driver. + * + * @param by The FlutterBy instance representing the locator to parse. + * @return A Map containing the parsed locator information with keys using and value. + */ + protected static Map parseFlutterLocator(AppiumBy.FlutterBy by) { + By.Remotable.Parameters parameters = by.getRemoteParameters(); + return Map.of( + "using", parameters.using(), + "value", parameters.value() + ); + } + + public abstract Map toJson(); +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java new file mode 100644 index 000000000..695cb060d --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java @@ -0,0 +1,87 @@ +package io.appium.java_client.flutter.commands; + +import com.google.common.base.Preconditions; +import io.appium.java_client.AppiumBy; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.openqa.selenium.WebElement; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Accessors(chain = true) +@Getter +@Setter +public class ScrollParameter extends FlutterCommandParameter { + private AppiumBy.FlutterBy scrollTo; + private WebElement scrollView; + private ScrollDirection scrollDirection; + private Integer delta; + private Integer maxScrolls; + private Integer settleBetweenScrollsTimeout; + private Duration dragDuration; + + private ScrollParameter() { + } + + /** + * Constructs a new ScrollOptions object with the given parameters. + * + * @param scrollTo the locator used for scrolling to a specific element + */ + public ScrollParameter(AppiumBy.FlutterBy scrollTo) { + this(scrollTo, ScrollDirection.DOWN); + } + + /** + * Constructs a new ScrollOptions object with the given parameters. + * + * @param scrollTo the locator used for scrolling to a specific element + * @param scrollDirection the direction in which to scroll (e.g., ScrollDirection.DOWN) + * @throws IllegalArgumentException if scrollTo is null + */ + public ScrollParameter(AppiumBy.FlutterBy scrollTo, ScrollDirection scrollDirection) { + Preconditions.checkArgument(scrollTo != null, "Must supply a valid locator for scrollTo"); + this.scrollTo = scrollTo; + this.scrollDirection = scrollDirection; + } + + @Override + public Map toJson() { + Map params = new HashMap<>(); + + params.put("finder", parseFlutterLocator(scrollTo)); + Optional.ofNullable(scrollView) + .ifPresent(scrollView -> params.put("scrollView", scrollView)); + Optional.ofNullable(delta) + .ifPresent(delta -> params.put("delta", delta)); + Optional.ofNullable(maxScrolls) + .ifPresent(maxScrolls -> params.put("delta", maxScrolls)); + Optional.ofNullable(settleBetweenScrollsTimeout) + .ifPresent(timeout -> params.put("delta", settleBetweenScrollsTimeout)); + Optional.ofNullable(scrollDirection) + .ifPresent(direction -> params.put("scrollDirection", direction.getDirection())); + Optional.ofNullable(dragDuration) + .ifPresent(direction -> params.put("dragDuration", dragDuration.getSeconds())); + + return Collections.unmodifiableMap(params); + } + + @Getter + public static enum ScrollDirection { + UP("up"), + RIGHT("right"), + DOWN("down"), + LEFT("left"); + + private final String direction; + + ScrollDirection(String direction) { + this.direction = direction; + } + } +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java b/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java new file mode 100644 index 000000000..89e0a19cf --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java @@ -0,0 +1,38 @@ +package io.appium.java_client.flutter.commands; + +import com.google.common.base.Preconditions; +import io.appium.java_client.AppiumBy; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.openqa.selenium.WebElement; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Accessors(chain = true) +@Getter +@Setter +public class WaitParameter extends FlutterCommandParameter { + private WebElement element; + private AppiumBy.FlutterBy locator; + private Duration timeout; + + @Override + public Map toJson() { + Preconditions.checkArgument(element != null || locator != null, + "Must supply a valid element or locator to wait for"); + Map params = new HashMap<>(); + Optional.ofNullable(element) + .ifPresent(element -> params.put("element", element)); + Optional.ofNullable(locator) + .ifPresent(locator -> params.put("locator", parseFlutterLocator(locator))); + Optional.ofNullable(timeout) + .ifPresent(timeout -> params.put("timeout", timeout)); + + return Collections.unmodifiableMap(params); + } +} 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 7df904c4d..e941d516b 100644 --- a/src/main/java/io/appium/java_client/remote/AutomationName.java +++ b/src/main/java/io/appium/java_client/remote/AutomationName.java @@ -38,4 +38,6 @@ public interface AutomationName { // Third-party drivers // https://github.com/YOU-i-Labs/appium-youiengine-driver String YOUI_ENGINE = "youiengine"; + //https://github.com/AppiumTestDistribution/appium-flutter-integration-driver + String FLUTTER_INTEGRATION = "FlutterIntegration"; } From c809510e63120ca1117a01d195094a804f11c99b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 07:45:37 +0300 Subject: [PATCH 252/314] build(deps): Bump org.apache.commons:commons-lang3 from 3.14.0 to 3.15.0 (#2204) Bumps org.apache.commons:commons-lang3 from 3.14.0 to 3.15.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 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 3488703d7..b4656f8a5 100644 --- a/build.gradle +++ b/build.gradle @@ -233,7 +233,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('org.apache.commons:commons-lang3:3.14.0') + implementation('org.apache.commons:commons-lang3:3.15.0') } targets.configureEach { From 62a3a6fdcb082bd1884645838f4a584f97fc207d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 00:41:20 +0300 Subject: [PATCH 253/314] build(deps): Bump org.owasp.dependencycheck from 10.0.2 to 10.0.3 (#2205) Bumps org.owasp.dependencycheck from 10.0.2 to 10.0.3. --- 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 b4656f8a5..b65d41661 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '10.0.2' + id 'org.owasp.dependencycheck' version '10.0.3' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 6bd38a15ed5f971d7172d2f7edb4d069538d1d15 Mon Sep 17 00:00:00 2001 From: Sudharsan Selvaraj Date: Wed, 24 Jul 2024 06:09:03 +0530 Subject: [PATCH 254/314] feat: Add support for FlutterIOSDriver (#2206) --- .github/workflows/gradle.yml | 25 ++++--- .../java_client/android/BaseFlutterTest.java | 50 ++++++++++---- .../java_client/android/CommandTest.java | 60 +++++++++++++++- .../java_client/android/FinderTests.java | 10 +-- .../flutter/FlutterDriverOptions.java | 51 ++++++++++++++ .../flutter/FlutterIntegrationTestDriver.java | 23 +++++++ .../SupportsGestureOnFlutterElements.java | 35 ++++++++++ .../flutter/android/FlutterAndroidDriver.java | 7 +- .../commands/DoubleClickParameter.java | 34 +++++++++ .../commands/DragAndDropParameter.java | 35 ++++++++++ .../flutter/commands/LongPressParameter.java | 33 +++++++++ .../flutter/commands/ScrollParameter.java | 4 +- .../flutter/commands/WaitParameter.java | 4 +- .../flutter/ios/FlutterIOSDriver.java | 69 +++++++++++++++++++ ...pportsFlutterElementWaitTimeoutOption.java | 36 ++++++++++ ...portsFlutterServerLaunchTimeoutOption.java | 36 ++++++++++ .../SupportsFlutterSystemPortOption.java | 33 +++++++++ 17 files changed, 506 insertions(+), 39 deletions(-) create mode 100644 src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java create mode 100644 src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java create mode 100644 src/main/java/io/appium/java_client/flutter/SupportsGestureOnFlutterElements.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/DoubleClickParameter.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/DragAndDropParameter.java create mode 100644 src/main/java/io/appium/java_client/flutter/commands/LongPressParameter.java create mode 100644 src/main/java/io/appium/java_client/flutter/ios/FlutterIOSDriver.java create mode 100644 src/main/java/io/appium/java_client/flutter/options/SupportsFlutterElementWaitTimeoutOption.java create mode 100644 src/main/java/io/appium/java_client/flutter/options/SupportsFlutterServerLaunchTimeoutOption.java create mode 100644 src/main/java/io/appium/java_client/flutter/options/SupportsFlutterSystemPortOption.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8cdab0ef7..93527e84a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -27,6 +27,7 @@ env: IOS_DEVICE_NAME: iPhone 15 IOS_PLATFORM_VERSION: "17.5" FLUTTER_ANDROID_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/app-debug.apk" + FLUTTER_IOS_APP: "https://github.com/AppiumTestDistribution/appium-flutter-server/releases/latest/download/ios.zip" jobs: build: @@ -38,6 +39,10 @@ jobs: # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available platform: macos-14 e2e-tests: ios + - java: 17 + # Need to use specific (not `-latest`) version of macOS to be sure the required version of Xcode/simulator is available + platform: macos-14 + e2e-tests: flutter-ios - java: 17 platform: ubuntu-latest e2e-tests: android @@ -71,19 +76,19 @@ jobs: - name: Build with Gradle run: | latest_snapshot=$(curl -sf https://oss.sonatype.org/content/repositories/snapshots/org/seleniumhq/selenium/selenium-api/ | \ - python -c "import sys,re; print(re.findall(r'\d+\.\d+\.\d+-SNAPSHOT', sys.stdin.read())[-1])") + python -c "import sys,re; print(re.findall(r'\d+\.\d+\.\d+-SNAPSHOT', sys.stdin.read())[-1])") echo ">>> $latest_snapshot" echo "latest_snapshot=$latest_snapshot" >> "$GITHUB_ENV" ./gradlew clean build -PisCI -Pselenium.version=$latest_snapshot - name: Install Node.js - if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-android' + if: ${{ matrix.e2e-tests }} uses: actions/setup-node@v4 with: node-version: 'lts/*' - name: Install Appium - if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-android' + if: ${{ matrix.e2e-tests }} run: npm install --location=global appium - name: Install UIA2 driver @@ -91,7 +96,7 @@ jobs: run: appium driver install uiautomator2 - name: Install Flutter Integration driver - if: matrix.e2e-tests == 'flutter-android' + if: matrix.e2e-tests == 'flutter-android' || matrix.e2e-tests == 'flutter-ios' run: appium driver install appium-flutter-integration-driver --source npm - name: Run Android E2E tests @@ -117,22 +122,26 @@ jobs: target: ${{ env.ANDROID_EMU_TARGET }} - name: Select Xcode - if: matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "${{ env.XCODE_VERSION }}" - name: Prepare iOS simulator - if: matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' uses: futureware-tech/simulator-action@v3 with: model: "${{ env.IOS_DEVICE_NAME }}" os_version: "${{ env.IOS_PLATFORM_VERSION }}" - name: Install XCUITest driver - if: matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' run: appium driver install xcuitest - name: Prebuild XCUITest driver - if: matrix.e2e-tests == 'ios' + if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' run: appium driver run xcuitest build-wda - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' run: ./gradlew e2eIosTest -PisCI -Pselenium.version=$latest_snapshot + + - name: Run Flutter iOS E2E tests + if: matrix.e2e-tests == 'flutter-ios' + run: ./gradlew e2eFlutterTest -Pplatform="ios" -Pselenium.version=$latest_snapshot -PisCI -PflutterApp=${{ env.FLUTTER_IOS_APP }} diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java index e18112d4a..b2dc6f1eb 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java @@ -2,9 +2,12 @@ import io.appium.java_client.AppiumBy; import io.appium.java_client.android.options.UiAutomator2Options; +import io.appium.java_client.flutter.FlutterDriverOptions; +import io.appium.java_client.flutter.FlutterIntegrationTestDriver; import io.appium.java_client.flutter.android.FlutterAndroidDriver; import io.appium.java_client.flutter.commands.ScrollParameter; -import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.flutter.ios.FlutterIOSDriver; +import io.appium.java_client.ios.options.XCUITestOptions; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.junit.jupiter.api.AfterAll; @@ -12,10 +15,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.openqa.selenium.By; -import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.WebElement; import java.net.MalformedURLException; +import java.time.Duration; import java.util.Optional; class BaseFlutterTest { @@ -29,7 +32,7 @@ class BaseFlutterTest { protected static final int PORT = 4723; private static AppiumDriverLocalService service; - protected static FlutterAndroidDriver driver; + protected static FlutterIntegrationTestDriver driver; protected static final By LOGIN_BUTTON = AppiumBy.flutterText("Login"); /** @@ -45,35 +48,52 @@ public static void beforeClass() { } @BeforeEach - public void startSession() throws MalformedURLException { + void startSession() throws MalformedURLException { + FlutterDriverOptions flutterOptions = new FlutterDriverOptions() + .setFlutterServerLaunchTimeout(Duration.ofMinutes(2)) + .setFlutterSystemPort(9999) + .setFlutterElementWaitTimeout(Duration.ofSeconds(10)); if (IS_ANDROID) { - // TODO: update it with FlutterDriverOptions once implemented - UiAutomator2Options options = new UiAutomator2Options() - .setAutomationName(AutomationName.FLUTTER_INTEGRATION) - .setApp(System.getProperty("flutterApp")) - .eventTimings(); - driver = new FlutterAndroidDriver(service.getUrl(), options); + driver = new FlutterAndroidDriver(service.getUrl(), flutterOptions + .setUiAutomator2Options(new UiAutomator2Options() + .setApp(System.getProperty("flutterApp")) + .eventTimings()) + ); } else { - throw new InvalidArgumentException( - "Currently flutter driver implementation only supports android platform"); + String deviceName = System.getenv("IOS_DEVICE_NAME") != null + ? System.getenv("IOS_DEVICE_NAME") + : "iPhone 12"; + String platformVersion = System.getenv("IOS_PLATFORM_VERSION") != null + ? System.getenv("IOS_PLATFORM_VERSION") + : "14.5"; + driver = new FlutterIOSDriver(service.getUrl(), flutterOptions + .setXCUITestOptions(new XCUITestOptions() + .setApp(System.getProperty("flutterApp")) + .setDeviceName(deviceName) + .setPlatformVersion(platformVersion) + .setWdaLaunchTimeout(Duration.ofMinutes(4)) + .setSimulatorStartupTimeout(Duration.ofMinutes(5)) + .eventTimings() + ) + ); } } @AfterEach - public void stopSession() { + void stopSession() { if (driver != null) { driver.quit(); } } @AfterAll - public static void afterClass() { + static void afterClass() { if (service.isRunning()) { service.stop(); } } - public void openScreen(String screenTitle) { + void openScreen(String screenTitle) { ScrollParameter scrollOptions = new ScrollParameter( AppiumBy.flutterText(screenTitle), ScrollParameter.ScrollDirection.DOWN); WebElement element = driver.scrollTillVisible(scrollOptions); diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java index 80f32002d..efee1c74b 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java @@ -1,9 +1,13 @@ package io.appium.java_client.android; import io.appium.java_client.AppiumBy; +import io.appium.java_client.flutter.commands.DoubleClickParameter; +import io.appium.java_client.flutter.commands.DragAndDropParameter; +import io.appium.java_client.flutter.commands.LongPressParameter; import io.appium.java_client.flutter.commands.ScrollParameter; import io.appium.java_client.flutter.commands.WaitParameter; import org.junit.jupiter.api.Test; +import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,7 +20,7 @@ class CommandTest extends BaseFlutterTest { private static final AppiumBy.FlutterBy TOGGLE_BUTTON = AppiumBy.flutterKey("toggle_button"); @Test - public void testWaitCommand() { + void testWaitCommand() { WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); loginButton.click(); openScreen("Lazy Loading"); @@ -39,7 +43,7 @@ public void testWaitCommand() { } @Test - public void testScrollTillVisibleCommand() { + void testScrollTillVisibleCommand() { WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); loginButton.click(); openScreen("Vertical Swiping"); @@ -59,4 +63,56 @@ public void testScrollTillVisibleCommand() { assertFalse(Boolean.parseBoolean(lastElement.getAttribute("displayed"))); } + @Test + void testDoubleClickCommand() { + driver.findElement(BaseFlutterTest.LOGIN_BUTTON).click(); + openScreen("Double Tap"); + + WebElement doubleTapButton = driver + .findElement(AppiumBy.flutterKey("double_tap_button")) + .findElement(AppiumBy.flutterText("Double Tap")); + assertEquals("Double Tap", doubleTapButton.getText()); + + AppiumBy.FlutterBy okButton = AppiumBy.flutterText("Ok"); + AppiumBy.FlutterBy successPopup = AppiumBy.flutterTextContaining("Successful"); + + driver.performDoubleClick(new DoubleClickParameter().setElement(doubleTapButton)); + assertEquals(driver.findElement(successPopup).getText(), "Double Tap Successful"); + driver.findElement(okButton).click(); + + driver.performDoubleClick(new DoubleClickParameter() + .setElement(doubleTapButton) + .setOffset(new Point(10, 2)) + ); + assertEquals(driver.findElement(successPopup).getText(), "Double Tap Successful"); + driver.findElement(okButton).click(); + } + + @Test + void testLongPressCommand() { + driver.findElement(BaseFlutterTest.LOGIN_BUTTON).click(); + openScreen("Long Press"); + + AppiumBy.FlutterBy successPopup = AppiumBy.flutterText("It was a long press"); + WebElement longPressButton = driver + .findElement(AppiumBy.flutterKey("long_press_button")); + + driver.performLongPress(new LongPressParameter().setElement(longPressButton)); + assertEquals(driver.findElement(successPopup).getText(), "It was a long press"); + assertTrue(driver.findElement(successPopup).isDisplayed()); + } + + @Test + void testDragAndDropCommand() { + driver.findElement(BaseFlutterTest.LOGIN_BUTTON).click(); + openScreen("Drag & Drop"); + + driver.performDragAndDrop(new DragAndDropParameter( + driver.findElement(AppiumBy.flutterKey("drag_me")), + driver.findElement(AppiumBy.flutterKey("drop_zone")) + )); + assertTrue(driver.findElement(AppiumBy.flutterText("The box is dropped")).isDisplayed()); + assertEquals(driver.findElement(AppiumBy.flutterText("The box is dropped")).getText(), "The box is dropped"); + + } } diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java b/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java index dc2361869..e8f78e414 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/FinderTests.java @@ -10,7 +10,7 @@ class FinderTests extends BaseFlutterTest { @Test - public void testFlutterByKey() { + void testFlutterByKey() { WebElement userNameField = driver.findElement(AppiumBy.flutterKey("username_text_field")); assertEquals("admin", userNameField.getText()); userNameField.clear(); @@ -19,13 +19,13 @@ public void testFlutterByKey() { } @Test - public void testFlutterByType() { + void testFlutterByType() { WebElement loginButton = driver.findElement(AppiumBy.flutterType("ElevatedButton")); assertEquals(loginButton.findElement(AppiumBy.flutterType("Text")).getText(), "Login"); } @Test - public void testFlutterText() { + void testFlutterText() { WebElement loginButton = driver.findElement(AppiumBy.flutterText("Login")); assertEquals(loginButton.getText(), "Login"); loginButton.click(); @@ -34,7 +34,7 @@ public void testFlutterText() { } @Test - public void testFlutterTextContaining() { + void testFlutterTextContaining() { WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); loginButton.click(); assertEquals(driver.findElement(AppiumBy.flutterTextContaining("Vertical")).getText(), @@ -42,7 +42,7 @@ public void testFlutterTextContaining() { } @Test - public void testFlutterSemanticsLabel() { + void testFlutterSemanticsLabel() { WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); loginButton.click(); openScreen("Lazy Loading"); diff --git a/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java b/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java new file mode 100644 index 000000000..e50b5d134 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java @@ -0,0 +1,51 @@ +package io.appium.java_client.flutter; + +import io.appium.java_client.android.options.UiAutomator2Options; +import io.appium.java_client.flutter.options.SupportsFlutterElementWaitTimeoutOption; +import io.appium.java_client.flutter.options.SupportsFlutterServerLaunchTimeoutOption; +import io.appium.java_client.flutter.options.SupportsFlutterSystemPortOption; +import io.appium.java_client.ios.options.XCUITestOptions; +import io.appium.java_client.remote.AutomationName; +import io.appium.java_client.remote.options.BaseOptions; +import org.openqa.selenium.Capabilities; + +import java.util.Map; + +/** + * https://github.com/AppiumTestDistribution/appium-flutter-integration-driver#capabilities-for-appium-flutter-integration-driver + */ +public class FlutterDriverOptions extends BaseOptions implements + SupportsFlutterSystemPortOption, + SupportsFlutterServerLaunchTimeoutOption, + SupportsFlutterElementWaitTimeoutOption { + + public FlutterDriverOptions() { + setDefaultOptions(); + } + + public FlutterDriverOptions(Capabilities source) { + super(source); + setDefaultOptions(); + } + + public FlutterDriverOptions(Map source) { + super(source); + setDefaultOptions(); + } + + public FlutterDriverOptions setUiAutomator2Options(UiAutomator2Options uiAutomator2Options) { + return setDefaultOptions(merge(uiAutomator2Options)); + } + + public FlutterDriverOptions setXCUITestOptions(XCUITestOptions xcuiTestOptions) { + return setDefaultOptions(merge(xcuiTestOptions)); + } + + private void setDefaultOptions() { + setDefaultOptions(this); + } + + private FlutterDriverOptions setDefaultOptions(FlutterDriverOptions flutterDriverOptions) { + return flutterDriverOptions.setAutomationName(AutomationName.FLUTTER_INTEGRATION); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java b/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java new file mode 100644 index 000000000..dce74507c --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java @@ -0,0 +1,23 @@ +package io.appium.java_client.flutter; + +import org.openqa.selenium.WebDriver; + +/** + * The {@code FlutterDriver} interface represents a driver that controls interactions with + * Flutter applications, extending WebDriver and providing additional capabilities for + * interacting with Flutter-specific elements and behaviors. + * + *

    This interface serves as a common entity for drivers that support Flutter applications + * on different platforms, such as Android and iOS.

    + * + * @see WebDriver + * @see SupportsGestureOnFlutterElements + * @see SupportsScrollingOfFlutterElements + * @see SupportsWaitingForFlutterElements + */ +public interface FlutterIntegrationTestDriver extends + WebDriver, + SupportsGestureOnFlutterElements, + SupportsScrollingOfFlutterElements, + SupportsWaitingForFlutterElements { +} diff --git a/src/main/java/io/appium/java_client/flutter/SupportsGestureOnFlutterElements.java b/src/main/java/io/appium/java_client/flutter/SupportsGestureOnFlutterElements.java new file mode 100644 index 000000000..7e80e8a97 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/SupportsGestureOnFlutterElements.java @@ -0,0 +1,35 @@ +package io.appium.java_client.flutter; + +import io.appium.java_client.flutter.commands.DoubleClickParameter; +import io.appium.java_client.flutter.commands.DragAndDropParameter; +import io.appium.java_client.flutter.commands.LongPressParameter; + +public interface SupportsGestureOnFlutterElements extends CanExecuteFlutterScripts { + + /** + * Performs a double click action on an element. + * + * @param parameter The parameters for double-clicking, specifying element details. + */ + default void performDoubleClick(DoubleClickParameter parameter) { + executeFlutterCommand("doubleClick", parameter); + } + + /** + * Performs a long press action on an element. + * + * @param parameter The parameters for long pressing, specifying element details. + */ + default void performLongPress(LongPressParameter parameter) { + executeFlutterCommand("longPress", parameter); + } + + /** + * Performs a drag-and-drop action between two elements. + * + * @param parameter The parameters for drag-and-drop, specifying source and target elements. + */ + default void performDragAndDrop(DragAndDropParameter parameter) { + executeFlutterCommand("dragAndDrop", parameter); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java b/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java index f7be154ff..8bbf45cbf 100644 --- a/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java +++ b/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java @@ -2,8 +2,7 @@ import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.android.AndroidDriver; -import io.appium.java_client.flutter.SupportsScrollingOfFlutterElements; -import io.appium.java_client.flutter.SupportsWaitingForFlutterElements; +import io.appium.java_client.flutter.FlutterIntegrationTestDriver; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.openqa.selenium.Capabilities; @@ -16,9 +15,7 @@ /** * Custom AndroidDriver implementation with additional Flutter-specific capabilities. */ -public class FlutterAndroidDriver extends AndroidDriver implements - SupportsWaitingForFlutterElements, - SupportsScrollingOfFlutterElements { +public class FlutterAndroidDriver extends AndroidDriver implements FlutterIntegrationTestDriver { public FlutterAndroidDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, capabilities); diff --git a/src/main/java/io/appium/java_client/flutter/commands/DoubleClickParameter.java b/src/main/java/io/appium/java_client/flutter/commands/DoubleClickParameter.java new file mode 100644 index 000000000..859f26057 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/DoubleClickParameter.java @@ -0,0 +1,34 @@ +package io.appium.java_client.flutter.commands; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Accessors(chain = true) +@Setter +@Getter +public class DoubleClickParameter extends FlutterCommandParameter { + private WebElement element; + private Point offset; + + + @Override + public Map toJson() { + Require.precondition(element != null || offset != null, + "Must supply a valid element or offset to perform flutter gesture event"); + + Map params = new HashMap<>(); + Optional.ofNullable(element).ifPresent(element -> params.put("origin", element)); + Optional.ofNullable(offset).ifPresent(offset -> + params.put("offset", Map.of("x", offset.getX(), "y", offset.getY()))); + return Collections.unmodifiableMap(params); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/DragAndDropParameter.java b/src/main/java/io/appium/java_client/flutter/commands/DragAndDropParameter.java new file mode 100644 index 000000000..14bc04cbf --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/DragAndDropParameter.java @@ -0,0 +1,35 @@ +package io.appium.java_client.flutter.commands; + +import lombok.Getter; +import lombok.experimental.Accessors; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; + +import java.util.Map; + +@Accessors(chain = true) +@Getter +public class DragAndDropParameter extends FlutterCommandParameter { + private final WebElement source; + private final WebElement target; + + /** + * Constructs a new instance of {@code DragAndDropParameter} with the given source and target {@link WebElement}s. + * Throws an {@link IllegalArgumentException} if either {@code source} or {@code target} is {@code null}. + * + * @param source The source {@link WebElement} from which the drag operation starts. + * @param target The target {@link WebElement} where the drag operation ends. + * @throws IllegalArgumentException if {@code source} or {@code target} is {@code null}. + */ + public DragAndDropParameter(WebElement source, WebElement target) { + Require.precondition(source != null && target != null, + "Must supply valid source and target element to perform drag and drop event"); + this.source = source; + this.target = target; + } + + @Override + public Map toJson() { + return Map.of("source", source, "target", target); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/LongPressParameter.java b/src/main/java/io/appium/java_client/flutter/commands/LongPressParameter.java new file mode 100644 index 000000000..36f80772d --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/LongPressParameter.java @@ -0,0 +1,33 @@ +package io.appium.java_client.flutter.commands; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Accessors(chain = true) +@Setter +@Getter +public class LongPressParameter extends FlutterCommandParameter { + private WebElement element; + private Point offset; + + @Override + public Map toJson() { + Require.precondition(element != null || offset != null, + "Must supply a valid element or offset to perform flutter gesture event"); + + Map params = new HashMap<>(); + Optional.ofNullable(element).ifPresent(element -> params.put("origin", element)); + Optional.ofNullable(offset).ifPresent(offset -> + params.put("offset", Map.of("x", offset.getX(), "y", offset.getY()))); + return Collections.unmodifiableMap(params); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java index 695cb060d..773ece810 100644 --- a/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java +++ b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java @@ -1,11 +1,11 @@ package io.appium.java_client.flutter.commands; -import com.google.common.base.Preconditions; import io.appium.java_client.AppiumBy; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; import java.time.Duration; import java.util.Collections; @@ -45,7 +45,7 @@ public ScrollParameter(AppiumBy.FlutterBy scrollTo) { * @throws IllegalArgumentException if scrollTo is null */ public ScrollParameter(AppiumBy.FlutterBy scrollTo, ScrollDirection scrollDirection) { - Preconditions.checkArgument(scrollTo != null, "Must supply a valid locator for scrollTo"); + Require.precondition(scrollTo != null, "Must supply a valid locator for scrollTo"); this.scrollTo = scrollTo; this.scrollDirection = scrollDirection; } diff --git a/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java b/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java index 89e0a19cf..d9f057032 100644 --- a/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java +++ b/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java @@ -1,11 +1,11 @@ package io.appium.java_client.flutter.commands; -import com.google.common.base.Preconditions; import io.appium.java_client.AppiumBy; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.Require; import java.time.Duration; import java.util.Collections; @@ -23,7 +23,7 @@ public class WaitParameter extends FlutterCommandParameter { @Override public Map toJson() { - Preconditions.checkArgument(element != null || locator != null, + Require.precondition(element != null || locator != null, "Must supply a valid element or locator to wait for"); Map params = new HashMap<>(); Optional.ofNullable(element) diff --git a/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSDriver.java b/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSDriver.java new file mode 100644 index 000000000..2d8c9c991 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSDriver.java @@ -0,0 +1,69 @@ +package io.appium.java_client.flutter.ios; + +import io.appium.java_client.AppiumClientConfig; +import io.appium.java_client.flutter.FlutterIntegrationTestDriver; +import io.appium.java_client.ios.IOSDriver; +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.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URL; + +/** + * Custom IOSDriver implementation with additional Flutter-specific capabilities. + */ +public class FlutterIOSDriver extends IOSDriver implements FlutterIntegrationTestDriver { + + public FlutterIOSDriver(HttpCommandExecutor executor, Capabilities capabilities) { + super(executor, capabilities); + } + + public FlutterIOSDriver(URL remoteAddress, Capabilities capabilities) { + super(remoteAddress, capabilities); + } + + public FlutterIOSDriver(URL remoteAddress, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(remoteAddress, httpClientFactory, capabilities); + } + + public FlutterIOSDriver(AppiumDriverLocalService service, Capabilities capabilities) { + super(service, capabilities); + } + + public FlutterIOSDriver( + AppiumDriverLocalService service, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(service, httpClientFactory, capabilities); + } + + public FlutterIOSDriver(AppiumServiceBuilder builder, Capabilities capabilities) { + super(builder, capabilities); + } + + public FlutterIOSDriver( + AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(builder, httpClientFactory, capabilities); + } + + public FlutterIOSDriver(HttpClient.Factory httpClientFactory, Capabilities capabilities) { + super(httpClientFactory, capabilities); + } + + public FlutterIOSDriver(ClientConfig clientConfig, Capabilities capabilities) { + super(clientConfig, capabilities); + } + + public FlutterIOSDriver(AppiumClientConfig appiumClientConfig, Capabilities capabilities) { + super(appiumClientConfig, capabilities); + } + + public FlutterIOSDriver(URL remoteSessionAddress) { + super(remoteSessionAddress); + } + + public FlutterIOSDriver(Capabilities capabilities) { + super(capabilities); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterElementWaitTimeoutOption.java b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterElementWaitTimeoutOption.java new file mode 100644 index 000000000..794c955d4 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterElementWaitTimeoutOption.java @@ -0,0 +1,36 @@ +package io.appium.java_client.flutter.options; + +import io.appium.java_client.internal.CapabilityHelpers; +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.time.Duration; +import java.util.Optional; + +public interface SupportsFlutterElementWaitTimeoutOption> extends + Capabilities, CanSetCapability { + String FLUTTER_ELEMENT_WAIT_TIMEOUT_OPTION = "flutterElementWaitTimeout"; + + /** + * Sets the Flutter element wait timeout. + * Defaults to 5 seconds. + * + * @param timeout The duration to wait for Flutter elements during findElement method + * @return self instance for chaining. + */ + default T setFlutterElementWaitTimeout(Duration timeout) { + return amend(FLUTTER_ELEMENT_WAIT_TIMEOUT_OPTION, timeout.toMillis()); + } + + /** + * Retrieves the current Flutter element wait timeout if set. + * + * @return An {@link Optional} containing the duration of the Flutter element wait timeout, or empty if not set. + */ + default Optional getFlutterElementWaitTimeout() { + return Optional.ofNullable( + CapabilityHelpers.toDuration(getCapability(FLUTTER_ELEMENT_WAIT_TIMEOUT_OPTION)) + ); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterServerLaunchTimeoutOption.java b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterServerLaunchTimeoutOption.java new file mode 100644 index 000000000..52b51a8eb --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterServerLaunchTimeoutOption.java @@ -0,0 +1,36 @@ +package io.appium.java_client.flutter.options; + +import io.appium.java_client.internal.CapabilityHelpers; +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.time.Duration; +import java.util.Optional; + +public interface SupportsFlutterServerLaunchTimeoutOption> extends + Capabilities, CanSetCapability { + String FLUTTER_SERVER_LAUNCH_TIMEOUT_OPTION = "flutterServerLaunchTimeout"; + + /** + * Timeout to wait for FlutterServer to be pingable, + * e.g. finishes building. Defaults to 60000ms. + * + * @param timeout Timeout to wait until FlutterServer is listening. + * @return self instance for chaining. + */ + default T setFlutterServerLaunchTimeout(Duration timeout) { + return amend(FLUTTER_SERVER_LAUNCH_TIMEOUT_OPTION, timeout.toMillis()); + } + + /** + * Get the maximum timeout to wait until FlutterServer is listening. + * + * @return Timeout value. + */ + default Optional getFlutterServerLaunchTimeout() { + return Optional.ofNullable( + CapabilityHelpers.toDuration(getCapability(FLUTTER_SERVER_LAUNCH_TIMEOUT_OPTION)) + ); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterSystemPortOption.java b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterSystemPortOption.java new file mode 100644 index 000000000..3f25ccec3 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterSystemPortOption.java @@ -0,0 +1,33 @@ +package io.appium.java_client.flutter.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toInteger; + +public interface SupportsFlutterSystemPortOption> extends + Capabilities, CanSetCapability { + String FLUTTER_SYSTEM_PORT_OPTION = "flutterSystemPort"; + + /** + * Set the port where Flutter server starts. + * + * @param flutterSystemPort is the port number + * @return self instance for chaining. + */ + default T setFlutterSystemPort(int flutterSystemPort) { + return amend(FLUTTER_SYSTEM_PORT_OPTION, flutterSystemPort); + } + + /** + * Get the number of the port Flutter server starts on the system. + * + * @return Port number + */ + default Optional getFlutterSystemPort() { + return Optional.ofNullable(toInteger(getCapability(FLUTTER_SYSTEM_PORT_OPTION))); + } +} From 2caff36b16c6c39b19e44646ac7817d07dfc1b77 Mon Sep 17 00:00:00 2001 From: Sudharsan Selvaraj Date: Thu, 25 Jul 2024 12:33:26 +0530 Subject: [PATCH 255/314] feat: add flutter driver commands to support camera mocking (#2207) * feat: add flutter driver commands to support camera mocking --- .../java_client/android/BaseFlutterTest.java | 10 +++- .../java_client/android/CommandTest.java | 24 +++++++++ src/e2eFlutterTest/resources/second_qr.png | Bin 0 -> 132652 bytes src/e2eFlutterTest/resources/success_qr.png | Bin 0 -> 32757 bytes .../flutter/CanExecuteFlutterScripts.java | 15 +++++- .../flutter/FlutterDriverOptions.java | 4 +- .../flutter/FlutterIntegrationTestDriver.java | 3 +- .../flutter/SupportsFlutterCameraMocking.java | 47 ++++++++++++++++++ .../SupportsFlutterEnableMockCamera.java | 33 ++++++++++++ 9 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 src/e2eFlutterTest/resources/second_qr.png create mode 100644 src/e2eFlutterTest/resources/success_qr.png create mode 100644 src/main/java/io/appium/java_client/flutter/SupportsFlutterCameraMocking.java create mode 100644 src/main/java/io/appium/java_client/flutter/options/SupportsFlutterEnableMockCamera.java diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java index b2dc6f1eb..a0dd5ccaa 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java @@ -10,6 +10,7 @@ import io.appium.java_client.ios.options.XCUITestOptions; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; +import io.appium.java_client.service.local.flags.GeneralServerFlag; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -43,6 +44,10 @@ public static void beforeClass() { service = new AppiumServiceBuilder() .withIPAddress("127.0.0.1") .usingPort(PORT) + // Flutter driver mocking command requires adb_shell permission to set certain permissions + // to the AUT. This can be removed once the server logic is updated to use a different approach + // for setting the permission + .withArgument(GeneralServerFlag.ALLOW_INSECURE, "adb_shell") .build(); service.start(); } @@ -52,11 +57,14 @@ void startSession() throws MalformedURLException { FlutterDriverOptions flutterOptions = new FlutterDriverOptions() .setFlutterServerLaunchTimeout(Duration.ofMinutes(2)) .setFlutterSystemPort(9999) - .setFlutterElementWaitTimeout(Duration.ofSeconds(10)); + .setFlutterElementWaitTimeout(Duration.ofSeconds(10)) + .setFlutterEnableMockCamera(true); + if (IS_ANDROID) { driver = new FlutterAndroidDriver(service.getUrl(), flutterOptions .setUiAutomator2Options(new UiAutomator2Options() .setApp(System.getProperty("flutterApp")) + .setAutoGrantPermissions(true) .eventTimings()) ); } else { diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java index efee1c74b..d8f587e52 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java @@ -1,6 +1,7 @@ package io.appium.java_client.android; import io.appium.java_client.AppiumBy; +import io.appium.java_client.TestUtils; import io.appium.java_client.flutter.commands.DoubleClickParameter; import io.appium.java_client.flutter.commands.DragAndDropParameter; import io.appium.java_client.flutter.commands.LongPressParameter; @@ -10,6 +11,8 @@ import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -115,4 +118,25 @@ void testDragAndDropCommand() { assertEquals(driver.findElement(AppiumBy.flutterText("The box is dropped")).getText(), "The box is dropped"); } + + @Test + void testCameraMocking() throws IOException { + driver.findElement(BaseFlutterTest.LOGIN_BUTTON).click(); + openScreen("Image Picker"); + + final String successQr = driver.injectMockImage( + TestUtils.resourcePathToAbsolutePath("success_qr.png").toFile()); + driver.injectMockImage( + TestUtils.resourcePathToAbsolutePath("second_qr.png").toFile()); + + driver.findElement(AppiumBy.flutterKey("capture_image")).click(); + driver.findElement(AppiumBy.flutterText("PICK")).click(); + assertTrue(driver.findElement(AppiumBy.flutterText("SecondInjectedImage")).isDisplayed()); + + driver.activateInjectedImage(successQr); + + driver.findElement(AppiumBy.flutterKey("capture_image")).click(); + driver.findElement(AppiumBy.flutterText("PICK")).click(); + assertTrue(driver.findElement(AppiumBy.flutterText("Success!")).isDisplayed()); + } } diff --git a/src/e2eFlutterTest/resources/second_qr.png b/src/e2eFlutterTest/resources/second_qr.png new file mode 100644 index 0000000000000000000000000000000000000000..355548c30e2066dccf7f2cbf5e990eff59801677 GIT binary patch literal 132652 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 e|N7x2c$sT16K9I5mue}_+y**5hr(AVh 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 S^YP!2q$&#P^0n{GLjDhG!;-iF literal 0 HcmV?d00001 diff --git a/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java b/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java index f8eaf3af4..e844388be 100644 --- a/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java +++ b/src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java @@ -3,6 +3,8 @@ import io.appium.java_client.flutter.commands.FlutterCommandParameter; import org.openqa.selenium.JavascriptExecutor; +import java.util.Map; + public interface CanExecuteFlutterScripts extends JavascriptExecutor { /** @@ -13,8 +15,19 @@ public interface CanExecuteFlutterScripts extends JavascriptExecutor { * @return The result of executing the script. */ default Object executeFlutterCommand(String scriptName, FlutterCommandParameter parameter) { + return executeFlutterCommand(scriptName, parameter.toJson()); + } + + /** + * Executes a Flutter-specific script using JavascriptExecutor. + * + * @param scriptName The name of the Flutter script to execute. + * @param args The args for the Flutter command in Map format. + * @return The result of executing the script. + */ + default Object executeFlutterCommand(String scriptName, Map args) { String commandName = String.format("flutter: %s", scriptName); - return executeScript(commandName, parameter.toJson()); + return executeScript(commandName, args); } } diff --git a/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java b/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java index e50b5d134..6a00c0510 100644 --- a/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java +++ b/src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java @@ -2,6 +2,7 @@ import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.flutter.options.SupportsFlutterElementWaitTimeoutOption; +import io.appium.java_client.flutter.options.SupportsFlutterEnableMockCamera; import io.appium.java_client.flutter.options.SupportsFlutterServerLaunchTimeoutOption; import io.appium.java_client.flutter.options.SupportsFlutterSystemPortOption; import io.appium.java_client.ios.options.XCUITestOptions; @@ -17,7 +18,8 @@ public class FlutterDriverOptions extends BaseOptions implements SupportsFlutterSystemPortOption, SupportsFlutterServerLaunchTimeoutOption, - SupportsFlutterElementWaitTimeoutOption { + SupportsFlutterElementWaitTimeoutOption, + SupportsFlutterEnableMockCamera { public FlutterDriverOptions() { setDefaultOptions(); diff --git a/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java b/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java index dce74507c..4eb74e82a 100644 --- a/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java +++ b/src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java @@ -19,5 +19,6 @@ public interface FlutterIntegrationTestDriver extends WebDriver, SupportsGestureOnFlutterElements, SupportsScrollingOfFlutterElements, - SupportsWaitingForFlutterElements { + SupportsWaitingForFlutterElements, + SupportsFlutterCameraMocking { } diff --git a/src/main/java/io/appium/java_client/flutter/SupportsFlutterCameraMocking.java b/src/main/java/io/appium/java_client/flutter/SupportsFlutterCameraMocking.java new file mode 100644 index 000000000..6ffad1089 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/SupportsFlutterCameraMocking.java @@ -0,0 +1,47 @@ +package io.appium.java_client.flutter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Map; + +/** + * This interface extends {@link CanExecuteFlutterScripts} and provides methods + * to support mocking of camera inputs in Flutter applications. + */ +public interface SupportsFlutterCameraMocking extends CanExecuteFlutterScripts { + + /** + * Injects a mock image into the Flutter application using the provided file. + * + * @param image the image file to be mocked (must be in PNG format) + * @return an {@code String} representing a unique id of the injected image + * @throws IOException if an I/O error occurs while reading the image file + */ + default String injectMockImage(File image) throws IOException { + String base64EncodedImage = Base64.getEncoder().encodeToString(Files.readAllBytes(image.toPath())); + return injectMockImage(base64EncodedImage); + } + + /** + * Injects a mock image into the Flutter application using the provided Base64-encoded image string. + * + * @param base64Image the Base64-encoded string representation of the image (must be in PNG format) + * @return an {@code String} representing the result of the injection operation + */ + default String injectMockImage(String base64Image) { + return (String) executeFlutterCommand("injectImage", Map.of( + "base64Image", base64Image + )); + } + + /** + * Activates the injected image identified by the specified image ID in the Flutter application. + * + * @param imageId the ID of the injected image to activate + */ + default void activateInjectedImage(String imageId) { + executeFlutterCommand("activateInjectedImage", Map.of("imageId", imageId)); + } +} diff --git a/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterEnableMockCamera.java b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterEnableMockCamera.java new file mode 100644 index 000000000..baffaf96d --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/options/SupportsFlutterEnableMockCamera.java @@ -0,0 +1,33 @@ +package io.appium.java_client.flutter.options; + +import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.CanSetCapability; +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean; + +public interface SupportsFlutterEnableMockCamera> extends + Capabilities, CanSetCapability { + String FLUTTER_ENABLE_MOCK_CAMERA_OPTION = "flutterEnableMockCamera"; + + /** + * Sets the 'flutterEnableMockCamera' capability to the specified value. + * + * @param value the value to set for the 'flutterEnableMockCamera' capability + * @return an instance of type {@code T} with the updated capability set + */ + default T setFlutterEnableMockCamera(boolean value) { + return amend(FLUTTER_ENABLE_MOCK_CAMERA_OPTION, value); + } + + /** + * Retrieves the current value of the 'flutterEnableMockCamera' capability, if available. + * + * @return an {@code Optional} containing the current value of the capability, + */ + default Optional doesFlutterEnableMockCamera() { + return Optional.ofNullable(toSafeBoolean(getCapability(FLUTTER_ENABLE_MOCK_CAMERA_OPTION))); + } +} From 8c750162ef4a8d3e3e4421b20513f870007266d7 Mon Sep 17 00:00:00 2001 From: Srinivasan Sekar Date: Fri, 26 Jul 2024 14:04:31 +0800 Subject: [PATCH 256/314] release: v9.3.0 (#2208) --- CHANGELOG.md | 22 ++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44838a04f..09c42fd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +_9.3.0_ +- **[ENHANCEMENTS]** + - Add support for FlutterIOSDriver. [#2206](https://github.com/appium/java-client/pull/2206) + - add support for FlutterAndroidDriver. [#2203](https://github.com/appium/java-client/pull/2203) + - Add locator types supported by flutter integration driver. [#2201](https://github.com/appium/java-client/pull/2201) + - add flutter driver commands to support camera mocking. [#2207](https://github.com/appium/java-client/pull/2207) + - Add ability to use secure WebSocket to listen Logcat messages. [#2182](https://github.com/appium/java-client/pull/2182) + - Add mobile: replacements to clipboard API wrappers. [#2188](https://github.com/appium/java-client/pull/2188) +- **[DEPRECATION]** + - Deprecate obsolete TouchAction helpers. [#2199](https://github.com/appium/java-client/pull/2199) +- **[REFACTOR]** + - Bump iOS version in CI. [#2167](https://github.com/appium/java-client/pull/2167) +- **[DOCUMENTATION]** + - README updates. [#2193](https://github.com/appium/java-client/pull/2193) +- **[DEPENDENCY UPDATES]** + - `org.junit.jupiter:junit-jupiter` was updated to 5.10.3. + - `org.projectlombok:lombok` was updated to 1.18.34. + - `io.github.bonigarcia:webdrivermanager` was updated to 5.9.1. + - `org.owasp.dependencycheck` was updated to 10.0.3. + - `org.apache.commons:commons-lang3` was updated to 3.15.0. + _9.2.3_ - **[BUG FIX]** - Properly represent `FeaturesMatchingResult` model if `multiple` option is enabled [#2170](https://github.com/appium/java-client/pull/2170) diff --git a/gradle.properties b/gradle.properties index 44154a53b..10017f9d5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.19.0 # Please increment the value in a release -appiumClient.version=9.2.3 +appiumClient.version=9.3.0 From 567deee4ae069965e7da4f4e9bd1a05a09dfcfb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:00:22 +0300 Subject: [PATCH 257/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2209) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.9.1 to 5.9.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.9.1...webdrivermanager-5.9.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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b65d41661..ec4e247a6 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.9.1') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.2') { exclude group: 'org.seleniumhq.selenium' } } @@ -257,7 +257,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.9.1') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.2') { exclude group: 'org.seleniumhq.selenium' } } From a64ee0dc68d1e224aafae185c206cb799ca24a0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:55:55 +0300 Subject: [PATCH 258/314] build(deps): Bump org.hamcrest:hamcrest from 2.2 to 3.0 (#2212) Bumps [org.hamcrest:hamcrest](https://github.com/hamcrest/JavaHamcrest) from 2.2 to 3.0. - [Release notes](https://github.com/hamcrest/JavaHamcrest/releases) - [Changelog](https://github.com/hamcrest/JavaHamcrest/blob/master/CHANGES.md) - [Commits](https://github.com/hamcrest/JavaHamcrest/compare/v2.2...v3.0) --- updated-dependencies: - dependency-name: org.hamcrest:hamcrest 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 ec4e247a6..11b7edff3 100644 --- a/build.gradle +++ b/build.gradle @@ -197,7 +197,7 @@ testing { dependencies { implementation 'org.junit.jupiter:junit-jupiter:5.10.3' runtimeOnly 'org.junit.platform:junit-platform-launcher' - implementation 'org.hamcrest:hamcrest:2.2' + implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } targets.configureEach { From 76ddf4388414aabba94496f2c84901e189c1d135 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:16:26 +0300 Subject: [PATCH 259/314] build(deps): Bump gradle/actions from 3 to 4 (#2213) Bumps [gradle/actions](https://github.com/gradle/actions) from 3 to 4. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/v3...v4) --- updated-dependencies: - dependency-name: gradle/actions 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> --- .github/workflows/gradle-wrapper-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 72a15d717..c2f33a25a 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -24,4 +24,4 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 + - uses: gradle/actions/wrapper-validation@v4 From f4496a990dfdc010ff7c8b91a335893277007457 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 5 Aug 2024 17:22:46 +0300 Subject: [PATCH 260/314] ci: Use the `setup-gradle` action (#2214) - https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#why-use-the-setup-gradle-action - https://github.com/gradle/actions/releases/tag/v4.0.0-rc.1: wrapper validation is now enabled in the setup-gradle action by default, removing the need to use a separate workflow file with the `gradle/actions/wrapper-validation` action - https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#incompatibility-with-other-caching-mechanisms --- .../workflows/gradle-wrapper-validation.yml | 27 ------------------- .github/workflows/gradle.yml | 4 ++- .github/workflows/publish.yml | 7 +++-- 3 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 .github/workflows/gradle-wrapper-validation.yml diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index c2f33a25a..000000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Validate Gradle Wrapper" - -on: - push: - branches: - - master - paths: - - 'gradlew' - - 'gradlew.bat' - - 'gradle/wrapper/**' - - '.github/workflows/gradle-wrapper-validation.yml' - pull_request: - branches: - - master - paths: - - 'gradlew' - - 'gradlew.bat' - - 'gradle/wrapper/**' - - '.github/workflows/gradle-wrapper-validation.yml' - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 93527e84a..e960bf822 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -71,7 +71,9 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 - name: Build with Gradle run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 96fcfce38..6b2a253c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,11 +12,14 @@ jobs: with: java-version: '11' distribution: 'zulu' - cache: 'gradle' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + - name: Publish package env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} PGP_SECRET: ${{ secrets.SIGNING_KEY }} PGP_PASSPHRASE: ${{ secrets.SIGNING_PASSWORD }} - run: ./gradlew publish \ No newline at end of file + run: ./gradlew publish From 7e23de25ef8a4145e3f0cbbc82eacca28b6c3496 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 5 Aug 2024 18:46:44 +0300 Subject: [PATCH 261/314] build: bump Gradle from `8.7` to `8.9` (#2215) --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +++++-- gradlew.bat | 2 ++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 11b7edff3..7955434a5 100644 --- a/build.gradle +++ b/build.gradle @@ -179,7 +179,7 @@ signing { } wrapper { - gradleVersion = '8.7' + gradleVersion = '8.9' distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 8703 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 Ojj7^59?Sdh$^Qpp*~F>< delta 8662 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e7646dead..dedd5d1e6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e46..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 1d4c1d0dbdcf11e92587987623f08fe1a520e4b9 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 5 Aug 2024 19:22:41 +0300 Subject: [PATCH 262/314] docs: Add recent clients versions to the compatibility matrix (#2216) --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index edbb5cc83..91e61c688 100644 --- a/README.md +++ b/README.md @@ -93,16 +93,16 @@ dependencies { ``` ### Compatibility Matrix - Appium Java Client | Selenium client --------------------------------------------------------------------------------------------|----------------- - `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2`, `9.2.3` | `4.19.0`, `4.19.1`, `4.20.0`, `4.21.0` - `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` - `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` - N/A | `4.14.0` - `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` - `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` - `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` - `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` + Appium Java Client | Selenium client +----------------------------------------------------------------------------------------------------|----------------- + `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2`, `9.2.3`, `9.3.0` | `4.19.0`, `4.19.1`, `4.20.0`, `4.21.0`, `4.22.0`, `4.23.0` + `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` + `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` + N/A | `4.14.0` + `8.5.0`, `8.5.1`, `8.6.0` | `4.9.1`, `4.10.0`, `4.11.0`, `4.12.0`, `4.12.1` (known issue: appium/java-client#2004), `4.13.0` + `8.4.0` | `4.8.2`, `4.8.3`, `4.9.0` + `8.3.0` | `4.7.0`, `4.7.1`, `4.7.2`, `4.8.0`, `4.8.1` + `8.2.1` | `4.5.0`, `4.5.1`, `4.5.2`, `4.5.3`, `4.6.0` #### Why is it so complicated? From 9a49ef8cdef2625c071a927b8d5aaac8287010d2 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 5 Aug 2024 19:23:21 +0300 Subject: [PATCH 263/314] build(deps): Bump JaCoCo from `0.8.11` to `0.8.12` (#2217) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7955434a5..bd7225b23 100644 --- a/build.gradle +++ b/build.gradle @@ -74,7 +74,7 @@ dependencyCheck { } jacoco { - toolVersion = '0.8.11' + toolVersion = '0.8.12' } tasks.withType(JacocoReport).configureEach { From 546d6cc2588859a83eb10836281ceadcc8701e63 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 5 Aug 2024 20:03:45 +0300 Subject: [PATCH 264/314] build(deps): Bump Checkstyle from `10.14.2` to `10.17.0` (#2218) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bd7225b23..9c3f23dca 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ jacocoTestReport.dependsOn test apply plugin: 'checkstyle' checkstyle { - toolVersion = '10.14.2' + toolVersion = '10.17.0' configFile = configDirectory.file('appium-style.xml').get().getAsFile() showViolations = true ignoreFailures = false From 62303cec207a4a7d75e026d329032704d47e3750 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:48:01 +0300 Subject: [PATCH 265/314] build(deps): Bump org.apache.commons:commons-lang3 from 3.15.0 to 3.16.0 (#2220) Bumps org.apache.commons:commons-lang3 from 3.15.0 to 3.16.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 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 9c3f23dca..6197189ef 100644 --- a/build.gradle +++ b/build.gradle @@ -233,7 +233,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('org.apache.commons:commons-lang3:3.15.0') + implementation('org.apache.commons:commons-lang3:3.16.0') } targets.configureEach { From bbb38ec04e3ddbe86b1bc4ec81aba1a6da4bef59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:16:41 +0300 Subject: [PATCH 266/314] build(deps): Bump slf4jVersion from 2.0.13 to 2.0.16 (#2221) Bumps `slf4jVersion` from 2.0.13 to 2.0.16. Updates `org.slf4j:slf4j-api` from 2.0.13 to 2.0.16 Updates `org.slf4j:slf4j-simple` from 2.0.13 to 2.0.16 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 6197189ef..320c44bc9 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.13' + slf4jVersion = '2.0.16' } dependencies { From b96c86803dedf639bc342b4b38d35e393de2ea2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 19:05:09 +0300 Subject: [PATCH 267/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.10.3 to 5.11.0 (#2223) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.3 to 5.11.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.3...r5.11.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 320c44bc9..a4a58564b 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.10.3' + implementation 'org.junit.jupiter:junit-jupiter:5.11.0' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From f8ef9c18da59eaec1e948edbbe36971777cdaa78 Mon Sep 17 00:00:00 2001 From: MummanaSubramanya Date: Thu, 29 Aug 2024 16:46:17 +0200 Subject: [PATCH 268/314] fix: Scroll issue in flutter integration driver (#2227) --- .../java_client/android/CommandTest.java | 31 ++++++++++++++++--- .../flutter/commands/ScrollParameter.java | 9 +++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java index d8f587e52..0f056e51a 100644 --- a/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java +++ b/src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java @@ -12,7 +12,9 @@ import org.openqa.selenium.WebElement; import java.io.IOException; +import java.time.Duration; +import static java.lang.Boolean.parseBoolean; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -52,18 +54,37 @@ void testScrollTillVisibleCommand() { openScreen("Vertical Swiping"); WebElement firstElement = driver.scrollTillVisible(new ScrollParameter(AppiumBy.flutterText("Java"))); - assertTrue(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); + assertTrue(parseBoolean(firstElement.getAttribute("displayed"))); WebElement lastElement = driver.scrollTillVisible(new ScrollParameter(AppiumBy.flutterText("Protractor"))); - assertTrue(Boolean.parseBoolean(lastElement.getAttribute("displayed"))); - assertFalse(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); + assertTrue(parseBoolean(lastElement.getAttribute("displayed"))); + assertFalse(parseBoolean(firstElement.getAttribute("displayed"))); firstElement = driver.scrollTillVisible( new ScrollParameter(AppiumBy.flutterText("Java"), ScrollParameter.ScrollDirection.UP) ); - assertTrue(Boolean.parseBoolean(firstElement.getAttribute("displayed"))); - assertFalse(Boolean.parseBoolean(lastElement.getAttribute("displayed"))); + assertTrue(parseBoolean(firstElement.getAttribute("displayed"))); + assertFalse(parseBoolean(lastElement.getAttribute("displayed"))); + } + + @Test + void testScrollTillVisibleWithScrollParametersCommand() { + WebElement loginButton = driver.findElement(BaseFlutterTest.LOGIN_BUTTON); + loginButton.click(); + openScreen("Vertical Swiping"); + + ScrollParameter scrollParameter = new ScrollParameter(AppiumBy.flutterText("Playwright")); + scrollParameter + .setScrollView(AppiumBy.flutterType("Scrollable")) + .setMaxScrolls(30) + .setDelta(30) + // Drag duration currently works when the value is greater than 33 secs + .setDragDuration(Duration.ofMillis(35000)) + .setSettleBetweenScrollsTimeout(5000); + + WebElement element = driver.scrollTillVisible(scrollParameter); + assertTrue(parseBoolean(element.getAttribute("displayed"))); } @Test diff --git a/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java index 773ece810..d2a2674c7 100644 --- a/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java +++ b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java @@ -4,7 +4,6 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import org.openqa.selenium.WebElement; import org.openqa.selenium.internal.Require; import java.time.Duration; @@ -18,7 +17,7 @@ @Setter public class ScrollParameter extends FlutterCommandParameter { private AppiumBy.FlutterBy scrollTo; - private WebElement scrollView; + private AppiumBy.FlutterBy scrollView; private ScrollDirection scrollDirection; private Integer delta; private Integer maxScrolls; @@ -56,13 +55,13 @@ public Map toJson() { params.put("finder", parseFlutterLocator(scrollTo)); Optional.ofNullable(scrollView) - .ifPresent(scrollView -> params.put("scrollView", scrollView)); + .ifPresent(scrollView -> params.put("scrollView", parseFlutterLocator(scrollView))); Optional.ofNullable(delta) .ifPresent(delta -> params.put("delta", delta)); Optional.ofNullable(maxScrolls) - .ifPresent(maxScrolls -> params.put("delta", maxScrolls)); + .ifPresent(maxScrolls -> params.put("maxScrolls", maxScrolls)); Optional.ofNullable(settleBetweenScrollsTimeout) - .ifPresent(timeout -> params.put("delta", settleBetweenScrollsTimeout)); + .ifPresent(timeout -> params.put("settleBetweenScrollsTimeout", settleBetweenScrollsTimeout)); Optional.ofNullable(scrollDirection) .ifPresent(direction -> params.put("scrollDirection", direction.getDirection())); Optional.ofNullable(dragDuration) From 34a8d5f154f7337db400e2566e643c1ea4052ae3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 22:01:49 +0200 Subject: [PATCH 269/314] build(deps): Bump org.owasp.dependencycheck from 10.0.3 to 10.0.4 (#2229) Bumps org.owasp.dependencycheck from 10.0.3 to 10.0.4. --- 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 a4a58564b..a95746877 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '10.0.3' + id 'org.owasp.dependencycheck' version '10.0.4' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 9b3194e29857b6204f51d7f198d3583d2d904e50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:48:55 +0300 Subject: [PATCH 270/314] build(deps): Bump org.apache.commons:commons-lang3 from 3.16.0 to 3.17.0 (#2228) Bumps org.apache.commons:commons-lang3 from 3.16.0 to 3.17.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 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 a95746877..e0fc588ce 100644 --- a/build.gradle +++ b/build.gradle @@ -233,7 +233,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('org.apache.commons:commons-lang3:3.16.0') + implementation('org.apache.commons:commons-lang3:3.17.0') } targets.configureEach { From 81c9a589cda621bb74164b0b40260899f63aff05 Mon Sep 17 00:00:00 2001 From: Nick Chursin Date: Wed, 25 Sep 2024 14:58:36 +0300 Subject: [PATCH 271/314] ci: Build WDA for specific device (#2236) --- .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 e960bf822..89a292f87 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -139,7 +139,7 @@ jobs: run: appium driver install xcuitest - name: Prebuild XCUITest driver if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' - run: appium driver run xcuitest build-wda + run: appium driver run xcuitest build-wda --sdk=${{ env.IOS_PLATFORM_VERSION }} --name='${{ env.IOS_DEVICE_NAME }}' - name: Run iOS E2E tests if: matrix.e2e-tests == 'ios' run: ./gradlew e2eIosTest -PisCI -Pselenium.version=$latest_snapshot From 87b222552ca481b3a8886b78592707d38e48227d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:24:18 +0300 Subject: [PATCH 272/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.11.0 to 5.11.1 (#2237) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.0 to 5.11.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.0...r5.11.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 e0fc588ce..8220a2889 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.11.0' + implementation 'org.junit.jupiter:junit-jupiter:5.11.1' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From 2721bf48ac64fb7f6e577ec6ae5f63604f6a7f84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:13:34 +0300 Subject: [PATCH 273/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.11.1 to 5.11.2 (#2240) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.1 to 5.11.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.1...r5.11.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 8220a2889..f5ecfa68c 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.11.1' + implementation 'org.junit.jupiter:junit-jupiter:5.11.2' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From 45bb1d7b2b423fc3b86e79ce31eb09d5fb90da4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:11:09 +0200 Subject: [PATCH 274/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.11.2 to 5.11.3 (#2242) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.2 to 5.11.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.2...r5.11.3) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 f5ecfa68c..f9e92bece 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.11.2' + implementation 'org.junit.jupiter:junit-jupiter:5.11.3' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From 778dc6442c55d5a4acbb81a381fc4dba49fd8828 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:22:08 +0200 Subject: [PATCH 275/314] build(deps): Bump org.owasp.dependencycheck from 10.0.4 to 11.0.0 (#2241) Bumps org.owasp.dependencycheck from 10.0.4 to 11.0.0. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck 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 f9e92bece..1bc70ed78 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '10.0.4' + id 'org.owasp.dependencycheck' version '11.0.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 9276c948e597eb3f6073b29fd50dc93680b88342 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:05:54 +0200 Subject: [PATCH 276/314] build(deps): Bump org.owasp.dependencycheck from 11.0.0 to 11.1.0 (#2244) Bumps org.owasp.dependencycheck from 11.0.0 to 11.1.0. --- 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 1bc70ed78..60eb40ea4 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '11.0.0' + id 'org.owasp.dependencycheck' version '11.1.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From c9d72208710fcfda2847d27e8c33a6c2718c5667 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:21:30 +0200 Subject: [PATCH 277/314] build(deps): Bump futureware-tech/simulator-action from 3 to 4 (#2243) Bumps [futureware-tech/simulator-action](https://github.com/futureware-tech/simulator-action) from 3 to 4. - [Release notes](https://github.com/futureware-tech/simulator-action/releases) - [Changelog](https://github.com/futureware-tech/simulator-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/futureware-tech/simulator-action/compare/v3...v4) --- updated-dependencies: - dependency-name: futureware-tech/simulator-action 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> --- .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 89a292f87..252710957 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -130,7 +130,7 @@ jobs: xcode-version: "${{ env.XCODE_VERSION }}" - name: Prepare iOS simulator if: matrix.e2e-tests == 'ios' || matrix.e2e-tests == 'flutter-ios' - uses: futureware-tech/simulator-action@v3 + uses: futureware-tech/simulator-action@v4 with: model: "${{ env.IOS_DEVICE_NAME }}" os_version: "${{ env.IOS_PLATFORM_VERSION }}" From 1c581182d859ad37aeffe6431bbdaf2025e335b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 23:01:32 +0200 Subject: [PATCH 278/314] build(deps): Bump org.projectlombok:lombok from 1.18.34 to 1.18.36 (#2245) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.34 to 1.18.36. - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.34...v1.18.36) --- 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 60eb40ea4..4e0b67886 100644 --- a/build.gradle +++ b/build.gradle @@ -38,8 +38,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.34' - annotationProcessor 'org.projectlombok:lombok:1.18.34' + compileOnly 'org.projectlombok:lombok:1.18.36' + annotationProcessor 'org.projectlombok:lombok:1.18.36' if (project.hasProperty("isCI")) { api "org.seleniumhq.selenium:selenium-api:${seleniumVersion}" From 42480feca9845bbbb569b13d995b80980bf4ca33 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 27 Nov 2024 08:49:46 +0100 Subject: [PATCH 279/314] chore: Bump the minimum java client version to 4.26.0 (#2246) --- gradle.properties | 2 +- .../io/appium/java_client/AppiumDriver.java | 23 +++++----------- .../appium/java_client/AppiumFluentWait.java | 26 +++++-------------- .../remote/AppiumCommandExecutor.java | 11 ++++---- .../service/local/AppiumServiceBuilder.java | 6 +---- 5 files changed, 22 insertions(+), 46 deletions(-) diff --git a/gradle.properties b/gradle.properties index 10017f9d5..1bd7b6aed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.daemon=true -selenium.version=4.19.0 +selenium.version=4.26.0 # Please increment the value in a release appiumClient.version=9.3.0 diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index c8d660e9f..a8757843c 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -17,7 +17,6 @@ package io.appium.java_client; import io.appium.java_client.internal.CapabilityHelpers; -import io.appium.java_client.internal.ReflectionHelpers; import io.appium.java_client.internal.SessionHelpers; import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.AppiumW3CHttpCommandCodec; @@ -27,7 +26,6 @@ import lombok.Getter; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; -import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.OutputType; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.UnsupportedCommandException; @@ -152,12 +150,10 @@ public AppiumDriver(Capabilities capabilities) { */ public AppiumDriver(URL remoteSessionAddress, String platformName, String automationName) { super(); - ReflectionHelpers.setPrivateFieldValue( - RemoteWebDriver.class, this, "capabilities", new ImmutableCapabilities( - Map.of( - PLATFORM_NAME, platformName, - APPIUM_PREFIX + AUTOMATION_NAME_OPTION, automationName - ) + this.capabilities = new ImmutableCapabilities( + Map.of( + PLATFORM_NAME, platformName, + APPIUM_PREFIX + AUTOMATION_NAME_OPTION, automationName ) ); SessionHelpers.SessionAddress sessionAddress = SessionHelpers.parseSessionAddress(remoteSessionAddress); @@ -168,7 +164,7 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa executor.setResponseCodec(new W3CHttpResponseCodec()); setCommandExecutor(executor); this.executeMethod = new AppiumExecutionMethod(this); - locationContext = new RemoteLocationContext(executeMethod); + this.locationContext = new RemoteLocationContext(executeMethod); super.setErrorHandler(ERROR_HANDLER); this.remoteAddress = executor.getAddressOfRemoteServer(); @@ -293,10 +289,7 @@ protected void startSession(Capabilities capabilities) { && isNullOrEmpty((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { rawCapabilities.remove(CapabilityType.BROWSER_NAME); } - MutableCapabilities returnedCapabilities = new BaseOptions<>(rawCapabilities); - ReflectionHelpers.setPrivateFieldValue( - RemoteWebDriver.class, this, "capabilities", returnedCapabilities - ); + this.capabilities = new BaseOptions<>(rawCapabilities); setSessionId(response.getSessionId()); } @@ -345,8 +338,6 @@ public AppiumDriver markExtensionAbsence(String extName) { } protected HttpClient getHttpClient() { - return ReflectionHelpers.getPrivateFieldValue( - HttpCommandExecutor.class, getCommandExecutor(), "client", HttpClient.class - ); + return ((HttpCommandExecutor) getCommandExecutor()).client; } } diff --git a/src/main/java/io/appium/java_client/AppiumFluentWait.java b/src/main/java/io/appium/java_client/AppiumFluentWait.java index 6361a5652..a284e1ebb 100644 --- a/src/main/java/io/appium/java_client/AppiumFluentWait.java +++ b/src/main/java/io/appium/java_client/AppiumFluentWait.java @@ -17,7 +17,6 @@ package io.appium.java_client; import com.google.common.base.Throwables; -import io.appium.java_client.internal.ReflectionHelpers; import lombok.AccessLevel; import lombok.Getter; import org.openqa.selenium.TimeoutException; @@ -114,43 +113,32 @@ public AppiumFluentWait withPollDelay(Duration pollDelay) { return this; } - private B getPrivateFieldValue(String fieldName, Class fieldType) { - return ReflectionHelpers.getPrivateFieldValue(FluentWait.class, this, fieldName, fieldType); - } - - private Object getPrivateFieldValue(String fieldName) { - return getPrivateFieldValue(fieldName, Object.class); - } - protected Clock getClock() { - return getPrivateFieldValue("clock", Clock.class); + return clock; } protected Duration getTimeout() { - return getPrivateFieldValue("timeout", Duration.class); + return timeout; } protected Duration getInterval() { - return getPrivateFieldValue("interval", Duration.class); + return interval; } protected Sleeper getSleeper() { - return getPrivateFieldValue("sleeper", Sleeper.class); + return sleeper; } - @SuppressWarnings("unchecked") protected List> getIgnoredExceptions() { - return getPrivateFieldValue("ignoredExceptions", List.class); + return ignoredExceptions; } - @SuppressWarnings("unchecked") protected Supplier getMessageSupplier() { - return getPrivateFieldValue("messageSupplier", Supplier.class); + return messageSupplier; } - @SuppressWarnings("unchecked") protected T getInput() { - return (T) getPrivateFieldValue("input"); + return (T) input; } /** 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 118f0ff81..a689e7d33 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -119,12 +119,14 @@ public AppiumCommandExecutor(Map additionalCommands, this(additionalCommands, service, HttpClient.Factory.createDefault(), appiumClientConfig); } + @Deprecated @SuppressWarnings("SameParameterValue") protected B getPrivateFieldValue( Class cls, String fieldName, Class fieldType) { return ReflectionHelpers.getPrivateFieldValue(cls, this, fieldName, fieldType); } + @Deprecated @SuppressWarnings("SameParameterValue") protected void setPrivateFieldValue( Class cls, String fieldName, Object newValue) { @@ -137,20 +139,19 @@ protected Map getAdditionalCommands() { } protected CommandCodec getCommandCodec() { - //noinspection unchecked - return getPrivateFieldValue(HttpCommandExecutor.class, "commandCodec", CommandCodec.class); + return this.commandCodec; } public void setCommandCodec(CommandCodec newCodec) { - setPrivateFieldValue(HttpCommandExecutor.class, "commandCodec", newCodec); + this.commandCodec = newCodec; } public void setResponseCodec(ResponseCodec codec) { - setPrivateFieldValue(HttpCommandExecutor.class, "responseCodec", codec); + this.responseCodec = codec; } protected HttpClient getClient() { - return getPrivateFieldValue(HttpCommandExecutor.class, "client", HttpClient.class); + return this.client; } /** 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 ad3729e77..5dadb6403 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 @@ -20,7 +20,6 @@ import com.google.gson.GsonBuilder; import io.appium.java_client.android.options.context.SupportsChromedriverExecutableOption; import io.appium.java_client.android.options.signing.SupportsKeystoreOptions; -import io.appium.java_client.internal.ReflectionHelpers; import io.appium.java_client.remote.MobileBrowserType; import io.appium.java_client.remote.options.SupportsAppOption; import io.appium.java_client.service.local.flags.GeneralServerFlag; @@ -400,10 +399,7 @@ protected List createArgs() { @Override protected void loadSystemProperties() { - File driverExecutable = ReflectionHelpers.getPrivateFieldValue( - DriverService.Builder.class, this, "exe", File.class - ); - if (driverExecutable == null) { + if (this.exe == null) { usingDriverExecutable(findDefaultExecutable()); } } From 7fc702e6fdff0c5df33e9c986bd1b2ff9a029a2a Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Sun, 15 Dec 2024 20:35:46 +0300 Subject: [PATCH 280/314] fix: Fix compatibility with latest Selenium snapshots (#2249) --- src/main/java/io/appium/java_client/AppiumDriver.java | 10 ++++++---- .../java_client/remote/AppiumCommandExecutor.java | 8 ++++++-- 2 files changed, 12 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 a8757843c..8d0bf3cb1 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -31,6 +31,7 @@ import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.CapabilityType; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.ErrorHandler; import org.openqa.selenium.remote.ExecuteMethod; @@ -242,22 +243,23 @@ public Map getStatus() { * @param methodName The name of custom appium command. */ public void addCommand(HttpMethod httpMethod, String url, String methodName) { + CommandInfo commandInfo; switch (httpMethod) { case GET: - MobileCommand.commandRepository.put(methodName, MobileCommand.getC(url)); + commandInfo = MobileCommand.getC(url); break; case POST: - MobileCommand.commandRepository.put(methodName, MobileCommand.postC(url)); + commandInfo = MobileCommand.postC(url); break; case DELETE: - MobileCommand.commandRepository.put(methodName, MobileCommand.deleteC(url)); + commandInfo = MobileCommand.deleteC(url); break; default: throw new WebDriverException(String.format("Unsupported HTTP Method: %s. Only %s methods are supported", httpMethod, Arrays.toString(HttpMethod.values()))); } - ((AppiumCommandExecutor) getCommandExecutor()).refreshAdditionalCommands(); + ((AppiumCommandExecutor) getCommandExecutor()).defineCommand(methodName, commandInfo); } @Override 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 a689e7d33..d22084c3b 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -133,7 +133,7 @@ protected void setPrivateFieldValue( ReflectionHelpers.setPrivateFieldValue(cls, this, fieldName, newValue); } - protected Map getAdditionalCommands() { + public Map getAdditionalCommands() { //noinspection unchecked return getPrivateFieldValue(HttpCommandExecutor.class, "additionalCommands", Map.class); } @@ -192,7 +192,11 @@ private Response createSession(Command command) throws IOException { } public void refreshAdditionalCommands() { - getAdditionalCommands().forEach(this::defineCommand); + getAdditionalCommands().forEach(super::defineCommand); + } + + public void defineCommand(String commandName, CommandInfo info) { + super.defineCommand(commandName, info); } @SuppressWarnings("unchecked") From d01161d5f852ff381ea4e1d27dc08bba05132b4b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 16 Dec 2024 08:30:18 +0100 Subject: [PATCH 281/314] feat: Implement HasBiDi interface support in AppiumDriver (#2250) --- .github/workflows/gradle.yml | 1 + .../android/AndroidContextTest.java | 7 +- .../android/AndroidFunctionTest.java | 3 +- .../io/appium/java_client/ios/AppIOSTest.java | 1 + .../java_client/ios/BaseIOSWebViewTest.java | 1 - .../appium/java_client/ios/IOSBiDiTest.java | 42 +++ .../java_client/ios/IOSContextTest.java | 5 +- .../java_client/ios/IOSWebViewTest.java | 5 + .../io/appium/java_client/AppiumDriver.java | 254 ++++++++++++------ .../appium/java_client/HasBrowserCheck.java | 4 +- .../utils/WebDriverUnpackUtility.java | 5 +- .../remote/AppiumCommandExecutor.java | 5 +- .../remote/options/BaseOptions.java | 3 +- .../options/SupportsWebSocketUrlOption.java | 54 ++++ .../java/io/appium/java_client/TestUtils.java | 4 + 15 files changed, 296 insertions(+), 98 deletions(-) create mode 100644 src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java create mode 100644 src/main/java/io/appium/java_client/remote/options/SupportsWebSocketUrlOption.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 252710957..cd70a8203 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,6 +19,7 @@ concurrency: cancel-in-progress: true env: + CI: true ANDROID_SDK_VERSION: "28" ANDROID_EMU_NAME: test ANDROID_EMU_TARGET: default diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java index fdc47664b..1a9a5657d 100644 --- a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidContextTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -31,7 +32,7 @@ public class AndroidContextTest extends BaseAndroidTest { } @Test public void testGetContext() { - assertEquals("NATIVE_APP", driver.getContext()); + assertEquals(NATIVE_CONTEXT, driver.getContext()); } @Test public void testGetContextHandles() { @@ -42,8 +43,8 @@ public class AndroidContextTest extends BaseAndroidTest { driver.getContextHandles(); driver.context("WEBVIEW_io.appium.android.apis"); assertEquals(driver.getContext(), "WEBVIEW_io.appium.android.apis"); - driver.context("NATIVE_APP"); - assertEquals(driver.getContext(), "NATIVE_APP"); + driver.context(NATIVE_CONTEXT); + assertEquals(driver.getContext(), NATIVE_CONTEXT); } @Test public void testContextError() { diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java index 79d327ae1..0db6f2647 100644 --- a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidFunctionTest.java @@ -18,6 +18,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; import static java.time.Duration.ofMillis; import static java.time.Duration.ofSeconds; import static org.hamcrest.MatcherAssert.assertThat; @@ -75,7 +76,7 @@ public static void startWebViewActivity() { @BeforeEach public void setUp() { - driver.context("NATIVE_APP"); + driver.context(NATIVE_CONTEXT); } @Test diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java index 25574d727..595114978 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/AppIOSTest.java @@ -21,6 +21,7 @@ public static void beforeClass() { .setDeviceName(DEVICE_NAME) .setCommandTimeouts(Duration.ofSeconds(240)) .setApp(TEST_APP_ZIP) + .enableBiDi() .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT); try { driver = new IOSDriver(service.getUrl(), options); diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index 752a0c539..2ffe6c79c 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -39,7 +39,6 @@ public static void beforeClass() { .setDeviceName(DEVICE_NAME) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT) .setCommandTimeouts(Duration.ofSeconds(240)) - .setShowIosLog(true) .setApp(VODQA_ZIP); Supplier createDriver = () -> new IOSDriver(service.getUrl(), options); try { diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java new file mode 100644 index 000000000..e25d3f515 --- /dev/null +++ b/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.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.ios; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.bidi.log.LogEntry; +import org.openqa.selenium.bidi.module.LogInspector; + +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class IOSBiDiTest extends AppIOSTest { + + @Test + @Disabled("Need to resolve compatibility issues") + public void listenForIosLogs() { + var logs = new CopyOnWriteArrayList(); + try (var logInspector = new LogInspector(NATIVE_CONTEXT, driver)) { + logInspector.onLog(logs::add); + driver.getPageSource(); + } + assertFalse(logs.isEmpty()); + } + +} diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java index f2ac548b1..c7e0af42f 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/IOSContextTest.java @@ -19,6 +19,7 @@ import io.appium.java_client.NoSuchContextException; import org.junit.jupiter.api.Test; +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.StringContains.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -27,7 +28,7 @@ public class IOSContextTest extends BaseIOSWebViewTest { @Test public void testGetContext() { - assertEquals("NATIVE_APP", driver.getContext()); + assertEquals(NATIVE_CONTEXT, driver.getContext()); } @Test public void testGetContextHandles() { @@ -38,7 +39,7 @@ public class IOSContextTest extends BaseIOSWebViewTest { driver.getContextHandles(); findAndSwitchToWebView(); assertThat(driver.getContext(), containsString("WEBVIEW")); - driver.context("NATIVE_APP"); + driver.context(NATIVE_CONTEXT); } @Test public void testContextError() { diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java index 60943342e..da68eeecb 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/IOSWebViewTest.java @@ -1,6 +1,8 @@ package io.appium.java_client.ios; import io.appium.java_client.AppiumBy; +import io.appium.java_client.TestUtils; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.support.ui.ExpectedConditions; @@ -15,6 +17,9 @@ public class IOSWebViewTest extends BaseIOSWebViewTest { @Test public void webViewPageTestCase() throws InterruptedException { + // this test is not stable in the CI env + Assumptions.assumeFalse(TestUtils.isCiEnv()); + new WebDriverWait(driver, LOOKUP_TIMEOUT) .until(ExpectedConditions.presenceOfElementLocated(By.id("login"))) .click(); diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 8d0bf3cb1..01065fe48 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -21,6 +21,7 @@ import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.AppiumW3CHttpCommandCodec; import io.appium.java_client.remote.options.BaseOptions; +import io.appium.java_client.remote.options.SupportsWebSocketUrlOption; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import lombok.Getter; @@ -30,6 +31,9 @@ import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.UnsupportedCommandException; import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.bidi.BiDi; +import org.openqa.selenium.bidi.BiDiException; +import org.openqa.selenium.bidi.HasBiDi; import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.DriverCommand; @@ -43,6 +47,9 @@ import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpMethod; +import javax.annotation.Nonnull; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -67,7 +74,8 @@ public class AppiumDriver extends RemoteWebDriver implements LogsEvents, HasBrowserCheck, CanRememberExtensionPresence, - HasSettings { + HasSettings, + HasBiDi { private static final ErrorHandler ERROR_HANDLER = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters @@ -77,6 +85,9 @@ public class AppiumDriver extends RemoteWebDriver implements protected final RemoteLocationContext locationContext; private final ExecuteMethod executeMethod; private final Set absentExtensionNames = new HashSet<>(); + private URI biDiUri; + private BiDi biDi; + private boolean wasBiDiRequested = false; /** * Creates a new instance based on command {@code executor} and {@code capabilities}. @@ -146,8 +157,8 @@ public AppiumDriver(Capabilities capabilities) { * !!! This API is supposed to be used for **debugging purposes only**. * * @param remoteSessionAddress The address of the **running** session including the session identifier. - * @param platformName The name of the target platform. - * @param automationName The name of the target automation. + * @param platformName The name of the target platform. + * @param automationName The name of the target automation. */ public AppiumDriver(URL remoteSessionAddress, String platformName, String automationName) { super(); @@ -172,54 +183,6 @@ public AppiumDriver(URL remoteSessionAddress, String platformName, String automa setSessionId(sessionAddress.getId()); } - /** - * Changes platform name if it is not set and returns merged capabilities. - * - * @param originalCapabilities the given {@link Capabilities}. - * @param defaultName a platformName value which has to be set up - * @return {@link Capabilities} with changed platform name value or the original capabilities - */ - protected static Capabilities ensurePlatformName( - Capabilities originalCapabilities, String defaultName) { - return originalCapabilities.getPlatformName() == null - ? originalCapabilities.merge(new ImmutableCapabilities(PLATFORM_NAME, defaultName)) - : originalCapabilities; - } - - /** - * Changes automation name if it is not set and returns merged capabilities. - * - * @param originalCapabilities the given {@link Capabilities}. - * @param defaultName a platformName value which has to be set up - * @return {@link Capabilities} with changed mobile automation name value or the original capabilities - */ - protected static Capabilities ensureAutomationName( - Capabilities originalCapabilities, String defaultName) { - String currentAutomationName = CapabilityHelpers.getCapability( - originalCapabilities, AUTOMATION_NAME_OPTION, String.class); - if (isNullOrEmpty(currentAutomationName)) { - String capabilityName = originalCapabilities.getCapabilityNames() - .contains(AUTOMATION_NAME_OPTION) ? AUTOMATION_NAME_OPTION : APPIUM_PREFIX + AUTOMATION_NAME_OPTION; - return originalCapabilities.merge(new ImmutableCapabilities(capabilityName, defaultName)); - } - return originalCapabilities; - } - - /** - * Changes platform and automation names if they are not set - * and returns merged capabilities. - * - * @param originalCapabilities the given {@link Capabilities}. - * @param defaultPlatformName a platformName value which has to be set up - * @param defaultAutomationName The default automation name to set up for this class - * @return {@link Capabilities} with changed platform/automation name value or the original capabilities - */ - protected static Capabilities ensurePlatformAndAutomationNames( - Capabilities originalCapabilities, String defaultPlatformName, String defaultAutomationName) { - Capabilities capsWithPlatformFixed = ensurePlatformName(originalCapabilities, defaultPlatformName); - return ensureAutomationName(capsWithPlatformFixed, defaultAutomationName); - } - @Override public ExecuteMethod getExecuteMethod() { return executeMethod; @@ -262,39 +225,6 @@ public void addCommand(HttpMethod httpMethod, String url, String methodName) { ((AppiumCommandExecutor) getCommandExecutor()).defineCommand(methodName, commandInfo); } - @Override - protected void startSession(Capabilities capabilities) { - var response = Optional.ofNullable( - execute(DriverCommand.NEW_SESSION(singleton(capabilities))) - ).orElseThrow(() -> new SessionNotCreatedException( - "The underlying command executor returned a null response." - )); - - var rawCapabilities = Optional.ofNullable(response.getValue()) - .map(value -> { - if (!(value instanceof Map)) { - throw new SessionNotCreatedException(String.format( - "The underlying command executor returned a response " - + "with a non well formed payload: %s", response) - ); - } - //noinspection unchecked - return (Map) value; - }) - .orElseThrow(() -> new SessionNotCreatedException( - "The underlying command executor returned a response without payload: " + response) - ); - - // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version - rawCapabilities.remove("platform"); - if (rawCapabilities.containsKey(CapabilityType.BROWSER_NAME) - && isNullOrEmpty((String) rawCapabilities.get(CapabilityType.BROWSER_NAME))) { - rawCapabilities.remove(CapabilityType.BROWSER_NAME); - } - this.capabilities = new BaseOptions<>(rawCapabilities); - setSessionId(response.getSessionId()); - } - @Override public Response execute(String driverCommand, Map parameters) { return super.execute(driverCommand, parameters); @@ -339,7 +269,163 @@ public AppiumDriver markExtensionAbsence(String extName) { return this; } + @Override + public Optional maybeGetBiDi() { + return Optional.ofNullable(this.biDi); + } + + @Override + @Nonnull + public BiDi getBiDi() { + var webSocketUrl = ((BaseOptions) this.capabilities).getWebSocketUrl().orElseThrow( + () -> { + var suffix = wasBiDiRequested + ? "Do both the server and the driver declare BiDi support?" + : String.format("Did you set %s to true?", SupportsWebSocketUrlOption.WEB_SOCKET_URL); + return new BiDiException(String.format( + "BiDi is not enabled for this driver session. %s", suffix + )); + } + ); + if (this.biDiUri == null) { + throw new BiDiException( + String.format( + "BiDi is not enabled for this driver session. " + + "Is the %s '%s' received from the create session response valid?", + SupportsWebSocketUrlOption.WEB_SOCKET_URL, webSocketUrl + ) + ); + } + if (this.biDi == null) { + // This should not happen + throw new IllegalStateException(); + } + return this.biDi; + } + protected HttpClient getHttpClient() { return ((HttpCommandExecutor) getCommandExecutor()).client; } + + @Override + protected void startSession(Capabilities requestCapabilities) { + var response = Optional.ofNullable( + execute(DriverCommand.NEW_SESSION(singleton(requestCapabilities))) + ).orElseThrow(() -> new SessionNotCreatedException( + "The underlying command executor returned a null response." + )); + + var rawResponseCapabilities = Optional.ofNullable(response.getValue()) + .map(value -> { + if (!(value instanceof Map)) { + throw new SessionNotCreatedException(String.format( + "The underlying command executor returned a response " + + "with a non well formed payload: %s", response) + ); + } + //noinspection unchecked + return (Map) value; + }) + .orElseThrow(() -> new SessionNotCreatedException( + "The underlying command executor returned a response without payload: " + response) + ); + + // TODO: remove this workaround for Selenium API enforcing some legacy capability values in major version + rawResponseCapabilities.remove("platform"); + if (rawResponseCapabilities.containsKey(CapabilityType.BROWSER_NAME) + && isNullOrEmpty((String) rawResponseCapabilities.get(CapabilityType.BROWSER_NAME))) { + rawResponseCapabilities.remove(CapabilityType.BROWSER_NAME); + } + this.capabilities = new BaseOptions<>(rawResponseCapabilities); + this.wasBiDiRequested = Boolean.TRUE.equals( + requestCapabilities.getCapability(SupportsWebSocketUrlOption.WEB_SOCKET_URL) + ); + if (wasBiDiRequested) { + this.initBiDi((BaseOptions) capabilities); + } + setSessionId(response.getSessionId()); + } + + /** + * Changes platform name if it is not set and returns merged capabilities. + * + * @param originalCapabilities the given {@link Capabilities}. + * @param defaultName a platformName value which has to be set up + * @return {@link Capabilities} with changed platform name value or the original capabilities + */ + protected static Capabilities ensurePlatformName( + Capabilities originalCapabilities, String defaultName) { + return originalCapabilities.getPlatformName() == null + ? originalCapabilities.merge(new ImmutableCapabilities(PLATFORM_NAME, defaultName)) + : originalCapabilities; + } + + /** + * Changes automation name if it is not set and returns merged capabilities. + * + * @param originalCapabilities the given {@link Capabilities}. + * @param defaultName a platformName value which has to be set up + * @return {@link Capabilities} with changed mobile automation name value or the original capabilities + */ + protected static Capabilities ensureAutomationName( + Capabilities originalCapabilities, String defaultName) { + String currentAutomationName = CapabilityHelpers.getCapability( + originalCapabilities, AUTOMATION_NAME_OPTION, String.class); + if (isNullOrEmpty(currentAutomationName)) { + String capabilityName = originalCapabilities.getCapabilityNames() + .contains(AUTOMATION_NAME_OPTION) ? AUTOMATION_NAME_OPTION : APPIUM_PREFIX + AUTOMATION_NAME_OPTION; + return originalCapabilities.merge(new ImmutableCapabilities(capabilityName, defaultName)); + } + return originalCapabilities; + } + + /** + * Changes platform and automation names if they are not set + * and returns merged capabilities. + * + * @param originalCapabilities the given {@link Capabilities}. + * @param defaultPlatformName a platformName value which has to be set up + * @param defaultAutomationName The default automation name to set up for this class + * @return {@link Capabilities} with changed platform/automation name value or the original capabilities + */ + protected static Capabilities ensurePlatformAndAutomationNames( + Capabilities originalCapabilities, String defaultPlatformName, String defaultAutomationName) { + Capabilities capsWithPlatformFixed = ensurePlatformName(originalCapabilities, defaultPlatformName); + return ensureAutomationName(capsWithPlatformFixed, defaultAutomationName); + } + + private void initBiDi(BaseOptions responseCaps) { + var webSocketUrl = responseCaps.getWebSocketUrl(); + if (webSocketUrl.isEmpty()) { + return; + } + URISyntaxException uriSyntaxError = null; + try { + this.biDiUri = new URI(String.valueOf(webSocketUrl.get())); + } catch (URISyntaxException e) { + uriSyntaxError = e; + } + if (uriSyntaxError != null || this.biDiUri.getScheme() == null) { + var message = String.format( + "BiDi cannot be enabled for this driver session. " + + "Is the %s '%s' received from the create session response valid?", + SupportsWebSocketUrlOption.WEB_SOCKET_URL, webSocketUrl.get() + ); + if (uriSyntaxError == null) { + throw new BiDiException(message); + } + throw new BiDiException(message, uriSyntaxError); + } + var executor = getCommandExecutor(); + final HttpClient wsClient; + if (executor instanceof AppiumCommandExecutor) { + var wsConfig = ((AppiumCommandExecutor) executor).getAppiumClientConfig().baseUri(biDiUri); + wsClient = ((AppiumCommandExecutor) executor).getHttpClientFactory().createClient(wsConfig); + } else { + var wsConfig = AppiumClientConfig.defaultConfig().baseUri(biDiUri); + wsClient = HttpClient.Factory.createDefault().createClient(wsConfig); + } + var biDiConnection = new org.openqa.selenium.bidi.Connection(wsClient, biDiUri.toString()); + this.biDi = new BiDi(biDiConnection); + } } diff --git a/src/main/java/io/appium/java_client/HasBrowserCheck.java b/src/main/java/io/appium/java_client/HasBrowserCheck.java index 76094b5ca..ebce4a3c5 100644 --- a/src/main/java/io/appium/java_client/HasBrowserCheck.java +++ b/src/main/java/io/appium/java_client/HasBrowserCheck.java @@ -10,6 +10,8 @@ import static java.util.Objects.requireNonNull; public interface HasBrowserCheck extends ExecutesMethod, HasCapabilities { + String NATIVE_CONTEXT = "NATIVE_APP"; + /** * Validates if the driver is currently in a web browser context. * @@ -32,7 +34,7 @@ default boolean isBrowser() { } try { var context = ((ContextAware) this).getContext(); - return context != null && !context.toUpperCase().contains("NATIVE_APP"); + return context != null && !context.toUpperCase().contains(NATIVE_CONTEXT); } catch (WebDriverException e) { return false; } diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 190f9c4ae..8b59d7ba6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -28,12 +28,11 @@ import java.util.Optional; +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; public final class WebDriverUnpackUtility { - private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; - private WebDriverUnpackUtility() { } @@ -109,7 +108,7 @@ public static ContentType getCurrentContentType(SearchContext context) { var contextAware = unpackObjectFromSearchContext(context, ContextAware.class); if (contextAware.map(ContextAware::getContext) - .filter(c -> c.toUpperCase().contains(NATIVE_APP_PATTERN)).isPresent()) { + .filter(c -> c.toUpperCase().contains(NATIVE_CONTEXT)).isPresent()) { return NATIVE_MOBILE_SPECIFIC; } 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 d22084c3b..84ac2c089 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -19,6 +19,7 @@ import com.google.common.base.Throwables; import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.internal.ReflectionHelpers; +import lombok.Getter; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; @@ -54,9 +55,9 @@ public class AppiumCommandExecutor extends HttpCommandExecutor { private final Optional serviceOptional; - + @Getter private final HttpClient.Factory httpClientFactory; - + @Getter private final AppiumClientConfig appiumClientConfig; /** diff --git a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java index dff4f5c44..7e3ade21f 100644 --- a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java +++ b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java @@ -49,7 +49,8 @@ public class BaseOptions> extends MutableCapabilities i SupportsFullResetOption, SupportsNewCommandTimeoutOption, SupportsBrowserNameOption, - SupportsPlatformVersionOption { + SupportsPlatformVersionOption, + SupportsWebSocketUrlOption { /** * Creates new instance with no preset capabilities. diff --git a/src/main/java/io/appium/java_client/remote/options/SupportsWebSocketUrlOption.java b/src/main/java/io/appium/java_client/remote/options/SupportsWebSocketUrlOption.java new file mode 100644 index 000000000..1e14174cc --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/options/SupportsWebSocketUrlOption.java @@ -0,0 +1,54 @@ +/* + * 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.options; + +import org.openqa.selenium.Capabilities; + +import java.util.Optional; + +public interface SupportsWebSocketUrlOption> extends + Capabilities, CanSetCapability { + String WEB_SOCKET_URL = "webSocketUrl"; + + /** + * Enable BiDi session support. + * + * @return self instance for chaining. + */ + default T enableBiDi() { + return amend(WEB_SOCKET_URL, true); + } + + /** + * Whether to enable BiDi session support. + * + * @return self instance for chaining. + */ + default T setWebSocketUrl(boolean value) { + return amend(WEB_SOCKET_URL, value); + } + + /** + * For input capabilities: whether enable BiDi session support is enabled. + * For session creation response capabilities: BiDi web socket URL. + * + * @return If called on request capabilities if BiDi support is enabled for the driver session + */ + default Optional getWebSocketUrl() { + return Optional.ofNullable(getCapability(WEB_SOCKET_URL)); + } +} diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index 1d650777c..73d104469 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -78,4 +78,8 @@ public static Point getCenter(WebElement webElement, @Nullable Point location) { } return new Point(location.x + dim.width / 2, location.y + dim.height / 2); } + + public static boolean isCiEnv() { + return System.getenv("CI") != null; + } } From f8b7510b70d34625afd84c84cf09752dffbbe54a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:53:12 +0200 Subject: [PATCH 282/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.11.3 to 5.11.4 (#2251) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.3 to 5.11.4. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.3...r5.11.4) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 4e0b67886..3b677ed9f 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.11.3' + implementation 'org.junit.jupiter:junit-jupiter:5.11.4' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From 3a149c9a8bf2fc7e44a8e64680f449156a77e008 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:53:38 +0200 Subject: [PATCH 283/314] build(deps): Bump org.owasp.dependencycheck from 11.1.0 to 11.1.1 (#2248) Bumps org.owasp.dependencycheck from 11.1.0 to 11.1.1. --- 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 3b677ed9f..b64622565 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '11.1.0' + id 'org.owasp.dependencycheck' version '11.1.1' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 39ee841a8979a4deb1be484120848027018987d2 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 7 Jan 2025 12:19:08 +0100 Subject: [PATCH 284/314] test: Reenable BiDi logging tests for iOS (#2254) --- .../appium/java_client/ios/IOSBiDiTest.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java b/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java index e25d3f515..d6288165d 100644 --- a/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java +++ b/src/e2eIosTest/java/io/appium/java_client/ios/IOSBiDiTest.java @@ -16,8 +16,8 @@ package io.appium.java_client.ios; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.openqa.selenium.bidi.Event; import org.openqa.selenium.bidi.log.LogEntry; import org.openqa.selenium.bidi.module.LogInspector; @@ -29,8 +29,23 @@ public class IOSBiDiTest extends AppIOSTest { @Test - @Disabled("Need to resolve compatibility issues") - public void listenForIosLogs() { + public void listenForIosLogsGeneric() { + var logs = new CopyOnWriteArrayList<>(); + var listenerId = driver.getBiDi().addListener( + NATIVE_CONTEXT, + new Event("log.entryAdded", m -> m), + logs::add + ); + try { + driver.getPageSource(); + } finally { + driver.getBiDi().removeListener(listenerId); + } + assertFalse(logs.isEmpty()); + } + + @Test + public void listenForIosLogsSpecific() { var logs = new CopyOnWriteArrayList(); try (var logInspector = new LogInspector(NATIVE_CONTEXT, driver)) { logInspector.onLog(logs::add); From f75963dff9ea9a4037915c034c48079bc4169888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:19:15 +0300 Subject: [PATCH 285/314] build(deps): Bump org.owasp.dependencycheck from 11.1.1 to 12.0.0 (#2255) Bumps org.owasp.dependencycheck from 11.1.1 to 12.0.0. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck 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 b64622565..88f4d7558 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '11.1.1' + id 'org.owasp.dependencycheck' version '12.0.0' id 'com.github.johnrengelman.shadow' version '8.1.1' } From a13724b56b45f641031a9c75ad2a7a9041a79ec9 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 13 Jan 2025 23:01:42 +0100 Subject: [PATCH 286/314] test: Add Android BiDi events test (#2256) --- .../java_client/android/AndroidBiDiTest.java | 57 +++++++++++++++++++ .../java_client/android/BaseAndroidTest.java | 1 + 2 files changed, 58 insertions(+) create mode 100644 src/e2eAndroidTest/java/io/appium/java_client/android/AndroidBiDiTest.java diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidBiDiTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidBiDiTest.java new file mode 100644 index 000000000..9901b50d6 --- /dev/null +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/AndroidBiDiTest.java @@ -0,0 +1,57 @@ +/* + * 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 org.junit.jupiter.api.Test; +import org.openqa.selenium.bidi.Event; +import org.openqa.selenium.bidi.log.LogEntry; +import org.openqa.selenium.bidi.module.LogInspector; + +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class AndroidBiDiTest extends BaseAndroidTest { + + @Test + public void listenForAndroidLogsGeneric() { + var logs = new CopyOnWriteArrayList<>(); + var listenerId = driver.getBiDi().addListener( + NATIVE_CONTEXT, + new Event("log.entryAdded", m -> m), + logs::add + ); + try { + driver.getPageSource(); + } finally { + driver.getBiDi().removeListener(listenerId); + } + assertFalse(logs.isEmpty()); + } + + @Test + public void listenForAndroidLogsSpecific() { + var logs = new CopyOnWriteArrayList(); + try (var logInspector = new LogInspector(NATIVE_CONTEXT, driver)) { + logInspector.onLog(logs::add); + driver.getPageSource(); + } + assertFalse(logs.isEmpty()); + } + +} diff --git a/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java index 347304cf5..d56c8ca45 100644 --- a/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/e2eAndroidTest/java/io/appium/java_client/android/BaseAndroidTest.java @@ -44,6 +44,7 @@ public class BaseAndroidTest { UiAutomator2Options options = new UiAutomator2Options() .setDeviceName("Android Emulator") + .enableBiDi() .setApp(TestResources.API_DEMOS_APK.toString()) .eventTimings(); driver = new AndroidDriver(service.getUrl(), options); From f99533c46894d078519b0ad1aa9b1f114388cc5d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 17 Jan 2025 16:27:32 +0100 Subject: [PATCH 287/314] fix: Update the definition of 'logcatFilterSpecs' option (#2258) --- .../adb/SupportsLogcatFilterSpecsOption.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/appium/java_client/android/options/adb/SupportsLogcatFilterSpecsOption.java b/src/main/java/io/appium/java_client/android/options/adb/SupportsLogcatFilterSpecsOption.java index 6aca7a15e..e671e4848 100644 --- a/src/main/java/io/appium/java_client/android/options/adb/SupportsLogcatFilterSpecsOption.java +++ b/src/main/java/io/appium/java_client/android/options/adb/SupportsLogcatFilterSpecsOption.java @@ -20,6 +20,7 @@ import io.appium.java_client.remote.options.CanSetCapability; import org.openqa.selenium.Capabilities; +import java.util.List; import java.util.Optional; public interface SupportsLogcatFilterSpecsOption> extends @@ -27,25 +28,28 @@ public interface SupportsLogcatFilterSpecsOption> exten String LOGCAT_FILTER_SPECS_OPTION = "logcatFilterSpecs"; /** - * Series of tag[:priority] where tag is a log component tag (or * for all) - * and priority is: V Verbose, D Debug, I Info, W Warn, E Error, F Fatal, - * S Silent (supress all output). '' means ':d' and tag by itself means tag:v. - * If not specified on the commandline, filterspec is set from ANDROID_LOG_TAGS. - * If no filterspec is found, filter defaults to '*:I'. + * Allows to customize logcat output filtering. * - * @param format The filter specifier. + * @param format The filter specifier. Consists from series of `tag[:priority]` items, + * where `tag` is a log component tag (or `*` to match any value) + * and `priority`: V (Verbose), D (Debug), I (Info), W (Warn), E (Error), F (Fatal), + * S (Silent - supresses all output). `tag` without `priority` defaults to `tag:v`. + * If not specified, filterspec is set from ANDROID_LOG_TAGS environment variable. + * If no filterspec is found, filter defaults to `*:I`, which means + * to only show log lines with any tag and the log level INFO or higher. * @return self instance for chaining. */ - default T setLogcatFilterSpecs(String format) { + default T setLogcatFilterSpecs(List format) { return amend(LOGCAT_FILTER_SPECS_OPTION, format); } /** * Get the logcat filter format. * - * @return Format specifier. + * @return Format specifier. See the documentation on {@link #setLogcatFilterSpecs(List)} for more details. */ - default Optional getLogcatFilterSpecs() { - return Optional.ofNullable((String) getCapability(LOGCAT_FILTER_SPECS_OPTION)); + default Optional> getLogcatFilterSpecs() { + //noinspection unchecked + return Optional.ofNullable((List) getCapability(LOGCAT_FILTER_SPECS_OPTION)); } } From eac05b1bb13bfefeab758c4438ad31e6b49876f1 Mon Sep 17 00:00:00 2001 From: Auto81 <48491431+Auto81@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:15:03 +0000 Subject: [PATCH 288/314] fix: Use WeakHashMap for caching proxy classes (#2260) --- .../io/appium/java_client/proxy/Helpers.java | 7 ++-- .../tests/combined/CombinedWidgetTest.java | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index d162c3ed5..7224d763d 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -31,9 +31,9 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.WeakHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -50,7 +50,8 @@ public class Helpers { // the performance and to avoid extensive memory usage for our case, where // the amount of instrumented proxy classes we create is low in comparison to the amount // of proxy instances. - private static final ConcurrentMap> CACHED_PROXY_CLASSES = new ConcurrentHashMap<>(); + private static final Map> CACHED_PROXY_CLASSES = + Collections.synchronizedMap(new WeakHashMap<>()); private Helpers() { } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java index 3c1c9145d..26e0d2f74 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedWidgetTest.java @@ -12,20 +12,29 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; import static org.openqa.selenium.support.PageFactory.initElements; @SuppressWarnings({"unchecked", "unused"}) public class CombinedWidgetTest { + /** + * Based on how many Proxy Classes are created during this test class, + * this number is used to determine if the cache is being purged correctly between tests. + */ + private static final int THRESHOLD_SIZE = 50; + /** * Test data generation. * @@ -57,6 +66,7 @@ public static Stream data() { @ParameterizedTest @MethodSource("data") void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, Class widgetClass) { + assertProxyClassCacheGrowth(); initElements(new AppiumFieldDecorator(driver), app); assertThat("Expected widget class was " + widgetClass.getName(), app.getWidget().getSubWidget().getSelfReference().getClass(), @@ -161,4 +171,32 @@ public List getWidgets() { return multipleWidgets; } } + + + /** + * Assert proxy class cache growth for this test class. + * The (@link io.appium.java_client.proxy.Helpers#CACHED_PROXY_CLASSES) should be populated during these tests. + * Prior to the Caching issue being resolved + * - the CACHED_PROXY_CLASSES would grow indefinitely, resulting in an Out Of Memory exception. + * - this ParameterizedTest would have the CACHED_PROXY_CLASSES grow to 266 entries. + */ + private void assertProxyClassCacheGrowth() { + System.gc(); //Trying to force a collection for more accurate check numbers + assertThat( + "Proxy Class Cache threshold is " + THRESHOLD_SIZE, + getCachedProxyClassesSize(), + lessThan(THRESHOLD_SIZE) + ); + } + + private int getCachedProxyClassesSize() { + try { + Field cpc = Class.forName("io.appium.java_client.proxy.Helpers").getDeclaredField("CACHED_PROXY_CLASSES"); + cpc.setAccessible(true); + Map cachedProxyClasses = (Map) cpc.get(null); + return cachedProxyClasses.size(); + } catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } } From b8240570b1ed5fc590d742506253374a0c14e0ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 21:15:28 +0100 Subject: [PATCH 289/314] build(deps): Bump org.owasp.dependencycheck from 12.0.0 to 12.0.1 (#2261) Bumps org.owasp.dependencycheck from 12.0.0 to 12.0.1. --- 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 88f4d7558..86a8dbff8 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '12.0.0' + id 'org.owasp.dependencycheck' version '12.0.1' id 'com.github.johnrengelman.shadow' version '8.1.1' } From 75d095c465ed0d8f20b24f3a8c9e62fc4a902190 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 21 Jan 2025 17:37:43 +0300 Subject: [PATCH 290/314] release: v9.4.0 (#2262) --- CHANGELOG.md | 12 ++++++++++++ README.md | 5 +++-- gradle.properties | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c42fd19..afce5b183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +_9.4.0_ +- **[ENHANCEMENTS]** + - Implement `HasBiDi` interface support in `AppiumDriver` [#2250](https://github.com/appium/java-client/pull/2250), [#2254](https://github.com/appium/java-client/pull/2254), [#2256](https://github.com/appium/java-client/pull/2256) + - Add compatibility with Selenium `4.28.0` [#2249](https://github.com/appium/java-client/pull/2249) +- **[BUG FIX]** + - Fix scroll issue in flutter integration driver [#2227](https://github.com/appium/java-client/pull/2227) + - Fix the definition of `logcatFilterSpecs` option [#2258](https://github.com/appium/java-client/pull/2258) + - Use `WeakHashMap` for caching proxy classes [#2260](https://github.com/appium/java-client/pull/2260) +- **[DEPENDENCY UPDATES]** + - Bump minimum supported Selenium version from `4.19.0` to `4.26.0` [#2246](https://github.com/appium/java-client/pull/2246) + - Bump Apache Commons Lang from `3.15.0` to `3.16.1` [#2220](https://github.com/appium/java-client/pull/2220), [#2228](https://github.com/appium/java-client/pull/2228) + - Bump SLF4J from `2.0.13` to `2.0.16` [#2221](https://github.com/appium/java-client/pull/2221) _9.3.0_ - **[ENHANCEMENTS]** diff --git a/README.md b/README.md index 91e61c688..2ca984af3 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,9 @@ dependencies { ### Compatibility Matrix Appium Java Client | Selenium client -----------------------------------------------------------------------------------------------------|----------------- - `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2`, `9.2.3`, `9.3.0` | `4.19.0`, `4.19.1`, `4.20.0`, `4.21.0`, `4.22.0`, `4.23.0` +----------------------------------------------------------------------------------------------------|----------------------------- +`9.4.0` | `4.26.0`, `4.27.0`, `4.28.0` + `9.2.1`(known issues: appium/java-client#2145, appium/java-client#2146), `9.2.2`, `9.2.3`, `9.3.0` | `4.19.0`, `4.19.1`, `4.20.0`, `4.21.0`, `4.22.0`, `4.23.0`, `4.23.1`, `4.24.0`, `4.25.0`, `4.26.0`, `4.27.0` `9.1.0`, `9.2.0` | `4.17.0`, `4.18.0`, `4.18.1` `9.0.0` | `4.14.1`, `4.15.0`, `4.16.0` (partially [corrupted](https://github.com/SeleniumHQ/selenium/issues/13256)), `4.16.1` N/A | `4.14.0` diff --git a/gradle.properties b/gradle.properties index 1bd7b6aed..4f5bf63fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,4 +2,4 @@ org.gradle.daemon=true selenium.version=4.26.0 # Please increment the value in a release -appiumClient.version=9.3.0 +appiumClient.version=9.4.0 From 8d882c75faebdc2e6dbde79a0bd388ab2ada45d7 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Mon, 27 Jan 2025 18:55:00 +0300 Subject: [PATCH 291/314] build(deps): Use new coordinates of Gradle Shadow plugin (#2264) The maintenance of the plugin was transferred to the GradleUp organization to ensure future development, see https://github.com/GradleUp/shadow/issues/908. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 86a8dbff8..3ec8bb004 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'signing' id 'org.owasp.dependencycheck' version '12.0.1' - id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'com.gradleup.shadow' version '8.3.5' } repositories { From 2745ecf55f33a20205579f3d0659a1373eb68315 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:21:41 +0300 Subject: [PATCH 292/314] build(deps): Bump com.gradleup.shadow from 8.3.5 to 8.3.6 (#2265) Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.5 to 8.3.6. - [Release notes](https://github.com/GradleUp/shadow/releases) - [Commits](https://github.com/GradleUp/shadow/compare/8.3.5...8.3.6) --- updated-dependencies: - dependency-name: com.gradleup.shadow 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 3ec8bb004..5ce55a488 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { id 'jacoco' id 'signing' id 'org.owasp.dependencycheck' version '12.0.1' - id 'com.gradleup.shadow' version '8.3.5' + id 'com.gradleup.shadow' version '8.3.6' } repositories { From 265bde644a6a4006d980c549231efc24886ed274 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:43:56 +0300 Subject: [PATCH 293/314] build(deps): Bump com.google.code.gson:gson from 2.11.0 to 2.12.1 (#2267) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.11.0 to 2.12.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.11.0...gson-parent-2.12.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson 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 5ce55a488..0e8e00c21 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { } } } - implementation 'com.google.code.gson:gson:2.11.0' + implementation 'com.google.code.gson:gson:2.12.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" } From 15229beab2e4738612606d34ab26a306d85b1e1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:46:13 +0300 Subject: [PATCH 294/314] build(deps): Bump org.owasp.dependencycheck from 12.0.1 to 12.0.2 (#2266) Bumps org.owasp.dependencycheck from 12.0.1 to 12.0.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 0e8e00c21..783d39e24 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '12.0.1' + id 'org.owasp.dependencycheck' version '12.0.2' id 'com.gradleup.shadow' version '8.3.6' } From eda193c37049ba92742ca882ad39490fa4561945 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:59:46 +0300 Subject: [PATCH 295/314] build(deps): Bump org.owasp.dependencycheck from 12.0.2 to 12.1.0 (#2269) Bumps org.owasp.dependencycheck from 12.0.2 to 12.1.0. --- 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 783d39e24..9ccff1500 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '12.0.2' + id 'org.owasp.dependencycheck' version '12.1.0' id 'com.gradleup.shadow' version '8.3.6' } From 6b6de17409a26c080597e95ffee15c2a430b101a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 20:27:58 +0300 Subject: [PATCH 296/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2270) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.9.2 to 5.9.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.9.2...webdrivermanager-5.9.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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 9ccff1500..90fa79f9a 100644 --- a/build.gradle +++ b/build.gradle @@ -213,7 +213,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.9.2') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.3') { exclude group: 'org.seleniumhq.selenium' } } @@ -257,7 +257,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.9.2') { + implementation('io.github.bonigarcia:webdrivermanager:5.9.3') { exclude group: 'org.seleniumhq.selenium' } } From 3c5d12be5b15120a697b69e6e8b8b8d13185f6e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:12:43 +0300 Subject: [PATCH 297/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.11.4 to 5.12.0 (#2272) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.11.4 to 5.12.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.11.4...r5.12.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 90fa79f9a..3a164d7a9 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.11.4' + implementation 'org.junit.jupiter:junit-jupiter:5.12.0' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From eff2b601b1542b59d411d39055346b28915f2283 Mon Sep 17 00:00:00 2001 From: Alexander <2487708+alexanderkaiser@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:58:39 +0100 Subject: [PATCH 298/314] feat: Allow extension capability keys to contain dot characters (#2271) --- .../remote/options/W3CCapabilityKeys.java | 2 +- .../remote/options/BaseOptionsTest.java | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/appium/java_client/remote/options/BaseOptionsTest.java diff --git a/src/main/java/io/appium/java_client/remote/options/W3CCapabilityKeys.java b/src/main/java/io/appium/java_client/remote/options/W3CCapabilityKeys.java index b29150311..09ff1680f 100644 --- a/src/main/java/io/appium/java_client/remote/options/W3CCapabilityKeys.java +++ b/src/main/java/io/appium/java_client/remote/options/W3CCapabilityKeys.java @@ -23,7 +23,7 @@ public class W3CCapabilityKeys implements Predicate { public static final W3CCapabilityKeys INSTANCE = new W3CCapabilityKeys(); private static final Predicate ACCEPTED_W3C_PATTERNS = Stream.of( - "^[\\w-]+:.*$", + "^[\\w-\\.]+:.*$", "^acceptInsecureCerts$", "^browserName$", "^browserVersion$", diff --git a/src/test/java/io/appium/java_client/remote/options/BaseOptionsTest.java b/src/test/java/io/appium/java_client/remote/options/BaseOptionsTest.java new file mode 100644 index 000000000..50e36818d --- /dev/null +++ b/src/test/java/io/appium/java_client/remote/options/BaseOptionsTest.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.remote.options; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BaseOptionsTest { + + @ParameterizedTest + @CsvSource({ + "test, appium:test", + "appium:test, appium:test", + "browserName, browserName", + "digital.ai:accessKey, digital.ai:accessKey", + "digital-ai:accessKey, digital-ai:accessKey", + "digital-ai:my_custom-cap:xyz, digital-ai:my_custom-cap:xyz", + "digital-ai:my_custom-cap?xyz, digital-ai:my_custom-cap?xyz", + }) + void verifyW3CMapping(String capName, String expected) { + var w3cName = BaseOptions.toW3cName(capName); + assertEquals(expected, w3cName); + } +} \ No newline at end of file From b74e807921f13ad6d3e050ebd37648265b7b0ae9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:07:48 +0300 Subject: [PATCH 299/314] build(deps): Bump slf4jVersion from 2.0.16 to 2.0.17 (#2274) Bumps `slf4jVersion` from 2.0.16 to 2.0.17. Updates `org.slf4j:slf4j-api` from 2.0.16 to 2.0.17 Updates `org.slf4j:slf4j-simple` from 2.0.16 to 2.0.17 --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.slf4j:slf4j-simple 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 3a164d7a9..17f6e1ed4 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ java { ext { seleniumVersion = project.property('selenium.version') appiumClientVersion = project.property('appiumClient.version') - slf4jVersion = '2.0.16' + slf4jVersion = '2.0.17' } dependencies { From aa42e785c8f2128d56b596db3469a50234c32426 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 10 Mar 2025 14:01:24 +0100 Subject: [PATCH 300/314] feat: Add StorageClient (#2275) --- .../plugins/storage/StorageClient.java | 248 ++++++++++++++++++ .../plugins/storage/StorageItem.java | 10 + .../plugins/storage/StorageUtils.java | 90 +++++++ .../java_client/plugin/StorageTest.java | 62 +++++ 4 files changed, 410 insertions(+) create mode 100644 src/main/java/io/appium/java_client/plugins/storage/StorageClient.java create mode 100644 src/main/java/io/appium/java_client/plugins/storage/StorageItem.java create mode 100644 src/main/java/io/appium/java_client/plugins/storage/StorageUtils.java create mode 100644 src/test/java/io/appium/java_client/plugin/StorageTest.java diff --git a/src/main/java/io/appium/java_client/plugins/storage/StorageClient.java b/src/main/java/io/appium/java_client/plugins/storage/StorageClient.java new file mode 100644 index 000000000..013782ec8 --- /dev/null +++ b/src/main/java/io/appium/java_client/plugins/storage/StorageClient.java @@ -0,0 +1,248 @@ +/* + * 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.plugins.storage; + +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.remote.ErrorCodec; +import org.openqa.selenium.remote.codec.AbstractHttpResponseCodec; +import org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.Contents; +import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpHeader; +import org.openqa.selenium.remote.http.HttpMethod; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.HttpResponse; +import org.openqa.selenium.remote.http.WebSocket; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static io.appium.java_client.plugins.storage.StorageUtils.calcSha1Digest; +import static io.appium.java_client.plugins.storage.StorageUtils.streamFileToWebSocket; + +/** + * This is a Java implementation of the Appium server storage plugin client. + * See the plugin README + * for more details. + */ +public class StorageClient { + public static final String PREFIX = "/storage"; + private final Json json = new Json(); + private final AbstractHttpResponseCodec responseCodec = new W3CHttpResponseCodec(); + private final ErrorCodec errorCodec = ErrorCodec.createDefault(); + + private final URL baseUrl; + private final HttpClient httpClient; + + public StorageClient(URL baseUrl) { + this.baseUrl = baseUrl; + this.httpClient = HttpClient.Factory.createDefault().createClient(baseUrl); + } + + public StorageClient(ClientConfig clientConfig) { + this.httpClient = HttpClient.Factory.createDefault().createClient(clientConfig); + this.baseUrl = clientConfig.baseUrl(); + } + + /** + * Adds a local file to the server storage. + * The remote file name is be set to the same value as the local file name. + * + * @param file File instance. + */ + public void add(File file) { + add(file, file.getName()); + } + + /** + * Adds a local file to the server storage. + * + * @param file File instance. + * @param name The remote file name. + */ + public void add(File file, String name) { + var request = new HttpRequest(HttpMethod.POST, formatPath(baseUrl, PREFIX, "add").toString()); + var httpResponse = httpClient.execute(setJsonPayload(request, Map.of( + "name", name, + "sha1", calcSha1Digest(file) + ))); + Map value = requireResponseValue(httpResponse); + final var wsTtlMs = (Long) value.get("ttlMs"); + //noinspection unchecked + var wsInfo = (Map) value.get("ws"); + var streamWsPathname = (String) wsInfo.get("stream"); + var eventWsPathname = (String) wsInfo.get("events"); + final var completion = new CountDownLatch(1); + final var lastException = new AtomicReference(null); + try (var streamWs = httpClient.openSocket( + new HttpRequest(HttpMethod.POST, formatPath(baseUrl, streamWsPathname).toString()), + new WebSocket.Listener() {} + ); var eventsWs = httpClient.openSocket( + new HttpRequest(HttpMethod.POST, formatPath(baseUrl, eventWsPathname).toString()), + new EventWsListener(lastException, completion) + )) { + streamFileToWebSocket(file, streamWs); + streamWs.close(); + if (!completion.await(wsTtlMs, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException(String.format( + "Could not receive a confirmation about adding '%s' to the server storage within %sms timeout", + name, wsTtlMs + )); + } + var exc = lastException.get(); + if (exc != null) { + throw exc instanceof RuntimeException ? (RuntimeException) exc : new WebDriverException(exc); + } + } catch (InterruptedException e) { + throw new WebDriverException(e); + } + } + + /** + * Lists items that exist in the storage. + * + * @return All storage items. + */ + public List list() { + var request = new HttpRequest(HttpMethod.GET, formatPath(baseUrl, PREFIX, "list").toString()); + var httpResponse = httpClient.execute(request); + List> items = requireResponseValue(httpResponse); + return items.stream().map(item -> new StorageItem( + (String) item.get("name"), + (String) item.get("path"), + (Long) item.get("size") + )).collect(Collectors.toList()); + } + + /** + * Deletes an item from the server storage. + * + * @param name The name of the item to be deleted. + * @return true if the dletion was successful. + */ + public boolean delete(String name) { + var request = new HttpRequest(HttpMethod.POST, formatPath(baseUrl, PREFIX, "delete").toString()); + var httpResponse = httpClient.execute(setJsonPayload(request, Map.of( + "name", name + ))); + return requireResponseValue(httpResponse); + } + + /** + * Resets all items of the server storage. + */ + public void reset() { + var request = new HttpRequest(HttpMethod.POST, formatPath(baseUrl, PREFIX, "reset").toString()); + var httpResponse = httpClient.execute(request); + requireResponseValue(httpResponse); + } + + private static URL formatPath(URL url, String... suffixes) { + if (suffixes.length == 0) { + return url; + } + try { + var uri = url.toURI(); + var updatedPath = (uri.getPath() + "/" + String.join("/", suffixes)).replaceAll("(/{2,})", "/"); + return new URI( + uri.getScheme(), + uri.getAuthority(), + uri.getHost(), + uri.getPort(), + updatedPath, + uri.getQuery(), + uri.getFragment() + ).toURL(); + } catch (URISyntaxException | MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + private HttpRequest setJsonPayload(HttpRequest request, Map payload) { + var strData = json.toJson(payload); + var data = strData.getBytes(StandardCharsets.UTF_8); + request.setHeader(HttpHeader.ContentLength.getName(), String.valueOf(data.length)); + request.setHeader(HttpHeader.ContentType.getName(), "application/json; charset=utf-8"); + request.setContent(Contents.bytes(data)); + return request; + } + + private T requireResponseValue(HttpResponse httpResponse) { + var response = responseCodec.decode(httpResponse); + var value = response.getValue(); + if (value instanceof WebDriverException) { + throw (WebDriverException) value; + } + //noinspection unchecked + return (T) response.getValue(); + } + + private final class EventWsListener implements WebSocket.Listener { + private final AtomicReference lastException; + private final CountDownLatch completion; + + public EventWsListener(AtomicReference lastException, CountDownLatch completion) { + this.lastException = lastException; + this.completion = completion; + } + + @Override + public void onBinary(byte[] data) { + extractException(new String(data, StandardCharsets.UTF_8)).ifPresent(lastException::set); + completion.countDown(); + } + + @Override + public void onText(CharSequence data) { + extractException(data.toString()).ifPresent(lastException::set); + completion.countDown(); + } + + @Override + public void onError(Throwable cause) { + lastException.set(cause); + completion.countDown(); + } + + private Optional extractException(String payload) { + try { + Map record = json.toType(payload, Json.MAP_TYPE); + //noinspection unchecked + var value = (Map) record.get("value"); + if ((Boolean) value.get("success")) { + return Optional.empty(); + } + return Optional.of(errorCodec.decode(record)); + } catch (Exception e) { + return Optional.of(new WebDriverException(payload, e)); + } + } + } +} diff --git a/src/main/java/io/appium/java_client/plugins/storage/StorageItem.java b/src/main/java/io/appium/java_client/plugins/storage/StorageItem.java new file mode 100644 index 000000000..17ae1472e --- /dev/null +++ b/src/main/java/io/appium/java_client/plugins/storage/StorageItem.java @@ -0,0 +1,10 @@ +package io.appium.java_client.plugins.storage; + +import lombok.Value; + +@Value +public class StorageItem { + String name; + String path; + long size; +} diff --git a/src/main/java/io/appium/java_client/plugins/storage/StorageUtils.java b/src/main/java/io/appium/java_client/plugins/storage/StorageUtils.java new file mode 100644 index 000000000..3ef6c943c --- /dev/null +++ b/src/main/java/io/appium/java_client/plugins/storage/StorageUtils.java @@ -0,0 +1,90 @@ +/* + * 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.plugins.storage; + +import org.openqa.selenium.remote.http.WebSocket; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Formatter; + +public class StorageUtils { + private static final int BUFFER_SIZE = 0xFFFF; + + private StorageUtils() { + } + + /** + * Calculates SHA1 hex digest of the given file. + * + * @param source The file instance to calculate the hash for. + * @return Hash digest represented as a string of hexadecimal numbers. + */ + public static String calcSha1Digest(File source) { + MessageDigest sha1sum; + try { + sha1sum = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + var buffer = new byte[BUFFER_SIZE]; + int bytesRead; + try (var in = new BufferedInputStream(new FileInputStream(source))) { + while ((bytesRead = in.read(buffer)) != -1) { + sha1sum.update(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return byteToHex(sha1sum.digest()); + } + + /** + * Feeds the content of the given file to the provided web socket. + * + * @param source The source file instance. + * @param socket The destination web socket. + */ + public static void streamFileToWebSocket(File source, WebSocket socket) { + var buffer = new byte[BUFFER_SIZE]; + int bytesRead; + try (var in = new BufferedInputStream(new FileInputStream(source))) { + while ((bytesRead = in.read(buffer)) != -1) { + var currentBuffer = new byte[bytesRead]; + System.arraycopy(buffer, 0, currentBuffer, 0, bytesRead); + socket.sendBinary(currentBuffer); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String byteToHex(final byte[] hash) { + var formatter = new Formatter(); + for (byte b : hash) { + formatter.format("%02x", b); + } + var result = formatter.toString(); + formatter.close(); + return result; + } +} diff --git a/src/test/java/io/appium/java_client/plugin/StorageTest.java b/src/test/java/io/appium/java_client/plugin/StorageTest.java new file mode 100644 index 000000000..b1042a971 --- /dev/null +++ b/src/test/java/io/appium/java_client/plugin/StorageTest.java @@ -0,0 +1,62 @@ +/* + * 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.plugin; + +import io.appium.java_client.TestUtils; +import io.appium.java_client.plugins.storage.StorageClient; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StorageTest { + private StorageClient storageClient; + + @BeforeEach + void before() throws MalformedURLException { + // These tests assume Appium server with storage plugin is already running + // at the given baseUrl + Assumptions.assumeFalse(TestUtils.isCiEnv()); + storageClient = new StorageClient(new URL("http://127.0.0.1:4723/")); + storageClient.reset(); + } + + @Test + void shouldBeAbleToPerformBasicStorageActions() { + assertTrue(storageClient.list().isEmpty()); + var name = "hello appium - saved page.htm"; + var testFile = TestUtils.resourcePathToAbsolutePath("html/" + name).toFile(); + storageClient.add(testFile); + assertItemsCount(1); + assertTrue(storageClient.delete(name)); + assertItemsCount(0); + storageClient.add(testFile); + assertItemsCount(1); + storageClient.reset(); + assertItemsCount(0); + } + + private void assertItemsCount(int expected) { + var items = storageClient.list(); + assertEquals(expected, items.size()); + } +} From f2b824e1d5ff1e7179e791e16225839be100de36 Mon Sep 17 00:00:00 2001 From: muhammadyunus99 Date: Mon, 10 Mar 2025 20:33:43 +0200 Subject: [PATCH 301/314] docs: Replace deprecated new URL(String) with URI(String).toURL() (#2263) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2ca984af3..be1f8e546 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ UiAutomator2Options options = new UiAutomator2Options() .setApp("/home/myapp.apk"); AndroidDriver driver = new AndroidDriver( // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub - new URL("http://127.0.0.1:4723/"), options + new URI("http://127.0.0.1:4723/").toURL(), options ); try { WebElement el = driver.findElement(AppiumBy.xpath("//Button")); @@ -193,7 +193,7 @@ XCUITestOptions options = new XCUITestOptions() .setApp("/home/myapp.ipa"); IOSDriver driver = new IOSDriver( // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub - new URL("http://127.0.0.1:4723/"), options + new URI("http://127.0.0.1:4723/").toURL(), options ); try { WebElement el = driver.findElement(AppiumBy.accessibilityId("myId")); @@ -214,7 +214,7 @@ BaseOptions options = new BaseOptions() .amend("mycapability2", "capvalue2"); AppiumDriver driver = new AppiumDriver( // The default URL in Appium 1 is http://127.0.0.1:4723/wd/hub - new URL("http://127.0.0.1:4723/"), options + new URI("http://127.0.0.1:4723/").toURL(), options ); try { WebElement el = driver.findElement(AppiumBy.className("myClass")); From c145c7d3b1ba988f237d924712cb07abc5292fff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 20:35:22 +0300 Subject: [PATCH 302/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.12.0 to 5.12.1 (#2278) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter 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 17f6e1ed4..63cdc954c 100644 --- a/build.gradle +++ b/build.gradle @@ -195,7 +195,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.12.0' + implementation 'org.junit.jupiter:junit-jupiter:5.12.1' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From febcffb812dd18296fb2acdb5a7c6525d3303d51 Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Thu, 27 Mar 2025 11:16:20 +0300 Subject: [PATCH 303/314] fix: Add explicit dependency on JSR 305 annotations library (#2280) --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 63cdc954c..095975515 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,7 @@ dependencies { } implementation 'com.google.code.gson:gson:2.12.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" + implementation 'com.google.code.findbugs:jsr305:3.0.2' } dependencyCheck { From fcde595162266590c95fb7a7eade1e0611c0e340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Mar 2025 12:52:36 +0300 Subject: [PATCH 304/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2279) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 5.9.3 to 6.0.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-5.9.3...webdrivermanager-6.0.0) --- 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 095975515..33bb7fd4c 100644 --- a/build.gradle +++ b/build.gradle @@ -214,7 +214,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:5.9.3') { + implementation('io.github.bonigarcia:webdrivermanager:6.0.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -258,7 +258,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:5.9.3') { + implementation('io.github.bonigarcia:webdrivermanager:6.0.0') { exclude group: 'org.seleniumhq.selenium' } } From 9a59abddfc670367155578291ae53b536c43284b Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Fri, 28 Mar 2025 08:20:50 +0300 Subject: [PATCH 305/314] refactor: Migrate from JSR 305 to JSpecify (#2281) --- build.gradle | 2 +- .../io/appium/java_client/AppiumClientConfig.java | 2 +- src/main/java/io/appium/java_client/AppiumDriver.java | 4 ++-- .../io/appium/java_client/CommandExecutionHelper.java | 2 +- .../java/io/appium/java_client/ComparesImages.java | 2 +- .../java/io/appium/java_client/ExecuteCDPCommand.java | 2 +- .../io/appium/java_client/ExecutesDriverScript.java | 2 +- .../java/io/appium/java_client/InteractsWithApps.java | 2 +- src/main/java/io/appium/java_client/Location.java | 3 +-- .../java/io/appium/java_client/MobileCommand.java | 2 +- .../io/appium/java_client/android/StartsActivity.java | 3 +-- .../java_client/internal/CapabilityHelpers.java | 2 +- .../internal/filters/AppiumUserAgentFilter.java | 7 +++---- .../pagefactory/AppiumElementLocatorFactory.java | 2 +- .../java_client/pagefactory/AppiumFieldDecorator.java | 6 +++--- .../java_client/pagefactory/WidgetInterceptor.java | 8 +++----- .../pagefactory/WidgetListInterceptor.java | 5 ++--- .../java_client/pagefactory/bys/ContentMappedBy.java | 4 ++-- .../pagefactory/bys/builder/AppiumByBuilder.java | 2 +- .../interceptors/InterceptorOfAListOfElements.java | 2 +- .../interceptors/InterceptorOfASingleElement.java | 5 ++--- .../pagefactory/utils/WebDriverUnpackUtility.java | 3 +-- .../java/io/appium/java_client/proxy/Helpers.java | 2 +- .../java_client/remote/AppiumCommandExecutor.java | 11 ++++++----- .../io/appium/java_client/remote/DirectConnect.java | 2 +- .../java_client/remote/SupportsContextSwitching.java | 2 +- .../java_client/remote/options/BaseOptions.java | 4 ++-- .../service/local/AppiumDriverLocalService.java | 2 +- .../service/local/AppiumServiceBuilder.java | 4 ++-- .../appium/java_client/ws/StringWebSocketClient.java | 2 +- src/test/java/io/appium/java_client/TestUtils.java | 2 +- 31 files changed, 48 insertions(+), 55 deletions(-) diff --git a/build.gradle b/build.gradle index 33bb7fd4c..1f97df659 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,7 @@ dependencies { } implementation 'com.google.code.gson:gson:2.12.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" - implementation 'com.google.code.findbugs:jsr305:3.0.2' + implementation 'org.jspecify:jspecify:1.0.0' } dependencyCheck { diff --git a/src/main/java/io/appium/java_client/AppiumClientConfig.java b/src/main/java/io/appium/java_client/AppiumClientConfig.java index e6ebfd0bb..49097f341 100644 --- a/src/main/java/io/appium/java_client/AppiumClientConfig.java +++ b/src/main/java/io/appium/java_client/AppiumClientConfig.java @@ -18,12 +18,12 @@ import io.appium.java_client.internal.filters.AppiumIdempotencyFilter; import io.appium.java_client.internal.filters.AppiumUserAgentFilter; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.Credentials; import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.http.ClientConfig; import org.openqa.selenium.remote.http.Filter; -import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import java.net.Proxy; import java.net.URI; diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 01065fe48..4c641fd2b 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -25,6 +25,7 @@ import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import lombok.Getter; +import org.jspecify.annotations.NonNull; import org.openqa.selenium.Capabilities; import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.OutputType; @@ -47,7 +48,6 @@ import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpMethod; -import javax.annotation.Nonnull; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -275,7 +275,7 @@ public Optional maybeGetBiDi() { } @Override - @Nonnull + @NonNull public BiDi getBiDi() { var webSocketUrl = ((BaseOptions) this.capabilities).getWebSocketUrl().orElseThrow( () -> { diff --git a/src/main/java/io/appium/java_client/CommandExecutionHelper.java b/src/main/java/io/appium/java_client/CommandExecutionHelper.java index e64470b38..b56a2f4ac 100644 --- a/src/main/java/io/appium/java_client/CommandExecutionHelper.java +++ b/src/main/java/io/appium/java_client/CommandExecutionHelper.java @@ -16,9 +16,9 @@ package io.appium.java_client; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.remote.Response; -import javax.annotation.Nullable; import java.util.Collections; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/ComparesImages.java b/src/main/java/io/appium/java_client/ComparesImages.java index 5a9a58b1c..4f44d6e0a 100644 --- a/src/main/java/io/appium/java_client/ComparesImages.java +++ b/src/main/java/io/appium/java_client/ComparesImages.java @@ -23,8 +23,8 @@ import io.appium.java_client.imagecomparison.OccurrenceMatchingResult; import io.appium.java_client.imagecomparison.SimilarityMatchingOptions; import io.appium.java_client.imagecomparison.SimilarityMatchingResult; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java index 8b9f18317..7114da787 100644 --- a/src/main/java/io/appium/java_client/ExecuteCDPCommand.java +++ b/src/main/java/io/appium/java_client/ExecuteCDPCommand.java @@ -16,9 +16,9 @@ package io.appium.java_client; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.remote.Response; -import javax.annotation.Nullable; import java.util.Collections; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/ExecutesDriverScript.java b/src/main/java/io/appium/java_client/ExecutesDriverScript.java index 1ffebedb9..2509dba85 100644 --- a/src/main/java/io/appium/java_client/ExecutesDriverScript.java +++ b/src/main/java/io/appium/java_client/ExecutesDriverScript.java @@ -18,9 +18,9 @@ import io.appium.java_client.driverscripts.ScriptOptions; import io.appium.java_client.driverscripts.ScriptValue; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.remote.Response; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 9fe25dc24..0ca018abb 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -22,10 +22,10 @@ import io.appium.java_client.appmanagement.BaseOptions; import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.UnsupportedCommandException; -import javax.annotation.Nullable; import java.time.Duration; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/Location.java b/src/main/java/io/appium/java_client/Location.java index 336c09797..322665a42 100644 --- a/src/main/java/io/appium/java_client/Location.java +++ b/src/main/java/io/appium/java_client/Location.java @@ -19,8 +19,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; - -import javax.annotation.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents the physical location. diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 029c1abb7..b4df90047 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -21,10 +21,10 @@ import io.appium.java_client.imagecomparison.ComparisonMode; import io.appium.java_client.screenrecording.BaseStartScreenRecordingOptions; import io.appium.java_client.screenrecording.BaseStopScreenRecordingOptions; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.http.HttpMethod; -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java index 9f56d7b1a..23b0ad7a9 100644 --- a/src/main/java/io/appium/java_client/android/StartsActivity.java +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -19,10 +19,9 @@ import io.appium.java_client.CanRememberExtensionPresence; import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.ExecutesMethod; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.UnsupportedCommandException; -import javax.annotation.Nullable; - import java.util.Map; import static io.appium.java_client.MobileCommand.CURRENT_ACTIVITY; 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 855a40cdb..345e60a9c 100644 --- a/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java +++ b/src/main/java/io/appium/java_client/internal/CapabilityHelpers.java @@ -16,9 +16,9 @@ package io.appium.java_client.internal; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.Capabilities; -import javax.annotation.Nullable; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; diff --git a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java index d4f1842ce..b8086e1f8 100644 --- a/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java +++ b/src/main/java/io/appium/java_client/internal/filters/AppiumUserAgentFilter.java @@ -17,14 +17,13 @@ package io.appium.java_client.internal.filters; import io.appium.java_client.internal.Config; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.remote.http.AddSeleniumUserAgent; import org.openqa.selenium.remote.http.Filter; import org.openqa.selenium.remote.http.HttpHandler; import org.openqa.selenium.remote.http.HttpHeader; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - /** * Manage Appium Client configurations. */ @@ -41,7 +40,7 @@ public class AppiumUserAgentFilter implements Filter { */ public static final String USER_AGENT = buildUserAgentHeaderValue(AddSeleniumUserAgent.USER_AGENT); - private static String buildUserAgentHeaderValue(@Nonnull String previousUA) { + private static String buildUserAgentHeaderValue(@NonNull String previousUA) { return String.format("%s%s (%s)", USER_AGENT_PREFIX, Config.main().getValue(VERSION_KEY, String.class), previousUA); } diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 77b3120fc..f423d1dca 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -19,9 +19,9 @@ import io.appium.java_client.pagefactory.bys.builder.AppiumByBuilder; import io.appium.java_client.pagefactory.locator.CacheableElementLocatorFactory; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.SearchContext; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; 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 05fa41a42..ccbed8fac 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -19,6 +19,8 @@ import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -30,8 +32,6 @@ import org.openqa.selenium.support.pagefactory.ElementLocatorFactory; import org.openqa.selenium.support.pagefactory.FieldDecorator; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -123,7 +123,7 @@ contextReference, duration, new WidgetByBuilder(platform, automation) ); } - @Nonnull + @NonNull private static WeakReference requireWebDriverReference(SearchContext searchContext) { var wd = unpackObjectFromSearchContext( checkNotNull(searchContext, "The provided search context cannot be null"), diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java index bfc358bd8..46d946628 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetInterceptor.java @@ -19,11 +19,11 @@ import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -48,11 +48,9 @@ public class WidgetInterceptor extends InterceptorOfASingleElement { * Proxy interceptor class for widgets. */ public WidgetInterceptor( - @Nullable - CacheableLocator locator, + @Nullable CacheableLocator locator, WeakReference driverReference, - @Nullable - WeakReference cachedElementReference, + @Nullable WeakReference cachedElementReference, Map> instantiationMap, Duration duration ) { diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java index ff9983f8c..bb4bb1889 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetListInterceptor.java @@ -19,10 +19,10 @@ import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Method; @@ -50,8 +50,7 @@ public class WidgetListInterceptor extends InterceptorOfAListOfElements { * Proxy interceptor class for lists of widgets. */ public WidgetListInterceptor( - @Nullable - CacheableLocator locator, + @Nullable CacheableLocator locator, WeakReference driver, Map> instantiationMap, Class declaredType, diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java index 6c0c0f99f..14967c6d7 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java @@ -17,11 +17,11 @@ package io.appium.java_client.pagefactory.bys; import lombok.EqualsAndHashCode; +import org.jspecify.annotations.NonNull; import org.openqa.selenium.By; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebElement; -import javax.annotation.Nonnull; import java.util.List; import java.util.Map; @@ -43,7 +43,7 @@ public ContentMappedBy(Map map) { * @param type required content type {@link ContentType} * @return self-reference. */ - public By useContent(@Nonnull ContentType type) { + public By useContent(@NonNull ContentType type) { requireNonNull(type); currentContent = type; return this; 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 0e7146a0c..73f6717aa 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 @@ -16,11 +16,11 @@ package io.appium.java_client.pagefactory.bys.builder; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.By; import org.openqa.selenium.support.pagefactory.AbstractAnnotations; import org.openqa.selenium.support.pagefactory.ByAll; -import javax.annotation.Nullable; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java index fc35f9992..3f8bd4fdf 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfAListOfElements.java @@ -17,10 +17,10 @@ package io.appium.java_client.pagefactory.interceptors; import io.appium.java_client.proxy.MethodCallListener; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; -import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java index 738f49823..968ff824d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java +++ b/src/main/java/io/appium/java_client/pagefactory/interceptors/InterceptorOfASingleElement.java @@ -17,13 +17,13 @@ package io.appium.java_client.pagefactory.interceptors; import io.appium.java_client.proxy.MethodCallListener; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.support.pagefactory.ElementLocator; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.util.Objects; @@ -34,8 +34,7 @@ public abstract class InterceptorOfASingleElement implements MethodCallListener private final WeakReference driverReference; public InterceptorOfASingleElement( - @Nullable - ElementLocator locator, + @Nullable ElementLocator locator, WeakReference driverReference ) { this.locator = locator; diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 8b59d7ba6..997460be5 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -18,14 +18,13 @@ import io.appium.java_client.HasBrowserCheck; import io.appium.java_client.pagefactory.bys.ContentType; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.ContextAware; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.WrapsElement; -import javax.annotation.Nullable; - import java.util.Optional; import static io.appium.java_client.HasBrowserCheck.NATIVE_CONTEXT; diff --git a/src/main/java/io/appium/java_client/proxy/Helpers.java b/src/main/java/io/appium/java_client/proxy/Helpers.java index 7224d763d..f9fae7768 100644 --- a/src/main/java/io/appium/java_client/proxy/Helpers.java +++ b/src/main/java/io/appium/java_client/proxy/Helpers.java @@ -26,8 +26,8 @@ import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; 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 84ac2c089..bf02271d3 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -20,6 +20,8 @@ import io.appium.java_client.AppiumClientConfig; import io.appium.java_client.internal.ReflectionHelpers; import lombok.Getter; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; @@ -34,12 +36,11 @@ import org.openqa.selenium.remote.ResponseCodec; import org.openqa.selenium.remote.codec.w3c.W3CHttpCommandCodec; import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpClient.Factory; import org.openqa.selenium.remote.http.HttpRequest; import org.openqa.selenium.remote.http.HttpResponse; import org.openqa.selenium.remote.service.DriverService; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.IOException; import java.net.ConnectException; import java.net.MalformedURLException; @@ -69,10 +70,10 @@ public class AppiumCommandExecutor extends HttpCommandExecutor { * @param appiumClientConfig take a look at {@link AppiumClientConfig} */ public AppiumCommandExecutor( - @Nonnull Map additionalCommands, + @NonNull Map additionalCommands, @Nullable DriverService service, - @Nullable HttpClient.Factory httpClientFactory, - @Nonnull AppiumClientConfig appiumClientConfig) { + @Nullable Factory httpClientFactory, + @NonNull AppiumClientConfig appiumClientConfig) { super(additionalCommands, appiumClientConfig, ofNullable(httpClientFactory).orElseGet(AppiumCommandExecutor::getDefaultClientFactory) diff --git a/src/main/java/io/appium/java_client/remote/DirectConnect.java b/src/main/java/io/appium/java_client/remote/DirectConnect.java index 809fdc736..fb1a05c51 100644 --- a/src/main/java/io/appium/java_client/remote/DirectConnect.java +++ b/src/main/java/io/appium/java_client/remote/DirectConnect.java @@ -18,8 +18,8 @@ import lombok.AccessLevel; import lombok.Getter; +import org.jspecify.annotations.Nullable; -import javax.annotation.Nullable; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java index b126e4fa1..7fad6f0e5 100644 --- a/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java +++ b/src/main/java/io/appium/java_client/remote/SupportsContextSwitching.java @@ -19,12 +19,12 @@ import io.appium.java_client.ExecutesMethod; import io.appium.java_client.MobileCommand; import io.appium.java_client.NoSuchContextException; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.ContextAware; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Response; -import javax.annotation.Nullable; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java index 7e3ade21f..cc544022c 100644 --- a/src/main/java/io/appium/java_client/remote/options/BaseOptions.java +++ b/src/main/java/io/appium/java_client/remote/options/BaseOptions.java @@ -16,6 +16,7 @@ package io.appium.java_client.remote.options; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.Capabilities; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.Platform; @@ -23,7 +24,6 @@ import org.openqa.selenium.internal.Require; import org.openqa.selenium.remote.CapabilityType; -import javax.annotation.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; @@ -164,4 +164,4 @@ public Object getCapability(String capabilityName) { public static String toW3cName(String capName) { return W3CCapabilityKeys.INSTANCE.test(capName) ? capName : APPIUM_PREFIX + capName; } -} \ No newline at end of file +} 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 8026300ad..1ded306b8 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 @@ -18,13 +18,13 @@ import lombok.Getter; import lombok.SneakyThrows; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.os.ExternalProcess; import org.openqa.selenium.remote.service.DriverService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; -import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; 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 5dadb6403..3a3529926 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,13 +25,13 @@ import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.appium.java_client.service.local.flags.ServerArgument; import lombok.SneakyThrows; +import org.jspecify.annotations.Nullable; 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.service.DriverService; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -471,4 +471,4 @@ protected AppiumDriverLocalService createDriverService(File nodeJSExecutable, in return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, startupTimeout, nodeArguments, nodeEnvironment).withBasePath(basePath); } -} \ No newline at end of file +} 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 33c5d8aa6..f080c061c 100644 --- a/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java +++ b/src/main/java/io/appium/java_client/ws/StringWebSocketClient.java @@ -16,12 +16,12 @@ package io.appium.java_client.ws; +import org.jspecify.annotations.Nullable; 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; -import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.net.URI; import java.util.List; diff --git a/src/test/java/io/appium/java_client/TestUtils.java b/src/test/java/io/appium/java_client/TestUtils.java index 73d104469..5f8b11ddf 100644 --- a/src/test/java/io/appium/java_client/TestUtils.java +++ b/src/test/java/io/appium/java_client/TestUtils.java @@ -1,11 +1,11 @@ package io.appium.java_client; +import org.jspecify.annotations.Nullable; import org.openqa.selenium.Dimension; import org.openqa.selenium.Point; import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; -import javax.annotation.Nullable; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; From 6a6aeeaae792b5ee87cc21edbe3f2fa46601d181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:17:22 +0300 Subject: [PATCH 306/314] build(deps): Bump org.projectlombok:lombok from 1.18.36 to 1.18.38 (#2284) Bumps [org.projectlombok:lombok](https://github.com/projectlombok/lombok) from 1.18.36 to 1.18.38. - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.36...v1.18.38) --- updated-dependencies: - dependency-name: org.projectlombok:lombok dependency-version: 1.18.38 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 1f97df659..e3a702ad6 100644 --- a/build.gradle +++ b/build.gradle @@ -38,8 +38,8 @@ ext { } dependencies { - compileOnly 'org.projectlombok:lombok:1.18.36' - annotationProcessor 'org.projectlombok:lombok:1.18.36' + compileOnly 'org.projectlombok:lombok:1.18.38' + annotationProcessor 'org.projectlombok:lombok:1.18.38' if (project.hasProperty("isCI")) { api "org.seleniumhq.selenium:selenium-api:${seleniumVersion}" From 05a4bdf1b7e6f7016d44e08aa8dcc41ba5f1bd43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:17:47 +0300 Subject: [PATCH 307/314] build(deps): Bump org.owasp.dependencycheck from 12.1.0 to 12.1.1 (#2283) Bumps org.owasp.dependencycheck from 12.1.0 to 12.1.1. --- updated-dependencies: - dependency-name: org.owasp.dependencycheck dependency-version: 12.1.1 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 e3a702ad6..26a41a51b 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { id 'maven-publish' id 'jacoco' id 'signing' - id 'org.owasp.dependencycheck' version '12.1.0' + id 'org.owasp.dependencycheck' version '12.1.1' id 'com.gradleup.shadow' version '8.3.6' } From cad009af51085961579a9483762bb25c9de0bc22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:18:11 +0300 Subject: [PATCH 308/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2285) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 6.0.0 to 6.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-6.0.0...webdrivermanager-6.0.1) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-version: 6.0.1 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 26a41a51b..2434c9ccb 100644 --- a/build.gradle +++ b/build.gradle @@ -214,7 +214,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:6.0.0') { + implementation('io.github.bonigarcia:webdrivermanager:6.0.1') { exclude group: 'org.seleniumhq.selenium' } } @@ -258,7 +258,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:6.0.0') { + implementation('io.github.bonigarcia:webdrivermanager:6.0.1') { exclude group: 'org.seleniumhq.selenium' } } From 80806c5e3feb3efe892487fbafd52fc837b498c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 18:20:45 +0300 Subject: [PATCH 309/314] build(deps): Bump com.google.code.gson:gson from 2.12.1 to 2.13.0 (#2286) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.12.1 to 2.13.0. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.12.1...gson-parent-2.13.0) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-version: 2.13.0 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 2434c9ccb..6ad4f7987 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { } } } - implementation 'com.google.code.gson:gson:2.12.1' + implementation 'com.google.code.gson:gson:2.13.0' implementation "org.slf4j:slf4j-api:${slf4jVersion}" implementation 'org.jspecify:jspecify:1.0.0' } From 12a0f004d85c89f953d1bca2058ea98c9d960235 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:59:51 +0300 Subject: [PATCH 310/314] build(deps): Bump org.junit.jupiter:junit-jupiter from 5.12.1 to 5.12.2 (#2287) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-version: 5.12.2 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 6ad4f7987..dcad66d7e 100644 --- a/build.gradle +++ b/build.gradle @@ -196,7 +196,7 @@ testing { configureEach { useJUnitJupiter() dependencies { - implementation 'org.junit.jupiter:junit-jupiter:5.12.1' + implementation 'org.junit.jupiter:junit-jupiter:5.12.2' runtimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.hamcrest:hamcrest:3.0' runtimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" From 0cd5eae713c095847b181d937404a7a0be5783fb Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 26 Apr 2025 16:33:46 +0200 Subject: [PATCH 311/314] ci: Tune CC title script --- .github/workflows/pr-title.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 393ca3bd6..1658a2957 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -1,6 +1,7 @@ name: Conventional Commits on: pull_request: + types: [opened, edited, synchronize, reopened] jobs: From cafe304df716f3bedcb9e0a99870aa31f1aa2f72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:59:41 +0300 Subject: [PATCH 312/314] build(deps): Bump com.google.code.gson:gson from 2.13.0 to 2.13.1 (#2290) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.13.0 to 2.13.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.13.0...gson-parent-2.13.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-version: 2.13.1 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 dcad66d7e..6f9dec120 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { } } } - implementation 'com.google.code.gson:gson:2.13.0' + implementation 'com.google.code.gson:gson:2.13.1' implementation "org.slf4j:slf4j-api:${slf4jVersion}" implementation 'org.jspecify:jspecify:1.0.0' } From 5f2bc1a4d417ee09e0207f62b35d659b368daf2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:27:33 +0300 Subject: [PATCH 313/314] build(deps): Bump io.github.bonigarcia:webdrivermanager (#2289) Bumps [io.github.bonigarcia:webdrivermanager](https://github.com/bonigarcia/webdrivermanager) from 6.0.1 to 6.1.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-6.0.1...webdrivermanager-6.1.0) --- updated-dependencies: - dependency-name: io.github.bonigarcia:webdrivermanager dependency-version: 6.1.0 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 6f9dec120..5304c65c4 100644 --- a/build.gradle +++ b/build.gradle @@ -214,7 +214,7 @@ testing { test { dependencies { implementation "org.seleniumhq.selenium:selenium-chrome-driver:${seleniumVersion}" - implementation('io.github.bonigarcia:webdrivermanager:6.0.1') { + implementation('io.github.bonigarcia:webdrivermanager:6.1.0') { exclude group: 'org.seleniumhq.selenium' } } @@ -258,7 +258,7 @@ testing { dependencies { implementation project() implementation(sourceSets.test.output) - implementation('io.github.bonigarcia:webdrivermanager:6.0.1') { + implementation('io.github.bonigarcia:webdrivermanager:6.1.0') { exclude group: 'org.seleniumhq.selenium' } } From 3e5f9e2045913d35b859bc37b55453dcebe94324 Mon Sep 17 00:00:00 2001 From: Max B Date: Mon, 28 Apr 2025 12:26:13 -0500 Subject: [PATCH 314/314] feat: Swap check for Widget and WebElement (#2277) --- .../pagefactory/AppiumFieldDecorator.java | 4 +- .../DesktopBrowserCompatibilityTest.java | 3 + .../widget/tests/DefaultStubWidget.java | 83 ++++++++++++++++++- .../tests/combined/CombinedAppTest.java | 4 + 4 files changed, 91 insertions(+), 3 deletions(-) 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 ccbed8fac..792932cd4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -193,8 +193,8 @@ protected boolean isDecoratableList(Field field) { * @return a field value or null. */ public Object decorate(ClassLoader ignored, Field field) { - Object result = defaultElementFieldDecorator.decorate(ignored, field); - return result == null ? decorateWidget(field) : result; + Object result = decorateWidget(field); + return result == null ? defaultElementFieldDecorator.decorate(ignored, field) : result; } @Nullable 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 40c672ae7..911fefc02 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 @@ -21,6 +21,7 @@ import io.appium.java_client.pagefactory.AndroidFindBy; import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.HowToUseLocators; +import io.appium.java_client.pagefactory.Widget; import io.appium.java_client.pagefactory.iOSXCUITFindBy; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -37,6 +38,7 @@ import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -69,6 +71,7 @@ public class DesktopBrowserCompatibilityTest { assertNotEquals(0, main.size()); assertNull(trap1); assertNull(trap2); + foundLinks.forEach(element -> assertFalse(Widget.class.isAssignableFrom(element.getClass()))); } finally { driver.quit(); } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java index 5977646d7..7de8cf327 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/DefaultStubWidget.java @@ -1,11 +1,17 @@ package io.appium.java_client.pagefactory_tests.widget.tests; import io.appium.java_client.pagefactory.Widget; +import org.jspecify.annotations.Nullable; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.Point; +import org.openqa.selenium.Rectangle; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import java.util.List; -public class DefaultStubWidget extends Widget { +public class DefaultStubWidget extends Widget implements WebElement { protected DefaultStubWidget(WebElement element) { super(element); } @@ -22,4 +28,79 @@ public List getSubWidgets() { public String toString() { return getWrappedElement().toString(); } + + @Override + public void click() { + getWrappedElement().click(); + } + + @Override + public void submit() { + getWrappedElement().submit(); + } + + @Override + public void sendKeys(CharSequence... keysToSend) { + getWrappedElement().sendKeys(keysToSend); + } + + @Override + public void clear() { + getWrappedElement().clear(); + } + + @Override + public String getTagName() { + return getWrappedElement().getTagName(); + } + + @Override + public @Nullable String getAttribute(String name) { + return getWrappedElement().getAttribute(name); + } + + @Override + public boolean isSelected() { + return getWrappedElement().isSelected(); + } + + @Override + public boolean isEnabled() { + return getWrappedElement().isEnabled(); + } + + @Override + public String getText() { + return getWrappedElement().getText(); + } + + @Override + public boolean isDisplayed() { + return getWrappedElement().isDisplayed(); + } + + @Override + public Point getLocation() { + return getWrappedElement().getLocation(); + } + + @Override + public Dimension getSize() { + return getWrappedElement().getSize(); + } + + @Override + public Rectangle getRect() { + return getWrappedElement().getRect(); + } + + @Override + public String getCssValue(String propertyName) { + return getWrappedElement().getCssValue(propertyName); + } + + @Override + public X getScreenshotAs(OutputType target) throws WebDriverException { + return getWrappedElement().getScreenshotAs(target); + } } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java index c3ee905fb..c7e50ef5f 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/widget/tests/combined/CombinedAppTest.java @@ -6,10 +6,12 @@ import io.appium.java_client.pagefactory_tests.widget.tests.AbstractStubWebDriver; import io.appium.java_client.pagefactory_tests.widget.tests.DefaultStubWidget; import io.appium.java_client.pagefactory_tests.widget.tests.android.DefaultAndroidWidget; +import org.hamcrest.Matchers; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; import java.util.List; import java.util.stream.Stream; @@ -58,6 +60,8 @@ void checkThatWidgetsAreCreatedCorrectly(AbstractApp app, WebDriver driver, assertThat("Expected widget class was " + widgetClass.getName(), app.getWidget().getSelfReference().getClass(), equalTo(widgetClass)); + assertThat(app.getWidget().getSelfReference(), + Matchers.instanceOf(WebElement.class)); List> classes = app.getWidgets().stream().map(abstractWidget -> abstractWidget .getSelfReference().getClass())