Skip to content

Commit cfe6a5c

Browse files
committed
doc: add LHCI configuration
1 parent 6a60c11 commit cfe6a5c

File tree

3 files changed

+693
-0
lines changed

3 files changed

+693
-0
lines changed

docs/guides/4-lhci-server.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
label: LHCI Server
3+
icon: server
4+
order: 800
5+
---
6+
7+
# Utilisation avec LHCI Server
8+
9+
## Documentation externe des dépendances
10+
11+
[!ref target="blank" text="LHCI Server"](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md)
12+
13+
## Objectifs
14+
15+
**LHCI Server** est un serveur web qui permet de stocker et de visualiser les rapports d'audits Lighthouse.
16+
17+
!!!warning
18+
**Par défaut, il n'est pas en capacité d'afficher les résultats des audits EcoIndex**. Ce guide vous permettra de configurer **LHCI Server** pour afficher les résultats des audits EcoIndex.
19+
!!!
20+
21+
### Installation
22+
23+
Suivre le documentations de **LHCI Server** pour l'installation.
24+
25+
[!ref target="blank" text="LHCI Server"](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/server.md)
26+
27+
### Configuration pour afficher les résultats des audits EcoIndex
28+
29+
!!! warning
30+
A faire avant d'uploader les audits EcoIndex !
31+
!!!
32+
33+
1. Editer ce fichier : `/usr/src/lhci/node_modules/@lhci/server/src/api/statistic-definitions.js` (suivant votre installation, le chemin peut varier).
34+
2. Ajouter ces lignes :
35+
36+
```javascript
37+
const definitions = {
38+
// ...
39+
'category_lighthouse-plugin-ecoindex_median': categoryScoreMedian(
40+
'lighthouse-plugin-ecoindex',
41+
),
42+
'category_lighthouse-plugin-ecoindex_min': categoryScoreMinOrMax(
43+
'lighthouse-plugin-ecoindex',
44+
'min',
45+
),
46+
'category_lighthouse-plugin-ecoindex_max': categoryScoreMinOrMax(
47+
'lighthouse-plugin-ecoindex',
48+
'max',
49+
),
50+
// ...
51+
}
52+
```
53+
54+
!!! warning
55+
**Si vous utilisez Docker, cette configuration sera parfois perdue**.
56+
Pour palier à ce problème, vous pouvez créer un script qui modifie/rempalce le fichier de configuration à chaque redémarrage du conteneur.
57+
!!!
58+
59+
==- `statistic-definitions.js` sans les modifications
60+
:::code source="default.statistic-definitions.js" :::
61+
===
62+
==- `statistic-definitions.js` avec les modifications
63+
:::code source="ecoindex.statistic-definitions.js" :::
64+
===
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
/**
2+
* @license Copyright 2019 Google Inc. All Rights Reserved.
3+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5+
*/
6+
'use strict'
7+
8+
const {
9+
computeRepresentativeRuns,
10+
} = require('@lhci/utils/src/representative-runs')
11+
12+
/** @typedef {(lhrs: Array<LH.Result>) => ({value: number})} StatisticFn */
13+
14+
/**
15+
* @return {StatisticFn}
16+
*/
17+
function metaLighthouseVersion() {
18+
return lhrs => {
19+
const version = lhrs[0].lighthouseVersion || ''
20+
const [_, major = '0', minor = '0', patch = '0'] =
21+
version.match(/^(\d+)\.(\d+)\.(\d+)/) || []
22+
const versionAsNumber =
23+
Number(major) * 100 * 100 + Number(minor) * 100 + Number(patch)
24+
return { value: versionAsNumber || 0 }
25+
}
26+
}
27+
28+
/** @param {Array<number>} values */
29+
function median(values) {
30+
const sorted = [...values].sort((a, b) => a - b)
31+
const medianIndex = Math.floor(values.length / 2)
32+
33+
if (values.length === 0) return { value: -1 }
34+
return { value: sorted[medianIndex] }
35+
}
36+
37+
/**
38+
* @param {string} auditId
39+
* @return {StatisticFn}
40+
*/
41+
function auditNumericValueMedian(auditId) {
42+
return lhrs => {
43+
const values = lhrs
44+
.map(lhr => lhr.audits[auditId] && lhr.audits[auditId].numericValue)
45+
.filter(
46+
/** @return {value is number} */ value =>
47+
typeof value === 'number' && Number.isFinite(value),
48+
)
49+
50+
return median(values)
51+
}
52+
}
53+
54+
/**
55+
* @param {string} categoryId
56+
* @return {StatisticFn}
57+
*/
58+
function categoryScoreMedian(categoryId) {
59+
return lhrs => {
60+
const values = lhrs
61+
.map(
62+
lhr => lhr.categories[categoryId] && lhr.categories[categoryId].score,
63+
)
64+
.filter(
65+
/** @return {value is number} */ value =>
66+
typeof value === 'number' && Number.isFinite(value),
67+
)
68+
69+
return median(values)
70+
}
71+
}
72+
73+
/**
74+
* @param {string} categoryId
75+
* @param {'min'|'max'} type
76+
* @return {StatisticFn}
77+
*/
78+
function categoryScoreMinOrMax(categoryId, type) {
79+
return lhrs => {
80+
const values = lhrs
81+
.map(
82+
lhr => lhr.categories[categoryId] && lhr.categories[categoryId].score,
83+
)
84+
.filter(
85+
/** @return {value is number} */ value =>
86+
typeof value === 'number' && Number.isFinite(value),
87+
)
88+
89+
if (!values.length) return { value: -1 }
90+
return { value: Math[type](...values) }
91+
}
92+
}
93+
94+
/**
95+
* @param {string} groupId
96+
* @param {'pass'|'fail'|'na'} type
97+
* @return {StatisticFn}
98+
*/
99+
function auditGroupCountOfMedianLhr(groupId, type) {
100+
return lhrs => {
101+
const [medianLhr] = computeRepresentativeRuns([lhrs.map(lhr => [lhr, lhr])])
102+
if (!medianLhr) return { value: -1 }
103+
104+
// Start out with -1 as "no data available"
105+
let count = -1
106+
for (const category of Object.values(medianLhr.categories)) {
107+
for (const auditRef of category.auditRefs || []) {
108+
if (auditRef.group !== groupId) continue
109+
const audit = medianLhr.audits[auditRef.id]
110+
if (!audit) continue
111+
112+
// Once we find our first candidate audit, set the count to 0.
113+
if (count === -1) count = 0
114+
115+
const { score, scoreDisplayMode } = audit
116+
if (scoreDisplayMode === 'informative' && type === 'na') count++
117+
if (scoreDisplayMode === 'notApplicable' && type === 'na') count++
118+
if (scoreDisplayMode === 'binary' && score === 1 && type === 'pass')
119+
count++
120+
if (scoreDisplayMode === 'binary' && score !== 1 && type === 'fail')
121+
count++
122+
if (scoreDisplayMode === 'error' && type === 'fail') count++
123+
}
124+
}
125+
126+
return { value: count }
127+
}
128+
}
129+
130+
/** @type {Record<LHCI.ServerCommand.StatisticName, StatisticFn>} */
131+
const definitions = {
132+
meta_lighthouse_version: metaLighthouseVersion(),
133+
audit_interactive_median: auditNumericValueMedian('interactive'),
134+
'audit_speed-index_median': auditNumericValueMedian('speed-index'),
135+
'audit_first-contentful-paint_median': auditNumericValueMedian(
136+
'first-contentful-paint',
137+
),
138+
'audit_largest-contentful-paint_median': auditNumericValueMedian(
139+
'largest-contentful-paint',
140+
),
141+
'audit_total-blocking-time_median': auditNumericValueMedian(
142+
'total-blocking-time',
143+
),
144+
'audit_max-potential-fid_median':
145+
auditNumericValueMedian('max-potential-fid'),
146+
category_performance_median: categoryScoreMedian('performance'),
147+
category_pwa_median: categoryScoreMedian('pwa'),
148+
category_seo_median: categoryScoreMedian('seo'),
149+
category_accessibility_median: categoryScoreMedian('accessibility'),
150+
'category_best-practices_median': categoryScoreMedian('best-practices'),
151+
category_performance_min: categoryScoreMinOrMax('performance', 'min'),
152+
category_pwa_min: categoryScoreMinOrMax('pwa', 'min'),
153+
category_seo_min: categoryScoreMinOrMax('seo', 'min'),
154+
category_accessibility_min: categoryScoreMinOrMax('accessibility', 'min'),
155+
'category_best-practices_min': categoryScoreMinOrMax('best-practices', 'min'),
156+
category_performance_max: categoryScoreMinOrMax('performance', 'max'),
157+
category_pwa_max: categoryScoreMinOrMax('pwa', 'max'),
158+
category_seo_max: categoryScoreMinOrMax('seo', 'max'),
159+
category_accessibility_max: categoryScoreMinOrMax('accessibility', 'max'),
160+
'category_best-practices_max': categoryScoreMinOrMax('best-practices', 'max'),
161+
'auditgroup_pwa-fast-reliable_pass': auditGroupCountOfMedianLhr(
162+
'pwa-fast-reliable',
163+
'pass',
164+
),
165+
'auditgroup_pwa-fast-reliable_fail': auditGroupCountOfMedianLhr(
166+
'pwa-fast-reliable',
167+
'fail',
168+
),
169+
'auditgroup_pwa-fast-reliable_na': auditGroupCountOfMedianLhr(
170+
'pwa-fast-reliable',
171+
'na',
172+
),
173+
'auditgroup_pwa-installable_pass': auditGroupCountOfMedianLhr(
174+
'pwa-installable',
175+
'pass',
176+
),
177+
'auditgroup_pwa-installable_fail': auditGroupCountOfMedianLhr(
178+
'pwa-installable',
179+
'fail',
180+
),
181+
'auditgroup_pwa-installable_na': auditGroupCountOfMedianLhr(
182+
'pwa-installable',
183+
'na',
184+
),
185+
'auditgroup_pwa-optimized_pass': auditGroupCountOfMedianLhr(
186+
'pwa-optimized',
187+
'pass',
188+
),
189+
'auditgroup_pwa-optimized_fail': auditGroupCountOfMedianLhr(
190+
'pwa-optimized',
191+
'fail',
192+
),
193+
'auditgroup_pwa-optimized_na': auditGroupCountOfMedianLhr(
194+
'pwa-optimized',
195+
'na',
196+
),
197+
'auditgroup_a11y-best-practices_pass': auditGroupCountOfMedianLhr(
198+
'a11y-best-practices',
199+
'pass',
200+
),
201+
'auditgroup_a11y-best-practices_fail': auditGroupCountOfMedianLhr(
202+
'a11y-best-practices',
203+
'fail',
204+
),
205+
'auditgroup_a11y-best-practices_na': auditGroupCountOfMedianLhr(
206+
'a11y-best-practices',
207+
'na',
208+
),
209+
'auditgroup_a11y-color-contrast_pass': auditGroupCountOfMedianLhr(
210+
'a11y-color-contrast',
211+
'pass',
212+
),
213+
'auditgroup_a11y-color-contrast_fail': auditGroupCountOfMedianLhr(
214+
'a11y-color-contrast',
215+
'fail',
216+
),
217+
'auditgroup_a11y-color-contrast_na': auditGroupCountOfMedianLhr(
218+
'a11y-color-contrast',
219+
'na',
220+
),
221+
'auditgroup_a11y-names-labels_pass': auditGroupCountOfMedianLhr(
222+
'a11y-names-labels',
223+
'pass',
224+
),
225+
'auditgroup_a11y-names-labels_fail': auditGroupCountOfMedianLhr(
226+
'a11y-names-labels',
227+
'fail',
228+
),
229+
'auditgroup_a11y-names-labels_na': auditGroupCountOfMedianLhr(
230+
'a11y-names-labels',
231+
'na',
232+
),
233+
'auditgroup_a11y-navigation_pass': auditGroupCountOfMedianLhr(
234+
'a11y-navigation',
235+
'pass',
236+
),
237+
'auditgroup_a11y-navigation_fail': auditGroupCountOfMedianLhr(
238+
'a11y-navigation',
239+
'fail',
240+
),
241+
'auditgroup_a11y-navigation_na': auditGroupCountOfMedianLhr(
242+
'a11y-navigation',
243+
'na',
244+
),
245+
'auditgroup_a11y-aria_pass': auditGroupCountOfMedianLhr('a11y-aria', 'pass'),
246+
'auditgroup_a11y-aria_fail': auditGroupCountOfMedianLhr('a11y-aria', 'fail'),
247+
'auditgroup_a11y-aria_na': auditGroupCountOfMedianLhr('a11y-aria', 'na'),
248+
'auditgroup_a11y-language_pass': auditGroupCountOfMedianLhr(
249+
'a11y-language',
250+
'pass',
251+
),
252+
'auditgroup_a11y-language_fail': auditGroupCountOfMedianLhr(
253+
'a11y-language',
254+
'fail',
255+
),
256+
'auditgroup_a11y-language_na': auditGroupCountOfMedianLhr(
257+
'a11y-language',
258+
'na',
259+
),
260+
'auditgroup_a11y-audio-video_pass': auditGroupCountOfMedianLhr(
261+
'a11y-audio-video',
262+
'pass',
263+
),
264+
'auditgroup_a11y-audio-video_fail': auditGroupCountOfMedianLhr(
265+
'a11y-audio-video',
266+
'fail',
267+
),
268+
'auditgroup_a11y-audio-video_na': auditGroupCountOfMedianLhr(
269+
'a11y-audio-video',
270+
'na',
271+
),
272+
'auditgroup_a11y-tables-lists_pass': auditGroupCountOfMedianLhr(
273+
'a11y-tables-lists',
274+
'pass',
275+
),
276+
'auditgroup_a11y-tables-lists_fail': auditGroupCountOfMedianLhr(
277+
'a11y-tables-lists',
278+
'fail',
279+
),
280+
'auditgroup_a11y-tables-lists_na': auditGroupCountOfMedianLhr(
281+
'a11y-tables-lists',
282+
'na',
283+
),
284+
'auditgroup_seo-mobile_pass': auditGroupCountOfMedianLhr(
285+
'seo-mobile',
286+
'pass',
287+
),
288+
'auditgroup_seo-mobile_fail': auditGroupCountOfMedianLhr(
289+
'seo-mobile',
290+
'fail',
291+
),
292+
'auditgroup_seo-mobile_na': auditGroupCountOfMedianLhr('seo-mobile', 'na'),
293+
'auditgroup_seo-content_pass': auditGroupCountOfMedianLhr(
294+
'seo-content',
295+
'pass',
296+
),
297+
'auditgroup_seo-content_fail': auditGroupCountOfMedianLhr(
298+
'seo-content',
299+
'fail',
300+
),
301+
'auditgroup_seo-content_na': auditGroupCountOfMedianLhr('seo-content', 'na'),
302+
'auditgroup_seo-crawl_pass': auditGroupCountOfMedianLhr('seo-crawl', 'pass'),
303+
'auditgroup_seo-crawl_fail': auditGroupCountOfMedianLhr('seo-crawl', 'fail'),
304+
'auditgroup_seo-crawl_na': auditGroupCountOfMedianLhr('seo-crawl', 'na'),
305+
}
306+
307+
// Keep the export separate from declaration to enable tsc to typecheck the `@type` annotation.
308+
module.exports = { definitions, VERSION: 2 }

0 commit comments

Comments
 (0)