Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Teodor Nikolov authored and Teodor Nikolov committed Apr 3, 2024
0 parents commit 1bf5f69
Show file tree
Hide file tree
Showing 150 changed files with 23,621 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
lib
tet-results
npm-debug.log
.nyc
.env
.DS_Store
26 changes: 26 additions & 0 deletions @bellatrix/appium/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@bellatrix/appium",
"version": "0.0.1",
"type": "module",
"exports": {
"./core": "./src/core/index.ts",
"./android": "./src/android/index.ts",
"./ios": "./src/ios/index.ts",
"./windows": "./src/windows/index.ts",
"./common/commands": "./src/common/commands/index.ts",
"./core/contracts": "./src/core/contracts/index.ts",
"./common/http": "./src/common/http/index.ts",
"./types": "./src/types/index.ts",
"./common/http/contracts": "./src/common/http/contracts/index.ts"
},
"devDependencies": {
"@types/selenium-webdriver": "^4.1.21"
},
"dependencies": {
"@bellatrix/core": "file:../core",
"selenium-webdriver": "^4.16.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
21 changes: 21 additions & 0 deletions @bellatrix/appium/src/_usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AndroidDriver } from "@bellatrix/appium/android";

const capabilities = {
platformName: 'Android',
'appium:avd': 'android34',
'appium:app': '/Users/Teodor/Downloads/ApiDemos-debug.apk',
'appium:noReset': false,
'appium:automationName': 'UiAutomator2'
};

const driver = new AndroidDriver('http://127.0.0.1:4723/', capabilities);

async function myTest() {
await driver.createSession();
await driver.getBatteryInfo().then(console.log);
await driver.deleteSession();
}

myTest();


16 changes: 16 additions & 0 deletions @bellatrix/appium/src/_win_usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { WindowsDriver } from "./windows/WindowsDriver";

const capabilities = {
platformName: 'Windows',
'appium:automationName': 'Windows',
'appium:app': 'Root',
};

const driver = new WindowsDriver('http://172.27.80.1:4723/', capabilities);

async function myTest() {
await driver.createSession();
await driver.deleteSession();
}

myTest();
166 changes: 166 additions & 0 deletions @bellatrix/appium/src/android/AndroidDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Image } from '@bellatrix/core/image';
import { AndroidPermission } from '@bellatrix/appium/types';
import { AppiumDriver } from '@bellatrix/appium/core';
import { MobileCommands } from '@bellatrix/appium/common/commands';


type CreatedSessionResponse = {
capabilities: object;
sessionId: string;
}

type AndroidBatteryInfo = {
percentage: number;
state: BatteryState;
}

type BatteryState = 'Unknown' | 'Charging' | 'Discharging' | 'Not charging' | 'Full';

type EditorAction = 'normal' | 'unspecified' | 'none' | 'go' | 'search' | 'send' | 'next' | 'done' | 'previous';

type PermissionType = 'denied' | 'granted' | 'requested';

type SMSListResponse = {
items: SMS[];
total: number;
}

type SMS = {
id: string;
address: string;
person: string | null;
date: string;
read: string;
status: string;
type: string;
subject: string | null;
body: string;
serviceCenter: string | null;
}

export class AndroidDriver extends AppiumDriver {
private static readonly ANDROID_PLATOFRM = 'Android';

constructor(serverUrl: string, capabilities: object) {
super(serverUrl, AppiumDriver.validatePlatformName(capabilities, AndroidDriver.ANDROID_PLATOFRM));
}

async createSession(): Promise<void> {
const response = await this.commandExecutor.execute<CreatedSessionResponse>(MobileCommands.CREATE_SESSION, { capabilities: { alwaysMatch: this.capabilities } });
this.commandExecutor.setSessionId(response.sessionId);
}

async deleteSession(): Promise<void> {
const response = await this.commandExecutor.execute(MobileCommands.DELETE_SESSION)
this.commandExecutor.unsetSessionId();
}

async getContexts(): Promise<string[]> {
return this.commandExecutor.execute(MobileCommands.GET_CONTEXTS);
}

async getCurrentContext(): Promise<string> {
return this.commandExecutor.execute(MobileCommands.GET_CURRENT_CONTEXT);
}

async setContext(name: string): Promise<void> {
this.commandExecutor.execute(MobileCommands.SET_CONTEXT, { name });
}

async openNotifications(): Promise<void> {
this.commandExecutor.execute(MobileCommands.OPEN_NOTIFICATIONS);
}

async closeNotifications(): Promise<void> {
this.executeADBShellCommand('service call statusbar 2');
}

async toggleLocationServices(): Promise<void> {
this.commandExecutor.execute(MobileCommands.TOGGLE_LOCATION_SERVICES);
}

async getBatteryInfo(): Promise<AndroidBatteryInfo> {
const response: { level: number; state: number } = await this.execute('mobile: batteryInfo');
const percentage = response.level * 100;

const stateMap: Record<number, BatteryState> = {
2: 'Charging',
3: 'Discharging',
4: 'Not charging',
5: 'Full',
};

const state: BatteryState = stateMap[response.state] || 'Unknown';

return { percentage, state }
}

async execute<T>(script: string, args: object = {}): Promise<T> {
return this.commandExecutor.execute(MobileCommands.EXECUTE_SCRIPT, { script, args }) as T;
}

async executeADBShellCommand<T>(command: string, includeStderr?: boolean, timeout?: number): Promise<T>;
async executeADBShellCommand<T>(command: string, timeout?: number): Promise<T>;
async executeADBShellCommand<T>(command: string, includeStderrOrTimeout?: boolean | number, timeout?: number): Promise<T> {
let includeStderr: boolean | undefined;
if (typeof includeStderrOrTimeout === 'boolean') {
includeStderr = includeStderrOrTimeout;
} else {
timeout = includeStderrOrTimeout;
}

const tokens = command.split(/\s+/);
return this.execute('mobile: shell', { command: tokens[0], args: tokens.slice(1), includeStderr, timeout });
}

async performEditorAction(action: EditorAction) {
return this.execute('mobile: performEditorAction', { action })
}

async getPermissions(type?: PermissionType, appPackage?: string): Promise<AndroidPermission[]> {
return (await this.execute<string[]>('mobile: getPermissions', { type, appPackage }))
.map(permission => permission.replace('android.permission.', '')) as AndroidPermission[]
}

async grantPermissions(...permissions: AndroidPermission[]): Promise<void> {
return this.execute('mobile: changePermissions', {
action: 'grant', permissions: permissions
.map(permission => `android.permission.${permission}`)
})
}

async revokePermission(...permissions: AndroidPermission[]): Promise<void> {
return this.execute('mobile: changePermissions', {
action: 'revoke', permissions: permissions
.map(permission => `android.permission.${permission}`)
})
}

async getScreenshot(): Promise<Image> {
const base64image = await this.commandExecutor.execute<Promise<string>>(MobileCommands.SCREENSHOT);
return Image.fromBase64(base64image);
}

async getElementScreenshot(elementId: string) { // WIP:temp
this.commandExecutor.setParam('elementId', elementId);
const base64image = await this.commandExecutor.execute<Promise<string>>(MobileCommands.ELEMENT_SCREENSHOT);
return base64image;
}

async getActiveElement() { // WIP
return this.commandExecutor.execute(MobileCommands.GET_ACTIVE_ELEMENT);
}

async listSMS(): Promise<SMSListResponse> {
return this.execute('mobile: listSms');
}

async startRecordingScreen(): Promise<void> {
return this.commandExecutor.execute(MobileCommands.START_RECORDING_SCREEN);
}

async stopRecordingScreen() { // WIP
const base64video = this.commandExecutor.execute(MobileCommands.STOP_RECORDING_SCREEN);
return base64video;
}
}
1 change: 1 addition & 0 deletions @bellatrix/appium/src/android/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AndroidDriver';
148 changes: 148 additions & 0 deletions @bellatrix/appium/src/common/commands/MobileCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { CommandInfo } from '@bellatrix/appium/types';
import { CommandRepository } from '@bellatrix/appium/core/contracts';

export class MobileCommands {
static readonly CREATE_SESSION = 'createSession';
static readonly DELETE_SESSION = 'deleteSession';
static readonly GET_CONTEXTS = 'getContexts';
static readonly GET_CURRENT_CONTEXT = 'getCurrentContext';
static readonly SET_CONTEXT = 'setContext';
static readonly EXECUTE_SCRIPT = 'executeScript';
static readonly IMPLICIT_WAIT = 'implicitWait';
static readonly AVAILABLE_IME_ENGINES = 'availableIMEEngines';
static readonly ACTIVE_IME_ENGINE = 'getActiveIMEEngine';
static readonly IS_IME_ACTIVATED = 'isIMEActivated';
static readonly DEACTIVATE_IME_ENGINE = 'deactivateIMEEngine';
static readonly ACTIVATE_IME_ENGINE = 'activateIMEEngine';
static readonly GET_WINDOW_SIZE = 'getWindowSize';
static readonly KEYS = 'keys';
static readonly GET_LOCATION = 'getLocation';
static readonly GET_LOCATION_IN_VIEW = 'getLocationInView';
static readonly GET_SIZE = 'getSize';
static readonly TOUCH_CLICK = 'click';
static readonly TOUCH_DOWN = 'touchDown';
static readonly TOUCH_UP = 'touchUp';
static readonly TOUCH_MOVE = 'touchMove';
static readonly TOUCH_LONG_CLICK = 'touchLongClick';
static readonly TOUCH_FLICK = 'flick';
static readonly TOUCH_PERFORM = 'performTouch';
static readonly TOUCH_MULTI_PERFORM = 'performMultiAction';
static readonly DEVICE_LOCK = 'lock';
static readonly DEVICE_UNLOCK = 'unlock';
static readonly IS_DEVICE_LOCKED = 'isLocked';
static readonly START_RECORDING_SCREEN = 'startRecordingScreen';
static readonly STOP_RECORDING_SCREEN = 'stopRecordingScreen';
static readonly GET_PERFORMANCE_DATA_TYPES = 'getPerformanceDataTypes';
static readonly GET_PERFORMANCE_DATA = 'getPerformanceData';
static readonly PRESS_KEYCODE = 'pressKeyCode';
static readonly LONG_PRESS_KEYCODE = 'longPressKeyCode';
static readonly FINGERPRINT = 'fingerprint';
static readonly SEND_SMS = 'sendSMS';
static readonly GSM_CALL = 'gsmCall';
static readonly GSM_SIGNAL = 'gsmSignal';
static readonly GSM_VOICE = 'gsmVoice';
static readonly POWER_CAPACITY = 'powerCapacity';
static readonly POWER_AC = 'powerAC';
static readonly NETWORK_SPEED = 'networkSpeed';
static readonly KEYEVENT = 'keyevent';
static readonly GET_CURRENT_ACTIVITY = 'getCurrentActivity';
static readonly GET_CURRENT_PACKAGE = 'getCurrentPackage';
static readonly QUERY_APP_STATE = 'queryAppState';
static readonly TOGGLE_AIRPLANE_MODE = 'toggleFlightMode';
static readonly TOGGLE_DATA = 'toggleData';
static readonly TOGGLE_WIFI = 'toggleWiFi';
static readonly TOGGLE_LOCATION_SERVICES = 'toggleLocationServices';
static readonly OPEN_NOTIFICATIONS = 'openNotifications';
static readonly START_ACTIVITY = 'startActivity';
static readonly GET_SYSTEM_BARS = 'getSystemBars';
static readonly GET_DISPLAY_DENSITY = 'getDisplayDensity';
static readonly LAUNCH_APP = 'launchApp';
static readonly CLOSE_APP = 'closeApp';
static readonly RESET_APP = 'reset';
static readonly BACKGROUND_APP = 'background';
static readonly GET_STRINGS = 'getStrings';
static readonly SET_VALUE_IMMEDIATE = 'setValueImmediate';
static readonly REPLACE_VALUE = 'replaceValue';
static readonly SCREENSHOT = 'screenshot';
static readonly ELEMENT_SCREENSHOT = 'elementScreenshot'
static readonly GET_ACTIVE_ELEMENT = 'getActiveElement'

public static commandRepository: CommandRepository = new Map([
[this.CREATE_SESSION, this.postCommand('/session')],
[this.DELETE_SESSION, this.deleteCommand('/session/:sessionId')],
[this.GET_CONTEXTS, this.getCommand('/session/:sessionId/contexts')],
[this.GET_CURRENT_CONTEXT, this.getCommand('/session/:sessionId/context')],
[this.SET_CONTEXT, this.postCommand('/session/:sessionId/context')],
[this.EXECUTE_SCRIPT, this.postCommand('/session/:sessionId/execute')],
[this.IMPLICIT_WAIT, this.postCommand('/session/:sessionId/timeouts/implicit_wait')],
[this.AVAILABLE_IME_ENGINES, this.getCommand('/session/:sessionId/ime/available_engines')],
[this.ACTIVE_IME_ENGINE, this.getCommand('/session/:sessionId/ime/active_engine')],
[this.IS_IME_ACTIVATED, this.getCommand('/session/:sessionId/ime/activated')],
[this.DEACTIVATE_IME_ENGINE, this.postCommand('/session/:sessionId/ime/deactivate')],
[this.ACTIVATE_IME_ENGINE, this.postCommand('/session/:sessionId/ime/activate')],
[this.GET_WINDOW_SIZE, this.getCommand('/session/:sessionId/window/:windowhandle/size')],
[this.KEYS, this.postCommand('/session/:sessionId/keys')],
[this.GET_LOCATION, this.getCommand('/session/:sessionId/element/:elementId/location')],
[this.GET_LOCATION_IN_VIEW, this.getCommand('/session/:sessionId/element/:elementId/location_in_view')],
[this.GET_SIZE, this.getCommand('/session/:sessionId/element/:elementId/size')],
[this.TOUCH_CLICK, this.postCommand('/session/:sessionId/touch/click')],
[this.TOUCH_DOWN, this.postCommand('/session/:sessionId/touch/down')],
[this.TOUCH_UP, this.postCommand('/session/:sessionId/touch/up')],
[this.TOUCH_MOVE, this.postCommand('/session/:sessionId/touch/move')],
[this.TOUCH_LONG_CLICK, this.postCommand('/session/:sessionId/touch/longclick')],
[this.TOUCH_FLICK, this.postCommand('/session/:sessionId/touch/flick')],
[this.TOUCH_PERFORM, this.postCommand('/session/:sessionId/touch/perform')],
[this.TOUCH_MULTI_PERFORM, this.postCommand('/session/:sessionId/touch/multi/perform')],
[this.DEVICE_LOCK, this.postCommand('/session/:sessionId/appium/device/lock')],
[this.DEVICE_UNLOCK, this.postCommand('/session/:sessionId/appium/device/unlock')],
[this.IS_DEVICE_LOCKED, this.postCommand('/session/:sessionId/appium/device/is_locked')],
[this.START_RECORDING_SCREEN, this.postCommand('/session/:sessionId/appium/start_recording_screen')],
[this.STOP_RECORDING_SCREEN, this.postCommand('/session/:sessionId/appium/stop_recording_screen')],
[this.GET_PERFORMANCE_DATA_TYPES, this.postCommand('/session/:sessionId/appium/performanceData/types')],
[this.GET_PERFORMANCE_DATA, this.postCommand('/session/:sessionId/appium/getPerformanceData')],
[this.PRESS_KEYCODE, this.postCommand('/session/:sessionId/appium/device/press_keycode')],
[this.LONG_PRESS_KEYCODE, this.postCommand('/session/:sessionId/appium/device/long_press_keycode')],
[this.FINGERPRINT, this.postCommand('/session/:sessionId/appium/device/finger_print')],
[this.SEND_SMS, this.postCommand('/session/:sessionId/appium/device/send_sms')],
[this.GSM_CALL, this.postCommand('/session/:sessionId/appium/device/gsm_call')],
[this.GSM_SIGNAL, this.postCommand('/session/:sessionId/appium/device/gsm_signal')],
[this.GSM_VOICE, this.postCommand('/session/:sessionId/appium/device/gsm_voice')],
[this.POWER_CAPACITY, this.postCommand('/session/:sessionId/appium/device/power_capacity')],
[this.POWER_AC, this.postCommand('/session/:sessionId/appium/device/power_ac')],
[this.NETWORK_SPEED, this.postCommand('/session/:sessionId/appium/device/network_speed')],
[this.KEYEVENT, this.postCommand('/session/:sessionId/appium/device/keyevent')],
[this.GET_CURRENT_ACTIVITY, this.getCommand('/session/:sessionId/appium/device/current_activity')],
[this.GET_CURRENT_PACKAGE, this.getCommand('/session/:sessionId/appium/device/current_package')],
[this.QUERY_APP_STATE, this.postCommand('/session/:sessionId/appium/device/app_state')],
[this.TOGGLE_AIRPLANE_MODE, this.postCommand('/session/:sessionId/appium/device/toggle_airplane_mode')],
[this.TOGGLE_DATA, this.postCommand('/session/:sessionId/appium/device/toggle_data')],
[this.TOGGLE_WIFI, this.postCommand('/session/:sessionId/appium/device/toggle_wifi')],
[this.TOGGLE_LOCATION_SERVICES, this.postCommand('/session/:sessionId/appium/device/toggle_location_services')],
[this.OPEN_NOTIFICATIONS, this.postCommand('/session/:sessionId/appium/device/open_notifications')],
[this.START_ACTIVITY, this.postCommand('/session/:sessionId/appium/device/start_activity')],
[this.GET_SYSTEM_BARS, this.getCommand('/session/:sessionId/appium/device/system_bars')],
[this.GET_DISPLAY_DENSITY, this.getCommand('/session/:sessionId/appium/device/display_density')],
[this.LAUNCH_APP, this.postCommand('/session/:sessionId/appium/app/launch')],
[this.CLOSE_APP, this.postCommand('/session/:sessionId/appium/app/close')],
[this.RESET_APP, this.postCommand('/session/:sessionId/appium/app/reset')],
[this.BACKGROUND_APP, this.postCommand('/session/:sessionId/appium/app/background')],
[this.GET_STRINGS, this.postCommand('/session/:sessionId/appium/app/strings')],
[this.SET_VALUE_IMMEDIATE, this.postCommand('/session/:sessionId/appium/element/:elementId/value')],
[this.REPLACE_VALUE, this.postCommand('/session/:sessionId/appium/element/:elementId/replace_value')],
[this.SCREENSHOT, this.getCommand('/session/:sessionId/screenshot')],
[this.ELEMENT_SCREENSHOT, this.getCommand('/session/:sessionId/element/:elementId/screenshot')],
[this.GET_ACTIVE_ELEMENT, this.getCommand('/session/:sessionId/element/active')],
]);

private static postCommand(path: string): CommandInfo {
return { method: 'POST', path };
}

private static getCommand(path: string): CommandInfo {
return { method: 'GET', path };
}

private static deleteCommand(path: string): CommandInfo {
return { method: 'DELETE', path };
}
}
Loading

0 comments on commit 1bf5f69

Please sign in to comment.