From 59fe77ee7b1e2ad7988c419f97c5014db819dccd Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 13:49:10 -0500 Subject: [PATCH 01/50] merge viewer changes --- .../app/src/lighthouse-report-viewer.js | 75 ++++++++++++++++--- lighthouse-viewer/app/styles/viewer.css | 12 +++ lighthouse-viewer/test/drag-and-drop-test.js | 67 ++++++----------- 3 files changed, 99 insertions(+), 55 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 0570268aa566..91bbf2656109 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -23,6 +23,40 @@ function find(query, context) { return result; } +/** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ + +const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; + +/** + * @param {string} url + * @return {Promise} + */ +function callPSI(url) { + const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); + /** @type {Record} */ + const params = { + key: PSI_KEY, + url, + category: [ + 'performance', + 'accessibility', + 'seo', + 'best-practices', + 'pwa', + ], + strategy: 'mobile', + utm_source: 'Lighthouse Chrome Extension', + }; + Object.entries(params).forEach(([key, value]) => { + const values = Array.isArray(value) ? value : [value]; + for (const singleValue of values) { + psiUrl.searchParams.append(key, singleValue); + } + }); + + return fetch(psiUrl.href).then(res => res.json()); +} + /** * Class that manages viewing Lighthouse reports. */ @@ -42,9 +76,17 @@ class LighthouseReportViewer { */ this._reportIsFromGist = false; - this._addEventListeners(); - this._loadFromDeepLink(); - this._listenForMessages(); + this._reportIsFromPSI = false; + + const params = new URLSearchParams(location.search); + const url = params.get('url'); + if (url) { + this._loadFromPSI(url); + } else { + this._addEventListeners(); + this._loadFromDeepLink(); + this._listenForMessages(); + } } static get APP_URL() { @@ -159,9 +201,9 @@ class LighthouseReportViewer { renderer.renderReport(json, container); // Only give gist-saving callback (and clear gist from query string) if - // current report isn't from a gist. + // current report isn't from a gist or PSI. let saveCallback = null; - if (!this._reportIsFromGist) { + if (!this._reportIsFromGist && !this._reportIsFromPSI) { saveCallback = this._onSaveJson; history.pushState({}, '', LighthouseReportViewer.APP_URL); } @@ -202,7 +244,7 @@ class LighthouseReportViewer { } catch (e) { throw new Error('Could not parse JSON file.'); } - this._reportIsFromGist = false; + this._reportIsFromGist = this._reportIsFromPSI = false; this._replaceReportHtml(json); }).catch(err => logger.error(err.message)); } @@ -276,7 +318,6 @@ class LighthouseReportViewer { * @private */ _onPaste(e) { - if (!e.clipboardData) return; e.preventDefault(); // Try paste as gist URL. @@ -294,7 +335,7 @@ class LighthouseReportViewer { // Try paste as json content. try { const json = JSON.parse(e.clipboardData.getData('text')); - this._reportIsFromGist = false; + this._reportIsFromGist = this._reportIsFromPSI = false; this._replaceReportHtml(json); if (window.ga) { @@ -358,7 +399,7 @@ class LighthouseReportViewer { _listenForMessages() { window.addEventListener('message', e => { if (e.source === self.opener && e.data.lhresults) { - this._reportIsFromGist = false; + this._reportIsFromGist = this._reportIsFromPSI = false; this._replaceReportHtml(e.data.lhresults); if (self.opener && !self.opener.closed) { @@ -375,6 +416,22 @@ class LighthouseReportViewer { self.opener.postMessage({opened: true}, '*'); } } + + /** + * @param {string} url + */ + _loadFromPSI(url) { + const loadingOverlayEl = document.createElement('div'); + loadingOverlayEl.classList.add('lh-loading-overlay'); + loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; + find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); + document.body.appendChild(loadingOverlayEl); + callPSI(url).then(psiResponse => { + this._reportIsFromPSI = true; + loadingOverlayEl.remove(); + this._replaceReportHtml(psiResponse.lighthouseResult); + }); + } } // node export for testing. diff --git a/lighthouse-viewer/app/styles/viewer.css b/lighthouse-viewer/app/styles/viewer.css index a4925187f355..052e69c30cf7 100644 --- a/lighthouse-viewer/app/styles/viewer.css +++ b/lighthouse-viewer/app/styles/viewer.css @@ -124,3 +124,15 @@ .lh-tools__dropdown .lh-tools--gist { display: block !important; } + +.lh-loading-overlay { + position: absolute; + top: 50%; + transform: translateX(-50%); + left: 50%; + filter: none; +} + +.lh-loading { + filter: blur(2px); +} diff --git a/lighthouse-viewer/test/drag-and-drop-test.js b/lighthouse-viewer/test/drag-and-drop-test.js index 7b1d67c78110..641c14633af1 100644 --- a/lighthouse-viewer/test/drag-and-drop-test.js +++ b/lighthouse-viewer/test/drag-and-drop-test.js @@ -7,6 +7,8 @@ /* eslint-env jest */ +const assert = require('assert'); + const testHelpers = require('./test-helpers.js'); // Called before other src import so code that relies on `document` and @@ -15,6 +17,10 @@ testHelpers.setupJsDomGlobals(); const DragAndDrop = require('../app/src/drag-and-drop.js'); +function assertUIReset() { + assert.ok(!document.querySelector('.drop_zone').classList.contains('dropping')); +} + describe('DragAndDrop', () => { beforeEach(function() { // Reconstruct page on every test so event listeners are clean. @@ -23,62 +29,31 @@ describe('DragAndDrop', () => { afterEach(testHelpers.cleanupJsDomGlobals); - it('document responds to drop event with file', () => { - const mockCallback = jest.fn(); - new DragAndDrop(mockCallback); - - // create custom drop event with mock files in dataTransfer - const event = new window.CustomEvent('drop'); - event.dataTransfer = { - files: ['mock file'], + // TODO: test drop event on document. Callback is not getting called + // because jsdom doesn't support clipboard API: https://github.com/tmpvar/jsdom/issues/1568/. + it.skip('document responds to drag and drop events', done => { + const callback = _ => { + assert.ok(true, 'file change callback is called after drop event'); + done(); }; - document.dispatchEvent(event); - expect(mockCallback).toBeCalledWith('mock file'); - }); - it('document ignores drop event without file', () => { - const mockCallback = jest.fn(); - new DragAndDrop(mockCallback); + new DragAndDrop(callback); document.dispatchEvent(new window.CustomEvent('drop')); - expect(mockCallback).not.toBeCalled(); - }); - - it('document responds to dragover event with file', () => { - const mockCallback = jest.fn(); - new DragAndDrop(mockCallback); - - const event = new window.CustomEvent('dragover'); - event.dataTransfer = { - files: ['mock file'], - }; - document.dispatchEvent(event); - expect(event.dataTransfer.dropEffect).toEqual('copy'); - }); - - it('document ignores dragover event without file', () => { - const mockCallback = jest.fn(); - new DragAndDrop(mockCallback); - - const event = new window.CustomEvent('dragover'); - document.dispatchEvent(event); - expect(event.dataTransfer).toBeUndefined(); }); - it('document responds to mouseleave event when not dragging', () => { - new DragAndDrop(jest.fn); + it('document responds to drag and drop events', () => { + // eslint-disable-next-line no-unused-vars + const dragAndDrop = new DragAndDrop(); document.dispatchEvent(new window.CustomEvent('mouseleave')); - expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeFalsy(); - }); - - it('document responds to mouseleave and dragenter events', () => { - new DragAndDrop(jest.fn); + assertUIReset(); document.dispatchEvent(new window.CustomEvent('dragenter')); - expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeTruthy(); + assert.ok(document.querySelector('.drop_zone').classList.contains('dropping')); - document.dispatchEvent(new window.CustomEvent('mouseleave')); - expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeFalsy(); + // TODO: see note above about drop event testing. + // document.dispatchEvent(new window.CustomEvent('drop')); + // assertUIReset(); }); }); From 62cc1b10709b9439272e15c2e42fed74cf3085e7 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 13:49:19 -0500 Subject: [PATCH 02/50] categories query param --- .../app/src/lighthouse-report-viewer.js | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 91bbf2656109..d7655b32dcf6 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -26,24 +26,26 @@ function find(query, context) { /** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; +const PSI_DEFAULT_CATEGORIES = [ + 'performance', + 'accessibility', + 'seo', + 'best-practices', + 'pwa', +]; /** * @param {string} url + * @param {string[]} categories * @return {Promise} */ -function callPSI(url) { +function callPSI(url, categories) { const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); /** @type {Record} */ const params = { key: PSI_KEY, url, - category: [ - 'performance', - 'accessibility', - 'seo', - 'best-practices', - 'pwa', - ], + category: categories, strategy: 'mobile', utm_source: 'Lighthouse Chrome Extension', }; @@ -81,7 +83,9 @@ class LighthouseReportViewer { const params = new URLSearchParams(location.search); const url = params.get('url'); if (url) { - this._loadFromPSI(url); + const categoriesCsv = params.get('categories'); + const categories = categoriesCsv ? categoriesCsv.split(',') : PSI_DEFAULT_CATEGORIES; + this._loadFromPSI(url, categories); } else { this._addEventListeners(); this._loadFromDeepLink(); @@ -419,14 +423,15 @@ class LighthouseReportViewer { /** * @param {string} url + * @param {string[]} categories */ - _loadFromPSI(url) { + _loadFromPSI(url, categories) { const loadingOverlayEl = document.createElement('div'); loadingOverlayEl.classList.add('lh-loading-overlay'); loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); - callPSI(url).then(psiResponse => { + callPSI(url, categories).then(psiResponse => { this._reportIsFromPSI = true; loadingOverlayEl.remove(); this._replaceReportHtml(psiResponse.lighthouseResult); From 02a48006c47d151787d0c8e00d0a7a5d8b052acc Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 15:15:33 -0500 Subject: [PATCH 03/50] viewer: psi api --- lighthouse-viewer/test/viewer-test-pptr.js | 203 +++++++++++++++------ package.json | 2 +- yarn.lock | 93 +++++----- 3 files changed, 201 insertions(+), 97 deletions(-) diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index d7b0f42bdd2f..681f3ed4c161 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -8,6 +8,7 @@ /* eslint-env jest */ const path = require('path'); +const fs = require('fs'); const assert = require('assert'); const puppeteer = require('../../node_modules/puppeteer/index.js'); @@ -21,7 +22,7 @@ const lighthouseCategories = Object.keys(config.categories); const getAuditsOfCategory = category => config.categories[category].auditRefs; // TODO: should be combined in some way with clients/test/extension/extension-test.js -describe('Lighthouse Viewer', function() { +describe('Lighthouse Viewer', () => { // eslint-disable-next-line no-console console.log('\n✨ Be sure to have recently run this: yarn build-viewer'); @@ -29,6 +30,11 @@ describe('Lighthouse Viewer', function() { let viewerPage; const pageErrors = []; + const selectors = { + audits: '.lh-audit, .lh-metric', + titles: '.lh-audit__title, .lh-metric__title', + }; + function getAuditElementsIds({category, selector}) { return viewerPage.evaluate( ({category, selector}) => { @@ -59,16 +65,12 @@ describe('Lighthouse Viewer', function() { }); viewerPage = await browser.newPage(); viewerPage.on('pageerror', pageError => pageErrors.push(pageError)); - await viewerPage.goto(viewerUrl, {waitUntil: 'networkidle2', timeout: 30000}); - const fileInput = await viewerPage.$('#hidden-file-input'); - await fileInput.uploadFile(sampleLhr); - await viewerPage.waitForSelector('.lh-container', {timeout: 30000}); }); afterAll(async function() { // Log any page load errors encountered in case before() failed. // eslint-disable-next-line no-console - console.error(pageErrors); + if (pageErrors.length > 0) console.error(pageErrors); await Promise.all([ new Promise(resolve => server.close(resolve)), @@ -76,63 +78,160 @@ describe('Lighthouse Viewer', function() { ]); }); + describe('Upload', () => { + beforeAll(async function() { + await viewerPage.goto(viewerUrl, {waitUntil: 'networkidle2', timeout: 30000}); + const fileInput = await viewerPage.$('#hidden-file-input'); + await fileInput.uploadFile(sampleLhr); + await viewerPage.waitForSelector('.lh-container', {timeout: 30000}); + }); - const selectors = { - audits: '.lh-audit, .lh-metric', - titles: '.lh-audit__title, .lh-metric__title', - }; + it('should load with no errors', async () => { + assert.deepStrictEqual(pageErrors, []); + }); - it('should load with no errors', async () => { - assert.deepStrictEqual(pageErrors, []); - }); + it('should contain all categories', async () => { + const categories = await getCategoryElementsIds(); + assert.deepStrictEqual( + categories.sort(), + lighthouseCategories.sort(), + `all categories not found` + ); + }); - it('should contain all categories', async () => { - const categories = await getCategoryElementsIds(); - assert.deepStrictEqual( - categories.sort(), - lighthouseCategories.sort(), - `all categories not found` - ); - }); + it('should contain audits of all categories', async () => { + for (const category of lighthouseCategories) { + let expected = getAuditsOfCategory(category); + if (category === 'performance') { + expected = getAuditsOfCategory(category).filter(a => !!a.group); + } + expected = expected.map(audit => audit.id); + const elementIds = await getAuditElementsIds({category, selector: selectors.audits}); + + assert.deepStrictEqual( + elementIds.sort(), + expected.sort(), + `${category} does not have the identical audits` + ); + } + }); + + it('should contain a filmstrip', async () => { + const filmstrip = await viewerPage.$('.lh-filmstrip'); + + assert.ok(!!filmstrip, `filmstrip is not available`); + }); - it('should contain audits of all categories', async () => { - for (const category of lighthouseCategories) { - let expected = getAuditsOfCategory(category); - if (category === 'performance') { - expected = getAuditsOfCategory(category).filter(a => !!a.group); + it('should not have any unexpected audit errors', async () => { + function getErrors(elems, selectors) { + return elems.map(el => { + const audit = el.closest(selectors.audits); + const auditTitle = audit && audit.querySelector(selectors.titles); + return { + explanation: el.textContent, + title: auditTitle ? auditTitle.textContent : 'Audit title unvailable', + }; + }); } - expected = expected.map(audit => audit.id); - const elementIds = await getAuditElementsIds({category, selector: selectors.audits}); - assert.deepStrictEqual( - elementIds.sort(), - expected.sort(), - `${category} does not have the identical audits` - ); - } + const errorSelectors = '.lh-audit-explanation, .tooltip--error'; + const auditErrors = await viewerPage.$$eval(errorSelectors, getErrors, selectors); + const errors = auditErrors.filter(item => item.explanation.includes('Audit error:')); + assert.deepStrictEqual(errors, [], 'Audit errors found within the report'); + }); }); - it('should contain a filmstrip', async () => { - const filmstrip = await viewerPage.$('.lh-filmstrip'); + describe('PSI', () => { + let onApiRequestInterception; + let apiRequestInterceptionResolve; - assert.ok(!!filmstrip, `filmstrip is not available`); - }); + function onRequest(interceptedRequest) { + if (interceptedRequest.url().includes('https://www.googleapis.com')) { + apiRequestInterceptionResolve(interceptedRequest); + } else { + interceptedRequest.continue(); + } + } + + beforeAll(async () => { + await viewerPage.setRequestInterception(true); + viewerPage.on('request', onRequest); + }); + + afterAll(async () => { + viewerPage.off('request', onRequest); + await viewerPage.setRequestInterception(false); + }); + + beforeEach(() => { + onApiRequestInterception = new Promise(resolve => apiRequestInterceptionResolve = resolve); + }); - it('should not have any unexpected audit errors', async () => { - function getErrors(elems, selectors) { - return elems.map(el => { - const audit = el.closest(selectors.audits); - const auditTitle = audit && audit.querySelector(selectors.titles); - return { - explanation: el.textContent, - title: auditTitle ? auditTitle.textContent : 'Audit title unvailable', - }; + it('should call out to PSI with all categories by default', async () => { + const url = `${viewerUrl}?url=https://www.example.com`; + await viewerPage.goto(url); + + // Intercept and respond with sample lhr. + const interceptedRequest = await onApiRequestInterception; + const interceptedUrl = new URL(interceptedRequest.url()); + expect(interceptedUrl.origin + interceptedUrl.pathname).toEqual('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); + expect({ + url: interceptedUrl.searchParams.get('url'), + category: interceptedUrl.searchParams.getAll('category'), + }).toEqual({ + url: 'https://www.example.com', + category: [ + 'performance', + 'accessibility', + 'seo', + 'best-practices', + 'pwa', + ], }); - } + const psiResponse = { + lighthouseResult: JSON.parse(fs.readFileSync(sampleLhr, 'utf-8')), + }; + await interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(psiResponse), + }); + + // Wait for report to render. + await viewerPage.waitForSelector('.lh-columns'); + + // No errors. + assert.deepStrictEqual(pageErrors, []); + + // All categories. + const categories = await getCategoryElementsIds(); + assert.deepStrictEqual( + categories.sort(), + lighthouseCategories.sort(), + `all categories not found` + ); + }); - const errorSelectors = '.lh-audit-explanation, .tooltip--error'; - const auditErrors = await viewerPage.$$eval(errorSelectors, getErrors, selectors); - const errors = auditErrors.filter(item => item.explanation.includes('Audit error:')); - assert.deepStrictEqual(errors, [], 'Audit errors found within the report'); + it('should call out to PSI with specified categoeries', async () => { + const url = `${viewerUrl}?url=https://www.example.com&categories=seo,pwa`; + await viewerPage.goto(url); + + const interceptedRequest = await onApiRequestInterception; + const interceptedUrl = new URL(interceptedRequest.url()); + expect(interceptedUrl.origin + interceptedUrl.pathname).toEqual('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); + expect({ + url: interceptedUrl.searchParams.get('url'), + category: interceptedUrl.searchParams.getAll('category'), + }).toEqual({ + url: 'https://www.example.com', + category: [ + 'seo', + 'pwa', + ], + }); + + // Just wanted to check the params are correct, let's abort. Previous test does more of an end-to-end test. + interceptedRequest.abort(); + }); }); }); diff --git a/package.json b/package.json index 498bae8908c6..81b862e05130 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "package-json-versionify": "^1.0.4", "prettier": "^1.14.3", "pretty-json-stringify": "^0.0.2", - "puppeteer": "^1.10.0", + "puppeteer": "^1.19.0", "typescript": "3.5.3", "uglify-es": "3.0.15", "url-search-params": "0.6.1", diff --git a/yarn.lock b/yarn.lock index 728393c36db4..ccafaaedd5d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -824,10 +824,10 @@ add-stream@^1.0.0: resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= -agent-base@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" - integrity sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg== +agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== dependencies: es6-promisify "^5.0.0" @@ -1080,9 +1080,9 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== async@^2.0.0, async@^2.6.1: version "2.6.1" @@ -1592,9 +1592,9 @@ buffer-fill@^1.0.0: integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" - integrity sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ== + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== buffer-xor@^1.0.3: version "1.0.3" @@ -2441,13 +2441,20 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: dependencies: ms "2.0.0" -debug@=3.1.0, debug@^3.1.0: +debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -2787,9 +2794,9 @@ es6-map@^0.1.5: event-emitter "~0.3.5" es6-promise@^4.0.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" - integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ== + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== es6-promisify@^5.0.0: version "5.0.0" @@ -3770,11 +3777,11 @@ https-browserify@^1.0.0: integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= https-proxy-agent@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" - integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz#271ea8e90f836ac9f119daccd39c19ff7dfb0793" + integrity sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg== dependencies: - agent-base "^4.1.0" + agent-base "^4.3.0" debug "^3.1.0" humanize-url@^1.0.0: @@ -3863,7 +3870,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -3873,6 +3880,11 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" @@ -5368,9 +5380,9 @@ mime-types@~2.1.19: mime-db "~1.37.0" mime@^2.0.3: - version "2.3.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" - integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" + integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== mimic-fn@^1.0.0: version "1.1.0" @@ -6163,19 +6175,19 @@ private@^0.1.7: integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" - integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8= +progress@^2.0.0, progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== prompts@^2.0.1: version "2.0.3" @@ -6252,18 +6264,18 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== puppeteer@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.10.0.tgz#e3005f1251c2feae0e10c0f7a35afbcd56589ceb" - integrity sha512-3i28X/ucX8t3eL4TZA60FLMOQNKqudFSOGDHr0cT7T4dE027CrcS885aAqjdxNybhMPliM5yImNsKJ6SQrPzhw== + version "1.19.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.19.0.tgz#e3b7b448c2c97933517078d7a2c53687361bebea" + integrity sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw== dependencies: - debug "^3.1.0" + debug "^4.1.0" extract-zip "^1.6.6" https-proxy-agent "^2.2.1" mime "^2.0.3" - progress "^2.0.0" + progress "^2.0.1" proxy-from-env "^1.0.0" rimraf "^2.6.1" - ws "^5.1.1" + ws "^6.1.0" q@^1.4.1: version "1.5.1" @@ -6659,12 +6671,12 @@ rx@^4.1.0: resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= -safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== -safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -7928,13 +7940,6 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" -ws@^5.1.1: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" - integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== - dependencies: - async-limiter "~1.0.0" - ws@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.0.tgz#119a9dbf92c54e190ec18d10e871d55c95cf9373" From 47227d8d120bb9cc94c2d88ce5b7a7e6ce9190e1 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 15:19:43 -0500 Subject: [PATCH 04/50] undo accidental changes to drop test --- lighthouse-viewer/test/drag-and-drop-test.js | 67 ++++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/lighthouse-viewer/test/drag-and-drop-test.js b/lighthouse-viewer/test/drag-and-drop-test.js index 641c14633af1..7b1d67c78110 100644 --- a/lighthouse-viewer/test/drag-and-drop-test.js +++ b/lighthouse-viewer/test/drag-and-drop-test.js @@ -7,8 +7,6 @@ /* eslint-env jest */ -const assert = require('assert'); - const testHelpers = require('./test-helpers.js'); // Called before other src import so code that relies on `document` and @@ -17,10 +15,6 @@ testHelpers.setupJsDomGlobals(); const DragAndDrop = require('../app/src/drag-and-drop.js'); -function assertUIReset() { - assert.ok(!document.querySelector('.drop_zone').classList.contains('dropping')); -} - describe('DragAndDrop', () => { beforeEach(function() { // Reconstruct page on every test so event listeners are clean. @@ -29,31 +23,62 @@ describe('DragAndDrop', () => { afterEach(testHelpers.cleanupJsDomGlobals); - // TODO: test drop event on document. Callback is not getting called - // because jsdom doesn't support clipboard API: https://github.com/tmpvar/jsdom/issues/1568/. - it.skip('document responds to drag and drop events', done => { - const callback = _ => { - assert.ok(true, 'file change callback is called after drop event'); - done(); + it('document responds to drop event with file', () => { + const mockCallback = jest.fn(); + new DragAndDrop(mockCallback); + + // create custom drop event with mock files in dataTransfer + const event = new window.CustomEvent('drop'); + event.dataTransfer = { + files: ['mock file'], }; + document.dispatchEvent(event); + expect(mockCallback).toBeCalledWith('mock file'); + }); - new DragAndDrop(callback); + it('document ignores drop event without file', () => { + const mockCallback = jest.fn(); + new DragAndDrop(mockCallback); document.dispatchEvent(new window.CustomEvent('drop')); + expect(mockCallback).not.toBeCalled(); + }); + + it('document responds to dragover event with file', () => { + const mockCallback = jest.fn(); + new DragAndDrop(mockCallback); + + const event = new window.CustomEvent('dragover'); + event.dataTransfer = { + files: ['mock file'], + }; + document.dispatchEvent(event); + expect(event.dataTransfer.dropEffect).toEqual('copy'); + }); + + it('document ignores dragover event without file', () => { + const mockCallback = jest.fn(); + new DragAndDrop(mockCallback); + + const event = new window.CustomEvent('dragover'); + document.dispatchEvent(event); + expect(event.dataTransfer).toBeUndefined(); }); - it('document responds to drag and drop events', () => { - // eslint-disable-next-line no-unused-vars - const dragAndDrop = new DragAndDrop(); + it('document responds to mouseleave event when not dragging', () => { + new DragAndDrop(jest.fn); document.dispatchEvent(new window.CustomEvent('mouseleave')); - assertUIReset(); + expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeFalsy(); + }); + + it('document responds to mouseleave and dragenter events', () => { + new DragAndDrop(jest.fn); document.dispatchEvent(new window.CustomEvent('dragenter')); - assert.ok(document.querySelector('.drop_zone').classList.contains('dropping')); + expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeTruthy(); - // TODO: see note above about drop event testing. - // document.dispatchEvent(new window.CustomEvent('drop')); - // assertUIReset(); + document.dispatchEvent(new window.CustomEvent('mouseleave')); + expect(document.querySelector('.drop_zone').classList.contains('dropping')).toBeFalsy(); }); }); From 2fa37c9cd0a91926bd1c50432dc734f9c597e02c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 15:28:09 -0500 Subject: [PATCH 05/50] lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index ccafaaedd5d7..bf8b71eaf3c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6263,7 +6263,7 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@^1.10.0: +puppeteer@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.19.0.tgz#e3b7b448c2c97933517078d7a2c53687361bebea" integrity sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw== From 4c33bb6e1f183380d4ca08529c813e99dfc7a973 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 15:58:34 -0500 Subject: [PATCH 06/50] redo lock --- yarn.lock | 73 +++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/yarn.lock b/yarn.lock index bf8b71eaf3c3..6d10545cc3cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -824,10 +824,10 @@ add-stream@^1.0.0: resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== +agent-base@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" + integrity sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg== dependencies: es6-promisify "^5.0.0" @@ -1080,9 +1080,9 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== async@^2.0.0, async@^2.6.1: version "2.6.1" @@ -1592,9 +1592,9 @@ buffer-fill@^1.0.0: integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + integrity sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ== buffer-xor@^1.0.3: version "1.0.3" @@ -2441,20 +2441,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: dependencies: ms "2.0.0" -debug@=3.1.0: +debug@=3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -2794,9 +2787,9 @@ es6-map@^0.1.5: event-emitter "~0.3.5" es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" + integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ== es6-promisify@^5.0.0: version "5.0.0" @@ -3777,11 +3770,11 @@ https-browserify@^1.0.0: integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= https-proxy-agent@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz#271ea8e90f836ac9f119daccd39c19ff7dfb0793" - integrity sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg== + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== dependencies: - agent-base "^4.3.0" + agent-base "^4.1.0" debug "^3.1.0" humanize-url@^1.0.0: @@ -3870,7 +3863,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.1: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -3880,11 +3873,6 @@ inherits@2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= -inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" @@ -5380,9 +5368,9 @@ mime-types@~2.1.19: mime-db "~1.37.0" mime@^2.0.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" + integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg== mimic-fn@^1.0.0: version "1.1.0" @@ -6175,16 +6163,21 @@ private@^0.1.7: integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.0, progress@^2.0.1: +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + integrity sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8= + +progress@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -6671,12 +6664,12 @@ rx@^4.1.0: resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I= -safe-buffer@^5.0.1: +safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== -safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== From 2e8fbeedf8f8b7bd7c08f507bedcfd82fc814069 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:13:07 -0500 Subject: [PATCH 07/50] psi-api.js, ?category --- .../app/src/lighthouse-report-viewer.js | 48 +++-------------- lighthouse-viewer/app/src/psi-api.js | 52 +++++++++++++++++++ lighthouse-viewer/test/viewer-test-pptr.js | 2 +- lighthouse-viewer/types/viewer.d.ts | 2 + 4 files changed, 61 insertions(+), 43 deletions(-) create mode 100644 lighthouse-viewer/app/src/psi-api.js diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index d7655b32dcf6..ae56c339e7a8 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -5,7 +5,7 @@ */ 'use strict'; -/* global DOM, ViewerUIFeatures, ReportRenderer, DragAndDrop, GithubApi, logger, idbKeyval */ +/* global DOM, ViewerUIFeatures, ReportRenderer, DragAndDrop, GithubApi, PSIApi, logger, idbKeyval */ /** * Guaranteed context.querySelector. Always returns an element or throws if @@ -23,42 +23,6 @@ function find(query, context) { return result; } -/** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ - -const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; -const PSI_DEFAULT_CATEGORIES = [ - 'performance', - 'accessibility', - 'seo', - 'best-practices', - 'pwa', -]; - -/** - * @param {string} url - * @param {string[]} categories - * @return {Promise} - */ -function callPSI(url, categories) { - const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); - /** @type {Record} */ - const params = { - key: PSI_KEY, - url, - category: categories, - strategy: 'mobile', - utm_source: 'Lighthouse Chrome Extension', - }; - Object.entries(params).forEach(([key, value]) => { - const values = Array.isArray(value) ? value : [value]; - for (const singleValue of values) { - psiUrl.searchParams.append(key, singleValue); - } - }); - - return fetch(psiUrl.href).then(res => res.json()); -} - /** * Class that manages viewing Lighthouse reports. */ @@ -71,6 +35,7 @@ class LighthouseReportViewer { this._dragAndDropper = new DragAndDrop(this._onFileLoad); this._github = new GithubApi(); + this._psi = new PSIApi(); /** * Used for tracking whether to offer to upload as a gist. @@ -83,8 +48,7 @@ class LighthouseReportViewer { const params = new URLSearchParams(location.search); const url = params.get('url'); if (url) { - const categoriesCsv = params.get('categories'); - const categories = categoriesCsv ? categoriesCsv.split(',') : PSI_DEFAULT_CATEGORIES; + const categories = params.has('category') ? params.getAll('category') : null; this._loadFromPSI(url, categories); } else { this._addEventListeners(); @@ -423,7 +387,7 @@ class LighthouseReportViewer { /** * @param {string} url - * @param {string[]} categories + * @param {?string[]} categories */ _loadFromPSI(url, categories) { const loadingOverlayEl = document.createElement('div'); @@ -431,10 +395,10 @@ class LighthouseReportViewer { loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); - callPSI(url, categories).then(psiResponse => { + this._psi.callPSI(url, categories).then(response => { this._reportIsFromPSI = true; loadingOverlayEl.remove(); - this._replaceReportHtml(psiResponse.lighthouseResult); + this._replaceReportHtml(response.lighthouseResult); }); } } diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js new file mode 100644 index 000000000000..345c90c90c68 --- /dev/null +++ b/lighthouse-viewer/app/src/psi-api.js @@ -0,0 +1,52 @@ +/** + * @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'; + +/** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ + +const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; +const PSI_DEFAULT_CATEGORIES = [ + 'performance', + 'accessibility', + 'seo', + 'best-practices', + 'pwa', +]; + +/** + * Wrapper around the PSI API for fetching LHR. + */ +class PSIApi { + /** + * @param {string} url + * @param {?string[]} categories + * @return {Promise} + */ + callPSI(url, categories) { + const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); + /** @type {Record} */ + const params = { + key: PSI_KEY, + url, + category: categories || PSI_DEFAULT_CATEGORIES, + strategy: 'mobile', + utm_source: 'Lighthouse Chrome Extension', + }; + Object.entries(params).forEach(([key, value]) => { + const values = Array.isArray(value) ? value : [value]; + for (const singleValue of values) { + psiUrl.searchParams.append(key, singleValue); + } + }); + + return fetch(psiUrl.href).then(res => res.json()); + } +} + +// node export for testing. +if (typeof module !== 'undefined' && module.exports) { + module.exports = PSIApi; +} diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index 681f3ed4c161..efa96a7ad462 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -213,7 +213,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with specified categoeries', async () => { - const url = `${viewerUrl}?url=https://www.example.com&categories=seo,pwa`; + const url = `${viewerUrl}?url=https://www.example.com&category=seo&category=pwa`; await viewerPage.goto(url); const interceptedRequest = await onApiRequestInterception; diff --git a/lighthouse-viewer/types/viewer.d.ts b/lighthouse-viewer/types/viewer.d.ts index 37696767eb9c..20469f3c9255 100644 --- a/lighthouse-viewer/types/viewer.d.ts +++ b/lighthouse-viewer/types/viewer.d.ts @@ -9,6 +9,7 @@ import _Logger = require('../../lighthouse-core/report/html/renderer/logger.js') import _LighthouseReportViewer = require('../app/src/lighthouse-report-viewer.js'); import _DragAndDrop = require('../app/src/drag-and-drop.js'); import _GithubApi = require('../app/src/github-api.js'); +import _PSIApi = require('../app/src/psi-api.js'); import _FirebaseAuth = require('../app/src/firebase-auth.js'); import _ViewerUIFeatures = require('../app/src/viewer-ui-features.js'); import 'google.analytics'; @@ -23,6 +24,7 @@ declare global { var LighthouseReportViewer: typeof _LighthouseReportViewer; var DragAndDrop: typeof _DragAndDrop; var GithubApi: typeof _GithubApi; + var PSIApi: typeof _PSIApi; var FirebaseAuth: typeof _FirebaseAuth; var ViewerUIFeatures: typeof _ViewerUIFeatures; From 621ede6612ecf2aceac0f28a5e3e6bd04464d674 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:15:39 -0500 Subject: [PATCH 08/50] allow gist --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index ae56c339e7a8..c7fd40c3b9d0 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -169,10 +169,14 @@ class LighthouseReportViewer { renderer.renderReport(json, container); // Only give gist-saving callback (and clear gist from query string) if - // current report isn't from a gist or PSI. + // current report isn't from a gist. let saveCallback = null; if (!this._reportIsFromGist && !this._reportIsFromPSI) { saveCallback = this._onSaveJson; + } + + // Only modify history if not from a gist. + if (!this._reportIsFromGist) { history.pushState({}, '', LighthouseReportViewer.APP_URL); } From c0513b3566136aa50e76342476d7a12c10b59ec4 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:20:08 -0500 Subject: [PATCH 09/50] fix logic --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index c7fd40c3b9d0..9d7bcadbf0a0 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -171,12 +171,12 @@ class LighthouseReportViewer { // Only give gist-saving callback (and clear gist from query string) if // current report isn't from a gist. let saveCallback = null; - if (!this._reportIsFromGist && !this._reportIsFromPSI) { + if (!this._reportIsFromGist) { saveCallback = this._onSaveJson; } - // Only modify history if not from a gist. - if (!this._reportIsFromGist) { + // Only modify history if not from a gist and not using PSI. + if (!this._reportIsFromPSI && !this._reportIsFromGist) { history.pushState({}, '', LighthouseReportViewer.APP_URL); } From 4e911d09f37ba6137f8149a21fa3e94cbe7a63c1 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:24:56 -0500 Subject: [PATCH 10/50] make tests less of a joke --- lighthouse-viewer/test/viewer-test-pptr.js | 37 ++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index efa96a7ad462..43c238d09b39 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -175,19 +175,15 @@ describe('Lighthouse Viewer', () => { const interceptedRequest = await onApiRequestInterception; const interceptedUrl = new URL(interceptedRequest.url()); expect(interceptedUrl.origin + interceptedUrl.pathname).toEqual('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); - expect({ - url: interceptedUrl.searchParams.get('url'), - category: interceptedUrl.searchParams.getAll('category'), - }).toEqual({ - url: 'https://www.example.com', - category: [ - 'performance', - 'accessibility', - 'seo', - 'best-practices', - 'pwa', - ], - }); + expect(interceptedUrl.searchParams.get('url')).toEqual('https://www.example.com'); + expect(interceptedUrl.searchParams.getAll('category')).toEqual([ + 'performance', + 'accessibility', + 'seo', + 'best-practices', + 'pwa', + ]); + const psiResponse = { lighthouseResult: JSON.parse(fs.readFileSync(sampleLhr, 'utf-8')), }; @@ -219,16 +215,11 @@ describe('Lighthouse Viewer', () => { const interceptedRequest = await onApiRequestInterception; const interceptedUrl = new URL(interceptedRequest.url()); expect(interceptedUrl.origin + interceptedUrl.pathname).toEqual('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); - expect({ - url: interceptedUrl.searchParams.get('url'), - category: interceptedUrl.searchParams.getAll('category'), - }).toEqual({ - url: 'https://www.example.com', - category: [ - 'seo', - 'pwa', - ], - }); + expect(interceptedUrl.searchParams.get('url')).toEqual('https://www.example.com'); + expect(interceptedUrl.searchParams.getAll('category')).toEqual([ + 'seo', + 'pwa', + ]); // Just wanted to check the params are correct, let's abort. Previous test does more of an end-to-end test. interceptedRequest.abort(); From 70a3b233551d278cf04b074f308c51b100ec01fc Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:28:19 -0500 Subject: [PATCH 11/50] array param setting for query --- lighthouse-viewer/app/src/psi-api.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 345c90c90c68..8acfdda4d983 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -27,20 +27,18 @@ class PSIApi { */ callPSI(url, categories) { const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); - /** @type {Record} */ const params = { key: PSI_KEY, url, - category: categories || PSI_DEFAULT_CATEGORIES, strategy: 'mobile', utm_source: 'Lighthouse Chrome Extension', }; Object.entries(params).forEach(([key, value]) => { - const values = Array.isArray(value) ? value : [value]; - for (const singleValue of values) { - psiUrl.searchParams.append(key, singleValue); - } + psiUrl.searchParams.append(key, value); }); + for (const category of (categories || PSI_DEFAULT_CATEGORIES)) { + psiUrl.searchParams.append('category', category); + } return fetch(psiUrl.href).then(res => res.json()); } From 550a0ea21f1a8908f7f185439ff3b13a75ca558f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:47:39 -0500 Subject: [PATCH 12/50] use all psi api --- .../app/src/lighthouse-report-viewer.js | 16 ++++++++----- lighthouse-viewer/app/src/psi-api.js | 24 +++++++++---------- lighthouse-viewer/types/viewer.d.ts | 8 +++++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 9d7bcadbf0a0..5246c8a1b10c 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -48,8 +48,13 @@ class LighthouseReportViewer { const params = new URLSearchParams(location.search); const url = params.get('url'); if (url) { - const categories = params.has('category') ? params.getAll('category') : null; - this._loadFromPSI(url, categories); + this._loadFromPSI({ + url, + category: params.has('category') ? params.getAll('category') : undefined, + strategy: params.get('strategy') || undefined, + locale: params.get('locale') || undefined, + utm_source: params.get('utm_source') || undefined, + }); } else { this._addEventListeners(); this._loadFromDeepLink(); @@ -390,16 +395,15 @@ class LighthouseReportViewer { } /** - * @param {string} url - * @param {?string[]} categories + * @param {PSIParams} params */ - _loadFromPSI(url, categories) { + _loadFromPSI(params) { const loadingOverlayEl = document.createElement('div'); loadingOverlayEl.classList.add('lh-loading-overlay'); loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); - this._psi.callPSI(url, categories).then(response => { + this._psi.callPSI(params).then(response => { this._reportIsFromPSI = true; loadingOverlayEl.remove(); this._replaceReportHtml(response.lighthouseResult); diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 8acfdda4d983..848f8e2727dd 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -7,6 +7,7 @@ /** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ +const PSI_URL = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'; const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; const PSI_DEFAULT_CATEGORIES = [ 'performance', @@ -21,26 +22,25 @@ const PSI_DEFAULT_CATEGORIES = [ */ class PSIApi { /** - * @param {string} url - * @param {?string[]} categories + * @param {PSIParams} params * @return {Promise} */ - callPSI(url, categories) { - const psiUrl = new URL('https://www.googleapis.com/pagespeedonline/v5/runPagespeed'); - const params = { + callPSI(params) { + params = Object.assign({ key: PSI_KEY, - url, strategy: 'mobile', - utm_source: 'Lighthouse Chrome Extension', - }; + }, params); + + const apiUrl = new URL(PSI_URL); Object.entries(params).forEach(([key, value]) => { - psiUrl.searchParams.append(key, value); + if (key === 'category') return; + if (value) apiUrl.searchParams.append(key, value); }); - for (const category of (categories || PSI_DEFAULT_CATEGORIES)) { - psiUrl.searchParams.append('category', category); + for (const singleCategory of (params.category || PSI_DEFAULT_CATEGORIES)) { + apiUrl.searchParams.append('category', singleCategory); } - return fetch(psiUrl.href).then(res => res.json()); + return fetch(apiUrl.href).then(res => res.json()); } } diff --git a/lighthouse-viewer/types/viewer.d.ts b/lighthouse-viewer/types/viewer.d.ts index 20469f3c9255..d55ea3cc88b5 100644 --- a/lighthouse-viewer/types/viewer.d.ts +++ b/lighthouse-viewer/types/viewer.d.ts @@ -39,6 +39,14 @@ declare global { // Inserted by viewer gulpfile build. LH_CURRENT_VERSION: string; } + + interface PSIParams { + url: string; + category?: string[]; + locale?: string; + strategy?: string; + utm_source?: string; + } } // empty export to keep file a module From 01af9cb2a71107813a83679b0f93af550d6eb73f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:48:52 -0500 Subject: [PATCH 13/50] rename --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 2 +- lighthouse-viewer/app/src/psi-api.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 5246c8a1b10c..88c47016c253 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -403,7 +403,7 @@ class LighthouseReportViewer { loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); - this._psi.callPSI(params).then(response => { + this._psi.fetchPSI(params).then(response => { this._reportIsFromPSI = true; loadingOverlayEl.remove(); this._replaceReportHtml(response.lighthouseResult); diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 848f8e2727dd..26ca776a987a 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -25,7 +25,7 @@ class PSIApi { * @param {PSIParams} params * @return {Promise} */ - callPSI(params) { + fetchPSI(params) { params = Object.assign({ key: PSI_KEY, strategy: 'mobile', From 04ed696be67fb1aa15742e311a3e04ccca4a2b98 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 9 Aug 2019 16:51:01 -0500 Subject: [PATCH 14/50] readme --- lighthouse-viewer/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lighthouse-viewer/README.md b/lighthouse-viewer/README.md index 21f335c4d694..47dafb570c9e 100644 --- a/lighthouse-viewer/README.md +++ b/lighthouse-viewer/README.md @@ -20,3 +20,11 @@ yarn deploy-viewer ``` For more information on deployment, see `releasing.md`. + +## PSI + +``` +http://localhost:8000/?url=https://www.example.com&category=pwa&category=seo +``` + +Other options: `locale`, `strategy`, `utm_source` From a680af4c8022517036f835bba6ad35f1bc45afbb Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Mon, 12 Aug 2019 13:44:18 -0700 Subject: [PATCH 15/50] psi errors --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 11 +++++++++++ lighthouse-viewer/app/src/psi-api.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 88c47016c253..554a8ab72b11 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -404,6 +404,17 @@ class LighthouseReportViewer { find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); this._psi.fetchPSI(params).then(response => { + if (!response.lighthouseResult) { + if (response.error) { + // eslint-disable-next-line no-console + console.error(response.error); + loadingOverlayEl.innerText = response.error.message; + } else { + loadingOverlayEl.innerText = 'PSI did not return a Lighthouse Result'; + } + return; + } + this._reportIsFromPSI = true; loadingOverlayEl.remove(); this._replaceReportHtml(response.lighthouseResult); diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 26ca776a987a..9c120600a1f5 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -5,7 +5,7 @@ */ 'use strict'; -/** @typedef {{lighthouseResult: LH.Result}} PSIResponse */ +/** @typedef {{lighthouseResult?: LH.Result, error?: {message: string}}} PSIResponse */ const PSI_URL = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'; const PSI_KEY = 'AIzaSyAjcDRNN9CX9dCazhqI4lGR7yyQbkd_oYE'; From 3ec612735aeadcbed4f79ef182334b3aa313c38e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:12:42 -0700 Subject: [PATCH 16/50] readme --- lighthouse-viewer/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lighthouse-viewer/README.md b/lighthouse-viewer/README.md index 47dafb570c9e..8f1cd2616d5c 100644 --- a/lighthouse-viewer/README.md +++ b/lighthouse-viewer/README.md @@ -23,8 +23,15 @@ For more information on deployment, see `releasing.md`. ## PSI +Example: ``` http://localhost:8000/?url=https://www.example.com&category=pwa&category=seo ``` -Other options: `locale`, `strategy`, `utm_source` +Options: + +`url` - URL to audit +`category` - Category to enable. One per category. +`strategy` - mobile, desktop +`locale` - locale to render report with +`utm_source` - id that identifies the tool using the viewer From e3d212fe0ae764ec5f46a9c2deb9c9a1106eac0a Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:20:35 -0700 Subject: [PATCH 17/50] load from deep link --- .../app/src/lighthouse-report-viewer.js | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 554a8ab72b11..3965be39a3dc 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -45,21 +45,7 @@ class LighthouseReportViewer { this._reportIsFromPSI = false; - const params = new URLSearchParams(location.search); - const url = params.get('url'); - if (url) { - this._loadFromPSI({ - url, - category: params.has('category') ? params.getAll('category') : undefined, - strategy: params.get('strategy') || undefined, - locale: params.get('locale') || undefined, - utm_source: params.get('utm_source') || undefined, - }); - } else { - this._addEventListeners(); - this._loadFromDeepLink(); - this._listenForMessages(); - } + this._loadFromDeepLink(); } static get APP_URL() { @@ -103,20 +89,31 @@ class LighthouseReportViewer { /** * Attempts to pull gist id from URL and render report from it. - * @return {Promise} * @private */ _loadFromDeepLink() { const params = new URLSearchParams(location.search); const gistId = params.get('gist'); - if (!gistId) { - return Promise.resolve(); + const url = params.get('url'); + + if (url) { + return this._loadFromPSI({ + url, + category: params.has('category') ? params.getAll('category') : undefined, + strategy: params.get('strategy') || undefined, + locale: params.get('locale') || undefined, + utm_source: params.get('utm_source') || undefined, + }); } - return this._github.getGistFileContentAsJson(gistId).then(reportJson => { - this._reportIsFromGist = true; - this._replaceReportHtml(reportJson); - }).catch(err => logger.error(err.message)); + if (gistId) { + return this._github.getGistFileContentAsJson(gistId).then(reportJson => { + this._reportIsFromGist = true; + this._replaceReportHtml(reportJson); + }).catch(err => logger.error(err.message)); + } + + return Promise.resolve(); } /** @@ -403,7 +400,7 @@ class LighthouseReportViewer { loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); document.body.appendChild(loadingOverlayEl); - this._psi.fetchPSI(params).then(response => { + return this._psi.fetchPSI(params).then(response => { if (!response.lighthouseResult) { if (response.error) { // eslint-disable-next-line no-console From 6934f194f835ca700b42fb6fd5a4692361387891 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:22:08 -0700 Subject: [PATCH 18/50] comment --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 3965be39a3dc..3d041f068716 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -170,14 +170,13 @@ class LighthouseReportViewer { try { renderer.renderReport(json, container); - // Only give gist-saving callback (and clear gist from query string) if - // current report isn't from a gist. + // Only give gist-saving callback if current report isn't from a gist. let saveCallback = null; if (!this._reportIsFromGist) { saveCallback = this._onSaveJson; } - // Only modify history if not from a gist and not using PSI. + // Only clear query string if current report isn't from a gist or PSI. if (!this._reportIsFromPSI && !this._reportIsFromGist) { history.pushState({}, '', LighthouseReportViewer.APP_URL); } From 552b03feb1f50bdb58cc7ffa69c496dd62cb710c Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:31:50 -0700 Subject: [PATCH 19/50] test url doesnt change --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 2 ++ lighthouse-viewer/test/viewer-test-pptr.js | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 3d041f068716..34e222e06845 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -45,7 +45,9 @@ class LighthouseReportViewer { this._reportIsFromPSI = false; + this._addEventListeners(); this._loadFromDeepLink(); + this._listenForMessages(); } static get APP_URL() { diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index 43c238d09b39..038a1498593c 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -206,6 +206,9 @@ describe('Lighthouse Viewer', () => { lighthouseCategories.sort(), `all categories not found` ); + + // Should not clear the query string. + expect(await viewerPage.url()).toEqual(url); }); it('should call out to PSI with specified categoeries', async () => { From 4fef7874703f3e0fcbbca0bf16de851bc1600ca6 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:32:56 -0700 Subject: [PATCH 20/50] put that code back --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 34e222e06845..111182f9af6b 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -293,6 +293,7 @@ class LighthouseReportViewer { * @private */ _onPaste(e) { + if (!e.clipboardData) return; e.preventDefault(); // Try paste as gist URL. From 6ca5ad019336118bba68aadf29dbcddc82a89bd1 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 15:43:36 -0700 Subject: [PATCH 21/50] cat test meoooow --- .../app/src/lighthouse-report-viewer.js | 1 + lighthouse-viewer/app/src/psi-api.js | 1 + lighthouse-viewer/test/psi-api-test.js | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 lighthouse-viewer/test/psi-api-test.js diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 111182f9af6b..370782b79533 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -407,6 +407,7 @@ class LighthouseReportViewer { if (response.error) { // eslint-disable-next-line no-console console.error(response.error); + logger.error(response.error.message); loadingOverlayEl.innerText = response.error.message; } else { loadingOverlayEl.innerText = 'PSI did not return a Lighthouse Result'; diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 9c120600a1f5..bdb34cf55af4 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -47,4 +47,5 @@ class PSIApi { // node export for testing. if (typeof module !== 'undefined' && module.exports) { module.exports = PSIApi; + module.exports.PSI_DEFAULT_CATEGORIES = PSI_DEFAULT_CATEGORIES; } diff --git a/lighthouse-viewer/test/psi-api-test.js b/lighthouse-viewer/test/psi-api-test.js new file mode 100644 index 000000000000..fa94fb4d1a99 --- /dev/null +++ b/lighthouse-viewer/test/psi-api-test.js @@ -0,0 +1,19 @@ +/** + * @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'; + +/* eslint-env jest */ + +const psiApi = require('../app/src/psi-api.js'); +const Config = require('../../lighthouse-core/config/config.js'); +const defaultConfig = require('../../lighthouse-core/config/default-config.js'); + +describe('PSI API', () => { + it('default psi categories is same as default config categories', () => { + const categories = Config.getCategories(defaultConfig).map(c => c.id).sort(); + expect(psiApi.PSI_DEFAULT_CATEGORIES.slice(0).sort()).toEqual(categories); + }); +}); From 82ad7745424d0fe9b19205de37804c5979fe27dd Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 16:03:36 -0700 Subject: [PATCH 22/50] for of --- lighthouse-viewer/app/src/psi-api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index bdb34cf55af4..661afd83315d 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -32,10 +32,10 @@ class PSIApi { }, params); const apiUrl = new URL(PSI_URL); - Object.entries(params).forEach(([key, value]) => { - if (key === 'category') return; + for (const [key, value] of Object.entries(params)) { + if (key === 'category') continue; if (value) apiUrl.searchParams.append(key, value); - }); + } for (const singleCategory of (params.category || PSI_DEFAULT_CATEGORIES)) { apiUrl.searchParams.append('category', singleCategory); } From 943df67a6d59bc342c5cebf4b362cd2e605864b8 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 16:09:06 -0700 Subject: [PATCH 23/50] fetch psi refactor --- lighthouse-viewer/app/src/psi-api.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index 661afd83315d..ff51787f28f1 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -26,20 +26,19 @@ class PSIApi { * @return {Promise} */ fetchPSI(params) { - params = Object.assign({ - key: PSI_KEY, - strategy: 'mobile', - }, params); - const apiUrl = new URL(PSI_URL); - for (const [key, value] of Object.entries(params)) { + for (const kv of Object.entries(params)) { + const key = kv[0]; + let value = kv[1]; + if (key === 'category') continue; - if (value) apiUrl.searchParams.append(key, value); + if (key === 'strategy') value = value || 'mobile'; + if (typeof value !== 'undefined') apiUrl.searchParams.append(key, value); } for (const singleCategory of (params.category || PSI_DEFAULT_CATEGORIES)) { apiUrl.searchParams.append('category', singleCategory); } - + apiUrl.searchParams.append('key', PSI_KEY); return fetch(apiUrl.href).then(res => res.json()); } } From 67b51801876542cca30d38b45848753e1ad4c407 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 16:10:17 -0700 Subject: [PATCH 24/50] name --- lighthouse-viewer/app/src/psi-api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index ff51787f28f1..f6e4d83411c8 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -28,12 +28,12 @@ class PSIApi { fetchPSI(params) { const apiUrl = new URL(PSI_URL); for (const kv of Object.entries(params)) { - const key = kv[0]; + const name = kv[0]; let value = kv[1]; - if (key === 'category') continue; - if (key === 'strategy') value = value || 'mobile'; - if (typeof value !== 'undefined') apiUrl.searchParams.append(key, value); + if (name === 'category') continue; + if (name === 'strategy') value = value || 'mobile'; + if (typeof value !== 'undefined') apiUrl.searchParams.append(name, value); } for (const singleCategory of (params.category || PSI_DEFAULT_CATEGORIES)) { apiUrl.searchParams.append('category', singleCategory); From 2de9b62ec719d9c8ea5676c3162e6596131f2169 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 16:21:53 -0700 Subject: [PATCH 25/50] provider --- .../app/src/lighthouse-report-viewer.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 370782b79533..579f4b909fb7 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -38,12 +38,10 @@ class LighthouseReportViewer { this._psi = new PSIApi(); /** - * Used for tracking whether to offer to upload as a gist. - * @type {boolean} + * Used for tracking where the report came from. + * @type {'gist' | 'psi' | 'opener' | 'paste' | 'file' | null} */ - this._reportIsFromGist = false; - - this._reportIsFromPSI = false; + this._reportProvider = null; this._addEventListeners(); this._loadFromDeepLink(); @@ -110,7 +108,7 @@ class LighthouseReportViewer { if (gistId) { return this._github.getGistFileContentAsJson(gistId).then(reportJson => { - this._reportIsFromGist = true; + this._reportProvider = 'gist'; this._replaceReportHtml(reportJson); }).catch(err => logger.error(err.message)); } @@ -174,12 +172,12 @@ class LighthouseReportViewer { // Only give gist-saving callback if current report isn't from a gist. let saveCallback = null; - if (!this._reportIsFromGist) { + if (this._reportProvider !== 'gist') { saveCallback = this._onSaveJson; } // Only clear query string if current report isn't from a gist or PSI. - if (!this._reportIsFromPSI && !this._reportIsFromGist) { + if (this._reportProvider !== 'gist' && this._reportProvider !== 'psi') { history.pushState({}, '', LighthouseReportViewer.APP_URL); } @@ -219,7 +217,7 @@ class LighthouseReportViewer { } catch (e) { throw new Error('Could not parse JSON file.'); } - this._reportIsFromGist = this._reportIsFromPSI = false; + this._reportProvider = 'file'; this._replaceReportHtml(json); }).catch(err => logger.error(err.message)); } @@ -280,7 +278,7 @@ class LighthouseReportViewer { window.ga('send', 'event', 'report', 'created'); } - this._reportIsFromGist = true; + this._reportProvider = 'gist'; history.pushState({}, '', `${LighthouseReportViewer.APP_URL}?gist=${id}`); return id; @@ -311,14 +309,14 @@ class LighthouseReportViewer { // Try paste as json content. try { const json = JSON.parse(e.clipboardData.getData('text')); - this._reportIsFromGist = this._reportIsFromPSI = false; + this._reportProvider = 'paste'; this._replaceReportHtml(json); if (window.ga) { window.ga('send', 'event', 'report', 'paste'); } } catch (err) { - // noop + this._reportProvider = null; } } @@ -375,7 +373,7 @@ class LighthouseReportViewer { _listenForMessages() { window.addEventListener('message', e => { if (e.source === self.opener && e.data.lhresults) { - this._reportIsFromGist = this._reportIsFromPSI = false; + this._reportProvider = 'opener'; this._replaceReportHtml(e.data.lhresults); if (self.opener && !self.opener.closed) { @@ -415,7 +413,7 @@ class LighthouseReportViewer { return; } - this._reportIsFromPSI = true; + this._reportProvider = 'psi'; loadingOverlayEl.remove(); this._replaceReportHtml(response.lighthouseResult); }); From 97ddc98f69202dcc3658e9ce478e09f634bd5ab1 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Tue, 13 Aug 2019 16:35:02 -0700 Subject: [PATCH 26/50] use logger --- .../app/src/lighthouse-report-viewer.js | 15 +++++++-------- lighthouse-viewer/app/styles/viewer.css | 8 -------- lighthouse-viewer/test/viewer-test-pptr.js | 2 +- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 579f4b909fb7..fc67a51b736d 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -395,26 +395,25 @@ class LighthouseReportViewer { * @param {PSIParams} params */ _loadFromPSI(params) { - const loadingOverlayEl = document.createElement('div'); - loadingOverlayEl.classList.add('lh-loading-overlay'); - loadingOverlayEl.textContent = 'Waiting for Lighthouse results ...'; - find('.viewer-placeholder-inner', document.body).classList.add('lh-loading'); - document.body.appendChild(loadingOverlayEl); + const placeholder = find('.viewer-placeholder-inner', document.body); + placeholder.classList.add('lh-loading'); + logger.log('Waiting for Lighthouse results ...'); return this._psi.fetchPSI(params).then(response => { + logger.hide(); + placeholder.classList.remove('lh-loading'); + if (!response.lighthouseResult) { if (response.error) { // eslint-disable-next-line no-console console.error(response.error); logger.error(response.error.message); - loadingOverlayEl.innerText = response.error.message; } else { - loadingOverlayEl.innerText = 'PSI did not return a Lighthouse Result'; + logger.error('PSI did not return a Lighthouse Result'); } return; } this._reportProvider = 'psi'; - loadingOverlayEl.remove(); this._replaceReportHtml(response.lighthouseResult); }); } diff --git a/lighthouse-viewer/app/styles/viewer.css b/lighthouse-viewer/app/styles/viewer.css index 052e69c30cf7..a9f7dddb5f6c 100644 --- a/lighthouse-viewer/app/styles/viewer.css +++ b/lighthouse-viewer/app/styles/viewer.css @@ -125,14 +125,6 @@ display: block !important; } -.lh-loading-overlay { - position: absolute; - top: 50%; - transform: translateX(-50%); - left: 50%; - filter: none; -} - .lh-loading { filter: blur(2px); } diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index 038a1498593c..890aeeaa9cfe 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -78,7 +78,7 @@ describe('Lighthouse Viewer', () => { ]); }); - describe('Upload', () => { + describe('Renders the report', () => { beforeAll(async function() { await viewerPage.goto(viewerUrl, {waitUntil: 'networkidle2', timeout: 30000}); const fileInput = await viewerPage.$('#hidden-file-input'); From 143f3bd2abc87553683efbd8ed5422c93e7d83b5 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Wed, 14 Aug 2019 12:38:39 -0700 Subject: [PATCH 27/50] provider param --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 11 +++++++++-- lighthouse-viewer/test/viewer-test-pptr.js | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index fc67a51b736d..20a1896b4bf5 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -93,10 +93,17 @@ class LighthouseReportViewer { */ _loadFromDeepLink() { const params = new URLSearchParams(location.search); + + let provider = params.get('provider'); const gistId = params.get('gist'); const url = params.get('url'); - if (url) { + // Default provider is gist, but only if `gist` param exists (to maintain backwards compatibility) + if (!provider && params.has('gist')) { + provider = 'gist'; + } + + if (provider === 'psi' && url) { return this._loadFromPSI({ url, category: params.has('category') ? params.getAll('category') : undefined, @@ -106,7 +113,7 @@ class LighthouseReportViewer { }); } - if (gistId) { + if (provider === 'gist' && gistId) { return this._github.getGistFileContentAsJson(gistId).then(reportJson => { this._reportProvider = 'gist'; this._replaceReportHtml(reportJson); diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index 890aeeaa9cfe..702ad74991d1 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -168,7 +168,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with all categories by default', async () => { - const url = `${viewerUrl}?url=https://www.example.com`; + const url = `${viewerUrl}?provider=psi&url=https://www.example.com`; await viewerPage.goto(url); // Intercept and respond with sample lhr. @@ -212,7 +212,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with specified categoeries', async () => { - const url = `${viewerUrl}?url=https://www.example.com&category=seo&category=pwa`; + const url = `${viewerUrl}?provider=psi&url=https://www.example.com&category=seo&category=pwa`; await viewerPage.goto(url); const interceptedRequest = await onApiRequestInterception; From 8cab861ab32bd7213cd6697b7ba24ce07c25200e Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 11:37:52 -0700 Subject: [PATCH 28/50] defer main --- lighthouse-viewer/app/src/main.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lighthouse-viewer/app/src/main.js b/lighthouse-viewer/app/src/main.js index e33ad603ff9f..792d26c4aa53 100644 --- a/lighthouse-viewer/app/src/main.js +++ b/lighthouse-viewer/app/src/main.js @@ -48,7 +48,11 @@ function main() { window.viewer = new LighthouseReportViewer(); } -main(); +// Defer main until the entire script is loaded. +// This is because the current build system for the viewer concats files in alphabetical order, +// which means "psi-api.js" comes _after_ "main.js", causing the constructor to error if `main` +// is called synchronously. +Promise.resolve().then(main); if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js'); From 547549f4e4de65755ebdb68a49536fafecdb7997 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 14:40:57 -0700 Subject: [PATCH 29/50] readme --- lighthouse-viewer/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lighthouse-viewer/README.md b/lighthouse-viewer/README.md index 8f1cd2616d5c..c8a6874e681c 100644 --- a/lighthouse-viewer/README.md +++ b/lighthouse-viewer/README.md @@ -21,11 +21,15 @@ yarn deploy-viewer For more information on deployment, see `releasing.md`. +## Gist + +http://localhost:8000/?gist=bd1779783a5bbcb348564a58f80f7099 + ## PSI Example: ``` -http://localhost:8000/?url=https://www.example.com&category=pwa&category=seo +http://localhost:8000/?provider=psi&url=https://www.example.com&category=pwa&category=seo ``` Options: From 48f9e46eae3bf7caf04501bae5af09d5bde54430 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 14:43:58 -0700 Subject: [PATCH 30/50] source --- lighthouse-viewer/README.md | 2 +- .../app/src/lighthouse-report-viewer.js | 12 ++++++------ lighthouse-viewer/test/viewer-test-pptr.js | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lighthouse-viewer/README.md b/lighthouse-viewer/README.md index c8a6874e681c..e9fca47691e1 100644 --- a/lighthouse-viewer/README.md +++ b/lighthouse-viewer/README.md @@ -29,7 +29,7 @@ http://localhost:8000/?gist=bd1779783a5bbcb348564a58f80f7099 Example: ``` -http://localhost:8000/?provider=psi&url=https://www.example.com&category=pwa&category=seo +http://localhost:8000/?source=psi&url=https://www.example.com&category=pwa&category=seo ``` Options: diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 20a1896b4bf5..5e060f6c38fb 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -94,16 +94,16 @@ class LighthouseReportViewer { _loadFromDeepLink() { const params = new URLSearchParams(location.search); - let provider = params.get('provider'); + let source = params.get('source'); const gistId = params.get('gist'); const url = params.get('url'); - // Default provider is gist, but only if `gist` param exists (to maintain backwards compatibility) - if (!provider && params.has('gist')) { - provider = 'gist'; + // Default source is gist, but only if `gist` param exists (to maintain backwards compatibility) + if (!source && params.has('gist')) { + source = 'gist'; } - if (provider === 'psi' && url) { + if (source === 'psi' && url) { return this._loadFromPSI({ url, category: params.has('category') ? params.getAll('category') : undefined, @@ -113,7 +113,7 @@ class LighthouseReportViewer { }); } - if (provider === 'gist' && gistId) { + if (source === 'gist' && gistId) { return this._github.getGistFileContentAsJson(gistId).then(reportJson => { this._reportProvider = 'gist'; this._replaceReportHtml(reportJson); diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index 702ad74991d1..bc8520dc8d2d 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -168,7 +168,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with all categories by default', async () => { - const url = `${viewerUrl}?provider=psi&url=https://www.example.com`; + const url = `${viewerUrl}?source=psi&url=https://www.example.com`; await viewerPage.goto(url); // Intercept and respond with sample lhr. @@ -212,7 +212,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with specified categoeries', async () => { - const url = `${viewerUrl}?provider=psi&url=https://www.example.com&category=seo&category=pwa`; + const url = `${viewerUrl}?source=psi&url=https://www.example.com&category=seo&category=pwa`; await viewerPage.goto(url); const interceptedRequest = await onApiRequestInterception; From 69f3dd205ea714978d5445c5c7bfeea759bcaba6 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 14:49:01 -0700 Subject: [PATCH 31/50] for --- lighthouse-viewer/app/src/psi-api.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index f6e4d83411c8..f628f641ad7e 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -27,10 +27,7 @@ class PSIApi { */ fetchPSI(params) { const apiUrl = new URL(PSI_URL); - for (const kv of Object.entries(params)) { - const name = kv[0]; - let value = kv[1]; - + for (let [name, value] of Object.entries(params)) { if (name === 'category') continue; if (name === 'strategy') value = value || 'mobile'; if (typeof value !== 'undefined') apiUrl.searchParams.append(name, value); From c1046042d6374f664e5b0bbf7aa6ce17699043d3 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 15:06:02 -0700 Subject: [PATCH 32/50] load blur for gist too --- .../app/src/lighthouse-report-viewer.js | 29 ++++++++++++++----- lighthouse-viewer/app/styles/viewer.css | 7 ++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 5e060f6c38fb..1b985604df25 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -103,23 +103,28 @@ class LighthouseReportViewer { source = 'gist'; } + let loadPromise = null; + if (source === 'psi' && url) { - return this._loadFromPSI({ + loadPromise = this._loadFromPSI({ url, category: params.has('category') ? params.getAll('category') : undefined, strategy: params.get('strategy') || undefined, locale: params.get('locale') || undefined, utm_source: params.get('utm_source') || undefined, }); - } - - if (source === 'gist' && gistId) { - return this._github.getGistFileContentAsJson(gistId).then(reportJson => { + } else if (source === 'gist' && gistId) { + loadPromise = this._github.getGistFileContentAsJson(gistId).then(reportJson => { this._reportProvider = 'gist'; this._replaceReportHtml(reportJson); }).catch(err => logger.error(err.message)); } + if (loadPromise) { + this._toggleLoadingBlur(true); + return loadPromise.finally(() => this._toggleLoadingBlur(false)); + } + return Promise.resolve(); } @@ -355,6 +360,7 @@ class LighthouseReportViewer { * @private */ _loadFromGistURL(urlStr) { + this._toggleLoadingBlur(true); try { const url = new URL(urlStr); @@ -370,6 +376,8 @@ class LighthouseReportViewer { } } catch (err) { logger.error('Invalid URL'); + } finally { + this._toggleLoadingBlur(false); } } @@ -402,12 +410,9 @@ class LighthouseReportViewer { * @param {PSIParams} params */ _loadFromPSI(params) { - const placeholder = find('.viewer-placeholder-inner', document.body); - placeholder.classList.add('lh-loading'); logger.log('Waiting for Lighthouse results ...'); return this._psi.fetchPSI(params).then(response => { logger.hide(); - placeholder.classList.remove('lh-loading'); if (!response.lighthouseResult) { if (response.error) { @@ -424,6 +429,14 @@ class LighthouseReportViewer { this._replaceReportHtml(response.lighthouseResult); }); } + + /** + * @param {boolean} force + */ + _toggleLoadingBlur(force) { + const placeholder = find('.viewer-placeholder-inner', document.body); + placeholder.classList.toggle('lh-loading', force); + } } // node export for testing. diff --git a/lighthouse-viewer/app/styles/viewer.css b/lighthouse-viewer/app/styles/viewer.css index a9f7dddb5f6c..2d608f2a11c5 100644 --- a/lighthouse-viewer/app/styles/viewer.css +++ b/lighthouse-viewer/app/styles/viewer.css @@ -60,6 +60,9 @@ cursor: pointer; background-color: #fff; } +.viewer-placeholder-inner.lh-loading { + filter: blur(2px); +} .viewer-placeholder-inner.dropping { border-color: currentColor; } @@ -124,7 +127,3 @@ .lh-tools__dropdown .lh-tools--gist { display: block !important; } - -.lh-loading { - filter: blur(2px); -} From fb8f814429c22d1035a6cbbdad2195747417154b Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Thu, 15 Aug 2019 15:44:00 -0700 Subject: [PATCH 33/50] psiurl --- lighthouse-viewer/README.md | 2 +- .../app/src/lighthouse-report-viewer.js | 14 ++++---------- lighthouse-viewer/test/viewer-test-pptr.js | 4 ++-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/lighthouse-viewer/README.md b/lighthouse-viewer/README.md index e9fca47691e1..0ba5d3306641 100644 --- a/lighthouse-viewer/README.md +++ b/lighthouse-viewer/README.md @@ -29,7 +29,7 @@ http://localhost:8000/?gist=bd1779783a5bbcb348564a58f80f7099 Example: ``` -http://localhost:8000/?source=psi&url=https://www.example.com&category=pwa&category=seo +http://localhost:8000/?psiurl=https://www.example.com&category=pwa&category=seo ``` Options: diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 1b985604df25..642c781ae589 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -94,26 +94,20 @@ class LighthouseReportViewer { _loadFromDeepLink() { const params = new URLSearchParams(location.search); - let source = params.get('source'); const gistId = params.get('gist'); - const url = params.get('url'); - - // Default source is gist, but only if `gist` param exists (to maintain backwards compatibility) - if (!source && params.has('gist')) { - source = 'gist'; - } + const psiurl = params.get('psiurl'); let loadPromise = null; - if (source === 'psi' && url) { + if (psiurl) { loadPromise = this._loadFromPSI({ - url, + url: psiurl, category: params.has('category') ? params.getAll('category') : undefined, strategy: params.get('strategy') || undefined, locale: params.get('locale') || undefined, utm_source: params.get('utm_source') || undefined, }); - } else if (source === 'gist' && gistId) { + } else if (gistId) { loadPromise = this._github.getGistFileContentAsJson(gistId).then(reportJson => { this._reportProvider = 'gist'; this._replaceReportHtml(reportJson); diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js index bc8520dc8d2d..188f3636ba3f 100644 --- a/lighthouse-viewer/test/viewer-test-pptr.js +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -168,7 +168,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with all categories by default', async () => { - const url = `${viewerUrl}?source=psi&url=https://www.example.com`; + const url = `${viewerUrl}?psiurl=https://www.example.com`; await viewerPage.goto(url); // Intercept and respond with sample lhr. @@ -212,7 +212,7 @@ describe('Lighthouse Viewer', () => { }); it('should call out to PSI with specified categoeries', async () => { - const url = `${viewerUrl}?source=psi&url=https://www.example.com&category=seo&category=pwa`; + const url = `${viewerUrl}?psiurl=https://www.example.com&category=seo&category=pwa`; await viewerPage.goto(url); const interceptedRequest = await onApiRequestInterception; From fd05a06997c8b1e2f3ba00f5c37a124238843540 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 16 Aug 2019 11:52:06 -0700 Subject: [PATCH 34/50] remove extra blue. rename to fetchFromPSI --- lighthouse-viewer/app/src/lighthouse-report-viewer.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 642c781ae589..0c43b501a048 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -100,7 +100,7 @@ class LighthouseReportViewer { let loadPromise = null; if (psiurl) { - loadPromise = this._loadFromPSI({ + loadPromise = this._fetchFromPSI({ url: psiurl, category: params.has('category') ? params.getAll('category') : undefined, strategy: params.get('strategy') || undefined, @@ -354,7 +354,6 @@ class LighthouseReportViewer { * @private */ _loadFromGistURL(urlStr) { - this._toggleLoadingBlur(true); try { const url = new URL(urlStr); @@ -370,8 +369,6 @@ class LighthouseReportViewer { } } catch (err) { logger.error('Invalid URL'); - } finally { - this._toggleLoadingBlur(false); } } @@ -403,7 +400,7 @@ class LighthouseReportViewer { /** * @param {PSIParams} params */ - _loadFromPSI(params) { + _fetchFromPSI(params) { logger.log('Waiting for Lighthouse results ...'); return this._psi.fetchPSI(params).then(response => { logger.hide(); From e849af1154ebe1233b7df247ac63a75904704e8f Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 16 Aug 2019 11:56:22 -0700 Subject: [PATCH 35/50] Update lighthouse-viewer/app/src/main.js Co-Authored-By: Brendan Kenny --- lighthouse-viewer/app/src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-viewer/app/src/main.js b/lighthouse-viewer/app/src/main.js index 792d26c4aa53..febccc14a518 100644 --- a/lighthouse-viewer/app/src/main.js +++ b/lighthouse-viewer/app/src/main.js @@ -52,7 +52,7 @@ function main() { // This is because the current build system for the viewer concats files in alphabetical order, // which means "psi-api.js" comes _after_ "main.js", causing the constructor to error if `main` // is called synchronously. -Promise.resolve().then(main); +window.addEventListener('DOMContentLoaded', main); if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js'); From 0c8bb2886d489915a1b91d3100c733f14dffcd42 Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 16 Aug 2019 12:06:48 -0700 Subject: [PATCH 36/50] paul promise refactor. eslint ignore. --- .../app/src/lighthouse-report-viewer.js | 15 ++++++--------- lighthouse-viewer/app/src/psi-api.js | 1 + 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lighthouse-viewer/app/src/lighthouse-report-viewer.js b/lighthouse-viewer/app/src/lighthouse-report-viewer.js index 0c43b501a048..9d1e3278304b 100644 --- a/lighthouse-viewer/app/src/lighthouse-report-viewer.js +++ b/lighthouse-viewer/app/src/lighthouse-report-viewer.js @@ -97,8 +97,10 @@ class LighthouseReportViewer { const gistId = params.get('gist'); const psiurl = params.get('psiurl'); - let loadPromise = null; + if (!gistId && !psiurl) return; + this._toggleLoadingBlur(true); + let loadPromise = Promise.resolve(); if (psiurl) { loadPromise = this._fetchFromPSI({ url: psiurl, @@ -114,12 +116,7 @@ class LighthouseReportViewer { }).catch(err => logger.error(err.message)); } - if (loadPromise) { - this._toggleLoadingBlur(true); - return loadPromise.finally(() => this._toggleLoadingBlur(false)); - } - - return Promise.resolve(); + return loadPromise.finally(() => this._toggleLoadingBlur(false)); } /** @@ -425,8 +422,8 @@ class LighthouseReportViewer { * @param {boolean} force */ _toggleLoadingBlur(force) { - const placeholder = find('.viewer-placeholder-inner', document.body); - placeholder.classList.toggle('lh-loading', force); + const placeholder = document.querySelector('.viewer-placeholder-inner'); + if (placeholder) placeholder.classList.toggle('lh-loading', force); } } diff --git a/lighthouse-viewer/app/src/psi-api.js b/lighthouse-viewer/app/src/psi-api.js index f628f641ad7e..e166cc92730a 100644 --- a/lighthouse-viewer/app/src/psi-api.js +++ b/lighthouse-viewer/app/src/psi-api.js @@ -27,6 +27,7 @@ class PSIApi { */ fetchPSI(params) { const apiUrl = new URL(PSI_URL); + // eslint-disable-next-line prefer-const for (let [name, value] of Object.entries(params)) { if (name === 'category') continue; if (name === 'strategy') value = value || 'mobile'; From 04f4e6b3d0b86500bdbae370ad29fc5f91a42f2d Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Fri, 16 Aug 2019 12:08:13 -0700 Subject: [PATCH 37/50] better main init --- lighthouse-viewer/app/index.html | 7 +++++++ lighthouse-viewer/app/src/main.js | 10 ---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lighthouse-viewer/app/index.html b/lighthouse-viewer/app/index.html index 6e90dbe40063..5f065b06bc99 100644 --- a/lighthouse-viewer/app/index.html +++ b/lighthouse-viewer/app/index.html @@ -44,6 +44,13 @@

Lighthouse Report Viewer

+