Skip to content
Merged
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
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,19 +311,13 @@ Please replace them properly with your client.

This module extends the `appium-flutter-driver` with custom visibility-related commands using `driver.execute()`.

## ✅ Supported Commands
## ✅ Supported Shortcut Commands

| **Command** | **Status** | **Example Usage** | **Target** |
|---------------------|------------|--------------------------------------------------------------------------------------------------------------------------|------------|
| `assertVisible` | ✅ | `driver.execute('flutter:assertVisible', { key: 'myKey' })`<br>`driver.execute('flutter:assertVisible', { text: 'Login' })` | Widget |
| `assertNotVisible` | ✅ | `driver.execute('flutter:assertNotVisible', { key: 'hiddenWidget' })` | Widget |
| `assertTappable` | ✅ | `driver.execute('flutter:assertTappable', { label: 'Submit' })` | Widget |
| `tap` | ✅ | `driver.execute('flutter:tap', [{ key: 'submit_button' }])` | Widget |
| `click` | ✅ | `driver.execute('flutter:click', { text: 'Continue' })` | Widget |
`getText` | ✅ | `driver.execute('flutter:getText', { key: 'counterText' })` | Widget |
| `pageBack` | ✅ | `await driver.execute('flutter:pageBack')` | Navigation |
| `clear` | ✅ | `await driver.execute('flutter:clear', { key: 'emailInput' })` | Input Field |



## 🔍 Input Formats

Expand All @@ -343,7 +337,22 @@ These map to Flutter finders:
These commands are typically invoked using a client helper method like:

```ts
// TypeScript
await assertVisible(driver, { key: 'submit_button' });
```

Or as a flutter command.

```ruby
# ruby
driver.execute_script 'flutter:assertVisible', {text: 'Tap me!'}, 10000

# This is equivalent to
text_finder = by_text 'Tap me!'
driver.execute_script 'flutter:assertVisible', text_finder, 10000
# or
driver.execute_script 'flutter:waitFor', text_finder, 10000
```

**NOTE**
>`flutter:launchApp` launches an app via instrument service. `mobile:activateApp` and `driver.activate_app` are via XCTest API. They are a bit different.
Expand Down Expand Up @@ -393,13 +402,13 @@ This is a command extension for Flutter Driver, utilizing the [CommandExtension-
Available commands:

- `dragAndDropWithCommandExtension` – performs a drag-and-drop action on the screen by specifying the start and end coordinates and the action duration.
- `getTextWithCommandExtension` - get text data from Text widget that contains TextSpan widgets.
- `getTextWithCommandExtension` - get text data from Text widget that contains TextSpan widgets.

### How to use

Copy the sample dart files to the `lib` folder of your project. Please note that you don't need to copy all files, just copy the file matched with the command you need.
- dragAndDropWithCommandExtension: [drag_commands.dart](./example/dart/drag_commands.dart)
- getTextWithCommandExtension: [get_text_command.dart](./example/dart/get_text_command.dart)
- getTextWithCommandExtension: [get_text_command.dart](./example/dart/get_text_command.dart)

The entry point must include the `List<CommandExtension>?` commands argument in either `main.dart` or `test_main.dart` to properly handle the command extension.

Expand Down
430 changes: 1 addition & 429 deletions driver/README.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions driver/lib/commands/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ function getFinderBase64(input: FinderInput): string {
}

if ('key' in input) {
return serializeFinder(byValueKey(input.key));
return byValueKey(input.key);
}

if ('text' in input) {
return serializeFinder(byText(input.text));
return byText(input.text);
}

if ('label' in input) {
return serializeFinder(byTooltip(input.label));
return byTooltip(input.label);
}

throw new Error('Invalid finder input: must provide key, text, label, raw finder, or FlutterElement');
Expand Down
22 changes: 2 additions & 20 deletions driver/lib/commands/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import {
scroll,
scrollIntoView,
scrollUntilVisible,
scrollUntilTapable,
pageBack
scrollUntilTapable
} from './execute/scroll';
import {
waitFor,
waitForAbsent,
waitForTappable
} from './execute/wait';
import { clear, getText } from './element';
import { tap, click } from './gesture';
import {
assertVisible,
assertNotVisible,
Expand Down Expand Up @@ -135,7 +132,7 @@ const commandHandlers: CommandMap = {
await driver.socket!.executeSocketCommand({ command: 'enter_text', text }),
requestData: async (driver, message: string) =>
await driver.socket!.executeSocketCommand({ command: 'request_data', message }),
longTap: async (driver, finder: string, durationOrOptions?: LongTapOptions) =>
longTap: async (driver, finder: string, durationOrOptions: LongTapOptions) =>
await longTap(driver, finder, durationOrOptions),
waitForFirstFrame: async (driver) =>
await driver.executeElementCommand('waitForCondition', '', { conditionName: 'FirstFrameRasterizedCondition' }),
Expand All @@ -156,25 +153,10 @@ const commandHandlers: CommandMap = {
}),
assertVisible: async (driver, input: FinderInput, timeout = 5000) =>
await assertVisible(driver, input, timeout),

assertNotVisible: async (driver, input: FinderInput, timeout = 5000) =>
await assertNotVisible(driver, input, timeout),

assertTappable: async (driver, input: FinderInput, timeout = 5000) =>
await assertTappable(driver, input, timeout),

tap: async (driver, gestures: Record<string, any>[], longPress: boolean = false) =>
await tap.call(driver, gestures, longPress),

click: async (driver, input: FinderInput) =>
await click.call(driver, input),
getText: async (driver, element: string) =>
await getText.call(driver, element),
pageBack: async (driver) =>
await pageBack.call(driver),
clear: async (driver, input: FinderInput) =>
await clear.call(driver, input),

getTextWithCommandExtension: async (driver, params: { findBy: string }) =>
await driver.socket!.executeSocketCommand({
command: 'getTextWithCommandExtension',
Expand Down
20 changes: 3 additions & 17 deletions driver/lib/commands/execute/scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,12 @@ export const scroll = async (
export const longTap = async (
self: FlutterDriver,
elementBase64: string,
durationOrOptions: number | {
options: {
durationMilliseconds: number;
frequency?: number;
} = 1000, // Default to 1000ms if nothing provided
},
) => {
const options = typeof durationOrOptions === 'number'
? { durationMilliseconds: durationOrOptions }
: durationOrOptions;

const { durationMilliseconds, frequency = 60 } = options;
const { durationMilliseconds = 1000, frequency = 60 } = options;

if (typeof durationMilliseconds !== 'number' || typeof frequency !== 'number') {
throw new Error(`Invalid longTap options: ${JSON.stringify(options)}`);
Expand Down Expand Up @@ -204,13 +200,3 @@ export const scrollIntoView = async (

return await self.executeElementCommand(`scrollIntoView`, elementBase64, args);
};


export const pageBack = async function (this: FlutterDriver): Promise<void> {
if (this.proxydriver && typeof this.proxydriver.back === 'function') {
return await this.proxydriver.back(); // use platform driver
}

// fallback
throw new Error(`Back navigation is not supported on this platform.`);
};
6 changes: 5 additions & 1 deletion driver/lib/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ class FlutterDriver extends BaseDriver<FluttertDriverConstraints> {
return await this.proxydriver.terminateApp(appId);
}

public async back() {
return await this.proxydriver.back();
}

public async getOrientation(): Promise<string> {
switch (_.toLower(this.internalCaps.platformName)) {
case PLATFORM.IOS:
Expand Down Expand Up @@ -217,7 +221,7 @@ class FlutterDriver extends BaseDriver<FluttertDriverConstraints> {
} else if (cmd === `receiveAsyncResponse`) {
logger.debug(`Executing FlutterDriver response '${cmd}'`);
return await this.receiveAsyncResponse(...args);
} else if ([`setOrientation`, `getOrientation`].includes(cmd)) {
} else if ([`setOrientation`, `getOrientation`, `back`].includes(cmd)) {
// The `setOrientation` and `getOrientation` commands are handled differently
// for iOS and Android platforms. These commands are deferred to the base driver's
// implementation (`super.executeCommand`) to ensure compatibility with both platforms
Expand Down
9 changes: 7 additions & 2 deletions example/ruby/example_sample2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@ def test_run_example_android
@driver.context = 'FLUTTER'

text_finder = by_text 'Tap me!'

@driver.execute_script 'flutter:assertVisible', {text: 'Tap me!'}, 10000

element = ::Appium::Flutter::Element.new(@driver, finder: text_finder)
# @driver.execute_script('flutter:waitForTappable', text_finder, 1000)
# @driver.execute_script('flutter:waitForTappable', text_finder, 10000)

assert_equal 'Tap me!', element.text

element.click
element.click
@driver.execute_script 'flutter:clickElement', text_finder, {timeout:10000}

text_finder = by_text 'Taps: 2'
element = ::Appium::Flutter::Element.new(@driver, finder: text_finder)
Expand All @@ -75,5 +78,7 @@ def test_run_example_android
text_finder = by_text 'Tap me!'
element = ::Appium::Flutter::Element.new(@driver, finder: text_finder)
assert_equal 'Tap me!', element.text

@driver.back
end
end
8 changes: 0 additions & 8 deletions package.json

This file was deleted.

Loading