Repository: GoogleChrome/lighthouse
Pull request #12907 (2021-08-12 16:42:38)

core(fr): parity on Stacks and FullPageSnapshot gatherers
**Summary**
Achieves parity with legacy runner on Stacks gatherer and FullPageSnapshot gatherer (the two necessary to match results on the dobetterweb smoketest). This also adds the ability to target assertions to the legacy or FR runner in smoketests to account for irreconcilable differences like fewer passes and extra contexts that change the ID of certain artifacts.

**Related Issues/PRs**
ref #12861 #11313 


brendankenny: pretty amazing dbw works! 🎉 

Patch:
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
index 2c30fc393..1f7a3485d 100644
--- a/.github/workflows/smoke.yml
+++ b/.github/workflows/smoke.yml
@@ -106,7 +106,7 @@ jobs:
 
     - run: sudo apt-get install xvfb
     - name: yarn smoke --fraggle-rock
-      run: xvfb-run --auto-servernum yarn smoke --debug --fraggle-rock -j=1 --retries=2 a11y lantern seo-passing seo-failing csp metrics perf-debug
+      run: xvfb-run --auto-servernum yarn smoke --debug --fraggle-rock -j=1 --retries=2 a11y lantern seo-passing seo-failing csp metrics perf-debug dbw
 
     # Fail if any changes were written to source files.
     - run: git diff --exit-code
diff --git a/build/build-report.js b/build/build-report.js
index d0377a808..c23a17243 100644
--- a/build/build-report.js
+++ b/build/build-report.js
@@ -41,20 +41,6 @@ async function buildPsiReport() {
   });
 }
 
-async function buildTreemapReport() {
-  const bundle = await rollup.rollup({
-    input: 'report/clients/treemap.js',
-    plugins: [
-      commonjs(),
-    ],
-  });
-
-  await bundle.write({
-    file: 'dist/report/treemap.js',
-    format: 'iife',
-  });
-}
-
 async function buildEsModulesBundle() {
   const bundle = await rollup.rollup({
     input: 'report/clients/bundle.js',
@@ -110,5 +96,4 @@ module.exports = {
   buildStandaloneReport,
   buildPsiReport,
   buildUmdBundle,
-  buildTreemapReport,
 };
diff --git a/lighthouse-core/scripts/build-report-for-autodeployment.js b/build/build-sample-reports.js
similarity index 92%
rename from lighthouse-core/scripts/build-report-for-autodeployment.js
rename to build/build-sample-reports.js
index ce2f1e2b8..963639505 100644
--- a/lighthouse-core/scripts/build-report-for-autodeployment.js
+++ b/build/build-sample-reports.js
@@ -10,13 +10,13 @@
 /* eslint-disable no-console */
 const fs = require('fs');
 const path = require('path');
-const swapLocale = require('../lib/i18n/swap-locale.js');
-const ReportGenerator = require('../../report/report-generator.js');
-const {defaultSettings} = require('../config/constants.js');
-const lighthouse = require('../index.js');
-const lhr = /** @type {LH.Result} */ (require('../../lighthouse-core/test/results/sample_v2.json'));
-const {LH_ROOT} = require('../../root.js');
-const htmlReportAssets = require('../../report/report-assets.js');
+const swapLocale = require('../lighthouse-core/lib/i18n/swap-locale.js');
+const ReportGenerator = require('../lighthouse-core/../report/report-generator.js');
+const {defaultSettings} = require('../lighthouse-core/config/constants.js');
+const lighthouse = require('../lighthouse-core/index.js');
+const lhr = /** @type {LH.Result} */ (require('../lighthouse-core/test/results/sample_v2.json'));
+const {LH_ROOT} = require('../root.js');
+const htmlReportAssets = require('../lighthouse-core/../report/report-assets.js');
 
 
 const DIST = path.join(LH_ROOT, `dist/now`);
diff --git a/build/build-treemap.js b/build/build-treemap.js
index c85a8bd24..63f4b724e 100644
--- a/build/build-treemap.js
+++ b/build/build-treemap.js
@@ -8,7 +8,6 @@
 /** @typedef {import('../lighthouse-core/lib/i18n/locales').LhlMessages} LhlMessages */
 
 const fs = require('fs');
-const {buildTreemapReport} = require('./build-report.js');
 const GhPagesApp = require('./gh-pages-app.js');
 const {LH_ROOT} = require('../root.js');
 
@@ -19,10 +18,9 @@ const {LH_ROOT} = require('../root.js');
  */
 function buildStrings() {
   const locales = require('../lighthouse-core/lib/i18n/locales.js');
-  const UIStrings = require(
-    // Prevent `tsc -p .` from evaluating util.js using core types, it is already typchecked by `tsc -p lighthouse-treemap`.
-    '' + '../lighthouse-treemap/app/src/util.js'
-  ).UIStrings;
+  // TODO(esmodules): use dynamic import when build/ is esm.
+  const utilCode = fs.readFileSync(LH_ROOT + '/lighthouse-treemap/app/src/util.js', 'utf-8');
+  const {UIStrings} = eval(utilCode.replace('export ', '') + '\nmodule.exports = TreemapUtil;');
   const strings = /** @type {Record<LH.Locale, LhlMessages>} */ ({});
 
   for (const [locale, lhlMessages] of Object.entries(locales)) {
@@ -46,8 +44,6 @@ function buildStrings() {
  * Build treemap app, optionally deploying to gh-pages if `--deploy` flag was set.
  */
 async function run() {
-  await buildTreemapReport();
-
   const app = new GhPagesApp({
     name: 'treemap',
     appDir: `${LH_ROOT}/lighthouse-treemap/app`,
@@ -68,9 +64,7 @@ async function run() {
       fs.readFileSync(require.resolve('pako/dist/pako_inflate.js'), 'utf-8'),
       /* eslint-enable max-len */
       buildStrings(),
-      {path: '../../lighthouse-viewer/app/src/deps-for-treemap.js', rollup: true},
-      {path: '../../dist/report/treemap.js'},
-      {path: 'src/**/*'},
+      {path: 'src/main.js', rollup: true},
     ],
     assets: [
       {path: 'images/**/*'},
diff --git a/lighthouse-core/fraggle-rock/config/default-config.js b/lighthouse-core/fraggle-rock/config/default-config.js
index d62f16efa..094c00909 100644
--- a/lighthouse-core/fraggle-rock/config/default-config.js
+++ b/lighthouse-core/fraggle-rock/config/default-config.js
@@ -158,7 +158,6 @@ const defaultConfig = {
         artifacts.EmbeddedContent,
         artifacts.FontSize,
         artifacts.FormElements,
-        artifacts.FullPageScreenshot,
         artifacts.GatherContext,
         artifacts.GlobalListeners,
         artifacts.IFrameElements,
@@ -187,6 +186,9 @@ const defaultConfig = {
         // Compat artifacts come last.
         artifacts.devtoolsLogs,
         artifacts.traces,
+
+        // FullPageScreenshot comes at the very end so all other node analysis is captured.
+        artifacts.FullPageScreenshot,
       ],
     },
   ],
diff --git a/lighthouse-core/fraggle-rock/gather/runner-helpers.js b/lighthouse-core/fraggle-rock/gather/runner-helpers.js
index 2f5986dad..b4ddc01a6 100644
--- a/lighthouse-core/fraggle-rock/gather/runner-helpers.js
+++ b/lighthouse-core/fraggle-rock/gather/runner-helpers.js
@@ -22,6 +22,8 @@
 
 /** @typedef {LH.Gatherer.FRTransitionalContext<LH.Gatherer.DependencyKey>['dependencies']} Dependencies */
 
+const log = require('lighthouse-logger');
+
 /**
  *
  * @param {{id: string}} dependency
@@ -75,6 +77,8 @@ async function collectPhaseArtifacts(options) {
   const priorPhaseArtifacts = (priorPhase && artifactState[priorPhase]) || {};
 
   for (const artifactDefn of artifactDefinitions) {
+    const logLevel = phase === 'getArtifact' ? 'log' : 'verbose';
+    log[logLevel](`artifacts:${phase}`, artifactDefn.id);
     const gatherer = artifactDefn.gatherer.instance;
 
     const priorArtifactPromise = priorPhaseArtifacts[artifactDefn.id] || Promise.resolve();
diff --git a/lighthouse-core/gather/gatherers/stacks.js b/lighthouse-core/gather/gatherers/stacks.js
index 9348dda4e..7e3845dbd 100644
--- a/lighthouse-core/gather/gatherers/stacks.js
+++ b/lighthouse-core/gather/gatherers/stacks.js
@@ -120,7 +120,7 @@ class Stacks extends FRGatherer {
    * @param {LH.Gatherer.FRTransitionalContext} context
    * @return {Promise<LH.Artifacts['Stacks']>}
    */
-  async snapshot(context) {
+  async getArtifact(context) {
     return Stacks.collectStacks(context.driver.executionContext);
   }
 }
diff --git a/lighthouse-core/lib/i18n/README.md b/lighthouse-core/lib/i18n/README.md
index d36888202..523bad7a0 100644
--- a/lighthouse-core/lib/i18n/README.md
+++ b/lighthouse-core/lib/i18n/README.md
@@ -39,7 +39,7 @@ To a typical developer, the pipeline looks like this:
 $ yarn i18n:collect-strings
 
 # Test to see that the new translations are valid and apply to all strings
-$ node lighthouse-core/scripts/build-report-for-autodeployment.js && open dist/xl-accented/index.html
+$ yarn build-sample-reports && open dist/xl-accented/index.html
 ```
 
 Note: Why do `en-US` and `en-XL` get baked early?  We write all our strings in `en-US` by default, so they do not need to be translated, so it can be immediately baked without going to the translators.  Similarly, `en-XL` is a debugging language, it is an automated version of `en-US` that simply adds markers to `en` strings in order to make it obvious that something has or hasn't been translated.  So neither of these files need to go to translators to be used, and both can be used at develop-time to help developer i18n workflow.
diff --git a/lighthouse-core/scripts/dogfood-lhci.sh b/lighthouse-core/scripts/dogfood-lhci.sh
index 226aff692..f3b3eb4bf 100755
--- a/lighthouse-core/scripts/dogfood-lhci.sh
+++ b/lighthouse-core/scripts/dogfood-lhci.sh
@@ -37,7 +37,7 @@ fi
 
 # Generate HTML reports in ./dist/now/
 yarn build-report
-yarn now-build
+yarn vercel-build
 
 # Install LHCI
 npm install -g @lhci/cli@next
diff --git a/lighthouse-treemap/app/src/main.js b/lighthouse-treemap/app/src/main.js
index 602c92ba1..dc9c12b67 100644
--- a/lighthouse-treemap/app/src/main.js
+++ b/lighthouse-treemap/app/src/main.js
@@ -9,7 +9,14 @@
 
 /* eslint-env browser */
 
-/* globals I18n webtreemap strings TreemapUtil TextEncoding Tabulator Cell Row DragAndDrop Logger GithubApi */
+/* globals webtreemap strings Tabulator Cell Row */
+
+import {TreemapUtil} from './util.js';
+import {DragAndDrop} from '../../../lighthouse-viewer/app/src/drag-and-drop.js';
+import {GithubApi} from '../../../lighthouse-viewer/app/src/github-api.js';
+import {I18n} from '../../../report/renderer/i18n.js';
+import {TextEncoding} from '../../../report/renderer/text-encoding.js';
+import {Logger} from '../../../report/renderer/logger.js';
 
 const DUPLICATED_MODULES_IGNORE_THRESHOLD = 1024;
 const DUPLICATED_MODULES_IGNORE_ROOT_RATIO = 0.01;
@@ -707,7 +714,7 @@ function injectOptions(options) {
  * @param {LhlMessages} localeMessages
  */
 function getStrings(localeMessages) {
-  const strings = /** @type {TreemapUtil['UIStrings']} */ ({});
+  const strings = /** @type {typeof TreemapUtil['UIStrings']} */ ({});
 
   for (const varName of Object.keys(localeMessages)) {
     const key = /** @type {keyof typeof TreemapUtil['UIStrings']} */ (varName);
@@ -777,7 +784,7 @@ class LighthouseTreemap {
     for (const node of document.querySelectorAll('[data-i18n]')) {
       // These strings are guaranteed to (at least) have a default English string in TreemapUtil.UIStrings,
       // so this cannot be undefined as long as `report-ui-features.data-i18n` test passes.
-      const i18nAttr = /** @type {keyof TreemapUtil['UIStrings']} */ (
+      const i18nAttr = /** @type {keyof typeof TreemapUtil['UIStrings']} */ (
         node.getAttribute('data-i18n'));
       node.textContent = TreemapUtil.i18n.strings[i18nAttr];
     }
diff --git a/lighthouse-treemap/app/src/util.js b/lighthouse-treemap/app/src/util.js
index 0e07dc17e..e89f21bd3 100644
--- a/lighthouse-treemap/app/src/util.js
+++ b/lighthouse-treemap/app/src/util.js
@@ -11,7 +11,30 @@
 /** @template {string} T @typedef {import('typed-query-selector/parser').ParseSelector<T, Element>} ParseSelector */
 /** @template T @typedef {import('../../../report/renderer/i18n').I18n<T>} I18n */
 
-class TreemapUtil {
+export class TreemapUtil {
+  /** @type {I18n<typeof TreemapUtil['UIStrings']>} */
+  // @ts-expect-error: Is set in main.
+  static i18n = null;
+
+  static UIStrings = {
+    /** Label for a button that alternates between showing or hiding a table. */
+    toggleTableButtonLabel: 'Toggle Table',
+    /** Text for an option in a dropdown menu. When selected, the app shows information for all scripts that were found in a web page. */
+    allScriptsDropdownLabel: 'All Scripts',
+    /** Label for a table column where the values are URLs, JS module names, or arbitrary identifiers. For simplicity, just 'name' is used. */
+    tableColumnName: 'Name',
+    /** Label for column giving the size of a file in bytes. */
+    resourceBytesLabel: 'Resource Bytes',
+    /** Label for a value associated with how many bytes of a script are not executed. */
+    unusedBytesLabel: 'Unused Bytes',
+    /** Label for a column where the values represent how much of a file is used bytes vs unused bytes (coverage). */
+    coverageColumnName: 'Coverage',
+    /** Label for a button that shows everything (or rather, does not highlight any specific mode such as: unused bytes, duplicate bytes, etc). */
+    allLabel: 'All',
+    /** Label for a button that highlights information about duplicate modules (aka: files, javascript resources that were included twice by a web page). */
+    duplicateModulesLabel: 'Duplicate Modules',
+  };
+
   /**
    * @param {LH.Treemap.Node} node
    * @param {(node: NodeWithElement, path: string[]) => void} fn
@@ -191,33 +214,3 @@ TreemapUtil.COLOR_HUES = [
   15.9,
   199.5,
 ];
-
-/** @type {I18n<typeof TreemapUtil['UIStrings']>} */
-// @ts-expect-error: Is set in main.
-TreemapUtil.i18n = null;
-
-TreemapUtil.UIStrings = {
-  /** Label for a button that alternates between showing or hiding a table. */
-  toggleTableButtonLabel: 'Toggle Table',
-  /** Text for an option in a dropdown menu. When selected, the app shows information for all scripts that were found in a web page. */
-  allScriptsDropdownLabel: 'All Scripts',
-  /** Label for a table column where the values are URLs, JS module names, or arbitrary identifiers. For simplicity, just 'name' is used. */
-  tableColumnName: 'Name',
-  /** Label for column giving the size of a file in bytes. */
-  resourceBytesLabel: 'Resource Bytes',
-  /** Label for a value associated with how many bytes of a script are not executed. */
-  unusedBytesLabel: 'Unused Bytes',
-  /** Label for a column where the values represent how much of a file is used bytes vs unused bytes (coverage). */
-  coverageColumnName: 'Coverage',
-  /** Label for a button that shows everything (or rather, does not highlight any specific mode such as: unused bytes, duplicate bytes, etc). */
-  allLabel: 'All',
-  /** Label for a button that highlights information about duplicate modules (aka: files, javascript resources that were included twice by a web page). */
-  duplicateModulesLabel: 'Duplicate Modules',
-};
-
-// node export for testing.
-if (typeof module !== 'undefined' && module.exports) {
-  module.exports = TreemapUtil;
-} else {
-  self.TreemapUtil = TreemapUtil;
-}
diff --git a/lighthouse-treemap/package.json b/lighthouse-treemap/package.json
new file mode 100644
index 000000000..bd3462847
--- /dev/null
+++ b/lighthouse-treemap/package.json
@@ -0,0 +1,4 @@
+{
+  "type": "module",
+  "//": "Any directory that uses `import ... from` or `export ...` must be type module. Temporary file until root package.json is type: module"
+}
\ No newline at end of file
diff --git a/lighthouse-treemap/types/treemap.d.ts b/lighthouse-treemap/types/treemap.d.ts
index a2219afe8..94be41f9f 100644
--- a/lighthouse-treemap/types/treemap.d.ts
+++ b/lighthouse-treemap/types/treemap.d.ts
@@ -1,11 +1,4 @@
-import _TreemapUtil = require('../app/src/util.js');
-import {DragAndDrop as _DragAndDrop} from '../../lighthouse-viewer/app/src/drag-and-drop.js';
-import {FirebaseAuth as _FirebaseAuth} from '../../lighthouse-viewer/app/src/firebase-auth.js';
-import {GithubApi as _GithubApi} from '../../lighthouse-viewer/app/src/github-api.js';
-import {TextEncoding as _TextEncoding} from '../../report/renderer/text-encoding.js';
 import {Logger as _Logger} from '../../report/renderer/logger.js';
-import {I18n as _I18n} from '../../report/renderer/i18n.js';
-import {getFilenamePrefix as _getFilenamePrefix} from '../../report/renderer/file-namer.js';
 import {FirebaseNamespace} from '@firebase/app-types';
 
 declare global {
@@ -38,17 +31,10 @@ declare global {
     render(el: HTMLElement, data: any, options: WebTreeMapOptions): void;
     sort(data: any): void;
   };
-  var TreemapUtil: typeof _TreemapUtil;
-  var TextEncoding: typeof _TextEncoding;
-  var Logger: typeof _Logger;
-  var DragAndDrop: typeof _DragAndDrop;
-  var GithubApi: typeof _GithubApi;
-  var FirebaseAuth: typeof _FirebaseAuth;
+  var logger: _Logger;
   var firebase: Required<FirebaseNamespace>;
   var idbKeyval: typeof import('idb-keyval');
   var strings: Record<LH.Locale, import('../../lighthouse-core/lib/i18n/locales').LhlMessages>;
-  var getFilenamePrefix: typeof _getFilenamePrefix;
-  var I18n: typeof _I18n;
 
   interface Window {
     logger: _Logger;
diff --git a/lighthouse-viewer/app/src/deps-for-treemap.js b/lighthouse-viewer/app/src/deps-for-treemap.js
deleted file mode 100644
index 56b6dace8..000000000
--- a/lighthouse-viewer/app/src/deps-for-treemap.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
- */
-'use strict';
-
-// TODO(esmodules): remove when treemap is esm.
-
-import {GithubApi} from './github-api.js';
-import {FirebaseAuth} from './firebase-auth.js';
-import {DragAndDrop} from './drag-and-drop.js';
-
-// @ts-expect-error
-window.GithubApi = GithubApi;
-// @ts-expect-error
-window.FirebaseAuth = FirebaseAuth;
-// @ts-expect-error
-window.DragAndDrop = DragAndDrop;
diff --git a/package.json b/package.json
index a7a87d826..3e91b233d 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
     "build-lr": "yarn reset-link && node ./build/build-lightrider-bundles.js",
     "build-pack": "bash build/build-pack.sh",
     "build-report": "node build/build-report-components.js && yarn eslint --fix report/renderer/components.js && node build/build-report.js",
-    "build-dist-reports": "yarn build-report && node lighthouse-core/scripts/build-report-for-autodeployment.js",
+    "build-sample-reports": "yarn build-report && node build/build-sample-reports.js",
     "build-treemap": "node ./build/build-treemap.js",
     "build-viewer": "node ./build/build-viewer.js",
     "reset-link": "(yarn unlink || true) && yarn link && yarn link lighthouse",
@@ -47,7 +47,7 @@
     "unit-core": "yarn jest \"lighthouse-core\"",
     "unit-cli": "yarn jest \"lighthouse-cli/\"",
     "unit-report": "yarn jest \"report/\"",
-    "unit-treemap": "jest \"lighthouse-treemap/.*-test.js\"",
+    "unit-treemap": "yarn jest \"lighthouse-treemap/.*-test.js\"",
     "unit-viewer": "yarn jest \"lighthouse-viewer/.*-test.js\"",
     "unit": "yarn unit-core && yarn unit-cli && yarn unit-report && yarn unit-viewer && yarn unit-treemap",
     "unit:ci": "NODE_OPTIONS=--max-old-space-size=8192 npm run unit-core -- --ci && npm run unit-cli -- --ci && npm run unit-report -- --ci && npm run unit-viewer -- --ci && npm run unit-treemap -- --ci",
@@ -63,7 +63,7 @@
     "fast": "node ./lighthouse-cli/index.js --preset=desktop --throttlingMethod=provided",
     "deploy-treemap": "yarn build-treemap --deploy",
     "deploy-viewer": "yarn build-viewer --deploy",
-    "now-build": "yarn build-dist-reports && yarn build-viewer && yarn build-treemap && cp -r dist/gh-pages dist/now/gh-pages",
+    "vercel-build": "yarn build-sample-reports && yarn build-viewer && yarn build-treemap && cp -r dist/gh-pages dist/now/gh-pages",
     "dogfood-lhci": "./lighthouse-core/scripts/dogfood-lhci.sh",
     "timing-trace": "node lighthouse-core/scripts/generate-timing-trace.js",
     "changelog": "conventional-changelog --config ./build/changelog-generator/index.js --infile changelog.md --same-file",
diff --git a/report/clients/treemap.js b/report/clients/treemap.js
deleted file mode 100644
index 896a4752f..000000000
--- a/report/clients/treemap.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
- */
-'use strict';
-
-/* global window */
-
-// TODO(esmodules): delete when treemap app is esm.
-
-import {I18n} from '../renderer/i18n.js';
-import {Logger} from '../renderer/logger.js';
-import {TextEncoding} from '../renderer/text-encoding.js';
-
-// @ts-expect-error
-window.I18n = I18n;
-// @ts-expect-error
-window.Logger = Logger;
-// @ts-expect-error
-window.TextEncoding = TextEncoding;
diff --git a/types/smokehouse.d.ts b/types/smokehouse.d.ts
index fc4e9f664..a9d84baf3 100644
--- a/types/smokehouse.d.ts
+++ b/types/smokehouse.d.ts
@@ -24,7 +24,7 @@ declare global {
     export type ExpectedRunnerResult = {
       lhr: ExpectedLHR,
       artifacts?: Partial<Record<keyof Artifacts|'_maxChromiumMilestone'|'_minChromiumMilestone', any>>
-      networkRequests?: {length: number};
+      networkRequests?: {length: number, _legacyOnly?: boolean, _fraggleRockOnly?: boolean};
     }
 
     export interface TestDfn {


Test patch:
diff --git a/lighthouse-cli/test/smokehouse/report-assert.js b/lighthouse-cli/test/smokehouse/report-assert.js
index aead6bd6f..48b13f063 100644
--- a/lighthouse-cli/test/smokehouse/report-assert.js
+++ b/lighthouse-cli/test/smokehouse/report-assert.js
@@ -163,6 +163,8 @@ function pruneExpectations(localConsole, lhr, expected) {
   const userAgentMatch = /Chrome\/(\d+)/.exec(userAgent); // Chrome/85.0.4174.0
   if (!userAgentMatch) throw new Error('Could not get chrome version.');
   const actualChromeVersion = Number(userAgentMatch[1]);
+  const isFraggleRock = lhr.configSettings.channel === 'fraggle-rock-cli';
+
   /**
    * @param {*} obj
    */
@@ -175,7 +177,7 @@ function pruneExpectations(localConsole, lhr, expected) {
   /**
    * @param {*} obj
    */
-  function pruneNewerChromeExpectations(obj) {
+  function pruneRecursively(obj) {
     for (const key of Object.keys(obj)) {
       const value = obj[key];
       if (!value || typeof value !== 'object') {
@@ -189,17 +191,33 @@ function pruneExpectations(localConsole, lhr, expected) {
           `Actual Chromium version: ${actualChromeVersion}`,
         ].join(' '));
         delete obj[key];
+      } else if (value._legacyOnly && isFraggleRock) {
+        localConsole.log([
+          `[${key}] marked legacy only but run is Fraggle Rock, pruning expectation:`,
+          JSON.stringify(value, null, 2),
+        ].join(' '));
+        delete obj[key];
+      } else if (value._fraggleRockOnly && !isFraggleRock) {
+        localConsole.log([
+          `[${key}] marked Fraggle Rock only but run is legacy, pruning expectation:`,
+          JSON.stringify(value, null, 2),
+          `Actual channel: ${lhr.configSettings.channel}`,
+        ].join(' '));
+        delete obj[key];
       } else {
-        pruneNewerChromeExpectations(value);
+        pruneRecursively(value);
       }
     }
+
+    delete obj._legacyOnly;
+    delete obj._fraggleRockOnly;
     delete obj._minChromiumMilestone;
     delete obj._maxChromiumMilestone;
   }
 
   const cloned = cloneDeep(expected);
 
-  pruneNewerChromeExpectations(cloned);
+  pruneRecursively(cloned);
   return cloned;
 }
 
diff --git a/lighthouse-cli/test/smokehouse/test-definitions/dobetterweb/dbw-expectations.js b/lighthouse-cli/test/smokehouse/test-definitions/dobetterweb/dbw-expectations.js
index c587d1bda..1855043d3 100644
--- a/lighthouse-cli/test/smokehouse/test-definitions/dobetterweb/dbw-expectations.js
+++ b/lighthouse-cli/test/smokehouse/test-definitions/dobetterweb/dbw-expectations.js
@@ -11,10 +11,14 @@
  */
 const expectations = {
   networkRequests: {
-    // 50 requests made for normal page testing.
+    // Number of network requests differs between Fraggle Rock and legacy modes because
+    // FR has fewer passes, preserve this check moving forward.
+    _fraggleRockOnly: true,
+
+    // 22 requests made for a single navigation.
     // 6 extra requests made because stylesheets are evicted from the cache by the time DT opens.
-    // 3 extra requests made to /dobetterweb/clock.appcache
-    length: 59,
+    // 1 request made to /dobetterweb/clock.appcache
+    length: 29,
   },
   artifacts: {
     HostFormFactor: 'desktop',
@@ -191,43 +195,40 @@ const expectations = {
       'errors-in-console': {
         score: 0,
         details: {
-          items: [
-            {
+          items: {
+            0: {
               source: 'other',
               description: 'Application Cache Error event: Manifest fetch failed (404) http://localhost:10200/dobetterweb/clock.appcache',
               url: 'http://localhost:10200/dobetterweb/dbw_tester.html',
             },
-            {
+            1: {
               source: 'exception',
               description: /^Error: A distinctive error\s+at http:\/\/localhost:10200\/dobetterweb\/dbw_tester.html:\d+:\d+$/,
               url: 'http://localhost:10200/dobetterweb/dbw_tester.html',
             },
-            {
+            2: {
               source: 'console.error',
               description: 'Error! Error!',
               url: 'http://localhost:10200/dobetterweb/dbw_tester.html',
             },
-            {
+            3: {
               source: 'network',
               description: 'Failed to load resource: the server responded with a status of 404 (Not Found)',
               url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200',
             },
-            {
+            4: {
               source: 'network',
               description: 'Failed to load resource: the server responded with a status of 404 (Not Found)',
               url: 'http://localhost:10200/dobetterweb/fcp-delayer.js?delay=5000',
             },
-            {
+            5: {
               source: 'network',
               description: 'Failed to load resource: the server responded with a status of 404 (Not Found)',
               url: 'http://localhost:10200/favicon.ico',
             },
-            {
-              source: 'network',
-              description: 'Failed to load resource: the server responded with a status of 404 (Not Found)',
-              url: 'http://localhost:10200/dobetterweb/unknown404.css?delay=200',
-            },
-          ],
+            // In legacy Lighthouse this audit will have additional duplicate failures which are a mistake.
+            // Fraggle Rock ordering of gatherer `stopInstrumentation` and `getArtifact` fixes the re-request issue.
+          },
         },
       },
       'is-on-https': {
@@ -418,8 +419,19 @@ const expectations = {
             data: /^data:image\/jpeg;.{500,}/,
           },
           nodes: {
+            // Test that the numbers for individual elements are in the ballpark.
+            // Exact ordering and IDs between FR and legacy differ, so fork the expectations.
             'page-0-IMG': {
-              // Test that these are numbers and in the ballpark.
+              _legacyOnly: true,
+              top: '650±50',
+              bottom: '650±50',
+              left: '10±10',
+              right: '120±20',
+              width: '120±20',
+              height: '20±20',
+            },
+            'page-2-IMG': {
+              _fraggleRockOnly: true,
               top: '650±50',
               bottom: '650±50',
               left: '10±10',
diff --git a/lighthouse-treemap/test/treemap-test-pptr.js b/lighthouse-treemap/test/treemap-test-pptr.js
index 0fe5e6330..a6a517e92 100644
--- a/lighthouse-treemap/test/treemap-test-pptr.js
+++ b/lighthouse-treemap/test/treemap-test-pptr.js
@@ -9,19 +9,24 @@
 
 /* global document, window */
 
-const fs = require('fs');
-const puppeteer = require('puppeteer');
-const {server} = require('../../lighthouse-cli/test/fixtures/static-server.js');
+import {jest} from '@jest/globals';
+import fs from 'fs';
+import puppeteer from 'puppeteer';
+import {server} from '../../lighthouse-cli/test/fixtures/static-server.js';
+import {LH_ROOT} from '../../root.js';
+
+const debugOptions = JSON.parse(
+  fs.readFileSync(LH_ROOT + '/lighthouse-treemap/app/debug.json', 'utf-8')
+);
 const portNumber = 20202;
 const treemapUrl = `http://localhost:${portNumber}/dist/gh-pages/treemap/index.html`;
-const debugOptions = require('../app/debug.json');
 
 // These tests run in Chromium and have their own timeouts.
 // Make sure we get the more helpful test-specific timeout error instead of jest's generic one.
 jest.setTimeout(35_000);
 
 function getTextEncodingCode() {
-  const code = fs.readFileSync(require.resolve('../../report/renderer/text-encoding.js'), 'utf-8');
+  const code = fs.readFileSync(LH_ROOT + '/report/renderer/text-encoding.js', 'utf-8');
   return code.replace('export ', '');
 }
 
diff --git a/lighthouse-treemap/test/util-test.js b/lighthouse-treemap/test/util-test.js
index dd7715d1c..2bd90c53a 100644
--- a/lighthouse-treemap/test/util-test.js
+++ b/lighthouse-treemap/test/util-test.js
@@ -7,10 +7,11 @@
 
 /* eslint-env jest */
 
-const assert = require('assert');
-const fs = require('fs');
-const jsdom = require('jsdom');
-const TreemapUtil = require('../app/src/util.js');
+import assert from 'assert';
+import fs from 'fs';
+import jsdom from 'jsdom';
+import {TreemapUtil} from '../app/src/util.js';
+import {LH_ROOT} from '../../root.js';
 
 describe('TreemapUtil', () => {
   it('pathsAreEqual works', () => {
@@ -75,7 +76,7 @@ describe('TreemapUtil', () => {
 
   describe('data-i18n', () => {
     it('should have only valid data-i18n values in treemap html', () => {
-      const TREEMAP_INDEX = fs.readFileSync(__dirname + '/../app/index.html', 'utf8');
+      const TREEMAP_INDEX = fs.readFileSync(LH_ROOT + '/lighthouse-treemap/app/index.html', 'utf8');
       const dom = new jsdom.JSDOM(TREEMAP_INDEX);
       for (const node of dom.window.document.querySelectorAll('[data-i18n]')) {
         const val = node.getAttribute('data-i18n');