0

Say for example I have the following code, a simple UI test.

async function testMyCoolUI() {
  await uiFramework.openMyApp(url);
      
  await sleep(2000);
    
  await uiFramework.clickButtonX();

  await uiFramework.clickButtonY();
}

Now a new requirement gets added. At any point during the test, a popup could come up on the screen saying "Are you a bot?", and we have to select "No".

How would you structure your test such that this "process" can run constantly in the background of the test, watching for this popup? My initial idea is to just kick off an async function polling for the popup, but don't wait on the promise in testMyCoolUI.

async function testMyCoolUI() {
  await uiFramework.openMyApp(url);
      
  await sleep(2000);

  startPollingForPopup(); // this is an async function, but not waiting on it
    
  await uiFramework.clickButtonX();

  await uiFramework.clickButtonY();
}

However this feels wrong, and the promise will sit unresolved and the process won't clean up nicely. What is the way to do this "correctly" in JS?

Other thought:

Promise.all([testMyCoolUI, pollForPopup]);

But in this case, the test would complete still before the polling ever resolved. And Promise.race doesn't really work here for the same reason.

Jack Ryan
  • 1,287
  • 12
  • 26

1 Answers1

1

A good code structuring pattern that will ensure proper cleanup is the promise disposer pattern:

async function testMyCoolUI() {
  await uiFramework.openMyApp(url); 
  await sleep(2000);
  await withPollingForPopup(async () => {
    await uiFramework.clickButtonX();
    await uiFramework.clickButtonY();
  });
}

async function withPollingForPopup(run) {
  try {
    startPollingForPopup(); // not waiting for anything
    return await run();
  } finally {
    stopPollingForPopup(); // optionally `await` it
  }
}

This assumes a background process, possibly an event subscription, that can be started and stopped.

Alternatively, if the background process does return a promise which rejects on errors and you want to abort as soon as possible, you might use

async function withPollingForPopup(run) {
  const poll = runPollingForPopup();
  const [res] = await Promise.all([
    run().finally(poll.stop),
    poll.promise
  ]);
  return res;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375