Repository: GoogleChrome/lighthouse
Pull request #7191 (2019-02-08 23:46:48)

core(font-display): more accurately follow CSS spec
**Summary**
One of the reasons people seem to be getting false failures is our semicolon. Thanks to @Diklla for pointing this out!

**Related Issues/PRs**
#6628


wardpeet: @patrickhulce do you mind adding a test where you check all possibilities from the regex?
```
display:  'optional'    ;
```
```
display:  'optional' }
```
```
display:  'optional'}
```
patrickhulce: great points everyone!

since at this point we're just taking a step back on the whole audit, I thought I'd tighten it up and make it more correct rather than the loosely goosey forgiving audit that it was before :)

Patch:
diff --git a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js
index 792cd8c84..c3337b04e 100644
--- a/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js
+++ b/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js
@@ -66,15 +66,26 @@ class UnusedBytes extends Audit {
    * @param {LH.Artifacts.NetworkRequest=} networkRecord
    * @param {number} totalBytes Uncompressed size of the resource
    * @param {LH.Crdp.Page.ResourceType=} resourceType
-   * @param {number=} compressionRatio
    * @return {number}
    */
-  static estimateTransferSize(networkRecord, totalBytes, resourceType, compressionRatio = 0.5) {
+  static estimateTransferSize(networkRecord, totalBytes, resourceType) {
     if (!networkRecord) {
       // We don't know how many bytes this asset used on the network, but we can guess it was
       // roughly the size of the content gzipped.
-      // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for multipliers
-      return Math.round(totalBytes * compressionRatio);
+      // See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer for specific CSS/Script examples
+      // See https://discuss.httparchive.org/t/file-size-and-compression-savings/145 for fallback multipliers
+      switch (resourceType) {
+        case 'Stylesheet':
+          // Stylesheets tend to compress extremely well.
+          return Math.round(totalBytes * 0.2);
+        case 'Script':
+        case 'Document':
+          // Scripts and HTML compress fairly well too.
+          return Math.round(totalBytes * 0.33);
+        default:
+          // Otherwise we'll just fallback to the average savings in HTTPArchive
+          return Math.round(totalBytes * 0.5);
+      }
     } else if (networkRecord.resourceType === resourceType) {
       // This was a regular standalone asset, just use the transfer size.
       return networkRecord.transferSize || 0;
diff --git a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js
index 3cfc199c5..542b726e8 100644
--- a/lighthouse-core/audits/byte-efficiency/total-byte-weight.js
+++ b/lighthouse-core/audits/byte-efficiency/total-byte-weight.js
@@ -78,7 +78,10 @@ class TotalByteWeight extends ByteEfficiencyAudit {
       results.push(result);
     });
     const totalCompletedRequests = results.length;
-    results = results.sort((itemA, itemB) => itemB.totalBytes - itemA.totalBytes).slice(0, 10);
+    results = results.sort((itemA, itemB) => {
+      return itemB.totalBytes - itemA.totalBytes ||
+        itemA.url.localeCompare(itemB.url);
+    }).slice(0, 10);
 
     const score = ByteEfficiencyAudit.computeLogNormalScore(
       totalBytes,
diff --git a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js
index 4fd21e9f3..13346411a 100644
--- a/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js
+++ b/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js
@@ -257,9 +257,11 @@ class CacheHeaders extends Audit {
         });
       }
 
-      results.sort(
-        (a, b) => a.cacheLifetimeMs - b.cacheLifetimeMs || b.totalBytes - a.totalBytes
-      );
+      results.sort((a, b) => {
+        return a.cacheLifetimeMs - b.cacheLifetimeMs ||
+          b.totalBytes - a.totalBytes ||
+          a.url.localeCompare(b.url);
+      });
 
       const score = Audit.computeLogNormalScore(
         totalWastedBytes,
diff --git a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
index a599dde47..669d52bfb 100644
--- a/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
+++ b/lighthouse-core/audits/dobetterweb/external-anchors-use-rel-noopener.js
@@ -20,7 +20,7 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
       description: 'Add `rel="noopener"` or `rel="noreferrer"` to any external links to improve ' +
           'performance and prevent security vulnerabilities. ' +
           '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
-      requiredArtifacts: ['URL', 'AnchorsWithNoRelNoopener'],
+      requiredArtifacts: ['URL', 'AnchorElements'],
     };
   }
 
@@ -32,8 +32,10 @@ class ExternalAnchorsUseRelNoopenerAudit extends Audit {
     /** @type {string[]} */
     const warnings = [];
     const pageHost = new URL(artifacts.URL.finalUrl).host;
+    const failingAnchors = artifacts.AnchorElements
+      .filter(anchor => anchor.target === '_blank' && !anchor.rel.includes('noopener') &&
+        !anchor.rel.includes('noreferrer'))
     // Filter usages to exclude anchors that are same origin
-    const failingAnchors = artifacts.AnchorsWithNoRelNoopener
       .filter(anchor => {
         try {
           return new URL(anchor.href).host !== pageHost;
diff --git a/lighthouse-core/audits/font-display.js b/lighthouse-core/audits/font-display.js
index 2a1ce2609..53953b73c 100644
--- a/lighthouse-core/audits/font-display.js
+++ b/lighthouse-core/audits/font-display.js
@@ -7,7 +7,7 @@
 
 const Audit = require('./audit');
 const URL = require('../lib/url-shim').URL;
-const PASSING_FONT_DISPLAY_REGEX = /block|fallback|optional|swap/;
+const PASSING_FONT_DISPLAY_REGEX = /^(block|fallback|optional|swap)$/;
 const CSS_URL_REGEX = /url\((.*?)\)/;
 const CSS_URL_GLOBAL_REGEX = new RegExp(CSS_URL_REGEX, 'g');
 const i18n = require('../lib/i18n/i18n.js');
@@ -57,11 +57,13 @@ class FontDisplay extends Audit {
       const fontFaceDeclarations = newlinesStripped.match(/@font-face\s*{(.*?)}/g) || [];
       // Go through all the @font-face declarations to find a declared `font-display: ` property
       for (const declaration of fontFaceDeclarations) {
-        const rawFontDisplay = declaration.match(/font-display:(.*?);/);
+        // Find the font-display value by matching a single token, optionally surrounded by whitespace,
+        // followed either by a semicolon or the end of a block.
+        const rawFontDisplay = declaration.match(/font-display\s*:\s*(\w+)\s*(;|\})/);
         // If they didn't have a font-display property, it's the default, and it's failing; bail
         if (!rawFontDisplay) continue;
         // If they don't have one of the passing font-display values, it's failing; bail
-        const hasPassingFontDisplay = PASSING_FONT_DISPLAY_REGEX.test(rawFontDisplay[0]);
+        const hasPassingFontDisplay = PASSING_FONT_DISPLAY_REGEX.test(rawFontDisplay[1]);
         if (!hasPassingFontDisplay) continue;
 
         // If it's passing, we'll try to find the URL it's referencing.
diff --git a/lighthouse-core/audits/seo/link-text.js b/lighthouse-core/audits/seo/link-text.js
index fc95ebd28..6c2cc93b2 100644
--- a/lighthouse-core/audits/seo/link-text.js
+++ b/lighthouse-core/audits/seo/link-text.js
@@ -30,7 +30,7 @@ class LinkText extends Audit {
       failureTitle: 'Links do not have descriptive text',
       description: 'Descriptive link text helps search engines understand your content. ' +
       '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text).',
-      requiredArtifacts: ['URL', 'CrawlableLinks'],
+      requiredArtifacts: ['URL', 'AnchorElements'],
     };
   }
 
@@ -39,7 +39,8 @@ class LinkText extends Audit {
    * @return {LH.Audit.Product}
    */
   static audit(artifacts) {
-    const failingLinks = artifacts.CrawlableLinks
+    const failingLinks = artifacts.AnchorElements
+      .filter(link => link.href && !link.rel.includes('nofollow'))
       .filter(link => {
         const href = link.href.toLowerCase();
         if (
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index bf2e8cc95..5a8370e57 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -96,10 +96,10 @@ const defaultConfig = {
       'runtime-exceptions',
       'chrome-console-messages',
       'accessibility',
+      'anchor-elements',
       'image-elements',
       'link-elements',
       'meta-elements',
-      'dobetterweb/anchors-with-no-rel-noopener',
       'dobetterweb/appcache',
       'dobetterweb/doctype',
       'dobetterweb/domstats',
@@ -109,7 +109,6 @@ const defaultConfig = {
       'dobetterweb/response-compression',
       'dobetterweb/tags-blocking-first-paint',
       'seo/font-size',
-      'seo/crawlable-links',
       'seo/hreflang',
       'seo/embedded-content',
       'seo/canonical',
diff --git a/lighthouse-core/gather/gatherers/anchor-elements.js b/lighthouse-core/gather/gatherers/anchor-elements.js
new file mode 100644
index 000000000..3f8b6b80f
--- /dev/null
+++ b/lighthouse-core/gather/gatherers/anchor-elements.js
@@ -0,0 +1,80 @@
+/**
+ * @license Copyright 2019 Google Inc. 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';
+
+const Gatherer = require('./gatherer.js');
+const pageFunctions = require('../../lib/page-functions.js');
+
+/* eslint-env browser, node */
+
+/**
+ * Function that is stringified and run in the page to collect anchor elements.
+ * Additional complexity is introduced because anchors can be HTML or SVG elements.
+ *
+ * We use this evaluateAsync method because the `node.getAttribute` method doesn't actually normalize
+ * the values like access from JavaScript in-page does.
+ *
+ * @return {LH.Artifacts['AnchorElements']}
+ */
+/* istanbul ignore next */
+function collectAnchorElements() {
+  /** @param {string} url */
+  const resolveURLOrEmpty = url => {
+    try {
+      return new URL(url, window.location.href).href;
+    } catch (_) {
+      return '';
+    }
+  };
+
+  /** @type {Array<HTMLAnchorElement|SVGAElement>} */
+  // @ts-ignore - put into scope via stringification
+  const anchorElements = getElementsInDocument('a'); // eslint-disable-line no-undef
+
+  return anchorElements.map(node => {
+    // @ts-ignore - put into scope via stringification
+    const outerHTML = getOuterHTMLSnippet(node); // eslint-disable-line no-undef
+
+    if (node instanceof HTMLAnchorElement) {
+      return {
+        href: node.href,
+        text: node.innerText, // we don't want to return hidden text, so use innerText
+        rel: node.rel,
+        target: node.target,
+        outerHTML,
+      };
+    }
+
+    return {
+      href: resolveURLOrEmpty(node.href.baseVal),
+      text: node.textContent || '',
+      rel: '',
+      target: node.target.baseVal || '',
+      outerHTML,
+    };
+  });
+}
+
+class AnchorElements extends Gatherer {
+  /**
+   * @param {LH.Gatherer.PassContext} passContext
+   * @return {Promise<LH.Artifacts['AnchorElements']>}
+   */
+  async afterPass(passContext) {
+    const driver = passContext.driver;
+    const expression = `(() => {
+      ${pageFunctions.getOuterHTMLSnippetString};
+      ${pageFunctions.getElementsInDocumentString};
+
+      return (${collectAnchorElements})();
+    })()`;
+
+    /** @type {Array<LH.Artifacts.AnchorElement>} */
+    return driver.evaluateAsync(expression, {useIsolation: true});
+  }
+}
+
+module.exports = AnchorElements;
diff --git a/lighthouse-core/gather/gatherers/dobetterweb/anchors-with-no-rel-noopener.js b/lighthouse-core/gather/gatherers/dobetterweb/anchors-with-no-rel-noopener.js
deleted file mode 100644
index 18ff463b9..000000000
--- a/lighthouse-core/gather/gatherers/dobetterweb/anchors-with-no-rel-noopener.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * @license Copyright 2016 Google Inc. 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';
-
-const Gatherer = require('../gatherer');
-const pageFunctions = require('../../../lib/page-functions.js');
-
-class AnchorsWithNoRelNoopener extends Gatherer {
-  /**
-   * @param {LH.Gatherer.PassContext} passContext
-   * @return {Promise<LH.Artifacts['AnchorsWithNoRelNoopener']>}
-   */
-  afterPass(passContext) {
-    const expression = `(function() {
-      ${pageFunctions.getOuterHTMLSnippetString};
-      ${pageFunctions.getElementsInDocumentString}; // define function on page
-      const selector = 'a[target="_blank"]:not([rel~="noopener"]):not([rel~="noreferrer"])';
-      const elements = getElementsInDocument(selector);
-      return elements.map(node => ({
-        href: node.href,
-        rel: node.getAttribute('rel'),
-        target: node.getAttribute('target'),
-        outerHTML: getOuterHTMLSnippet(node),
-      }));
-    })()`;
-
-    return passContext.driver.evaluateAsync(expression);
-  }
-}
-
-module.exports = AnchorsWithNoRelNoopener;
diff --git a/lighthouse-core/gather/gatherers/seo/crawlable-links.js b/lighthouse-core/gather/gatherers/seo/crawlable-links.js
deleted file mode 100644
index fb27136c1..000000000
--- a/lighthouse-core/gather/gatherers/seo/crawlable-links.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @license Copyright 2017 Google Inc. 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';
-
-const Gatherer = require('../gatherer');
-const pageFunctions = require('../../../lib/page-functions.js');
-
-class CrawlableLinks extends Gatherer {
-  /**
-   * @param {LH.Gatherer.PassContext} passContext
-   * @return {Promise<LH.Artifacts['CrawlableLinks']>}
-   */
-  async afterPass(passContext) {
-    const expression = `(function() {
-      ${pageFunctions.getElementsInDocumentString}; // define function on page
-      const resolveURLOrNull = url => {
-        try { return new URL(url, window.location.href).href; }
-        catch (_) { return null; }
-      };
-
-      const selector = 'a[href]:not([rel~="nofollow"])';
-      const elements = getElementsInDocument(selector);
-      return elements
-        .map(node => ({
-          href: node.href instanceof SVGAnimatedString ?
-            resolveURLOrNull(node.href.baseVal) :
-            node.href,
-          text: node.href instanceof SVGAnimatedString ?
-            node.textContent :
-            node.innerText,
-        }));
-    })()`;
-
-    /** @type {LH.Artifacts['CrawlableLinks']} */
-    const links = await passContext.driver.evaluateAsync(expression, {useIsolation: true});
-    return links.filter(link => typeof link.href === 'string' && link.href);
-  }
-}
-
-module.exports = CrawlableLinks;
-
diff --git a/lighthouse-core/lib/emulation.js b/lighthouse-core/lib/emulation.js
index 73a443747..b4b8ba2c9 100644
--- a/lighthouse-core/lib/emulation.js
+++ b/lighthouse-core/lib/emulation.js
@@ -40,12 +40,12 @@ const DESKTOP_EMULATION_METRICS = {
 
 const NEXUS5X_USERAGENT = {
   // eslint-disable-next-line max-len
-  userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3559.0 Mobile Safari/537.36',
+  userAgent: 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3694.0 Mobile Safari/537.36',
 };
 
 const DESKTOP_USERAGENT = {
   // eslint-disable-next-line max-len
-  userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3559.0 Safari/537.36',
+  userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3694.0 Safari/537.36',
 };
 
 const OFFLINE_METRICS = {
diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json
index f365ce24c..97a1c102e 100644
--- a/proto/sample_v2_round_trip.json
+++ b/proto/sample_v2_round_trip.json
@@ -2467,15 +2467,15 @@
                     }, 
                     {
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200"
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100"
                     }, 
                     {
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true"
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true"
                     }, 
                     {
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true"
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200"
                     }
                 ], 
                 "type": "table"
@@ -2713,35 +2713,35 @@
                         "cacheHitProbability": 0.0, 
                         "cacheLifetimeMs": 0.0, 
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", 
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", 
                         "wastedBytes": 821.0
                     }, 
                     {
                         "cacheHitProbability": 0.0, 
                         "cacheLifetimeMs": 0.0, 
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100", 
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", 
                         "wastedBytes": 821.0
                     }, 
                     {
                         "cacheHitProbability": 0.0, 
                         "cacheLifetimeMs": 0.0, 
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", 
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", 
                         "wastedBytes": 821.0
                     }, 
                     {
                         "cacheHitProbability": 0.0, 
                         "cacheLifetimeMs": 0.0, 
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200", 
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true", 
                         "wastedBytes": 821.0
                     }, 
                     {
                         "cacheHitProbability": 0.0, 
                         "cacheLifetimeMs": 0.0, 
                         "totalBytes": 821.0, 
-                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true", 
+                        "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200", 
                         "wastedBytes": 821.0
                     }, 
                     {
diff --git a/types/artifacts.d.ts b/types/artifacts.d.ts
index a40bc276a..9dca79bb4 100644
--- a/types/artifacts.d.ts
+++ b/types/artifacts.d.ts
@@ -50,8 +50,8 @@ declare global {
     export interface GathererArtifacts {
       /** The results of running the aXe accessibility tests on the page. */
       Accessibility: Artifacts.Accessibility;
-      /** Information on all anchors in the page that aren't nofollow or noreferrer. */
-      AnchorsWithNoRelNoopener: {href: string; rel: string; target: string, outerHTML: string}[];
+      /** Array of all anchors on the page. */
+      AnchorElements: Artifacts.AnchorElement[];
       /** The value of the page's <html> manifest attribute, or null if not defined */
       AppCacheManifest: string | null;
       /** Array of all URLs cached in CacheStorage. */
@@ -60,8 +60,6 @@ declare global {
       Canonical: (string | null)[];
       /** Console deprecation and intervention warnings logged by Chrome during page load. */
       ChromeConsoleMessages: Crdp.Log.EntryAddedEvent[];
-      /** The href and innerText of all non-nofollow anchors in the page. */
-      CrawlableLinks: {href: string, text: string}[];
       /** CSS coverage information for styles used by page's final state. */
       CSSUsage: {rules: Crdp.CSS.RuleUsage[], stylesheets: Artifacts.CSSStyleSheetInfo[]};
       /** Information on the document's doctype(or null if not present), specifically the name, publicId, and systemId.
@@ -178,6 +176,15 @@ declare global {
         crossOrigin: 'anonymous'|'use-credentials'|null
       }
 
+      /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Attributes */
+      export interface AnchorElement {
+        rel: string
+        href: string
+        text: string
+        target: string
+        outerHTML: string
+      }
+
       export interface Font {
         display: string;
         family: string;


Test patch:
diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
index 1b75abf0f..40666ee60 100644
--- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
+++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
@@ -1093,6 +1093,9 @@ Object {
         Object {
           "path": "accessibility",
         },
+        Object {
+          "path": "anchor-elements",
+        },
         Object {
           "path": "image-elements",
         },
@@ -1102,9 +1105,6 @@ Object {
         Object {
           "path": "meta-elements",
         },
-        Object {
-          "path": "dobetterweb/anchors-with-no-rel-noopener",
-        },
         Object {
           "path": "dobetterweb/appcache",
         },
@@ -1132,9 +1132,6 @@ Object {
         Object {
           "path": "seo/font-size",
         },
-        Object {
-          "path": "seo/crawlable-links",
-        },
         Object {
           "path": "seo/hreflang",
         },
diff --git a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html
index e355ea648..4b617c90b 100644
--- a/lighthouse-cli/test/fixtures/byte-efficiency/tester.html
+++ b/lighthouse-cli/test/fixtures/byte-efficiency/tester.html
@@ -23,7 +23,7 @@ function generateInlineStyleWithSize(sizeInBytes, firstContent = '', used = fals
       div.classList.add(className);
       document.body.appendChild(div);
     }
-    sizeInBytes -= rule.length / 3; // 1 byte per character, GZip estimate is 3x
+    sizeInBytes -= rule.length * 0.2; // 1 byte per character, GZip estimate is 20% for Stylesheets
   }
 
   const style = document.createElement('style');
@@ -157,11 +157,11 @@ setTimeout(() => {
 // PASS: unused but too small savings
 generateInlineStyleWithSize(512, '.too-small { background: none; }\n');
 // PASS: used
-generateInlineStyleWithSize(4000, '.mostly-used { background: none; }\n', true);
-// PASSWARN: unused and a bit of savings
-generateInlineStyleWithSize(2000, '.kinda-unused { background: none; }\n');
+generateInlineStyleWithSize(5000, '.mostly-used { background: none; }\n', true);
+// FAIL: unused and a bit of savings
+generateInlineStyleWithSize(5000, '.kinda-unused { background: none; }\n');
 // FAIL: unused and lots of savings
-generateInlineStyleWithSize(24000, '.definitely-unused { background: none; }\n');
+generateInlineStyleWithSize(30000, '.definitely-unused { background: none; }\n');
 </script>
 
 <!-- Ensure the page takes at least 7 seconds and we don't exit before the lazily loaded image -->
diff --git a/lighthouse-cli/test/smokehouse/byte-efficiency/expectations.js b/lighthouse-cli/test/smokehouse/byte-efficiency/expectations.js
index d4a379638..d26e7cf15 100644
--- a/lighthouse-cli/test/smokehouse/byte-efficiency/expectations.js
+++ b/lighthouse-cli/test/smokehouse/byte-efficiency/expectations.js
@@ -17,7 +17,7 @@ module.exports = [
         details: {
           overallSavingsBytes: '>17000',
           items: {
-            length: 1,
+            length: 2,
           },
         },
       },
@@ -33,7 +33,7 @@ module.exports = [
       },
       'unused-css-rules': {
         details: {
-          overallSavingsBytes: '>39000',
+          overallSavingsBytes: '>35000',
           items: {
             length: 2,
           },
diff --git a/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js b/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js
index 135c661fb..d000649f6 100644
--- a/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js
+++ b/lighthouse-core/test/audits/byte-efficiency/byte-efficiency-audit-test.js
@@ -53,9 +53,11 @@ describe('Byte efficiency base audit', () => {
   describe('#estimateTransferSize', () => {
     const estimate = ByteEfficiencyAudit.estimateTransferSize;
 
-    it('should estimate by compression ratio when no network record available', () => {
-      const result = estimate(undefined, 1000, '', 0.345);
-      assert.equal(result, 345);
+    it('should estimate by resource type compression ratio when no network info available', () => {
+      assert.equal(estimate(undefined, 1000, 'Stylesheet'), 200);
+      assert.equal(estimate(undefined, 1000, 'Script'), 330);
+      assert.equal(estimate(undefined, 1000, 'Document'), 330);
+      assert.equal(estimate(undefined, 1000, ''), 500);
     });
 
     it('should return transferSize when asset matches', () => {
diff --git a/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js b/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js
index 3a5304cf6..693ea43e7 100644
--- a/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js
+++ b/lighthouse-core/test/audits/byte-efficiency/unused-css-rules-test.js
@@ -166,8 +166,8 @@ describe('Best Practices: unused css rules audit', () => {
     it('fails when lots of rules are unused', () => {
       return UnusedCSSAudit.audit_(getArtifacts({
         CSSUsage: {rules: [
-          {styleSheetId: 'a', used: true, startOffset: 0, endOffset: 11}, // 44 * 1 / 4
-          {styleSheetId: 'b', used: true, startOffset: 0, endOffset: 6000}, // 4000 * 3 / 2
+          {styleSheetId: 'a', used: true, startOffset: 0, endOffset: 11}, // 44 * 25% = 11
+          {styleSheetId: 'b', used: true, startOffset: 0, endOffset: 60000}, // 40000 * 3 * 50% = 60000
         ], stylesheets: [
           {
             header: {styleSheetId: 'a', sourceURL: 'file://a.css'},
@@ -175,7 +175,7 @@ describe('Best Practices: unused css rules audit', () => {
           },
           {
             header: {styleSheetId: 'b', sourceURL: 'file://b.css'},
-            content: `${generate('123', 4000)}`,
+            content: `${generate('123', 40000)}`,
           },
           {
             header: {styleSheetId: 'c', sourceURL: ''},
@@ -185,7 +185,7 @@ describe('Best Practices: unused css rules audit', () => {
       }), networkRecords).then(result => {
         assert.equal(result.items.length, 2);
         assert.equal(result.items[0].totalBytes, 10 * 1024);
-        assert.equal(result.items[1].totalBytes, 6000);
+        assert.equal(result.items[1].totalBytes, 40000 * 3 * 0.2);
         assert.equal(result.items[0].wastedPercent, 75);
         assert.equal(result.items[1].wastedPercent, 50);
       });
diff --git a/lighthouse-core/test/audits/dobetterweb/external-anchors-use-rel-noopener-test.js b/lighthouse-core/test/audits/dobetterweb/external-anchors-use-rel-noopener-test.js
index 5d1efdb6a..f852eb230 100644
--- a/lighthouse-core/test/audits/dobetterweb/external-anchors-use-rel-noopener-test.js
+++ b/lighthouse-core/test/audits/dobetterweb/external-anchors-use-rel-noopener-test.js
@@ -16,9 +16,36 @@ const URL = 'https://google.com/test';
 describe('External anchors use rel="noopener"', () => {
   it('passes when links are from same hosts as the page host', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: 'https://google.com/test'},
-        {href: 'https://google.com/test1'},
+      AnchorElements: [
+        {href: 'https://google.com/test', target: '_blank', rel: ''},
+        {href: 'https://google.com/test1', target: '_blank', rel: ''},
+      ],
+      URL: {finalUrl: URL},
+    });
+    assert.equal(auditResult.rawValue, true);
+    assert.equal(auditResult.details.items.length, 0);
+    assert.equal(auditResult.details.items.length, 0);
+  });
+
+  it('passes when links have a valid rel', () => {
+    const auditResult = ExternalAnchorsAudit.audit({
+      AnchorElements: [
+        {href: 'https://other.com/test', target: '_blank', rel: 'nofollow noopener'},
+        {href: 'https://other.com/test1', target: '_blank', rel: 'noreferrer'},
+        {href: 'https://other.com/test2', target: '_blank', rel: 'noopener'},
+      ],
+      URL: {finalUrl: URL},
+    });
+    assert.equal(auditResult.rawValue, true);
+    assert.equal(auditResult.details.items.length, 0);
+    assert.equal(auditResult.details.items.length, 0);
+  });
+
+  it('passes when links do not use target=_blank', () => {
+    const auditResult = ExternalAnchorsAudit.audit({
+      AnchorElements: [
+        {href: 'https://other.com/test', rel: ''},
+        {href: 'https://other.com/test1', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
@@ -29,9 +56,9 @@ describe('External anchors use rel="noopener"', () => {
 
   it('passes when links have javascript in href attribute', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: 'javascript:void(0)'},
-        {href: 'JAVASCRIPT:void(0)'},
+      AnchorElements: [
+        {href: 'javascript:void(0)', target: '_blank', rel: ''},
+        {href: 'JAVASCRIPT:void(0)', target: '_blank', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
@@ -41,9 +68,9 @@ describe('External anchors use rel="noopener"', () => {
 
   it('passes when links have mailto in href attribute', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: 'mailto:inbox@email.com'},
-        {href: 'MAILTO:INBOX@EMAIL.COM'},
+      AnchorElements: [
+        {href: 'mailto:inbox@email.com', target: '_blank', rel: ''},
+        {href: 'MAILTO:INBOX@EMAIL.COM', target: '_blank', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
@@ -53,9 +80,9 @@ describe('External anchors use rel="noopener"', () => {
 
   it('fails when links are from different hosts than the page host', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: 'https://example.com/test'},
-        {href: 'https://example.com/test1'},
+      AnchorElements: [
+        {href: 'https://example.com/test', target: '_blank', rel: 'nofollow'},
+        {href: 'https://example.com/test1', target: '_blank', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
@@ -66,8 +93,8 @@ describe('External anchors use rel="noopener"', () => {
 
   it('fails when links have no href attribute', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: ''},
+      AnchorElements: [
+        {href: '', target: '_blank', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
@@ -79,11 +106,11 @@ describe('External anchors use rel="noopener"', () => {
 
   it('fails when links have href attribute starting with a protocol', () => {
     const auditResult = ExternalAnchorsAudit.audit({
-      AnchorsWithNoRelNoopener: [
-        {href: 'http://'},
-        {href: 'http:'},
-        {href: 'https://'},
-        {href: 'https:'},
+      AnchorElements: [
+        {href: 'http://', target: '_blank', rel: ''},
+        {href: 'http:', target: '_blank', rel: ''},
+        {href: 'https://', target: '_blank', rel: ''},
+        {href: 'https:', target: '_blank', rel: ''},
       ],
       URL: {finalUrl: URL},
     });
diff --git a/lighthouse-core/test/audits/font-display-test.js b/lighthouse-core/test/audits/font-display-test.js
index 6a9dadd0d..0d20f4951 100644
--- a/lighthouse-core/test/audits/font-display-test.js
+++ b/lighthouse-core/test/audits/font-display-test.js
@@ -37,11 +37,13 @@ describe('Performance: Font Display audit', () => {
       }
 
       @font-face {
+        font-display: 'optional'; // invalid quotes, should still fail for being invalid rule
         /* try up a directory with ' */
         src: url('../font-b.woff');
       }
 
       @font-face {
+        font-display: optional // missing a semi-colon, should still fail for being invalid block
         /* try no path with no quotes ' */
         src: url(font.woff);
       }
@@ -72,32 +74,32 @@ describe('Performance: Font Display audit', () => {
       {url: networkRecords[2].url, wastedMs: 1000},
     ];
     assert.strictEqual(result.rawValue, false);
-    assert.deepEqual(result.details.items, items);
+    expect(result.details.items).toEqual(items);
   });
 
   it('resolves URLs relative to stylesheet URL when available', async () => {
     stylesheet.header.sourceURL = 'https://cdn.example.com/foo/bar/file.css';
     stylesheet.content = `
       @font-face {
-        font-display: 'block';
+        font-display: block;
         /* try with " and same directory */
         src: url("./font-a.woff");
       }
 
       @font-face {
-        font-display: 'block';
+        font-display: block;
         /* try with " and site origin */
         src: url("https://example.com/foo/bar/document-font.woff");
       }
 
       @font-face {
-        font-display: 'fallback';
+        font-display: fallback;
         /* try up a directory with ' */
         src: url('../font-b.woff');
       }
 
       @font-face {
-        font-display: 'optional';
+        font-display: optional;
         /* try no path with no quotes ' */
         src: url(font.woff);
       }
@@ -128,25 +130,25 @@ describe('Performance: Font Display audit', () => {
 
     const result = await FontDisplayAudit.audit(getArtifacts(), context);
     assert.strictEqual(result.rawValue, true);
-    assert.deepEqual(result.details.items, []);
+    expect(result.details.items).toEqual([]);
   });
 
   it('passes when all fonts have a correct font-display rule', async () => {
     stylesheet.content = `
       @font-face {
-        font-display: 'block';
+        font-display: block;
         /* try with " */
         src: url("./font-a.woff");
       }
 
       @font-face {
-        font-display: 'fallback';
+        font-display: fallback;
         /* try up a directory with ' */
         src: url('../font-b.woff');
       }
 
       @font-face {
-        font-display: 'optional';
+        font-display: optional;
         /* try no path with no quotes ' */
         src: url(font.woff);
       }
@@ -172,7 +174,7 @@ describe('Performance: Font Display audit', () => {
 
     const result = await FontDisplayAudit.audit(getArtifacts(), context);
     assert.strictEqual(result.rawValue, true);
-    assert.deepEqual(result.details.items, []);
+    expect(result.details.items).toEqual([]);
   });
 
   it('should handle real-world font-face declarations', async () => {
@@ -212,7 +214,7 @@ describe('Performance: Font Display audit', () => {
 
     const result = await FontDisplayAudit.audit(getArtifacts(), context);
     assert.strictEqual(result.rawValue, false);
-    assert.deepEqual(result.details.items.map(item => item.url), [
+    expect(result.details.items.map(item => item.url)).toEqual([
       'https://edition.i.cdn.cnn.com/.a/fonts/cnn/3.7.2/cnnclock-black.woff2',
       'https://registry.api.cnn.io/assets/fave/fonts/2.0.15/cnnsans-bold.woff',
       // FontAwesome should pass
@@ -220,4 +222,36 @@ describe('Performance: Font Display audit', () => {
       'https://fonts.gstatic.com/s/lato/v14/S6u9w4BMUTPHh50XSwiPGQ3q5d0.woff2',
     ]);
   });
+
+  it('handles varied font-display declarations', async () => {
+    stylesheet.content = `
+      @font-face {
+        src: url(font-0.woff);
+        font-display: swap
+      }
+
+      @font-face {src: url(font-1.woff);font-display   : fallback
+      }
+
+      @font-face {
+        src: url(font-2.woff);
+        font-display:optional}
+
+      @font-face {
+        src: url(font-3.woff);
+        font-display    :  swap  ;  }
+
+      @font-face {src: url(font-4.woff);font-display:swap;}
+    `;
+
+    networkRecords = Array.from({length: 5}).map((_, idx) => ({
+      url: `https://example.com/foo/bar/font-${idx}.woff`,
+      endTime: 2, startTime: 1,
+      resourceType: 'Font',
+    }));
+
+    const result = await FontDisplayAudit.audit(getArtifacts(), context);
+    expect(result.details.items).toEqual([]);
+    assert.strictEqual(result.rawValue, true);
+  });
 });
diff --git a/lighthouse-core/test/audits/seo/link-text-test.js b/lighthouse-core/test/audits/seo/link-text-test.js
index 6fce06b0b..86ac948a8 100644
--- a/lighthouse-core/test/audits/seo/link-text-test.js
+++ b/lighthouse-core/test/audits/seo/link-text-test.js
@@ -12,15 +12,15 @@ const assert = require('assert');
 
 describe('SEO: link text audit', () => {
   it('fails when link with non descriptive text is found', () => {
-    const invalidLink = {href: 'https://example.com/otherpage.html', text: 'click here'};
+    const invalidLink = {href: 'https://example.com/otherpage.html', text: 'click here', rel: ''};
     const artifacts = {
       URL: {
         finalUrl: 'https://example.com/page.html',
       },
-      CrawlableLinks: [
-        {href: 'https://example.com/otherpage.html', text: 'legit link text'},
+      AnchorElements: [
+        {href: 'https://example.com/otherpage.html', text: 'legit link text', rel: ''},
         invalidLink,
-        {href: 'https://example.com/otherpage.html', text: 'legit link text'},
+        {href: 'https://example.com/otherpage.html', text: 'legit link text', rel: ''},
       ],
     };
 
@@ -36,11 +36,11 @@ describe('SEO: link text audit', () => {
       URL: {
         finalUrl: 'https://example.com/page.html',
       },
-      CrawlableLinks: [
-        {href: 'https://example.com/otherpage.html', text: 'legit link text'},
-        {href: 'https://example.com/page.html', text: 'click here'},
-        {href: 'https://example.com/page.html#test', text: 'click here'},
-        {href: 'https://example.com/otherpage.html', text: 'legit link text'},
+      AnchorElements: [
+        {href: 'https://example.com/otherpage.html', text: 'legit link text', rel: ''},
+        {href: 'https://example.com/page.html', text: 'click here', rel: ''},
+        {href: 'https://example.com/page.html#test', text: 'click here', rel: ''},
+        {href: 'https://example.com/otherpage.html', text: 'legit link text', rel: ''},
       ],
     };
 
@@ -53,10 +53,10 @@ describe('SEO: link text audit', () => {
       URL: {
         finalUrl: 'https://example.com/page.html',
       },
-      CrawlableLinks: [
-        {href: 'javascript:alert(1)', text: 'click here'},
-        {href: 'JavaScript:window.location="/otherpage.html"', text: 'click here'},
-        {href: 'JAVASCRIPT:void(0)', text: 'click here'},
+      AnchorElements: [
+        {href: 'javascript:alert(1)', text: 'click here', rel: ''},
+        {href: 'JavaScript:window.location="/otherpage.html"', text: 'click here', rel: ''},
+        {href: 'JAVASCRIPT:void(0)', text: 'click here', rel: ''},
       ],
     };
 
@@ -69,9 +69,37 @@ describe('SEO: link text audit', () => {
       URL: {
         finalUrl: 'https://example.com/page.html',
       },
-      CrawlableLinks: [
-        {href: 'mailto:info@example.com', text: 'click here'},
-        {href: 'mailto:mailmaster@localhost', text: 'click here'},
+      AnchorElements: [
+        {href: 'mailto:info@example.com', text: 'click here', rel: ''},
+        {href: 'mailto:mailmaster@localhost', text: 'click here', rel: ''},
+      ],
+    };
+
+    const auditResult = LinkTextAudit.audit(artifacts);
+    assert.equal(auditResult.rawValue, true);
+  });
+
+  it('ignores links with no href', () => {
+    const artifacts = {
+      URL: {
+        finalUrl: 'https://example.com/page.html',
+      },
+      AnchorElements: [
+        {href: '', text: 'click here', rel: ''},
+      ],
+    };
+
+    const auditResult = LinkTextAudit.audit(artifacts);
+    assert.equal(auditResult.rawValue, true);
+  });
+
+  it('ignores links with nofollow', () => {
+    const artifacts = {
+      URL: {
+        finalUrl: 'https://example.com/page.html',
+      },
+      AnchorElements: [
+        {href: '', text: 'click here', rel: 'noopener nofollow'},
       ],
     };
 
@@ -84,10 +112,10 @@ describe('SEO: link text audit', () => {
       URL: {
         finalUrl: 'https://example.com/page.html',
       },
-      CrawlableLinks: [
-        {href: 'https://example.com/otherpage.html', text: 'legit link text'},
-        {href: 'http://example.com/page.html?test=test', text: 'legit link text'},
-        {href: 'file://Users/user/Desktop/file.png', text: 'legit link text'},
+      AnchorElements: [
+        {href: 'https://example.com/otherpage.html', text: 'legit link text', rel: ''},
+        {href: 'http://example.com/page.html?test=test', text: 'legit link text', rel: ''},
+        {href: 'file://Users/user/Desktop/file.png', text: 'legit link text', rel: ''},
       ],
     };
 
diff --git a/lighthouse-core/test/results/artifacts/artifacts.json b/lighthouse-core/test/results/artifacts/artifacts.json
index 2a3807764..548581f7c 100644
--- a/lighthouse-core/test/results/artifacts/artifacts.json
+++ b/lighthouse-core/test/results/artifacts/artifacts.json
@@ -2229,39 +2229,52 @@
       "col": 45
     }
   ],
-  "AnchorsWithNoRelNoopener": [
+  "AnchorElements": [
     {
       "href": "https://www.google.com/",
       "outerHTML": "<a href=\"https://www.google.com/\" target=\"blank\">Hello</a>",
-      "rel": null,
-      "target": "_blank"
+      "rel": "",
+      "target": "_blank",
+      "text": "external link"
     },
     {
       "href": "",
       "outerHTML": "<a target=\"blank\">Hello</a>",
-      "rel": null,
-      "target": "_blank"
+      "rel": "",
+      "target": "_blank",
+      "text": "Hello"
+    },
+    {
+      "href": "https://www.google.com/",
+      "outerHTML": "<a rel=\"noopener\" href=\"https://www.google.com/\" target=\"blank\">Hello</a>",
+      "rel": "noopener",
+      "target": "_blank",
+      "text": "external link that uses noopener"
     },
     {
       "href": "https://www.google.com/",
       "outerHTML": "<a rel=\"nofollow\" href=\"https://www.google.com/\" target=\"blank\">Hello</a>",
       "rel": "nofollow",
-      "target": "_blank"
+      "target": "_blank",
+      "text": "external link that uses nofollow"
     },
     {
       "href": "http://localhost:10200/dobetterweb/doesnotexist",
-      "rel": null,
-      "target": "_blank"
+      "rel": "",
+      "target": "_blank",
+      "text": "internal link is ok"
     },
     {
       "href": "javascript:void(0)",
-      "rel": null,
-      "target": "_blank"
+      "rel": "",
+      "target": "_blank",
+      "text": ""
     },
     {
       "href": "mailto:inbox@email.com",
-      "rel": null,
-      "target": "_blank"
+      "rel": "",
+      "target": "_blank",
+      "text": ""
     }
   ],
   "AppCacheManifest": "clock.appcache",
@@ -2488,28 +2501,6 @@
     "visitedTextLength": 228,
     "totalTextLength": 228
   },
-  "CrawlableLinks": [
-    {
-      "href": "https://www.google.com/",
-      "text": "external link"
-    },
-    {
-      "href": "https://www.google.com/",
-      "text": "external link that uses rel noopener"
-    },
-    {
-      "href": "http://localhost:10200/dobetterweb/doesnotexist",
-      "text": "internal link is ok"
-    },
-    {
-      "href": "javascript:void(0)",
-      "text": ""
-    },
-    {
-      "href": "mailto:inbox@email.com",
-      "text": ""
-    }
-  ],
   "Hreflang": [],
   "EmbeddedContent": [],
   "Canonical": [],
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 950cfeddd..fd7b349a2 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -2158,35 +2158,35 @@
             "wastedBytes": 1108
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100",
             "cacheLifetimeMs": 0,
             "cacheHitProbability": 0,
             "totalBytes": 821,
             "wastedBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true",
             "cacheLifetimeMs": 0,
             "cacheHitProbability": 0,
             "totalBytes": 821,
             "wastedBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200",
             "cacheLifetimeMs": 0,
             "cacheHitProbability": 0,
             "totalBytes": 821,
             "wastedBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true",
             "cacheLifetimeMs": 0,
             "cacheHitProbability": 0,
             "totalBytes": 821,
             "wastedBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?scriptActivated&delay=200",
             "cacheLifetimeMs": 0,
             "cacheHitProbability": 0,
             "totalBytes": 821,
@@ -2264,15 +2264,15 @@
             "totalBytes": 1108
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=100",
             "totalBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=3000&async=true",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true",
             "totalBytes": 821
           },
           {
-            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2000&async=true",
+            "url": "http://localhost:10200/dobetterweb/dbw_tester.css?delay=2200",
             "totalBytes": 821
           }
         ]