From 924c7e96a3bc4e182254289eb4d6b472d2f95371 Mon Sep 17 00:00:00 2001 From: Devon Powell Date: Thu, 21 May 2026 17:33:24 -0400 Subject: [PATCH] Add report configuration schema v2 --- docs/report-configuration-format.md | 5 +- schemas/report-configuration/v2.json | 127 ++++++++++++++++++ src/helpers/report-builder.cjs | 20 ++- src/helpers/report-configuration.cjs | 69 +++++++++- src/helpers/schema.cjs | 3 + src/reporters/webdriverio.cjs | 2 + test/integration/data/configs/mocha.cjs | 1 + test/integration/data/configs/playwright.js | 1 + .../data/configs/web-test-runner.js | 1 + test/integration/data/configs/webdriverio.cjs | 1 + 10 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 schemas/report-configuration/v2.json diff --git a/docs/report-configuration-format.md b/docs/report-configuration-format.md index c7c2d767..9835c805 100644 --- a/docs/report-configuration-format.md +++ b/docs/report-configuration-format.md @@ -30,5 +30,6 @@ lists patterns to ignore when generating a report. ``` If `type` is omitted at the top level then every entry in `overrides` must -specify `type`. The same rule applies to `tool` and `experience`. If all three -are omitted at the top level then `overrides` is required. +specify `type`. The same rule applies to `tool` and `experience`. If `type`, +`tool` and `experience` are all omitted at the top level then `overrides` is +required. diff --git a/schemas/report-configuration/v2.json b/schemas/report-configuration/v2.json new file mode 100644 index 00000000..00891742 --- /dev/null +++ b/schemas/report-configuration/v2.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "/test-reporting/schemas/report-configuration/v2.json", + "$ref": "#/$defs/taxonomyObject", + "type": "object", + "unevaluatedProperties": false, + "properties": { + "ignorePatterns": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/nonEmptyUnpaddedString" + } + }, + "overrides": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "unevaluatedProperties": false, + "minProperties": 2, + "$ref": "#/$defs/taxonomyObject", + "properties": { + "pattern": { + "$ref": "#/$defs/nonEmptyUnpaddedString" + } + }, + "required": [ + "pattern" + ] + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "const": null + } + } + }, + "then": { + "properties": { + "overrides": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "$ref": "#/$defs/taxonomyObject/properties/type" + } + }, + "required": [ + "type" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "tool": { + "const": null + } + } + }, + "then": { + "properties": { + "overrides": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tool": { + "$ref": "#/$defs/taxonomyObject/properties/tool" + } + }, + "required": [ + "tool" + ] + } + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": null + }, + "tool": { + "const": null + } + } + }, + "then": { + "required": [ + "overrides" + ] + } + } + ], + "$defs": { + "nonEmptyUnpaddedString": { + "type": "string", + "minLength": 1, + "pattern": "^(?!\\s).+(? { + const { experience, overrides, ...rest } = configuration; + const upgraded = { ...rest }; + + if (experience != null) { + logger.warning('Report configuration field \'experience\' is no longer supported and will be ignored'); + } + + if (overrides) { + const upgradedOverrides = []; + + for (const override of overrides) { + const { experience: overrideExperience, ...overrideRest } = override; + + if (overrideExperience != null) { + logger.warning(`Report configuration override for pattern '${override.pattern}' has field 'experience' which is no longer supported and will be ignored`); + } + + if (Object.keys(overrideRest).length < 2) { + logger.warning(`Report configuration override for pattern '${override.pattern}' has no remaining fields after upgrade and will be dropped`); + + continue; + } + + upgradedOverrides.push(overrideRest); + } + + if (upgradedOverrides.length > 0) { + upgraded.overrides = upgradedOverrides; + } + } + + return upgraded; +}; + class ReportConfiguration { - constructor(path) { + constructor(path, logger, { configurationVersionLatest = false } = {}) { let reportConfiguration; let reportConfigurationPath; + this._configurationVersionLatest = configurationVersionLatest; + if (path) { path = resolve(path); @@ -45,7 +86,15 @@ class ReportConfiguration { reportConfigurationPath = makeRelativeFilePath(path); } - if (!validateReportConfigurationV1Ajv(reportConfiguration)) { + if (configurationVersionLatest) { + reportConfiguration = upgradeReportConfigurationV1ToV2(reportConfiguration, logger); + + if (!validateReportConfigurationV2Ajv(reportConfiguration)) { + const { errors } = validateReportConfigurationV2Ajv; + + throw new Error(formatErrorAjv(errors, { dataVar: 'report configuration' })); + } + } else if (!validateReportConfigurationV1Ajv(reportConfiguration)) { const { errors } = validateReportConfigurationV1Ajv; throw new Error(formatErrorAjv(errors, { dataVar: 'report configuration' })); @@ -76,7 +125,10 @@ class ReportConfiguration { if (minimatch(filePath, pattern)) { metadata.type = overriddenType?.toLowerCase(); metadata.tool = overriddenTool; - metadata.experience = overriddenExperience; + + if (!this._configurationVersionLatest) { + metadata.experience = overriddenExperience; + } break; } @@ -90,7 +142,10 @@ class ReportConfiguration { metadata.type = metadata.type ?? defaultType?.toLowerCase(); metadata.tool = metadata.tool ?? defaultTool; - metadata.experience = metadata.experience ?? defaultExperience; + + if (!this._configurationVersionLatest) { + metadata.experience = metadata.experience ?? defaultExperience; + } return metadata; } @@ -98,6 +153,10 @@ class ReportConfiguration { hasTaxonomy(filePath) { const taxonomy = this.getTaxonomy(filePath); + if (this._configurationVersionLatest) { + return taxonomy.type != null && taxonomy.tool != null; + } + return taxonomy.type != null && taxonomy.tool != null && taxonomy.experience != null; diff --git a/src/helpers/schema.cjs b/src/helpers/schema.cjs index b3411613..89fe11ce 100644 --- a/src/helpers/schema.cjs +++ b/src/helpers/schema.cjs @@ -13,6 +13,7 @@ addErrors(ajv); addFormats(ajv, ['date-time', 'uri', 'uuid']); ajv.addSchema(require('../../schemas/report-configuration/v1.json')); +ajv.addSchema(require('../../schemas/report-configuration/v2.json')); ajv.addSchema(require('../../schemas/report/v1.json')); ajv.addSchema(require('../../schemas/report/v2.json')); ajv.addSchema(latestReportSchema); @@ -39,6 +40,7 @@ ajv.addSchema({ }); const validateReportConfigurationV1Ajv = ajv.getSchema('/test-reporting/schemas/report-configuration/v1.json'); +const validateReportConfigurationV2Ajv = ajv.getSchema('/test-reporting/schemas/report-configuration/v2.json'); const validateReportV1ContextAjv = ajv.getSchema('/test-reporting/schemas/report/v1/context/loose.json'); const validateReportV2ContextAjv = ajv.getSchema('/test-reporting/schemas/report/v2/context/loose.json'); const validateReportV3ContextAjv = ajv.getSchema('/test-reporting/schemas/report/v3/context/loose.json'); @@ -53,6 +55,7 @@ const { details: { items: { properties: { browser: { enum: latestSupportedBrowse module.exports = { formatErrorAjv, validateReportConfigurationV1Ajv, + validateReportConfigurationV2Ajv, validateReportV1ContextAjv, validateReportV2ContextAjv, validateReportV3ContextAjv, diff --git a/src/reporters/webdriverio.cjs b/src/reporters/webdriverio.cjs index 20da0bc8..ab0c92e1 100644 --- a/src/reporters/webdriverio.cjs +++ b/src/reporters/webdriverio.cjs @@ -19,6 +19,7 @@ class WebdriverIO extends WDIOReporter { this._baseReportPath = options.reportPath || './d2l-test-report.json'; this._reportConfigurationPath = options.reportConfigurationPath || './d2l-test-reporting.config.json'; this._reportVersionLatest = options.reportVersionLatest || false; + this._reportConfigurationVersionLatest = options.reportConfigurationVersionLatest || false; this._verbose = options.verbose || false; this._logger = logger; this._report = null; @@ -65,6 +66,7 @@ class WebdriverIO extends WDIOReporter { reportPath: workerReportPath, reportConfigurationPath: this._reportConfigurationPath, reportVersionLatest: this._reportVersionLatest, + reportConfigurationVersionLatest: this._reportConfigurationVersionLatest, verbose: this._verbose }); console.log('[D2L Reporter] Initialized successfully'); diff --git a/test/integration/data/configs/mocha.cjs b/test/integration/data/configs/mocha.cjs index 170f0975..def44925 100644 --- a/test/integration/data/configs/mocha.cjs +++ b/test/integration/data/configs/mocha.cjs @@ -5,6 +5,7 @@ module.exports = { reporterOptions: [ 'reportPath=./d2l-test-report-mocha.json', 'reportVersionLatest=true', + 'reportConfigurationVersionLatest=true', 'verbose=true' ] }; diff --git a/test/integration/data/configs/playwright.js b/test/integration/data/configs/playwright.js index 9c366999..2aaa2971 100644 --- a/test/integration/data/configs/playwright.js +++ b/test/integration/data/configs/playwright.js @@ -3,6 +3,7 @@ import { defineConfig, devices } from '@playwright/test'; const playwrightReporterOptions = { reportPath: './d2l-test-report-playwright.json', reportVersionLatest: true, + reportConfigurationVersionLatest: true, verbose: true }; diff --git a/test/integration/data/configs/web-test-runner.js b/test/integration/data/configs/web-test-runner.js index dd4703e0..80cb0b23 100644 --- a/test/integration/data/configs/web-test-runner.js +++ b/test/integration/data/configs/web-test-runner.js @@ -9,6 +9,7 @@ export default { reporter({ reportPath: './d2l-test-report-web-test-runner.json', reportVersionLatest: true, + reportConfigurationVersionLatest: true, verbose: true }) ], diff --git a/test/integration/data/configs/webdriverio.cjs b/test/integration/data/configs/webdriverio.cjs index 400b1336..2fcb859f 100644 --- a/test/integration/data/configs/webdriverio.cjs +++ b/test/integration/data/configs/webdriverio.cjs @@ -26,6 +26,7 @@ exports.config = { reportPath: './d2l-test-report-webdriverio.json', reportConfigurationPath: './d2l-test-reporting.config.json', reportVersionLatest: true, + reportConfigurationVersionLatest: true, verbose: true }] ],