@terra-symposium/standards-reporting 0.0.0-stage → 0.4.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 CHANGED
@@ -1,3 +1,42 @@
1
- # Temporary Holding Version
1
+ # @terra-symposium/standards-reporting
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Reusable responsive Playwright diagnostics and evidence reports for Terra Symposium repositories.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add -D @terra-symposium/standards-reporting @playwright/test
9
+ ```
10
+
11
+ The package requires Node.js `>=18.18` and has an `@playwright/test >=1.52.0 <2` peer dependency. Spreadsheet, slideshow, and image-processing dependencies are installed with the package.
12
+
13
+ ## Configure responsive projects
14
+
15
+ ```ts
16
+ import { defineConfig } from '@playwright/test';
17
+ import { withResponsivePlaywright } from '@terra-symposium/standards-reporting/viewports';
18
+
19
+ export default withResponsivePlaywright(
20
+ defineConfig({
21
+ projects: [{ name: 'web', testMatch: '**/*.spec.ts' }],
22
+ }),
23
+ {
24
+ singleRunProjects: ['manual-browser-capture'],
25
+ },
26
+ );
27
+ ```
28
+
29
+ By default, regular projects run at the six shared viewport sizes. Set `PW_VIEWPORTS=mobile,lg` to select a subset. Setup projects and names listed in `singleRunProjects` run once. The helper enables full-page screenshots and the shared reporter.
30
+
31
+ A timestamped directory under `test-results/visual` contains the workbook, slideshow, and copied screenshots. Each Playwright result screenshot and keyed visual capture is also stored with metadata under `test-results/visual/screenshots`. Repeated test/viewport captures replace their previous file, and each new slideshow is rebuilt from the whole folder, so interrupted and subset runs retain earlier evidence. The Playwright output directory defaults to `test-results/playwright-output`.
32
+
33
+ ## More capabilities
34
+
35
+ - Import `diagnosticsTest` from `/playwright` to attach browser console, page, and request failures.
36
+ - Import `loadTest` to measure named operations and capture page-navigation timings. It reads `loadTesting` from `quality.json` by default.
37
+ - Import `visualTest` to call `visuals.capture(key)` anywhere inside a Playwright test. Matching keys are grouped across responsive projects. The reporter writes `report.pptx` as image-only responsive screenshot slides by default; set `screenshotOnlySlideshow: false` to use the diagnostic review slideshow instead.
38
+ - Import `writeScreenshotSlideshowFromDirectory` from `/slideshow` to rebuild an image-only deck from persisted screenshot evidence without running Playwright.
39
+ - Use `/spreadsheet` and `/slideshow` to write artifacts from normalized results without using the Playwright reporter.
40
+ - Use `/core`, `/viewports`, and `/load-testing` for shared types and helpers.
41
+
42
+ The repository remains responsible for test scenarios, authentication, browser setup, CI uploads, and retention. See the [reporting guide](https://standards-docs.netlify.app/packages/reporting/) for complete options and output details.
package/dist/core.d.ts ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * packages/reporting/src/core.ts
3
+ * Framework-neutral test evidence models and normalization helpers.
4
+ */
5
+ /** Status values emitted by the reporting pipeline. */
6
+ export declare const VISUAL_STATUSES: readonly ["passed", "failed", "timedOut", "skipped", "interrupted"];
7
+ /** A normalized Playwright result status used by both output writers. */
8
+ export type VisualStatus = (typeof VISUAL_STATUSES)[number];
9
+ /** Load budget grades emitted by the shared timing reporter. */
10
+ export declare const LOAD_GRADES: readonly ["pass", "warning", "fail"];
11
+ /** Grade assigned to one elapsed-time measurement. */
12
+ export type LoadGrade = (typeof LOAD_GRADES)[number];
13
+ /** Kind of work timed by the load reporter. */
14
+ export type LoadMeasurementKind = 'test' | 'page' | 'operation';
15
+ /** Warning and hard-failure thresholds for one measured item, in milliseconds. */
16
+ export type LoadBudget = {
17
+ warnMs: number;
18
+ failMs: number;
19
+ };
20
+ /** Partial budget accepted for per-test or per-operation overrides. */
21
+ export type LoadBudgetOverride = Partial<LoadBudget>;
22
+ /** Input measurement accepted from a fixture or a supplemental test runner. */
23
+ export type LoadMeasurementInput = {
24
+ name: string;
25
+ kind: LoadMeasurementKind;
26
+ durationMs: number;
27
+ budget?: LoadBudgetOverride;
28
+ };
29
+ /** Normalized timing measurement rendered in report artifacts. */
30
+ export type LoadMeasurement = LoadMeasurementInput & {
31
+ budget: LoadBudget;
32
+ grade: LoadGrade;
33
+ };
34
+ /** Optional repo-level load testing settings read from quality.json. */
35
+ export type LoadTestingConfig = {
36
+ enabled?: boolean;
37
+ defaultBudget?: LoadBudgetOverride;
38
+ testBudgets?: Record<string, LoadBudgetOverride>;
39
+ operationBudgets?: Record<string, LoadBudgetOverride>;
40
+ };
41
+ /**
42
+ * A completed or interrupted test result independent of a test framework's internal types.
43
+ * Screenshot paths are resolved before the result is passed to artifact writers.
44
+ */
45
+ export type VisualResult = {
46
+ browserErrors: string;
47
+ duration: number;
48
+ error: string;
49
+ file: string;
50
+ groups: string;
51
+ project: string;
52
+ retry: number;
53
+ screenshot: string;
54
+ status: VisualStatus;
55
+ terminalErrors: string;
56
+ title: string;
57
+ visual: string;
58
+ viewport: string;
59
+ viewportSize: string;
60
+ /** Timings collected for this test; absent for legacy/direct writer inputs. */
61
+ loadMeasurements?: readonly LoadMeasurement[];
62
+ /** Stable repo-relative key used to match per-test load budgets. */
63
+ loadTestId?: string;
64
+ };
65
+ /** Playwright project files used to populate the project summary sheet. */
66
+ export type ProjectDefinition = {
67
+ name: string;
68
+ configuredFiles: readonly string[];
69
+ };
70
+ /** All normalized evidence and run-level diagnostics for one report directory. */
71
+ export type ReportRun = {
72
+ results: readonly VisualResult[];
73
+ runDiagnostics: readonly string[];
74
+ featureFiles: readonly string[];
75
+ projects: readonly ProjectDefinition[];
76
+ /** Whether the reporter should render load timing sections, including an empty summary. */
77
+ loadTestingEnabled?: boolean;
78
+ };
79
+ /**
80
+ * Converts an external status into the normalized reporting status set.
81
+ *
82
+ * @param value External status value.
83
+ * @returns A supported reporting status, defaulting to `failed` for unknown values.
84
+ */
85
+ export declare const normalizeVisualStatus: (val: string) => VisualStatus;
86
+ /**
87
+ * Converts internal status casing into the label shown in artifacts.
88
+ *
89
+ * @param status Normalized reporting status.
90
+ * @returns The human-readable artifact label.
91
+ */
92
+ export declare const statusLabel: (status: VisualStatus) => string;
93
+ /**
94
+ * Counts newline-delimited diagnostic messages while treating an empty value as zero.
95
+ *
96
+ * @param value Newline-delimited diagnostic text.
97
+ * @returns The number of non-empty diagnostic lines.
98
+ */
99
+ export declare const diagnosticCount: (val: string) => number;
100
+ /**
101
+ * Returns one count for every supported status, including statuses absent from the run.
102
+ *
103
+ * @param results Normalized results to count.
104
+ * @returns A complete status-to-count map.
105
+ */
106
+ export declare const statusCounts: (results: readonly VisualResult[]) => Record<VisualStatus, number>;
107
+ /**
108
+ * Preserves configured feature-file order and appends unexpected result files once.
109
+ *
110
+ * @param run Normalized run whose configured and observed files are ordered.
111
+ * @returns Unique feature-file names in report order.
112
+ */
113
+ export declare const orderedFiles: (run: ReportRun) => string[];
114
+ /**
115
+ * Selects the normalized results that belong to one feature file.
116
+ *
117
+ * @param run Normalized run to search.
118
+ * @param file Feature-file name to select.
119
+ * @returns Results associated with the requested feature file.
120
+ */
121
+ export declare const resultsForFile: (run: ReportRun, file: string) => VisualResult[];
122
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
package/dist/core.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * packages/reporting/src/core.ts
3
+ * Framework-neutral test evidence models and normalization helpers.
4
+ */
5
+ /*//////////////////////////////////////////////////
6
+ CONFIG
7
+ //////////////////////////////////////////////////*/
8
+ /** Status values emitted by the reporting pipeline. */
9
+ export const VISUAL_STATUSES = ['passed', 'failed', 'timedOut', 'skipped', 'interrupted'];
10
+ /** Load budget grades emitted by the shared timing reporter. */
11
+ export const LOAD_GRADES = ['pass', 'warning', 'fail'];
12
+ /*//////////////////////////////////////////////////
13
+ HELPERS
14
+ //////////////////////////////////////////////////*/
15
+ /**
16
+ * Converts an external status into the normalized reporting status set.
17
+ *
18
+ * @param value External status value.
19
+ * @returns A supported reporting status, defaulting to `failed` for unknown values.
20
+ */
21
+ export const normalizeVisualStatus = (val) => (VISUAL_STATUSES.includes(val) ? val : 'failed');
22
+ /**
23
+ * Converts internal status casing into the label shown in artifacts.
24
+ *
25
+ * @param status Normalized reporting status.
26
+ * @returns The human-readable artifact label.
27
+ */
28
+ export const statusLabel = (status) => (status === 'timedOut' ? 'timed out' : status);
29
+ /**
30
+ * Counts newline-delimited diagnostic messages while treating an empty value as zero.
31
+ *
32
+ * @param value Newline-delimited diagnostic text.
33
+ * @returns The number of non-empty diagnostic lines.
34
+ */
35
+ export const diagnosticCount = (val) => (val ? val.split('\n').filter(Boolean).length : 0);
36
+ /**
37
+ * Returns one count for every supported status, including statuses absent from the run.
38
+ *
39
+ * @param results Normalized results to count.
40
+ * @returns A complete status-to-count map.
41
+ */
42
+ export const statusCounts = (results) => {
43
+ const counts = Object.fromEntries(VISUAL_STATUSES.map((s) => [s, 0]));
44
+ for (const result of results)
45
+ counts[result.status] += 1;
46
+ return counts;
47
+ };
48
+ /**
49
+ * Preserves configured feature-file order and appends unexpected result files once.
50
+ *
51
+ * @param run Normalized run whose configured and observed files are ordered.
52
+ * @returns Unique feature-file names in report order.
53
+ */
54
+ export const orderedFiles = (run) => [
55
+ ...run.featureFiles.filter((f) => run.results.some((r) => r.file === f)),
56
+ ...run.results
57
+ .map((r) => r.file)
58
+ .filter((f) => !run.featureFiles.includes(f))
59
+ .filter((f, i, v) => v.indexOf(f) === i),
60
+ ];
61
+ /**
62
+ * Selects the normalized results that belong to one feature file.
63
+ *
64
+ * @param run Normalized run to search.
65
+ * @param file Feature-file name to select.
66
+ * @returns Results associated with the requested feature file.
67
+ */
68
+ export const resultsForFile = (run, file) => run.results.filter((r) => r.file === file);
69
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;oDAEoD;AAEpD,uDAAuD;AACvD,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAU,CAAC;AAKnG,gEAAgE;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAU,CAAC;AAiEhE;;oDAEoD;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAW,EAAgB,EAAE,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAmB,CAAC,CAAC,CAAC,CAAE,GAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEvJ;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAoB,EAAU,EAAE,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5G;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAW,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3G;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAgC,EAAgC,EAAE;IAC9F,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAiC,CAAC;IACtG,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAc,EAAY,EAAE,CAAC;IACzD,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;IACxE,GAAG,GAAG,CAAC,OAAO;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;CACzC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAc,EAAE,IAAY,EAAkB,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * packages/reporting/src/load-testing.ts
3
+ * Load timing configuration and grading helpers.
4
+ */
5
+ import type { LoadBudget, LoadBudgetOverride, LoadGrade, LoadMeasurement, LoadMeasurementInput, LoadTestingConfig } from './core.js';
6
+ /** Default warning and failure thresholds used when a repo does not override them. */
7
+ export declare const DEFAULT_LOAD_BUDGET: LoadBudget;
8
+ /** Fully normalized load testing settings. */
9
+ export type NormalizedLoadTestingConfig = {
10
+ enabled: boolean;
11
+ defaultBudget: LoadBudget;
12
+ testBudgets: Readonly<Record<string, LoadBudgetOverride>>;
13
+ operationBudgets: Readonly<Record<string, LoadBudgetOverride>>;
14
+ root?: string;
15
+ };
16
+ /** Optional inputs that have higher precedence than quality.json values. */
17
+ export type LoadTestingOverrides = LoadTestingConfig;
18
+ /**
19
+ * Validates and copies a partial budget supplied by a test helper.
20
+ * @param valandidate partial threshold override.
21
+ * @returns A validated partial budget.
22
+ */
23
+ export declare const validateLoadBudgetOverride: (val: LoadBudgetOverride) => LoadBudgetOverride;
24
+ /**
25
+ * Normalizes raw settings and applies explicit options over quality.json settings.
26
+ * @param raw Values read from quality.json.
27
+ * @param overrides Explicit reporting options with higher precedence.
28
+ * @returns Validated settings with defaults applied.
29
+ */
30
+ export declare const normalizeLoadTestingConfig: (raw?: LoadTestingConfig, overrides?: LoadTestingOverrides) => NormalizedLoadTestingConfig;
31
+ /**
32
+ * Reads load settings from a repository's quality.json and applies explicit overrides.
33
+ * Missing quality.json files preserve the shared 2s/5s defaults.
34
+ * @param file Optional quality.json path; defaults to the current repository root.
35
+ * @param overrides Explicit settings with higher precedence than the file.
36
+ * @returns Validated and normalized settings.
37
+ */
38
+ export declare const readLoadTestingConfig: (file?: string, overrides?: LoadTestingOverrides) => NormalizedLoadTestingConfig;
39
+ /**
40
+ * Creates a stable test-budget key from a repository-relative file and full title path.
41
+ * @param file Absolute or relative test file path.
42
+ * @param titlePath Full test title path, including describe groups.
43
+ * @param root Repository root used to make the file portion portable.
44
+ * @returns A stable `<file>::<title path>` identifier.
45
+ */
46
+ export declare const loadTestId: (file: string, titlePath: readonly string[], root?: string) => string;
47
+ /**
48
+ * Grades one duration against an ordered budget.
49
+ * @param durationMs Elapsed time in milliseconds.
50
+ * @param budget Warning and failure thresholds.
51
+ * @returns Pass, warning, or failure grade.
52
+ */
53
+ export declare const gradeLoad: (durationMs: number, budget: LoadBudget) => LoadGrade;
54
+ /**
55
+ * Resolves and grades one measurement using test/helper overrides, named config, and defaults.
56
+ * @param input Raw timing measurement.
57
+ * @param cfgrmalized repository timing settings.
58
+ * @param testId Stable test key for per-test budgets.
59
+ * @param testOverride Optional budget set directly by the test fixture.
60
+ * @returns A normalized measurement ready for artifacts.
61
+ */
62
+ export declare const gradeLoadMeasurement: (input: LoadMeasurementInput, cfg: NormalizedLoadTestingConfig, testId: string, testOverride?: LoadBudgetOverride) => LoadMeasurement;
63
+ /**
64
+ * Summarizes how many load measurements landed in each grade.
65
+ * @param measurements Measurements to count.
66
+ * @returns Counts including grades that did not occur.
67
+ */
68
+ export declare const loadGradeCounts: (measurements: readonly Pick<LoadMeasurement, "grade">[]) => Record<LoadGrade, number>;
69
+ /**
70
+ * Times an operation, records its duration even when it throws, and preserves its value/error.
71
+ * @param name Stable human-readable operation name.
72
+ * @param opo time.
73
+ * @param recllback receiving the elapsed measurement.
74
+ * @param budget Optional helper-level budget override.
75
+ * @returns The operation result.
76
+ */
77
+ export declare const timeLoadOperation: <T>(name: string, op: () => T | Promise<T>, rec: (measurement: LoadMeasurementInput) => void, budget?: LoadBudgetOverride) => Promise<T>;
78
+ //# sourceMappingURL=load-testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-testing.d.ts","sourceRoot":"","sources":["../src/load-testing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAMrI,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,EAAE,UAA2C,CAAC;AAC9E,8CAA8C;AAC9C,MAAM,MAAM,2BAA2B,GAAG;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,UAAU,CAAC;IAC1B,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC1D,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AACF,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAkBrD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,KAAK,kBAAkB,KAAG,kBAIpE,CAAC;AAsBF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GAAI,MAAK,iBAAsB,EAAE,YAAW,oBAAyB,KAAG,2BAmB9G,CAAC;AACF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,aAAkD,EAAE,YAAW,oBAAyB,KAAG,2BAgBhI,CAAC;AACF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE,WAAW,SAAS,MAAM,EAAE,EAAE,aAAoB,KAAG,MACG,CAAC;AAClG;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,YAAY,MAAM,EAAE,QAAQ,UAAU,KAAG,SAKlE,CAAC;AACF;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,QAAQ,MAAM,EAAE,eAAe,kBAAkB,KAAG,eAUvJ,CAAC;AACF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,cAAc,SAAS,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,EAAE,KAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAIjH,CAAC;AACF;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAU,CAAC,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,EAAE,oBAAoB,KAAK,IAAI,EAAE,SAAS,kBAAkB,KAAG,OAAO,CAAC,CAAC,CAS3K,CAAC"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * packages/reporting/src/load-testing.ts
3
+ * Load timing configuration and grading helpers.
4
+ */
5
+ /*//////////////////////////////////////////////////
6
+ IMPORTS
7
+ //////////////////////////////////////////////////*/
8
+ // API
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { performance } from 'node:perf_hooks';
12
+ // DATA
13
+ import { LOAD_GRADES } from './core.js';
14
+ /*//////////////////////////////////////////////////
15
+ CONFIG
16
+ //////////////////////////////////////////////////*/
17
+ /** Default warning and failure thresholds used when a repo does not override them. */
18
+ export const DEFAULT_LOAD_BUDGET = { warnMs: 2000, failMs: 5000 };
19
+ const isRec = (val) => !!val && typeof val === 'object' && !Array.isArray(val);
20
+ /**
21
+ * Validates one partial budget and returns a fresh object.
22
+ * @param valandidate budget from quality.json or test code.
23
+ * @param label Location used in configuration errors.
24
+ * @returns A validated partial budget.
25
+ */
26
+ const budgetOverride = (val, label) => {
27
+ if (!isRec(val))
28
+ throw new Error(`${label} must be an object with optional warnMs and failMs numbers.`);
29
+ const output = {};
30
+ for (const key of ['warnMs', 'failMs']) {
31
+ if (val[key] === undefined)
32
+ continue;
33
+ if (typeof val[key] !== 'number' || !Number.isFinite(val[key]) || val[key] < 0)
34
+ throw new Error(`${label}.${key} must be a finite non-negative number.`);
35
+ output[key] = val[key];
36
+ }
37
+ return output;
38
+ };
39
+ /**
40
+ * Validates and copies a partial budget supplied by a test helper.
41
+ * @param valandidate partial threshold override.
42
+ * @returns A validated partial budget.
43
+ */
44
+ export const validateLoadBudgetOverride = (val) => {
45
+ const result = budgetOverride(val, 'load budget override');
46
+ if (result.warnMs !== undefined && result.failMs !== undefined && result.failMs <= result.warnMs)
47
+ throw new Error('load budget override.failMs must be greater than warnMs.');
48
+ return result;
49
+ };
50
+ /**
51
+ * Validates a named budget map.
52
+ * @param valandidate map from configuration.
53
+ * @param label Location used in configuration errors.
54
+ * @returns A validated map keyed by test identity or measurement name.
55
+ */
56
+ const budgetMap = (val, label) => {
57
+ if (val === undefined)
58
+ return {};
59
+ if (!isRec(val))
60
+ throw new Error(`${label} must be an object keyed by test identity or measurement name.`);
61
+ return Object.fromEntries(Object.entries(val).map(([key, budget]) => [key, budgetOverride(budget, `${label}.${key}`)]));
62
+ };
63
+ /**
64
+ * Checks that a complete budget has an ordered warning/failure boundary.
65
+ * @param valudget to validate.
66
+ * @param label Location used in configuration errors.
67
+ * @returns The validated budget.
68
+ */
69
+ const completeBudget = (val, label) => {
70
+ if (val.failMs <= val.warnMs)
71
+ throw new Error(`${label}.failMs must be greater than warnMs.`);
72
+ return val;
73
+ };
74
+ /**
75
+ * Normalizes raw settings and applies explicit options over quality.json settings.
76
+ * @param raw Values read from quality.json.
77
+ * @param overrides Explicit reporting options with higher precedence.
78
+ * @returns Validated settings with defaults applied.
79
+ */
80
+ export const normalizeLoadTestingConfig = (raw = {}, overrides = {}) => {
81
+ const rawDefault = budgetOverride(raw.defaultBudget ?? {}, 'loadTesting.defaultBudget');
82
+ const optDefault = budgetOverride(overrides.defaultBudget ?? {}, 'loadTesting options.defaultBudget');
83
+ const defaultBudget = completeBudget({ failMs: optDefault.failMs ?? rawDefault.failMs ?? DEFAULT_LOAD_BUDGET.failMs, warnMs: optDefault.warnMs ?? rawDefault.warnMs ?? DEFAULT_LOAD_BUDGET.warnMs }, 'loadTesting.defaultBudget');
84
+ const mergeMaps = (base, additional) => {
85
+ const merged = { ...base };
86
+ for (const [key, val] of Object.entries(additional))
87
+ merged[key] = { ...merged[key], ...val };
88
+ return merged;
89
+ };
90
+ const testBudgets = mergeMaps(budgetMap(raw.testBudgets, 'loadTesting.testBudgets'), budgetMap(overrides.testBudgets, 'loadTesting options.testBudgets'));
91
+ const opBudgets = mergeMaps(budgetMap(raw.operationBudgets, 'loadTesting.operationBudgets'), budgetMap(overrides.operationBudgets, 'loadTesting options.operationBudgets'));
92
+ for (const [key, value] of Object.entries(testBudgets))
93
+ completeBudget({ ...defaultBudget, ...value }, `loadTesting.testBudgets.${key}`);
94
+ for (const [key, value] of Object.entries(opBudgets))
95
+ completeBudget({ ...defaultBudget, ...value }, `loadTesting.operationBudgets.${key}`);
96
+ const enabled = overrides.enabled ?? raw.enabled ?? true;
97
+ if (typeof enabled !== 'boolean')
98
+ throw new Error('loadTesting.enabled must be a boolean.');
99
+ return { enabled, defaultBudget, testBudgets, operationBudgets: opBudgets };
100
+ };
101
+ /**
102
+ * Reads load settings from a repository's quality.json and applies explicit overrides.
103
+ * Missing quality.json files preserve the shared 2s/5s defaults.
104
+ * @param file Optional quality.json path; defaults to the current repository root.
105
+ * @param overrides Explicit settings with higher precedence than the file.
106
+ * @returns Validated and normalized settings.
107
+ */
108
+ export const readLoadTestingConfig = (file = path.resolve(process.cwd(), 'quality.json'), overrides = {}) => {
109
+ const cfgFile = path.resolve(file);
110
+ if (!fs.existsSync(cfgFile))
111
+ return { ...normalizeLoadTestingConfig({}, overrides), root: process.cwd() };
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(fs.readFileSync(cfgFile, 'utf8'));
115
+ }
116
+ catch (err) {
117
+ throw new Error(`Could not read load testing settings from ${cfgFile}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
118
+ }
119
+ if (!isRec(parsed))
120
+ throw new Error(`Quality configuration at ${cfgFile} must contain a JSON object.`);
121
+ const root = typeof parsed.root === 'string' ? path.resolve(path.dirname(cfgFile), parsed.root) : path.dirname(cfgFile);
122
+ if (parsed.loadTesting === undefined)
123
+ return { ...normalizeLoadTestingConfig({}, overrides), root };
124
+ if (!isRec(parsed.loadTesting))
125
+ throw new Error(`loadTesting in ${cfgFile} must be an object.`);
126
+ const raw = parsed.loadTesting;
127
+ if (raw.enabled !== undefined && typeof raw.enabled !== 'boolean')
128
+ throw new Error(`loadTesting.enabled in ${cfgFile} must be a boolean.`);
129
+ return { ...normalizeLoadTestingConfig(raw, overrides), root };
130
+ };
131
+ /**
132
+ * Creates a stable test-budget key from a repository-relative file and full title path.
133
+ * @param file Absolute or relative test file path.
134
+ * @param titlePath Full test title path, including describe groups.
135
+ * @param root Repository root used to make the file portion portable.
136
+ * @returns A stable `<file>::<title path>` identifier.
137
+ */
138
+ export const loadTestId = (file, titlePath, root = process.cwd()) => `${path.relative(root, path.resolve(file)).split(path.sep).join('/')}::${titlePath.join(' › ')}`;
139
+ /**
140
+ * Grades one duration against an ordered budget.
141
+ * @param durationMs Elapsed time in milliseconds.
142
+ * @param budget Warning and failure thresholds.
143
+ * @returns Pass, warning, or failure grade.
144
+ */
145
+ export const gradeLoad = (durationMs, budget) => {
146
+ if (!Number.isFinite(durationMs) || durationMs < 0)
147
+ throw new Error('Load duration must be a finite non-negative number.');
148
+ if (durationMs > budget.failMs)
149
+ return LOAD_GRADES[2];
150
+ if (durationMs > budget.warnMs)
151
+ return LOAD_GRADES[1];
152
+ return LOAD_GRADES[0];
153
+ };
154
+ /**
155
+ * Resolves and grades one measurement using test/helper overrides, named config, and defaults.
156
+ * @param input Raw timing measurement.
157
+ * @param cfgrmalized repository timing settings.
158
+ * @param testId Stable test key for per-test budgets.
159
+ * @param testOverride Optional budget set directly by the test fixture.
160
+ * @returns A normalized measurement ready for artifacts.
161
+ */
162
+ export const gradeLoadMeasurement = (input, cfg, testId, testOverride) => {
163
+ const cfgd = input.kind === 'test' ? cfg.testBudgets[testId] : cfg.operationBudgets[input.name];
164
+ const budget = completeBudget({
165
+ warnMs: input.budget?.warnMs ?? (input.kind === 'test' ? testOverride?.warnMs : undefined) ?? cfgd?.warnMs ?? cfg.defaultBudget.warnMs,
166
+ failMs: input.budget?.failMs ?? (input.kind === 'test' ? testOverride?.failMs : undefined) ?? cfgd?.failMs ?? cfg.defaultBudget.failMs,
167
+ }, `load budget for ${input.name}`);
168
+ return { ...input, budget, grade: gradeLoad(input.durationMs, budget) };
169
+ };
170
+ /**
171
+ * Summarizes how many load measurements landed in each grade.
172
+ * @param measurements Measurements to count.
173
+ * @returns Counts including grades that did not occur.
174
+ */
175
+ export const loadGradeCounts = (measurements) => {
176
+ const counts = { fail: 0, pass: 0, warning: 0 };
177
+ for (const item of measurements)
178
+ counts[item.grade] += 1;
179
+ return counts;
180
+ };
181
+ /**
182
+ * Times an operation, records its duration even when it throws, and preserves its value/error.
183
+ * @param name Stable human-readable operation name.
184
+ * @param opo time.
185
+ * @param recllback receiving the elapsed measurement.
186
+ * @param budget Optional helper-level budget override.
187
+ * @returns The operation result.
188
+ */
189
+ export const timeLoadOperation = async (name, op, rec, budget) => {
190
+ if (!name.trim())
191
+ throw new Error('A named load measurement is required.');
192
+ const valid = budget ? validateLoadBudgetOverride(budget) : undefined;
193
+ const start = performance.now();
194
+ try {
195
+ return await op();
196
+ }
197
+ finally {
198
+ rec({ name, kind: 'operation', durationMs: performance.now() - start, ...(valid ? { budget: valid } : {}) });
199
+ }
200
+ };
201
+ //# sourceMappingURL=load-testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-testing.js","sourceRoot":"","sources":["../src/load-testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * packages/reporting/src/metadata.ts
3
+ * Neutral metadata helpers for Open XML spreadsheet and slideshow artifacts.
4
+ */
5
+ /** Replaces library-generated application labels with neutral evidence metadata. */
6
+ export declare const neutralizeApplicationMetadata: (file: string) => Promise<void>;
7
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,oFAAoF;AACpF,eAAO,MAAM,6BAA6B,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,IAAI,CAW9E,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * packages/reporting/src/metadata.ts
3
+ * Neutral metadata helpers for Open XML spreadsheet and slideshow artifacts.
4
+ */
5
+ /*//////////////////////////////////////////////////
6
+ IMPORTS
7
+ //////////////////////////////////////////////////*/
8
+ // API
9
+ import fs from 'node:fs/promises';
10
+ // COMPONENTS
11
+ import JSZip from 'jszip';
12
+ /*//////////////////////////////////////////////////
13
+ CONFIG
14
+ //////////////////////////////////////////////////*/
15
+ /** Replaces library-generated application labels with neutral evidence metadata. */
16
+ export const neutralizeApplicationMetadata = async (file) => {
17
+ const archive = await JSZip.loadAsync(await fs.readFile(file));
18
+ const applicationProperties = archive.file('docProps/app.xml');
19
+ if (!applicationProperties)
20
+ return;
21
+ archive.file('docProps/app.xml', (await applicationProperties.async('string'))
22
+ .replace(/<Application>[^<]*<\/Application>/, '<Application>Test evidence reporter</Application>')
23
+ .replace(/<Company>[^<]*<\/Company>/, '<Company>Test evidence</Company>'));
24
+ await fs.writeFile(file, await archive.generateAsync({ type: 'nodebuffer' }));
25
+ };
26
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;oDAEoD;AAEpD,MAAM;AACN,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,aAAa;AACb,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;oDAEoD;AAEpD,oFAAoF;AACpF,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;IAClF,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC/D,IAAI,CAAC,qBAAqB;QAAE,OAAO;IACnC,OAAO,CAAC,IAAI,CACX,kBAAkB,EAClB,CAAC,MAAM,qBAAqB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;SAC3C,OAAO,CAAC,mCAAmC,EAAE,mDAAmD,CAAC;SACjG,OAAO,CAAC,2BAA2B,EAAE,kCAAkC,CAAC,CAC1E,CAAC;IACF,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC,CAAC"}