-
Notifications
You must be signed in to change notification settings - Fork 9.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tests: add test trace creator #6196
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,10 +7,10 @@ | |
|
||
const UnusedImages = | ||
require('../../../audits/byte-efficiency/offscreen-images.js'); | ||
const NetworkNode = require('../../../lib/dependency-graph/network-node'); | ||
const CPUNode = require('../../../lib/dependency-graph/cpu-node'); | ||
const assert = require('assert'); | ||
const LHError = require('../../../lib/lh-error'); | ||
const Runner = require('../../../runner.js'); | ||
const createTestTrace = require('../../create-test-trace.js'); | ||
const networkRecordsToDevtoolsLog = require('../../network-records-to-devtools-log.js'); | ||
|
||
/* eslint-env jest */ | ||
function generateRecord(resourceSizeInKb, startTime = 0, mimeType = 'image/png') { | ||
|
@@ -45,26 +45,6 @@ function generateImage(size, coords, networkRecord, src = 'https://google.com/lo | |
return image; | ||
} | ||
|
||
function generateInteractiveFunc(desiredTimeInSeconds) { | ||
return () => Promise.resolve({ | ||
timestamp: desiredTimeInSeconds * 1000000, | ||
}); | ||
} | ||
|
||
function generateInteractiveFuncError() { | ||
return () => Promise.reject( | ||
new LHError(LHError.errors.NO_TTI_NETWORK_IDLE_PERIOD) | ||
); | ||
} | ||
|
||
function generateTraceOfTab(desiredTimeInSeconds) { | ||
return () => Promise.resolve({ | ||
timestamps: { | ||
traceEnd: desiredTimeInSeconds * 1000000, | ||
}, | ||
}); | ||
} | ||
|
||
describe('OffscreenImages audit', () => { | ||
let context; | ||
const DEFAULT_DIMENSIONS = {innerWidth: 1920, innerHeight: 1080}; | ||
|
@@ -74,40 +54,45 @@ describe('OffscreenImages audit', () => { | |
}); | ||
|
||
it('handles images without network record', () => { | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(100, 100), [0, 0]), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 0); | ||
}); | ||
}); | ||
|
||
it('does not find used images', () => { | ||
const urlB = 'https://google.com/logo2.png'; | ||
const urlC = 'data:image/jpeg;base64,foobar'; | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(200, 200), [0, 0], generateRecord(100)), | ||
generateImage(generateSize(100, 100), [0, 1080], generateRecord(100), urlB), | ||
generateImage(generateSize(400, 400), [1720, 1080], generateRecord(3), urlC), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 0); | ||
}); | ||
}); | ||
|
||
it('finds unused images', () => { | ||
const url = s => `https://google.com/logo${s}.png`; | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
// offscreen to the right | ||
|
@@ -121,189 +106,204 @@ describe('OffscreenImages audit', () => { | |
// half offscreen to the top, should not warn | ||
generateImage(generateSize(1000, 1000), [0, -500], generateRecord(100), url('E')), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 4); | ||
}); | ||
}); | ||
|
||
it('finds images with 0 area', () => { | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], generateRecord(100)), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 1); | ||
assert.equal(auditResult.items[0].wastedBytes, 100 * 1024); | ||
}); | ||
}); | ||
|
||
it('de-dupes images', () => { | ||
const urlB = 'https://google.com/logo2.png'; | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(50, 50), [0, 0], generateRecord(50)), | ||
generateImage(generateSize(1000, 1000), [1000, 1000], generateRecord(50)), | ||
generateImage(generateSize(50, 50), [0, 1500], generateRecord(200), urlB), | ||
generateImage(generateSize(400, 400), [0, 1500], generateRecord(90), urlB), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 1); | ||
}); | ||
}); | ||
|
||
it('disregards images loaded after TTI', () => { | ||
return UnusedImages.audit_({ | ||
const topLevelTasks = [{ts: 1900, duration: 100}]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
// offscreen to the right | ||
generateImage(generateSize(200, 200), [3000, 0], generateRecord(100, 3)), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFunc(2), | ||
}, [], context, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 0); | ||
}); | ||
}); | ||
|
||
it('disregards images loaded after Trace End when interactive throws error', () => { | ||
return UnusedImages.audit_({ | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
// offscreen to the right | ||
generateImage(generateSize(200, 200), [3000, 0], generateRecord(100, 3)), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({traceEnd: 2000})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFuncError(), | ||
requestTraceOfTab: generateTraceOfTab(2), | ||
}, [], context, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 0); | ||
}); | ||
}); | ||
|
||
it('finds images loaded before Trace End when TTI when interactive throws error', () => { | ||
return UnusedImages.audit_({ | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
// offscreen to the right | ||
generateImage(generateSize(100, 100), [0, 2000], generateRecord(100)), | ||
], | ||
traces: {}, | ||
traces: {defaultPass: createTestTrace({traceEnd: 2000})}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFuncError(), | ||
requestTraceOfTab: generateTraceOfTab(2), | ||
}, [], context, [], context).then(auditResult => { | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 1); | ||
}); | ||
}); | ||
|
||
it('disregards images loaded after last long task (Lantern)', () => { | ||
context = {settings: {throttlingMethod: 'simulate'}}; | ||
const recordA = {url: 'a', resourceSize: 100 * 1024, requestId: 'a'}; | ||
const recordB = {url: 'b', resourceSize: 100 * 1024, requestId: 'b'}; | ||
|
||
const networkA = new NetworkNode(recordA); | ||
const networkB = new NetworkNode(recordB); | ||
const cpu = new CPUNode({}, []); | ||
const timings = new Map([ | ||
[networkA, {startTime: 1000}], | ||
[networkB, {startTime: 2000}], | ||
[cpu, {startTime: 1975, endTime: 2025, duration: 50}], | ||
]); | ||
|
||
return UnusedImages.audit_({ | ||
const wastedSize = 100 * 1024; | ||
const recordA = { | ||
url: 'https://example.com/a', | ||
resourceSize: wastedSize, | ||
requestId: 'a', | ||
startTime: 1, | ||
priority: 'High', | ||
timing: {receiveHeadersEnd: 1.25}, | ||
}; | ||
const recordB = { | ||
url: 'https://example.com/b', | ||
resourceSize: wastedSize, | ||
requestId: 'b', | ||
startTime: 2.25, | ||
priority: 'High', | ||
timing: {receiveHeadersEnd: 2.5}, | ||
}; | ||
const devtoolsLog = networkRecordsToDevtoolsLog([recordA, recordB]); | ||
|
||
const topLevelTasks = [ | ||
{ts: 1975, duration: 50}, | ||
]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], recordA, recordA.url), | ||
generateImage(generateSize(200, 200), [3000, 0], recordB, recordB.url), | ||
], | ||
traces: {}, | ||
devtoolsLogs: {}, | ||
requestInteractive: async () => ({pessimisticEstimate: {nodeTimings: timings}}), | ||
}, [], context, [], context).then(auditResult => { | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {defaultPass: devtoolsLog}, | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 1); | ||
assert.equal(auditResult.items[0].url, 'a'); | ||
assert.equal(auditResult.items[0].url, recordA.url); | ||
assert.equal(auditResult.items[0].wastedBytes, wastedSize); | ||
}); | ||
}); | ||
|
||
it('finds images loaded before last long task (Lantern)', () => { | ||
context = {settings: {throttlingMethod: 'simulate'}}; | ||
const recordA = {url: 'a', resourceSize: 100 * 1024, requestId: 'a'}; | ||
const recordB = {url: 'b', resourceSize: 100 * 1024, requestId: 'b'}; | ||
|
||
const networkA = new NetworkNode(recordA); | ||
const networkB = new NetworkNode(recordB); | ||
const cpu = new CPUNode({}, []); | ||
const timings = new Map([ | ||
[networkA, {startTime: 1000}], | ||
[networkB, {startTime: 1500}], | ||
[cpu, {startTime: 1975, endTime: 2025, duration: 50}], | ||
]); | ||
|
||
return UnusedImages.audit_({ | ||
const wastedSize = 100 * 1024; | ||
const recordA = { | ||
url: 'https://example.com/a', | ||
resourceSize: wastedSize, | ||
requestId: 'a', | ||
startTime: 1, | ||
priority: 'High', | ||
timing: {receiveHeadersEnd: 1.25}, | ||
}; | ||
const recordB = { | ||
url: 'https://example.com/b', | ||
resourceSize: wastedSize, | ||
requestId: 'b', | ||
startTime: 1.25, | ||
priority: 'High', | ||
timing: {receiveHeadersEnd: 1.5}, | ||
}; | ||
const devtoolsLog = networkRecordsToDevtoolsLog([recordA, recordB]); | ||
|
||
// Enough tasks to spread out graph. | ||
const topLevelTasks = [ | ||
{ts: 1000, duration: 10}, | ||
{ts: 1050, duration: 10}, | ||
{ts: 1975, duration: 50}, | ||
]; | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], recordA, recordA.url), | ||
generateImage(generateSize(200, 200), [3000, 0], recordB, recordB.url), | ||
], | ||
traces: {}, | ||
devtoolsLogs: {}, | ||
requestInteractive: async () => ({pessimisticEstimate: {nodeTimings: timings}}), | ||
requestTraceOfTab: generateTraceOfTab(2), | ||
}, [], context, [], context).then(auditResult => { | ||
traces: {defaultPass: createTestTrace({topLevelTasks})}, | ||
devtoolsLogs: {defaultPass: devtoolsLog}, | ||
}); | ||
|
||
return UnusedImages.audit_(artifacts, [], context).then(auditResult => { | ||
assert.equal(auditResult.items.length, 2); | ||
assert.equal(auditResult.items[0].url, 'a'); | ||
assert.equal(auditResult.items[1].url, 'b'); | ||
assert.equal(auditResult.items[0].url, recordA.url); | ||
assert.equal(auditResult.items[0].wastedBytes, wastedSize); | ||
assert.equal(auditResult.items[1].url, recordB.url); | ||
assert.equal(auditResult.items[1].wastedBytes, wastedSize); | ||
}); | ||
}); | ||
|
||
it('rethrow error when interactive throws error in Lantern', async () => { | ||
context = {settings: {throttlingMethod: 'simulate'}}; | ||
try { | ||
await UnusedImages.audit_({ | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], generateRecord(100, 3), 'a'), | ||
generateImage(generateSize(200, 200), [3000, 0], generateRecord(100, 4), 'b'), | ||
], | ||
traces: {}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFuncError(), | ||
requestTraceOfTab: generateTraceOfTab(2), | ||
}, [], context, [], context); | ||
} catch (err) { | ||
return; | ||
} | ||
assert.ok(false); | ||
}); | ||
const artifacts = Object.assign(Runner.instantiateComputedArtifacts(), { | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], generateRecord(100, 3), 'a'), | ||
generateImage(generateSize(200, 200), [3000, 0], generateRecord(100, 4), 'b'), | ||
], | ||
traces: {defaultPass: createTestTrace({traceEnd: 2000})}, | ||
devtoolsLogs: {}, | ||
}); | ||
|
||
it('finds images loaded before Trace End when interactive throws error (Lantern)', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as far as I can tell this test was identical to the above. The start time of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I think this was very recently added when I had @exterkamp keep fiddling with the tests, probably got duped in the shuffle sorry! |
||
context = {settings: {throttlingMethod: 'simulate'}}; | ||
try { | ||
await UnusedImages.audit_({ | ||
ViewportDimensions: DEFAULT_DIMENSIONS, | ||
ImageUsage: [ | ||
generateImage(generateSize(0, 0), [0, 0], generateRecord(100, 1), 'a'), | ||
generateImage(generateSize(200, 200), [3000, 0], generateRecord(100, 4), 'b'), | ||
], | ||
traces: {}, | ||
devtoolsLogs: {}, | ||
requestInteractive: generateInteractiveFuncError(), | ||
requestTraceOfTab: generateTraceOfTab(2), | ||
}, [], context, [], context); | ||
await UnusedImages.audit_(artifacts, [], context); | ||
} catch (err) { | ||
assert.ok(err.message.includes('Did not provide necessary metric computation data')); | ||
return; | ||
} | ||
assert.ok(false); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@patrickhulce I don't entirely understand why I couldn't get the pessimistic graph to download these images in parallel without these extra 10ms tasks. Instead the second image and the CPU node always went in parallel (even if the two images had identical start and end times) and so it was failing to see both of these images before the last long task started.
Inserting these extra top level tasks fixed that and doesn't feel too bad (most traces have more than one top level task :), but I do worry about the fragility of my solution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't tell for 100% just from glancing, but it's probably because they were inferred to have come from the same connection, if different connectionIds are added and/or the timings show they happened in parallel then it might fix itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, didn't work. There's probably some subtlety with leaving out real connection timings or something, too. I'll just leave it like this for now and maybe we'll figure it out when a lantern change breaks this test :)