diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java index 03d5692ef0..63bb5a0203 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java @@ -24,42 +24,57 @@ public class DetoxAssertion { + private static final double NANOSECONDS_IN_A_SECOND = 1_000_000_000.0; + private DetoxAssertion() { - // static class + // This is a utility class and shouldn't be instantiated. } - public static ViewInteraction assertMatcher(ViewInteraction i, Matcher m) { - return i.check(matches(m)); + /** + * Asserts the given matcher for the provided view interaction. + */ + public static ViewInteraction assertMatcher(ViewInteraction viewInteraction, Matcher viewMatcher) { + return viewInteraction.check(matches(viewMatcher)); } - public static ViewInteraction assertNotVisible(ViewInteraction i) { - ViewInteraction ret; + /** + * Asserts that the given view interaction is not visible. + */ + public static ViewInteraction assertNotVisible(ViewInteraction viewInteraction) { + ViewInteraction result; try { - ret = i.check(doesNotExist()); - return ret; + result = viewInteraction.check(doesNotExist()); + return result; } catch (AssertionFailedError e) { - ret = i.check(matches(not(isDisplayed()))); - return ret; + result = viewInteraction.check(matches(not(isDisplayed()))); + return result; } } - public static ViewInteraction assertNotExists(ViewInteraction i) { - return i.check(doesNotExist()); + /** + * Asserts that the given view interaction does not exist. + */ + public static ViewInteraction assertNotExists(ViewInteraction viewInteraction) { + return viewInteraction.check(doesNotExist()); } - public static void waitForAssertMatcher(final ViewInteraction i, final Matcher m, double timeoutSeconds) { - final long originTime = System.nanoTime(); + /** + * Waits until the provided matcher matches the view interaction or a timeout occurs. + */ + public static void waitForAssertMatcher(final ViewInteraction viewInteraction, final Matcher viewMatcher, double timeoutSeconds) { + final long startTime = System.nanoTime(); while (true) { long currentTime = System.nanoTime(); - long elapsed = currentTime - originTime; - double seconds = (double) elapsed / 1000000000.0; - if (seconds >= timeoutSeconds) { - throw new DetoxRuntimeException("" + timeoutSeconds + "sec timeout expired without matching of given matcher: " + m); + long elapsedTime = currentTime - startTime; + double elapsedSeconds = (double) elapsedTime / NANOSECONDS_IN_A_SECOND; + if (elapsedSeconds >= timeoutSeconds) { + throw new DetoxRuntimeException( + "" + timeoutSeconds + "sec timeout expired without matching of given matcher: " + viewMatcher); } try { - i.check(matches(m)); + viewInteraction.check(matches(viewMatcher)); break; } catch (AssertionFailedError err) { UiAutomatorHelper.espressoSync(20); @@ -67,21 +82,25 @@ public static void waitForAssertMatcher(final ViewInteraction i, final Matcher vm, - final ViewAction searchAction, - final Matcher searchMatcher) { - + final ViewInteraction viewInteraction, + final Matcher viewMatcher, + final ViewAction searchAction, + final Matcher searchMatcher + ) { while (true) { try { - assertMatcher(i, vm); + assertMatcher(viewInteraction, viewMatcher); break; } catch (AssertionFailedError err) { try { onView(searchMatcher).perform(searchAction); } catch (StaleActionException exStaleAction) { - assertMatcher(i, vm); + assertMatcher(viewInteraction, viewMatcher); break; } } diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java b/detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java index 1a96dddc68..d9cf3eb4cf 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java @@ -1,5 +1,7 @@ package com.wix.detox.espresso; +import com.wix.detox.espresso.performer.ViewActionPerformer; + import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; @@ -20,6 +22,7 @@ import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.NoMatchingViewException; import androidx.test.platform.app.InstrumentationRegistry; import static androidx.test.espresso.Espresso.onView; @@ -31,13 +34,9 @@ public class EspressoDetox { private static final String LOG_TAG = "detox"; - public static Object perform(ViewInteraction interaction, ViewAction action) { - interaction.perform(action); - - if (action instanceof ViewActionWithResult) { - return ((ViewActionWithResult) action).getResult(); - } - return null; + public static Object perform(Matcher matcher, ViewAction action) { + ViewActionPerformer performer = ViewActionPerformer.forAction(action); + return performer.performOn(matcher); } public static Activity getActivity(Context context) { diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt new file mode 100644 index 0000000000..2fb8c8154c --- /dev/null +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt @@ -0,0 +1,45 @@ +package com.wix.detox.espresso.performer + +import com.wix.detox.espresso.DetoxMatcher +import com.wix.detox.espresso.ViewActionWithResult + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.ViewAction +import org.hamcrest.Matcher + +class MultipleViewsActionPerformer( + private val action: ViewAction +) : ViewActionPerformer { + override fun performOn(matcher: Matcher): Any? { + val results = mutableListOf() + var index = 0 + + while (true) { + val indexedMatcher = DetoxMatcher.matcherForAtIndex(index, matcher) + + try { + onView(indexedMatcher).perform(action) + + (action as? ViewActionWithResult<*>)?.getResult()?.let { results.add(it) } + + index++ + } catch (e: NoMatchingViewException) { + if (index == 0) { + throw e + } + + break + } + } + + return if (results.isEmpty()) { + null + } else if (results.size == 1) { + results.first() + } else { + mapOf("elements" to results) + } + } +} diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt new file mode 100644 index 0000000000..231dcd8fca --- /dev/null +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt @@ -0,0 +1,19 @@ +package com.wix.detox.espresso.performer + +import com.wix.detox.espresso.ViewActionWithResult + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.ViewAction +import org.hamcrest.Matcher + +class SingleViewActionPerformer( + private val action: ViewAction +) : ViewActionPerformer { + override fun performOn(matcher: Matcher): Any? { + onView(matcher).perform(action) + + return (action as? ViewActionWithResult<*>)?.getResult() + } +} diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt new file mode 100644 index 0000000000..23f38e5942 --- /dev/null +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt @@ -0,0 +1,24 @@ +package com.wix.detox.espresso.performer + +import com.wix.detox.espresso.MultipleViewsAction + +import android.view.View +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.ViewAction +import org.hamcrest.Matcher + +interface ViewActionPerformer { + fun performOn(matcher: Matcher): Any? + + companion object { + @JvmStatic + fun forAction(action: ViewAction): ViewActionPerformer { + return if (action is MultipleViewsAction) { + MultipleViewsActionPerformer(action) + } else { + SingleViewActionPerformer(action) + } + } + } +} diff --git a/detox/android/detox/src/full/java/com/wix/invoke/types/Invocation.java b/detox/android/detox/src/full/java/com/wix/invoke/types/Invocation.java index e73be34ad6..3195144976 100644 --- a/detox/android/detox/src/full/java/com/wix/invoke/types/Invocation.java +++ b/detox/android/detox/src/full/java/com/wix/invoke/types/Invocation.java @@ -92,9 +92,9 @@ public void setArgs(JSONArray args) throws JSONException { } else if (type.equals("boolean")) { argument = jsonArgument.optBoolean("value"); } else if (type.equals("Invocation")) { - argument = new Invocation(jsonArgument.optJSONObject("value")); + argument = new Invocation(jsonArgument.optJSONObject("value")); } else { - throw new RuntimeException("Unhandled arg type" + type); + throw new RuntimeException("Unhandled arg type " + type); } } } @@ -105,6 +105,8 @@ public void setArgs(JSONArray args) throws JSONException { } public void setArgs(Object[] args) { + JsonParser parser = new JsonParser(); + for (int i = 0; i < args.length; i++) { Object argument = args[i]; if (argument instanceof HashMap && !((HashMap) argument).isEmpty()) { @@ -125,10 +127,9 @@ public void setArgs(Object[] args) { } else if (type.equals("boolean")) { argument = ((Boolean) value).booleanValue(); } else if (type.equals("Invocation")) { - JsonParser parser = new JsonParser(); argument = parser.parse((String)value); } else { - throw new RuntimeException("Unhandled arg type" + type); + throw new RuntimeException("Unhandled arg type " + type); } args[i] = argument; diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt b/detox/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt new file mode 100644 index 0000000000..827e169efb --- /dev/null +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt @@ -0,0 +1,4 @@ +package com.wix.detox.espresso + +// Marker interface for actions that should be applied to all matching elements without ambiguity. +interface MultipleViewsAction diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt b/detox/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt index 63732f4075..742af777f0 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt @@ -1,4 +1,3 @@ - package com.wix.detox.espresso import androidx.test.espresso.UiController diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt b/detox/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt index 6c9f8e1605..70c98c75bb 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt @@ -2,6 +2,7 @@ package com.wix.detox.espresso import androidx.test.espresso.ViewAction -interface ViewActionWithResult: ViewAction { +// Interface for actions that return a result. +interface ViewActionWithResult : ViewAction { fun getResult(): R } diff --git a/detox/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt b/detox/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt new file mode 100644 index 0000000000..75fb4bd4e4 --- /dev/null +++ b/detox/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt @@ -0,0 +1,37 @@ +package com.wix.detox.espresso.performer + +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe +import androidx.test.espresso.ViewAction +import com.wix.detox.espresso.MultipleViewsAction +import org.hamcrest.Matcher +import org.mockito.Mockito.* +import org.mockito.kotlin.mock + +object ViewActionPerformerSpec : Spek({ + + describe("ViewActionPerformer") { + context("forAction") { + context("given a regular ViewAction") { + val action = mock(ViewAction::class.java) + + it("should return a SingleViewActionPerformer") { + val performer = ViewActionPerformer.forAction(action) + assert(performer is SingleViewActionPerformer) + } + } + + context("given a MultipleViewsAction") { + val multipleViewsAction: ViewAction = mock( + ViewAction::class.java, + withSettings().extraInterfaces(MultipleViewsAction::class.java) + ) + + it("should return a MultipleViewsActionPerformer") { + val performer = ViewActionPerformer.forAction(multipleViewsAction) + assert(performer is MultipleViewsActionPerformer) + } + } + } + } +}) diff --git a/detox/src/android/core/NativeElement.js b/detox/src/android/core/NativeElement.js index 4e7a1969d0..df95c40d22 100644 --- a/detox/src/android/core/NativeElement.js +++ b/detox/src/android/core/NativeElement.js @@ -15,39 +15,37 @@ class NativeElement { constructor(invocationManager, emitter, matcher) { this._invocationManager = invocationManager; this._emitter = emitter; - this._originalMatcher = matcher; - this._selectElementWithMatcher(this._originalMatcher); + this._matcher = matcher; } - _selectElementWithMatcher(matcher) { - this._call = invoke.call(invoke.Espresso, 'onView', matcher._call); + get _call() { + return invoke.call(invoke.Espresso, 'onView', this._matcher._call); } atIndex(index) { if (typeof index !== 'number') throw new DetoxRuntimeError({ message: `Element atIndex argument must be a number, got ${typeof index}` }); - const matcher = this._originalMatcher; - this._originalMatcher._call = invoke.callDirectly(DetoxMatcherApi.matcherForAtIndex(index, matcher._call.value)); + const matcher = this._matcher; + this._matcher._call = invoke.callDirectly(DetoxMatcherApi.matcherForAtIndex(index, matcher._call.value)); - this._selectElementWithMatcher(this._originalMatcher); return this; } async tap(value) { const action = new actions.TapAction(value); const traceDescription = actionDescription.tapAtPoint(value); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapAtPoint(value) { const action = new actions.TapAtPointAction(value); const traceDescription = actionDescription.tapAtPoint(value); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async longPress() { const action = new actions.LongPressAction(); const traceDescription = actionDescription.longPress(); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async multiTap(times) { @@ -56,61 +54,61 @@ class NativeElement { const action = new actions.MultiClickAction(times); const traceDescription = actionDescription.multiTap(times); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapBackspaceKey() { const action = new actions.PressKeyAction(67); const traceDescription = actionDescription.tapBackspaceKey(); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapReturnKey() { const action = new actions.TypeTextAction('\n'); const traceDescription = actionDescription.tapReturnKey(); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async typeText(value) { const action = new actions.TypeTextAction(value); const traceDescription = actionDescription.typeText(value); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async replaceText(value) { const action = new actions.ReplaceTextAction(value); const traceDescription = actionDescription.replaceText(value); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async clearText() { const action = new actions.ClearTextAction(); const traceDescription = actionDescription.clearText(); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scroll(amount, direction = 'down', startPositionX, startPositionY) { const action = new actions.ScrollAmountAction(direction, amount, startPositionX, startPositionY); const traceDescription = actionDescription.scroll(amount, direction, startPositionX, startPositionY); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scrollTo(edge) { // override the user's element selection with an extended matcher that looks for UIScrollView children - this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews()); + this._matcher = this._matcher._extendToDescendantScrollViews(); const action = new actions.ScrollEdgeAction(edge); const traceDescription = actionDescription.scrollTo(edge); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scrollToIndex(index) { // override the user's element selection with an extended matcher that looks for UIScrollView children - this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews()); + this._matcher = this._matcher._extendToDescendantScrollViews(); const action = new actions.ScrollToIndex(index); const traceDescription = actionDescription.scrollToIndex(index); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async setDatePickerDate(rawDateString, formatString) { @@ -120,7 +118,7 @@ class NativeElement { const action = new actions.SetDatePickerDateAction(dateString, formatString); const traceDescription = actionDescription.setDatePickerDate(dateString, formatString); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } /** @@ -134,18 +132,18 @@ class NativeElement { normalizedSwipeOffset = Number.isNaN(normalizedSwipeOffset) ? 0.75 : normalizedSwipeOffset; // override the user's element selection with an extended matcher that avoids RN issues with RCTScrollView - this._selectElementWithMatcher(this._originalMatcher._avoidProblematicReactNativeElements()); + this._matcher = this._matcher._avoidProblematicReactNativeElements(); const action = new actions.SwipeAction(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY); const traceDescription = actionDescription.swipe(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async takeScreenshot(screenshotName) { // TODO this should be moved to a lower-layer handler of this use-case const action = new actions.TakeElementScreenshot(); const traceDescription = actionDescription.takeScreenshot(screenshotName); - const resultBase64 = await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + const resultBase64 = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); const filePath = tempfile('detox.element-screenshot.png'); await fs.writeFile(filePath, resultBase64, 'base64'); @@ -160,19 +158,19 @@ class NativeElement { async getAttributes() { const action = new actions.GetAttributes(); const traceDescription = actionDescription.getAttributes(); - const result = await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + const result = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); return JSON.parse(result); } async adjustSliderToPosition(newPosition) { const action = new actions.AdjustSliderToPosition(newPosition); const traceDescription = actionDescription.adjustSliderToPosition(newPosition); - return await new ActionInteraction(this._invocationManager, this, action, traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async performAccessibilityAction(actionName) { const traceDescription = actionDescription.performAccessibilityAction(actionName); - return await new ActionInteraction(this._invocationManager, this, new actions.AccessibilityActionAction(actionName), traceDescription).execute(); + return await new ActionInteraction(this._invocationManager, this._matcher, new actions.AccessibilityActionAction(actionName), traceDescription).execute(); } } diff --git a/detox/src/android/espressoapi/DetoxAssertion.js b/detox/src/android/espressoapi/DetoxAssertion.js index 31a672b1dd..b80ca6b857 100644 --- a/detox/src/android/espressoapi/DetoxAssertion.js +++ b/detox/src/android/espressoapi/DetoxAssertion.js @@ -14,7 +14,7 @@ function sanitize_matcher(matcher) { return originalMatcher.type ? originalMatcher.value : originalMatcher; } class DetoxAssertion { - static assertMatcher(i, m) { + static assertMatcher(viewInteraction, viewMatcher) { return { target: { type: "Class", @@ -23,15 +23,15 @@ class DetoxAssertion { method: "assertMatcher", args: [{ type: "Invocation", - value: i + value: viewInteraction }, { type: "Invocation", - value: sanitize_matcher(m) + value: sanitize_matcher(viewMatcher) }] }; } - static assertNotVisible(i) { + static assertNotVisible(viewInteraction) { return { target: { type: "Class", @@ -40,12 +40,12 @@ class DetoxAssertion { method: "assertNotVisible", args: [{ type: "Invocation", - value: i + value: viewInteraction }] }; } - static assertNotExists(i) { + static assertNotExists(viewInteraction) { return { target: { type: "Class", @@ -54,12 +54,12 @@ class DetoxAssertion { method: "assertNotExists", args: [{ type: "Invocation", - value: i + value: viewInteraction }] }; } - static waitForAssertMatcher(i, m, timeoutSeconds) { + static waitForAssertMatcher(viewInteraction, viewMatcher, timeoutSeconds) { if (typeof timeoutSeconds !== "number") throw new Error("timeoutSeconds should be a number, but got " + (timeoutSeconds + (" (" + (typeof timeoutSeconds + ")")))); return { target: { @@ -69,10 +69,10 @@ class DetoxAssertion { method: "waitForAssertMatcher", args: [{ type: "Invocation", - value: i + value: viewInteraction }, { type: "Invocation", - value: sanitize_matcher(m) + value: sanitize_matcher(viewMatcher) }, { type: "Double", value: timeoutSeconds @@ -80,7 +80,8 @@ class DetoxAssertion { }; } - static waitForAssertMatcherWithSearchAction(i, vm, searchAction, searchMatcher) { + static waitForAssertMatcherWithSearchAction(viewInteraction, viewMatcher, searchAction, searchMatcher + ) { return { target: { type: "Class", @@ -89,13 +90,14 @@ class DetoxAssertion { method: "waitForAssertMatcherWithSearchAction", args: [{ type: "Invocation", - value: i + value: viewInteraction }, { type: "Invocation", - value: sanitize_matcher(vm) + value: sanitize_matcher(viewMatcher) }, searchAction, { type: "Invocation", - value: sanitize_matcher(searchMatcher) + value: sanitize_matcher(searchMatcher + ) }] }; } diff --git a/detox/src/android/espressoapi/EspressoDetox.js b/detox/src/android/espressoapi/EspressoDetox.js index bb9f72793c..c4b59deec6 100644 --- a/detox/src/android/espressoapi/EspressoDetox.js +++ b/detox/src/android/espressoapi/EspressoDetox.js @@ -5,9 +5,16 @@ */ +function sanitize_matcher(matcher) { + if (!matcher._call) { + return matcher; + } + const originalMatcher = typeof matcher._call === 'function' ? matcher._call() : matcher._call; + return originalMatcher.type ? originalMatcher.value : originalMatcher; +} class EspressoDetox { - static perform(interaction, action) { + static perform(matcher, action) { return { target: { type: "Class", @@ -16,7 +23,7 @@ class EspressoDetox { method: "perform", args: [{ type: "Invocation", - value: interaction + value: sanitize_matcher(matcher) }, action] }; } diff --git a/detox/src/android/interactions/native.js b/detox/src/android/interactions/native.js index 64db6741d5..a7863c526b 100644 --- a/detox/src/android/interactions/native.js +++ b/detox/src/android/interactions/native.js @@ -25,9 +25,9 @@ class Interaction { } class ActionInteraction extends Interaction { - constructor(invocationManager, element, action, traceDescription) { + constructor(invocationManager, matcher, action, traceDescription) { super(invocationManager, traceDescription); - this._call = EspressoDetoxApi.perform(call(element._call), action._call); + this._call = EspressoDetoxApi.perform(matcher, action._call); // TODO: move this.execute() here from the caller } } @@ -48,7 +48,6 @@ class WaitForInteraction extends Interaction { super(invocationManager, expectTraceDescription); this._element = element; this._assertionMatcher = assertionMatcher; - this._element._selectElementWithMatcher(this._element._originalMatcher); } async withTimeout(timeout) {