diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01c8c1c..b89928a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+## [6.1.3](https://github.com/NativeScript/nativescript-dev-appium/compare/6.1.2...6.1.3) (2019-11-12)
+
+
+### Bug Fixes
+
+* **ios:** apply getActualRectangle ([#267](https://github.com/NativeScript/nativescript-dev-appium/issues/267)) ([ebe92e0](https://github.com/NativeScript/nativescript-dev-appium/commit/ebe92e0))
+* compareElement to use proper rectangle and generate proper diff image. ([cd76798](https://github.com/NativeScript/nativescript-dev-appium/commit/cd76798))
+* resolve device type by device name ([#277](https://github.com/NativeScript/nativescript-dev-appium/issues/277)) ([e7d7345](https://github.com/NativeScript/nativescript-dev-appium/commit/e7d7345))
+
+
+
+
+## [6.1.2](https://github.com/NativeScript/nativescript-dev-appium/compare/6.1.0...6.1.2) (2019-10-04)
+
+
+### Bug Fixes
+
+* **ios-13:** remove statusbar height from viewportRect ([c1a993c](https://github.com/NativeScript/nativescript-dev-appium/commit/c1a993c))
+
+
+
# [6.1.0](https://github.com/NativeScript/nativescript-dev-appium/compare/6.0.0...6.1.0) (2019-10-03)
diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts
index e30d196..f4615cc 100644
--- a/lib/appium-driver.ts
+++ b/lib/appium-driver.ts
@@ -30,7 +30,8 @@ import {
encodeImageToBase64,
ensureReportsDirExists,
checkImageLogType,
- adbShellCommand
+ adbShellCommand,
+ logWarn
} from "./utils";
import { INsCapabilities } from "./interfaces/ns-capabilities";
@@ -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();
}
@@ -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);
}
@@ -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);
@@ -272,7 +280,7 @@ export class AppiumDriver {
logInfo("Current version of appium doesn't support appium settings!");
}
- await DeviceManager.applyDeviceAdditionsSettings(driver, args, appiumCapsFromConfig);
+ await DeviceManager.applyDeviceAdditionsSettings(driver, args, sessionInfoDetails);
hasStarted = true;
} catch (error) {
@@ -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) {
@@ -318,10 +326,19 @@ export class AppiumDriver {
&& sessionInfoDetails.platformName.toLowerCase() === "ios"
&& sessionInfoDetails.platformVersion.startsWith("13")) {
try {
- const devicesInfos = IOSController.devicesDisplaysInfos();
- const matches = devicesInfos.filter(d => sessionInfoDetails.deviceName.includes(d.deviceType));
- const deviceType = matches[matches.length - 1];
- args.device.viewportRect.y += deviceType.statBarHeight * deviceType.density;
+ const devicesInfos = IOSController.devicesDisplaysInfos()
+ .filter(d => sessionInfoDetails.deviceName.includes(d.deviceType));
+
+ if (devicesInfos.length > 0) {
+ // sort devices by best match - in case we have iPhone XR 13 -> it will match device type 'iPhone X' and 'iPhone XR' -> after sort we will pick first longest match
+ devicesInfos
+ .sort((a, b) => {
+ return sessionInfoDetails.deviceName.replace(a.deviceType, "").length - sessionInfoDetails.deviceName.replace(b.deviceType, "").length
+ });
+ const deviceType = devicesInfos[0];
+ args.device.viewportRect.y += deviceType.actionBarHeight;
+ args.device.viewportRect.height -= deviceType.actionBarHeight;
+ }
} catch (error) { }
}
@@ -482,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);
}
/**
@@ -500,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--;
@@ -598,11 +617,11 @@ export class AppiumDriver {
return await this.driver.getSessionId();
}
- public async compareElement(element: UIElement, imageName?: string, tolerance: number = 0, timeOutSeconds: number = 3, toleranceType: ImageOptions = ImageOptions.percent) {
+ public async compareElement(element: UIElement, imageName?: string, tolerance: number = this.imageHelper.defaultTolerance, timeOutSeconds: number = 3, toleranceType: ImageOptions = this.imageHelper.defaultToleranceType) {
return await this.compareRectangle(await element.getActualRectangle(), imageName, timeOutSeconds, tolerance, toleranceType);
}
- public async compareRectangle(rect: IRectangle, imageName?: string, timeOutSeconds: number = 3, tolerance: number = 0, toleranceType: ImageOptions = ImageOptions.percent) {
+ public async compareRectangle(rect: IRectangle, imageName?: string, timeOutSeconds: number = 3, tolerance: number = this.imageHelper.defaultTolerance, toleranceType: ImageOptions = this.imageHelper.defaultToleranceType) {
imageName = imageName || this.imageHelper.testName;
const options = this.imageHelper.extendOptions({
imageName: imageName,
@@ -616,7 +635,7 @@ export class AppiumDriver {
return await this.imageHelper.compare(options);
}
- public async compareScreen(imageName?: string, timeOutSeconds: number = 3, tolerance: number = 0, toleranceType: ImageOptions = ImageOptions.percent) {
+ public async compareScreen(imageName?: string, timeOutSeconds: number = 3, tolerance: number = this.imageHelper.defaultTolerance, toleranceType: ImageOptions = this.imageHelper.defaultToleranceType) {
imageName = imageName || this.imageHelper.testName;
const options = this.imageHelper.extendOptions({
imageName: imageName,
@@ -855,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;
@@ -872,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}`);
diff --git a/lib/appium-server.ts b/lib/appium-server.ts
index e22ef5e..85230b4 100644
--- a/lib/appium-server.ts
+++ b/lib/appium-server.ts
@@ -126,7 +126,7 @@ export class AppiumServer {
private startAppiumServer(logLevel: string, isSauceLab: boolean) {
const startingServerArgs: Array = 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'!");
}
diff --git a/lib/device-manager.ts b/lib/device-manager.ts
index 63d3105..385f0ab 100644
--- a/lib/device-manager.ts
+++ b/lib/device-manager.ts
@@ -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 }
}
@@ -197,18 +197,19 @@ 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 {
args.device.apiLevel = sessionInfoDetails.platformVersion;
- args.device.deviceScreenDensity = sessionInfoDetails.pixelRatio;
+ args.device.deviceScreenDensity = sessionInfoDetails.pixelRatio || args.device.config.density;
const offsetPixels = +sessionInfoDetails.viewportRect.top - +sessionInfoDetails.statBarHeight;
args.device.config = { "density": sessionInfoDetails.pixelRatio || args.device.config.density, "offsetPixels": isNumber(offsetPixels) ? offsetPixels : args.device.config.offsetPixels };
}
args.device.statBarHeight = sessionInfoDetails.statBarHeight;
args.device.viewportRect = DeviceManager.convertViewportRectToIRectangle(sessionInfoDetails.viewportRect);
+ args.device.token = args.device.token || sessionInfoDetails.udid;
return args.device;
}
@@ -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 ...
@@ -272,11 +273,11 @@ export class DeviceManager implements IDeviceManager {
// }
public static async applyDeviceAdditionsSettings(driver, args: INsCapabilities, sessionInfo: any) {
- if ((!args.device.viewportRect || !args.device.viewportRect.x) && (!args.device.config || !args.device.config.offsetPixels)) {
+ if ((!args.device.viewportRect || !args.device.viewportRect.x) && (!args.device.config || !isNumber(args.device.config.offsetPixels))) {
args.device.config = {};
let density: number;
- if (sessionInfo && sessionInfo.length >= 1) {
- density = sessionInfo[1].deviceScreenDensity ? sessionInfo[1].deviceScreenDensity / 100 : undefined;
+ if (sessionInfo && Object.getOwnPropertyNames(sessionInfo).length >= 1) {
+ density = sessionInfo.pixelRatio ? sessionInfo.pixelRatio : undefined;
}
if (density) {
diff --git a/lib/direction.d.ts b/lib/direction.d.ts
index 8fe9856..2a7257e 100644
--- a/lib/direction.d.ts
+++ b/lib/direction.d.ts
@@ -1,4 +1,4 @@
-export declare const enum Direction {
+export declare enum Direction {
down = 0,
up = 1,
left = 2,
diff --git a/lib/direction.ts b/lib/direction.ts
index d1a3683..0fd5bd8 100644
--- a/lib/direction.ts
+++ b/lib/direction.ts
@@ -1,4 +1,4 @@
-export const enum Direction {
+export enum Direction {
down,
up,
left,
diff --git a/lib/image-helper.d.ts b/lib/image-helper.d.ts
index ddc080a..3842db1 100644
--- a/lib/image-helper.d.ts
+++ b/lib/image-helper.d.ts
@@ -64,6 +64,8 @@ export declare class ImageHelper {
private _blockOutAreas;
private _imagesResults;
private _options;
+ private _defaultToleranceType;
+ private _defaultTolerance;
private _defaultOptions;
constructor(_args: INsCapabilities, _driver: AppiumDriver);
static readonly pngFileExt = ".png";
@@ -80,6 +82,8 @@ export declare class ImageHelper {
delta: number;
options: IImageCompareOptions;
blockOutAreas: IRectangle[];
+ defaultToleranceType: ImageOptions;
+ defaultTolerance: number;
compareScreen(options?: IImageCompareOptions): Promise;
compareElement(element: UIElement, options?: IImageCompareOptions): Promise;
compareRectangle(cropRectangle: IRectangle, options?: IImageCompareOptions): Promise;
diff --git a/lib/image-helper.ts b/lib/image-helper.ts
index 3d88ad7..139024d 100644
--- a/lib/image-helper.ts
+++ b/lib/image-helper.ts
@@ -83,6 +83,8 @@ export class ImageHelper {
private _blockOutAreas: IRectangle[];
private _imagesResults = new Map();
private _options: IImageCompareOptions = {};
+ private _defaultToleranceType: ImageOptions = ImageOptions.percent;
+ private _defaultTolerance: number = 0;
private _defaultOptions: IImageCompareOptions = {
timeOutSeconds: 2,
tolerance: 0,
@@ -148,6 +150,22 @@ export class ImageHelper {
this._blockOutAreas = rectangles;
}
+ get defaultToleranceType(): ImageOptions {
+ return this._defaultToleranceType;
+ }
+
+ set defaultToleranceType(toleranceType: ImageOptions) {
+ this._defaultToleranceType = toleranceType;
+ }
+
+ get defaultTolerance(): number {
+ return this._defaultTolerance;
+ }
+
+ set defaultTolerance(tolerance: number) {
+ this._defaultTolerance = tolerance;
+ }
+
public async compareScreen(options?: IImageCompareOptions) {
options = this.extendOptions(options);
options.imageName = this.increaseImageName(options.imageName || this.testName, options);
@@ -314,13 +332,13 @@ export class ImageHelper {
public compareImages(options: IImageCompareOptions, actual: string, expected: string, output: string) {
const clipRect = {
- x: this.options.cropRectangle.x,
- y: this.options.cropRectangle.y,
- width: this.options.cropRectangle.width,
- height: this.options.cropRectangle.height
+ x: options.cropRectangle.x,
+ y: options.cropRectangle.y,
+ width: options.cropRectangle.width,
+ height: options.cropRectangle.height
}
- if (!this.options.keepOriginalImageSize) {
+ if (!options.keepOriginalImageSize) {
clipRect.x = 0;
clipRect.y = 0;
clipRect.width = undefined;
diff --git a/lib/interfaces/ns-capabilities-args.d.ts b/lib/interfaces/ns-capabilities-args.d.ts
index f1b29c9..a0c9a71 100644
--- a/lib/interfaces/ns-capabilities-args.d.ts
+++ b/lib/interfaces/ns-capabilities-args.d.ts
@@ -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;
diff --git a/lib/interfaces/ns-capabilities-args.ts b/lib/interfaces/ns-capabilities-args.ts
index 5e0ea58..2375303 100644
--- a/lib/interfaces/ns-capabilities-args.ts
+++ b/lib/interfaces/ns-capabilities-args.ts
@@ -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;
diff --git a/lib/ns-capabilities.d.ts b/lib/ns-capabilities.d.ts
index ae4f03a..267ed98 100644
--- a/lib/ns-capabilities.d.ts
+++ b/lib/ns-capabilities.d.ts
@@ -47,6 +47,7 @@ export declare class NsCapabilities implements INsCapabilities {
deviceTypeOrPlatform: string;
driverConfig: any;
logImageTypes: Array;
+ derivedDataPath: string;
constructor(_parser: INsCapabilitiesArgs);
readonly isAndroid: any;
readonly isIOS: boolean;
diff --git a/lib/ns-capabilities.ts b/lib/ns-capabilities.ts
index cb99c78..b9a123a 100644
--- a/lib/ns-capabilities.ts
+++ b/lib/ns-capabilities.ts
@@ -53,6 +53,7 @@ export class NsCapabilities implements INsCapabilities {
public deviceTypeOrPlatform: string;
public driverConfig: any;
public logImageTypes: Array;
+ public derivedDataPath: string;
constructor(private _parser: INsCapabilitiesArgs) {
this.projectDir = this._parser.projectDir;
@@ -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;
diff --git a/lib/parser.d.ts b/lib/parser.d.ts
index 40edb2e..b9aae80 100644
--- a/lib/parser.d.ts
+++ b/lib/parser.d.ts
@@ -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;
diff --git a/lib/parser.ts b/lib/parser.ts
index 977e173..dfd21e9 100644
--- a/lib/parser.ts
+++ b/lib/parser.ts
@@ -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" })
@@ -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),
@@ -208,6 +212,7 @@ export const {
devMode,
ignoreDeviceController,
wdaLocalPort,
+ derivedDataPath,
path,
relaxedSecurity,
cleanApp,
diff --git a/lib/ui-element.d.ts b/lib/ui-element.d.ts
index 2bb26d3..ee66fbb 100644
--- a/lib/ui-element.d.ts
+++ b/lib/ui-element.d.ts
@@ -16,6 +16,10 @@ export declare class UIElement {
* Click on element
*/
click(): Promise;
+ getCenter(): Promise<{
+ x: number;
+ y: number;
+ }>;
tapCenter(): Promise;
tapAtTheEnd(): Promise;
/**
@@ -26,9 +30,14 @@ export declare class UIElement {
*/
tap(): Promise;
/**
+ * @experimental
* Double tap on element
*/
- doubleTap(): Promise;
+ doubleTap(offset?: {
+ x: number;
+ y: number;
+ }): Promise;
+ longPress(duration: number): Promise;
/**
* Get location of element
*/
@@ -36,7 +45,10 @@ export declare class UIElement {
/**
* Get size of element
*/
- size(): Promise;
+ size(): Promise<{
+ width: number;
+ height: number;
+ }>;
/**
* Get text of element
*/
@@ -113,13 +125,6 @@ export declare class UIElement {
*/
scrollTo(direction: Direction, elementToSearch: () => Promise, yOffset?: number, xOffset?: number, retries?: number): Promise;
/**
- * Drag element with specific offset
- * @param direction
- * @param yOffset
- * @param xOffset - default value 0
- */
- drag(direction: Direction, yOffset: number, xOffset?: number): Promise;
- /**
* Click and hold over an element
* @param time in milliseconds to increase the default press period.
*/
@@ -165,4 +170,50 @@ export declare class UIElement {
* @param direction
*/
swipe(direction: Direction): Promise;
+ /**
+ * 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;
+ /**
+ *@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;
+ /**
+ * @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;
+ /**
+ * @experimental
+ * @param zoomFactory - zoomIn or zoomOut. Only zoomIn action is implemented
+ * @param offset
+ */
+ pinch(zoomType: "in" | "out", offset?: {
+ x: number;
+ y: number;
+ }): Promise;
}
diff --git a/lib/ui-element.ts b/lib/ui-element.ts
index 3f89674..62a37fa 100644
--- a/lib/ui-element.ts
+++ b/lib/ui-element.ts
@@ -2,7 +2,7 @@ import { Point } from "./point";
import { Direction } from "./direction";
import { INsCapabilities } from "./interfaces/ns-capabilities";
import { AutomationName } from "./automation-name";
-import { calculateOffset, adbShellCommand, logError } from "./utils";
+import { calculateOffset, adbShellCommand, logError, wait, logInfo } from "./utils";
import { AndroidKeyEvent } from "mobile-devices-controller";
export class UIElement {
@@ -24,12 +24,17 @@ export class UIElement {
return await (await this.element()).click();
}
+ public async getCenter() {
+ const rect = await this.getRectangle();
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
+ }
+
public async tapCenter() {
let action = new this._wd.TouchAction(this._driver);
- const rect = await this.getActualRectangle();
- this._args.testReporterLog(`Tap on center element ${{ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }}`);
+ const centerRect = await this.getCenter();
+ this._args.testReporterLog(`Tap on center element x: ${centerRect.x} y: ${centerRect.y}`);
action
- .tap({ x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 });
+ .tap(centerRect);
await action.perform();
await this._driver.sleep(150);
}
@@ -59,10 +64,40 @@ export class UIElement {
}
/**
+ * @experimental
* Double tap on element
*/
- public async doubleTap() {
- return await this._driver.execute('mobile: doubleTap', { element: (await this.element()).value.ELEMENT });
+ public async doubleTap(offset: { x: number, y: number } = { x: 0, y: 0 }) {
+ if (this._args.isAndroid) {
+ // hack double tap for android
+ const rect = await this.getRectangle();
+
+ if (`${this._args.device.apiLevel}`.startsWith("29")
+ || `${this._args.device.apiLevel}`.startsWith("9.")) {
+ const offsetPoint = { x: (rect.x + offset.x), y: (rect.y + offset.y) };
+ await adbShellCommand(this._driver, "input", ["tap", `${offsetPoint.x} ${offsetPoint.y}`]);
+ await adbShellCommand(this._driver, "input", ["tap", `${offsetPoint.x} ${offsetPoint.y}`]);
+ } else {
+ let action = new this._wd.TouchAction(this._driver);
+ action.press({ x: rect.x, y: rect.y }).release().perform();
+ action.press({ x: rect.x, y: rect.y }).release().perform();
+ await action.perform();
+ }
+ } else {
+ // this works only for ios, otherwise it throws error
+ return await this._driver.execute('mobile: doubleTap', { element: this._element.value });
+ }
+ }
+
+ public async longPress(duration: number) {
+ const rect = await this.getCenter();
+ console.log("LongPress at ", rect);
+ const action = new this._wd.TouchAction(this._driver);
+ action
+ .press({ x: rect.x, y: rect.y })
+ .wait(duration)
+ .release();
+ await action.perform();
}
/**
@@ -77,10 +112,9 @@ export class UIElement {
/**
* Get size of element
*/
- public async size() {
+ public async size(): Promise<{ width: number, height: number }> {
const size = await (await this.element()).getSize();
- const point = new Point(size.height, size.width);
- return point;
+ return size;
}
/**
@@ -233,7 +267,7 @@ export class UIElement {
public async getRectangle() {
const location = await this.location();
const size = await this.size();
- const rect = { x: location.x, y: location.y, width: size.y, height: size.x };
+ const rect = { x: location.x, y: location.y, width: size.width, height: size.height };
return rect;
}
@@ -242,7 +276,7 @@ export class UIElement {
*/
public async getActualRectangle() {
const actRect = await this.getRectangle();
- const density = this._args.device.config.density;
+ const density = this._args.device.deviceScreenDensity;
if (this._args.isIOS) {
if (density) {
actRect.x *= density;
@@ -263,40 +297,34 @@ export class UIElement {
* @param xOffset
*/
public async scroll(direction: Direction, yOffset: number = 0, xOffset: number = 0) {
- //await this._driver.execute("mobile: scroll", [{direction: 'up'}])
- //await this._driver.execute('mobile: scroll', { direction: direction === 0 ? "down" : "up", element: this._element.ELEMENT });
const location = await this.location();
const size = await this.size();
- const x = location.x === 0 ? 10 : location.x;
- let y = (location.y + 15);
- if (yOffset === 0) {
- yOffset = location.y + size.y - 15;
- }
- if (direction === Direction.down) {
- y = (location.y + size.y) - 15;
-
- if (!this._webio.isIOS) {
- if (yOffset === 0) {
- yOffset = location.y + size.y - 15;
- }
+ if (direction === Direction.down || direction === Direction.up) {
+ if (xOffset > 0) {
+ location.x += xOffset;
}
- }
- if (direction === Direction.up) {
if (yOffset === 0) {
- yOffset = size.y - 15;
+ yOffset = location.y + size.height - 5;
}
}
- const endPoint = calculateOffset(direction, y, yOffset, x, xOffset, this._webio.isIOS, false);
- if (direction === Direction.down) {
- //endPoint.point.y += location.y;
+ if (direction === Direction.left || direction === Direction.right) {
+ if (yOffset > 0) {
+ location.y += yOffset;
+ }
+ if (xOffset === 0) {
+ xOffset = location.x + size.width - 5;
+ }
}
- let action = new this._wd.TouchAction(this._driver);
+
+ const endPoint = calculateOffset(direction, location.y, yOffset, location.x, xOffset, this._args.isIOS);
+
+ const action = new this._wd.TouchAction(this._driver);
action
- .press({ x: x, y: y })
+ .press({ x: endPoint.startPoint.x, y: endPoint.startPoint.y })
.wait(endPoint.duration)
- .moveTo({ x: endPoint.point.x, y: endPoint.point.y })
+ .moveTo({ x: endPoint.endPoint.x, y: endPoint.endPoint.y })
.release();
await action.perform();
await this._driver.sleep(150);
@@ -316,7 +344,7 @@ export class UIElement {
while (el === null && retries >= 0) {
try {
el = await elementToSearch();
- if (!el || el === null || !(await el.isDisplayed())) {
+ if (!el || el === null || !(el && await el.isDisplayed())) {
el = null;
await this.scroll(direction, yOffset, xOffset);
}
@@ -329,41 +357,6 @@ export class UIElement {
return el;
}
- /**
- * Drag element with specific offset
- * @param direction
- * @param yOffset
- * @param xOffset - default value 0
- */
- public async drag(direction: Direction, yOffset: number, xOffset: number = 0) {
- const location = await this.location();
-
- const x = location.x === 0 ? 10 : location.x;
- const y = location.y === 0 ? 10 : location.y;
-
- const endPoint = calculateOffset(direction, y, yOffset, x, xOffset, this._webio.isIOS, false);
-
- if (this._args.isAndroid) {
- let action = new this._wd.TouchAction(this._driver);
- action
- .longPress({ x: x, y: y })
- .wait(endPoint.duration)
- .moveTo({ x: yOffset, y: yOffset })
- .release();
- await action.perform();
- } else {
- await this._wd.execute(`mobile: dragFromToForDuration`, {
- duration: endPoint.duration,
- fromX: x,
- fromY: y,
- toX: xOffset,
- toY: yOffset
- });
- }
-
- await this._driver.sleep(150);
- }
-
/**
* Click and hold over an element
* @param time in milliseconds to increase the default press period.
@@ -391,7 +384,7 @@ export class UIElement {
if (shouldClearText) {
await this.adbDeleteText(adbDeleteCharsCount);
}
- text = text.replace(" ","%s");
+ text = text.replace(" ", "%s");
await this.click();
await adbShellCommand(this._driver, "input", ["text", text]);
} else {
@@ -474,35 +467,161 @@ export class UIElement {
* @param direction
*/
public async swipe(direction: Direction) {
- const rectangle = await this.getRectangle();
- const centerX = rectangle.x + rectangle.width / 2;
- const centerY = rectangle.y + rectangle.height / 2;
- let swipeX;
- if (direction == Direction.right) {
- const windowSize = await this._driver.getWindowSize();
- swipeX = windowSize.width - 10;
- } else if (direction == Direction.left) {
- swipeX = 10;
+ logInfo(`Swipe direction: `, Direction[direction]);
+ if (this._args.isIOS) {
+ await this._driver
+ .execute('mobile: scroll', {
+ element: this._element.value,
+ direction: Direction[direction]
+ });
} else {
- console.log("Provided direction must be left or right !");
+ try {
+ await this.scroll(direction);
+ } catch (error) {
+ console.log("", error);
+ }
}
+ logInfo(`End swipe`);
+ }
+
+ /**
+ * Drag element with specific offset
+ * @experimental
+ * @param direction
+ * @param yOffset
+ * @param xOffset - default value 0
+ */
+ public async drag(direction: Direction, yOffset: number, xOffset: number = 0, duration?: number) {
+ direction = direction === Direction.up ? Direction.down : Direction.up;
+
+ const location = await this.location();
+
+ const x = location.x === 0 ? 10 : location.x;
+ const y = location.y === 0 ? 10 : location.y;
+
+ const endPoint = calculateOffset(direction, y, yOffset, x, xOffset, this._args.isIOS);
+ duration = duration || endPoint.duration;
if (this._args.isAndroid) {
- const action = new this._wd.TouchAction(this._driver);
- action.press({ x: centerX, y: centerY })
- .wait(200)
- .moveTo({ x: swipeX, y: centerY })
+ let action = new this._wd.TouchAction(this._driver);
+ action
+ .longPress({ x: x, y: y })
+ .wait(duration)
+ .moveTo({ x: yOffset, y: yOffset })
.release();
await action.perform();
- }
- else {
- await this._driver.execute('mobile: dragFromToForDuration', {
- duration: 2.0,
- fromX: centerX,
- fromY: centerY,
- toX: swipeX,
- toY: centerY
+ } else {
+ await this._driver.execute(`mobile: dragFromToForDuration`, {
+ duration: duration,
+ fromX: x,
+ fromY: y,
+ toX: xOffset,
+ toY: yOffset
});
}
+
+ await this._driver.sleep(150);
}
-}
+
+ /**
+ *@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.
+ */
+ public async pan(offsets: { x: number, y: number }[], initPointOffset: { x: number, y: number } = { x: 0, y: 0 }) {
+ logInfo("Start pan gesture!");
+ const rect = await this.getRectangle();
+ const action = new this._wd.TouchAction(this._driver);
+ await action.press({ x: rect.x + initPointOffset.x, y: rect.y + initPointOffset.y }).wait(200);
+ if (offsets.length > 1) {
+ for (let index = 1; index < offsets.length; index++) {
+ const p = offsets[index];
+ action.moveTo({ x: rect.x + p.x, y: rect.y + p.y });
+ }
+ }
+
+ await action.release().perform();
+ logInfo("End pan gesture!");
+ }
+
+ /**
+ * @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 }
+ */
+ public async rotate(offset: { x: number, y: number } = { x: 10, y: 10 }) {
+ logInfo("Start rotate gesture!");
+ const elementRect = await this.getRectangle();
+
+ const startPoint = { x: elementRect.x + offset.x, y: elementRect.y + offset.y };
+ const endPoint = { x: elementRect.x + elementRect.width - offset.x, y: elementRect.height + elementRect.y - offset.y };
+
+ const multiAction = new this._wd.MultiAction(this._driver);
+ const action1 = new this._wd.TouchAction(this._driver);
+ action1
+ .press(startPoint)
+ .wait(100)
+ .moveTo(endPoint)
+ .wait(1000)
+ .release();
+ multiAction.add(action1);
+
+ const action2 = new this._wd.TouchAction(this._driver);
+ action2
+ .press(endPoint)
+ .wait(100)
+ .moveTo({ x: startPoint.x, y: startPoint.y - 1 })
+ .wait(1000)
+ .release();
+ multiAction.add(action2);
+
+ await multiAction.perform();
+ logInfo("End rotate gesture!");
+ }
+
+ /**
+ * @experimental
+ * @param zoomFactory - zoomIn or zoomOut. Only zoomIn action is implemented
+ * @param offset
+ */
+ public async pinch(zoomType: "in" | "out", offset?: { x: number, y: number }) {
+ logInfo("Start pinch gesture!");
+ const elementRect = await this.getRectangle();
+
+ offset = offset || { x: elementRect.width / 2, y: elementRect.height / 2 };
+ elementRect.y = elementRect.y + elementRect.height / 2;
+
+ const endPoint = { x: offset.x, y: offset.y };
+
+ const startPointOne = { x: elementRect.x + 20, y: elementRect.y };
+ const action1 = new this._wd.TouchAction(this._driver);
+ action1
+ .press(startPointOne)
+ .wait(100)
+ .moveTo(endPoint)
+ .release()
+
+ const multiAction = new this._wd.MultiAction(this._driver);
+ multiAction.add(action1);
+
+ const startPointTwo = { x: elementRect.x + elementRect.width, y: elementRect.y };
+ const action2 = new this._wd.TouchAction(this._driver);
+ action2
+ .press(startPointTwo)
+ .wait(500)
+ .moveTo(endPoint)
+ .release()
+ multiAction.add(action2);
+
+ await multiAction.perform();
+ logInfo("End pinch gesture!");
+ }
+}
\ No newline at end of file
diff --git a/lib/utils.d.ts b/lib/utils.d.ts
index b1be4f2..2cd64f7 100644
--- a/lib/utils.d.ts
+++ b/lib/utils.d.ts
@@ -21,8 +21,9 @@ export declare const getStorage: (args: INsCapabilities) => string;
export declare function getReportPath(args: INsCapabilities): string;
export declare const getRegexResultsAsArray: (regex: any, str: any) => any[];
export declare function getAppPath(caps: INsCapabilities): string;
-export declare function calculateOffset(direction: any, y: number, yOffset: number, x: number, xOffset: number, isIOS: boolean, verbose: any): {
- point: Point;
+export declare function calculateOffset(direction: any, y: number, yOffset: number, x: number, xOffset: number, isIOS: boolean): {
+ startPoint: Point;
+ endPoint: Point;
duration: number;
};
/**
@@ -32,7 +33,7 @@ export declare function calculateOffset(direction: any, y: number, yOffset: numb
* @param yOffset
* @param xOffset
*/
-export declare function scroll(wd: any, driver: any, direction: Direction, isIOS: boolean, y: number, x: number, yOffset: number, xOffset: number, verbose: any): Promise;
+export declare function scroll(wd: any, driver: any, direction: Direction, isIOS: boolean, y: number, x: number, yOffset: number, xOffset: number): Promise;
export declare const addExt: (fileName: string, ext: string) => string;
export declare const isPortAvailable: (port: any) => Promise<{}>;
export declare const findFreePort: (retries?: number, port?: number) => Promise;
diff --git a/lib/utils.ts b/lib/utils.ts
index aeda908..75985e3 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -242,7 +242,7 @@ export function getStorageByDeviceName(args: INsCapabilities) {
storage = createStorageFolder(storage, getDeviceName(args));
logWarn(`Images storage set to: ${storage}!`);
-
+
return storage;
}
@@ -381,48 +381,55 @@ export function getAppPath(caps: INsCapabilities) {
return appFullPath;
}
-export function calculateOffset(direction, y: number, yOffset: number, x: number, xOffset: number, isIOS: boolean, verbose) {
+export function calculateOffset(direction, y: number, yOffset: number, x: number, xOffset: number, isIOS: boolean) {
let speed = 10;
- let yEnd = Math.abs(yOffset);
- let xEnd = Math.abs(xOffset);
+ let yEnd = y;
+ let xEnd = x;
let duration = Math.abs(yEnd) * speed;
- if (isIOS) {
- speed = 100;
- if (direction === Direction.down) {
- direction = -1;
- yEnd = direction * yEnd;
- }
- if (direction === Direction.right) {
- direction = -1;
- xEnd = direction * xEnd;
- }
- } else {
- if (direction === Direction.down) {
- yEnd = Math.abs(yOffset - y);
- }
- if (direction === Direction.up) {
- yEnd = direction * Math.abs((Math.abs(yOffset) + y));
- }
-
+ if (direction === Direction.down) {
+ yEnd = Math.abs(y);
+ y = Math.abs(yOffset - y);
+ duration = Math.abs(yOffset) * speed;
+ }
+ if (direction === Direction.up) {
+ yEnd = Math.abs((Math.abs(y - yOffset)));
duration = Math.abs(yOffset) * speed;
+ }
- if (direction === Direction.right) {
- xEnd = Math.abs(xOffset - x);
- }
+ if (direction === Direction.right) {
+ xEnd = Math.abs(x);
+ x = Math.abs(xOffset - x);
+ duration = Math.abs(xOffset) * speed;
+ }
- if (direction === Direction.left) {
- xEnd = Math.abs(xOffset + x);
+ if (direction === Direction.left) {
+ xEnd = Math.abs(xOffset + x);
+ duration = Math.abs(xOffset) * speed;
+ const addToX = isIOS ? 50 : 5;
+ if (isIOS) {
+ x = x === 0 ? 50 : x;
+ } else {
+ x = x === 0 ? 5 : x;
}
-
- if (yOffset < xOffset && x) {
- duration = Math.abs(xOffset) * speed;
+ if (x === 0) {
+ logInfo(`Changing x to x + ${addToX}, since this will perform navigate back for ios or rise exception in android!`);
}
+ }
+ if (yOffset < xOffset) {
+ duration = Math.abs(xOffset) * speed;
}
- log({ point: new Point(xEnd, yEnd), duration: duration }, verbose);
- return { point: new Point(xEnd, yEnd), duration: duration };
+ logInfo("Start point: ", new Point(x, y));
+ logInfo("End point: ", new Point(xEnd, yEnd));
+ logInfo("Scrolling speed: ", duration);
+
+ return {
+ startPoint: new Point(x, y),
+ endPoint: new Point(xEnd, yEnd),
+ duration: duration
+ };
}
/**
@@ -432,19 +439,19 @@ export function calculateOffset(direction, y: number, yOffset: number, x: number
* @param yOffset
* @param xOffset
*/
-export async function scroll(wd, driver, direction: Direction, isIOS: boolean, y: number, x: number, yOffset: number, xOffset: number, verbose) {
+export async function scroll(wd, driver, direction: Direction, isIOS: boolean, y: number, x: number, yOffset: number, xOffset: number) {
if (x === 0) {
x = 20;
}
if (y === 0) {
y = 20;
}
- const endPoint = calculateOffset(direction, y, yOffset, x, xOffset, isIOS, verbose);
+ const endPoint = calculateOffset(direction, y, yOffset, x, xOffset, isIOS);
const action = new wd.TouchAction(driver);
action
.press({ x: x, y: y })
.wait(endPoint.duration)
- .moveTo({ x: endPoint.point.x, y: endPoint.point.y })
+ .moveTo({ x: endPoint.endPoint.x, y: endPoint.endPoint.y })
.release();
await action.perform();
await driver.sleep(150);
@@ -689,7 +696,7 @@ export const logColorized = (bgColor: ConsoleColor, frontColor: ConsoleColor, in
export async function adbShellCommand(wd: any, command: string, args: Array) {
- await wd.execute('mobile: shell', {"command": command, "args": args});
+ await wd.execute('mobile: shell', { "command": command, "args": args });
}
enum ConsoleColor {
diff --git a/package.json b/package.json
index 9535a46..4f0b86e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nativescript-dev-appium",
- "version": "6.1.0",
+ "version": "6.1.3",
"description": "A NativeScript plugin to help integrate and run Appium tests",
"author": "NativeScript",
"license": "MIT",
@@ -36,7 +36,7 @@
"frame-comparer": "^2.0.1",
"glob": "^7.1.0",
"inquirer": "^6.2.0",
- "mobile-devices-controller": "^5.2.0",
+ "mobile-devices-controller": "~5.2.0",
"wd": "~1.11.3",
"webdriverio": "~4.14.0",
"yargs": "~12.0.5"
diff --git a/test/device-manager.spec.ts b/test/device-manager.spec.ts
index bec3568..163b998 100644
--- a/test/device-manager.spec.ts
+++ b/test/device-manager.spec.ts
@@ -206,8 +206,8 @@ describe("start-appium-server-ios", async () => {
appPath: iosApp,
appiumCaps: {
platformName: Platform.IOS,
- deviceName: /^iPhone XR$/,
- platformVersion: /12/,
+ deviceName: /^iPhone XR 12$/,
+ platformVersion: "12.2",
fullReset: true
},
verbose: false
@@ -275,7 +275,7 @@ describe("dev-mode-options", async () => {
before("start devices", async () => {
await DeviceController.startDevice({ platform: Platform.ANDROID, apiLevel: "23" });
- await DeviceController.startDevice({ platform: Platform.IOS, apiLevel: /12./, name: "iPhone XR" });
+ await DeviceController.startDevice({ platform: Platform.IOS, apiLevel: "12.2", name: "iPhone XR 12" });
appiumServer = new AppiumServer({});
await appiumServer.start(8399);
});