Skip to content
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

Merged
merged 1 commit into from
Oct 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 125 additions & 125 deletions lighthouse-core/test/audits/byte-efficiency/offscreen-images-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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};
Expand All @@ -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
Expand All @@ -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},
Copy link
Member Author

@brendankenny brendankenny Oct 9, 2018

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

Copy link
Collaborator

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

different connectionIds are added and/or the timings show they happened in parallel

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 :)

];
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 () => {
Copy link
Member Author

Choose a reason for hiding this comment

The 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 generateRecord of 'a' is the only thing that's different, but throttlingMethod: 'simulate' uses the network record timing information, not the timing info in the ImageUsage artifact.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
Expand Down
Loading