├── 1 ├── index.js ├── helpers.js └── testPage.js ├── 2 ├── index.js ├── helpers.js └── testPage.js ├── 3 ├── index.js ├── helpers.js └── testPage.js ├── 4 ├── index.js ├── testPage.js └── helpers.js ├── 5 ├── index.js ├── testPage.js └── helpers.js ├── 6 ├── index.js ├── testPage.js └── helpers.js ├── .gitignore ├── package.json ├── results ├── 3G.js ├── cable.js ├── 3G-CPU6x.js └── test │ ├── testPage.js │ ├── helpers.js │ └── index.js ├── README.md └── LICENSE /1/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | const browser = await puppeteer.launch(); 6 | const page = await browser.newPage(); 7 | console.log(await testPage(page)); 8 | await browser.close(); 9 | })(); 10 | -------------------------------------------------------------------------------- /2/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | const browser = await puppeteer.launch(); 6 | const page = await browser.newPage(); 7 | console.log(await testPage(page)); 8 | await browser.close(); 9 | })(); 10 | -------------------------------------------------------------------------------- /3/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | const browser = await puppeteer.launch(); 6 | const page = await browser.newPage(); 7 | console.log(await testPage(page)); 8 | await browser.close(); 9 | })(); 10 | -------------------------------------------------------------------------------- /4/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | const browser = await puppeteer.launch(); 6 | const page = await browser.newPage(); 7 | console.log(await testPage(page)); 8 | await browser.close(); 9 | })(); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | 7 | # misc 8 | .DS_Store 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | .idea 19 | 20 | /results/*.json -------------------------------------------------------------------------------- /1/helpers.js: -------------------------------------------------------------------------------- 1 | const extractDataFromPerformanceTiming = (timing, ...dataNames) => { 2 | const navigationStart = timing.navigationStart; 3 | 4 | const extractedData = {}; 5 | dataNames.forEach(name => { 6 | extractedData[name] = timing[name] - navigationStart; 7 | }); 8 | 9 | return extractedData; 10 | }; 11 | 12 | module.exports = { 13 | extractDataFromPerformanceTiming, 14 | }; 15 | -------------------------------------------------------------------------------- /3/helpers.js: -------------------------------------------------------------------------------- 1 | const getTimeFromPerformanceMetrics = (metrics, name) => 2 | metrics.metrics.find(x => x.name === name).value * 1000; 3 | 4 | const getCustomMetric = (page, name) => 5 | new Promise(resolve => 6 | page.on('metrics', ({ title, metrics }) => { 7 | if (title === name) { 8 | resolve(metrics.Timestamp * 1000); 9 | } 10 | }) 11 | ); 12 | 13 | module.exports = { 14 | getTimeFromPerformanceMetrics, 15 | getCustomMetric, 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-website-performance-with-puppeteer", 3 | "version": "1.0.0", 4 | "repository": "https://github.com/Everettss/test-website-performance-with-puppeteer", 5 | "description": "Code used in article https://michaljanaszek.com/blog/test-website-performance-with-puppeteer", 6 | "main": "index.js", 7 | "author": "Michal Janaszek", 8 | "license": "MIT", 9 | "dependencies": { 10 | "progress": "^2.0.0", 11 | "puppeteer": "^1.6.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /1/testPage.js: -------------------------------------------------------------------------------- 1 | const { extractDataFromPerformanceTiming } = require('./helpers'); 2 | 3 | async function testPage(page) { 4 | await page.goto('http://localhost:8080'); 5 | 6 | const performanceTiming = JSON.parse( 7 | await page.evaluate(() => JSON.stringify(window.performance.timing)) 8 | ); 9 | 10 | return extractDataFromPerformanceTiming( 11 | performanceTiming, 12 | 'responseEnd', 13 | 'domInteractive', 14 | 'domContentLoadedEventEnd', 15 | 'loadEventEnd' 16 | ); 17 | } 18 | 19 | module.exports = testPage; 20 | -------------------------------------------------------------------------------- /results/3G.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('./test/index'); 3 | 4 | const runs = 3; 5 | const emulateConditions = async client => { 6 | await client.send('Network.enable'); 7 | await client.send('Network.emulateNetworkConditions', { 8 | offline: false, 9 | latency: 200, 10 | downloadThroughput: 768 * 1024 / 8, 11 | uploadThroughput: 330 * 1024 / 8, 12 | }); 13 | }; 14 | 15 | const filePath = path.join(__dirname, '3G.json'); 16 | 17 | (async () => { 18 | await test(runs, emulateConditions, filePath); 19 | })(); 20 | -------------------------------------------------------------------------------- /results/cable.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('./test/index'); 3 | 4 | const runs = 3; 5 | const emulateConditions = async client => { 6 | await client.send('Network.enable'); 7 | await client.send('Network.emulateNetworkConditions', { 8 | offline: false, 9 | latency: 28, 10 | downloadThroughput: 5 * 1024 * 1024 / 8, 11 | uploadThroughput: 1024 * 1024 / 8, 12 | }); 13 | }; 14 | 15 | const filePath = path.join(__dirname, 'cable.json'); 16 | 17 | (async () => { 18 | await test(runs, emulateConditions, filePath); 19 | })(); 20 | -------------------------------------------------------------------------------- /results/3G-CPU6x.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('./test/index'); 3 | 4 | const runs = 3; 5 | const emulateConditions = async client => { 6 | await client.send('Network.enable'); 7 | await client.send('Network.emulateNetworkConditions', { 8 | offline: false, 9 | latency: 200, 10 | downloadThroughput: 768 * 1024 / 8, 11 | uploadThroughput: 330 * 1024 / 8, 12 | }); 13 | await client.send('Emulation.setCPUThrottlingRate', { rate: 6 }); 14 | }; 15 | 16 | const filePath = path.join(__dirname, '3G-CPU6x.json'); 17 | 18 | (async () => { 19 | await test(runs, emulateConditions, filePath); 20 | })(); 21 | -------------------------------------------------------------------------------- /2/helpers.js: -------------------------------------------------------------------------------- 1 | const getTimeFromPerformanceMetrics = (metrics, name) => 2 | metrics.metrics.find(x => x.name === name).value * 1000; 3 | 4 | const extractDataFromPerformanceMetrics = (metrics, ...dataNames) => { 5 | const navigationStart = getTimeFromPerformanceMetrics( 6 | metrics, 7 | 'NavigationStart' 8 | ); 9 | 10 | const extractedData = {}; 11 | dataNames.forEach(name => { 12 | extractedData[name] = 13 | getTimeFromPerformanceMetrics(metrics, name) - navigationStart; 14 | }); 15 | 16 | return extractedData; 17 | }; 18 | 19 | module.exports = { 20 | getTimeFromPerformanceMetrics, 21 | extractDataFromPerformanceMetrics, 22 | }; 23 | -------------------------------------------------------------------------------- /5/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | let browser = await puppeteer.launch(); 6 | let page = await browser.newPage(); 7 | const client = await page.target().createCDPSession(); 8 | await client.send('Network.enable'); 9 | await client.send('Network.emulateNetworkConditions', { 10 | offline: false, 11 | latency: 200, // ms 12 | downloadThroughput: 780 * 1024 / 8, // 780 kb/s 13 | uploadThroughput: 330 * 1024 / 8, // 330 kb/s 14 | }); 15 | await client.send('Emulation.setCPUThrottlingRate', { rate: 4 }); 16 | console.log(await testPage(page, client)); 17 | await browser.close(); 18 | })(); 19 | -------------------------------------------------------------------------------- /3/testPage.js: -------------------------------------------------------------------------------- 1 | const { getTimeFromPerformanceMetrics, getCustomMetric } = require('./helpers'); 2 | 3 | async function testPage(page) { 4 | const client = await page.target().createCDPSession(); 5 | await client.send('Performance.enable'); 6 | 7 | const listLinksSpa = getCustomMetric(page, 'listLinksSpa'); 8 | 9 | await page.goto('http://localhost:8080'); 10 | 11 | const performanceMetrics = await client.send('Performance.getMetrics'); 12 | const navigationStart = getTimeFromPerformanceMetrics( 13 | performanceMetrics, 14 | 'NavigationStart' 15 | ); 16 | 17 | return { 18 | listLinksSpa: (await listLinksSpa) - navigationStart, 19 | }; 20 | } 21 | 22 | module.exports = testPage; 23 | -------------------------------------------------------------------------------- /2/testPage.js: -------------------------------------------------------------------------------- 1 | const { 2 | getTimeFromPerformanceMetrics, 3 | extractDataFromPerformanceMetrics, 4 | } = require('./helpers'); 5 | 6 | async function testPage(page) { 7 | const client = await page.target().createCDPSession(); 8 | await client.send('Performance.enable'); 9 | 10 | await page.goto('http://localhost:8080'); 11 | 12 | let firstMeaningfulPaint = 0; 13 | let performanceMetrics; 14 | while (firstMeaningfulPaint === 0) { 15 | await page.waitFor(300); 16 | performanceMetrics = await client.send('Performance.getMetrics'); 17 | firstMeaningfulPaint = getTimeFromPerformanceMetrics( 18 | performanceMetrics, 19 | 'FirstMeaningfulPaint' 20 | ); 21 | } 22 | 23 | return extractDataFromPerformanceMetrics( 24 | performanceMetrics, 25 | 'FirstMeaningfulPaint' 26 | ); 27 | } 28 | 29 | module.exports = testPage; 30 | -------------------------------------------------------------------------------- /4/testPage.js: -------------------------------------------------------------------------------- 1 | const { 2 | getTimeFromPerformanceMetrics, 3 | extractDataFromTracing, 4 | } = require('./helpers'); 5 | 6 | async function testPage(page) { 7 | const client = await page.target().createCDPSession(); 8 | await client.send('Performance.enable'); 9 | await page.tracing.start({ path: './trace.json' }); 10 | 11 | await page.goto('http://localhost:8080'); 12 | 13 | await page.tracing.stop(); 14 | const cssTracing = await extractDataFromTracing( 15 | './trace.json', 16 | 'common.3a2d55439989ceade22e.css' 17 | ); 18 | 19 | const performanceMetrics = await client.send('Performance.getMetrics'); 20 | const navigationStart = getTimeFromPerformanceMetrics( 21 | performanceMetrics, 22 | 'NavigationStart' 23 | ); 24 | 25 | return { 26 | cssStart: cssTracing.start - navigationStart, 27 | cssEnd: cssTracing.end - navigationStart, 28 | }; 29 | } 30 | 31 | module.exports = testPage; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # test-website-performance-with-puppeteer 2 | Code used in article https://michaljanaszek.com/blog/test-website-performance-with-puppeteer 3 | 4 | 5 | ## Build Setup 6 | 7 | ### Build [vue hn applictaion](https://vue-hn.now.sh) 8 | 9 | Clone https://github.com/Everettss/vue-hackernews-2.0 and inside that project run: 10 | 11 | ``` bash 12 | # install dependencies 13 | npm install 14 | 15 | # build for production 16 | npm run build 17 | 18 | # serve in production mode at `localhost:8080` 19 | npm start 20 | ``` 21 | This fork of `vue-hackernews-2.0` enables ServiceWorker on localhost and adds `console.timeStamp` in `src/views/ItemList.vue` 22 | 23 | ### Run tests 24 | 25 | 26 | ``` bash 27 | # install dependencies 28 | npm install 29 | ``` 30 | 31 | If you have running `vue-hackernews-2.0` on `localhost:8080` run test (for example): 32 | 33 | 34 | ``` bash 35 | # run Navigation Timing API test 36 | # https://michaljanaszek.com/blog/test-website-performance-with-puppeteer#navigationTimingAPI 37 | node 1/index.js 38 | ``` 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michał Janaszek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /6/index.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const testPage = require('./testPage'); 3 | 4 | (async () => { 5 | let browser = await puppeteer.launch(); 6 | let page = await browser.newPage(); 7 | let client = await page.target().createCDPSession(); 8 | console.log(await testPage(page, client)); // first enter 9 | console.log(await testPage(page, client)); // second enter with cache and sw 10 | await browser.close(); 11 | 12 | browser = await puppeteer.launch(); 13 | page = await browser.newPage(); 14 | client = await page.target().createCDPSession(); 15 | await testPage(page, client); // only for creating fresh instance 16 | await client.send('ServiceWorker.enable'); 17 | await client.send('ServiceWorker.unregister', { 18 | scopeURL: 'http://localhost:8080/', 19 | }); 20 | console.log(await testPage(page, client)); // second enter only with cache 21 | await browser.close(); 22 | 23 | browser = await puppeteer.launch(); 24 | page = await browser.newPage(); 25 | client = await page.target().createCDPSession(); 26 | await testPage(page, client); // only for creating fresh instance 27 | await client.send('Network.enable'); 28 | await client.send('Network.clearBrowserCache'); 29 | console.log(await testPage(page, client)); // second enter only with sw 30 | await browser.close(); 31 | })(); 32 | -------------------------------------------------------------------------------- /4/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const getTimeFromPerformanceMetrics = (metrics, name) => 4 | metrics.metrics.find(x => x.name === name).value * 1000; 5 | 6 | const extractDataFromTracing = (path, name) => 7 | new Promise(resolve => { 8 | fs.readFile(path, (err, data) => { 9 | const tracing = JSON.parse(data); 10 | 11 | const resourceTracings = tracing.traceEvents.filter( 12 | x => 13 | x.cat === 'devtools.timeline' && 14 | typeof x.args.data !== 'undefined' && 15 | typeof x.args.data.url !== 'undefined' && 16 | x.args.data.url.endsWith(name) 17 | ); 18 | const resourceTracingSendRequest = resourceTracings.find( 19 | x => x.name === 'ResourceSendRequest' 20 | ); 21 | const resourceId = resourceTracingSendRequest.args.data.requestId; 22 | const resourceTracingEnd = tracing.traceEvents.filter( 23 | x => 24 | x.cat === 'devtools.timeline' && 25 | typeof x.args.data !== 'undefined' && 26 | typeof x.args.data.requestId !== 'undefined' && 27 | x.args.data.requestId === resourceId 28 | ); 29 | const resourceTracingStartTime = resourceTracingSendRequest.ts / 1000; 30 | const resourceTracingEndTime = 31 | resourceTracingEnd.find(x => x.name === 'ResourceFinish').ts / 1000; 32 | 33 | fs.unlink(path, () => { 34 | resolve({ 35 | start: resourceTracingStartTime, 36 | end: resourceTracingEndTime, 37 | }); 38 | }); 39 | }); 40 | }); 41 | 42 | module.exports = { 43 | getTimeFromPerformanceMetrics, 44 | extractDataFromTracing, 45 | }; 46 | -------------------------------------------------------------------------------- /5/testPage.js: -------------------------------------------------------------------------------- 1 | const { 2 | extractDataFromPerformanceTiming, 3 | getTimeFromPerformanceMetrics, 4 | extractDataFromPerformanceMetrics, 5 | getCustomMetric, 6 | extractDataFromTracing, 7 | } = require('./helpers'); 8 | 9 | async function testPage(page, client) { 10 | await client.send('Performance.enable'); 11 | 12 | const listLinksSpa = getCustomMetric(page, 'listLinksSpa'); 13 | await page.tracing.start({ path: './trace.json' }); 14 | 15 | await page.goto('http://localhost:8080'); 16 | 17 | const performanceTiming = JSON.parse( 18 | await page.evaluate(() => JSON.stringify(window.performance.timing)) 19 | ); 20 | 21 | let firstMeaningfulPaint = 0; 22 | let performanceMetrics; 23 | while (firstMeaningfulPaint === 0) { 24 | await page.waitFor(300); 25 | performanceMetrics = await client.send('Performance.getMetrics'); 26 | firstMeaningfulPaint = getTimeFromPerformanceMetrics( 27 | performanceMetrics, 28 | 'FirstMeaningfulPaint' 29 | ); 30 | } 31 | 32 | await page.tracing.stop(); 33 | 34 | const navigationStart = getTimeFromPerformanceMetrics( 35 | performanceMetrics, 36 | 'NavigationStart' 37 | ); 38 | 39 | const cssTracing = await extractDataFromTracing( 40 | './trace.json', 41 | 'common.3a2d55439989ceade22e.css' 42 | ); 43 | 44 | return { 45 | cssStart: cssTracing.start - navigationStart, 46 | cssEnd: cssTracing.end - navigationStart, 47 | listLinksSpa: (await listLinksSpa) - navigationStart, 48 | ...extractDataFromPerformanceMetrics( 49 | performanceMetrics, 50 | 'FirstMeaningfulPaint' 51 | ), 52 | ...extractDataFromPerformanceTiming( 53 | performanceTiming, 54 | 'responseEnd', 55 | 'domInteractive', 56 | 'domContentLoadedEventEnd', 57 | 'loadEventEnd' 58 | ), 59 | }; 60 | } 61 | 62 | module.exports = testPage; 63 | -------------------------------------------------------------------------------- /6/testPage.js: -------------------------------------------------------------------------------- 1 | const { 2 | extractDataFromPerformanceTiming, 3 | getTimeFromPerformanceMetrics, 4 | extractDataFromPerformanceMetrics, 5 | getCustomMetric, 6 | extractDataFromTracing, 7 | } = require('./helpers'); 8 | 9 | async function testPage(page, client) { 10 | await client.send('Performance.enable'); 11 | 12 | const listLinksSpa = getCustomMetric(page, 'listLinksSpa'); 13 | await page.tracing.start({ path: './trace.json' }); 14 | 15 | await page.goto('http://localhost:8080'); 16 | 17 | const performanceTiming = JSON.parse( 18 | await page.evaluate(() => JSON.stringify(window.performance.timing)) 19 | ); 20 | 21 | let firstMeaningfulPaint = 0; 22 | let performanceMetrics; 23 | while (firstMeaningfulPaint === 0) { 24 | await page.waitFor(300); 25 | performanceMetrics = await client.send('Performance.getMetrics'); 26 | firstMeaningfulPaint = getTimeFromPerformanceMetrics( 27 | performanceMetrics, 28 | 'FirstMeaningfulPaint' 29 | ); 30 | } 31 | 32 | await page.tracing.stop(); 33 | 34 | const navigationStart = getTimeFromPerformanceMetrics( 35 | performanceMetrics, 36 | 'NavigationStart' 37 | ); 38 | 39 | const cssTracing = await extractDataFromTracing( 40 | './trace.json', 41 | 'common.3a2d55439989ceade22e.css' 42 | ); 43 | 44 | return { 45 | cssStart: cssTracing.start - navigationStart, 46 | cssEnd: cssTracing.end - navigationStart, 47 | listLinksSpa: (await listLinksSpa) - navigationStart, 48 | ...extractDataFromPerformanceMetrics( 49 | performanceMetrics, 50 | 'FirstMeaningfulPaint' 51 | ), 52 | ...extractDataFromPerformanceTiming( 53 | performanceTiming, 54 | 'responseEnd', 55 | 'domInteractive', 56 | 'domContentLoadedEventEnd', 57 | 'loadEventEnd' 58 | ), 59 | }; 60 | } 61 | 62 | module.exports = testPage; 63 | -------------------------------------------------------------------------------- /results/test/testPage.js: -------------------------------------------------------------------------------- 1 | const { 2 | extractDataFromPerformanceTiming, 3 | getTimeFromPerformanceMetrics, 4 | extractDataFromPerformanceMetrics, 5 | getCustomMetric, 6 | extractDataFromTracing, 7 | } = require('./helpers'); 8 | 9 | async function testPage(page, client) { 10 | await client.send('Performance.enable'); 11 | 12 | const listLinksSpa = getCustomMetric(page, 'listLinksSpa'); 13 | await page.tracing.start({ path: './trace.json' }); 14 | 15 | await page.goto('http://localhost:8080'); 16 | 17 | const performanceTiming = JSON.parse( 18 | await page.evaluate(() => JSON.stringify(window.performance.timing)) 19 | ); 20 | 21 | let firstMeaningfulPaint = 0; 22 | let performanceMetrics; 23 | while (firstMeaningfulPaint === 0) { 24 | await page.waitFor(300); 25 | performanceMetrics = await client.send('Performance.getMetrics'); 26 | firstMeaningfulPaint = getTimeFromPerformanceMetrics( 27 | performanceMetrics, 28 | 'FirstMeaningfulPaint' 29 | ); 30 | } 31 | 32 | await page.tracing.stop(); 33 | 34 | const navigationStart = getTimeFromPerformanceMetrics( 35 | performanceMetrics, 36 | 'NavigationStart' 37 | ); 38 | 39 | const cssTracing = await extractDataFromTracing( 40 | './trace.json', 41 | 'common.3a2d55439989ceade22e.css' 42 | ); 43 | 44 | return { 45 | cssStart: cssTracing.start - navigationStart, 46 | cssEnd: cssTracing.end - navigationStart, 47 | listLinksSpa: (await listLinksSpa) - navigationStart, 48 | ...extractDataFromPerformanceMetrics( 49 | performanceMetrics, 50 | 'FirstMeaningfulPaint' 51 | ), 52 | ...extractDataFromPerformanceTiming( 53 | performanceTiming, 54 | 'responseEnd', 55 | 'domInteractive', 56 | 'domContentLoadedEventEnd', 57 | 'loadEventEnd' 58 | ), 59 | }; 60 | } 61 | 62 | module.exports = testPage; 63 | -------------------------------------------------------------------------------- /5/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const extractDataFromPerformanceTiming = (timing, ...dataNames) => { 4 | const navigationStart = timing.navigationStart; 5 | 6 | const extractedData = {}; 7 | dataNames.forEach(name => { 8 | extractedData[name] = timing[name] - navigationStart; 9 | }); 10 | 11 | return extractedData; 12 | }; 13 | 14 | const getTimeFromPerformanceMetrics = (metrics, name) => 15 | metrics.metrics.find(x => x.name === name).value * 1000; 16 | 17 | const extractDataFromPerformanceMetrics = (metrics, ...dataNames) => { 18 | const navigationStart = getTimeFromPerformanceMetrics( 19 | metrics, 20 | 'NavigationStart' 21 | ); 22 | 23 | const extractedData = {}; 24 | dataNames.forEach(name => { 25 | extractedData[name] = 26 | getTimeFromPerformanceMetrics(metrics, name) - navigationStart; 27 | }); 28 | 29 | return extractedData; 30 | }; 31 | 32 | function getCustomMetric(page, name) { 33 | return new Promise(resolve => 34 | page.on('metrics', ({ title, metrics }) => { 35 | if (name === title) { 36 | resolve(metrics.Timestamp * 1000); 37 | } 38 | }) 39 | ); 40 | } 41 | 42 | const extractDataFromTracing = (path, name) => 43 | new Promise(resolve => { 44 | fs.readFile(path, (err, data) => { 45 | const tracing = JSON.parse(data); 46 | 47 | const resourceTracings = tracing.traceEvents.filter( 48 | x => 49 | x.cat === 'devtools.timeline' && 50 | typeof x.args.data !== 'undefined' && 51 | typeof x.args.data.url !== 'undefined' && 52 | x.args.data.url.endsWith(name) 53 | ); 54 | const resourceTracingSendRequest = resourceTracings.find( 55 | x => x.name === 'ResourceSendRequest' 56 | ); 57 | const resourceId = resourceTracingSendRequest.args.data.requestId; 58 | const resourceTracingEnd = tracing.traceEvents.filter( 59 | x => 60 | x.cat === 'devtools.timeline' && 61 | typeof x.args.data !== 'undefined' && 62 | typeof x.args.data.requestId !== 'undefined' && 63 | x.args.data.requestId === resourceId 64 | ); 65 | const resourceTracingStartTime = resourceTracingSendRequest.ts / 1000; 66 | const resourceTracingEndTime = 67 | resourceTracingEnd.find(x => x.name === 'ResourceFinish').ts / 1000; 68 | 69 | fs.unlink(path, () => { 70 | resolve({ 71 | start: resourceTracingStartTime, 72 | end: resourceTracingEndTime, 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | module.exports = { 79 | extractDataFromPerformanceTiming, 80 | getTimeFromPerformanceMetrics, 81 | extractDataFromPerformanceMetrics, 82 | getCustomMetric, 83 | extractDataFromTracing, 84 | }; 85 | -------------------------------------------------------------------------------- /6/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const extractDataFromPerformanceTiming = (timing, ...dataNames) => { 4 | const navigationStart = timing.navigationStart; 5 | 6 | const extractedData = {}; 7 | dataNames.forEach(name => { 8 | extractedData[name] = timing[name] - navigationStart; 9 | }); 10 | 11 | return extractedData; 12 | }; 13 | 14 | const getTimeFromPerformanceMetrics = (metrics, name) => 15 | metrics.metrics.find(x => x.name === name).value * 1000; 16 | 17 | const extractDataFromPerformanceMetrics = (metrics, ...dataNames) => { 18 | const navigationStart = getTimeFromPerformanceMetrics( 19 | metrics, 20 | 'NavigationStart' 21 | ); 22 | 23 | const extractedData = {}; 24 | dataNames.forEach(name => { 25 | extractedData[name] = 26 | getTimeFromPerformanceMetrics(metrics, name) - navigationStart; 27 | }); 28 | 29 | return extractedData; 30 | }; 31 | 32 | function getCustomMetric(page, name) { 33 | return new Promise(resolve => 34 | page.on('metrics', ({ title, metrics }) => { 35 | if (name === title) { 36 | resolve(metrics.Timestamp * 1000); 37 | } 38 | }) 39 | ); 40 | } 41 | 42 | const extractDataFromTracing = (path, name) => 43 | new Promise(resolve => { 44 | fs.readFile(path, (err, data) => { 45 | const tracing = JSON.parse(data); 46 | 47 | const resourceTracings = tracing.traceEvents.filter( 48 | x => 49 | x.cat === 'devtools.timeline' && 50 | typeof x.args.data !== 'undefined' && 51 | typeof x.args.data.url !== 'undefined' && 52 | x.args.data.url.endsWith(name) 53 | ); 54 | const resourceTracingSendRequest = resourceTracings.find( 55 | x => x.name === 'ResourceSendRequest' 56 | ); 57 | const resourceId = resourceTracingSendRequest.args.data.requestId; 58 | const resourceTracingEnd = tracing.traceEvents.filter( 59 | x => 60 | x.cat === 'devtools.timeline' && 61 | typeof x.args.data !== 'undefined' && 62 | typeof x.args.data.requestId !== 'undefined' && 63 | x.args.data.requestId === resourceId 64 | ); 65 | const resourceTracingStartTime = resourceTracingSendRequest.ts / 1000; 66 | const resourceTracingEndTime = 67 | resourceTracingEnd.find(x => x.name === 'ResourceFinish').ts / 1000; 68 | 69 | fs.unlink(path, () => { 70 | resolve({ 71 | start: resourceTracingStartTime, 72 | end: resourceTracingEndTime, 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | module.exports = { 79 | extractDataFromPerformanceTiming, 80 | getTimeFromPerformanceMetrics, 81 | extractDataFromPerformanceMetrics, 82 | getCustomMetric, 83 | extractDataFromTracing, 84 | }; 85 | -------------------------------------------------------------------------------- /results/test/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const extractDataFromPerformanceTiming = (timing, ...dataNames) => { 4 | const navigationStart = timing.navigationStart; 5 | 6 | const extractedData = {}; 7 | dataNames.forEach(name => { 8 | extractedData[name] = timing[name] - navigationStart; 9 | }); 10 | 11 | return extractedData; 12 | }; 13 | 14 | const getTimeFromPerformanceMetrics = (metrics, name) => 15 | metrics.metrics.find(x => x.name === name).value * 1000; 16 | 17 | const extractDataFromPerformanceMetrics = (metrics, ...dataNames) => { 18 | const navigationStart = getTimeFromPerformanceMetrics( 19 | metrics, 20 | 'NavigationStart' 21 | ); 22 | 23 | const extractedData = {}; 24 | dataNames.forEach(name => { 25 | extractedData[name] = 26 | getTimeFromPerformanceMetrics(metrics, name) - navigationStart; 27 | }); 28 | 29 | return extractedData; 30 | }; 31 | 32 | function getCustomMetric(page, name) { 33 | return new Promise(resolve => 34 | page.on('metrics', ({ title, metrics }) => { 35 | if (name === title) { 36 | resolve(metrics.Timestamp * 1000); 37 | } 38 | }) 39 | ); 40 | } 41 | 42 | const extractDataFromTracing = (path, name) => 43 | new Promise(resolve => { 44 | fs.readFile(path, (err, data) => { 45 | const tracing = JSON.parse(data); 46 | 47 | const resourceTracings = tracing.traceEvents.filter( 48 | x => 49 | x.cat === 'devtools.timeline' && 50 | typeof x.args.data !== 'undefined' && 51 | typeof x.args.data.url !== 'undefined' && 52 | x.args.data.url.endsWith(name) 53 | ); 54 | const resourceTracingSendRequest = resourceTracings.find( 55 | x => x.name === 'ResourceSendRequest' 56 | ); 57 | const resourceId = resourceTracingSendRequest.args.data.requestId; 58 | const resourceTracingEnd = tracing.traceEvents.filter( 59 | x => 60 | x.cat === 'devtools.timeline' && 61 | typeof x.args.data !== 'undefined' && 62 | typeof x.args.data.requestId !== 'undefined' && 63 | x.args.data.requestId === resourceId 64 | ); 65 | const resourceTracingStartTime = resourceTracingSendRequest.ts / 1000; 66 | const resourceTracingEndTime = 67 | resourceTracingEnd.find(x => x.name === 'ResourceFinish').ts / 1000; 68 | 69 | fs.unlink(path, () => { 70 | resolve({ 71 | start: resourceTracingStartTime, 72 | end: resourceTracingEndTime, 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | module.exports = { 79 | extractDataFromPerformanceTiming, 80 | getTimeFromPerformanceMetrics, 81 | extractDataFromPerformanceMetrics, 82 | getCustomMetric, 83 | extractDataFromTracing, 84 | }; 85 | -------------------------------------------------------------------------------- /results/test/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { promisify } = require('util'); 3 | 4 | const puppeteer = require('puppeteer'); 5 | const testPage = require('./testPage'); 6 | const ProgressBar = require('progress'); 7 | 8 | const convertResultsToArr = (res, stage) => { 9 | const arr = []; 10 | 11 | for (let [variable, value] of Object.entries(res)) { 12 | arr.push({ 13 | variable, 14 | value, 15 | stage, 16 | }); 17 | } 18 | return arr; 19 | }; 20 | 21 | const test = async (runs, emulateConditions, filePath) => { 22 | const bar = new ProgressBar(':bar :percent :current/:total remain :eta s', { 23 | total: runs * 6, 24 | }); 25 | 26 | await promisify(fs.writeFile)(filePath, JSON.stringify([])); 27 | for (let i = 0; i < runs; i++) { 28 | let res = []; 29 | 30 | // ------- first browser run --------- 31 | let browser = await puppeteer.launch(); 32 | let page = await browser.newPage(); 33 | let client = await page.target().createCDPSession(); 34 | await emulateConditions(client); 35 | 36 | // first enter test 37 | res = [ 38 | ...res, 39 | ...convertResultsToArr(await testPage(page, client), 'firstEnter'), 40 | ]; 41 | 42 | bar.tick(); 43 | await page.close(); 44 | 45 | page = await browser.newPage(); 46 | client = await page.target().createCDPSession(); 47 | await emulateConditions(client); 48 | 49 | // second enter test with ServiceWorker and cache 50 | res = [ 51 | ...res, 52 | ...convertResultsToArr(await testPage(page, client), 'swAndCache'), 53 | ]; 54 | 55 | bar.tick(); 56 | await browser.close(); 57 | 58 | // ------- second browser run --------- 59 | browser = await puppeteer.launch(); 60 | page = await browser.newPage(); 61 | client = await page.target().createCDPSession(); 62 | 63 | // first enter only for hydration ServiceWorker and cache 64 | await testPage(page, client); 65 | 66 | bar.tick(); 67 | await page.close(); 68 | 69 | page = await browser.newPage(); 70 | client = await page.target().createCDPSession(); 71 | await emulateConditions(client); 72 | 73 | await client.send('ServiceWorker.enable'); 74 | await client.send('ServiceWorker.unregister', { 75 | scopeURL: 'http://localhost:8080/', 76 | }); 77 | 78 | // second enter test with cache 79 | res = [ 80 | ...res, 81 | ...convertResultsToArr(await testPage(page, client), 'cache'), 82 | ]; 83 | bar.tick(); 84 | await browser.close(); 85 | 86 | // ------- third browser run --------- 87 | browser = await puppeteer.launch(); 88 | page = await browser.newPage(); 89 | client = await page.target().createCDPSession(); 90 | 91 | // first enter only for hydration ServiceWorker and cache 92 | await testPage(page, client); 93 | 94 | bar.tick(); 95 | await page.close(); 96 | 97 | page = await browser.newPage(); 98 | client = await page.target().createCDPSession(); 99 | await emulateConditions(client); 100 | await client.send('Network.clearBrowserCache'); 101 | 102 | // second enter test with ServiceWorker 103 | res = [...res, ...convertResultsToArr(await testPage(page, client), 'sw')]; 104 | 105 | bar.tick(); 106 | await browser.close(); 107 | 108 | const file = JSON.parse(await promisify(fs.readFile)(filePath)); 109 | await promisify(fs.writeFile)(filePath, JSON.stringify([...file, ...res])); 110 | } 111 | }; 112 | 113 | module.exports = test; 114 | --------------------------------------------------------------------------------