Skip to content

Commit

Permalink
improve port discovery of flutter server (#6)
Browse files Browse the repository at this point in the history
* Improve the port finding logic for flutter server
* reduce connectionRetryCount
* Fix port remove in delete session
---------

Co-authored-by: Sai Krishna <saikrishna321@yahoo.com>
  • Loading branch information
sudharsan-selvaraj and saikrishna321 authored Jun 14, 2024
1 parent f63f014 commit 082f148
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 193 deletions.
110 changes: 58 additions & 52 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
dart pub global activate rps --version 0.8.0
flutter build apk --debug
cd android && ./gradlew app:assembleDebug -Ptarget=`pwd`/../integration_test/appium.dart
- name: "List files"
- name: 'List files'
continue-on-error: true
run: |
ls -l ${{ github.workspace }}/server/demo-app/build/app/outputs/apk/debug/
Expand All @@ -48,54 +48,60 @@ jobs:
name: Android-build
path: ${{ github.workspace }}/server/demo-app/build/app/outputs/apk/debug/app-debug.apk
Run_Tests:
needs: Build_Server
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [ 29 ]
target: [ google_apis ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- uses: dawidd6/action-download-artifact@v3
with:
name: Android-build
- name: Display structure of downloaded files
run: ls -R
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.19.6
channel: 'stable'
- name: Enable KVM group perms
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: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: |
adb devices
node --version
npm install -g wait-on
npm install -g appium
appium driver install uiautomator2
npm install
npm run build
appium driver list
npm run wdio-android
# appium server -pa=/wd/hub & wait-on http://127.0.0.1:4723/wd/hub/status &&



needs: Build_Server
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [29]
target: [google_apis]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'adopt'
- uses: dawidd6/action-download-artifact@v3
with:
name: Android-build
- name: Display structure of downloaded files
run: ls -R
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.19.6
channel: 'stable'
- name: Enable KVM group perms
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: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: x86_64
profile: Nexus 6
script: |
adb devices
node --version
npm install -g wait-on
npm install -g appium
appium driver install uiautomator2
npm install
npm run build
appium driver list
mkdir ${{ github.workspace }}/appium-logs
adb logcat > ${{ github.workspace }}/appium-logs/flutter.txt &
npm run wdio-android
adb logcat -t 1000 | grep "flutter"
# appium server -pa=/wd/hub & wait-on http://127.0.0.1:4723/wd/hub/status &&
- name: upload appium logs
if: always()
uses: actions/upload-artifact@v4
with:
name: appium-logs
path: ${{ github.workspace }}/appium-logs
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,5 @@ dist
.yarn/install-state.gz
.pnp.*
.history
.npmrc
.npmrc
appium-logs
39 changes: 15 additions & 24 deletions src/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { log } from './logger';
import type { InitialOpts } from '@appium/types';
import { AppiumFlutterDriver } from './driver';
import ADB from 'appium-adb';
import { sleep } from 'asyncbox';
import { fetchFlutterServerPort, getFreePort } from './utils';

const setupNewAndroidDriver = async (
...args: any[]
Expand All @@ -19,34 +17,27 @@ export const startAndroidSession = async (
flutterDriver: AppiumFlutterDriver,
caps: Record<string, any>,
...args: any[]
): Promise<[AndroidUiautomator2Driver, null]> => {
): Promise<AndroidUiautomator2Driver> => {
log.info(`Starting an Android proxy session`);
const androiddriver = await setupNewAndroidDriver(...args);
log.info('Looking for the port in where Flutter server is listening too...');
await sleep(2000);
const flutterServerPort = fetchFlutterServerPort(
(androiddriver.adb.logcat?.logs as [{ message: string }]) || [],
);
caps.flutterServerPort = await portForward(
caps.udid,
flutterServerPort,
caps.flutterServerPort,
);
return [androiddriver, caps.flutterServerPort];
return androiddriver;
};

const portForward = async (
export async function androidPortForward(
udid: string,
systemPort: number,
devicePort: number,
systemPort?: number,
) => {
if (!systemPort) {
systemPort = await getFreePort();
}
) {
let adb = new ADB();
if (udid) adb.setDeviceId(udid);
await adb.forwardPort(systemPort!, devicePort);
const adbForwardList = await adb.getForwardList();
log.info(`Port forwarding: ${JSON.stringify(adbForwardList)}`);
return systemPort;
};
}

export async function androidRemovePortForward(
udid: string,
systemPort: number,
) {
let adb = new ADB();
if (udid) adb.setDeviceId(udid);
await adb.removePortForward(systemPort);
}
85 changes: 54 additions & 31 deletions src/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ import {
ELEMENT_CACHE,
} from './commands/element';

import { isFlutterDriverCommand } from './utils';
import {
fetchFlutterServerPort,
getFreePort,
isFlutterDriverCommand,
} from './utils';
import { W3C_WEB_ELEMENT_IDENTIFIER } from '@appium/support/build/lib/util';
import { sleep, waitForCondition } from 'asyncbox';
import { log } from './logger';

const DEFAULT_FLUTTER_SERVER_PORT = 8888;
import { androidPortForward, androidRemovePortForward } from './android';
import { iosPortForward, iosRemovePortForward } from './iOS';
import { sleep } from 'asyncbox';

export class AppiumFlutterDriver extends BaseDriver<FlutterDriverConstraints> {
// @ts-ignore
Expand Down Expand Up @@ -60,7 +63,7 @@ export class AppiumFlutterDriver extends BaseDriver<FlutterDriverConstraints> {
'class name',
'semantics label',
'text',
'type'
'type',
];
}

Expand Down Expand Up @@ -152,7 +155,7 @@ export class AppiumFlutterDriver extends BaseDriver<FlutterDriverConstraints> {
'POST',
{
origin,
offset
offset,
},
);
}
Expand Down Expand Up @@ -220,35 +223,52 @@ export class AppiumFlutterDriver extends BaseDriver<FlutterDriverConstraints> {
caps,
...JSON.parse(JSON.stringify(args)),
);
const packageName =
this.proxydriver instanceof AndroidUiautomator2Driver
? this.proxydriver.opts.appPackage!
: this.proxydriver.opts.bundleId!;

let portcallbacks = {};
if (this.proxydriver instanceof AndroidUiautomator2Driver) {
portcallbacks = {
portForwardCallback: androidPortForward,
portReleaseCallback: androidRemovePortForward,
};
} else if (this.proxydriver.isRealDevice()) {
portcallbacks = {
portForwardCallback: iosPortForward,
portReleaseCallback: iosRemovePortForward,
};
}

const systemPort =
this.proxydriver instanceof XCUITestDriver &&
!this.proxydriver.isRealDevice()
? null
: await getFreePort();

const udid = this.proxydriver.opts.udid!;

this.flutterPort = await fetchFlutterServerPort({
udid,
packageName,
...portcallbacks,
systemPort,
});

if (!this.flutterPort) {
throw new Error(
`Flutter server is not started. ` +
`Please make sure the application under test is configured properly according to ` +
`https://github.com/AppiumTestDistribution/appium-flutter-server and that it does not crash on startup.`,
);
}

// HACK for eliminatin socket hang up by waiting 1 sec
await sleep(1000);
// @ts-ignore
console.log('PageSource', await this.proxydriver.getPageSource());
// @ts-ignore
this.proxy = new JWProxy({
server: '127.0.0.1',
port: this.flutterPort,
});
try {
await waitForCondition(async () => {
try {
// @ts-ignore
await this.proxy.command('/status', 'GET');
return true;
} catch(err: any) {
log.info('FlutterServer not reachable, Trying..', err);
return false;
}
}, {
waitMs: 15000,
intervalMs: 1000,
})
} catch(err: any) {
log.error('FlutterServer not reachable', err);
throw new Error(err);
}


await this.proxy.command('/session', 'POST', { capabilities: caps });
return sessionCreated;
Expand All @@ -259,7 +279,10 @@ export class AppiumFlutterDriver extends BaseDriver<FlutterDriverConstraints> {
}

async deleteSession() {
if (this.proxydriver instanceof AndroidUiautomator2Driver) {
if (
this.proxydriver instanceof AndroidUiautomator2Driver &&
this.flutterPort
) {
// @ts-ignore
await this.proxydriver.adb.removePortForward(this.flutterPort);
}
Expand Down
51 changes: 18 additions & 33 deletions src/iOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,38 @@ import XCUITestDriver from 'appium-xcuitest-driver';
import type { InitialOpts } from '@appium/types';
import { log } from './logger';
import { DEVICE_CONNECTIONS_FACTORY } from './iProxy';
import { sleep } from 'asyncbox';
import { fetchFlutterServerPort, getFreePort } from './utils';

const setupNewIOSDriver = async (...args: any[]): Promise<XCUITestDriver> => {
const iosdriver = new XCUITestDriver({} as InitialOpts);
await iosdriver.createSession(...args);
return iosdriver;
};

const portForward = async (
export const startIOSSession = async (
flutterDriver: AppiumFlutterDriver,
caps: Record<string, any>,
...args: any[]
): Promise<XCUITestDriver> => {
log.info(`Starting an IOS proxy session`);
const iOSDriver = await setupNewIOSDriver(...args);
log.info('iOS session started', iOSDriver);
return iOSDriver;
};

export async function iosPortForward(
udid: string,
systemPort: number,
devicePort: any,
) => {
if (!systemPort) {
systemPort = await getFreePort();
}
devicePort: number,
) {
log.info(
`Forwarding port ${systemPort} to device port ${devicePort} ${udid}`,
);
await DEVICE_CONNECTIONS_FACTORY.requestConnection(udid, systemPort, {
usePortForwarding: true,
devicePort: devicePort,
});
return systemPort;
};
}

export const startIOSSession = async (
flutterDriver: AppiumFlutterDriver,
caps: Record<string, any>,
...args: any[]
): Promise<[XCUITestDriver, number]> => {
log.info(`Starting an IOS proxy session`);
const iOSDriver = await setupNewIOSDriver(...args);
log.info('Looking for port Flutter server is listening too...');
await sleep(2000);
const flutterServerPort = fetchFlutterServerPort(iOSDriver.logs.syslog.logs);
if (iOSDriver.isRealDevice()) {
caps.flutterServerPort = await portForward(
iOSDriver.udid,
caps.flutterServerPort,
flutterServerPort,
);
} else {
//incase of emulator use the same port where the flutter server is running
caps.flutterServerPort = flutterServerPort;
}
log.info('iOS session started', iOSDriver);
return [iOSDriver, caps.flutterServerPort];
};
export function iosRemovePortForward(udid: string, systemPort: number) {
DEVICE_CONNECTIONS_FACTORY.releaseConnection(udid, systemPort);
}
Loading

0 comments on commit 082f148

Please # to comment.