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

feat(plugin-lighthouse): implement basic audit parsing #523

Merged
merged 14 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
37 changes: 35 additions & 2 deletions packages/plugin-lighthouse/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CliFlags } from 'lighthouse';
import { Audit } from '@code-pushup/models';
import { type CliFlags } from 'lighthouse';
import { Result } from 'lighthouse/types/lhr/audit-result';
import { Audit, AuditOutput, AuditOutputs } from '@code-pushup/models';
import { objectToCliArgs, toArray } from '@code-pushup/utils';
import { LIGHTHOUSE_REPORT_NAME } from './constants';

Expand Down Expand Up @@ -68,3 +69,35 @@ export function validateOnlyAudits(
}
return true;
}

export function toAuditOutputs(lhrAudits: Result[]): AuditOutputs {
return lhrAudits.map(
({
id: slug,
score,
numericValue: value = 0, // not every audit has a numericValue
details,
displayValue,
}: Result) => {
const auditOutput: AuditOutput = {
slug,
score: score ?? 1, // score can be null
value,
displayValue,
};
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved

if (details == null) {
return auditOutput;
}

// @TODO implement switch case for detail parsing. Related to #90
const unsupportedType = details.type;
// @TODO use cliui.logger.info Resolve TODO after PR #487 is merged.
console.info(
`Parsing details from type ${unsupportedType} is not implemented.`,
);

return auditOutput;
},
);
}
229 changes: 228 additions & 1 deletion packages/plugin-lighthouse/src/lib/utils.unit.test.ts
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { expect } from 'vitest';
import Details from 'lighthouse/types/lhr/audit-details';
import { describe, expect, it } from 'vitest';
import {
AuditsNotImplementedError,
getLighthouseCliArguments,
toAuditOutputs,
validateOnlyAudits,
} from './utils';

Expand Down Expand Up @@ -54,3 +56,228 @@ describe('validateOnlyAudits', () => {
).toThrow(new AuditsNotImplementedError(['d']));
});
});

describe('toAuditOutputs', () => {
it('should parse valid lhr details', () => {
expect(
toAuditOutputs([
{
id: 'first-contentful-paint',
title: 'First Contentful Paint',
description:
'First Contentful Paint marks the time at which the first text or image is painted. [Learn more about the First Contentful Paint metric](https://developer.chrome.com/docs/lighthouse/performance/first-contentful-paint/).',
score: 0.55,
scoreDisplayMode: 'numeric',
numericValue: 2838.974,
numericUnit: 'millisecond',
displayValue: '2.8 s',
},
]),
).toStrictEqual([
{
displayValue: '2.8 s',
score: 0.55,
slug: 'first-contentful-paint',
value: 2838.974,
},
]);
});

it('should convert null score to 1', () => {
expect(
toAuditOutputs([
{
id: 'performance-budget',
title: 'Performance budget',
description:
'Keep the quantity and size of network requests under the targets set by the provided performance budget. [Learn more about performance budgets](https://developers.google.com/web/tools/lighthouse/audits/budgets).',
score: null,
scoreDisplayMode: 'notApplicable',
},
]),
).toStrictEqual(
expect.arrayContaining([expect.objectContaining({ score: 1 })]),
);
});

it('should inform that opportunity type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'dummy-audit',
title: 'Dummy Audit',
description: 'This is a dummy audit.',
score: null,
scoreDisplayMode: 'informative',
details: {
type: 'opportunity',
headings: [
{
key: 'url',
valueType: 'url',
label: 'URL',
},
{
key: 'responseTime',
valueType: 'timespanMs',
label: 'Time Spent',
},
],
items: [
{
url: 'https://staging.code-pushup.dev/login',
responseTime: 449.292_000_000_000_03,
},
],
overallSavingsMs: 349.292_000_000_000_03,
} satisfies Details.Opportunity,
},
]);

expect(outputs[0]?.details).toBeUndefined();
});

it('should inform that table type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'dummy-audit',
title: 'Dummy Audit',
description: 'This is a dummy audit.',
score: null,
scoreDisplayMode: 'informative',
details: {
type: 'table',
headings: [],
items: [],
},
},
]);

expect(outputs[0]?.details).toBeUndefined();
});

it('should inform that debugdata type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'cumulative-layout-shift',
title: 'Cumulative Layout Shift',
description:
'Cumulative Layout Shift measures the movement of visible elements within the viewport. [Learn more about the Cumulative Layout Shift metric](https://web.dev/cls/).',
score: 1,
scoreDisplayMode: 'numeric',
numericValue: 0.000_350_978_852_728_593_95,
numericUnit: 'unitless',
displayValue: '0',
details: {
type: 'debugdata',
items: [
{
cumulativeLayoutShiftMainFrame: 0.000_350_978_852_728_593_95,
},
],
},
},
]);

// @TODO add check that cliui.logger is called. Resolve TODO after PR #487 is merged.

expect(outputs[0]?.details).toBeUndefined();
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
});

it('should inform that filmstrip type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'screenshot-thumbnails',
title: 'Screenshot Thumbnails',
description: 'This is what the load of your site looked like.',
score: null,
scoreDisplayMode: 'informative',
details: {
type: 'filmstrip',
scale: 3000,
items: [
{
timing: 375,
timestamp: 106_245_424_545,
data: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwY...',
},
],
},
},
]);

expect(outputs[0]?.details).toBeUndefined();
});

it('should inform that screenshot type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'final-screenshot',
title: 'Final Screenshot',
description: 'The last screenshot captured of the pageload.',
score: null,
scoreDisplayMode: 'informative',
details: {
type: 'screenshot',
timing: 541,
timestamp: 106_245_590_644,
data: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD//2Q==',
},
},
]);

expect(outputs[0]?.details).toBeUndefined();
});

it('should inform that treemap-data type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'script-treemap-data',
title: 'Script Treemap Data',
description: 'Used for treemap app',
score: null,
scoreDisplayMode: 'informative',
details: {
type: 'treemap-data',
nodes: [],
},
},
]);

expect(outputs[0]?.details).toBeUndefined();
});

it('should inform that criticalrequestchain type is not supported yet', () => {
const outputs = toAuditOutputs([
{
id: 'critical-request-chains',
title: 'Avoid chaining critical requests',
description:
'The Critical Request Chains below show you what resources are loaded with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn how to avoid chaining critical requests](https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains/).',
score: null,
scoreDisplayMode: 'notApplicable',
displayValue: '',
details: {
type: 'criticalrequestchain',
chains: {
EED301D300C9A7B634A444E0C6019FC1: {
request: {
url: 'https://example.com/',
startTime: 106_245.050_727,
endTime: 106_245.559_225,
responseReceivedTime: 106_245.559_001,
transferSize: 849,
},
},
},
longestChain: {
duration: 508.498_000_010_848_05,
length: 1,
transferSize: 849,
},
},
},
]);

expect(outputs[0]?.details).toBeUndefined();
});
});