From f66cffd11701372416c9681bd5d97069fb148270 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Thu, 26 Sep 2019 17:31:21 +0300 Subject: [PATCH 01/11] feat: implement pinch, pan, rotate and scroll Still in experimental mode --- lib/appium-driver.ts | 4 +- lib/ui-element.d.ts | 66 ++++++++-- lib/ui-element.ts | 295 +++++++++++++++++++++++++++++-------------- lib/utils.d.ts | 7 +- lib/utils.ts | 79 ++++++------ 5 files changed, 309 insertions(+), 142 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index 309b583..1d5a678 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -484,7 +484,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); } /** @@ -504,7 +504,7 @@ export class AppiumDriver { el = await element(); isDisplayed = 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.x, offsetPoint.y); el = null; } } catch (error) { diff --git a/lib/ui-element.d.ts b/lib/ui-element.d.ts index 2bb26d3..704f9e7 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,11 @@ export declare class UIElement { */ tap(): Promise; /** + * @experimental * Double tap on element */ doubleTap(): Promise; + longPress(duration: number): Promise; /** * Get location of element */ @@ -36,7 +42,10 @@ export declare class UIElement { /** * Get size of element */ - size(): Promise; + size(): Promise<{ + width: number; + height: number; + }>; /** * Get text of element */ @@ -113,13 +122,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. */ @@ -164,5 +166,51 @@ export declare class UIElement { * Swipe element left/right * @param direction */ - swipe(direction: Direction): Promise; + swipe(direction: "up" | "down" | "left" | "right"): Promise; + /** + * Drag element with specific offset + * @experimental + * @param direction + * @param yOffset + * @param xOffset - default value 0 + */ + drag(direction: Direction, yOffset: number, xOffset?: number): Promise; + /** + *@experimental + * Pan element with specific offset + * @param points 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(points: { + 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..ae92cda 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,32 @@ export class UIElement { } /** + * @experimental * Double tap on element */ public async doubleTap() { - return await this._driver.execute('mobile: doubleTap', { element: (await this.element()).value.ELEMENT }); + if (this._args.isAndroid) { + // hack double tap for android + let action = new this._wd.TouchAction(this._driver); + const rect = await this.getRectangle(); + 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 +104,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 +259,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; } @@ -263,40 +289,28 @@ 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.up) { + if (direction === Direction.down || 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 (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); @@ -329,41 +343,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. @@ -473,36 +452,168 @@ export class UIElement { * Swipe element left/right * @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; + public async swipe(direction: "up" | "down" | "left" | "right") { + logInfo(`Swipe direction: `, direction); + if (this._args.isIOS) { + await this._driver + .execute('mobile: scroll', { + element: this._element.value, + direction: direction + }); } else { - console.log("Provided direction must be left or right !"); + try { + let scrollDirection = Direction.up; + switch (direction) { + case "down": scrollDirection = Direction.down; + break; + case "left": scrollDirection = Direction.left; + break; + case "right": scrollDirection = Direction.right; + break; + } + await this.scroll(scrollDirection); + } 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) { + 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); 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(endPoint.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: endPoint.duration, + fromX: x, + fromY: y, + toX: xOffset, + toY: yOffset }); } + + await this._driver.sleep(150); } -} + + /** + *@experimental + * Pan element with specific offset + * @param points 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(points: { 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(100) + if (points.length > 1) { + for (let index = 1; index < points.length; index++) { + const element = points[index]; + action.moveTo({ x: element.x, y: element.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..054130d 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 = direction * Math.abs((Math.abs(yOffset) + y)); + 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); + // } + logInfo("Start point point: ", new Point(x, y)); + logInfo("End point: ", new Point(xEnd, yEnd)); + logInfo("Scrolling speed point: ", duration); - return { point: new Point(xEnd, yEnd), duration: 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 { From f533d1201c00fbb00a99d1baf6b7128f736b217a Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Tue, 1 Oct 2019 15:06:58 +0300 Subject: [PATCH 02/11] additional logging --- lib/direction.d.ts | 2 +- lib/direction.ts | 2 +- lib/ui-element.d.ts | 4 ++-- lib/ui-element.ts | 22 ++++++++-------------- lib/utils.ts | 6 +++--- 5 files changed, 15 insertions(+), 21 deletions(-) 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/ui-element.d.ts b/lib/ui-element.d.ts index 704f9e7..6579214 100644 --- a/lib/ui-element.d.ts +++ b/lib/ui-element.d.ts @@ -166,7 +166,7 @@ export declare class UIElement { * Swipe element left/right * @param direction */ - swipe(direction: "up" | "down" | "left" | "right"): Promise; + swipe(direction: Direction): Promise; /** * Drag element with specific offset * @experimental @@ -174,7 +174,7 @@ export declare class UIElement { * @param yOffset * @param xOffset - default value 0 */ - drag(direction: Direction, yOffset: number, xOffset?: number): Promise; + drag(direction: Direction, yOffset: number, xOffset?: number, duration?: number): Promise; /** *@experimental * Pan element with specific offset diff --git a/lib/ui-element.ts b/lib/ui-element.ts index ae92cda..0b9d28b 100644 --- a/lib/ui-element.ts +++ b/lib/ui-element.ts @@ -452,26 +452,17 @@ export class UIElement { * Swipe element left/right * @param direction */ - public async swipe(direction: "up" | "down" | "left" | "right") { - logInfo(`Swipe direction: `, direction); + public async swipe(direction: Direction) { + logInfo(`Swipe direction: `, Direction[direction]); if (this._args.isIOS) { await this._driver .execute('mobile: scroll', { element: this._element.value, - direction: direction + direction: Direction[direction] }); } else { try { - let scrollDirection = Direction.up; - switch (direction) { - case "down": scrollDirection = Direction.down; - break; - case "left": scrollDirection = Direction.left; - break; - case "right": scrollDirection = Direction.right; - break; - } - await this.scroll(scrollDirection); + await this.scroll(direction); } catch (error) { console.log("", error); } @@ -486,13 +477,16 @@ export class UIElement { * @param yOffset * @param xOffset - default value 0 */ - public async drag(direction: Direction, yOffset: number, xOffset: number = 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) { let action = new this._wd.TouchAction(this._driver); diff --git a/lib/utils.ts b/lib/utils.ts index 054130d..421b2a7 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -420,10 +420,10 @@ export function calculateOffset(direction, y: number, yOffset: number, x: number if (yOffset < xOffset) { duration = Math.abs(xOffset) * speed; } - // } - logInfo("Start point point: ", new Point(x, y)); + + logInfo("Start point: ", new Point(x, y)); logInfo("End point: ", new Point(xEnd, yEnd)); - logInfo("Scrolling speed point: ", duration); + logInfo("Scrolling speed: ", duration); return { startPoint: new Point(x, y), From 22470a396c44eac4a78c3897f50cb37c762e11f2 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Thu, 3 Oct 2019 17:20:39 +0300 Subject: [PATCH 03/11] chore: update mobile-devices-controller version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 639e473..f14708d 100644 --- a/package.json +++ b/package.json @@ -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" From 517ee592289c3571bd5590a2b8eb713a888f78a2 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Tue, 8 Oct 2019 17:43:20 +0300 Subject: [PATCH 04/11] fix: sctollTo --- lib/appium-driver.ts | 6 ++++-- lib/utils.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index 1b5b9cd..b61d912 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -521,13 +521,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); + 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--; diff --git a/lib/utils.ts b/lib/utils.ts index 421b2a7..75985e3 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -393,7 +393,7 @@ export function calculateOffset(direction, y: number, yOffset: number, x: number duration = Math.abs(yOffset) * speed; } if (direction === Direction.up) { - yEnd = direction * Math.abs((Math.abs(yOffset) + y)); + yEnd = Math.abs((Math.abs(y - yOffset))); duration = Math.abs(yOffset) * speed; } From 8e8922fbda80f5ac781921efb35ebda5ab519052 Mon Sep 17 00:00:00 2001 From: Zdravko Branzov Date: Wed, 9 Oct 2019 18:05:06 +0300 Subject: [PATCH 05/11] fix: respect provided offset parameters when scrolling vertical and horizontal --- lib/ui-element.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ui-element.ts b/lib/ui-element.ts index 465527c..f829165 100644 --- a/lib/ui-element.ts +++ b/lib/ui-element.ts @@ -293,12 +293,18 @@ export class UIElement { const size = await this.size(); if (direction === Direction.down || direction === Direction.up) { + if (xOffset > 0) { + location.x += xOffset; + } if (yOffset === 0) { yOffset = location.y + size.height - 5; } } if (direction === Direction.left || direction === Direction.right) { + if (yOffset > 0) { + location.y += yOffset; + } if (xOffset === 0) { xOffset = location.x + size.width - 5; } @@ -330,7 +336,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); } @@ -370,7 +376,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 { From 21d5d600c90bc5e21bc821e0b9273bb791dc39f8 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Fri, 18 Oct 2019 11:30:59 +0300 Subject: [PATCH 06/11] update double tap gesture and pan --- lib/appium-driver.ts | 2 +- lib/appium-server.ts | 2 +- lib/device-manager.ts | 2 +- lib/ui-element.d.ts | 9 ++++++--- lib/ui-element.ts | 33 +++++++++++++++++++++------------ 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index b61d912..c915179 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -262,7 +262,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); } 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 251766d..41ed8f2 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 } } diff --git a/lib/ui-element.d.ts b/lib/ui-element.d.ts index 6579214..ee66fbb 100644 --- a/lib/ui-element.d.ts +++ b/lib/ui-element.d.ts @@ -33,7 +33,10 @@ export declare class UIElement { * @experimental * Double tap on element */ - doubleTap(): Promise; + doubleTap(offset?: { + x: number; + y: number; + }): Promise; longPress(duration: number): Promise; /** * Get location of element @@ -178,10 +181,10 @@ export declare class UIElement { /** *@experimental * Pan element with specific offset - * @param points where the finger should move to. + * @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(points: { + pan(offsets: { x: number; y: number; }[], initPointOffset?: { diff --git a/lib/ui-element.ts b/lib/ui-element.ts index f829165..7e76836 100644 --- a/lib/ui-element.ts +++ b/lib/ui-element.ts @@ -67,14 +67,22 @@ export class UIElement { * @experimental * Double tap on element */ - public async doubleTap() { + public async doubleTap(offset: { x: number, y: number } = { x: 0, y: 0 }) { if (this._args.isAndroid) { // hack double tap for android - let action = new this._wd.TouchAction(this._driver); const rect = await this.getRectangle(); - action.press({ x: rect.x, y: rect.y }).release().perform(); - action.press({ x: rect.x, y: rect.y }).release().perform(); - await action.perform(); + + 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 }); @@ -518,18 +526,19 @@ export class UIElement { /** *@experimental * Pan element with specific offset - * @param points where the finger should move to. + * @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(points: { x: number, y: number }[], initPointOffset: { x: number, y: number } = { x: 0, y: 0 }) { + 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(100) - if (points.length > 1) { - for (let index = 1; index < points.length; index++) { - const element = points[index]; - action.moveTo({ x: element.x, y: element.y }); + await action.press({ x: rect.x + initPointOffset.x, y: rect.y + initPointOffset.y }); + await this.doubleTap(); + 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 }); } } From 19a7cc29ff346668d791dd04631ade71a82919aa Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Mon, 21 Oct 2019 21:37:18 +0300 Subject: [PATCH 07/11] chore: update pan gesture --- lib/device-manager.ts | 1 + lib/ui-element.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/device-manager.ts b/lib/device-manager.ts index 41ed8f2..94478a5 100644 --- a/lib/device-manager.ts +++ b/lib/device-manager.ts @@ -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; } diff --git a/lib/ui-element.ts b/lib/ui-element.ts index 7e76836..1bcf09a 100644 --- a/lib/ui-element.ts +++ b/lib/ui-element.ts @@ -533,8 +533,7 @@ export class UIElement { 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 }); - await this.doubleTap(); + 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]; From b5626dd93dd15f028663777f5ba0cb6cd5f70a49 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Wed, 30 Oct 2019 13:38:15 +0200 Subject: [PATCH 08/11] fix: apiLevel of devices --- lib/appium-driver.ts | 5 +++++ lib/device-manager.d.ts | 2 +- lib/device-manager.ts | 8 ++++---- lib/parser.d.ts | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index c915179..8967e84 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -185,6 +185,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(); } diff --git a/lib/device-manager.d.ts b/lib/device-manager.d.ts index c6c8916..17ca9a9 100644 --- a/lib/device-manager.d.ts +++ b/lib/device-manager.d.ts @@ -13,7 +13,7 @@ export declare class DeviceManager implements IDeviceManager { static getInstalledApps(device: IDevice): Promise; static getDefaultDevice(args: INsCapabilities, deviceName?: string, token?: string, type?: DeviceType, platformVersion?: number): IDevice; private static convertViewportRectToIRectangle; - static applyAppiumSessionInfoDetails(args: INsCapabilities, sessionInfoDetails: any): any; + static applyAppiumSessionInfoDetails(args: INsCapabilities, sessionInfoDetails: any): IDevice; static setDontKeepActivities(args: INsCapabilities, driver: any, value: any): Promise; static executeShellCommand(driver: any, commandArgs: { command: string; diff --git a/lib/device-manager.ts b/lib/device-manager.ts index 94478a5..385f0ab 100644 --- a/lib/device-manager.ts +++ b/lib/device-manager.ts @@ -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 { @@ -218,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 ... diff --git a/lib/parser.d.ts b/lib/parser.d.ts index 40edb2e..386ae5d 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, path: string, relaxedSecurity: boolean, cleanApp: boolean, attachToDebug: boolean, sessionId: string, startSession: boolean, capabilitiesName: string, imagesPath: string, startDeviceOptions: string, deviceTypeOrPlatform: string, device: import("mobile-devices-controller/lib/device").IDevice, driverConfig: any, logImageTypes: LogImageType[], appiumCaps: any; From 8130d8f023f3cca9f7f32194a932720897dc0b34 Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Fri, 1 Nov 2019 13:37:36 +0200 Subject: [PATCH 09/11] add option for derivedDataPath --- lib/appium-driver.ts | 22 +++++++++++++++------- lib/device-manager.d.ts | 2 +- lib/interfaces/ns-capabilities-args.d.ts | 1 + lib/interfaces/ns-capabilities-args.ts | 1 + lib/ns-capabilities.d.ts | 1 + lib/ns-capabilities.ts | 2 ++ lib/parser.d.ts | 2 +- lib/parser.ts | 5 +++++ 8 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index 8967e84..a9f8ae4 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"; @@ -280,6 +281,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); @@ -303,11 +306,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) { @@ -883,7 +886,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; @@ -900,6 +903,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/device-manager.d.ts b/lib/device-manager.d.ts index 17ca9a9..c6c8916 100644 --- a/lib/device-manager.d.ts +++ b/lib/device-manager.d.ts @@ -13,7 +13,7 @@ export declare class DeviceManager implements IDeviceManager { static getInstalledApps(device: IDevice): Promise; static getDefaultDevice(args: INsCapabilities, deviceName?: string, token?: string, type?: DeviceType, platformVersion?: number): IDevice; private static convertViewportRectToIRectangle; - static applyAppiumSessionInfoDetails(args: INsCapabilities, sessionInfoDetails: any): IDevice; + static applyAppiumSessionInfoDetails(args: INsCapabilities, sessionInfoDetails: any): any; static setDontKeepActivities(args: INsCapabilities, driver: any, value: any): Promise; static executeShellCommand(driver: any, commandArgs: { command: string; 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 386ae5d..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: import("mobile-devices-controller/lib/device").IDevice, 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, From e98b4a23162545aeb479ec6c7a56fa0adc80ed2f Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Mon, 4 Nov 2019 15:03:10 +0200 Subject: [PATCH 10/11] fix: duration --- lib/ui-element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui-element.ts b/lib/ui-element.ts index 1bcf09a..62a37fa 100644 --- a/lib/ui-element.ts +++ b/lib/ui-element.ts @@ -506,13 +506,13 @@ export class UIElement { let action = new this._wd.TouchAction(this._driver); action .longPress({ x: x, y: y }) - .wait(endPoint.duration) + .wait(duration) .moveTo({ x: yOffset, y: yOffset }) .release(); await action.perform(); } else { await this._driver.execute(`mobile: dragFromToForDuration`, { - duration: endPoint.duration, + duration: duration, fromX: x, fromY: y, toX: xOffset, From baa4ff119cf4c18db22b64a5de651e3588c8ad1c Mon Sep 17 00:00:00 2001 From: SvetoslavTsenov Date: Fri, 8 Nov 2019 19:40:00 +0200 Subject: [PATCH 11/11] fix: match ios device by type and name --- lib/appium-driver.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/appium-driver.ts b/lib/appium-driver.ts index a9f8ae4..50fa929 100644 --- a/lib/appium-driver.ts +++ b/lib/appium-driver.ts @@ -344,10 +344,16 @@ 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)); - if (matches && matches.length > 0) { - const deviceType = matches[matches.length - 1]; + 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; }