Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | 1x 1x 1x 1x 7x 7x 7x 7x 6x 6x 6x 1x 7x 4x 1x 1x 7x 2x 2x 1x 1x 2x 2x 2x 2x 7x 7x 5x 5x 2x 2x 2x 2x 2x 2x | import weightedMean from 'weighted-mean' import Diagnostics from 'lighthouse/lighthouse-core/audits/diagnostics' import MainThreadWorkBreakdown from 'lighthouse/lighthouse-core/audits/mainthread-work-breakdown' import Metrics from 'lighthouse/lighthouse-core/audits/metrics' import TTFBMetric from 'lighthouse/lighthouse-core/audits/time-to-first-byte' import { quantileAtValue } from './utils' import logger from '@wdio/logger' const log = logger('@wdio/devtools-service:Auditor') /** * metric scoring to calculate Lighthouse Performance Score * contains: [media, falloff] * see https://docs.google.com/spreadsheets/d/1_iDW0B870vZF6dAhf1Icdher0gX_KFxCd6Df0_fZ6do/edit#gid=283330180 */ const METRIC_SCORING = { FMP: [4000, 1600], // first meaningful paint FI: [10000, 1700], // first interactive CI: [10000, 1700], // consistently interactive SI: [5500, 1250], // speed index EIL: [100, 50] // estimated input latency } const WEIGHT_WITHIN_CATEGORY = { FMP: 5, FI: 5, CI: 5, SI: 1, EIL: 1 } const SHARED_AUDIT_CONTEXT = { settings: { throttlingMethod: 'devtools' }, LighthouseRunWarnings: false, computedCache: new Map() } export default class Auditor { constructor (traceLogs, devtoolsLogs) { this.devtoolsLogs = devtoolsLogs this.traceLogs = traceLogs this.url = traceLogs.pageUrl this.loaderId = traceLogs.loaderId } _audit (AUDIT, params = {}) { const auditContext = { options: { ...AUDIT.defaultOptions }, ...SHARED_AUDIT_CONTEXT } try { return AUDIT.audit({ traces: { defaultPass: this.traceLogs }, devtoolsLogs: { defaultPass: this.devtoolsLogs }, ...params }, auditContext) } catch (e) { log.error(e) return {} } } /** * an Auditor instance is created for every trace so provide an updateCommands * function to receive the latest performance metrics with the browser instance */ updateCommands (browser) { const commands = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter( fnName => fnName !== 'constructor' && fnName !== 'updateCommands' && !fnName.startsWith('_')) commands.forEach(fnName => browser.addCommand(fnName, ::this[fnName])) } async getMainThreadWorkBreakdown () { const result = await this._audit(MainThreadWorkBreakdown) return result.details.items.map( ({ group, duration }) => ({ group, duration }) ) } async getDiagnostics () { const result = await this._audit(Diagnostics) /** * return null if Audit fails */ if (!Object.prototype.hasOwnProperty.call(result, 'details')) { return null } return result.details.items[0] } async getMetrics () { const timeToFirstByte = await this._audit(TTFBMetric, { URL: this.url }) const result = await this._audit(Metrics) const metrics = result.details.items[0] || {} return { estimatedInputLatency: metrics.estimatedInputLatency, timeToFirstByte: Math.round(timeToFirstByte.rawValue, 10), domContentLoaded: metrics.observedDomContentLoaded, firstVisualChange: metrics.observedFirstVisualChange, firstPaint: metrics.observedFirstPaint, firstContentfulPaint: metrics.firstContentfulPaint, firstMeaningfulPaint: metrics.firstMeaningfulPaint, lastVisualChange: metrics.observedLastVisualChange, firstCPUIdle: metrics.firstCPUIdle, firstInteractive: metrics.interactive, load: metrics.observedLoad, speedIndex: metrics.speedIndex } } async getPerformanceScore () { const { firstMeaningfulPaint, firstCPUIdle, firstInteractive, speedIndex, estimatedInputLatency } = await this.getMetrics() /** * return null if any of the metrics could not bet calculated and * therefor are undefined */ if (!firstMeaningfulPaint || !firstCPUIdle || !firstInteractive || !speedIndex || !estimatedInputLatency) { log.info('One or multiple required metrics couldn\'t be found, setting performance score to: null') return null } const FMPScore = quantileAtValue(...METRIC_SCORING.FMP, firstMeaningfulPaint) const FIScore = quantileAtValue(...METRIC_SCORING.FI, firstCPUIdle) const CIScore = quantileAtValue(...METRIC_SCORING.CI, firstInteractive) const SIScore = quantileAtValue(...METRIC_SCORING.SI, speedIndex) const EILScore = quantileAtValue(...METRIC_SCORING.EIL, estimatedInputLatency) return weightedMean([ [FMPScore, WEIGHT_WITHIN_CATEGORY.FMP], [FIScore, WEIGHT_WITHIN_CATEGORY.FI], [CIScore, WEIGHT_WITHIN_CATEGORY.CI], [SIScore, WEIGHT_WITHIN_CATEGORY.SI], [EILScore, WEIGHT_WITHIN_CATEGORY.EIL] ]) } } |