function createTestState() {
  /** @param {string} name @return {any} */
  const any = name => new Proxy({}, {get: () => {
    throw new Error(`${name} used without invoking \`state.before\``);
  }});

  /** @type {LH.Trace|undefined} */
  let trace;

  return {
    browser: /** @type {puppeteer.Browser} */ (any('browser')),
    page: /** @type {puppeteer.Page} */ (any('page')),
    server: /** @type {StaticServer} */ (any('server')),
    secondaryServer: /** @type {StaticServer} */ (any('server')),
    serverBaseUrl: '',
    secondaryServerBaseUrl: '',

    installSetupAndTeardownHooks() {
      before(async () => {
        this.server = new Server(10200);
        this.secondaryServer = new Server(10503);
        await this.server.listen(10200, '127.0.0.1');
        await this.secondaryServer.listen(10503, '127.0.0.1');
        this.serverBaseUrl = `http://localhost:${this.server.getPort()}`;
        this.secondaryServerBaseUrl = `http://localhost:${this.secondaryServer.getPort()}`;
      });

      before(async () => {
        this.browser = await puppeteer.launch({
          executablePath: getChromePath(),
          ignoreDefaultArgs: ['--enable-automation'],
        });
      });

      beforeEach(async () => {
        trace = undefined;
        this.page = await this.browser.newPage();
      });

      afterEach(async () => {
        await this.page.close();
      });

      afterEach(function() {
        // eslint-disable-next-line no-invalid-this
        const currentTest = this.currentTest;
        if (currentTest?.state === 'failed' && trace) {
          const dirname = currentTest.fullTitle().replace(/[^a-z0-9]/gi, '_').toLowerCase();
          const testOutputDir = `${UNIT_OUTPUT_DIR}/${dirname}`;
          fs.mkdirSync(testOutputDir, {recursive: true});
          fs.writeFileSync(`${testOutputDir}/trace.json`, JSON.stringify(trace, null, 2));
        }
      });

      after(async () => {
        await this.browser.close();
      });

      after(async () => {
        await this.server.close();
        await this.secondaryServer.close();
      });
    },

    /**
     * @param {LH.Trace} testTrace
     */
    saveTrace(testTrace) {
      trace = testTrace;
    },
  };
}
Some audits can be notApplicable based on machine timing information.
Exclude these audits from applicability comparisons.