From de0599fc017346705cb43a6955eef5d31dfcc4c2 Mon Sep 17 00:00:00 2001 From: Andrii Ulozhenko Date: Fri, 22 Dec 2023 13:28:52 +0200 Subject: [PATCH 1/7] Add start position as an optional parameter to scrollTo action --- .../com/wix/detox/espresso/DetoxAction.java | 9 ++++-- .../detox/espresso/scroll/ScrollHelper.java | 6 ++-- detox/detox.d.ts | 7 ++-- .../Detox/Actions/UIScrollView+DetoxActions.h | 2 ++ .../Detox/Actions/UIScrollView+DetoxActions.m | 32 +++++++++++++++++++ detox/ios/Detox/Invocation/Action.swift | 18 +++++++++-- detox/ios/Detox/Invocation/Element.swift | 9 ++++-- detox/src/android/actions/native.js | 4 +-- detox/src/android/core/NativeElement.js | 6 ++-- detox/src/android/espressoapi/DetoxAction.js | 10 +++++- detox/src/ios/expectTwo.js | 9 ++++-- .../src/utils/invocationTraceDescriptions.js | 5 +-- detox/test/e2e/03.actions-scroll.test.js | 26 +++++++++++++++ docs/api/actions.md | 8 +++-- 14 files changed, 126 insertions(+), 25 deletions(-) diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java index 8474e3526d..129de82bdd 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java @@ -79,9 +79,14 @@ public float[] calculateCoordinates(View view) { * Scrolls to the edge of the given scrollable view. * * @param edge Direction to scroll (see {@link MotionDir}) + * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. + * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. * @return ViewAction */ - public static ViewAction scrollToEdge(final int edge) { + public static ViewAction scrollToEdge(final int edge, double startOffsetPercentX, double startOffsetPercentY) { + + final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX; + final Float _startOffsetPercentY = startOffsetPercentY < 0 ? null : (float) startOffsetPercentY; return actionWithAssertions(new ViewAction() { @Override public Matcher getConstraints() { @@ -97,7 +102,7 @@ public String getDescription() { public void perform(UiController uiController, View view) { try { for (int i = 0; i < 100; i++) { - ScrollHelper.performOnce(uiController, view, edge); + ScrollHelper.performOnce(uiController, view, edge, _startOffsetPercentX, _startOffsetPercentY); } throw new DetoxRuntimeException("Scrolling a lot without reaching the edge: force-breaking the loop"); } catch (ScrollEdgeException e) { diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java index bfca25cb1f..2ab78dbeb0 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java @@ -64,10 +64,12 @@ public static void perform(UiController uiController, View view, @MotionDir int * of the screen.) * * @param direction Direction to scroll (see {@link @MotionDir}) + * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. Null means select automatically. + * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. Null means select automatically. */ - public static void performOnce(UiController uiController, View view, @MotionDir int direction) throws ScrollEdgeException { + public static void performOnce(UiController uiController, View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException { final int scrollableRangePx = getViewSafeScrollableRangePix(view, direction); - scrollOnce(uiController, view, direction, scrollableRangePx, null, null); + scrollOnce(uiController, view, direction, scrollableRangePx, startOffsetPercentX, startOffsetPercentY); } private static void scrollOnce(UiController uiController, View view, @MotionDir int direction, int userAmountPx, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException { diff --git a/detox/detox.d.ts b/detox/detox.d.ts index 86314cccdc..b39ae586c9 100644 --- a/detox/detox.d.ts +++ b/detox/detox.d.ts @@ -1363,10 +1363,13 @@ declare global { /** * Scroll to edge. - * @example await element(by.id('scrollView')).scrollTo('bottom'); + * @param edge - left|right|top|bottom + * @param startPositionX - the X starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically + * @param startPositionY - the Y starting scroll position, in percentage; valid input: `[0.0, 1.0]`, `NaN`; default: `NaN`—choose the best value automatically + * @example await element(by.id('scrollView')).scrollTo('bottom', NaN, 0.85); * @example await element(by.id('scrollView')).scrollTo('top'); */ - scrollTo(edge: Direction): Promise; + scrollTo(edge: Direction, startPositionX?: number, startPositionY?: number): Promise; /** * Adjust slider to position. diff --git a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.h b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.h index c2c0638fd0..5719a0ca57 100644 --- a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.h +++ b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.h @@ -13,6 +13,8 @@ NS_ASSUME_NONNULL_BEGIN @interface UIScrollView (DetoxActions) - (void)dtx_scrollToEdge:(UIRectEdge)edge NS_SWIFT_NAME(dtx_scroll(to:)); +- (void)dtx_scrollToEdge:(UIRectEdge)edge + normalizedStartingPoint:(CGPoint)normalizedStartingPoint; - (void)dtx_scrollWithOffset:(CGPoint)offset; - (void)dtx_scrollWithOffset:(CGPoint)offset normalizedStartingPoint:(CGPoint)normalizedStartingPoint NS_SWIFT_NAME(dtx_scroll(withOffset:normalizedStartingPoint:)); diff --git a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m index 5bcd147473..38210eb2f6 100644 --- a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m +++ b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m @@ -121,6 +121,38 @@ - (void)_dtx_scrollToNormalizedEdge:(CGPoint)edge [self _dtx_scrollWithOffset:CGPointMake(- edge.x * CGFLOAT_MAX, - edge.y * CGFLOAT_MAX) normalizedStartingPoint:CGPointMake(NAN, NAN) strict:NO]; } +- (void)dtx_scrollToEdge:(UIRectEdge)edge + normalizedStartingPoint:(CGPoint)normalizedStartingPoint +{ + CGPoint normalizedEdge; + switch (edge) { + case UIRectEdgeTop: + normalizedEdge = CGPointMake(0, -1); + break; + case UIRectEdgeBottom: + normalizedEdge = CGPointMake(0, 1); + break; + case UIRectEdgeLeft: + normalizedEdge = CGPointMake(-1, 0); + break; + case UIRectEdgeRight: + normalizedEdge = CGPointMake(1, 0); + break; + default: + DTXAssert(NO, @"Incorect edge provided."); + return; + } + + [self _dtx_scrollToNormalizedEdge:normalizedEdge normalizedStartingPoint: normalizedStartingPoint ]; +} + +- (void)_dtx_scrollToNormalizedEdge:(CGPoint)edge + normalizedStartingPoint:(CGPoint)normalizedStartingPoint +{ + [self _dtx_scrollWithOffset:CGPointMake(- edge.x * CGFLOAT_MAX, - edge.y * CGFLOAT_MAX) normalizedStartingPoint:normalizedStartingPoint strict:NO]; +} + + DTX_ALWAYS_INLINE static NSString* _DTXScrollDirectionDescriptionWithOffset(CGPoint offset) { diff --git a/detox/ios/Detox/Invocation/Action.swift b/detox/ios/Detox/Invocation/Action.swift index a4ac09db30..8cd14cca83 100644 --- a/detox/ios/Detox/Invocation/Action.swift +++ b/detox/ios/Detox/Invocation/Action.swift @@ -435,8 +435,22 @@ class ScrollToEdgeAction : Action { break; } - element.scroll(to: targetEdge) - + let startPositionX : Double + if params?.count ?? 0 > 1, let param1 = params?[1] as? Double, param1.isNaN == false { + startPositionX = param1 + } else { + startPositionX = Double.nan + } + let startPositionY : Double + if params?.count ?? 0 > 2, let param2 = params?[2] as? Double, param2.isNaN == false { + startPositionY = param2 + } else { + startPositionY = Double.nan + } + let normalizedStartingPoint = CGPoint(x: startPositionX, y: startPositionY) + + element.scroll(to: targetEdge, normalizedStartingPoint: normalizedStartingPoint) + return nil } } diff --git a/detox/ios/Detox/Invocation/Element.swift b/detox/ios/Detox/Invocation/Element.swift index 5570ab5ba5..378608b8af 100644 --- a/detox/ios/Detox/Invocation/Element.swift +++ b/detox/ios/Detox/Invocation/Element.swift @@ -140,10 +140,13 @@ class Element : NSObject { view.dtx_pinch(withScale: scale, velocity: velocity, angle: angle) } - func scroll(to edge: UIRectEdge) { + func scroll(to edge: UIRectEdge, normalizedStartingPoint: CGPoint? = nil) { let scrollView = extractScrollView() - - scrollView.dtx_scroll(to: edge) + if let normalizedStartingPoint = normalizedStartingPoint { + scrollView.dtx_scroll(to: edge, normalizedStarting: normalizedStartingPoint) + } else { + scrollView.dtx_scroll(to: edge) + } } func scroll(withOffset offset: CGPoint, normalizedStartingPoint: CGPoint? = nil) { diff --git a/detox/src/android/actions/native.js b/detox/src/android/actions/native.js index 601bef562d..7b28efbed5 100644 --- a/detox/src/android/actions/native.js +++ b/detox/src/android/actions/native.js @@ -81,10 +81,10 @@ class ScrollAmountStopAtEdgeAction extends Action { } class ScrollEdgeAction extends Action { - constructor(edge) { + constructor(edge, startPositionX = -1, startPositionY = -1) { super(); - this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge)); + this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge, startPositionX, startPositionY)); } } diff --git a/detox/src/android/core/NativeElement.js b/detox/src/android/core/NativeElement.js index 1ecbfa7597..d493a83e8c 100644 --- a/detox/src/android/core/NativeElement.js +++ b/detox/src/android/core/NativeElement.js @@ -93,12 +93,12 @@ class NativeElement { return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } - async scrollTo(edge) { + async scrollTo(edge, startPositionX, startPositionY) { // override the user's element selection with an extended matcher that looks for UIScrollView children this._matcher = this._matcher._extendToDescendantScrollViews(); - const action = new actions.ScrollEdgeAction(edge); - const traceDescription = actionDescription.scrollTo(edge); + const action = new actions.ScrollEdgeAction(edge, startPositionX, startPositionY); + const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } diff --git a/detox/src/android/espressoapi/DetoxAction.js b/detox/src/android/espressoapi/DetoxAction.js index 6ed2d213f8..df5c313900 100644 --- a/detox/src/android/espressoapi/DetoxAction.js +++ b/detox/src/android/espressoapi/DetoxAction.js @@ -68,8 +68,10 @@ class DetoxAction { }; } - static scrollToEdge(edge) { + static scrollToEdge(edge, startOffsetPercentX, startOffsetPercentY) { if (typeof edge !== "string") throw new Error("edge should be a string, but got " + (edge + (" (" + (typeof edge + ")")))); + if (typeof startOffsetPercentX !== "number") throw new Error("startOffsetPercentX should be a number, but got " + (startOffsetPercentX + (" (" + (typeof startOffsetPercentX + ")")))); + if (typeof startOffsetPercentY !== "number") throw new Error("startOffsetPercentY should be a number, but got " + (startOffsetPercentY + (" (" + (typeof startOffsetPercentY + ")")))); return { target: { type: "Class", @@ -79,6 +81,12 @@ class DetoxAction { args: [{ type: "Integer", value: sanitize_android_edge(edge) + }, { + type: "Double", + value: startOffsetPercentX + }, { + type: "Double", + value: startOffsetPercentY }] }; } diff --git a/detox/src/ios/expectTwo.js b/detox/src/ios/expectTwo.js index b2153b4386..f89b7f26ae 100644 --- a/detox/src/ios/expectTwo.js +++ b/detox/src/ios/expectTwo.js @@ -247,10 +247,13 @@ class Element { return this.withAction('scroll', traceDescription, pixels, direction, startPositionX, startPositionY); } - scrollTo(edge) { + scrollTo(edge, startPositionX = NaN, startPositionY = NaN) { if (!['left', 'right', 'top', 'bottom'].some(option => option === edge)) throw new Error('edge should be one of [left, right, top, bottom], but got ' + edge); - const traceDescription = actionDescription.scrollTo(edge); - return this.withAction('scrollTo', traceDescription, edge); + if (typeof startPositionX !== 'number') throw new Error('startPositionX should be a number, but got ' + (startPositionX + (' (' + (typeof startPositionX + ')')))); + if (typeof startPositionY !== 'number') throw new Error('startPositionY should be a number, but got ' + (startPositionY + (' (' + (typeof startPositionY + ')')))); + + const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY); + return this.withAction('scrollTo', traceDescription, edge, startPositionX, startPositionY); } swipe(direction, speed = 'fast', normalizedSwipeOffset = NaN, normalizedStartingPointX = NaN, normalizedStartingPointY = NaN) { diff --git a/detox/src/utils/invocationTraceDescriptions.js b/detox/src/utils/invocationTraceDescriptions.js index a9168327ae..63969bf635 100644 --- a/detox/src/utils/invocationTraceDescriptions.js +++ b/detox/src/utils/invocationTraceDescriptions.js @@ -12,8 +12,9 @@ module.exports = { pinchWithAngle: (direction, speed, angle) => `pinch with direction ${direction}, speed ${speed}, and angle ${angle}`, replaceText: (value) => `replace input text: "${value}"`, scroll: (amount, direction, startPositionX, startPositionY) => - `scroll ${amount} pixels ${direction}${startPositionX !== undefined && startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`, - scrollTo: (edge) => `scroll to ${edge}`, + `scroll ${amount} pixels ${direction}${startPositionX !== undefined || startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`, + scrollTo: (edge, startPositionX, startPositionY) => + `scroll to ${edge} ${startPositionX !== undefined || startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`, scrollToIndex: (index) => `scroll to index #${index}`, setColumnToValue: (column, value) => `set column ${column} to value ${value}`, setDatePickerDate: (dateString, dateFormat) => `set date picker date to ${dateString} using format ${dateFormat}`, diff --git a/detox/test/e2e/03.actions-scroll.test.js b/detox/test/e2e/03.actions-scroll.test.js index e4cb32f444..6615d6ea4b 100644 --- a/detox/test/e2e/03.actions-scroll.test.js +++ b/detox/test/e2e/03.actions-scroll.test.js @@ -46,6 +46,32 @@ describe('Actions - Scroll', () => { await expect(element(by.text('HText1'))).toBeVisible(); }); + it('should scroll to edge from a custom start-position ratio', async () => { + await expect(element(by.text('Text12'))).not.toBeVisible(); + await element(by.id('toggleScrollOverlays')).tap(); + await element(by.id('ScrollView161')).scrollTo('bottom', 0.2, 0.4); + await element(by.id('toggleScrollOverlays')).tap(); + await expect(element(by.text('Text12'))).toBeVisible(); + + await element(by.id('toggleScrollOverlays')).tap(); + await element(by.id('ScrollView161')).scrollTo('top', 0.8, 0.6); + await element(by.id('toggleScrollOverlays')).tap(); + await expect(element(by.text('Text1'))).toBeVisible(); + }); + + it('should scroll to edge horizontally from a custom start-position ratio', async () => { + await expect(element(by.text('HText8'))).not.toBeVisible(); + await element(by.id('toggleScrollOverlays')).tap(); + await element(by.id('ScrollViewH')).scrollTo('right', 0.8, 0.6); + await element(by.id('toggleScrollOverlays')).tap(); + await expect(element(by.text('HText8'))).toBeVisible(); + + await element(by.id('toggleScrollOverlays')).tap(); + await element(by.id('ScrollViewH')).scrollTo('left',0.2, 0.4); + await element(by.id('toggleScrollOverlays')).tap(); + await expect(element(by.text('HText1'))).toBeVisible(); + }); + it('should scroll from a custom start-position ratio', async () => { await expect(element(by.text('Text12'))).not.toBeVisible(); await element(by.id('toggleScrollOverlays')).tap(); diff --git a/docs/api/actions.md b/docs/api/actions.md index c249c0aa67..4c6d9f2891 100644 --- a/docs/api/actions.md +++ b/docs/api/actions.md @@ -144,15 +144,17 @@ Continuously scrolls the scroll element until the specified expectation is resol await waitFor(element(by.text('Text5'))).toBeVisible().whileElement(by.id('ScrollView630')).scroll(50, 'down'); ``` -### `scrollTo(edge)` +### `scrollTo(edge[, startPositionX, startPositionY])` Simulates a scroll to the specified edge. -`edge`—the edge to scroll to (valid input: `"left"`/`"right"`/`"top"`/`"bottom"`) +`edge`—the edge to scroll to (valid input: `"left"`/`"right"`/`"top"`/`"bottom"`)
+`startPositionX`—the normalized x percentage of the element to use as scroll start point (optional, valid input: \[0.0, 1.0], `NaN`—choose an optimal value automatically, default is `NaN`)
+`startPositionY`—the normalized y percentage of the element to use as scroll start point (optional, valid input: \[0.0, 1.0], `NaN`—choose an optimal value automatically, default is `NaN`) ```js await element(by.id('scrollView')).scrollTo('bottom'); -await element(by.id('scrollView')).scrollTo('top'); +await element(by.id('scrollView')).scrollTo('top', NaN, 0.2); ``` ### `typeText(text)` From 1cca74aec89136f5693c67275df16beee9706e7d Mon Sep 17 00:00:00 2001 From: Andrii Ulozhenko Date: Fri, 22 Dec 2023 14:55:21 +0200 Subject: [PATCH 2/7] Fix test coverage --- detox/src/ios/expectTwoApiCoverage.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/detox/src/ios/expectTwoApiCoverage.test.js b/detox/src/ios/expectTwoApiCoverage.test.js index 2980bbdb4d..442366d75b 100644 --- a/detox/src/ios/expectTwoApiCoverage.test.js +++ b/detox/src/ios/expectTwoApiCoverage.test.js @@ -181,6 +181,8 @@ describe('expectTwo API Coverage', () => { await expectToThrow(() => e.element(e.by.id('someId')).scrollTo(0)); await expectToThrow(() => e.element(e.by.id('someId')).scrollTo('noDirection')); + await expectToThrow(() => e.element(e.by.id('someId')).scrollTo('top','Nan', 0.5)); + await expectToThrow(() => e.element(e.by.id('someId')).scrollTo('top', 0.5, 'Nan')); await expectToThrow(() => e.element(e.by.id('someId')).swipe(4, 'fast')); await expectToThrow(() => e.element(e.by.id('someId')).swipe('left', 'fast', 20)); @@ -266,6 +268,7 @@ describe('expectTwo API Coverage', () => { await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).scroll(50, 'down'); await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).scroll(50, 'down', 0, 0); await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).scrollTo('left'); + await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).scrollTo('left', 0.1, 0.1); await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).swipe('left'); await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).swipe('left', 'fast'); await e.waitFor(e.element(e.by.id('id'))).toBeVisible().whileElement(e.by.id('id2')).swipe('left', 'slow', 0.1); From 69b340e2f55a4a27e39d537b004aa88b57b4e08e Mon Sep 17 00:00:00 2001 From: Andrey Ulozhenko <89072573+andrey-wix@users.noreply.github.com> Date: Wed, 3 Jan 2024 08:56:19 +0200 Subject: [PATCH 3/7] Update detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java Co-authored-by: Asaf Korem <55082339+asafkorem@users.noreply.github.com> --- .../detox/src/full/java/com/wix/detox/espresso/DetoxAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java index 129de82bdd..80fed3bcda 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java @@ -84,9 +84,9 @@ public float[] calculateCoordinates(View view) { * @return ViewAction */ public static ViewAction scrollToEdge(final int edge, double startOffsetPercentX, double startOffsetPercentY) { - final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX; final Float _startOffsetPercentY = startOffsetPercentY < 0 ? null : (float) startOffsetPercentY; + return actionWithAssertions(new ViewAction() { @Override public Matcher getConstraints() { From 73303f85c8f0ca94f595dd5afde0720ff7e0ddf8 Mon Sep 17 00:00:00 2001 From: Andrey Ulozhenko <89072573+andrey-wix@users.noreply.github.com> Date: Wed, 3 Jan 2024 08:56:40 +0200 Subject: [PATCH 4/7] Update detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java Co-authored-by: Asaf Korem <55082339+asafkorem@users.noreply.github.com> --- .../src/full/java/com/wix/detox/espresso/DetoxAction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java index 80fed3bcda..7eede5706e 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java @@ -79,8 +79,8 @@ public float[] calculateCoordinates(View view) { * Scrolls to the edge of the given scrollable view. * * @param edge Direction to scroll (see {@link MotionDir}) - * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. - * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. + * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. + * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. * @return ViewAction */ public static ViewAction scrollToEdge(final int edge, double startOffsetPercentX, double startOffsetPercentY) { From ccc0d8903990f36e15a4c70b2d8ef5af27ae893e Mon Sep 17 00:00:00 2001 From: Andrey Ulozhenko <89072573+andrey-wix@users.noreply.github.com> Date: Wed, 3 Jan 2024 08:56:54 +0200 Subject: [PATCH 5/7] Update detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java Co-authored-by: Asaf Korem <55082339+asafkorem@users.noreply.github.com> --- .../main/java/com/wix/detox/espresso/scroll/ScrollHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java index 2ab78dbeb0..99ca4e6826 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java @@ -64,8 +64,8 @@ public static void perform(UiController uiController, View view, @MotionDir int * of the screen.) * * @param direction Direction to scroll (see {@link @MotionDir}) - * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. Null means select automatically. - * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. Null means select automatically. + * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. Null means select automatically. + * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. Null means select automatically. */ public static void performOnce(UiController uiController, View view, @MotionDir int direction, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException { final int scrollableRangePx = getViewSafeScrollableRangePix(view, direction); From d52c3d6dc439be59a1d47d8aa2f0ef688f83f5ff Mon Sep 17 00:00:00 2001 From: Andrii Ulozhenko Date: Wed, 3 Jan 2024 09:04:25 +0200 Subject: [PATCH 6/7] Fix comments --- .../src/full/java/com/wix/detox/espresso/DetoxAction.java | 8 ++++---- .../java/com/wix/detox/espresso/scroll/ScrollHelper.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java index 7eede5706e..8ffa60d061 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java @@ -117,8 +117,8 @@ public void perform(UiController uiController, View view) { * * @param direction Direction to scroll (see {@link MotionDir}) * @param amountInDP Density Independent Pixels - * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. - * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. + * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. + * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. */ public static ViewAction scrollInDirection(final int direction, final double amountInDP, double startOffsetPercentX, double startOffsetPercentY) { final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX; @@ -134,8 +134,8 @@ public static ViewAction scrollInDirection(final int direction, final double amo * * @param direction Direction to scroll (see {@link MotionDir}) * @param amountInDP Density Independent Pixels - * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. - * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. + * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. + * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. */ public static ViewAction scrollInDirectionStaleAtEdge(final int direction, final double amountInDP, double startOffsetPercentX, double startOffsetPercentY) { final Float _startOffsetPercentX = startOffsetPercentX < 0 ? null : (float) startOffsetPercentX; diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java index 99ca4e6826..ac08100841 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/scroll/ScrollHelper.java @@ -42,8 +42,8 @@ private ScrollHelper() { * * @param direction Direction to scroll (see {@link MotionDir}) * @param amountInDP Density Independent Pixels - * @param startOffsetPercentX Percentage denoting where X-swipe should start, with respect to the scrollable view. Null means select automatically. - * @param startOffsetPercentY Percentage denoting where Y-swipe should start, with respect to the scrollable view. Null means select automatically. + * @param startOffsetPercentX Percentage denoting where the scroll should start from on the X-axis, with respect to the scrollable view. Null means select automatically. + * @param startOffsetPercentY Percentage denoting where the scroll should start from on the Y-axis, with respect to the scrollable view. Null means select automatically. */ public static void perform(UiController uiController, View view, @MotionDir int direction, double amountInDP, Float startOffsetPercentX, Float startOffsetPercentY) throws ScrollEdgeException { final int amountInPx = DeviceDisplay.convertDpiToPx(amountInDP); From 728d8b3e365ad0f6527a8eda3cb5628894afb64d Mon Sep 17 00:00:00 2001 From: Andrii Ulozhenko Date: Wed, 3 Jan 2024 10:20:47 +0200 Subject: [PATCH 7/7] Fix review --- .../Detox/Actions/UIScrollView+DetoxActions.m | 36 +++++++---------- detox/ios/Detox/Invocation/Action.swift | 40 +++++++------------ 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m index 38210eb2f6..d78c470e48 100644 --- a/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m +++ b/detox/ios/Detox/Actions/UIScrollView+DetoxActions.m @@ -83,7 +83,7 @@ @implementation UIScrollView (DetoxActions) [self setContentOffset:pointMakeMacro(target) animated:YES]; \ [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:[[self valueForKeyPath:@"animation.duration"] doubleValue] + 0.05]]; -- (void)dtx_scrollToEdge:(UIRectEdge)edge +- (CGPoint)_edgeToNormalizedEdge:(UIRectEdge)edge { CGPoint normalizedEdge; switch (edge) { @@ -100,10 +100,19 @@ - (void)dtx_scrollToEdge:(UIRectEdge)edge normalizedEdge = CGPointMake(1, 0); break; default: + normalizedEdge= CGPointMake(0, 0); DTXAssert(NO, @"Incorect edge provided."); - return; } - + return normalizedEdge; +} + + +- (void)dtx_scrollToEdge:(UIRectEdge)edge +{ + CGPoint normalizedEdge = [self _edgeToNormalizedEdge:edge]; + if(normalizedEdge.x == 0 && normalizedEdge.y == 0) + return; + [self _dtx_scrollToNormalizedEdge:normalizedEdge]; } @@ -124,24 +133,9 @@ - (void)_dtx_scrollToNormalizedEdge:(CGPoint)edge - (void)dtx_scrollToEdge:(UIRectEdge)edge normalizedStartingPoint:(CGPoint)normalizedStartingPoint { - CGPoint normalizedEdge; - switch (edge) { - case UIRectEdgeTop: - normalizedEdge = CGPointMake(0, -1); - break; - case UIRectEdgeBottom: - normalizedEdge = CGPointMake(0, 1); - break; - case UIRectEdgeLeft: - normalizedEdge = CGPointMake(-1, 0); - break; - case UIRectEdgeRight: - normalizedEdge = CGPointMake(1, 0); - break; - default: - DTXAssert(NO, @"Incorect edge provided."); - return; - } + CGPoint normalizedEdge = [self _edgeToNormalizedEdge:edge]; + if(normalizedEdge.x == 0 && normalizedEdge.y == 0) + return; [self _dtx_scrollToNormalizedEdge:normalizedEdge normalizedStartingPoint: normalizedStartingPoint ]; } diff --git a/detox/ios/Detox/Invocation/Action.swift b/detox/ios/Detox/Invocation/Action.swift index 8cd14cca83..1a4470d741 100644 --- a/detox/ios/Detox/Invocation/Action.swift +++ b/detox/ios/Detox/Invocation/Action.swift @@ -129,6 +129,15 @@ class Action : CustomStringConvertible { } } + func startPosition(forIndex index: Int, in params: [Any]?) -> Double { + guard params?.count ?? 0 > index, + let param = params?[index] as? Double, + param.isNaN == false else { + return Double.nan + } + return param + } + var description: String { let paramsDescription: String if let params = params { @@ -434,19 +443,9 @@ class ScrollToEdgeAction : Action { fatalError("Unknown scroll direction") break; } - - let startPositionX : Double - if params?.count ?? 0 > 1, let param1 = params?[1] as? Double, param1.isNaN == false { - startPositionX = param1 - } else { - startPositionX = Double.nan - } - let startPositionY : Double - if params?.count ?? 0 > 2, let param2 = params?[2] as? Double, param2.isNaN == false { - startPositionY = param2 - } else { - startPositionY = Double.nan - } + + let startPositionX = startPosition(forIndex: 1, in: params) + let startPositionY = startPosition(forIndex: 2, in: params) let normalizedStartingPoint = CGPoint(x: startPositionX, y: startPositionY) element.scroll(to: targetEdge, normalizedStartingPoint: normalizedStartingPoint) @@ -501,18 +500,9 @@ class SwipeAction : Action { targetNormalizedOffset.x *= CGFloat(appliedPercentage) targetNormalizedOffset.y *= CGFloat(appliedPercentage) - let startPositionX : Double - if params?.count ?? 0 > 3, let param2 = params?[3] as? Double, param2.isNaN == false { - startPositionX = param2 - } else { - startPositionX = Double.nan - } - let startPositionY : Double - if params?.count ?? 0 > 4, let param3 = params?[4] as? Double, param3.isNaN == false { - startPositionY = param3 - } else { - startPositionY = Double.nan - } + + let startPositionX = startPosition(forIndex: 3, in: params) + let startPositionY = startPosition(forIndex: 4, in: params) let normalizedStartingPoint = CGPoint(x: startPositionX, y: startPositionY) element.swipe(normalizedOffset: targetNormalizedOffset, velocity: velocity, normalizedStartingPoint: normalizedStartingPoint)