diff --git a/.travis.yml b/.travis.yml index 7a5a5af9a80c..94fab7fddd78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,6 +39,7 @@ script: - yarn diff:sample-json - yarn smoke:silentcoverage - yarn test-extension + - yarn test-viewer # _JAVA_OPTIONS is breaking parsing of compiler output. See #3338. - unset _JAVA_OPTIONS # FIXME(paulirish): re-enable after a roll of LH->CDT diff --git a/lighthouse-cli/test/fixtures/static-server.js b/lighthouse-cli/test/fixtures/static-server.js index da3a75f4086e..0a4bb7e39b86 100644 --- a/lighthouse-cli/test/fixtures/static-server.js +++ b/lighthouse-cli/test/fixtures/static-server.js @@ -24,16 +24,22 @@ function requestHandler(request, response) { const queryString = requestUrl.search && parseQueryString(requestUrl.search.slice(1)); let absoluteFilePath = path.join(__dirname, filePath); + if (filePath.startsWith('/lighthouse-viewer')) { + // Rewrite lighthouse-viewer paths to point to that location. + absoluteFilePath = path.join(__dirname, '/../../../', filePath); + } + if (filePath === '/zone.js') { // evaluateAsync previously had a bug that LH would fail if a page polyfilled Promise. // We bring in an aggressive Promise polyfill (zone) to ensure we don't still fail. const zonePath = '../../../node_modules/zone.js'; absoluteFilePath = path.join(__dirname, `${zonePath}/dist/zone.js`); - } - - // Disallow file requests outside of LH folder - if (!path.parse(absoluteFilePath).dir.startsWith(lhRootDirPath)) { - return readFileCallback(new Error('Disallowed path')); + } else { + // Otherwise, disallow file requests outside of LH folder or from node_modules + const filePathDir = path.parse(absoluteFilePath).dir; + if (!filePathDir.startsWith(lhRootDirPath) || filePathDir.includes('node_modules')) { + return readFileCallback(new Error('Disallowed path')); + } } fs.exists(absoluteFilePath, fsExistsCallback); diff --git a/lighthouse-viewer/package.json b/lighthouse-viewer/package.json index 28b85d816c5e..7eba82fb8075 100644 --- a/lighthouse-viewer/package.json +++ b/lighthouse-viewer/package.json @@ -6,7 +6,8 @@ }, "scripts": { "watch": "gulp watch", - "build": "gulp" + "build": "gulp", + "pptr-test": "mocha test/viewer-test-pptr.js" }, "devDependencies": { "brfs": "^1.4.3", diff --git a/lighthouse-viewer/test/viewer-test-pptr.js b/lighthouse-viewer/test/viewer-test-pptr.js new file mode 100644 index 000000000000..465d784fc8cd --- /dev/null +++ b/lighthouse-viewer/test/viewer-test-pptr.js @@ -0,0 +1,124 @@ +/** + * @license Copyright 2018 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 mocha */ + +const path = require('path'); +const assert = require('assert'); +const puppeteer = require('../../node_modules/puppeteer/index.js'); + +const {server} = require('../../lighthouse-cli/test/fixtures/static-server.js'); +const portNumber = 10200; +const viewerUrl = `http://localhost:${portNumber}/lighthouse-viewer/dist/index.html`; +const sampleLhr = __dirname + '/../../lighthouse-core/test/results/sample_v2.json'; + +const config = require(path.resolve(__dirname, '../../lighthouse-core/config/default-config.js')); +const lighthouseCategories = Object.keys(config.categories); +const getAuditsOfCategory = category => config.categories[category].audits; + +// TODO: should be combined in some way with lighthouse-extension/test/extension-test.js +describe('Lighthouse Viewer', function() { + // eslint-disable-next-line no-console + console.log('\n✨ Be sure to have recently run this: yarn build-viewer'); + + let browser; + let viewerPage; + const pageErrors = []; + + function getAuditElementsCount({category, selector}) { + return viewerPage.evaluate( + ({category, selector}) => + document.querySelector(`#${category}`).parentNode.querySelectorAll(selector).length, + {category, selector} + ); + } + + before(async function() { + server.listen(portNumber, 'localhost'); + + // start puppeteer + browser = await puppeteer.launch({ + headless: true, + executablePath: process.env.CHROME_PATH, + }); + 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}); + }); + + after(async function() { + await Promise.all([ + new Promise(resolve => server.close(resolve)), + browser && browser.close(), + ]); + }); + + + const selectors = { + audits: '.lh-audit, .lh-perf-metric, .lh-perf-hint', + titles: '.lh-score__title, .lh-perf-hint__title, .lh-perf-metric__title', + }; + + it('should load with no errors', async () => { + assert.deepStrictEqual(pageErrors, []); + }); + + it('should contain all categories', async () => { + const categories = await viewerPage.$$(`#${lighthouseCategories.join(',#')}`); + assert.equal( + categories.length, + lighthouseCategories.length, + `${categories.join(' ')} does not match ${lighthouseCategories.join(' ')}` + ); + }); + + it('should contain audits of all categories', async () => { + for (const category of lighthouseCategories) { + let expected = getAuditsOfCategory(category).length; + if (category === 'performance') { + expected = getAuditsOfCategory(category).filter(a => !!a.group).length; + } + + const elementCount = await getAuditElementsCount({category, selector: selectors.audits}); + + assert.equal( + expected, + elementCount, + `${category} does not have the correct amount of audits` + ); + } + }); + + it('should contain a filmstrip', async () => { + const filmstrip = await viewerPage.$('.lh-filmstrip'); + + assert.ok(!!filmstrip, `filmstrip is not available`); + }); + + it('should not have any unexpected audit errors', async () => { + function getDebugStrings(elems, selectors) { + return elems.map(el => { + const audit = el.closest(selectors.audits); + const auditTitle = audit && audit.querySelector(selectors.titles); + return { + debugString: el.textContent, + title: auditTitle ? auditTitle.textContent : 'Audit title unvailable', + }; + }); + } + + const auditErrors = await viewerPage.$$eval('.lh-debug', getDebugStrings, selectors); + const errors = auditErrors.filter(item => item.debugString.includes('Audit error:')); + const unexpectedErrrors = errors.filter(item => { + return !item.debugString.includes('Required RobotsTxt gatherer did not run'); + }); + assert.deepStrictEqual(unexpectedErrrors, [], 'Audit errors found within the report'); + }); +}); diff --git a/package.json b/package.json index 60fb6ba2135e..221aab8f5b4e 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "start": "node ./lighthouse-cli/index.js", "test": "yarn lint --quiet && yarn unit && yarn type-check && yarn closure && yarn diff:sample-json", "test-extension": "cd lighthouse-extension && yarn test", + "test-viewer": "cd lighthouse-viewer && yarn pptr-test", "unit-core": "mocha --reporter dot \"lighthouse-core/test/**/*-test.js\"", "unit-cli": "mocha --reporter dot \"lighthouse-cli/test/**/*-test.js\"",