5

In my test, I would like to simulate a tap in the "cancelUpgrade" button only if it is displayed:

it('should be in home menu', async () => {
  await waitFor(element(by.id('cancelUpgrade')))
    .toBeVisible()
    .withTimeout(2000);
  await element(by.id('cancelUpgrade')).tap();
});

It returns the expected error Error: Cannot find UI element.

https://github.com/wix/detox

Paul
  • 1,290
  • 6
  • 24
  • 46

3 Answers3

9

You can wrap tap in the try/catch block:

it('should be in home menu', async () => {
  await waitFor(element(by.id('cancelUpgrade')))
    .toBeVisible()
    .withTimeout(2000);
  try {
    await element(by.id('cancelUpgrade')).tap();
  } catch (e) {}
  // continue your tests
});

Not the best way but i think that's what currently possible within detox.

Paul
  • 1,290
  • 6
  • 24
  • 46
  • 1
    Can you please explain what your is doing differently from the OP and why your example works as expected? – Francesco Montesano Oct 04 '17 at 20:16
  • 1
    Just to give more info on why this solution works. From detox documentation: "NOTE: waitFor will not throw when reaching timeout, instead it will just continue to the next line." So if the button is not available .tap() will throw, and it's silently swallowed by the catch in this case. – Antoni4 Nov 24 '17 at 14:00
  • Also, FWIW, you can use the `try/catch` "trick" to place alternate code path in the `catch` block. – MarkHu May 12 '18 at 11:45
0

I suspect we can use this pattern with toExist

it('should be in home menu', async () => {
  await waitFor(element(by.id('cancelUpgrade')))
    .toBeVisible()
    .withTimeout(2000);
  try {
    await expect(element(by.id('cancelUpgrade'))).toExist();
  } catch (e) {}
  // continue your tests
});

And if you don't need to wait:

it('should be in home menu', async () => {
  try {
    await expect(element(by.id('cancelUpgrade'))).toExist();
  } catch (e) {}
  // continue your tests
});
Daryn
  • 3,394
  • 5
  • 30
  • 41
0

It may not be the best solution but I created a util function called waitForElement and it worked.

export const sleep = async (milliseconds) =>
   new Promise((resolve) => setTimeout(resolve, milliseconds));

/**
 * Wait for element to be available in the UI hierarchy
 * @param {Detox.Element} appElement detox element
 * @param {Number} timeout Timeout in milliseconds
 * @param {Number} maxRetries
 * @returns {Object}
 * {
 *   found: <Boolean>, // Determines if element was found
 *   retryCount: <Number> // Number of tries to find the element
 * }
 */
export const waitForElement = async (appElement, timeout, maxRetries = 5) => {
  let found = false;
  let retryCount = 0;

  while (!found && retryCount < maxRetries) {
    try {
      await expect(appElement).toExist();
      found = true;
    } catch (err) {
      retryCount += 1;
      if (retryCount < maxRetries) {
        await sleep(timeout);
      } else {
        throw err;
      }
    }
  }

  return {found, retryCount};
};

Usage example:

it('should be in home menu', async () => {
    const cancelUpgradeElement = element(by.id('cancelUpgrade'));
    await waitForElement(cancelUpgradeElement, 2000);
    await cancelUpgradeElement.tap();
});
Rahul
  • 183
  • 1
  • 2
  • 8