detox: tap button only if exists

Viewed 4394

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

3 Answers

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.

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
});

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();
});
Related