function waitUntil(predicate, message, options = {}) {
  const { interval = 50, timeout = 1000 } = options;

  // Save the current stack so that we can reference it later if we timeout.
  const { stack } = new Error();

  return new Promise((resolve, reject) => {
    let timeoutId;

    setTimeout(() => {
      clearTimeout(timeoutId);

      const error = new Error(
        message ? `Timeout: ${message}` : `waitUntil timed out after ${timeout}ms`,
      );
      error.stack = stack;

      reject(error);
    }, timeout);

    async function nextInterval() {
      try {
        if (await predicate()) {
          resolve();
        } else {
          timeoutId = setTimeout(() => {
            nextInterval();
          }, interval);
        }
      } catch (error) {
        reject(error);
      }
    }
    nextInterval();
  });
}
Waits until the given predicate returns a truthy value. Calls and awaits the predicate
function at the given interval time. Can be used to poll until a certain condition is true.

@example
```js
import { fixture, waitUntil } from '@open-wc/testing-helpers';

const element = await fixture(html`<my-element></my-element>`);

await waitUntil(() => element.someAsyncProperty, 'element should become ready');
```

@param {() => unknown | Promise<unknown>} predicate - predicate function which is called each poll interval.
  The predicate is awaited, so it can return a promise.
@param {string} [message] an optional message to display when the condition timed out
@param {{ interval?: number, timeout?: number }} [options] timeout and polling interval