Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@ 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:

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: 21
platform: ubuntu-latest
fail-fast: false

runs-on: ${{ matrix.platform }}
Expand Down Expand Up @@ -78,9 +79,11 @@ jobs:
with:
node-version: 'lts/*'

- name: Install Appium
- name: Install Appium & Flutter driver
if: matrix.e2e-tests == 'android' || matrix.e2e-tests == 'ios'
run: npm install --location=global appium
run: |
npm install --location=global appium
appium driver install appium-flutter-integration-driver --source npm

- name: Install UIA2 driver
if: matrix.e2e-tests == 'android'
Expand All @@ -89,7 +92,8 @@ jobs:
if: matrix.e2e-tests == 'android'
uses: reactivecircus/android-emulator-runner@v2
with:
script: ./gradlew e2eAndroidTest -PisCI -Pselenium.version=$latest_snapshot
script: ./gradlew e2eAndroidTest -PisCI -Pselenium.version=$latest_snapshot &&
./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
Expand Down
26 changes: 24 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ dependencies {
}

dependencyCheck {
failBuildOnCVSS=22
failBuildOnCVSS = 22
}

jacoco {
Expand Down Expand Up @@ -185,7 +185,7 @@ wrapper {

processResources {
filter ReplaceTokens, tokens: [
'selenium.version': seleniumVersion,
'selenium.version' : seleniumVersion,
'appiumClient.version': appiumClientVersion
]
}
Expand Down Expand Up @@ -290,5 +290,27 @@ testing {
}
}
}

e2eFlutterTest(JvmTestSuite) {
sources {
java {
srcDirs = ['src/e2eFlutterTest/java']
}
}
dependencies {
implementation project()
implementation(sourceSets.test.output)
implementation('io.github.bonigarcia:webdrivermanager:5.9.1') {
exclude group: 'org.seleniumhq.selenium'
}
}

targets.configureEach {
testTask.configure {
shouldRunAfter(test)
systemProperties project.properties.subMap(["platform", "flutterApp"])
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

public class BaseFlutterTest {

protected static final boolean IS_ANDROID = Optional
.ofNullable(System.getProperty("platform"))
.orElse("android")
.equalsIgnoreCase("android");
public 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 By loginButton = 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();
}
}
Original file line number Diff line number Diff line change
@@ -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;

public class CommandTest extends BaseFlutterTest {

AppiumBy.FlutterBy messageFieldLocator = AppiumBy.flutterKey("message_field");
AppiumBy.FlutterBy toggleButtonLocator = AppiumBy.flutterKey("toggle_button");

@Test
public void testWaitCommand() {
WebElement loginButton = driver.findElement(this.loginButton);
loginButton.click();
openScreen("Lazy Loading");

WebElement messageField = driver.findElement(messageFieldLocator);
WebElement toggleButton = driver.findElement(toggleButtonLocator);

assertEquals(messageField.getText(), "Hello world");
toggleButton.click();
assertEquals(messageField.getText(), "Hello world");

WaitParameter waitParameter = new WaitParameter().setLocator(messageFieldLocator);

driver.waitForInVisible(waitParameter);
assertEquals(0, driver.findElements(messageFieldLocator).size());
toggleButton.click();
driver.waitForVisible(waitParameter);
assertEquals(1, driver.findElements(messageFieldLocator).size());
assertEquals(messageField.getText(), "Hello world");
}

@Test
public void testScrollTillVisibleCommand() {
WebElement loginButton = driver.findElement(this.loginButton);
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")));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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;


public 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(this.loginButton);
loginButton.click();
assertEquals(driver.findElement(AppiumBy.flutterTextContaining("Vertical")).getText(),
"Vertical Swiping");
}

@Test
public void testFlutterSemanticsLabel() {
WebElement loginButton = driver.findElement(this.loginButton);
loginButton.click();
openScreen("Lazy Loading");

WebElement messageField = driver.findElement(AppiumBy.flutterSemanticsLabel("message_field"));
assertEquals(messageField.getText(),
"Hello world");
}
}
10 changes: 5 additions & 5 deletions src/main/java/io/appium/java_client/AppiumBy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down
Loading