chromium/third_party/blink/web_tests/inspector-protocol/worker/worker-console.js

(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
  var {page, session, dp} = await testRunner.startBlank(
      `Tests how console messages from worker get into page's console once worker is destroyed.`);

  // TODO(johannes): Within this test, it appears that it's necessary to
  // wait until messages have trickled into the page log
  // via dp.Log.onEntryAdded or into the console API
  // via childSession.protocol.Runtime.onConsoleAPICalled, before calling
  // worker.terminate, or otherwise the messages may be dropped, or even
  // appear much later, after another worker has been started.
  // For now, this test is non-flaky primarily by sprinkling several
  // Promise.all calls around postToWorker. Further investigation /
  // clarification / research may be needed.

  await session.evaluate(`
    let worker;

    // This function returns a promise, which, if awaited (e.g. with
    // await session.evaluateAsync("startWorker()") ensures that the
    // worker has been created. This works by worker-console-worker.js posting
    // a message upon which worker.onmessage resolves the promise.
    function startWorker() {
      return new Promise(resolve => {
        worker = new Worker(
            '${testRunner.url('../resources/worker-console-worker.js')}');
        worker.onmessage = resolve;
      });
    }

    // Similar to the above method, this method returns a promise such that
    // awaiting it ensures that the worker has posted a message back to the
    // page. In this way, we know that worker-console-worker.js gets a chance
    // to call console.log.
    // We can terminate the worker after awaiting the promise returned by this
    // method, and then observe that the console.log events were registered
    // anyway. This is much of the point of this test.
    function postToWorker(message) {
      return new Promise(resolve => {
        worker.onmessage = resolve;
        worker.postMessage(message);
      });
    }
  `);

  // To avoid flaky test output, we emit messages into these three different
  // logs, and flush them after each section of the test.
  const clientLog = [];
  const pageLog = [];
  const consoleLog = [];
  function flushLogs() {
    for (let line of clientLog) {
      testRunner.log(line);
    }
    clientLog.length = 0;
    for (let line of pageLog) {
      testRunner.log('<- Log from page: ' + line);
    }
    pageLog.length = 0;
    for (let line of consoleLog) {
      testRunner.log('<-- Console API from worker: ' + line);
    }
    consoleLog.length = 0;
  }

  await dp.Log.enable();
  dp.Log.onEntryAdded((event) => { pageLog.push(event.params.entry.text); });

  function postToWorker(message) {
    clientLog.push('-> Posting to worker: ' + message);
    return session.evaluateAsync('postToWorker("' + message + '")');
  }

  {
    clientLog.push(
        "\n=== console.log event won't get lost despite worker.terminate. ===");
    clientLog.push('Starting worker');
    await session.evaluateAsync('startWorker()');
    await Promise.all([
      postToWorker('message0 (posted after starting worker)'),
      dp.Log.onceEntryAdded()]);
    // The key part of this test is that its expectation contains
    // "<- Log from page: message0 (posted after starting worker)", even
    // though we terminated the worker without awaiting the log event
    // (postToWorker only ensures that the message was echoed back to the page).
    clientLog.push('Terminating worker');
    await session.evaluate('worker.terminate()');
    flushLogs();
  }

  {
    clientLog.push(
        '\n=== Scenario with autoattach enabled and stopped. ===');
    clientLog.push('Starting worker');
    await session.evaluateAsync('startWorker()');
    await Promise.all([postToWorker('message1'), dp.Log.onceEntryAdded()]);

    // Now we call Target.setAutoAttach, which will immediately generate
    // an event that we're attached; which we receive below to create the
    // childSession instance.
    clientLog.push('Starting autoattach');
    const attachedPromise = dp.Target.onceAttachedToTarget();
    await dp.Target.setAutoAttach({
      autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
    const childSession = session.createChild(
        (await attachedPromise).params.sessionId);
    childSession.protocol.Runtime.onConsoleAPICalled((event) => {
      consoleLog.push(event.params.args[0].value);
    });
    clientLog.push('child session for worker created');

    clientLog.push('Sending Runtime.enable to worker');
    childSession.protocol.Runtime.enable();
    await childSession.protocol.Runtime.onceConsoleAPICalled();

    await Promise.all([
      postToWorker('message2 (posted after runtime enabled)'),
      dp.Log.onceEntryAdded(),
      childSession.protocol.Runtime.onceConsoleAPICalled()]);
    await Promise.all([
      postToWorker('throw1 (posted after runtime enabled; ' +
                   'yields exception in worker)'),
      dp.Log.onceEntryAdded()]);

    // This unregisters the child session, so we stop getting the console API
    // calls, but still receive the log messages for the page.
    clientLog.push('Stopping autoattach');
    await dp.Target.setAutoAttach({autoAttach: false,
                                   waitForDebuggerOnStart: false});
    await Promise.all([
      postToWorker('message3 (posted after auto-attach)'),
      dp.Log.onceEntryAdded()]);
    clientLog.push('Terminating worker');
    flushLogs();
    await session.evaluate('worker.terminate()');
  }

  {
    clientLog.push(
        '\n=== Scenario with autoattach from the get-go. ===');
    // This time we start the worker only after Target.setAutoAttach, so
    // we may await the autoattach response.
    clientLog.push('Starting autoattach');
    const attachedPromise = dp.Target.onceAttachedToTarget();
    await dp.Target.setAutoAttach({
      autoAttach: true, waitForDebuggerOnStart: false, flatten: true});

    clientLog.push('Starting worker');
    session.evaluate('startWorker()');
    const childSession = session.createChild(
        (await attachedPromise).params.sessionId);
    childSession.protocol.Runtime.onConsoleAPICalled((event) => {
      consoleLog.push(event.params.args[0].value);
    });
    clientLog.push('child session for worker created');

    // Here, we test the behavior of posting before / after Runtime.enable.
    await Promise.all([
      postToWorker("message4 (posted before worker's runtime agent enabled)"),
      dp.Log.onceEntryAdded()]);

    clientLog.push('Sending Runtime.enable to worker');
    childSession.protocol.Runtime.enable();
    await childSession.protocol.Runtime.onceConsoleAPICalled();
    await Promise.all([
      postToWorker("message5 (posted after worker's runtime agent enabled)"),
      childSession.protocol.Runtime.onceConsoleAPICalled(),
      dp.Log.onceEntryAdded()]);
    clientLog.push('Terminating worker');
    flushLogs();
    await session.evaluate('worker.terminate()');
  }

  {
    clientLog.push(
        '\n=== New worker, with auto-attach still enabled. ===');
    clientLog.push('Starting worker');
    const attachedPromise = dp.Target.onceAttachedToTarget();
    session.evaluate('startWorker()');
    const childSession = session.createChild(
        (await attachedPromise).params.sessionId);
    childSession.protocol.Runtime.onConsoleAPICalled((event) => {
      consoleLog.push(event.params.args[0].value);
    });
    clientLog.push('child session for worker created');

    await Promise.all([
      postToWorker('message6 (posted just before worker termination)'),
      dp.Log.onceEntryAdded()]);

    clientLog.push('Terminating worker');
    clientLog.push('Stopping autoattach');
    flushLogs();
    await session.evaluate('worker.terminate()');

    dp.Target.setAutoAttach({autoAttach: false,
                             waitForDebuggerOnStart: false});
  }
  testRunner.completeTest();
})