function createMockOnFn() {
  /**
   * @template {keyof LH.CrdpEvents} E
   * @type {Array<{event: E|any, response?: MockEvent<E>}>}
   */
  const mockEvents = [];
  const mockFnImpl = fnAny().mockImplementation((eventName, listener) => {
    const events = mockEvents.filter(entry => entry.event === eventName);
    if (!events.length) return;
    for (const event of events) {
      const indexOfEvent = mockEvents.indexOf(event);
      mockEvents.splice(indexOfEvent, 1);
    }
    // Wait a tick because real events never fire immediately
    setTimeout(() => {
      for (const event of events) {
        listener(event.response);
      }
    }, 0);
  });

  const mockFn = Object.assign(mockFnImpl, {
    /**
     * @template {keyof LH.CrdpEvents} E
     * @param {E} event
     * @param {MockEvent<E>} response
     */
    mockEvent(event, response) {
      mockEvents.push({event, response});
      return mockFn;
    },
    /**
     * @param {keyof LH.CrdpEvents} event
     */
    findListener(event) {
      expect(mockFn).toHaveBeenCalledWith(event, expect.anything());
      const call = mockFn.mock.calls.find(call => call[0] === event);
      if (!call) throw new Error(`missing listener for event: ${event}`);
      return call[1];
    },
    /**
     * @param {keyof LH.CrdpEvents} event
     */
    getListeners(event) {
      return mockFn.mock.calls.filter(call => call[0] === event).map(call => call[1]);
    },
  });

  return mockFn;
}
Very much like `createMockOnceFn`, but will fire all the events (not just one for every call).
So it's good for .on w/ many events.