Skip to content

feat: implement pinch, pan, rotate and scroll #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 19, 2019
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
37 changes: 26 additions & 11 deletions lib/appium-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
encodeImageToBase64,
ensureReportsDirExists,
checkImageLogType,
adbShellCommand
adbShellCommand,
logWarn
} from "./utils";

import { INsCapabilities } from "./interfaces/ns-capabilities";
Expand Down Expand Up @@ -167,6 +168,11 @@ export class AppiumDriver {
}

public async navBack() {
if (this.isAndroid) {
logInfo("=== Navigate back with hardware button!");
} else {
logInfo("=== Navigate back.");
}
return await this._driver.back();
}

Expand Down Expand Up @@ -244,7 +250,7 @@ export class AppiumDriver {
prepareApp(args);
if (!args.device) {
if (args.isAndroid) {
args.device = DeviceManager.getDefaultDevice(args, sessionInfo.capabilities.desired.deviceName, sessionInfo.capabilities.deviceUDID.replace("emulator-", ""), sessionInfo.capabilities.deviceUDID.includes("emulator") ? DeviceType.EMULATOR : DeviceType.SIMULATOR, sessionInfo.capabilities.desired.platformVersion || sessionInfo.capabilities.platformVersion);
args.device = DeviceManager.getDefaultDevice(args, sessionInfo.capabilities.desired.deviceName, sessionInfo.capabilities.deviceUDID.replace("emulator-", ""), sessionInfo.capabilities.deviceUDID.includes("emulator") ? DeviceType.EMULATOR : DeviceType.SIMULATOR, sessionInfo.capabilities.deviceApiLevel || sessionInfo.capabilities.platformVersion);
} else {
args.device = DeviceManager.getDefaultDevice(args);
}
Expand All @@ -257,6 +263,8 @@ export class AppiumDriver {
}
} catch (error) {
args.verbose = true;
console.log("===============================");
console.log("", error)
if (!args.ignoreDeviceController && error && error.message && error.message.includes("Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE]")) {
await DeviceManager.kill(args.device);
await DeviceController.startDevice(args.device);
Expand All @@ -280,11 +288,11 @@ export class AppiumDriver {
console.log("Retry launching appium driver!");
hasStarted = false;

if (error && error.message && error.message.includes("WebDriverAgent")) {
const freePort = await findFreePort(100, args.wdaLocalPort);
console.log("args.appiumCaps['wdaLocalPort']", freePort);
args.appiumCaps["wdaLocalPort"] = freePort;
}
// if (error && error.message && error.message.includes("WebDriverAgent")) {
// const freePort = await findFreePort(100, args.wdaLocalPort);
// console.log("args.appiumCaps['wdaLocalPort']", freePort);
// args.appiumCaps["wdaLocalPort"] = freePort;
// }
}

if (hasStarted) {
Expand Down Expand Up @@ -491,7 +499,7 @@ export class AppiumDriver {
* @param xOffset
*/
public async scroll(direction: Direction, y: number, x: number, yOffset: number, xOffset: number = 0) {
await scroll(this._wd, this._driver, direction, this._webio.isIOS, y, x, yOffset, xOffset, this._args.verbose);
await scroll(this._wd, this._driver, direction, this._webio.isIOS, y, x, yOffset, xOffset);
}

/**
Expand All @@ -509,13 +517,15 @@ export class AppiumDriver {
while ((el === null || !isDisplayed) && retryCount > 0) {
try {
el = await element();
isDisplayed = await el.isDisplayed();
isDisplayed = el && await el.isDisplayed();
if (!isDisplayed) {
await scroll(this._wd, this._driver, direction, this._webio.isIOS, startPoint.y, startPoint.x, offsetPoint.x, offsetPoint.y, this._args.verbose);
await scroll(this._wd, this._driver, direction, this._webio.isIOS, startPoint.y, startPoint.x, offsetPoint.y, offsetPoint.x);
el = null;
}
} catch (error) {
console.log("scrollTo Error: " + error);
await scroll(this._wd, this._driver, direction, this._webio.isIOS, startPoint.y, startPoint.x, offsetPoint.y, offsetPoint.x);
el = null;
}

retryCount--;
Expand Down Expand Up @@ -864,7 +874,7 @@ export class AppiumDriver {
}
}

private static async applyAdditionalSettings(args) {
private static async applyAdditionalSettings(args: INsCapabilities) {
if (args.isSauceLab) return;

args.appiumCaps['udid'] = args.appiumCaps['udid'] || args.device.token;
Expand All @@ -881,6 +891,11 @@ export class AppiumDriver {
args.appiumCaps["wdaStartupRetries"] = 5;
args.appiumCaps["shouldUseSingletonTestManager"] = args.appiumCaps.shouldUseSingletonTestManager;

if (args.derivedDataPath) {
args.appiumCaps["derivedDataPath"] = `${args.derivedDataPath}/${args.device.token}`;
logWarn('Changed derivedDataPath to: ', args.appiumCaps["derivedDataPath"]);
}

// It looks we need it for XCTest (iOS 10+ automation)
if (args.appiumCaps.platformVersion >= 10 && args.wdaLocalPort) {
console.log(`args.appiumCaps['wdaLocalPort']: ${args.wdaLocalPort}`);
Expand Down
2 changes: 1 addition & 1 deletion lib/appium-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class AppiumServer {

private startAppiumServer(logLevel: string, isSauceLab: boolean) {
const startingServerArgs: Array<string> = isSauceLab ? ["--log-level", logLevel] : ["-p", this.port.toString(), "--log-level", logLevel];
if (this._args.isAndroid && this._args.ignoreDeviceController && !this._args.isSauceLab) {
if (this._args.isAndroid) {
this._args.relaxedSecurity ? startingServerArgs.push("--relaxed-security") : console.log("'relaxedSecurity' is not enabled!\nTo enabled it use '--relaxedSecurity'!");
}

Expand Down
11 changes: 6 additions & 5 deletions lib/device-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export class DeviceManager implements IDeviceManager {
type: type,
platform: args.appiumCaps.platformName.toLowerCase(),
token: token,
apiLevel: platformVersion || args.appiumCaps.platformVersion,
apiLevel: platformVersion || args.appiumCaps.deviceApiLevel || args.appiumCaps.platformVersion,
config: { "density": args.appiumCaps.density, "offsetPixels": args.appiumCaps.offsetPixels }
}

Expand All @@ -197,7 +197,7 @@ export class DeviceManager implements IDeviceManager {
const sizeArr = sessionInfoDetails.deviceScreenSize.split("x");
args.device.deviceScreenSize = { width: sizeArr[0], height: sizeArr[1] };

args.device.apiLevel = sessionInfoDetails.deviceApiLevel;
args.device.apiLevel = sessionInfoDetails.deviceApiLevel || args.device.apiLevel;
args.device.deviceScreenDensity = sessionInfoDetails.deviceScreenDensity / 100;
args.device.config = { "density": args.device.deviceScreenDensity || args.device.config.density, "offsetPixels": +sessionInfoDetails.statBarHeight || args.device.config.offsetPixels };
} else {
Expand All @@ -209,6 +209,7 @@ export class DeviceManager implements IDeviceManager {

args.device.statBarHeight = sessionInfoDetails.statBarHeight;
args.device.viewportRect = DeviceManager.convertViewportRectToIRectangle(sessionInfoDetails.viewportRect);
args.device.token = args.device.token || sessionInfoDetails.udid;

return args.device;
}
Expand All @@ -217,14 +218,14 @@ export class DeviceManager implements IDeviceManager {
const status = value ? 1 : 0;
try {
if (args.isAndroid) {
if (!args.ignoreDeviceController) {
AndroidController.setDontKeepActivities(value, args.device);
} else if (args.relaxedSecurity) {
if (args.relaxedSecurity) {
const output = await DeviceManager.executeShellCommand(driver, { command: "settings", args: ['put', 'global', 'always_finish_activities', status] });
console.log(`Output from setting always_finish_activities to ${status}: ${output}`);
//check if set
const check = await DeviceManager.executeShellCommand(driver, { command: "settings", args: ['get', 'global', 'always_finish_activities'] });
console.info(`Check if always_finish_activities is set correctly: ${check}`);
} else if (!args.ignoreDeviceController) {
AndroidController.setDontKeepActivities(value, args.device);
}
} else {
// Do nothing for iOS ...
Expand Down
2 changes: 1 addition & 1 deletion lib/direction.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export declare const enum Direction {
export declare enum Direction {
down = 0,
up = 1,
left = 2,
Expand Down
2 changes: 1 addition & 1 deletion lib/direction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const enum Direction {
export enum Direction {
down,
up,
left,
Expand Down
1 change: 1 addition & 0 deletions lib/interfaces/ns-capabilities-args.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AutomationName } from "../automation-name";
import { ITestReporter } from "./test-reporter";
import { LogImageType } from "../enums/log-image-type";
export interface INsCapabilitiesArgs {
derivedDataPath?: string;
port?: number;
wdaLocalPort?: number;
projectDir?: string;
Expand Down
1 change: 1 addition & 0 deletions lib/interfaces/ns-capabilities-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ITestReporter } from "./test-reporter";
import { LogImageType } from "../enums/log-image-type";

export interface INsCapabilitiesArgs {
derivedDataPath?: string;
port?: number;
wdaLocalPort?: number;
projectDir?: string;
Expand Down
1 change: 1 addition & 0 deletions lib/ns-capabilities.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export declare class NsCapabilities implements INsCapabilities {
deviceTypeOrPlatform: string;
driverConfig: any;
logImageTypes: Array<LogImageType>;
derivedDataPath: string;
constructor(_parser: INsCapabilitiesArgs);
readonly isAndroid: any;
readonly isIOS: boolean;
Expand Down
2 changes: 2 additions & 0 deletions lib/ns-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class NsCapabilities implements INsCapabilities {
public deviceTypeOrPlatform: string;
public driverConfig: any;
public logImageTypes: Array<LogImageType>;
public derivedDataPath: string;

constructor(private _parser: INsCapabilitiesArgs) {
this.projectDir = this._parser.projectDir;
Expand All @@ -76,6 +77,7 @@ export class NsCapabilities implements INsCapabilities {
this.isSauceLab = this._parser.isSauceLab;
this.ignoreDeviceController = this._parser.ignoreDeviceController;
this.wdaLocalPort = this._parser.wdaLocalPort;
this.derivedDataPath = this._parser.derivedDataPath;
this.path = this._parser.path;
this.capabilitiesName = this._parser.capabilitiesName;
this.imagesPath = this._parser.imagesPath;
Expand Down
2 changes: 1 addition & 1 deletion lib/parser.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { LogImageType } from "./enums/log-image-type";
export declare const projectDir: string, projectBinary: string, pluginRoot: string, pluginBinary: string, port: number, verbose: boolean, appiumCapsLocation: string, testFolder: string, runType: string, isSauceLab: boolean, appPath: string, storage: string, testReports: string, devMode: boolean, ignoreDeviceController: boolean, wdaLocalPort: number, path: string, relaxedSecurity: boolean, cleanApp: boolean, attachToDebug: boolean, sessionId: string, startSession: boolean, capabilitiesName: string, imagesPath: string, startDeviceOptions: string, deviceTypeOrPlatform: string, device: any, driverConfig: any, logImageTypes: LogImageType[], appiumCaps: any;
export declare const projectDir: string, projectBinary: string, pluginRoot: string, pluginBinary: string, port: number, verbose: boolean, appiumCapsLocation: string, testFolder: string, runType: string, isSauceLab: boolean, appPath: string, storage: string, testReports: string, devMode: boolean, ignoreDeviceController: boolean, wdaLocalPort: number, derivedDataPath: string, path: string, relaxedSecurity: boolean, cleanApp: boolean, attachToDebug: boolean, sessionId: string, startSession: boolean, capabilitiesName: string, imagesPath: string, startDeviceOptions: string, deviceTypeOrPlatform: string, device: any, driverConfig: any, logImageTypes: LogImageType[], appiumCaps: any;
5 changes: 5 additions & 0 deletions lib/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ const config = (() => {
type: "string"
})
.option("wdaLocalPort", { alias: "wda", describe: "WDA port", type: "number" })
.options("derivedDataPath", {
describe: "set the unique derived data path root for each driver instance. This will help to avoid possible conflicts and to speed up the parallel execution",
type: "string" })
.option("verbose", { alias: "v", describe: "Log actions", type: "boolean" })
.option("path", { describe: "Execution path", default: process.cwd(), type: "string" })
.option("relaxedSecurity", { describe: "appium relaxedSecurity", default: false, type: "boolean" })
Expand Down Expand Up @@ -160,6 +163,7 @@ const config = (() => {
pluginRoot: pluginRoot,
pluginBinary: pluginBinary,
wdaLocalPort: options.wdaLocalPort || process.env.npm_config_wdaLocalPort || process.env["WDA_LOCAL_PORT"] || 8410,
derivedDataPath: options.derivedDataPath || process.env.npm_config_derivedDataPath || process.env["DERIVED_DATA_PATH"],
testFolder: options.testFolder || process.env.npm_config_testFolder || "e2e",
runType: options.runType || process.env.npm_config_runType,
appiumCapsLocation: options.appiumCapsLocation || process.env.npm_config_appiumCapsLocation || join(projectDir, options.testFolder, "config", options.capabilitiesName),
Expand Down Expand Up @@ -208,6 +212,7 @@ export const {
devMode,
ignoreDeviceController,
wdaLocalPort,
derivedDataPath,
path,
relaxedSecurity,
cleanApp,
Expand Down
69 changes: 60 additions & 9 deletions lib/ui-element.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export declare class UIElement {
* Click on element
*/
click(): Promise<any>;
getCenter(): Promise<{
x: number;
y: number;
}>;
tapCenter(): Promise<void>;
tapAtTheEnd(): Promise<void>;
/**
Expand All @@ -26,17 +30,25 @@ export declare class UIElement {
*/
tap(): Promise<any>;
/**
* @experimental
* Double tap on element
*/
doubleTap(): Promise<any>;
doubleTap(offset?: {
x: number;
y: number;
}): Promise<any>;
longPress(duration: number): Promise<void>;
/**
* Get location of element
*/
location(): Promise<Point>;
/**
* Get size of element
*/
size(): Promise<Point>;
size(): Promise<{
width: number;
height: number;
}>;
/**
* Get text of element
*/
Expand Down Expand Up @@ -113,13 +125,6 @@ export declare class UIElement {
*/
scrollTo(direction: Direction, elementToSearch: () => Promise<UIElement>, yOffset?: number, xOffset?: number, retries?: number): Promise<UIElement>;
/**
* Drag element with specific offset
* @param direction
* @param yOffset
* @param xOffset - default value 0
*/
drag(direction: Direction, yOffset: number, xOffset?: number): Promise<void>;
/**
* Click and hold over an element
* @param time in milliseconds to increase the default press period.
*/
Expand Down Expand Up @@ -165,4 +170,50 @@ export declare class UIElement {
* @param direction
*/
swipe(direction: Direction): Promise<void>;
/**
* Drag element with specific offset
* @experimental
* @param direction
* @param yOffset
* @param xOffset - default value 0
*/
drag(direction: Direction, yOffset: number, xOffset?: number, duration?: number): Promise<void>;
/**
*@experimental
* Pan element with specific offset
* @param offsets where the finger should move to.
* @param initPointOffset element.getRectangle() is used as start point. In case some additional offset should be provided use this param.
*/
pan(offsets: {
x: number;
y: number;
}[], initPointOffset?: {
x: number;
y: number;
}): Promise<void>;
/**
* @experimental
* This method will try to move two fingers from opposite corners.
* One finger starts from
* { x: elementRect.x + offset.x, y: elementRect.y + offset.y }
* and ends to
* { x: elementRect.x + elementRect.width - offset.x, y: elementRect.height + elementRect.y - offset.y }
* and the other finger starts from
* { x: elementRect.width + elementRect.x - offset.x, y: elementRect.height + elementRect.y - offset.y }
* and ends to
* { x: elementRect.x + offset.x, y: elementRect.y + offset.y }
*/
rotate(offset?: {
x: number;
y: number;
}): Promise<void>;
/**
* @experimental
* @param zoomFactory - zoomIn or zoomOut. Only zoomIn action is implemented
* @param offset
*/
pinch(zoomType: "in" | "out", offset?: {
x: number;
y: number;
}): Promise<void>;
}
Loading