diff --git a/src/main/java/io/appium/java_client/flutter/FlutterDriver.java b/src/main/java/io/appium/java_client/flutter/FlutterDriver.java new file mode 100644 index 000000000..99266ba38 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/FlutterDriver.java @@ -0,0 +1,92 @@ +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.FlutterCommandParameter; +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.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; + +/** + * Interface representing a Flutter driver that extends {@link RemoteWebDriver}. + * Provides convenience methods for executing Flutter-specific commands using JavaScriptExecutor. + */ +public interface FlutterDriver { + + /** + * 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) { + executeScript("waitForVisible", parameter); + } + + /** + * Waits for an element to become invisible on the screen. + * + * @param parameter The parameters for waiting, specifying timeout and element details. + */ + default void waitForInVisible(WaitParameter parameter) { + executeScript("waitForAbsent", parameter); + } + + /** + * 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) executeScript("scrollTillVisible", parameter); + } + + /** + * Performs a double-click action on an element. + * + * @param parameter The parameters for double-clicking, specifying element details. + */ + default void performDoubleClick(DoubleClickParameter parameter) { + executeScript("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) { + executeScript("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) { + executeScript("dragAndDrop", parameter); + } + + /** + * 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. + */ + private Object executeScript(String scriptName, FlutterCommandParameter parameter) { + String commandName = String.format("flutter: %s", scriptName); + return ((JavascriptExecutor) this.getDriver()).executeScript(commandName, parameter.toJson()); + } + + /** + * Retrieves the underlying RemoteWebDriver instance associated with this FlutterDriver. + * + * @return The RemoteWebDriver instance used by this FlutterDriver. + */ + RemoteWebDriver getDriver(); +} \ No newline at end of file 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..16cfea62d --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/android/FlutterAndroidDriver.java @@ -0,0 +1,75 @@ +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.FlutterDriver; +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.RemoteWebDriver; +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 FlutterDriver { + + 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); + } + + @Override + public RemoteWebDriver getDriver() { + return this; + } +} 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..3eee83876 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/DoubleClickParameter.java @@ -0,0 +1,12 @@ +package io.appium.java_client.flutter.commands; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Accessors(chain = true) +@Setter +@Getter +public class DoubleClickParameter extends FlutterGestureParameter { + +} 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..78689b25e --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/DragAndDropParameter.java @@ -0,0 +1,27 @@ +package io.appium.java_client.flutter.commands; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import org.openqa.selenium.WebElement; + +import java.util.Map; + +@Accessors(chain = true) +@Getter +@AllArgsConstructor +public class DragAndDropParameter extends FlutterCommandParameter { + @NonNull + WebElement source; + @NonNull + WebElement target; + + private DragAndDropParameter() { + } + + @Override + public Map toJson() { + return Map.of("source", source, "target", target); + } +} 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..befe2b310 --- /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. + */ + 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/FlutterGestureParameter.java b/src/main/java/io/appium/java_client/flutter/commands/FlutterGestureParameter.java new file mode 100644 index 000000000..4120e96e0 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/FlutterGestureParameter.java @@ -0,0 +1,29 @@ +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Accessors(chain = true) +@Setter +@Getter +public class FlutterGestureParameter extends FlutterCommandParameter { + WebElement element; + Point point; + + @Override + public Map toJson() { + Map args = new HashMap<>(); + args.put("origin", element); + if (point != null) { + args.put("offset", Map.of("x", point.getX(), "y", point.getY())); + } + return Collections.unmodifiableMap(args); + } +} 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..668b4c1ba --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/LongPressParameter.java @@ -0,0 +1,13 @@ +package io.appium.java_client.flutter.commands; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + + +@Accessors(chain = true) +@Setter +@Getter +public class LongPressParameter extends FlutterGestureParameter { + +} 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..26fb8a360 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/ScrollParameter.java @@ -0,0 +1,70 @@ +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.Map; +import java.util.Optional; + +@Accessors(chain = true) +@Getter +@Setter +public class ScrollParameter extends FlutterCommandParameter { + AppiumBy.FlutterBy scrollTo; + WebElement scrollView; + ScrollDirection scrollDirection; + Integer delta; + Integer maxScrolls; + Integer settleBetweenScrollsTimeout; + Duration dragDuration; + + private ScrollParameter() { + } + + /** + * 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() { + return Map.of( + "finder", parseFlutterLocator(scrollTo), + "scrollView", scrollView, + "delta", delta, + "maxScrolls", maxScrolls, + "settleBetweenScrollsTimeout", settleBetweenScrollsTimeout, + "scrollDirection", Optional.ofNullable(scrollDirection) + .orElse(ScrollDirection.UP).getDirection(), + "dragDuration", Optional.ofNullable(dragDuration) + .orElse(Duration.ZERO).getSeconds() + ); + } + + @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..2e0941780 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/commands/WaitParameter.java @@ -0,0 +1,34 @@ +package io.appium.java_client.flutter.commands; + +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; + +@Accessors(chain = true) +@Getter +@Setter +public class WaitParameter extends FlutterCommandParameter { + private WebElement element; + private AppiumBy.FlutterBy locator; + private Duration timeout; + + @Override + public Map toJson() { + Map args = new HashMap<>(); + args.put("element", element); + if (locator != null) { + args.put("locator", parseFlutterLocator(locator)); + } + if (timeout != null) { + args.put("timeout", timeout.getSeconds()); + } + return Collections.unmodifiableMap(args); + } +} 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..f79fc2970 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSDriver.java @@ -0,0 +1,75 @@ +package io.appium.java_client.flutter.ios; + +import io.appium.java_client.AppiumClientConfig; +import io.appium.java_client.flutter.FlutterDriver; +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.RemoteWebDriver; +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 FlutterIOSDriver extends IOSDriver implements FlutterDriver { + + 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); + } + + @Override + public RemoteWebDriver getDriver() { + return this; + } +} diff --git a/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSOptions.java b/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSOptions.java new file mode 100644 index 000000000..9d1ec9817 --- /dev/null +++ b/src/main/java/io/appium/java_client/flutter/ios/FlutterIOSOptions.java @@ -0,0 +1,29 @@ +package io.appium.java_client.flutter.ios; + +import io.appium.java_client.ios.options.XCUITestOptions; +import io.appium.java_client.remote.AutomationName; +import org.openqa.selenium.Capabilities; + +import java.util.Map; + +public class FlutterIOSOptions extends XCUITestOptions { + + public FlutterIOSOptions() { + super(); + addDefaultOptions(); + } + + public FlutterIOSOptions(Capabilities source) { + super(source); + addDefaultOptions(); + } + + public FlutterIOSOptions(Map source) { + super(source); + addDefaultOptions(); + } + + private void addDefaultOptions() { + setAutomationName(AutomationName.FLUTTER_INTEGRATION); + } +} 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"; }