lighthouse-reporting 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -43
- package/dist/index.cjs +2 -0
- package/dist/index.js +4 -2
- package/dist/lighthouse-reporting.umd.cjs +51 -8
- package/dist/lighthouseReports.cjs +43 -2
- package/dist/lighthouseReports.d.ts +45 -1
- package/dist/lighthouseReports.js +44 -3
- package/dist/storybookPlaywright.cjs +2 -1
- package/dist/storybookPlaywright.d.ts +20 -1
- package/dist/storybookPlaywright.js +2 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -92,12 +92,16 @@ import {
|
|
|
92
92
|
LighthouseResult,
|
|
93
93
|
StorybookIndexStory,
|
|
94
94
|
storybookPlaywright,
|
|
95
|
+
writeScoresToJson
|
|
95
96
|
} from 'lighthouse-reporting'
|
|
96
97
|
|
|
97
98
|
playwrightLighthouseTest.setTimeout(60000)
|
|
98
|
-
const
|
|
99
|
+
const lhScoresDir = path.join(process.cwd(), process.env.LH_SCORES_DIR || 'lh-scores')
|
|
100
|
+
const reportDir = path.join(process.cwd(), process.env.LH_REPORT_DIR || 'lighthouse')
|
|
99
101
|
const htmlFilePath = path.join(reportDir, 'index.html')
|
|
100
102
|
|
|
103
|
+
// use stories.json instead of index.json for storybook v6
|
|
104
|
+
// make sure to set buildStoriesJson to true in storybook main.js feature section
|
|
101
105
|
const stories = storybookPlaywright.getStories('./storybook-static/index.json', (story) => {
|
|
102
106
|
// skip docs, etc
|
|
103
107
|
if (story.type !== 'story') {
|
|
@@ -147,6 +151,8 @@ const runLighthouse = async (story: StorybookIndexStory, context: BrowserContext
|
|
|
147
151
|
const scores = getScores(result)
|
|
148
152
|
await writeCsvResult(reportDir, name, scores, thresholds)
|
|
149
153
|
await writeHtmlListEntryWithRetry(htmlFilePath, name, scores, thresholds, result.comparisonError)
|
|
154
|
+
// write score results in JSON, allows generating the Average csv report
|
|
155
|
+
await writeScoresToJson(lhScoresDir, name, scores, result)
|
|
150
156
|
}
|
|
151
157
|
```
|
|
152
158
|
|
|
@@ -162,6 +168,8 @@ import { PlaywrightTestConfig } from '@playwright/test'
|
|
|
162
168
|
|
|
163
169
|
const baseURL = 'http://127.0.0.1:6009'
|
|
164
170
|
// process.env.LH_REPORT_DIR = 'lighthouse-storybook' // adjust lighthouse output folder if required
|
|
171
|
+
// process.env.LH_SCORES_DIR = 'lh-scores' // to write and store scores in json format or write average report
|
|
172
|
+
|
|
165
173
|
|
|
166
174
|
const config: PlaywrightTestConfig = {
|
|
167
175
|
use: {
|
|
@@ -209,49 +217,13 @@ export default config
|
|
|
209
217
|
<summary>global-setup.ts</summary>
|
|
210
218
|
|
|
211
219
|
```ts
|
|
212
|
-
import {
|
|
213
|
-
|
|
214
|
-
const baseURL = 'http://127.0.0.1:6009'
|
|
215
|
-
// process.env.LH_REPORT_DIR = 'lighthouse-storybook' // adjust lighthouse output folder if required
|
|
220
|
+
import { lighthouseSetup } from 'lighthouse-reporting'
|
|
216
221
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
viewport: { width: 1280, height: 820 },
|
|
220
|
-
ignoreHTTPSErrors: true,
|
|
221
|
-
acceptDownloads: false,
|
|
222
|
-
trace: 'off',
|
|
223
|
-
baseURL,
|
|
224
|
-
screenshot: { mode: 'off' },
|
|
225
|
-
},
|
|
226
|
-
projects: [
|
|
227
|
-
{
|
|
228
|
-
name: 'chromium',
|
|
229
|
-
use: {
|
|
230
|
-
browserName: 'chromium',
|
|
231
|
-
launchOptions: { args: ['--disable-gpu'] },
|
|
232
|
-
},
|
|
233
|
-
retries: 0,
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
expect: { toMatchSnapshot: { threshold: 0.2 } },
|
|
237
|
-
reporter: 'line',
|
|
238
|
-
testDir: 'test/storybook',
|
|
239
|
-
testMatch: '*.spec.ts',
|
|
240
|
-
fullyParallel: true,
|
|
241
|
-
globalSetup: './src/global-setup.ts',
|
|
242
|
-
globalTeardown: './src/global-teardown.ts',
|
|
243
|
-
forbidOnly: true,
|
|
244
|
-
webServer: [
|
|
245
|
-
{
|
|
246
|
-
command: 'npx http-server ./storybook-static --port 6009 --silent',
|
|
247
|
-
url: `${baseURL}/index.json`,
|
|
248
|
-
timeout: 15 * 1000,
|
|
249
|
-
reuseExistingServer: false,
|
|
250
|
-
ignoreHTTPSErrors: true,
|
|
251
|
-
},
|
|
252
|
-
],
|
|
222
|
+
async function globalSetup() {
|
|
223
|
+
await lighthouseSetup()
|
|
253
224
|
}
|
|
254
|
-
|
|
225
|
+
|
|
226
|
+
export default globalSetup
|
|
255
227
|
```
|
|
256
228
|
|
|
257
229
|
</details>
|
|
@@ -260,10 +232,14 @@ export default config
|
|
|
260
232
|
<summary>global-teardown.ts</summary>
|
|
261
233
|
|
|
262
234
|
```ts
|
|
263
|
-
import { lighthousePlaywrightTeardown } from 'lighthouse-reporting'
|
|
235
|
+
import { lighthousePlaywrightTeardown, buildAverageCsv } from 'lighthouse-reporting'
|
|
236
|
+
|
|
237
|
+
const lhScoresDir = path.join(process.cwd(), process.env.LH_SCORES_DIR || 'lh-scores')
|
|
238
|
+
const reportDir = path.join(process.cwd(), 'lighthouse')
|
|
264
239
|
|
|
265
240
|
async function globalTeardown() {
|
|
266
241
|
await lighthousePlaywrightTeardown()
|
|
242
|
+
await buildAverageCsv(lhScoresDir, reportDir)
|
|
267
243
|
}
|
|
268
244
|
|
|
269
245
|
export default globalTeardown
|
package/dist/index.cjs
CHANGED
|
@@ -13,8 +13,10 @@ require("get-port");
|
|
|
13
13
|
require("@playwright/test");
|
|
14
14
|
exports.lighthousePlaywrightTeardown = hooks.lighthousePlaywrightTeardown;
|
|
15
15
|
exports.lighthouseSetup = hooks.lighthouseSetup;
|
|
16
|
+
exports.buildAverageCsv = lighthouseReports.buildAverageCsv;
|
|
16
17
|
exports.getScores = lighthouseReports.getScores;
|
|
17
18
|
exports.writeCsvResult = lighthouseReports.writeCsvResult;
|
|
18
19
|
exports.writeHtmlListEntryWithRetry = lighthouseReports.writeHtmlListEntryWithRetry;
|
|
20
|
+
exports.writeScoresToJson = lighthouseReports.writeScoresToJson;
|
|
19
21
|
exports.playwrightLighthouseTest = playwrightLighthouseTest.playwrightLighthouseTest;
|
|
20
22
|
exports.storybookPlaywright = storybookPlaywright.storybookPlaywright;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lighthousePlaywrightTeardown, lighthouseSetup } from "./hooks.js";
|
|
2
|
-
import { getScores, writeCsvResult, writeHtmlListEntryWithRetry } from "./lighthouseReports.js";
|
|
2
|
+
import { buildAverageCsv, getScores, writeCsvResult, writeHtmlListEntryWithRetry, writeScoresToJson } from "./lighthouseReports.js";
|
|
3
3
|
import { playwrightLighthouseTest } from "./playwrightLighthouseTest.js";
|
|
4
4
|
import { storybookPlaywright } from "./storybookPlaywright.js";
|
|
5
5
|
import "os";
|
|
@@ -10,11 +10,13 @@ import "./constants-226e9774.js";
|
|
|
10
10
|
import "get-port";
|
|
11
11
|
import "@playwright/test";
|
|
12
12
|
export {
|
|
13
|
+
buildAverageCsv,
|
|
13
14
|
getScores,
|
|
14
15
|
lighthousePlaywrightTeardown,
|
|
15
16
|
lighthouseSetup,
|
|
16
17
|
playwrightLighthouseTest,
|
|
17
18
|
storybookPlaywright,
|
|
18
19
|
writeCsvResult,
|
|
19
|
-
writeHtmlListEntryWithRetry
|
|
20
|
+
writeHtmlListEntryWithRetry,
|
|
21
|
+
writeScoresToJson
|
|
20
22
|
};
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
async function lighthousePlaywrightTeardown() {
|
|
22
22
|
await fse.remove(path.join(os.tmpdir(), PW_TMP_DIR));
|
|
23
23
|
}
|
|
24
|
-
const writeCsvResult = async (reportDir2, name, scores, thresholds, swimlanes = []) => {
|
|
24
|
+
const writeCsvResult = async (reportDir2, name, scores, thresholds = {}, swimlanes = []) => {
|
|
25
25
|
let csvData = Object.keys(scores).join(",");
|
|
26
26
|
if (swimlanes.length > 0) {
|
|
27
27
|
csvData += "," + swimlanes.map((swimlane) => `${swimlane}_threshold`);
|
|
28
28
|
}
|
|
29
29
|
csvData += "\n";
|
|
30
30
|
csvData += Object.values(scores).join(",");
|
|
31
|
-
if (swimlanes.length > 0) {
|
|
31
|
+
if (swimlanes.length > 0 && Object.keys(thresholds).length > 0) {
|
|
32
32
|
csvData += "," + swimlanes.map((swimlane) => thresholds[swimlane]);
|
|
33
33
|
}
|
|
34
34
|
await fse.writeFile(path.join(reportDir2, `${name}.csv`), csvData);
|
|
@@ -61,6 +61,45 @@
|
|
|
61
61
|
prev[key] = Math.floor(c.score * 100);
|
|
62
62
|
return prev;
|
|
63
63
|
}, {});
|
|
64
|
+
const writeScoresToJson = async (lhScoresDir, name, scores, result) => {
|
|
65
|
+
const json = Object.entries(scores).reduce((prev, [k, score]) => {
|
|
66
|
+
prev[k] = { score };
|
|
67
|
+
return prev;
|
|
68
|
+
}, {});
|
|
69
|
+
const accessibilityViolations = result.artifacts.Accessibility.violations.map((v) => {
|
|
70
|
+
return {
|
|
71
|
+
title: result.lhr.audits[v.id].title,
|
|
72
|
+
nodes: v.nodes.length
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
if (accessibilityViolations.length > 0) {
|
|
76
|
+
json.accessibility.issues = accessibilityViolations;
|
|
77
|
+
}
|
|
78
|
+
await fse.writeFile(path.join(lhScoresDir, `${name}.json`), JSON.stringify(json, null, 2));
|
|
79
|
+
};
|
|
80
|
+
const buildAverageCsv = async (lhScoresDir, reportDir2) => {
|
|
81
|
+
const files = await fse.readdir(lhScoresDir);
|
|
82
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
83
|
+
if (jsonFiles.length === 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const scores = {};
|
|
87
|
+
for (const fileName of jsonFiles) {
|
|
88
|
+
const score = await fse.readJson(
|
|
89
|
+
path.join(lhScoresDir, fileName)
|
|
90
|
+
);
|
|
91
|
+
Object.entries(score).forEach(([k, v]) => {
|
|
92
|
+
if (!scores[k]) {
|
|
93
|
+
scores[k] = 0;
|
|
94
|
+
}
|
|
95
|
+
scores[k] += v.score;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
Object.entries(scores).forEach(([k, v]) => {
|
|
99
|
+
scores[k] = v / jsonFiles.length;
|
|
100
|
+
});
|
|
101
|
+
await writeCsvResult(reportDir2, "_AVERAGE_", scores);
|
|
102
|
+
};
|
|
64
103
|
class Locked extends Error {
|
|
65
104
|
constructor(port) {
|
|
66
105
|
super(`${port} is locked`);
|
|
@@ -71,7 +110,7 @@
|
|
|
71
110
|
young: /* @__PURE__ */ new Set()
|
|
72
111
|
};
|
|
73
112
|
const releaseOldLockedPortsIntervalMs = 1e3 * 15;
|
|
74
|
-
let
|
|
113
|
+
let timeout;
|
|
75
114
|
const getLocalHosts = () => {
|
|
76
115
|
const interfaces = os.networkInterfaces();
|
|
77
116
|
const results = /* @__PURE__ */ new Set([void 0, "0.0.0.0"]);
|
|
@@ -137,13 +176,14 @@
|
|
|
137
176
|
exclude = new Set(excludeIterable);
|
|
138
177
|
}
|
|
139
178
|
}
|
|
140
|
-
if (
|
|
141
|
-
|
|
179
|
+
if (timeout === void 0) {
|
|
180
|
+
timeout = setTimeout(() => {
|
|
181
|
+
timeout = void 0;
|
|
142
182
|
lockedPorts.old = lockedPorts.young;
|
|
143
183
|
lockedPorts.young = /* @__PURE__ */ new Set();
|
|
144
184
|
}, releaseOldLockedPortsIntervalMs);
|
|
145
|
-
if (
|
|
146
|
-
|
|
185
|
+
if (timeout.unref) {
|
|
186
|
+
timeout.unref();
|
|
147
187
|
}
|
|
148
188
|
}
|
|
149
189
|
const hosts = getLocalHosts();
|
|
@@ -199,7 +239,8 @@
|
|
|
199
239
|
throw new Error("Please build storybook before running tests!");
|
|
200
240
|
}
|
|
201
241
|
const storybookIndexJson = fse.readJsonSync(pathToStorybookIndexJson);
|
|
202
|
-
const
|
|
242
|
+
const storyObject = storybookIndexJson.entries || storybookIndexJson.stories;
|
|
243
|
+
const stories = Object.values(storyObject).filter(storyFilterFn);
|
|
203
244
|
return stories;
|
|
204
245
|
},
|
|
205
246
|
captureScreenshot: async (story, context, screenshotOptions, actionBeforeScreenshot) => {
|
|
@@ -214,6 +255,7 @@
|
|
|
214
255
|
await test.expect(page).toHaveScreenshot(`${story.id}.png`, screenshotOptions);
|
|
215
256
|
}
|
|
216
257
|
};
|
|
258
|
+
exports2.buildAverageCsv = buildAverageCsv;
|
|
217
259
|
exports2.getScores = getScores;
|
|
218
260
|
exports2.lighthousePlaywrightTeardown = lighthousePlaywrightTeardown;
|
|
219
261
|
exports2.lighthouseSetup = lighthouseSetup;
|
|
@@ -221,5 +263,6 @@
|
|
|
221
263
|
exports2.storybookPlaywright = storybookPlaywright;
|
|
222
264
|
exports2.writeCsvResult = writeCsvResult;
|
|
223
265
|
exports2.writeHtmlListEntryWithRetry = writeHtmlListEntryWithRetry;
|
|
266
|
+
exports2.writeScoresToJson = writeScoresToJson;
|
|
224
267
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
225
268
|
});
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const fse = require("fs-extra");
|
|
5
|
-
const writeCsvResult = async (reportDir, name, scores, thresholds, swimlanes = []) => {
|
|
5
|
+
const writeCsvResult = async (reportDir, name, scores, thresholds = {}, swimlanes = []) => {
|
|
6
6
|
let csvData = Object.keys(scores).join(",");
|
|
7
7
|
if (swimlanes.length > 0) {
|
|
8
8
|
csvData += "," + swimlanes.map((swimlane) => `${swimlane}_threshold`);
|
|
9
9
|
}
|
|
10
10
|
csvData += "\n";
|
|
11
11
|
csvData += Object.values(scores).join(",");
|
|
12
|
-
if (swimlanes.length > 0) {
|
|
12
|
+
if (swimlanes.length > 0 && Object.keys(thresholds).length > 0) {
|
|
13
13
|
csvData += "," + swimlanes.map((swimlane) => thresholds[swimlane]);
|
|
14
14
|
}
|
|
15
15
|
await fse.writeFile(path.join(reportDir, `${name}.csv`), csvData);
|
|
@@ -42,6 +42,47 @@ const getScores = (result) => Object.entries(result.lhr.categories).reduce((prev
|
|
|
42
42
|
prev[key] = Math.floor(c.score * 100);
|
|
43
43
|
return prev;
|
|
44
44
|
}, {});
|
|
45
|
+
const writeScoresToJson = async (lhScoresDir, name, scores, result) => {
|
|
46
|
+
const json = Object.entries(scores).reduce((prev, [k, score]) => {
|
|
47
|
+
prev[k] = { score };
|
|
48
|
+
return prev;
|
|
49
|
+
}, {});
|
|
50
|
+
const accessibilityViolations = result.artifacts.Accessibility.violations.map((v) => {
|
|
51
|
+
return {
|
|
52
|
+
title: result.lhr.audits[v.id].title,
|
|
53
|
+
nodes: v.nodes.length
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
if (accessibilityViolations.length > 0) {
|
|
57
|
+
json.accessibility.issues = accessibilityViolations;
|
|
58
|
+
}
|
|
59
|
+
await fse.writeFile(path.join(lhScoresDir, `${name}.json`), JSON.stringify(json, null, 2));
|
|
60
|
+
};
|
|
61
|
+
const buildAverageCsv = async (lhScoresDir, reportDir) => {
|
|
62
|
+
const files = await fse.readdir(lhScoresDir);
|
|
63
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
64
|
+
if (jsonFiles.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const scores = {};
|
|
68
|
+
for (const fileName of jsonFiles) {
|
|
69
|
+
const score = await fse.readJson(
|
|
70
|
+
path.join(lhScoresDir, fileName)
|
|
71
|
+
);
|
|
72
|
+
Object.entries(score).forEach(([k, v]) => {
|
|
73
|
+
if (!scores[k]) {
|
|
74
|
+
scores[k] = 0;
|
|
75
|
+
}
|
|
76
|
+
scores[k] += v.score;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
Object.entries(scores).forEach(([k, v]) => {
|
|
80
|
+
scores[k] = v / jsonFiles.length;
|
|
81
|
+
});
|
|
82
|
+
await writeCsvResult(reportDir, "_AVERAGE_", scores);
|
|
83
|
+
};
|
|
84
|
+
exports.buildAverageCsv = buildAverageCsv;
|
|
45
85
|
exports.getScores = getScores;
|
|
46
86
|
exports.writeCsvResult = writeCsvResult;
|
|
47
87
|
exports.writeHtmlListEntryWithRetry = writeHtmlListEntryWithRetry;
|
|
88
|
+
exports.writeScoresToJson = writeScoresToJson;
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
interface NodeDetails {
|
|
2
|
+
lhId: string;
|
|
3
|
+
devtoolsNodePath: string;
|
|
4
|
+
selector: string;
|
|
5
|
+
boudingRect: {
|
|
6
|
+
[k: string]: number;
|
|
7
|
+
};
|
|
8
|
+
snippet: string;
|
|
9
|
+
nodeLabel: string;
|
|
10
|
+
}
|
|
11
|
+
interface RuleExecutionError {
|
|
12
|
+
name: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
interface AxeRuleResult {
|
|
16
|
+
id: string;
|
|
17
|
+
impact?: string;
|
|
18
|
+
tags: Array<string>;
|
|
19
|
+
nodes: Array<{
|
|
20
|
+
target: Array<string>;
|
|
21
|
+
failureSummary?: string;
|
|
22
|
+
node: NodeDetails;
|
|
23
|
+
relatedNodes: NodeDetails[];
|
|
24
|
+
}>;
|
|
25
|
+
error?: RuleExecutionError;
|
|
26
|
+
}
|
|
1
27
|
export interface LighthouseResult {
|
|
2
28
|
lhr: {
|
|
3
29
|
categories: {
|
|
@@ -5,12 +31,30 @@ export interface LighthouseResult {
|
|
|
5
31
|
score: number;
|
|
6
32
|
};
|
|
7
33
|
};
|
|
34
|
+
audits: {
|
|
35
|
+
[k: string]: {
|
|
36
|
+
title: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
artifacts: {
|
|
41
|
+
Accessibility: {
|
|
42
|
+
violations: Array<AxeRuleResult>;
|
|
43
|
+
};
|
|
8
44
|
};
|
|
9
45
|
comparisonError?: string;
|
|
10
46
|
}
|
|
11
|
-
export declare const writeCsvResult: (reportDir: string, name: string, scores: Record<string, number>, thresholds
|
|
47
|
+
export declare const writeCsvResult: (reportDir: string, name: string, scores: Record<string, number>, thresholds?: Record<string, number>, swimlanes?: Array<string>) => Promise<void>;
|
|
12
48
|
/**
|
|
13
49
|
* workaround conflicts when multiple threads write the same file
|
|
14
50
|
*/
|
|
15
51
|
export declare const writeHtmlListEntryWithRetry: (htmlFilePath: string, name: string, scores: Record<string, number>, thresholds: Record<string, number>, comparisonError?: string, addReportLink?: boolean) => Promise<void>;
|
|
16
52
|
export declare const getScores: (result: LighthouseResult) => Record<string, number>;
|
|
53
|
+
export declare const writeScoresToJson: (lhScoresDir: string, name: string, scores: Record<string, number>, result: LighthouseResult) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Generate average csv file. Make sure to use `writeScoresToJson` in your test!
|
|
56
|
+
* @param lhScoresDir path to folder with lighthouse json files. See `writeScoresToJson`.
|
|
57
|
+
* @param reportDir folder where `_AVERAGE_.json` will be generated
|
|
58
|
+
*/
|
|
59
|
+
export declare const buildAverageCsv: (lhScoresDir: string, reportDir: string) => Promise<void>;
|
|
60
|
+
export {};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fse from "fs-extra";
|
|
3
|
-
const writeCsvResult = async (reportDir, name, scores, thresholds, swimlanes = []) => {
|
|
3
|
+
const writeCsvResult = async (reportDir, name, scores, thresholds = {}, swimlanes = []) => {
|
|
4
4
|
let csvData = Object.keys(scores).join(",");
|
|
5
5
|
if (swimlanes.length > 0) {
|
|
6
6
|
csvData += "," + swimlanes.map((swimlane) => `${swimlane}_threshold`);
|
|
7
7
|
}
|
|
8
8
|
csvData += "\n";
|
|
9
9
|
csvData += Object.values(scores).join(",");
|
|
10
|
-
if (swimlanes.length > 0) {
|
|
10
|
+
if (swimlanes.length > 0 && Object.keys(thresholds).length > 0) {
|
|
11
11
|
csvData += "," + swimlanes.map((swimlane) => thresholds[swimlane]);
|
|
12
12
|
}
|
|
13
13
|
await fse.writeFile(path.join(reportDir, `${name}.csv`), csvData);
|
|
@@ -40,8 +40,49 @@ const getScores = (result) => Object.entries(result.lhr.categories).reduce((prev
|
|
|
40
40
|
prev[key] = Math.floor(c.score * 100);
|
|
41
41
|
return prev;
|
|
42
42
|
}, {});
|
|
43
|
+
const writeScoresToJson = async (lhScoresDir, name, scores, result) => {
|
|
44
|
+
const json = Object.entries(scores).reduce((prev, [k, score]) => {
|
|
45
|
+
prev[k] = { score };
|
|
46
|
+
return prev;
|
|
47
|
+
}, {});
|
|
48
|
+
const accessibilityViolations = result.artifacts.Accessibility.violations.map((v) => {
|
|
49
|
+
return {
|
|
50
|
+
title: result.lhr.audits[v.id].title,
|
|
51
|
+
nodes: v.nodes.length
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
if (accessibilityViolations.length > 0) {
|
|
55
|
+
json.accessibility.issues = accessibilityViolations;
|
|
56
|
+
}
|
|
57
|
+
await fse.writeFile(path.join(lhScoresDir, `${name}.json`), JSON.stringify(json, null, 2));
|
|
58
|
+
};
|
|
59
|
+
const buildAverageCsv = async (lhScoresDir, reportDir) => {
|
|
60
|
+
const files = await fse.readdir(lhScoresDir);
|
|
61
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
62
|
+
if (jsonFiles.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const scores = {};
|
|
66
|
+
for (const fileName of jsonFiles) {
|
|
67
|
+
const score = await fse.readJson(
|
|
68
|
+
path.join(lhScoresDir, fileName)
|
|
69
|
+
);
|
|
70
|
+
Object.entries(score).forEach(([k, v]) => {
|
|
71
|
+
if (!scores[k]) {
|
|
72
|
+
scores[k] = 0;
|
|
73
|
+
}
|
|
74
|
+
scores[k] += v.score;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
Object.entries(scores).forEach(([k, v]) => {
|
|
78
|
+
scores[k] = v / jsonFiles.length;
|
|
79
|
+
});
|
|
80
|
+
await writeCsvResult(reportDir, "_AVERAGE_", scores);
|
|
81
|
+
};
|
|
43
82
|
export {
|
|
83
|
+
buildAverageCsv,
|
|
44
84
|
getScores,
|
|
45
85
|
writeCsvResult,
|
|
46
|
-
writeHtmlListEntryWithRetry
|
|
86
|
+
writeHtmlListEntryWithRetry,
|
|
87
|
+
writeScoresToJson
|
|
47
88
|
};
|
|
@@ -9,7 +9,8 @@ const storybookPlaywright = {
|
|
|
9
9
|
throw new Error("Please build storybook before running tests!");
|
|
10
10
|
}
|
|
11
11
|
const storybookIndexJson = fse.readJsonSync(pathToStorybookIndexJson);
|
|
12
|
-
const
|
|
12
|
+
const storyObject = storybookIndexJson.entries || storybookIndexJson.stories;
|
|
13
|
+
const stories = Object.values(storyObject).filter(storyFilterFn);
|
|
13
14
|
return stories;
|
|
14
15
|
},
|
|
15
16
|
captureScreenshot: async (story, context, screenshotOptions, actionBeforeScreenshot) => {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { BrowserContext, Locator, Page } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Storybook v7
|
|
4
|
+
*/
|
|
2
5
|
export interface StorybookIndexStory {
|
|
3
6
|
id: string;
|
|
4
7
|
title: string;
|
|
@@ -7,7 +10,23 @@ export interface StorybookIndexStory {
|
|
|
7
10
|
tags: Array<string>;
|
|
8
11
|
type: 'story' | 'docs';
|
|
9
12
|
}
|
|
10
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Storybook v6
|
|
15
|
+
*/
|
|
16
|
+
export interface StorybookStoriesStory {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
name: string;
|
|
20
|
+
importPath: string;
|
|
21
|
+
kind: string;
|
|
22
|
+
story: string;
|
|
23
|
+
parameters: {
|
|
24
|
+
__id: string;
|
|
25
|
+
docsOnly: boolean;
|
|
26
|
+
fileName: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
type StoriesFilterFn = <V7 = true>(story: V7 extends true ? StorybookIndexStory : StorybookStoriesStory) => boolean;
|
|
11
30
|
export declare const storybookPlaywright: {
|
|
12
31
|
getStories: (pathToStorybookIndexJson: string, storyFilterFn: StoriesFilterFn) => StorybookIndexStory[];
|
|
13
32
|
captureScreenshot: (story: StorybookIndexStory, context: BrowserContext, screenshotOptions?: {
|
|
@@ -7,7 +7,8 @@ const storybookPlaywright = {
|
|
|
7
7
|
throw new Error("Please build storybook before running tests!");
|
|
8
8
|
}
|
|
9
9
|
const storybookIndexJson = fse.readJsonSync(pathToStorybookIndexJson);
|
|
10
|
-
const
|
|
10
|
+
const storyObject = storybookIndexJson.entries || storybookIndexJson.stories;
|
|
11
|
+
const stories = Object.values(storyObject).filter(storyFilterFn);
|
|
11
12
|
return stories;
|
|
12
13
|
},
|
|
13
14
|
captureScreenshot: async (story, context, screenshotOptions, actionBeforeScreenshot) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lighthouse-reporting",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "vite build && vite build -c vite.umd.config.ts && tsc -p ./tsconfig.build.json",
|
|
@@ -24,25 +24,25 @@
|
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
26
|
"@playwright/test": "^1",
|
|
27
|
-
"get-port": "
|
|
27
|
+
"get-port": ">=7"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@playwright/test": "^1.34.3",
|
|
31
31
|
"@types/fs-extra": "^11.0.1",
|
|
32
|
-
"@types/node": "^20.
|
|
33
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
34
|
-
"@typescript-eslint/parser": "^5.
|
|
35
|
-
"eslint": "^8.
|
|
32
|
+
"@types/node": "^20.3.1",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
|
34
|
+
"@typescript-eslint/parser": "^5.60.0",
|
|
35
|
+
"eslint": "^8.43.0",
|
|
36
36
|
"eslint-config-prettier": "^8.8.0",
|
|
37
37
|
"eslint-plugin-prettier": "^4.2.1",
|
|
38
|
-
"get-port": "^
|
|
38
|
+
"get-port": "^7.0.0",
|
|
39
39
|
"husky": "^8.0.3",
|
|
40
40
|
"npm-run-all": "^4.1.5",
|
|
41
41
|
"prettier": "^2.8.8",
|
|
42
|
-
"semantic-release": "^21.0.
|
|
43
|
-
"typescript": "^5.
|
|
42
|
+
"semantic-release": "^21.0.5",
|
|
43
|
+
"typescript": "^5.1.3",
|
|
44
44
|
"vite": "^4.3.9",
|
|
45
|
-
"vite-plugin-static-copy": "^0.
|
|
45
|
+
"vite-plugin-static-copy": "^0.16.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"fs-extra": "^11.1.1"
|