19

I am using Alert from react-native.

How do I get detox to press the "Log out" button on the alert message?

enter image description here

I tried using await element(by.text('Log out')).tap();

But I get "Multiple elements were matched" error. Presumably it finds 3 elements with same label. The original button with label "Log out" used to trigger the alert message, the alert message title, and the alert message button I want detox to press.

Error Trace: [
  {
    "Description" : "Multiple elements were matched: (
    "<UILabel:0x7fe7964db910; AX=Y; AX.label='Log out'; AX.frame={{41, 234}, {238, 20.5}}; AX.activationPoint={160, 244.25}; AX.traits='UIAccessibilityTraitStaticText'; AX.focused='N'; frame={{16, 20}, {238, 20.5}}; opaque; alpha=1; UIE=N; text='Log out'>",
    "<UILabel:0x7fe7964dda90; AX=Y; AX.label='Log out'; AX.frame={{198.5, 322.5}, {58, 20.5}}; AX.activationPoint={227.5, 332.75}; AX.traits='UIAccessibilityTraitStaticText'; AX.focused='N'; frame={{0, 12}, {58, 20.5}}; opaque; alpha=1; UIE=N; text='Log out'>",
    "<RCTText:0x7fe79652f300; AX=Y; AX.label='Log out'; AX.frame={{16, 338.5}, {288, 17}}; AX.activationPoint={160, 347}; AX.traits='UIAccessibilityTraitStaticText'; AX.focused='N'; frame={{0, 0}, {288, 17}}; alpha=1>"
). Please use selection matchers to narrow the selection down to single element.",
    "Error Domain" : "com.google.earlgrey.ElementInteractionErrorDomain",
    "Error Code" : "5",
    "File Name" : "GREYElementInteraction.m",
    "Function Name" : "-[GREYElementInteraction grey_errorForMultipleMatchingElements:withMatchedElementsIndexOutOfBounds:]",
    "Line" : "956"
  }
]

I guess one way is to use .atIndex(), but that means I need to play with indexes every time something changes to determine the correct element.

Is there no better way to address this issue?

Thanks.

Antoni4
  • 2,115
  • 20
  • 34

4 Answers4

22

After some tinkering I ended up using this:

await element(by.label('Log out').and(by.type('_UIAlertControllerActionView'))).tap();

Not sure if this will work for every iOS version, but seem to work on 10.3 and 11.1

Use View Hierarchy Debugger provided by Xcode to see if the type has changed for a different version of iOS.

Antoni4
  • 2,115
  • 20
  • 34
  • Great finding! But how did you find out it's the `_UIAlertControllerActionView `? What if we need to inspect for example a photo gallery and tap on an image and tap on `Choose` button? – mjakic Apr 12 '18 at 08:45
  • 1
    Hi, I believe I used `Debug View Hierarchy` from Xcode for this – Antoni4 Apr 16 '18 at 16:28
  • 2
    will it work for alert boxes opened by the system? like asking for permission from the user. – arjun May 01 '18 at 14:48
  • Hi, just tried it for myself on my project and the detox library never finds the element. Is there any other way? – jaumevn May 18 '18 at 08:21
  • Hi, is there anyway to find out the UI Element Types using Expo for Detox tests, like the Debug View Hierarchy you mentioned before? – srikanth Jul 03 '18 at 17:23
  • 5
    @arjun it doesn't appear to work for alert boxes opened by the system. I tried using this solution, replacing 'Log out' with 'Allow' (for push notification permission), but Detox still quits with `Error: Error: Cannot find UI element.` – Pat Needham Aug 20 '18 at 16:09
  • Unfortunately it doesn't work on iOS 12 and react-native, any help? – Mark Oct 06 '18 at 20:38
  • @Mark have you tried using View Hierarchy Debugger provided by Xcode to see if type has changed in iOS 12? – Antoni4 Oct 10 '18 at 11:20
  • I checked the `Debug View Hierarchy` in Xcode in iOS 12.2. The `Confirm` and `Cancel` labels are each an `RCTView` containing an `RCTTextView`. – Leo Jun 25 '19 at 14:46
  • 1
    [I enabled the permission in the detox `init.js` file](https://stackoverflow.com/a/56548173/7295772) and the alert does not display anymore, but as explained from [Leo Natan--reinstate Monica](https://stackoverflow.com/users/983912/leo-natan-reinstate-monica) the alert is displayed from a different process from the one where Detox operates and [it is not possible to interact with it](https://stackoverflow.com/a/56799443/7295772). Seems that the [discussion on this issue is limited to SO](https://github.com/wix/Detox/issues/194) – Fabrizio Bertoglio Dec 20 '19 at 17:14
  • It works for iOS, for Android is here: https://stackoverflow.com/a/59737117/551744 – Chaki_Black Aug 20 '20 at 13:04
5

It should work with finding element by text

await element(by.text('Log out')).tap();

Demo repo: https://github.com/FDiskas/demonas/blob/c703840a991b2f3d96a18ac8c5120ee1d5f901f8/e2e/firstTest.spec.ts#L11

FDisk
  • 6,213
  • 1
  • 38
  • 45
2

You can now press on native dialogs. Tested on iOS. (did not test on Android)

If your button says "OK" ie:

Alert.alert(
  `Are you sure you would like to remove this image as the coming soon image?`,
  undefined,
  [
    {
      text: "No",
      style: "cancel",
    },
    {
      text: "OK",
      style: "destructive",
      onPress: this.onRemoveHero,
    },
  ]
);

You would click this by doing:

element(by.label("OK")).atIndex(0).tap();
Noitidart
  • 30,310
  • 26
  • 103
  • 267
  • This is working fine with detox 17.0.2 which broke some ways of targeting elements like Alert in RN (you can no longer target alert buttons with a simple `by.text('OK')` – Dantereve Jun 29 '20 at 15:44
  • @Dantereve can you please clarify, did `by.text('OK')` used to work? But now the only way is to do it with `by.label('OK')`? – Noitidart Jun 29 '20 at 19:04
  • 2
    yes, `by.text('OK')` worked with Detox 16.x but not anymore with Detox 17 on both apps I'm currently testing with Detox. The fix was to use `element(by.label("OK")).atIndex(0)`. This is most certainly related to this : [Detox Issue 2156](https://github.com/wix/Detox/issues/2156) – Dantereve Jun 30 '20 at 15:42
0

I have wrote a utility function which allows you to do this cross platform.

Function

/**
 * Detects a systme dialog button by label
 * 
 * @param {string} label
 * 
 * @returns {*}
 */
export function systemDialog(label){
    if (device.getPlatform() === 'ios') {
        return element(by.label(label)).atIndex(0);
    }

    return element(by.text(label));
}

Usage

import { systemDialog } from "path to system dialog";

...

await systemDialog('OK').tap();