lighthouse 8.6.0-dev.20211024 → 8.6.0-dev.20211028

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/report/flow.js +11 -11
  2. package/dist/report/standalone.js +2 -2
  3. package/flow-report/assets/styles.css +57 -1
  4. package/flow-report/src/common.tsx +8 -8
  5. package/flow-report/src/header.tsx +7 -8
  6. package/flow-report/src/i18n/ui-strings.js +2 -0
  7. package/flow-report/src/summary/category.tsx +73 -4
  8. package/flow-report/src/summary/summary.tsx +2 -1
  9. package/flow-report/src/util.ts +4 -4
  10. package/flow-report/src/wrappers/markdown.tsx +27 -0
  11. package/flow-report/src/wrappers/report.tsx +12 -0
  12. package/flow-report/test/common-test.tsx +6 -6
  13. package/flow-report/test/summary/category-test.tsx +111 -19
  14. package/flow-report/test/wrappers/markdown-test.tsx +29 -0
  15. package/jest.config.js +2 -2
  16. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +4 -2
  17. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +11 -10
  18. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +5 -3
  19. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +2 -1
  20. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +2 -1
  21. package/lighthouse-core/audits/image-aspect-ratio.js +4 -3
  22. package/lighthouse-core/audits/image-size-responsive.js +3 -2
  23. package/lighthouse-core/audits/preload-lcp-image.js +7 -5
  24. package/lighthouse-core/audits/script-treemap-data.js +1 -1
  25. package/lighthouse-core/audits/unsized-images.js +1 -2
  26. package/package.json +7 -7
  27. package/report/README.md +1 -1
  28. package/report/generator/README.md +1 -1
  29. package/report/renderer/category-renderer.js +7 -0
  30. package/report/renderer/pwa-category-renderer.js +0 -10
  31. package/report/renderer/report-renderer.js +1 -1
  32. package/report/test/renderer/category-renderer-test.js +41 -0
  33. package/report/test/renderer/pwa-category-renderer-test.js +4 -4
  34. package/report/test/renderer/text-encoding-test.js +1 -1
  35. package/shared/localization/format.js +5 -9
  36. package/shared/localization/locales/ar-XB.json +8 -8
  37. package/shared/localization/locales/ar.json +8 -8
  38. package/shared/localization/locales/bg.json +8 -8
  39. package/shared/localization/locales/ca.json +8 -8
  40. package/shared/localization/locales/cs.json +8 -8
  41. package/shared/localization/locales/da.json +8 -8
  42. package/shared/localization/locales/de.json +8 -8
  43. package/shared/localization/locales/el.json +8 -8
  44. package/shared/localization/locales/en-GB.json +8 -8
  45. package/shared/localization/locales/en-US.json +27 -24
  46. package/shared/localization/locales/en-XA.json +8 -8
  47. package/shared/localization/locales/en-XL.json +27 -24
  48. package/shared/localization/locales/es-419.json +8 -8
  49. package/shared/localization/locales/es.json +8 -8
  50. package/shared/localization/locales/fi.json +8 -8
  51. package/shared/localization/locales/fil.json +8 -8
  52. package/shared/localization/locales/fr.json +8 -8
  53. package/shared/localization/locales/he.json +8 -8
  54. package/shared/localization/locales/hi.json +8 -8
  55. package/shared/localization/locales/hr.json +8 -8
  56. package/shared/localization/locales/hu.json +8 -8
  57. package/shared/localization/locales/id.json +8 -8
  58. package/shared/localization/locales/it.json +8 -8
  59. package/shared/localization/locales/ja.json +8 -8
  60. package/shared/localization/locales/ko.json +8 -8
  61. package/shared/localization/locales/lt.json +8 -8
  62. package/shared/localization/locales/lv.json +8 -8
  63. package/shared/localization/locales/nl.json +8 -8
  64. package/shared/localization/locales/no.json +8 -8
  65. package/shared/localization/locales/pl.json +8 -8
  66. package/shared/localization/locales/pt-PT.json +8 -8
  67. package/shared/localization/locales/pt.json +8 -8
  68. package/shared/localization/locales/ro.json +8 -8
  69. package/shared/localization/locales/ru.json +8 -8
  70. package/shared/localization/locales/sk.json +8 -8
  71. package/shared/localization/locales/sl.json +8 -8
  72. package/shared/localization/locales/sr-Latn.json +8 -8
  73. package/shared/localization/locales/sr.json +8 -8
  74. package/shared/localization/locales/sv.json +8 -8
  75. package/shared/localization/locales/ta.json +8 -8
  76. package/shared/localization/locales/te.json +8 -8
  77. package/shared/localization/locales/th.json +8 -8
  78. package/shared/localization/locales/tr.json +8 -8
  79. package/shared/localization/locales/uk.json +8 -8
  80. package/shared/localization/locales/vi.json +8 -8
  81. package/shared/localization/locales/zh-HK.json +8 -8
  82. package/shared/localization/locales/zh-TW.json +8 -8
  83. package/shared/localization/locales/zh.json +8 -8
  84. package/tsconfig-all.json +2 -2
  85. package/types/lhr/audit-details.d.ts +5 -5
@@ -63,7 +63,7 @@ class s{constructor(e){this._document=e,this._lighthouseChannel="unknown",this._
63
63
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
64
64
  * See the License for the specific language governing permissions and
65
65
  * limitations under the License.
66
- */class d{constructor(e,t){this.dom=e,this.detailsRenderer=t}get _clumpTitles(){return{warning:l.i18n.strings.warningAuditsGroupTitle,manual:l.i18n.strings.manualAuditsGroupTitle,passed:l.i18n.strings.passedAuditsGroupTitle,notApplicable:l.i18n.strings.notApplicableAuditsGroupTitle}}renderAudit(e){const t=this.dom.createComponent("audit");return this.populateAuditValues(e,t)}populateAuditValues(e,t){const n=l.i18n.strings,r=this.dom.find(".lh-audit",t);r.id=e.result.id;const o=e.result.scoreDisplayMode;e.result.displayValue&&(this.dom.find(".lh-audit__display-text",r).textContent=e.result.displayValue);const i=this.dom.find(".lh-audit__title",r);i.appendChild(this.dom.convertMarkdownCodeSnippets(e.result.title));const a=this.dom.find(".lh-audit__description",r);a.appendChild(this.dom.convertMarkdownLinkSnippets(e.result.description));for(const t of e.relevantMetrics||[]){const e=this.dom.createChildOf(a,"span","lh-audit__adorn");e.title=`Relevant to ${t.result.title}`,e.textContent=t.acronym||t.id}e.stackPacks&&e.stackPacks.forEach((e=>{const t=this.dom.createElement("div");t.classList.add("lh-audit__stackpack");const n=this.dom.createElement("img");n.classList.add("lh-audit__stackpack__img"),n.src=e.iconDataURL,n.alt=e.title,t.appendChild(n),t.appendChild(this.dom.convertMarkdownLinkSnippets(e.description)),this.dom.find(".lh-audit__stackpacks",r).appendChild(t)}));const s=this.dom.find("details",r);if(e.result.details){const t=this.detailsRenderer.render(e.result.details);t&&(t.classList.add("lh-details"),s.appendChild(t))}if(this.dom.find(".lh-chevron-container",r).appendChild(this._createChevron()),this._setRatingClass(r,e.result.score,o),"error"===e.result.scoreDisplayMode){r.classList.add("lh-audit--error");const t=this.dom.find(".lh-audit__display-text",r);t.textContent=n.errorLabel,t.classList.add("lh-tooltip-boundary");this.dom.createChildOf(t,"div","lh-tooltip lh-tooltip--error").textContent=e.result.errorMessage||n.errorMissingAuditInfo}else if(e.result.explanation){this.dom.createChildOf(i,"div","lh-audit-explanation").textContent=e.result.explanation}const c=e.result.warnings;if(!c||0===c.length)return r;const d=this.dom.find("summary",s),h=this.dom.createChildOf(d,"div","lh-warnings");if(this.dom.createChildOf(h,"span").textContent=n.warningHeader,1===c.length)h.appendChild(this.dom.document().createTextNode(c.join("")));else{const e=this.dom.createChildOf(h,"ul");for(const t of c){this.dom.createChildOf(e,"li").textContent=t}}return r}injectFinalScreenshot(e,t,n){const r=t["final-screenshot"];if(!r||"error"===r.scoreDisplayMode)return null;if(!r.details||"screenshot"!==r.details.type)return null;const o=this.dom.createElement("img","lh-final-ss-image"),i=r.details.data;o.src=i,o.alt=r.title;const a=this.dom.find(".lh-category .lh-category-header",e),l=this.dom.createElement("div","lh-category-headercol"),s=this.dom.createElement("div","lh-category-headercol lh-category-headercol--separator"),c=this.dom.createElement("div","lh-category-headercol");l.append(...a.childNodes),l.append(n),c.append(o),a.append(l,s,c),a.classList.add("lh-category-header__finalscreenshot")}_createChevron(){const e=this.dom.createComponent("chevron");return this.dom.find("svg.lh-chevron",e)}_setRatingClass(e,t,n){const r=l.calculateRating(t,n);return e.classList.add(`lh-audit--${n.toLowerCase()}`),"informative"!==n&&e.classList.add(`lh-audit--${r}`),e}renderCategoryHeader(e,t,n){const r=this.dom.createComponent("categoryHeader"),o=this.dom.find(".lh-score__gauge",r),i=this.renderCategoryScore(e,t,n);if(o.appendChild(i),e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);this.dom.find(".lh-category-header__description",r).appendChild(t)}return r}renderAuditGroup(e){const t=this.dom.createElement("div","lh-audit-group"),n=this.dom.createElement("div","lh-audit-group__header");if(this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);t.classList.add("lh-audit-group__description"),n.appendChild(t)}return t.appendChild(n),t}_renderGroupedAudits(e,t){const n=new Map,r="NotAGroup";n.set(r,[]);for(const t of e){const e=t.group||r,o=n.get(e)||[];o.push(t),n.set(e,o)}const o=[];for(const[e,i]of n){if(e===r){for(const e of i)o.push(this.renderAudit(e));continue}const n=t[e],a=this.renderAuditGroup(n);for(const e of i)a.appendChild(this.renderAudit(e));a.classList.add(`lh-audit-group--${e}`),o.push(a)}return o}renderUnexpandableClump(e,t){const n=this.dom.createElement("div");return this._renderGroupedAudits(e,t).forEach((e=>n.appendChild(e))),n}renderClump(e,{auditRefs:t,description:n}){const r=this.dom.createComponent("clump"),o=this.dom.find(".lh-clump",r);"warning"===e&&o.setAttribute("open","");this.dom.find("div.lh-audit-group__summary",o).appendChild(this._createChevron());const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];if(this.dom.find(".lh-audit-group__title",i).textContent=a,n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description"),i.appendChild(e)}this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const l=t.map(this.renderAudit.bind(this));return o.append(...l),o.classList.add(`lh-clump--${e.toLowerCase()}`),o}renderCategoryScore(e,t,n){return n&&l.shouldDisplayAsFraction(n.gatherMode)?this.renderCategoryFraction(e):this.renderScoreGauge(e,t)}renderScoreGauge(e,t){const n=this.dom.createComponent("gauge"),r=this.dom.find("a.lh-gauge__wrapper",n);l.isPluginCategory(e.id)&&r.classList.add("lh-gauge__wrapper--plugin");const o=Number(e.score),i=this.dom.find(".lh-gauge",n),a=this.dom.find("circle.lh-gauge-arc",i);a&&this._setGaugeArc(a,o);const s=Math.round(100*o),c=this.dom.find("div.lh-gauge__percentage",n);return c.textContent=s.toString(),null===e.score&&(c.textContent="?",c.title=l.i18n.strings.errorLabel),0===e.auditRefs.length||this.hasApplicableAudits(e)?r.classList.add(`lh-gauge__wrapper--${l.calculateRating(e.score)}`):(r.classList.add("lh-gauge__wrapper--not-applicable"),c.textContent="-",c.title=l.i18n.strings.notApplicableAuditsGroupTitle),this.dom.find(".lh-gauge__label",n).textContent=e.title,n}renderCategoryFraction(e){const t=this.dom.createComponent("fraction"),n=this.dom.find("a.lh-fraction__wrapper",t),{numPassed:r,numPassableAudits:o,totalWeight:i}=l.calculateCategoryFraction(e),a=r/o,s=this.dom.find(".lh-fraction__content",t),c=this.dom.createElement("span");c.textContent=`${r}/${o}`,s.appendChild(c);let d=l.calculateRating(a);return 0===i&&(d="null"),n.classList.add(`lh-fraction__wrapper--${d}`),this.dom.find(".lh-fraction__label",t).textContent=e.title,t}hasApplicableAudits(e){return e.auditRefs.some((e=>"notApplicable"!==e.result.scoreDisplayMode))}_setGaugeArc(e,t){const n=2*Math.PI*Number(e.getAttribute("r")),r=Number(e.getAttribute("stroke-width")),o=.25*r/n;e.style.transform=`rotate(${360*o-90}deg)`;let i=t*n-r/2;0===t&&(e.style.opacity="0"),1===t&&(i=n),e.style.strokeDasharray=`${Math.max(i,0)} ${n}`}_auditHasWarning(e){return Boolean(e.result.warnings&&e.result.warnings.length)}_getClumpIdForAuditRef(e){const t=e.result.scoreDisplayMode;return"manual"===t||"notApplicable"===t?t:l.showAsPassed(e.result)?this._auditHasWarning(e)?"warning":"passed":"failed"}render(e,t={},n){const r=this.dom.createElement("div","lh-category");r.id=e.id,r.appendChild(this.renderCategoryHeader(e,t,n));const o=new Map;o.set("failed",[]),o.set("warning",[]),o.set("manual",[]),o.set("passed",[]),o.set("notApplicable",[]);for(const t of e.auditRefs){const e=this._getClumpIdForAuditRef(t),n=o.get(e);n.push(t),o.set(e,n)}for(const[n,i]of o){if(0===i.length)continue;if("failed"===n){const e=this.renderUnexpandableClump(i,t);e.classList.add("lh-clump--failed"),r.appendChild(e);continue}const o="manual"===n?e.manualDescription:void 0,a=this.renderClump(n,{auditRefs:i,description:o});r.appendChild(a)}return r}}
66
+ */class d{constructor(e,t){this.dom=e,this.detailsRenderer=t}get _clumpTitles(){return{warning:l.i18n.strings.warningAuditsGroupTitle,manual:l.i18n.strings.manualAuditsGroupTitle,passed:l.i18n.strings.passedAuditsGroupTitle,notApplicable:l.i18n.strings.notApplicableAuditsGroupTitle}}renderAudit(e){const t=this.dom.createComponent("audit");return this.populateAuditValues(e,t)}populateAuditValues(e,t){const n=l.i18n.strings,r=this.dom.find(".lh-audit",t);r.id=e.result.id;const o=e.result.scoreDisplayMode;e.result.displayValue&&(this.dom.find(".lh-audit__display-text",r).textContent=e.result.displayValue);const i=this.dom.find(".lh-audit__title",r);i.appendChild(this.dom.convertMarkdownCodeSnippets(e.result.title));const a=this.dom.find(".lh-audit__description",r);a.appendChild(this.dom.convertMarkdownLinkSnippets(e.result.description));for(const t of e.relevantMetrics||[]){const e=this.dom.createChildOf(a,"span","lh-audit__adorn");e.title=`Relevant to ${t.result.title}`,e.textContent=t.acronym||t.id}e.stackPacks&&e.stackPacks.forEach((e=>{const t=this.dom.createElement("div");t.classList.add("lh-audit__stackpack");const n=this.dom.createElement("img");n.classList.add("lh-audit__stackpack__img"),n.src=e.iconDataURL,n.alt=e.title,t.appendChild(n),t.appendChild(this.dom.convertMarkdownLinkSnippets(e.description)),this.dom.find(".lh-audit__stackpacks",r).appendChild(t)}));const s=this.dom.find("details",r);if(e.result.details){const t=this.detailsRenderer.render(e.result.details);t&&(t.classList.add("lh-details"),s.appendChild(t))}if(this.dom.find(".lh-chevron-container",r).appendChild(this._createChevron()),this._setRatingClass(r,e.result.score,o),"error"===e.result.scoreDisplayMode){r.classList.add("lh-audit--error");const t=this.dom.find(".lh-audit__display-text",r);t.textContent=n.errorLabel,t.classList.add("lh-tooltip-boundary");this.dom.createChildOf(t,"div","lh-tooltip lh-tooltip--error").textContent=e.result.errorMessage||n.errorMissingAuditInfo}else if(e.result.explanation){this.dom.createChildOf(i,"div","lh-audit-explanation").textContent=e.result.explanation}const c=e.result.warnings;if(!c||0===c.length)return r;const d=this.dom.find("summary",s),h=this.dom.createChildOf(d,"div","lh-warnings");if(this.dom.createChildOf(h,"span").textContent=n.warningHeader,1===c.length)h.appendChild(this.dom.document().createTextNode(c.join("")));else{const e=this.dom.createChildOf(h,"ul");for(const t of c){this.dom.createChildOf(e,"li").textContent=t}}return r}injectFinalScreenshot(e,t,n){const r=t["final-screenshot"];if(!r||"error"===r.scoreDisplayMode)return null;if(!r.details||"screenshot"!==r.details.type)return null;const o=this.dom.createElement("img","lh-final-ss-image"),i=r.details.data;o.src=i,o.alt=r.title;const a=this.dom.find(".lh-category .lh-category-header",e),l=this.dom.createElement("div","lh-category-headercol"),s=this.dom.createElement("div","lh-category-headercol lh-category-headercol--separator"),c=this.dom.createElement("div","lh-category-headercol");l.append(...a.childNodes),l.append(n),c.append(o),a.append(l,s,c),a.classList.add("lh-category-header__finalscreenshot")}_createChevron(){const e=this.dom.createComponent("chevron");return this.dom.find("svg.lh-chevron",e)}_setRatingClass(e,t,n){const r=l.calculateRating(t,n);return e.classList.add(`lh-audit--${n.toLowerCase()}`),"informative"!==n&&e.classList.add(`lh-audit--${r}`),e}renderCategoryHeader(e,t,n){const r=this.dom.createComponent("categoryHeader"),o=this.dom.find(".lh-score__gauge",r),i=this.renderCategoryScore(e,t,n);if(o.appendChild(i),e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);this.dom.find(".lh-category-header__description",r).appendChild(t)}return r}renderAuditGroup(e){const t=this.dom.createElement("div","lh-audit-group"),n=this.dom.createElement("div","lh-audit-group__header");if(this.dom.createChildOf(n,"span","lh-audit-group__title").textContent=e.title,e.description){const t=this.dom.convertMarkdownLinkSnippets(e.description);t.classList.add("lh-audit-group__description"),n.appendChild(t)}return t.appendChild(n),t}_renderGroupedAudits(e,t){const n=new Map,r="NotAGroup";n.set(r,[]);for(const t of e){const e=t.group||r,o=n.get(e)||[];o.push(t),n.set(e,o)}const o=[];for(const[e,i]of n){if(e===r){for(const e of i)o.push(this.renderAudit(e));continue}const n=t[e],a=this.renderAuditGroup(n);for(const e of i)a.appendChild(this.renderAudit(e));a.classList.add(`lh-audit-group--${e}`),o.push(a)}return o}renderUnexpandableClump(e,t){const n=this.dom.createElement("div");return this._renderGroupedAudits(e,t).forEach((e=>n.appendChild(e))),n}renderClump(e,{auditRefs:t,description:n}){const r=this.dom.createComponent("clump"),o=this.dom.find(".lh-clump",r);"warning"===e&&o.setAttribute("open","");this.dom.find("div.lh-audit-group__summary",o).appendChild(this._createChevron());const i=this.dom.find(".lh-audit-group__header",o),a=this._clumpTitles[e];if(this.dom.find(".lh-audit-group__title",i).textContent=a,n){const e=this.dom.convertMarkdownLinkSnippets(n);e.classList.add("lh-audit-group__description"),i.appendChild(e)}this.dom.find(".lh-audit-group__itemcount",o).textContent=`(${t.length})`;const l=t.map(this.renderAudit.bind(this));return o.append(...l),o.classList.add(`lh-clump--${e.toLowerCase()}`),o}renderCategoryScore(e,t,n){return n&&l.shouldDisplayAsFraction(n.gatherMode)?this.renderCategoryFraction(e):this.renderScoreGauge(e,t)}renderScoreGauge(e,t){const n=this.dom.createComponent("gauge"),r=this.dom.find("a.lh-gauge__wrapper",n);l.isPluginCategory(e.id)&&r.classList.add("lh-gauge__wrapper--plugin");const o=Number(e.score),i=this.dom.find(".lh-gauge",n),a=this.dom.find("circle.lh-gauge-arc",i);a&&this._setGaugeArc(a,o);const s=Math.round(100*o),c=this.dom.find("div.lh-gauge__percentage",n);return c.textContent=s.toString(),null===e.score&&(c.textContent="?",c.title=l.i18n.strings.errorLabel),0===e.auditRefs.length||this.hasApplicableAudits(e)?r.classList.add(`lh-gauge__wrapper--${l.calculateRating(e.score)}`):(r.classList.add("lh-gauge__wrapper--not-applicable"),c.textContent="-",c.title=l.i18n.strings.notApplicableAuditsGroupTitle),this.dom.find(".lh-gauge__label",n).textContent=e.title,n}renderCategoryFraction(e){const t=this.dom.createComponent("fraction"),n=this.dom.find("a.lh-fraction__wrapper",t),{numPassed:r,numPassableAudits:o,totalWeight:i}=l.calculateCategoryFraction(e),a=r/o,s=this.dom.find(".lh-fraction__content",t),c=this.dom.createElement("span");c.textContent=`${r}/${o}`,s.appendChild(c);let d=l.calculateRating(a);return 0===i&&(d="null"),n.classList.add(`lh-fraction__wrapper--${d}`),this.dom.find(".lh-fraction__label",t).textContent=e.title,t}hasApplicableAudits(e){return e.auditRefs.some((e=>"notApplicable"!==e.result.scoreDisplayMode))}_setGaugeArc(e,t){const n=2*Math.PI*Number(e.getAttribute("r")),r=Number(e.getAttribute("stroke-width")),o=.25*r/n;e.style.transform=`rotate(${360*o-90}deg)`;let i=t*n-r/2;0===t&&(e.style.opacity="0"),1===t&&(i=n),e.style.strokeDasharray=`${Math.max(i,0)} ${n}`}_auditHasWarning(e){return Boolean(e.result.warnings&&e.result.warnings.length)}_getClumpIdForAuditRef(e){const t=e.result.scoreDisplayMode;return"manual"===t||"notApplicable"===t?t:l.showAsPassed(e.result)?this._auditHasWarning(e)?"warning":"passed":"failed"}render(e,t={},n){const r=this.dom.createElement("div","lh-category");r.id=e.id,r.appendChild(this.renderCategoryHeader(e,t,n));const o=new Map;o.set("failed",[]),o.set("warning",[]),o.set("manual",[]),o.set("passed",[]),o.set("notApplicable",[]);for(const t of e.auditRefs){const e=this._getClumpIdForAuditRef(t),n=o.get(e);n.push(t),o.set(e,n)}for(const e of o.values())e.sort(((e,t)=>t.weight-e.weight));for(const[n,i]of o){if(0===i.length)continue;if("failed"===n){const e=this.renderUnexpandableClump(i,t);e.classList.add("lh-clump--failed"),r.appendChild(e);continue}const o="manual"===n?e.manualDescription:void 0,a=this.renderClump(n,{auditRefs:i,description:o});r.appendChild(a)}return r}}
67
67
  /**
68
68
  * @license
69
69
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -142,7 +142,7 @@ class s{constructor(e){this._document=e,this._lighthouseChannel="unknown",this._
142
142
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
143
143
  * See the License for the specific language governing permissions and
144
144
  * limitations under the License.
145
- */class N extends d{render(e,t={}){const n=this.dom.createElement("div","lh-category");n.id=e.id,n.appendChild(this.renderCategoryHeader(e,t));const r=e.auditRefs,o=r.filter((e=>"manual"!==e.result.scoreDisplayMode)),i=this._renderAudits(o,t);n.appendChild(i);const a=r.filter((e=>"manual"===e.result.scoreDisplayMode)),l=this.renderClump("manual",{auditRefs:a,description:e.manualDescription});return n.appendChild(l),n}renderScoreGauge(e,t){return this.renderCategoryScore(e,t)}renderCategoryScore(e,t){if(null===e.score)return super.renderScoreGauge(e,t);const n=this.dom.createComponent("gaugePwa"),r=this.dom.find("a.lh-gauge--pwa__wrapper",n),o=n.querySelector("svg");if(!o)throw new Error("no SVG element found in PWA score gauge template");N._makeSvgReferencesUnique(o);const i=this._getGroupIds(e.auditRefs),a=this._getPassingGroupIds(e.auditRefs);if(a.size===i.size)r.classList.add("lh-badged--all");else for(const e of a)r.classList.add(`lh-badged--${e}`);return this.dom.find(".lh-gauge__label",n).textContent=e.title,r.title=this._getGaugeTooltip(e.auditRefs,t),n}_getGroupIds(e){const t=e.map((e=>e.group)).filter((e=>!!e));return new Set(t)}_getPassingGroupIds(e){const t=this._getGroupIds(e);for(const n of e)!l.showAsPassed(n.result)&&n.group&&t.delete(n.group);return t}_getGaugeTooltip(e,t){const n=this._getGroupIds(e),r=[];for(const o of n){const n=e.filter((e=>e.group===o)),i=n.length,a=n.filter((e=>l.showAsPassed(e.result))).length,s=t[o].title;r.push(`${s}: ${a}/${i}`)}return r.join(", ")}_renderAudits(e,t){const n=this.renderUnexpandableClump(e,t),r=this._getPassingGroupIds(e);for(const e of r){this.dom.find(`.lh-audit-group--${e}`,n).classList.add("lh-badged")}return n}static _makeSvgReferencesUnique(e){const t=e.querySelector("defs");if(!t)return;const n=l.getUniqueSuffix(),r=t.querySelectorAll("[id]");for(const t of r){const r=t.id,o=`${r}-${n}`;t.id=o;const i=e.querySelectorAll(`use[href="#${r}"]`);for(const e of i)e.setAttribute("href",`#${o}`);const a=e.querySelectorAll(`[fill="url(#${r})"]`);for(const e of a)e.setAttribute("fill",`url(#${o})`)}}}
145
+ */class N extends d{render(e,t={}){const n=this.dom.createElement("div","lh-category");n.id=e.id,n.appendChild(this.renderCategoryHeader(e,t));const r=e.auditRefs,o=r.filter((e=>"manual"!==e.result.scoreDisplayMode)),i=this._renderAudits(o,t);n.appendChild(i);const a=r.filter((e=>"manual"===e.result.scoreDisplayMode)),l=this.renderClump("manual",{auditRefs:a,description:e.manualDescription});return n.appendChild(l),n}renderCategoryScore(e,t){if(null===e.score)return super.renderScoreGauge(e,t);const n=this.dom.createComponent("gaugePwa"),r=this.dom.find("a.lh-gauge--pwa__wrapper",n),o=n.querySelector("svg");if(!o)throw new Error("no SVG element found in PWA score gauge template");N._makeSvgReferencesUnique(o);const i=this._getGroupIds(e.auditRefs),a=this._getPassingGroupIds(e.auditRefs);if(a.size===i.size)r.classList.add("lh-badged--all");else for(const e of a)r.classList.add(`lh-badged--${e}`);return this.dom.find(".lh-gauge__label",n).textContent=e.title,r.title=this._getGaugeTooltip(e.auditRefs,t),n}_getGroupIds(e){const t=e.map((e=>e.group)).filter((e=>!!e));return new Set(t)}_getPassingGroupIds(e){const t=this._getGroupIds(e);for(const n of e)!l.showAsPassed(n.result)&&n.group&&t.delete(n.group);return t}_getGaugeTooltip(e,t){const n=this._getGroupIds(e),r=[];for(const o of n){const n=e.filter((e=>e.group===o)),i=n.length,a=n.filter((e=>l.showAsPassed(e.result))).length,s=t[o].title;r.push(`${s}: ${a}/${i}`)}return r.join(", ")}_renderAudits(e,t){const n=this.renderUnexpandableClump(e,t),r=this._getPassingGroupIds(e);for(const e of r){this.dom.find(`.lh-audit-group--${e}`,n).classList.add("lh-badged")}return n}static _makeSvgReferencesUnique(e){const t=e.querySelector("defs");if(!t)return;const n=l.getUniqueSuffix(),r=t.querySelectorAll("[id]");for(const t of r){const r=t.id,o=`${r}-${n}`;t.id=o;const i=e.querySelectorAll(`use[href="#${r}"]`);for(const e of i)e.setAttribute("href",`#${o}`);const a=e.querySelectorAll(`[fill="url(#${r})"]`);for(const e of a)e.setAttribute("fill",`url(#${o})`)}}}
146
146
  /**
147
147
  * @license
148
148
  * Copyright 2017 The Lighthouse Authors. All Rights Reserved.
@@ -31,6 +31,7 @@
31
31
  --summary-subtitle-font-size: 12px;
32
32
  --summary-title-font-size: 20px;
33
33
  --summary-tooltip-box-shadow-color: rgba(0, 0, 0, 0.25);
34
+ --summary-tooltip-line-height: 16px;
34
35
  --topbar-button-font-size: 14px;
35
36
  --topbar-button-size: 45px;
36
37
  --topbar-title-font-size: 14px;
@@ -394,22 +395,33 @@
394
395
  border-radius: 3px;
395
396
  padding: var(--base-spacing);
396
397
  right: 0;
398
+ line-height: var(--summary-tooltip-line-height);
397
399
  box-shadow: 0px 4px 4px var(--summary-tooltip-box-shadow-color);
398
400
  z-index: 1;
399
401
  }
400
402
 
401
403
  .SummaryTooltip__title {
402
404
  font-weight: var(--bold-weight);
403
- margin-bottom: var(--half-base-spacing);
405
+ }
406
+
407
+ .SummaryTooltip__url {
408
+ overflow-x: hidden;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ margin-top: calc(0.25 * var(--base-spacing));
412
+ margin-bottom: calc(0.75 * var(--base-spacing));
413
+ color: var(--color-gray-700);
404
414
  }
405
415
 
406
416
  .SummaryTooltip__category {
407
417
  font-weight: var(--bold-weight);
408
418
  display: flex;
419
+ align-items: center;
409
420
  margin-top: var(--half-base-spacing);
410
421
  }
411
422
 
412
423
  .SummaryTooltip__category-title {
424
+ line-height: 24px;
413
425
  flex-grow: 1;
414
426
  }
415
427
 
@@ -424,6 +436,50 @@
424
436
  color: var(--color-fail);
425
437
  }
426
438
 
439
+ .SummaryTooltip__fraction {
440
+ color: var(--color-gray-700);
441
+ }
442
+
443
+ .SummaryTooltip__informative {
444
+ margin-top: calc(0.25 * var(--base-spacing));
445
+ color: var(--color-gray-700);
446
+ }
447
+
448
+ .SummaryTooltipAudits__title {
449
+ line-height: 24px;
450
+ margin-top: calc(0.75 * var(--base-spacing));
451
+ font-weight: var(--bold-weight);
452
+ }
453
+
454
+ .SummaryTooltipAudit {
455
+ display: flex;
456
+ margin: calc(0.25 * var(--base-spacing)) 0px;
457
+ color: var(--color-gray-700);
458
+ --score-icon-size: 10px;
459
+ }
460
+ .SummaryTooltipAudit code {
461
+ color: var(--snippet-color);
462
+ }
463
+ .SummaryTooltipAudit::before {
464
+ content: '';
465
+ height: var(--score-icon-size);
466
+ width: var(--score-icon-size);
467
+ margin-top: calc((var(--summary-tooltip-line-height) - var(--score-icon-size)) / 2);
468
+ margin-right: var(--half-base-spacing);
469
+ }
470
+ .SummaryTooltipAudit--pass::before {
471
+ background-color: var(--color-pass);
472
+ border-radius: 50%;
473
+ }
474
+ .SummaryTooltipAudit--average::before {
475
+ background-color: var(--color-average);
476
+ }
477
+ .SummaryTooltipAudit--fail::before {
478
+ border-left: calc(var(--score-icon-size) / 2) solid transparent;
479
+ border-right: calc(var(--score-icon-size) / 2) solid transparent;
480
+ border-bottom: var(--score-icon-size) solid var(--color-fail);
481
+ }
482
+
427
483
  .SummaryNavigationHeader {
428
484
  font-size: var(--summary-navigation-header-font-size);
429
485
  line-height: 16px;
@@ -8,7 +8,7 @@ import {FunctionComponent} from 'preact';
8
8
  import {useEffect, useState} from 'preact/hooks';
9
9
 
10
10
  import {NavigationIcon, SnapshotIcon, TimespanIcon} from './icons';
11
- import {getFilmstripFrames, getScreenDimensions, getScreenshot} from './util';
11
+ import {getFilmstripFrames, getScreenDimensions, getFullPageScreenshot} from './util';
12
12
 
13
13
  const ANIMATION_FRAME_DURATION_MS = 500;
14
14
 
@@ -72,15 +72,15 @@ const FlowStepAnimatedThumbnail: FunctionComponent<{
72
72
  };
73
73
 
74
74
  export const FlowStepThumbnail: FunctionComponent<{
75
- reportResult: LH.ReportResult,
75
+ lhr: LH.Result,
76
76
  width?: number,
77
77
  height?: number,
78
- }> = ({reportResult, width, height}) => {
79
- const screenshot = getScreenshot(reportResult);
80
- const frames = getFilmstripFrames(reportResult);
78
+ }> = ({lhr, width, height}) => {
79
+ const screenshot = getFullPageScreenshot(lhr);
80
+ const frames = getFilmstripFrames(lhr);
81
81
 
82
82
  // Resize the image to fit the viewport aspect ratio.
83
- const dimensions = getScreenDimensions(reportResult);
83
+ const dimensions = getScreenDimensions(lhr);
84
84
  if (width && height === undefined) {
85
85
  height = dimensions.height * width / dimensions.width;
86
86
  } else if (height && width === undefined) {
@@ -92,7 +92,7 @@ export const FlowStepThumbnail: FunctionComponent<{
92
92
  return <></>;
93
93
  }
94
94
 
95
- if (reportResult.gatherMode === 'timespan' && frames && frames.length) {
95
+ if (lhr.gatherMode === 'timespan' && frames && frames.length) {
96
96
  return <FlowStepAnimatedThumbnail frames={frames} width={width} height={height} />;
97
97
  }
98
98
 
@@ -101,7 +101,7 @@ export const FlowStepThumbnail: FunctionComponent<{
101
101
  screenshot &&
102
102
  <img
103
103
  className="FlowStepThumbnail"
104
- src={screenshot}
104
+ src={screenshot.screenshot.data}
105
105
  style={{width, height}}
106
106
  alt="Screenshot of a page tested by Lighthouse"
107
107
  />
@@ -6,7 +6,6 @@
6
6
 
7
7
  import {FunctionComponent} from 'preact';
8
8
 
9
- import {Util} from '../../report/renderer/util';
10
9
  import {FlowStepIcon, FlowStepThumbnail} from './common';
11
10
  import {useLocalizedStrings} from './i18n/i18n';
12
11
  import {getModeDescription, useFlowResult} from './util';
@@ -15,16 +14,16 @@ const SIDE_THUMBNAIL_HEIGHT = 80;
15
14
  const MAIN_THUMBNAIL_HEIGHT = 120;
16
15
 
17
16
  const HeaderThumbnail: FunctionComponent<{
18
- reportResult: LH.ReportResult,
17
+ lhr: LH.Result,
19
18
  position: 'prev'|'next'|'main'
20
19
  }> =
21
- ({reportResult, position}) => {
20
+ ({lhr, position}) => {
22
21
  const height = position === 'main' ? MAIN_THUMBNAIL_HEIGHT : SIDE_THUMBNAIL_HEIGHT;
23
22
  return (
24
23
  <div className={`HeaderThumbnail HeaderThumbnail--${position}`}>
25
- <FlowStepThumbnail reportResult={reportResult} height={height}/>
24
+ <FlowStepThumbnail lhr={lhr} height={height}/>
26
25
  <div className="HeaderThumbnail__icon">
27
- <FlowStepIcon mode={reportResult.gatherMode}/>
26
+ <FlowStepIcon mode={lhr.gatherMode}/>
28
27
  </div>
29
28
  </div>
30
29
  );
@@ -50,7 +49,7 @@ export const Header: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
50
49
  flowResult.steps[index - 2] && <div className="Header__segment"/>
51
50
  }
52
51
  <div className="Header__prev-thumbnail">
53
- <HeaderThumbnail reportResult={Util.prepareReportResult(prevStep.lhr)} position="prev"/>
52
+ <HeaderThumbnail lhr={prevStep.lhr} position="prev"/>
54
53
  <div className="Header__segment"/>
55
54
  </div>
56
55
  <a
@@ -60,7 +59,7 @@ export const Header: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
60
59
  </>
61
60
  }
62
61
  <div className="Header__current-thumbnail">
63
- <HeaderThumbnail reportResult={Util.prepareReportResult(step.lhr)} position="main"/>
62
+ <HeaderThumbnail lhr={step.lhr} position="main"/>
64
63
  </div>
65
64
  <div className="Header__current-title">
66
65
  {step.name}
@@ -72,7 +71,7 @@ export const Header: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
72
71
  nextStep && <>
73
72
  <div className="Header__next-thumbnail">
74
73
  <div className="Header__segment"/>
75
- <HeaderThumbnail reportResult={Util.prepareReportResult(nextStep.lhr)} position="next"/>
74
+ <HeaderThumbnail lhr={nextStep.lhr} position="next"/>
76
75
  </div>
77
76
  <a
78
77
  className="Header__next-title"
@@ -131,4 +131,6 @@ export const UIStrings = {
131
131
  =1 {{numInformative} informative audit}
132
132
  other {{numInformative} informative audits}
133
133
  }`,
134
+ /** Label for a list of Lighthouse audits that are the most impactful. */
135
+ highestImpact: 'Highest impact',
134
136
  };
@@ -10,9 +10,14 @@ import {Util} from '../../../report/renderer/util';
10
10
  import {Separator} from '../common';
11
11
  import {CategoryScore} from '../wrappers/category-score';
12
12
  import {useI18n, useStringFormatter, useLocalizedStrings} from '../i18n/i18n';
13
+ import {Markdown} from '../wrappers/markdown';
13
14
 
14
15
  import type {UIStringsType} from '../i18n/ui-strings';
15
16
 
17
+ const MAX_TOOLTIP_AUDITS = 2;
18
+
19
+ type ScoredAuditRef = LH.ReportResult.AuditRef & {result: {score: number}};
20
+
16
21
  function getGatherModeLabel(gatherMode: LH.Result.GatherMode, strings: UIStringsType) {
17
22
  switch (gatherMode) {
18
23
  case 'navigation': return strings.navigationReport;
@@ -30,10 +35,71 @@ function getCategoryRating(rating: string, strings: UIStringsType) {
30
35
  }
31
36
  }
32
37
 
38
+ function getScoreToBeGained(audit: ScoredAuditRef): number {
39
+ return audit.weight * (1 - audit.result.score);
40
+ }
41
+
42
+ function getOverallSavings(audit: LH.ReportResult.AuditRef): number {
43
+ return (
44
+ audit.result.details &&
45
+ audit.result.details.type === 'opportunity' &&
46
+ audit.result.details.overallSavingsMs
47
+ ) || 0;
48
+ }
49
+
50
+ const SummaryTooltipAudit: FunctionComponent<{audit: LH.ReportResult.AuditRef}> = ({audit}) => {
51
+ const rating = Util.calculateRating(audit.result.score, audit.result.scoreDisplayMode);
52
+ return (
53
+ <div className={`SummaryTooltipAudit SummaryTooltipAudit--${rating}`}>
54
+ <Markdown text={audit.result.title}/>
55
+ </div>
56
+ );
57
+ };
58
+
59
+ const SummaryTooltipAudits: FunctionComponent<{category: LH.ReportResult.Category}> =
60
+ ({category}) => {
61
+ const strings = useLocalizedStrings();
62
+
63
+ function isRelevantAudit(audit: LH.ReportResult.AuditRef): audit is ScoredAuditRef {
64
+ return audit.result.score !== null &&
65
+ // Metrics should not be displayed in this group.
66
+ audit.group !== 'metrics' &&
67
+ // Audits in performance without a group are hidden.
68
+ (audit.group !== undefined || category.id !== 'performance') &&
69
+ // We don't want unweighted audits except for opportunities with potential savings.
70
+ (audit.weight > 0 || getOverallSavings(audit) > 0) &&
71
+ // Passing audits should never be high impact.
72
+ !Util.showAsPassed(audit.result);
73
+ }
74
+
75
+ const audits = category.auditRefs
76
+ .filter(isRelevantAudit)
77
+ .sort((a, b) => {
78
+ // Remaining score should always be 0 for perf opportunities because weight is 0.
79
+ // In that case, we want to sort by `overallSavingsMs`.
80
+ const remainingScoreA = getScoreToBeGained(a);
81
+ const remainingScoreB = getScoreToBeGained(b);
82
+ if (remainingScoreA !== remainingScoreB) return remainingScoreB - remainingScoreA;
83
+ return getOverallSavings(b) - getOverallSavings(a);
84
+ })
85
+ .splice(0, MAX_TOOLTIP_AUDITS);
86
+ if (!audits.length) return null;
87
+
88
+ return (
89
+ <div className="SummaryTooltipAudits">
90
+ <div className="SummaryTooltipAudits__title">{strings.highestImpact}</div>
91
+ {
92
+ audits.map(audit => <SummaryTooltipAudit key={audit.id} audit={audit}/>)
93
+ }
94
+ </div>
95
+ );
96
+ };
97
+
33
98
  export const SummaryTooltip: FunctionComponent<{
34
99
  category: LH.ReportResult.Category,
35
- gatherMode: LH.Result.GatherMode
36
- }> = ({category, gatherMode}) => {
100
+ gatherMode: LH.Result.GatherMode,
101
+ url: string,
102
+ }> = ({category, gatherMode, url}) => {
37
103
  const strings = useLocalizedStrings();
38
104
  const str_ = useStringFormatter();
39
105
  const {
@@ -53,6 +119,7 @@ export const SummaryTooltip: FunctionComponent<{
53
119
  return (
54
120
  <div className="SummaryTooltip">
55
121
  <div className="SummaryTooltip__title">{getGatherModeLabel(gatherMode, strings)}</div>
122
+ <div className="SummaryTooltip__url">{url}</div>
56
123
  <Separator/>
57
124
  <div className="SummaryTooltip__category">
58
125
  <div className="SummaryTooltip__category-title">
@@ -81,6 +148,7 @@ export const SummaryTooltip: FunctionComponent<{
81
148
  {str_(strings.informativeAuditCount, {numInformative})}
82
149
  </div>
83
150
  }
151
+ <SummaryTooltipAudits category={category}/>
84
152
  </div>
85
153
  );
86
154
  };
@@ -89,7 +157,8 @@ export const SummaryCategory: FunctionComponent<{
89
157
  category: LH.ReportResult.Category|undefined,
90
158
  href: string,
91
159
  gatherMode: LH.Result.GatherMode,
92
- }> = ({category, href, gatherMode}) => {
160
+ finalUrl: string,
161
+ }> = ({category, href, gatherMode, finalUrl}) => {
93
162
  return (
94
163
  <div className="SummaryCategory">
95
164
  {
@@ -100,7 +169,7 @@ export const SummaryCategory: FunctionComponent<{
100
169
  href={href}
101
170
  gatherMode={gatherMode}
102
171
  />
103
- <SummaryTooltip category={category} gatherMode={gatherMode}/>
172
+ <SummaryTooltip category={category} gatherMode={gatherMode} url={finalUrl}/>
104
173
  </div> :
105
174
  <div className="SummaryCategory__null" data-testid="SummaryCategory__null"/>
106
175
  }
@@ -63,7 +63,7 @@ export const SummaryFlowStep: FunctionComponent<{
63
63
  <Separator/>
64
64
  </div>
65
65
  }
66
- <FlowStepThumbnail reportResult={reportResult} width={THUMBNAIL_WIDTH}/>
66
+ <FlowStepThumbnail lhr={lhr} width={THUMBNAIL_WIDTH}/>
67
67
  <FlowSegment mode={lhr.gatherMode}/>
68
68
  <div className="SummaryFlowStep__label">
69
69
  <div className="SummaryFlowStep__mode">{modeDescription}</div>
@@ -76,6 +76,7 @@ export const SummaryFlowStep: FunctionComponent<{
76
76
  category={reportResult.categories[c]}
77
77
  href={`#index=${hashIndex}&anchor=${c}`}
78
78
  gatherMode={lhr.gatherMode}
79
+ finalUrl={lhr.finalUrl}
79
80
  />
80
81
  ))
81
82
  }
@@ -35,24 +35,24 @@ export function classNames(...args: Array<string|undefined|Record<string, boolea
35
35
  return classes.join(' ');
36
36
  }
37
37
 
38
- export function getScreenDimensions(reportResult: LH.ReportResult) {
38
+ export function getScreenDimensions(reportResult: LH.Result) {
39
39
  const {width, height} = reportResult.configSettings.screenEmulation;
40
40
  return {width, height};
41
41
  }
42
42
 
43
- export function getScreenshot(reportResult: LH.ReportResult) {
43
+ export function getFullPageScreenshot(reportResult: LH.Result) {
44
44
  const fullPageScreenshotAudit = reportResult.audits['full-page-screenshot'];
45
45
  const fullPageScreenshot =
46
46
  fullPageScreenshotAudit &&
47
47
  fullPageScreenshotAudit.details &&
48
48
  fullPageScreenshotAudit.details.type === 'full-page-screenshot' &&
49
- fullPageScreenshotAudit.details.screenshot.data;
49
+ fullPageScreenshotAudit.details;
50
50
 
51
51
  return fullPageScreenshot || null;
52
52
  }
53
53
 
54
54
  export function getFilmstripFrames(
55
- reportResult: LH.ReportResult
55
+ reportResult: LH.Result
56
56
  ): Array<{data: string}> | undefined {
57
57
  const filmstripAudit = reportResult.audits['screenshot-thumbnails'];
58
58
  if (!filmstripAudit) return undefined;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+
7
+ import {FunctionComponent} from 'preact';
8
+ import {useLayoutEffect, useRef} from 'preact/hooks';
9
+
10
+ import {useReportRenderer} from '../wrappers/report-renderer';
11
+
12
+ export const Markdown: FunctionComponent<{text: string}> = ({text}) => {
13
+ const {dom} = useReportRenderer();
14
+ const ref = useRef<HTMLSpanElement>(null);
15
+
16
+ useLayoutEffect(() => {
17
+ if (ref.current) {
18
+ const md = dom.convertMarkdownCodeSnippets(text);
19
+ ref.current.appendChild(md);
20
+ }
21
+ return () => {
22
+ if (ref.current) ref.current.innerHTML = '';
23
+ };
24
+ }, [text]);
25
+
26
+ return <span ref={ref}/>;
27
+ };
@@ -7,6 +7,8 @@
7
7
  import {FunctionComponent} from 'preact';
8
8
  import {useLayoutEffect, useRef} from 'preact/hooks';
9
9
 
10
+ import {ElementScreenshotRenderer} from '../../../report/renderer/element-screenshot-renderer';
11
+ import {getFullPageScreenshot} from '../util';
10
12
  import {useReportRenderer} from './report-renderer';
11
13
 
12
14
  /**
@@ -41,6 +43,16 @@ export const Report: FunctionComponent<{currentLhr: LH.FlowResult.LhrRef}> =
41
43
  dom.clearComponentCache();
42
44
  reportRenderer.renderReport(currentLhr.value, ref.current);
43
45
  convertChildAnchors(ref.current, currentLhr.index);
46
+ const fullPageScreenshot = getFullPageScreenshot(currentLhr.value);
47
+ if (fullPageScreenshot) {
48
+ const container = dom.find('.lh-container', ref.current);
49
+ ElementScreenshotRenderer.installOverlayFeature({
50
+ dom,
51
+ reportEl: container,
52
+ overlayContainerEl: container,
53
+ fullPageScreenshot,
54
+ });
55
+ }
44
56
  const topbar = ref.current.querySelector('.lh-topbar');
45
57
  if (topbar) topbar.remove();
46
58
  }
@@ -9,7 +9,7 @@ import {act, render} from '@testing-library/preact';
9
9
 
10
10
  import {FlowStepThumbnail} from '../src/common';
11
11
 
12
- let lhr: LH.ReportResult;
12
+ let lhr: LH.Result;
13
13
 
14
14
  jest.useFakeTimers();
15
15
 
@@ -33,7 +33,7 @@ describe('FlowStepThumbnail', () => {
33
33
  });
34
34
 
35
35
  it('renders a thumbnail', () => {
36
- const root = render(<FlowStepThumbnail reportResult={lhr} width={200} height={200} />);
36
+ const root = render(<FlowStepThumbnail lhr={lhr} width={200} height={200} />);
37
37
 
38
38
  const thumbnail = root.getByAltText(/Screenshot/);
39
39
  expect(thumbnail.style.width).toEqual('200px');
@@ -41,14 +41,14 @@ describe('FlowStepThumbnail', () => {
41
41
  });
42
42
 
43
43
  it('renders nothing without dimensions', () => {
44
- const root = render(<FlowStepThumbnail reportResult={lhr} />);
44
+ const root = render(<FlowStepThumbnail lhr={lhr} />);
45
45
 
46
46
  expect(() => root.getByAltText(/Screenshot/)).toThrow();
47
47
  expect(global.console.warn).toHaveBeenCalled();
48
48
  });
49
49
 
50
50
  it('interpolates height', () => {
51
- const root = render(<FlowStepThumbnail reportResult={lhr} width={200} />);
51
+ const root = render(<FlowStepThumbnail lhr={lhr} width={200} />);
52
52
 
53
53
  const thumbnail = root.getByAltText(/Screenshot/);
54
54
  expect(thumbnail.style.width).toEqual('200px');
@@ -56,7 +56,7 @@ describe('FlowStepThumbnail', () => {
56
56
  });
57
57
 
58
58
  it('interpolates width', () => {
59
- const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
59
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
60
60
 
61
61
  const thumbnail = root.getByAltText(/Screenshot/);
62
62
  expect(thumbnail.style.width).toEqual('100px');
@@ -74,7 +74,7 @@ describe('FlowStepThumbnail', () => {
74
74
  ],
75
75
  },
76
76
  } as any;
77
- const root = render(<FlowStepThumbnail reportResult={lhr} height={150} />);
77
+ const root = render(<FlowStepThumbnail lhr={lhr} height={150} />);
78
78
 
79
79
  const thumbnail = root.getByAltText(/Animated/) as HTMLImageElement;
80
80
  expect(thumbnail.style.width).toEqual('100px');