Skip to content

Commit 2caff36

Browse files
feat: add flutter driver commands to support camera mocking (#2207)
* feat: add flutter driver commands to support camera mocking
1 parent 6bd38a1 commit 2caff36

File tree

9 files changed

+132
-4
lines changed

9 files changed

+132
-4
lines changed

src/e2eFlutterTest/java/io/appium/java_client/android/BaseFlutterTest.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.appium.java_client.ios.options.XCUITestOptions;
1111
import io.appium.java_client.service.local.AppiumDriverLocalService;
1212
import io.appium.java_client.service.local.AppiumServiceBuilder;
13+
import io.appium.java_client.service.local.flags.GeneralServerFlag;
1314
import org.junit.jupiter.api.AfterAll;
1415
import org.junit.jupiter.api.AfterEach;
1516
import org.junit.jupiter.api.BeforeAll;
@@ -43,6 +44,10 @@ public static void beforeClass() {
4344
service = new AppiumServiceBuilder()
4445
.withIPAddress("127.0.0.1")
4546
.usingPort(PORT)
47+
// Flutter driver mocking command requires adb_shell permission to set certain permissions
48+
// to the AUT. This can be removed once the server logic is updated to use a different approach
49+
// for setting the permission
50+
.withArgument(GeneralServerFlag.ALLOW_INSECURE, "adb_shell")
4651
.build();
4752
service.start();
4853
}
@@ -52,11 +57,14 @@ void startSession() throws MalformedURLException {
5257
FlutterDriverOptions flutterOptions = new FlutterDriverOptions()
5358
.setFlutterServerLaunchTimeout(Duration.ofMinutes(2))
5459
.setFlutterSystemPort(9999)
55-
.setFlutterElementWaitTimeout(Duration.ofSeconds(10));
60+
.setFlutterElementWaitTimeout(Duration.ofSeconds(10))
61+
.setFlutterEnableMockCamera(true);
62+
5663
if (IS_ANDROID) {
5764
driver = new FlutterAndroidDriver(service.getUrl(), flutterOptions
5865
.setUiAutomator2Options(new UiAutomator2Options()
5966
.setApp(System.getProperty("flutterApp"))
67+
.setAutoGrantPermissions(true)
6068
.eventTimings())
6169
);
6270
} else {

src/e2eFlutterTest/java/io/appium/java_client/android/CommandTest.java

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.appium.java_client.android;
22

33
import io.appium.java_client.AppiumBy;
4+
import io.appium.java_client.TestUtils;
45
import io.appium.java_client.flutter.commands.DoubleClickParameter;
56
import io.appium.java_client.flutter.commands.DragAndDropParameter;
67
import io.appium.java_client.flutter.commands.LongPressParameter;
@@ -10,6 +11,8 @@
1011
import org.openqa.selenium.Point;
1112
import org.openqa.selenium.WebElement;
1213

14+
import java.io.IOException;
15+
1316
import static org.junit.jupiter.api.Assertions.assertEquals;
1417
import static org.junit.jupiter.api.Assertions.assertFalse;
1518
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -115,4 +118,25 @@ void testDragAndDropCommand() {
115118
assertEquals(driver.findElement(AppiumBy.flutterText("The box is dropped")).getText(), "The box is dropped");
116119

117120
}
121+
122+
@Test
123+
void testCameraMocking() throws IOException {
124+
driver.findElement(BaseFlutterTest.LOGIN_BUTTON).click();
125+
openScreen("Image Picker");
126+
127+
final String successQr = driver.injectMockImage(
128+
TestUtils.resourcePathToAbsolutePath("success_qr.png").toFile());
129+
driver.injectMockImage(
130+
TestUtils.resourcePathToAbsolutePath("second_qr.png").toFile());
131+
132+
driver.findElement(AppiumBy.flutterKey("capture_image")).click();
133+
driver.findElement(AppiumBy.flutterText("PICK")).click();
134+
assertTrue(driver.findElement(AppiumBy.flutterText("SecondInjectedImage")).isDisplayed());
135+
136+
driver.activateInjectedImage(successQr);
137+
138+
driver.findElement(AppiumBy.flutterKey("capture_image")).click();
139+
driver.findElement(AppiumBy.flutterText("PICK")).click();
140+
assertTrue(driver.findElement(AppiumBy.flutterText("Success!")).isDisplayed());
141+
}
118142
}
130 KB
Loading
32 KB
Loading

src/main/java/io/appium/java_client/flutter/CanExecuteFlutterScripts.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.appium.java_client.flutter.commands.FlutterCommandParameter;
44
import org.openqa.selenium.JavascriptExecutor;
55

6+
import java.util.Map;
7+
68
public interface CanExecuteFlutterScripts extends JavascriptExecutor {
79

810
/**
@@ -13,8 +15,19 @@ public interface CanExecuteFlutterScripts extends JavascriptExecutor {
1315
* @return The result of executing the script.
1416
*/
1517
default Object executeFlutterCommand(String scriptName, FlutterCommandParameter parameter) {
18+
return executeFlutterCommand(scriptName, parameter.toJson());
19+
}
20+
21+
/**
22+
* Executes a Flutter-specific script using JavascriptExecutor.
23+
*
24+
* @param scriptName The name of the Flutter script to execute.
25+
* @param args The args for the Flutter command in Map format.
26+
* @return The result of executing the script.
27+
*/
28+
default Object executeFlutterCommand(String scriptName, Map<String, Object> args) {
1629
String commandName = String.format("flutter: %s", scriptName);
17-
return executeScript(commandName, parameter.toJson());
30+
return executeScript(commandName, args);
1831
}
1932

2033
}

src/main/java/io/appium/java_client/flutter/FlutterDriverOptions.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.appium.java_client.android.options.UiAutomator2Options;
44
import io.appium.java_client.flutter.options.SupportsFlutterElementWaitTimeoutOption;
5+
import io.appium.java_client.flutter.options.SupportsFlutterEnableMockCamera;
56
import io.appium.java_client.flutter.options.SupportsFlutterServerLaunchTimeoutOption;
67
import io.appium.java_client.flutter.options.SupportsFlutterSystemPortOption;
78
import io.appium.java_client.ios.options.XCUITestOptions;
@@ -17,7 +18,8 @@
1718
public class FlutterDriverOptions extends BaseOptions<FlutterDriverOptions> implements
1819
SupportsFlutterSystemPortOption<FlutterDriverOptions>,
1920
SupportsFlutterServerLaunchTimeoutOption<FlutterDriverOptions>,
20-
SupportsFlutterElementWaitTimeoutOption<FlutterDriverOptions> {
21+
SupportsFlutterElementWaitTimeoutOption<FlutterDriverOptions>,
22+
SupportsFlutterEnableMockCamera<FlutterDriverOptions> {
2123

2224
public FlutterDriverOptions() {
2325
setDefaultOptions();

src/main/java/io/appium/java_client/flutter/FlutterIntegrationTestDriver.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ public interface FlutterIntegrationTestDriver extends
1919
WebDriver,
2020
SupportsGestureOnFlutterElements,
2121
SupportsScrollingOfFlutterElements,
22-
SupportsWaitingForFlutterElements {
22+
SupportsWaitingForFlutterElements,
23+
SupportsFlutterCameraMocking {
2324
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.appium.java_client.flutter;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.Files;
6+
import java.util.Base64;
7+
import java.util.Map;
8+
9+
/**
10+
* This interface extends {@link CanExecuteFlutterScripts} and provides methods
11+
* to support mocking of camera inputs in Flutter applications.
12+
*/
13+
public interface SupportsFlutterCameraMocking extends CanExecuteFlutterScripts {
14+
15+
/**
16+
* Injects a mock image into the Flutter application using the provided file.
17+
*
18+
* @param image the image file to be mocked (must be in PNG format)
19+
* @return an {@code String} representing a unique id of the injected image
20+
* @throws IOException if an I/O error occurs while reading the image file
21+
*/
22+
default String injectMockImage(File image) throws IOException {
23+
String base64EncodedImage = Base64.getEncoder().encodeToString(Files.readAllBytes(image.toPath()));
24+
return injectMockImage(base64EncodedImage);
25+
}
26+
27+
/**
28+
* Injects a mock image into the Flutter application using the provided Base64-encoded image string.
29+
*
30+
* @param base64Image the Base64-encoded string representation of the image (must be in PNG format)
31+
* @return an {@code String} representing the result of the injection operation
32+
*/
33+
default String injectMockImage(String base64Image) {
34+
return (String) executeFlutterCommand("injectImage", Map.of(
35+
"base64Image", base64Image
36+
));
37+
}
38+
39+
/**
40+
* Activates the injected image identified by the specified image ID in the Flutter application.
41+
*
42+
* @param imageId the ID of the injected image to activate
43+
*/
44+
default void activateInjectedImage(String imageId) {
45+
executeFlutterCommand("activateInjectedImage", Map.of("imageId", imageId));
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.appium.java_client.flutter.options;
2+
3+
import io.appium.java_client.remote.options.BaseOptions;
4+
import io.appium.java_client.remote.options.CanSetCapability;
5+
import org.openqa.selenium.Capabilities;
6+
7+
import java.util.Optional;
8+
9+
import static io.appium.java_client.internal.CapabilityHelpers.toSafeBoolean;
10+
11+
public interface SupportsFlutterEnableMockCamera<T extends BaseOptions<T>> extends
12+
Capabilities, CanSetCapability<T> {
13+
String FLUTTER_ENABLE_MOCK_CAMERA_OPTION = "flutterEnableMockCamera";
14+
15+
/**
16+
* Sets the 'flutterEnableMockCamera' capability to the specified value.
17+
*
18+
* @param value the value to set for the 'flutterEnableMockCamera' capability
19+
* @return an instance of type {@code T} with the updated capability set
20+
*/
21+
default T setFlutterEnableMockCamera(boolean value) {
22+
return amend(FLUTTER_ENABLE_MOCK_CAMERA_OPTION, value);
23+
}
24+
25+
/**
26+
* Retrieves the current value of the 'flutterEnableMockCamera' capability, if available.
27+
*
28+
* @return an {@code Optional<Boolean>} containing the current value of the capability,
29+
*/
30+
default Optional<Boolean> doesFlutterEnableMockCamera() {
31+
return Optional.ofNullable(toSafeBoolean(getCapability(FLUTTER_ENABLE_MOCK_CAMERA_OPTION)));
32+
}
33+
}

0 commit comments

Comments
 (0)