From aba0076b990085d4f67f0bebaf909c7e9d5c60ba Mon Sep 17 00:00:00 2001 From: anpingli Date: Mon, 5 Jan 2026 10:11:57 +0800 Subject: [PATCH] Logging-ui-plugin test using the real data --- web/cypress.config.ts | 120 +- web/cypress/README.md | 8 + .../logging/adminConsoleAggregatedLogs.cy.ts | 81 + .../e2e/logging/adminConsoleObserveLogs.cy.ts | 215 ++ .../logging/devConsoleAggregatedLogs.cy.ts | 113 + .../e2e/logging/devConsoleObserveLogs.cy.ts | 216 ++ web/cypress/e2e/logging/testUtils.cy.ts | 531 +++++ web/cypress/fixtures/data-test.ts | 232 ++ web/cypress/support/commands.ts | 13 - web/cypress/support/commands/auth-commands.ts | 215 ++ web/cypress/support/commands/log-commands.ts | 453 ++++ .../support/commands/selector-commands.ts | 116 + .../support/commands/utility-commands.ts | 142 ++ web/cypress/support/e2e.ts | 40 +- web/cypress/support/index.ts | 35 + .../support/scripts/findLogsInLokistack.sh | 68 + web/cypress/views/nav.ts | 31 + web/cypress/views/tour.ts | 17 + web/cypress/views/utils.ts | 43 + web/package-lock.json | 2010 +++++++++++++++-- web/package.json | 8 +- web/scripts/run-cypress-logging.sh | 231 ++ 22 files changed, 4782 insertions(+), 156 deletions(-) create mode 100644 web/cypress/README.md create mode 100644 web/cypress/e2e/logging/adminConsoleAggregatedLogs.cy.ts create mode 100644 web/cypress/e2e/logging/adminConsoleObserveLogs.cy.ts create mode 100644 web/cypress/e2e/logging/devConsoleAggregatedLogs.cy.ts create mode 100644 web/cypress/e2e/logging/devConsoleObserveLogs.cy.ts create mode 100644 web/cypress/e2e/logging/testUtils.cy.ts create mode 100644 web/cypress/fixtures/data-test.ts delete mode 100644 web/cypress/support/commands.ts create mode 100644 web/cypress/support/commands/auth-commands.ts create mode 100644 web/cypress/support/commands/log-commands.ts create mode 100644 web/cypress/support/commands/selector-commands.ts create mode 100644 web/cypress/support/commands/utility-commands.ts create mode 100644 web/cypress/support/index.ts create mode 100644 web/cypress/support/scripts/findLogsInLokistack.sh create mode 100644 web/cypress/views/nav.ts create mode 100644 web/cypress/views/tour.ts create mode 100644 web/cypress/views/utils.ts create mode 100755 web/scripts/run-cypress-logging.sh diff --git a/web/cypress.config.ts b/web/cypress.config.ts index fcedccf8c..3b6935a03 100644 --- a/web/cypress.config.ts +++ b/web/cypress.config.ts @@ -1,13 +1,131 @@ import { defineConfig } from 'cypress'; +const fs = require('fs'); +const path = require('path'); +const report_dir = process.env.ARTIFACT_DIR || '/tmp'; export default defineConfig({ + screenshotsFolder: path.join(report_dir, 'cypress', 'screenshots'), + screenshotOnRunFailure: true, + trashAssetsBeforeRuns: true, + videosFolder: path.join(report_dir, 'cypress', 'videos'), + video: true, + videoCompression: false, + reporter: './node_modules/cypress-multi-reporters', + reporterOptions: { + reporterEnabled: 'mocha-junit-reporter, mochawesome', + mochaJunitReporterReporterOptions: { + mochaFile: path.join(report_dir, 'junit_cypress-[hash].xml'), + toConsole: false + }, + mochawesomeReporterOptions: { + reportDir: report_dir, + reportFilename: 'cypress_report', + overwrite: false, + html: false, + json: true + } + }, + env: { + grepFilterSpecs: false, + 'KUBECONFIG_PATH': process.env.KUBECONFIG, + 'NOO_CS_IMAGE': process.env.MULTISTAGE_PARAM_OVERRIDE_CYPRESS_NOO_CS_IMAGE, + 'OPENSHIFT_VERSION': process.env.CYPRESS_OPENSHIFT_VERSION, + }, + fixturesFolder: 'fixtures', + defaultCommandTimeout: 30000, + retries: { + runMode: 0, + openMode: 0, + }, + viewportWidth: 1600, + viewportHeight: 1200, e2e: { - baseUrl: 'http://localhost:9003', + baseUrl: process.env.CYPRESS_BASE_URL || process.env.BASE_URL || 'http://localhost:9003', setupNodeEvents(on, config) { // eslint-disable-next-line @typescript-eslint/no-var-requires require('@cypress/code-coverage/task')(on, config); + on('before:browser:launch', (browser = { + name: "", + family: "chromium", + channel: "", + displayName: "", + version: "", + majorVersion: "", + path: "", + isHeaded: false, + isHeadless: false + }, launchOptions) => { + if (browser.family === 'chromium' && browser.name !== 'electron') { + // auto open devtools + launchOptions.args.push('--enable-precise-memory-info') + } + + return launchOptions + + }); + // `on` is used to hook into various events Cypress emits + on('task', { + log(message) { + console.log(message); + return null; + }, + logError(message) { + console.error(message); + return null; + }, + logTable(data) { + console.table(data); + return null; + }, + readFileIfExists(filename) { + if (fs.existsSync(filename)) { + return fs.readFileSync(filename, 'utf8'); + } + return null; + }, + }); + on('after:screenshot', (details) => { + // Prepend "1_", "2_", etc. to screenshot filenames because they are sorted alphanumerically in CI's artifacts dir + const pathObj = path.parse(details.path); + fs.readdir(pathObj.dir, (error, files) => { + const newPath = `${pathObj.dir}${path.sep}${files.length}_${pathObj.base}`; + return new Promise((resolve, reject) => { + // eslint-disable-next-line consistent-return + fs.rename(details.path, newPath, (err) => { + if (err) return reject(err); + // because we renamed and moved the image, resolve with the new path + // so it is accurate in the test results + resolve({ path: newPath }); + }); + }); + }); + }); + on( + 'after:spec', + (spec: Cypress.Spec, results: CypressCommandLine.RunResult) => { + if (results && results.video) { + // Do we have failures for any retry attempts? + const failures = results.tests.some((test) => + test.attempts.some((attempt) => attempt.state === 'failed') + ) + if (!failures && fs.existsSync(results.video)) { + // delete the video if the spec passed and no tests retried + fs.unlinkSync(results.video) + } + } + } + ); + require('@cypress/grep/src/plugin')(config); return config; }, + supportFile: './cypress/support/e2e.ts', + specPattern: './cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', + numTestsKeptInMemory: 1, + testIsolation: false, + experimentalModifyObstructiveThirdPartyCode: true, + experimentalOriginDependencies: true, + experimentalMemoryManagement: true, + experimentalCspAllowList: ['default-src', 'script-src'] }, numTestsKeptInMemory: 2, video: false, diff --git a/web/cypress/README.md b/web/cypress/README.md new file mode 100644 index 000000000..c42763d4b --- /dev/null +++ b/web/cypress/README.md @@ -0,0 +1,8 @@ +#The following file are copyed from https://github.com/openshift/monitoring-plugin/tree/main/web. This will be synced manually. +web/cypress/support/commands/auth-commands.ts +web/cypress/support/commands/selector-commands.ts +web/cypress/support/commands/utility-commands.ts +web/cypress/views/nav.ts +web/cypress/views/tour.ts +web/cypress/views/utils.ts +web/src/components/data-test.ts diff --git a/web/cypress/e2e/logging/adminConsoleAggregatedLogs.cy.ts b/web/cypress/e2e/logging/adminConsoleAggregatedLogs.cy.ts new file mode 100644 index 000000000..853e6fb37 --- /dev/null +++ b/web/cypress/e2e/logging/adminConsoleAggregatedLogs.cy.ts @@ -0,0 +1,81 @@ +import { TestIds } from '../../../src/test-ids'; +import { aggrLogTest, observeLogTest, commonTest } from './testUtils.cy.ts'; +import { APP_NAMESPACE1,APP_NAMESPACE2,APP_MESSAGE } from './testUtils.cy.ts'; + +describe('AdminConsole: Admin in AggregatedLogs', { tags: ['@admin'] }, () => { + before( function() { + cy.uiLoginAsClusterAdmin("first_user"); + }); + + beforeEach( function() { + cy.showAdminConsolePodAggrLog(APP_NAMESPACE1); + }); + + after( function() { + cy.uiLogoutClusterAdmin("first_user"); + }); + + aggrLogTest(); + commonTest(); + it('admin can display infra container logs',{tags:['@aggr']}, () => { + cy.showAdminConsolePodAggrLog('openshift-monitoring','alertmanager-main-0'); + const indexFields : IndexField = [ + { name: '_timestamp', value: "" }, + { name: 'k8s_container_name', value: "" }, + { name: 'k8s_namespace_name', value: "openshift-monitoring" }, + { name: 'k8s_node_name', value: "" }, + { name: 'k8s_pod_name', value: "alertmanager-main-0" }, + { name: 'openshift_log_type', value: "infrastructure" }, + { name: 'log_source', value: "container" }, + { name: 'hostname', value: "" }, + { name: 'openshift_cluster_id', value: "" }, + { name: 'openshift_sequence', value: "" }, + ]; + cy.assertFieldsInLogDetail(indexFields) + }); +}) + +describe('AdminConsole: Impersonate User in AggregatedLogs',{ tags: ['@admin'] }, () => { + before( function() { + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.cliLogin("second_user") + cy.uiLoginAsClusterAdmin("first_user"); + cy.uiImpersonateUser("second_user"); + cy.switchToAdmConsole(); + }); + + beforeEach( function() { + cy.showAdminConsolePodAggrLog(APP_NAMESPACE1); + }); + + after( function() { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + }); + + aggrLogTest(); + commonTest(); +}) + +describe('AdminConsole: User in Aggregated Logs', { tags: ['@user'] }, () => { + before( function() { + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginUser("second_user"); + }); + + beforeEach( function() { + cy.showAdminConsolePodAggrLog(APP_NAMESPACE1); + }); + + after( function() { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + }); + + aggrLogTest(); + commonTest(); +}) diff --git a/web/cypress/e2e/logging/adminConsoleObserveLogs.cy.ts b/web/cypress/e2e/logging/adminConsoleObserveLogs.cy.ts new file mode 100644 index 000000000..4956476c8 --- /dev/null +++ b/web/cypress/e2e/logging/adminConsoleObserveLogs.cy.ts @@ -0,0 +1,215 @@ +import { TestIds } from '../../../src/test-ids'; +import { aggrLogTest, observeLogTest, commonTest, getRunningPodName } from './testUtils.cy.ts'; +import { APP_NAMESPACE1,APP_NAMESPACE2,APP_MESSAGE } from './testUtils.cy.ts'; + + +function admConsoleUserObsTest() { + it('user can not display audit logs',{tags:['@observ']}, () => { + cy.byTestID(TestIds.TenantToggle) + .click() + .get('#logging-view-tenant-dropdown') + .contains('button', 'audit') + .click() + cy.byTestID(TestIds.LogsTable) + .should('exist') + .within(() => { + cy.get('div.lv-plugin__table__row-error', { timeout: 600 }).should('contain', 'Forbidden'); + }) + }); +} + +function admConsoleObserveTest(){ + it('validate elements in core observeLogs',{tags:['@observ']}, () => { + const commonElements = [ + TestIds.ToggleHistogramButton, + TestIds.TimeRangeDropdown, + TestIds.RefreshIntervalDropdown, + TestIds.SyncButton, + TestIds.AvailableAttributes, + TestIds.SeverityDropdown, + TestIds.ShowStatsToggle, + TestIds.ExecuteVolumeButton, + TestIds.ExecuteQueryButton, + TestIds.ShowQueryToggle, + TestIds.LogsTable, + ]; + commonElements.forEach(id => { + cy.byTestID(id).should('exist'); + }); + + cy.byTestID(TestIds.TenantToggle).should('exist'); + cy.byTestID(TestIds.AttributeFilters).within(() => { + cy.byTestID(TestIds.AvailableAttributes).click(); + cy.contains('li', 'Content'); + cy.contains('li', 'Pod'); + cy.contains('li', 'Containers'); + cy.contains('li', 'Namespaces'); + }) + if (Cypress.env('CLUSTERLOGGING_DATAMODE') === "select" ) { + cy.byTestID(TestIds.SchemaToggle).should('exist'); + } + }) + + it('selected containers',{tags:['@observ']}, () => { + getRunningPodName(APP_NAMESPACE1).as('pod1Name'); + getRunningPodName(APP_NAMESPACE2).as('pod2Name'); + + cy.get('@pod1Name').then((pod1Name) => { + cy.get('@pod2Name').then((pod2Name) => { + const containers = [`${pod1Name} / centos-logtest`,`${pod2Name} / centos-logtest`] + cy.log(`container1=${pod1Name} / centos-logtest, container2=${pod2Name} / centos-logtest`); + cy.checkLogContainers(containers) + + cy.showQueryInput(); + let pattern = /{ kubernetes_container_name="centos-logtest", kubernetes_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } | json/; + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + pattern = /{ k8s_container_name="centos-logtest", k8s_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } /; + } + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('match', pattern); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + const indexFields : IndexField = [ + { name: 'k8s_namespace_name', value: `${APP_NAMESPACE1}|${APP_NAMESPACE2}` }, + { name: 'k8s_pod_name', value: `${pod1Name}|${pod2Name}` }, + { name: 'k8s_container_name', value: 'centos-logtest' }, + ] + cy.assertFieldsInLogDetail(indexFields) + }); + }); + }) +} + +describe('AdminConsole:: Admin in ObserveLogs', { tags: ['@admin'] }, () => { + before( function() { + cy.uiLoginAsClusterAdmin("first_user"); + }); + + beforeEach( function() { + // Load the other page to ensure Observe-Logs in clean status + cy.clickNavLink(['Home', 'Search']) + cy.clickNavLink(['Observe', 'Logs']) + }); + + after( function() { + cy.uiLogoutClusterAdmin("first_user"); + }); + admConsoleObserveTest(); + observeLogTest(); + commonTest(); + it('admin can display infra logs',{tags:['@observ']}, () => { + cy.selectLogTenant('infrastructure') + let query = '{ log_type="infrastructure" } | json' + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + query = '{ log_type="infrastructure" }' + } + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('eq', query) + cy.assertInfraLogsInLogsTable(); + }); + + it('admin can display infra container logs',{tags:['@observ']}, () => { + cy.selectLogTenant('infrastructure') + cy.runLogQuery('{{}log_type="infrastructure"{}}|json|log_source="container"'); + + const indexFields : IndexField = [ + { name: '_timestamp', value: "" }, + { name: 'k8s_container_name', value: "" }, + { name: 'k8s_namespace_name', value: "" }, + { name: 'k8s_node_name', value: "" }, + { name: 'k8s_pod_name', value: "" }, + { name: 'openshift_log_type', value: "infrastructure" }, + { name: 'log_source', value: "container" }, + { name: 'hostname', value: "" }, + { name: 'openshift_cluster_id', value: "" }, + { name: 'openshift_sequence', value: "" }, + ]; + cy.assertFieldsInLogDetail(indexFields) + }); + + it('admin can display infra node logs',{tags:['@smoke','@observ']}, () => { + cy.selectLogTenant('infrastructure') + cy.runLogQuery('{{}log_type="infrastructure"{}}|json|log_source="node"') + const indexFields : IndexField = [ + { name: '_timestamp', value: "" }, + { name: 'log_type', value: "infrastructure" }, + { name: 'openshift_log_type', value: "infrastructure" }, + { name: 'log_source', value: "node" }, + { name: 'hostname', value: "" }, + { name: 'openshift_cluster_id', value: "" }, + { name: 'openshift_sequence', value: "" }, + ]; + cy.assertFieldsInLogDetail(indexFields); + }); + + it('admin can display audit logs',{tags:['@observ']}, () => { + cy.selectLogTenant('audit') + let query = '{ log_type="audit" } | json' + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + query = '{ log_type="audit" }' + } + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('eq', query) + cy.assertAuditLogsInLogsTable(); + }); +}) + +describe.skip('AdminConsole: Impersonate User in ObserveLogs', { tags: ['@admin'] }, () => { + before( function() { + cy.cliLogin("second_user"); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginAsClusterAdmin("first_user"); + cy.uiImpersonateUser("second_user"); + cy.switchToAdmConsole(); + }); + + beforeEach( function() { + // Load the other page to ensure Observe-Logs in clean status + cy.clickNavLink(['Home', 'Search']) + cy.clickNavLink(['Observe', 'Logs']) + }); + + after( function() { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + }); + + admConsoleObserveTest(); + admConsoleUserObsTest(); + observeLogTest(); + commonTest(); +}) + +describe.skip('AdminConsole: User in ObserveLogs', { tags: ['@user'] }, () => { + before( function() { + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`) + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`) + cy.uiLoginUser("second_user"); + }); + + beforeEach( function() { + // Load the other page to ensure Observe-Logs in clean status + cy.clickNavLink(['Home', 'Search']) + cy.clickNavLink(['Observe', 'Logs']) + }); + + after( function() { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + }); + + admConsoleObserveTest(); + admConsoleUserObsTest(); + observeLogTest(); + commonTest(); +}) diff --git a/web/cypress/e2e/logging/devConsoleAggregatedLogs.cy.ts b/web/cypress/e2e/logging/devConsoleAggregatedLogs.cy.ts new file mode 100644 index 000000000..1c7df745b --- /dev/null +++ b/web/cypress/e2e/logging/devConsoleAggregatedLogs.cy.ts @@ -0,0 +1,113 @@ +import { TestIds } from '../../../src/test-ids'; +import { isDevConsoleReady, aggrLogTest, commonTest } from './testUtils.cy.ts'; +import { APP_NAMESPACE1,APP_NAMESPACE2,APP_MESSAGE } from './testUtils.cy.ts'; + +let SKIPALL= false + +function devConsoleUserAggrTest(){ + it('user can not display infra logs',{tags:['@aggr']}, () => { + cy.runLogQuery('{{} k8s_namespace_name="openshift-monitoring" {}}'); + cy.getByTestId(TestIds.LogsTable) + .should('contain.text', 'Warning alert:No datapoints found'); + }); +} + +describe('DevConsole: Admin in AggregatedLogs ', { tags: ['@admin'] }, () => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + SKIPALL= true + cy.task('log','DeveloperConsole is not ready — skipping suite'); + this.skip() + } + }); + cy.uiLoginAsClusterAdmin("first_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + cy.showDevConsolePodAggrLog(APP_NAMESPACE1) + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutClusterAdmin("first_user"); + } + }); + aggrLogTest(); + commonTest(); + + it('admin can display infra container logs', {tags: ['@aggr'] }, () => { + //load Aggregated Logs for pod in openshift-monitoring + cy.showDevConsolePodAggrLog('openshift-monitoring') + cy.assertInfraLogsInLogsTable(); + }); +}) + +describe('DevConsole: Impersonate User in AggregatedLogs', { tags: ['@admin'] },() => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + cy.task('log','DeveloperConsole is not ready — skipping suite'); + SKIPALL= true + this.skip() + } + }); + cy.cliLogin("second_user"); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginAsClusterAdmin("first_user"); + cy.uiImpersonateUser("second_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + cy.showDevConsolePodAggrLog(APP_NAMESPACE1) + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + } + }); + devConsoleUserAggrTest(); + aggrLogTest(); + commonTest() +}) + +describe('DevConsole: User in AggregatedLogs', { tags: ['@admin'] }, () => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + cy.task('log','DeveloperConsole is not ready — skipping suite'); + SKIPALL= true + this.skip() + } + }); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginUser("second_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + //hover on project page + cy.showDevConsolePodAggrLog(APP_NAMESPACE1) + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + } + }); + devConsoleUserAggrTest(); + aggrLogTest(); + commonTest(); +}); diff --git a/web/cypress/e2e/logging/devConsoleObserveLogs.cy.ts b/web/cypress/e2e/logging/devConsoleObserveLogs.cy.ts new file mode 100644 index 000000000..f2f837802 --- /dev/null +++ b/web/cypress/e2e/logging/devConsoleObserveLogs.cy.ts @@ -0,0 +1,216 @@ +import { TestIds } from '../../../src/test-ids'; +import { isDevConsoleReady, observeLogTest, commonTest, getRunningPodName } from './testUtils.cy.ts'; +import { APP_NAMESPACE1,APP_NAMESPACE2,APP_MESSAGE } from './testUtils.cy.ts'; + +let SKIPALL= false + +function devConsoleObsTest(){ + it('validate elements in dev observeLogs',{tags:['@devobserv']}, () => { + const commonElements = [ + TestIds.ToggleHistogramButton, + TestIds.TimeRangeDropdown, + TestIds.RefreshIntervalDropdown, + TestIds.SyncButton, + TestIds.AvailableAttributes, + TestIds.SeverityDropdown, + TestIds.ShowStatsToggle, + TestIds.ExecuteVolumeButton, + TestIds.ExecuteQueryButton, + TestIds.ShowQueryToggle, + TestIds.LogsTable, + ]; + commonElements.forEach(id => { + cy.byTestID(id).should('exist'); + }); + + cy.byTestID(TestIds.TenantToggle).should('not.exist'); //Specical feature + cy.byTestID(TestIds.AttributeFilters).within(() => { + cy.byTestID(TestIds.AvailableAttributes).click(); + cy.contains('li', 'Content'); + cy.contains('li', 'Pod'); + cy.contains('li', 'Containers'); + cy.contains('li', 'Namespaces'); + }) + if (Cypress.env('CLUSTERLOGGING_DATAMODE') === "select" ) { + cy.byTestID(TestIds.SchemaToggle).should('exist'); + } + }) + + it('selected containers',{tags:['@devobserv']}, () => { + getRunningPodName(APP_NAMESPACE1).as('pod1Name'); + getRunningPodName(APP_NAMESPACE2).as('pod2Name'); + + cy.get('@pod1Name').then((pod1Name) => { + cy.get('@pod2Name').then((pod2Name) => { + //const containers = [`${pod1Name} / centos-logtest`,`${pod2Name} / centos-logtest`] + //cy.log(`container1=${pod1Name} / centos-logtest, container2=${pod2Name} / centos-logtest`); + const containers = ['centos-logtest'] + cy.log('containers = centos-logtest') + cy.checkLogContainers(containers); + + cy.showQueryInput(); + let pattern = /{ kubernetes_container_name="centos-logtest", kubernetes_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } | json/; + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + pattern = /{ k8s_container_name="centos-logtest", k8s_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } /; + } + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('match', pattern); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + const indexFields : IndexField = [ + { name: 'k8s_namespace_name', value: `${APP_NAMESPACE1}|${APP_NAMESPACE2}` }, + { name: 'k8s_pod_name', value: `${pod1Name}|${pod2Name}` }, + { name: 'k8s_container_name', value: 'centos-logtest' }, + ] + cy.assertFieldsInLogDetail(indexFields) + }); + }); + }) + +} + +function devConsoleUserObsTest(){ + it('user can not display infra logs',{tags:['@smoke','@devobserv']}, () => { + cy.runLogQuery('{{} k8s_namespace_name="openshift-monitoring" {}}') + cy.getByTestId(TestIds.LogsTable) + .should('contain.text', 'No datapoints found'); + }); +} + +describe('DevConsole: Admin in ObserveLogs', { tags: ['@admin'] }, () => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + cy.task('log','DeveloperConsole is not ready — skipping suite'); + SKIPALL= true + this.skip() + return; // stop execution in this callback + } + }); + cy.uiLoginAsClusterAdmin("first_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + // reload observe->logs for current pod in APP_NAMESPACE1 + cy.clickNavLink(['Observe']) + cy.changeNamespace(APP_NAMESPACE1) + cy.byLegacyTestID('horizontal-link-Logs').click(); + cy.assertLogsInLogsTable(); + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutClusterAdmin("first_user"); + } + }); + + devConsoleObsTest(); + observeLogTest(); + commonTest(); + + it('admin can display infra logs',{tags:['@smoke','@devobserv']}, () => { + // reload observe->logs + cy.changeNamespace('openshift-monitoring'); + + let query = '{ kubernetes_namespace_name="openshift-monitoring" } | json' + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + query = '{ k8s_namespace_name="openshift-monitoring" }' + } + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('eq', query) + cy.assertInfraLogsInLogsTable(); + }); + + it('admin can not query without namespace',{tags:['@smoke','@devobserv']}, () => { + let queryText = `{{}log_type="infrastructure" {}}` + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .clear() + .type(queryText, { delay: 0 }) + cy.byTestID(TestIds.ExecuteQueryButton).should('be.disabled'); + cy.byTestID(TestIds.LogsQueryInput) + .should('contain.text', 'Please select a namespace'); + }); +}) + +describe('DevConsole: Impersonate User in ObserveLogs',{ tags: ['@admin'] }, () => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + cy.task('log','DeveloperConsole is not ready — skipping suite'); + SKIPALL= true + this.skip(); + return; // stop execution in this callback + } + }); + cy.cliLogin("second_user"); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginAsClusterAdmin("first_user"); + cy.uiImpersonateUser("second_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + // reload observe->logs for current pod in APP_NAMESPACE1 + cy.clickNavLink(['Observe']) + cy.changeNamespace(APP_NAMESPACE1) + cy.byLegacyTestID('horizontal-link-Logs').click(); + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + } + }); + devConsoleObsTest(); + devConsoleUserObsTest(); + observeLogTest(); + commonTest(); +}) + +describe('DevConsole: User in ObserveLogs', { tags: ['@user'] }, () => { + before( function() { + //Check if DevConsole is ready + isDevConsoleReady().then((ready) => { + if (!ready) { + cy.task('log','DeveloperConsole is not ready — skipping suite'); + SKIPALL= true + this.skip() + return; // stop execution in this callback + } + }); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.grantLogViewRoles("second_user", `${APP_NAMESPACE2}`); + cy.uiLoginUser("second_user"); + cy.switchToDevConsole(); + }); + + beforeEach( function() { + cy.clickNavLink(['Observe']) + cy.changeNamespace(APP_NAMESPACE1) + cy.byLegacyTestID('horizontal-link-Logs').click(); + }); + + after( function() { + if (!SKIPALL) { + cy.uiLogoutUser("second_user"); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE1}`); + cy.removeLogViewRoles("second_user", `${APP_NAMESPACE2}`); + } + }); + devConsoleObsTest(); + devConsoleUserObsTest(); + observeLogTest(); + commonTest(); +}) diff --git a/web/cypress/e2e/logging/testUtils.cy.ts b/web/cypress/e2e/logging/testUtils.cy.ts new file mode 100644 index 000000000..52afe0d05 --- /dev/null +++ b/web/cypress/e2e/logging/testUtils.cy.ts @@ -0,0 +1,531 @@ +//Common logging UI Cases +//Note: the default namespace is APP_NAMESPACE1 for all test in this file +import { TestIds } from '../../../src/test-ids'; +export const APP_NAMESPACE1 = "log-test-app1"; +export const APP_NAMESPACE2 = "log-test-app2"; +export const APP_MESSAGE = "SVTLogger"; + +export function isDevConsoleReady(): boolean { + //Check if DevConsole is enabled in 4.19+ + cy.task('log','Check if devConsole is enabled'); + const rawversion = Cypress.env('OPENSHIFT_VERSION'); + if (!rawversion) { + throw new Error('OPENSHIFT_VERSION is not defined'); + } + const version = String(rawversion) + const [major, minor] = version.split('.').map(Number); + if (major > 4 || (major === 4 && minor > 18)) { + return cy.exec("oc get console.operator cluster -o jsonpath='{.spec.customization.perspectives}'", { failOnNonZeroExit: false }) + .then((result) => { + const devReady = result.stdout === '[{"id":"dev","visibility":{"state":"Enabled"}}]'; + return devReady; + }); + } + return cy.wrap(true); +} + +export function getRunningPodName(namespace: string, labelSelector?: string) { + // Build the oc command + let cmd = `oc get pods -n ${namespace} --field-selector=status.phase=Running -o jsonpath="{.items[0].metadata.name}"` + if (labelSelector) { + cmd += ` -l ${labelSelector}` + } + return cy.exec(cmd).then((res) => res.stdout.trim()) +} + +export function aggrLogTest() { + it('validate elements in Aggregated Logs',{tags:['@aggr']}, () => { + const commonElements = [ + TestIds.ToggleHistogramButton, + TestIds.TimeRangeDropdown, + TestIds.RefreshIntervalDropdown, + TestIds.SyncButton, + TestIds.AvailableAttributes, + TestIds.SeverityDropdown, + TestIds.ShowStatsToggle, + TestIds.ExecuteVolumeButton, + TestIds.ExecuteQueryButton, + TestIds.ShowQueryToggle, + TestIds.LogsTable, + ]; + commonElements.forEach(id => { + cy.byTestID(id).should('exist'); + }); + + cy.byTestID(TestIds.TenantToggle).should('not.exist'); //Specical feature + cy.byTestID(TestIds.AttributeFilters).within(() => { + cy.byTestID(TestIds.AvailableAttributes).click(); + cy.contains('li', 'Content'); + cy.contains('li', 'Pod'); + cy.contains('li', 'Containers'); + cy.contains('li', 'Namespaces').should('not.exist'); //Specical feature + }) + if (Cypress.env('CLUSTERLOGGING_DATAMODE') === "select" ) { + cy.byTestID(TestIds.SchemaToggle).should('exist'); + } + }) + + it('Show Resources',{tags:['@common']}, () => { + cy.get('button').contains('Show Resources').click(); + getRunningPodName(APP_NAMESPACE1).then((pod1Name) => { + const pods = [pod1Name] + cy.checkLogPods(pods); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.byTestID(TestIds.LogsTable).within(() => { + cy.get('td[data-label="message"]') + .first() + .within(()=> { + cy.get(`a[href="/k8s/cluster/namespaces/${APP_NAMESPACE1}"]`).should('exist'); + cy.get(`a[href="/k8s/ns/${APP_NAMESPACE1}/pods/${pod1Name}"]`).should('exist'); + cy.get(`a[href="/k8s/ns/${APP_NAMESPACE1}/pods/${pod1Name}/containers/centos-logtest"]`).should('exist') + }); + }); + }); + }); + + //verify we can select both running and deleted pods + it('select both running and deleted pods',{tags:['@aggr']}, () => { + getRunningPodName(APP_NAMESPACE1).as('pod1Name'); + cy.get('@pod1Name').then((podName) => { + cy.exec(`oc -n ${APP_NAMESPACE1} delete pods ${podName} --wait=true`); + }); + getRunningPodName(APP_NAMESPACE1).as('pod1NewName'); + cy.get('@pod1NewName').then((pod1NewName) => { + cy.exec(`oc -n ${APP_NAMESPACE1} wait pods/${pod1NewName} --for=condition=Ready`); + }); + + cy.get('@pod1Name').then((pod1Name) => { + cy.get('@pod1NewName').then((pod1NewName) => { + const pods = [pod1Name, pod1NewName] + cy.log(`pod1Name=${pod1Name},pod1NewName=${pod1NewName}`); + //cy.task('log', `pod1Name=${pod1Name} pod1NewName=${pod1NewName}`); + cy.checkLogPods(pods); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .then((val) => { + //{ kubernetes_pod_name=~"centos-logtest-xx|centos-logtest-yyy" + expect(val).to.include(pod1Name) + expect(val).to.include(pod1NewName) + }); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: "application" }, + { name: 'k8s_namespace_name', value: APP_NAMESPACE1 }, + { name: 'k8s_pod_name', value: `${pod1Name}|${pod1NewName}` }, + ] + cy.assertFieldsInLogDetail(indexFields); + }); + }); + }); + + it('selected containers',{tags:['@aggr']}, () => { + const containers = ['centos-logtest'] + cy.log(`container=centos-logtest`); + cy.checkLogContainers(containers); + + cy.showQueryInput(); + let pattern = /{ kubernetes_container_name="centos-logtest", kubernetes_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } | json/; + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) === "otel") { + pattern = /{ k8s_container_name="centos-logtest", k8s_pod_name=~"centos-logtest-\w+|centos-logtest-\w+" } /; + } + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('match', pattern); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + const indexFields : IndexField = [ + { name: 'k8s_namespace_name', value: `${APP_NAMESPACE1}` }, + { name: 'k8s_container_name', value: 'centos-logtest' }, + ] + cy.assertFieldsInLogDetail(indexFields) + }); +} + +export function observeLogTest() { + + it('Show Resources',{tags:['@common']}, () => { + cy.get('button').contains('Show Resources').click(); + getRunningPodName(APP_NAMESPACE1).then((pod1Name) => { + const namespaces = [APP_NAMESPACE1] + const pods = [pod1Name] + cy.checkLogNamespaces(namespaces); + cy.checkLogPods(pods); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.byTestID(TestIds.LogsTable).within(() => { + cy.get('td[data-label="message"]') + .first() + .within(()=> { + cy.get(`a[href="/k8s/cluster/namespaces/${APP_NAMESPACE1}"]`).should('exist'); + cy.get(`a[href="/k8s/ns/${APP_NAMESPACE1}/pods/${pod1Name}"]`).should('exist'); + cy.get(`a[href="/k8s/ns/${APP_NAMESPACE1}/pods/${pod1Name}/containers/centos-logtest"]`).should('exist') + }); + }); + }); + }); + + it('selected namespaces',{tags:['@observ']}, () => { + const namespaces=[APP_NAMESPACE1, APP_NAMESPACE2] + cy.checkLogNamespaces(namespaces); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .then((val) => { + expect(val).to.include(APP_NAMESPACE1); + expect(val).to.include(APP_NAMESPACE2); + }); + + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: "application" }, + { name: 'k8s_namespace_name', value: `${APP_NAMESPACE1}|${APP_NAMESPACE2}` }, + ] + cy.assertFieldsInLogDetail(indexFields) + }); + + //verify we can select both running and deleted pods + it('select both running and deleted pods',{tags:['@observ']}, () => { + getRunningPodName(APP_NAMESPACE1).as('pod1Name'); + cy.get('@pod1Name').then((podName) => { + cy.exec(`oc -n ${APP_NAMESPACE1} delete pods ${podName} --wait=true`); + }); + getRunningPodName(APP_NAMESPACE1).as('pod1NewName'); + cy.get('@pod1NewName').then((pod1NewName) => { + cy.exec(`oc -n ${APP_NAMESPACE1} wait pods/${pod1NewName} --for=condition=Ready`); + }); + getRunningPodName(APP_NAMESPACE2).as('pod2Name'); + + cy.get('@pod1Name').then((pod1Name) => { + cy.get('@pod1NewName').then((pod1NewName) => { + cy.get('@pod2Name').then((pod2Name) => { + const pods = [pod1Name, pod1NewName, pod2Name] + cy.log(`pod1Name=${pod1Name},pod1NewName=${pod1NewName}, pod2Name=${pod2Name}`); + cy.checkLogPods(pods); + //cy.task('log', `pod1Name=${pod1Name} pod1NewName=${pod1NewName}, pod2Name=${pod2Name} `); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .then((val) => { + //{ kubernetes_pod_name=~"centos-logtest-xx|centos-logtest-yyy|centos-logtest-zzz" + expect(val).to.include(pod1Name); + expect(val).to.include(pod1NewName); + expect(val).to.include(pod2Name); + }); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: "application" }, + { name: 'k8s_namespace_name', value: `${APP_NAMESPACE1}|${APP_NAMESPACE2}` }, + { name: 'k8s_pod_name', value: `${pod1Name}|${pod1NewName}|${pod2Name}` }, + ] + cy.assertFieldsInLogDetail(indexFields); + }); + }); + }); + }); +} + +export function commonTest() { + it('display applicatioins logs',{tags:['@common']}, () => { + cy.runLogQuery(`{{}kubernetes_namespace_name="${APP_NAMESPACE1}" {}}`) + cy.assertAppLogsInLogsTable(); + }); + + // search APP_NAMESPACE1, this ensure the case succeed in Observe/Logs when there multiple namespace + it('Search by content ',{tags:['@common']}, () => { + cy.selectLogAttribute('Content'); + cy.byTestID(TestIds.AttributeFilters).within(() => { + cy.get('input[aria-label="Search by Content"]') + .clear() + .type('SVTLogger', {delay: 0}) + }); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('include', 'SVTLogger') + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertAppLogsInLogsTable(); + }) + + it('filter logs by last duration ',{tags:['@common']}, () => { + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 5 minutes').click(); + }); + cy.url().should('match', /start=now-5m&end=now/); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 2 hours').click(); + }); + cy.url().should('match', /start=now-2h&end=now/); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 1 day').click(); + }); + cy.url().should('match', /start=now-1d&end=now/); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 2 weeks').click(); + }); + cy.url().should('match', /start=now-2w&end=now/); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + // recover to 1 hour + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 1 hour').click(); + }); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + }); + + it('filter logs by custom range',{tags:['@common']}, () => { + const pad = (num) => num.toString().padStart(2, '0'); + const now = new Date(); + // startDate = now-3day + const startDate = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000) + const startDay = `${startDate.getFullYear()}-${pad(startDate.getMonth() + 1)}-${pad(startDate.getDate())}`; + // endDate = now-2day + const endDate = new Date(now.getTime() - 2* 24 * 60 * 60 * 1000) + // Format as 'YYYY-MM-DD' + const endDay = `${endDate.getFullYear()}-${pad(endDate.getMonth() + 1)}-${pad(endDate.getDate())}`; + // Format as 'hh:mm' + const startTime = `${pad(startDate.getHours())}:${pad(startDate.getMinutes())}`; + const endTime = `${pad(startDate.getHours())}:${pad(startDate.getMinutes())}`; + + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Custom time range').click(); + }); + cy.byTestID(TestIds.TimeRangeSelectModal).within(() => { + cy.get('input[aria-label="Date picker"]').first().clear().type(`${startDay}`).blur(); + cy.get('input[aria-label="Precision time picker"]').first().clear().type(`${startTime}{enter}`); + + cy.get('input[aria-label="Date picker"]').last().clear().type(`${endDay}`).blur(); + cy.get('input[aria-label="Precision time picker"]').last().clear().type(`${endTime}{enter}`); + }); + cy.byTestID(TestIds.TimeRangeDropdownSaveButton).click(); + cy.byTestID(TestIds.TimeRangeDropdown) + .within(() => { + cy.contains(`${startDay} ${startTime} - ${endDay} ${endTime}`); + }); + + //Remove milleseconds as we won't provide it in console + const start = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), startDate.getHours(), startDate.getMinutes()) + const end = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate(), endDate.getHours(), endDate.getMinutes()) + cy.url().should('match', new RegExp(`start=${start.getTime()}&end=${end.getTime()}`)); + // recover to 1 hour + + cy.byTestID(TestIds.TimeRangeDropdown) + .click() + .within(() => { + cy.contains('Last 1 hour').click(); + }); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.assertLogsInLogsTable() + }); + + //Check the fileds in Log detail for viaq + it('validate Viaq log format for container',{tags:['@common']}, function () { + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) !== "viaq") { + this.skip(); + } + const isoTimestampRegex = '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$' + const timestampPattern = /^[A-Z][a-z]{2} \d{1,2}, \d{4}, \d{2}:\d{2}:\d{2}\.\d{3}$/; + const viaqlogFormat = '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} - SVTLogger - INFO - .*$' + + const viaqUniqueFields : IndexField = [ + { name: '_timestamp', value: isoTimestampRegex }, + { name: 'hostname', value: "" }, + { name: 'message', value: "" }, + { name: 'kubernetes_container_id', value: "" }, + { name: 'kubernetes_container_image', value: "" }, + { name: 'kubernetes_container_iostream', value: "" }, + { name: 'kubernetes_pod_ip', value: "" }, + { name: 'kubernetes_pod_id', value: "" }, + { name: 'kubernetes_pod_owner', value: "" }, + { name: 'kubernetes_namespace_id',value: "" }, + { name: 'openshift_cluster_id', value: "" }, + { name: 'openshift_sequence', value: "" }, + { name: 'level', value: "" }, + { name: 'log_source', value: "container" }, + ]; + const otelFields : IndexField = [ + { name: 'k8s_container_name', value: "" }, + { name: 'k8s_namespace_name', value: "" }, + { name: 'k8s_node_name', value: "" }, + { name: 'k8s_pod_name', value: "" }, + { name: 'kubernetes_container_name',value: "" }, + { name: 'kubernetes_host', value: "" }, + { name: 'kubernetes_namespace_name', value: "" }, + { name: 'kubernetes_pod_name', value: "" }, + { name: 'log_type', value: "application" }, + { name: 'openshift_log_type', value: "application" }, + ]; + const mergedFields = [...viaqUniqueFields, ...otelFields]; + + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('include', '| json'); + + cy.byTestID(TestIds.LogsTable) + .should('exist') + .within(() => { + cy.get('td[data-label="date"]') + .first() + .invoke('text') + .should('match', timestampPattern); + cy.get('td[data-label="message"]') + .first() + .invoke('text') + .should('match', viaqlogFormat); + }); + cy.assertFieldsInLogDetail(mergedFields) + }); + + it('validate Otel log format for container',{tags:['@common']}, function(){ + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) != "otel" ) { + this.skip(); + } + const viaqUniqueFields : IndexField = [ + { name: '_timestamp', value: isoTimestampRegex }, + { name: 'hostname', value: "" }, + { name: 'message', value: "" }, + { name: 'kubernetes_container_id', value: "" }, + { name: 'kubernetes_container_image', value: "" }, + { name: 'kubernetes_container_iostream', value: "" }, + { name: 'kubernetes_pod_ip', value: "" }, + { name: 'kubernetes_pod_id', value: "" }, + { name: 'kubernetes_pod_owner', value: "" }, + { name: 'kubernetes_namespace_id',value: "" }, + { name: 'openshift_cluster_id', value: "" }, + { name: 'openshift_sequence', value: "" }, + { name: 'level', value: "" }, + { name: 'log_source', value: "container" }, + ]; + const otelFields : IndexField = [ + { name: 'k8s_container_name', value: "" }, + { name: 'k8s_namespace_name', value: "" }, + { name: 'k8s_node_name', value: "" }, + { name: 'k8s_pod_name', value: "" }, + { name: 'kubernetes_container_name',value: "" }, + { name: 'kubernetes_host', value: "" }, + { name: 'kubernetes_namespace_name', value: "" }, + { name: 'kubernetes_pod_name', value: "" }, + { name: 'log_type', value: "application" }, + { name: 'openshift_log_type', value: "application" }, + ]; + + const timestampPattern = /^[A-Z][a-z]{2} \d{1,2}, \d{4}, \d{2}:\d{2}:\d{2}\.\d{3}$/; + const otellogFormat = /^\{.*"@timestamp":".+?",.*"hostname":".+?",.*"kubernetes":\{.*\},.*"level":"\w+",.*"log_source":"container",.*"log_type":"application",.*"message":".+?",.*"openshift":\{.*\}.*\}$/ + + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('not.include', '| json'); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + + cy.byTestID(TestIds.LogsTable) + .should('exist') + .within(() => { + cy.get('td[data-label="date"]') + .first() + .invoke('text') + .should('match', timestampPattern); + + cy.get('td[data-label="message"]') + .first() + .invoke('text') + .should('match', otellogFormat); + + }); + cy.assertFieldsInLogDetail(otelFields) + }); + + it('switch the dataFormat',{tags:['@common']}, function () { + if (String(Cypress.env('CLUSTERLOGGING_DATAMODE')) != "select" ) { + this.skip(); + } + const viaqlogFormat = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} - SVTLogger - INFO - .*$/ + const otellogFormat = /^\{.*"@timestamp":".+?",.*"hostname":".+?",.*"kubernetes":\{.*\},.*"level":"\w+",.*"log_source":"container",.*"log_type":"application",.*"message":".+?",.*"openshift":\{.*\}.*\}$/ + + //default viaq + cy.byTestID(TestIds.SchemaToggle) + .invoke('text') + .should('eq', 'viaq'); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('include', '| json'); + cy.byTestID(TestIds.LogsTable) + .within(() => { + // Check first message matches viaqlogFormat + cy.get('td[data-label="message"]') + .first() + .invoke('text') + .should('match', viaqlogFormat); + }); + + //switch to Otel + cy.byTestID(TestIds.SchemaToggle).click({force: true}); + cy.get('li') + .contains('button', 'otel') + .click(); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('not.include', '| json'); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.byTestID(TestIds.LogsTable) + .should('exist') + .within(() => { + // Check first message matches otellogFormat + cy.get('td[data-label="message"]') + .first() + .invoke('text') + .should('match', otellogFormat); + }); + + //switch back to Viaq + cy.byTestID(TestIds.SchemaToggle).click({force: true}); + cy.get('li') + .contains('button', 'viaq') + .click(); + cy.showQueryInput(); + cy.byTestID(TestIds.LogsQueryInput) + .find('textarea') + .invoke('val') + .should('include', '| json'); + cy.byTestID(TestIds.ExecuteQueryButton).click(); + cy.byTestID(TestIds.LogsTable) + .should('exist') + .within(() => { + cy.get('td[data-label="message"]') + .first() + .invoke('text') + .should('match', viaqlogFormat); + }); + }); +} diff --git a/web/cypress/fixtures/data-test.ts b/web/cypress/fixtures/data-test.ts new file mode 100644 index 000000000..5d3479045 --- /dev/null +++ b/web/cypress/fixtures/data-test.ts @@ -0,0 +1,232 @@ +export const DataTestIDs = { + AlertCluster: 'alert-cluster', + AlertResourceIcon: 'alert-resource-icon', + AlertResourceLink: 'alert-resource-link', + AlertNamespace: 'alert-namespace', + AlertState: 'alert-state', + AlertSource: 'alert-source', + AlertingRuleArrow: 'alerting-rule-arrow', + AlertingRuleResourceIcon: 'alerting-rule-resource-icon', + AlertingRuleResourceLink: 'alerting-rule-resource-link', + AlertingRuleSeverityBadge: 'alerting-rule-severity-badge', + AlertingRuleStateBadge: 'alerting-rule-state-badge', + AlertingRuleTotalAlertsBadge: 'alerting-rule-total-alerts-badge', + LabelSuggestion: 'suggestion-line', + CancelButton: 'cancel-button', + Breadcrumb: 'breadcrumb', + DownloadCSVButton: 'download-csv-button', + EmptyBoxBody: 'empty-box-body', + ExpireSilenceButton: 'expire-silence-button', + ExpireXSilencesButton: 'expire-x-silences-button', + Expression: 'expression', + KebabDropdownButton: 'kebab-dropdown-button', + MastHeadHelpIcon: 'help-dropdown-toggle', + MastHeadApplicationItem: 'application-launcher-item', + MetricGraph: 'metric-graph', + MetricGraphNoDatapointsFound: 'datapoints-msg', + MetricGraphTimespanDropdown: 'graph-timespan-dropdown', + MetricGraphTimespanInput: 'graph-timespan-input', + MetricDisconnectedCheckbox: 'disconnected-checkbox', + MetricDropdownPollInterval: 'dropdown-poll-interval', + MetricGraphUnitsDropDown: 'graph-units-dropdown', + MetricHideShowGraphButton: 'hide-show-graph-button', + MetricResetZoomButton: 'reset-zoom-button', + MetricStackedCheckbox: 'stacked-checkbox', + MetricsPageActionsDropdownButton: 'actions-dropdown-button', + MetricsPageAddQueryButton: 'add-query-button', + MetricsPageAddQueryDropdownItem: 'add-query-dropdown-item', + MetricsPageDeleteAllQueriesDropdownItem: 'delete-all-queries-dropdown-item', + MetricsPageDeleteQueryDropdownItem: 'delete-query-dropdown-item', + MetricsPageDisableEnableQuerySwitch: 'disable-enable-query-switch', + MetricsPageDuplicateQueryDropdownItem: 'duplicate-query-dropdown-item', + MetricsPageDisableEnableQueryDropdownItem: 'disable-enable-query-dropdown-item', + MetricsPageExpandCollapseRowButton: 'expand-collapse-row-button', //div + MetricsPageExpandCollapseAllDropdownItem: 'expand-collapse-all-dropdown-item', + MetricsPageExportCsvDropdownItem: 'export-csv-dropdown-item', + MetricsPageHideShowAllSeriesDropdownItem: 'hide-show-all-series-dropdown-item', + MetricsPageInsertExampleQueryButton: 'insert-example-query-button', + MetricsPageNoQueryEnteredTitle: 'no-query-entered-title', + MetricsPageNoQueryEntered: 'no-query-entered', + MetricsPageQueryTable: 'query-table', + MetricsPageRunQueriesButton: 'run-queries-button', + MetricsPageSelectAllUnselectAllButton: 'select-all-unselect-all-button', + MetricsPageSeriesButton: 'series-button', + MetricsPageYellowNoDatapointsFound: 'yellow-no-datapoints-found', + NameInput: 'name-filter-input', + NameLabelDropdown: 'console-select-menu-toggle', + NamespaceDropdownMenuLink: 'dropdown-menu-item-link', + NameLabelDropdownOptions: 'console-select-item', + NamespaceDropdownShowSwitch: 'showSystemSwitch', + NamespaceDropdownTextFilter: 'dropdown-text-filter', + PersesDashboardDropdown: 'dashboard-dropdown', + SeverityBadgeHeader: 'severity-badge-header', + SeverityBadge: 'severity-badge', + SilenceAlertDropdownItem: 'silence-alert-dropdown-item', + SilenceButton: 'silence-button', + SilenceEditDropdownItem: 'silence-edit-dropdown-item', + SilenceExpireDropdownItem: 'silence-expire-dropdown-item', + SilenceRecreateDropdownItem: 'silence-recreate-dropdown-item', + SilenceResourceIcon: 'silence-resource-icon', + SilenceResourceLink: 'silence-resource-link', + SilencesPageFormTestIDs: { + AddLabel: 'add-label', + AlertLabelsDescription: 'alert-labels-description', + Comment: 'comment', + Creator: 'creator', + Description: 'description-header', + LabelName: 'label-name', + LabelValue: 'label-value', + NegativeMatcherCheckbox: 'negative-matcher-checkbox', + Regex: 'regex-checkbox', + RemoveLabel: 'remove-label', + SilenceFrom: 'silence-from', + SilenceFor: 'silence-for', + SilenceForToggle: 'silence-for-toggle', + SilenceUntil: 'silence-until', + StartImmediately: 'start-immediately', + }, + TypeaheadSelectInput: 'query-select-typeahead-input', + Table: 'OUIA-Generated-Table', //table ouiaid - ID to be used with byOUIAID(DataTestIDs.Table) + MetricsGraphAlertDanger: 'OUIA-Generated-Alert-danger', //ID to be used with byOUIAID(DataTestIDs.MetricsGraphAlertDanger) + + // Incidents Page Test IDs + IncidentsPage: { + Toolbar: 'incidents-toolbar', + DaysSelect: 'incidents-days-select', + DaysSelectToggle: 'incidents-days-select-toggle', + DaysSelectList: 'incidents-days-select-list', + DaysSelectOption: 'incidents-days-select-option', + FiltersSelect: 'incidents-filters-select', + FiltersSelectToggle: 'incidents-filters-select-toggle', + FiltersSelectList: 'incidents-filters-select-list', + FiltersSelectOption: 'incidents-filters-select-option', + FilterChip: 'incidents-filter-chip', + FilterChipRemove: 'incidents-filter-chip-remove', + ClearAllFiltersButton: 'incidents-clear-all-filters', + ToggleChartsButton: 'incidents-toggle-charts', + LoadingSpinner: 'incidents-loading-spinner', + }, + + // Incidents Chart Test IDs + IncidentsChart: { + Card: 'incidents-chart-card', + Title: 'incidents-chart-title', + ChartContainer: 'incidents-chart-container', + LoadingSpinner: 'incidents-chart-loading-spinner', + ChartBars: 'incidents-chart-bars', + ChartBar: 'incidents-chart-bar', + }, + + // Alerts Chart Test IDs + AlertsChart: { + Card: 'alerts-chart-card', + Title: 'alerts-chart-title', + EmptyState: 'alerts-chart-empty-state', + ChartContainer: 'alerts-chart-container', + ChartBar: 'alerts-chart-bar', + }, + + // Incidents Table Test IDs + IncidentsTable: { + Table: 'incidents-alerts-table', + ExpandButton: 'incidents-table-expand-button', + Row: 'incidents-table-row', + ComponentCell: 'incidents-table-component-cell', + SeverityCell: 'incidents-table-severity-cell', + StateCell: 'incidents-table-state-cell', + }, + + // Incidents Details Row Table Test IDs + IncidentsDetailsTable: { + Table: 'incidents-details-table', + LoadingSpinner: 'incidents-details-loading-spinner', + Row: 'incidents-details-row', + AlertRuleCell: 'incidents-details-alert-rule-cell', + NamespaceCell: 'incidents-details-namespace-cell', + SeverityCell: 'incidents-details-severity-cell', + StateCell: 'incidents-details-state-cell', + StartCell: 'incidents-details-start-cell', + EndCell: 'incidents-details-end-cell', + AlertRuleLink: 'incidents-details-alert-rule-link', + }, +}; + +export const LegacyDashboardPageTestIDs = { + TimeRangeDropdown: 'time-range-dropdown', //div + TimeRangeDropdownOptions: 'time-range-dropdown-options', + PollIntervalDropdown: 'poll-interval-dropdown', //div + PollIntervalDropdownOptions: 'poll-interval-dropdown-options', + Inspect: 'inspect', + ExportAsCsv: 'export-as-csv', + DashboardDropdown: 'dashboard-dropdown', //div + DashboardTimeRangeDropdownMenu: 'monitoring-time-range-dropdown', //div using get('#'+LegacyDashboardPageTestIDs.DashboardTimeRangeDropdownMenu) + DashboardRefreshIntervalDropdownMenu: 'refresh-interval-dropdown', //div using get('#'+LegacyDashboardPageTestIDs.DashboardRefreshIntervalDropdownMenu) + Graph: 'graph', +}; + +export const LegacyTestIDs = { + ItemFilter: 'item-filter', + SelectAllSilencesCheckbox: 'select-all-silences-checkbox', + PersesDashboardSection: 'dashboard', + NamespaceBarDropdown: 'namespace-bar-dropdown', +}; + +export const IDs = { + ChartAxis0ChartLabel: 'chart-axis-0-ChartLabel', //id^=IDs.ChartAxis0ChartLabel AxisX + ChartAxis1ChartLabel: 'chart-axis-1-ChartLabel', //id^=IDs.ChartAxis1ChartLabel AxisY +}; + +export const Classes = { + ExpandedRow: 'button[class="pf-v6-c-button pf-m-plain pf-m-expanded"]', + ToExpandRow: 'button[class="pf-v6-c-button pf-m-plain"]', + FilterDropdown: '.pf-v6-c-menu-toggle, .pf-v5-c-menu-toggle', + FilterDropdownExpanded: '.pf-v6-c-menu-toggle.pf-m-expanded, .pf-v5-c-menu-toggle.pf-m-expanded', + FilterDropdownOption: '.pf-v6-c-menu__item, .pf-c-select__menu-item', + GraphCardInlineInfo: + '.pf-v6-c-alert.pf-m-inline.pf-m-plain.pf-m-info, .pf-v5-c-alert.pf-m-inline.pf-m-plain.pf-m-info.query-browser__reduced-resolution', + HorizontalNav: '.pf-v6-c-tabs__item, .co-m-horizontal-nav__menu-item', + IndividualTag: '.pf-v6-c-label__text, .pf-v5-c-chip__text', + LabelTag: '.pf-v6-c-label__text, .pf-v5-c-label__text', + MainTag: '.pf-v6-c-label-group__label, .pf-v5-c-chip-group__label', + MenuItem: '.pf-v6-c-menu__item, .pf-c-dropdown__menu-item', + MenuItemDisabled: '.pf-v6-c-menu__list-item.pf-m-aria-disabled', + MenuToggle: '.pf-v6-c-menu-toggle, .pf-c-dropdown__toggle', + MetricsPagePredefinedQueriesMenuItem: '.pf-v6-c-menu__item, .pf-v5-c-select__menu-item', + MetricsPageRows: '.pf-v6-c-data-list.pf-m-grid-md', + MetricsPageExpandedRowIcon: '.pf-v6-c-data-list__item.pf-m-expanded', //li + MetricsPageCollapsedRowIcon: '.pf-v6-c-data-list__item', //li + MetricsPageQueryInput: '.cm-content.cm-lineWrapping', + MetricsPageUngraphableResults: '.pf-v6-c-title.pf-m-md', + MetricsPageUngraphableResultsDescription: '.pf-v6-c-empty-state__body', + MetricsPageQueryAutocomplete: '.cm-tooltip-autocomplete.cm-tooltip.cm-tooltip-below', + MoreLessTag: '.pf-v6-c-label-group__label, .pf-v5-c-chip-group__label', + NamespaceDropdown: '.pf-v6-c-menu-toggle.co-namespace-dropdown__menu-toggle', + SectionHeader: '.pf-v6-c-title.pf-m-h2, .co-section-heading', + TableHeaderColumn: '.pf-v6-c-table__button, .pf-c-table__button', + SilenceAlertTitle: '.pf-v6-c-alert__title, .pf-v5-c-alert__title', + SilenceAlertDescription: '.pf-v6-c-alert__description, .pf-v5-c-alert__description', + SilenceCommentWithoutError: '.pf-v6-c-form-control.pf-m-textarea.pf-m-resize-both', + SilenceCommentWithError: '.pf-v6-c-form-control.pf-m-textarea.pf-m-resize-both.pf-m-error', + SilenceCreatorWithError: '.pf-v6-c-form-control.pf-m-error', + SilenceHelpText: '.pf-v6-c-helper-text__item-text, .pf-v5-c-helper-text__item-text', + SilenceKebabDropdown: '.pf-v6-c-menu-toggle.pf-m-plain, .pf-v5-c-dropdown__toggle.pf-m-plain', + SilenceLabelRow: '.pf-v6-l-grid.pf-m-all-12-col-on-sm.pf-m-all-4-col-on-md.pf-m-gutter, .row', + SilenceState: '.pf-v6-l-stack__item, .co-break-word', + LogDetail: 'pf-v5-c-table__td lv-plugin__table__details', + LogToolbar: 'pf-v5-c-toolbar__content-section', +}; + +export const persesAriaLabels = { + TimeRangeDropdown: 'Select time range. Currently set to [object Object]', + RefreshButton: 'Refresh', + RefreshIntervalDropdown: 'Select refresh interval. Currently set to 0s', + ZoomInButton: 'Zoom in', + ZoomOutButton: 'Zoom out', +}; + +export const persesDataTestIDs = { + variableDropdown: 'variable', + panelGroupHeader: 'panel-group-header', + panelHeader: 'panel', +}; + diff --git a/web/cypress/support/commands.ts b/web/cypress/support/commands.ts deleted file mode 100644 index 123a73aa7..000000000 --- a/web/cypress/support/commands.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable @typescript-eslint/no-namespace */ -/// -import { TestIds } from '../../src/test-ids'; - -declare global { - namespace Cypress { - interface Chainable { - getByTestId(testId: TestIds): Chainable>; - } - } -} - -Cypress.Commands.add('getByTestId', (testId: TestIds) => cy.get(`[data-test="${testId}"]`)); diff --git a/web/cypress/support/commands/auth-commands.ts b/web/cypress/support/commands/auth-commands.ts new file mode 100644 index 000000000..2988f68c0 --- /dev/null +++ b/web/cypress/support/commands/auth-commands.ts @@ -0,0 +1,215 @@ +import { nav } from '../../views/nav'; +import { guidedTour } from '../../views/tour'; + + + +export {}; +declare global { + namespace Cypress { + interface Chainable { + switchPerspective(perspective: string); + uiLogin(provider: string, username: string, password: string, oauthurl?: string); + uiLogout(); + cliLogin(username?, password?, hostapi?); + cliLogout(); + login(provider?: string, username?: string, password?: string, oauthurl?: string): Chainable; + loginNoSession(provider: string, username: string, password: string, oauthurl: string): Chainable; + adminCLI(command: string, options?); + executeAndDelete(command: string); + validateLogin(): Chainable; + } + } +} + + + // Core login function (used by both session and non-session versions) + function performLogin( + provider: string, + username: string, + password: string, + oauthurl: string + ): void { + cy.visit(Cypress.config('baseUrl')); + cy.log('Session - after visiting'); + cy.window().then( + ( + win: any, // eslint-disable-line @typescript-eslint/no-explicit-any + ) => { + // Check if auth is disabled (for a local development environment) + if (win.SERVER_FLAGS?.authDisabled) { + cy.task('log', ' skipping login, console is running with auth disabled'); + return; + } + cy.exec( + `oc get node --selector=hypershift.openshift.io/managed --kubeconfig ${Cypress.env('KUBECONFIG_PATH')}`, + ).then((result) => { + cy.log(result.stdout); + cy.task('log', result.stdout); + if (result.stdout.includes('Ready')) { + cy.log(`Attempting login via cy.origin to: ${oauthurl}`); + cy.task('log', `Attempting login via cy.origin to: ${oauthurl}`); + cy.origin( + oauthurl, + { args: { username, password } }, + ({ username, password }) => { + cy.get('#inputUsername').type(username); + cy.get('#inputPassword').type(password); + cy.get('button[type=submit]').click(); + }, + ); + } else { + cy.task('log', ` Logging in as ${username} using fallback on ${oauthurl}`); + cy.origin( + oauthurl, + { args: { provider, username, password } }, + ({ provider, username, password }) => { + cy.get('[data-test-id="login"]').should('be.visible'); + cy.get('body').then(($body) => { + if ($body.text().includes(provider)) { + cy.contains(provider).should('be.visible').click(); + } + }); + cy.get('#inputUsername').type(username); + cy.get('#inputPassword').type(password); + cy.get('button[type=submit]').click(); + } + ); + } + }); + }, + ); + } + + Cypress.Commands.add('validateLogin', () => { + cy.visit('/'); + cy.wait(2000); + cy.byTestID("username", {timeout: 120000}).should('be.visible'); + guidedTour.close(); + }); + + // Session-wrapped login + Cypress.Commands.add( + 'login', + ( + provider: string = Cypress.env('LOGIN_IDP'), + username: string = Cypress.env('LOGIN_USERNAME'), + password: string = Cypress.env('LOGIN_PASSWORD'), + oauthurl: string, + ) => { + cy.session( + [provider, username], + () => { + performLogin(provider, username, password, oauthurl); + }, + { + cacheAcrossSpecs: true, + validate() { + cy.validateLogin(); + }, + }, + ); + }, + ); + + // Non-session login (for use within sessions) + Cypress.Commands.add('loginNoSession', (provider: string, username: string, password: string, oauthurl: string) => { + performLogin(provider, username, password, oauthurl); + cy.validateLogin(); + }); + + Cypress.Commands.add('switchPerspective', (perspective: string) => { + /* If side bar is collapsed then expand it + before switching perspecting */ + cy.wait(2000); + cy.get('body').then((body) => { + if (body.find('.pf-m-collapsed').length > 0) { + cy.get('#nav-toggle').click(); + } + }); + nav.sidenav.switcher.changePerspectiveTo(perspective); + }); + + // To avoid influence from upstream login change + Cypress.Commands.add('uiLogin', (provider: string, username: string, password: string) => { + cy.log('Commands uiLogin'); + cy.clearCookie('openshift-session-token'); + cy.visit('/'); + cy.window().then( + ( + win: any, // eslint-disable-line @typescript-eslint/no-explicit-any + ) => { + if (win.SERVER_FLAGS?.authDisabled) { + cy.task('log', 'Skipping login, console is running with auth disabled'); + return; + } + cy.get('[data-test-id="login"]').should('be.visible'); + cy.get('body').then(($body) => { + if ($body.text().includes(provider)) { + cy.contains(provider).should('be.visible').click(); + } else if ($body.find('li.idp').length > 0) { + //Using the last idp if doesn't provider idp name + cy.get('li.idp').last().click(); + } + }); + cy.get('#inputUsername').type(username); + cy.get('#inputPassword').type(password); + cy.get('button[type=submit]').click(); + cy.byTestID('username', { timeout: 120000 }).should('be.visible'); + }, + ); + cy.switchPerspective('Administrator'); + }); + + Cypress.Commands.add('uiLogout', () => { + cy.window().then( + ( + win: any, // eslint-disable-line @typescript-eslint/no-explicit-any + ) => { + if (win.SERVER_FLAGS?.authDisabled) { + cy.log('Skipping logout, console is running with auth disabled'); + return; + } + cy.log('Log out UI'); + cy.byTestID('username').click(); + cy.byTestID('log-out').should('be.visible'); + cy.byTestID('log-out').click({ force: true }); + }, + ); + }); + + Cypress.Commands.add('cliLogin', (username?, password?, hostapi?) => { + const loginUsername = username || Cypress.env('LOGIN_USERNAME'); + const loginPassword = password || Cypress.env('LOGIN_PASSWORD'); + const hostapiurl = hostapi || Cypress.env('HOST_API'); + cy.exec( + `oc login -u ${loginUsername} -p ${loginPassword} ${hostapiurl} --insecure-skip-tls-verify=true`, + { failOnNonZeroExit: false }, + ).then((result) => { + cy.log(result.stderr); + cy.log(result.stdout); + }); + }); + + Cypress.Commands.add('cliLogout', () => { + cy.exec(`oc logout`, { failOnNonZeroExit: false }).then((result) => { + cy.log(result.stderr); + cy.log(result.stdout); + }); + }); + + Cypress.Commands.add('adminCLI', (command: string) => { + const kubeconfig = Cypress.env('KUBECONFIG_PATH'); + cy.log(`Run admin command: ${command}`); + cy.exec(`${command} --kubeconfig ${kubeconfig}`); + }); + + Cypress.Commands.add('executeAndDelete', (command: string) => { + cy.exec(command, { failOnNonZeroExit: false }) + .then(result => { + if (result.code !== 0) { + cy.task('logError', `Command "${command}" failed: ${result.stderr || result.stdout}`); + } else { + cy.task('log', `Command "${command}" executed successfully`); + } + }); + }); diff --git a/web/cypress/support/commands/log-commands.ts b/web/cypress/support/commands/log-commands.ts new file mode 100644 index 000000000..66260f039 --- /dev/null +++ b/web/cypress/support/commands/log-commands.ts @@ -0,0 +1,453 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/// +import 'cypress-wait-until'; +import { guidedTour } from '../../views/tour'; +import { TestIds } from '../../../src/test-ids'; +import { Classes } from '../../fixtures/data-test'; +import * as helperfuncs from '../views/utils'; + +declare global { + namespace Cypress { + interface IndexField { + name: string; + value?: string; + } + interface Chainable { + cliLoginUser(rank: string); + uiLoginUser(rank: string); + uiLogoutAsUser(rank: string); + uiLoginAsClusterAdmin(rank: string); + uiLogoutClusterAdmin(rank: string); + switchToDevConsole(); + switchToAdmConsole(); + uiImpersonate(rank: string); + grantViewLogRoles(rank: string, project: string); + removeLogViewRoles(rank: string, project: string); + showQueryInput(); + closeQueryInput(); + runLogQuery(logQL: string); + assertLogsInLogsTable(); + assertFieldsInLogDetail(indexFields: IndexField[]): Chainable; + assertAppLogsInLogsTable(); + assertInfraLogsInLogsTable(); + assertAuditLogsInLogsTable(); + selectLogTenant(tenant: string); + selectLogAttribute(attribute: string); + checkLogNamespaces(namespaces: string[]); + checkLogPods(pods: string[]); + checkLogContainers(containers: string[]); + showAdminConsolePodAggrLog(projectName: string , podName?: string); + showDevConsolePodAggrLog(projectName: string , podName?: string); + waitUntilLogsInLokiStack(query?: string); + } + } +} + +const admin_kubeconfig = Cypress.env('KUBECONFIG_PATH'); +const normal_kubeconfig = "/tmp/logging_ui_kubeconfig" +const oauth_url = Cypress.config('baseUrl').replace("console-openshift-console", "oauth-openshift") +const rank_2_num = { + "first_user": 0, + "second_user": 1, + "third_user": 2, + "fourth_user": 3, + "fifth_user": 4, +}; + +Cypress.Commands.add('getByTestId', (testId: TestIds) => cy.get(`[data-test="${testId}"]`)); + +Cypress.Commands.add("getLogToolbar", () => { + cy.byClass(Classes.LogToolbar); +}) + +Cypress.Commands.add("showQueryInput", () => { + cy.byTestID(TestIds.ShowQueryToggle).then(($btn) => { + if ($btn.text().includes('Show Query')) { + cy.wrap($btn).click({ force: true }); + } + }); +}) + +Cypress.Commands.add("closeQueryInput", () => { + cy.byTestID(TestIds.ShowQueryToggle).then(($btn) => { + if ($btn.text().includes('Hide Query')) { + cy.wrap($btn).click({ force: true }); + } + }); +}) + +Cypress.Commands.add("runLogQuery", (logQL: string) => { + cy.showQueryInput(); + cy.getByTestId(TestIds.LogsQueryInput) + .find('textarea') + .type(`{selectall}${logQL}`, { delay: 0 }) + .then(() => { + cy.byTestID(TestIds.ExecuteQueryButton).click(); + }); +}) + +Cypress.Commands.add("switchToDevConsole",() => { + cy.switchPerspective('Developer'); + guidedTour.close(); +}) + +Cypress.Commands.add("switchToAdmConsole",() => { + cy.exec(`oc get console.operator cluster -o jsonpath='{.spec.customization.perspectives}'`).then((result) => { + if (!result.stdout.includes('{"state":"Enabled"}')){ + cy.log('no customization.perspectives is enabled'); + }else{ + switch (String(Cypress.env('OPENSHIFT_VERSION'))) { + case '4.12': + case '4.13': + case '4.14': + case '4.15': + case '4.16': + case '4.17': + case '4.18': + case '4.19': + case '4.20': + cy.switchPerspective('Administrator'); + break + default: + cy.switchPerspective('Core platform'); + } + } + }) + guidedTour.close(); +}) + +//login user using cli. +Cypress.Commands.add('cliLoginUser', (rank: string) => { + cy.log(`login ${rank}`); + let username=""; + let userpassword=""; + cy.readFile(admin_kubeconfig) + .then(content => cy.writeFile(normal_kubeconfig, content)); + + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + userpassword=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[1]; + } + if( username != "" && userpassword != "" ){ + cy.exec(`oc login -u ${username} -p ${userpassword} --kubeconfig=${normal_kubeconfig}`); + }else{ + cy.log('no user can be found.'); + cy.exit(); + } +}) + +//Login user as the cluster-admin +//Rank: first_user, second_user ... five_user +Cypress.Commands.add('uiLoginAsClusterAdmin', (rank: string) => { + let username=""; + let userpassword=""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + userpassword=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[1]; + } + if( username != "" && userpassword != "" && Cypress.env('LOGIN_IDP') != "" ){ + cy.adminCLI(`oc adm policy add-cluster-role-to-user cluster-admin ${username}`); + cy.login(Cypress.env('LOGIN_IDP'), username, userpassword, oauth_url); + guidedTour.close() + }else{ + cy.log('no user can be found.'); + cy.exit(); + } +}) + +//Login a user from LOGIN_USERS=test1:passwd,user2,passwd,user2:pasword,... +Cypress.Commands.add('uiLoginUser', (rank: string) => { + let username=""; + let userpassword=""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + userpassword=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[1]; + } + if( username != "" && userpassword != "" && Cypress.env('LOGIN_IDP') != "" ){ + cy.login(Cypress.env('LOGIN_IDP'), username, userpassword, oauth_url); + guidedTour.close() + }else{ + cy.log('no user can be found.'); + cy.exit(); + } +}) + +//Allow user to view observe/logs,alerts +Cypress.Commands.add('grantLogViewRoles', (rank: string, project: string) => { + let username=""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + if( username != "" ){ + cy.exec(`oc -n ${project} policy add-role-to-user view ${username}`); + cy.exec(`oc -n ${project} policy add-role-to-user cluster-logging-application-view ${username}`); + cy.exec(`oc -n ${project} policy add-role-to-user monitoring-rules-edit ${username}`); + cy.exec(`oc -n ${project} policy add-role-to-user cluster-monitoring-view ${username}`); + }else{ + cy.log(`can not find the ${rank}.`); + cy.exit(); + } + } +}) + +//Remove observe/logs,alerts roles from the user +Cypress.Commands.add('removeLogViewRoles', (rank: string, project: string) => { + let username=""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + if( username != "" ){ + cy.exec(`oc -n ${project} policy remove-role-from-user view ${username}`); + cy.exec(`oc -n ${project} policy remove-role-from-user cluster-logging-application-view ${username}`); + cy.exec(`oc -n ${project} policy remove-role-from-user monitoring-rules-edit ${username}`); + cy.exec(`oc -n ${project} policy remove-role-from-user cluster-monitoring-view ${username}`); + }else{ + cy.log(`can not find the ${rank}.`); + cy.exit(); + } + } +}) + +Cypress.Commands.add('uiLogoutUser', (rank: string) => { + cy.log('Logout user'); + let username = ""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + } + cy.uiLogout(); +}) + +Cypress.Commands.add('uiLogoutClusterAdmin', (rank: string) => { + cy.log('Remove the cluster Admin role and Logout'); + let username = ""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + } + if( username != "" ){ + cy.adminCLI(`oc adm policy remove-cluster-role-from-user cluster-admin ${username}`); + } + cy.uiLogout(); +}) + +Cypress.Commands.add('uiImpersonateUser', (rank: string) => { + let username = ""; + if (Cypress.env('LOGIN_USERS')){ + username=Cypress.env('LOGIN_USERS').split(',')[rank_2_num[rank]].split(':')[0]; + } + if( username == "" ){ + cy.log(`can not find the ${rank}.`); + this.skip(); + } + let fullusername=Cypress.env('LOGIN_IDP') + ":" + username + + cy.switchToAdmConsole(); + //cy.visit("/k8s/cluster/user.openshift.io~v1~User", { timeout: 120000 } ); + cy.clickNavLink(['User Management', 'Users']); + //We can check if User Table exist or not in 4.22+ + //cy.get(`table[aria-label="Users table"]`, { timeout: 120000 } ).should('exist'); + cy.contains('td', fullusername, { timeout: 120000 } ) + .closest('tr') + .find('button[data-test-id="kebab-button"]') + .click(); + cy.contains('button', 'Impersonate User').click(); + //find the username to see if Impersonate User succeed or not + cy.contains('[data-test="username"]', `${username}`).should('exist') + + //Close guide tour bar + guidedTour.close() +}) + +Cypress.Commands.add('assertLogsInLogsTable', () => { + // Ensure the table has loaded rows + cy.getByTestId(TestIds.LogsTable).within(() => { + cy.get('tr[data-test-rows="resource-row" ]').first().within(() => { + cy.get('td[data-label="date"]').should('exist'); + cy.get('td[data-label="message"]').should('exist'); + }); + //there are more than one row + cy.get('tr[data-test-rows="resource-row" ]') + .its('length') + .should('be.gt', 1); + }); +}) + +//Expand the the first row and check the expected fields in the log detail +//the filed value can be value, empty or pattern +Cypress.Commands.add('assertFieldsInLogDetail', (indexFields: IndexField[]) => { + + cy.getByTestId(TestIds.LogsTable).within(() => { + //there are more than one row in tables + cy.get('tr[data-test-rows="resource-row"]') + .its('length') + .should('be.gt', 1); + + //show the detail table + cy.get('tr[data-test-rows="resource-row"]') + .first() + .find('td') + .first() + .find('button') + .then(($btn) => { + if ($btn.attr('aria-expanded') !== 'true') { + cy.wrap($btn).click({force: true}); + } + }) + + // check the fields in detail + cy.byClass(Classes.LogDetail) + .within(() => { + indexFields.forEach(field => { + if (!Cypress._.isEmpty(field.value)) { // skip if value is empty, null, or undefined + let pattern = new RegExp(field.value); + cy.contains('dt', field.name) + .parent() + .within(() => { + cy.get('dd') + .invoke('text') + .should('match',pattern) + }); + } + else{ + cy.contains('dt', field.name) + } + }); + }); + }); +}) + +Cypress.Commands.add('assertAppLogsInLogsTable', () => { + //only check some of these index fileds + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: 'application' }, + { name: 'k8s_pod_name', value: '' }, + { name: 'k8s_namespace_name', value: '' }, + { name: 'k8s_node_name', value: '' }, + ] + cy.assertFieldsInLogDetail(indexFields); +}) + +Cypress.Commands.add('assertInfraLogsInLogsTable', () => { + //only check some of these index fileds + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: 'infrastructure' }, + ] + cy.assertFieldsInLogDetail(indexFields); +}) + +Cypress.Commands.add('assertAuditLogsInLogsTable', () => { + //only check some of these index fileds + const indexFields : IndexField = [ + { name: 'openshift_log_type', value: 'audit' } + ] + cy.assertFieldsInLogDetail(indexFields); +}) + +//select a tenant for logs +Cypress.Commands.add('selectLogTenant', (tenant: string) => { + cy.byTestID(TestIds.TenantToggle).click(); + cy.get('#logging-view-tenant-dropdown') + .contains('button', tenant) + .click(); + //close the dropdown list + cy.get('body').click(0, 0); +}) + +//select one Attribute in AttributeFilters +Cypress.Commands.add('selectLogAttribute', (attribute: string) => { + cy.byTestID(TestIds.AvailableAttributes).click(); + cy.byTestID(TestIds.AttributeFilters) + .contains('button', attribute) + .click(); + //close the dropdown list + cy.get('body').click(0, 0); +}) + +//select options from AttributeOptions dropbox +Cypress.Commands.add('checkLogDropboxOptions', (opts: string[]) => { + opts.forEach((opt) => { + cy.byTestID(TestIds.AttributeOptions) + .find('input[type="text"]') // input the expected options + .click() + .clear() + .type(opt); + cy.byTestID(TestIds.AttributeFilters, { timeout: 20000 }).within(() => { + cy.contains('[role="listbox"] li', opt, { timeout: 20000 , includeShadowDom: true }) + .scrollIntoView() // in case it's offscreen + .find('input[type="checkbox"]') + .check({ force: true }) // safe: never unchecks if already checked + .should('be.checked') + }); + }); + //close the dropdown panel + cy.get('body').click(0, 0); +}) + +//select namespaces from AttributeOptions +Cypress.Commands.add('checkLogNamespaces', (namespaces: string[]) => { + cy.selectLogAttribute('Namespaces'); + cy.checkLogDropboxOptions(namespaces); +}) + +//select pods from AttributeOptions +Cypress.Commands.add('checkLogPods', (pods: string[]) => { + cy.selectLogAttribute('Pods'); + cy.checkLogDropboxOptions(pods); +}) + +//select Containers from AttributeOptions +Cypress.Commands.add('checkLogContainers', (containers: string[]) => { + cy.selectLogAttribute('Containers'); + cy.checkLogDropboxOptions(containers); +}) + +//if no podName, will click the first pod +Cypress.Commands.add('clickPodLink', (podName?: string) => { + const name = podName ?? '.*' + const pattern = new RegExp(`/k8s/ns/.*/pods/${name}`) + cy.get('td a') + .filter((_, a) => a.href.match(pattern)) + .first() // pick the first match only + .click(); +}) + +//Show the AdminConsole pod logs in one project +Cypress.Commands.add('showAdminConsolePodAggrLog', (projectName: string , podName?: string) => { + //navigate to Workloads->Pods + cy.clickNavLink(['Workloads','Pods']); + //select namespace + cy.changeNamespace(projectName); + //select the first pod matching the podName + if (!podName) { + cy.clickPodLink(); + }else { + cy.clickPodLink(name); + } + cy.byLegacyTestID('horizontal-link-Aggregated Logs').click(); +}) + +//Show the devConsole pod logs in one project +Cypress.Commands.add('showDevConsolePodAggrLog', (projectName: string , podName?: string) => { + //navigate to Project page + cy.clickNavLink(['Project']); + //select namespace + cy.changeNamespace(projectName); + //navigate pods page in the project + cy.get(`a[data-test="resource-inventory-item"][href="/k8s/ns/${projectName}/pods"]`) + .scrollIntoView() + .click({ force: true }); + //select the first pod matching the podName + if (!podName) { + cy.clickPodLink(); + }else { + cy.clickPodLink(name); + } + cy.byLegacyTestID('horizontal-link-Aggregated Logs').click(); +}) + +//ensure the logs in lokistack before use it in UI +Cypress.Commands.add('waitUntilLogsInLokiStack', (query?: string) => { + cy.exec(`./cypress/support/scripts/findLogsInLokistack.sh '${query}'`, { failOnNonZeroExit: false }) + .then(({ code, stdout }) => { + cy.log(stdout) + expect(code === 0).to.be.true + }); +}) + diff --git a/web/cypress/support/commands/selector-commands.ts b/web/cypress/support/commands/selector-commands.ts new file mode 100644 index 000000000..f397b8c79 --- /dev/null +++ b/web/cypress/support/commands/selector-commands.ts @@ -0,0 +1,116 @@ +import Loggable = Cypress.Loggable; +import Timeoutable = Cypress.Timeoutable; +import Withinable = Cypress.Withinable; +import Shadow = Cypress.Shadow; + +export {}; + +declare global { + namespace Cypress { + interface Chainable { + byTestID( + selector: string, + options?: Partial, + ): Chainable; + byTestActionID(selector: string): Chainable>; + byLegacyTestID( + selector: string, + options?: Partial, + ): Chainable>; + byButtonText(selector: string): Chainable>; + byDataID(selector: string): Chainable>; + byTestSelector( + selector: string, + options?: Partial, + ): Chainable>; + byTestDropDownMenu(selector: string): Chainable>; + byTestOperatorRow( + selector: string, + options?: Partial, + ): Chainable>; + byTestSectionHeading(selector: string): Chainable>; + byTestOperandLink(selector: string): Chainable>; + byOUIAID(selector: string): Chainable; + byClass(selector: string): Chainable; + bySemanticElement(element: string, text?: string): Chainable>; + byAriaLabel(label: string, options?: Partial): Chainable>; + byPFRole(role: string, options?: Partial): Chainable>; + } + } +} + + +Cypress.Commands.add( + 'byTestID', + (selector: string, options?: Partial) => { + cy.get(`[data-test="${selector}"]`, options); + }, + ); + + Cypress.Commands.add('byTestActionID', (selector: string) => + cy.get(`[data-test-action="${selector}"]:not([disabled])`), + ); + + // Deprecated! new IDs should use 'data-test', ie. `cy.byTestID(...)` + Cypress.Commands.add( + 'byLegacyTestID', + (selector: string, options?: Partial) => { + cy.get(`[data-test-id="${selector}"]`, options); + }, + ); + + Cypress.Commands.add('byButtonText', (selector: string) => { + cy.get('button[type="button"]').contains(`${selector}`); + }); + + Cypress.Commands.add('byDataID', (selector: string) => { + cy.get(`[data-id="${selector}"]`); + }); + + Cypress.Commands.add( + 'byTestSelector', + (selector: string, options?: Partial) => { + cy.get(`[data-test-selector="${selector}"]`, options); + }, + ); + + Cypress.Commands.add('byTestDropDownMenu', (selector: string) => { + cy.get(`[data-test-dropdown-menu="${selector}"]`); + }); + + Cypress.Commands.add('byTestOperatorRow', (selector: string, options?: object) => { + cy.get(`[data-test-operator-row="${selector}"]`, options); + }); + + Cypress.Commands.add('byTestSectionHeading', (selector: string) => { + cy.get(`[data-test-section-heading="${selector}"]`); + }); + + Cypress.Commands.add('byTestOperandLink', (selector: string) => { + cy.get(`[data-test-operand-link="${selector}"]`); + }); + + Cypress.Commands.add('byOUIAID', (selector: string) => cy.get(`[data-ouia-component-id^="${selector}"]`)); + + Cypress.Commands.add('byClass', (selector: string) => cy.get(`[class="${selector}"]`)); + + Cypress.Commands.add('bySemanticElement', (element: string, text?: string) => { + if (text) { + return cy.get(element).contains(text); + } + return cy.get(element); + }); + + Cypress.Commands.add( + 'byAriaLabel', + (label: string, options?: Partial) => { + return cy.get(`[aria-label="${label}"]`, options); + } + ); + + Cypress.Commands.add( + 'byPFRole', + (role: string, options?: Partial) => { + return cy.get(`[role="${role}"]`, options); + } + ); diff --git a/web/cypress/support/commands/utility-commands.ts b/web/cypress/support/commands/utility-commands.ts new file mode 100644 index 000000000..40f8f8329 --- /dev/null +++ b/web/cypress/support/commands/utility-commands.ts @@ -0,0 +1,142 @@ +import { Classes, DataTestIDs, LegacyTestIDs } from "../../fixtures/data-test"; +export {}; + +declare global { + namespace Cypress { + interface Chainable { + waitUntilWithCustomTimeout( + fn: () => any, + options: { interval: number; timeout: number; timeoutMessage: string } + ): Cypress.Chainable; + clickNavLink(path: string[]): Chainable; + changeNamespace(namespace: string): Chainable; + aboutModal(): Chainable; + podImage(pod: string, namespace: string): Chainable; + } + } + } + +// Custom waitUntil with timeout message +Cypress.Commands.add('waitUntilWithCustomTimeout', ( + fn: () => any, + options: { interval: number; timeout: number; timeoutMessage: string } + ) => { + const { timeoutMessage, ...waitOptions } = options; + + // Set up custom error handling before the waitUntil call + cy.on('fail', (err) => { + if (err.message.includes('Timed out retrying')) { + // Create a new error with the custom message + const customError = new Error(timeoutMessage); + customError.stack = err.stack; + throw customError; + } + // For any other errors, re-throw them unchanged + throw err; + }); + + // Execute the waitUntil with the original options (without timeoutMessage) + return cy.waitUntil(fn, waitOptions); + + }); + + + Cypress.Commands.add('clickNavLink', (path: string[]) => { + cy.get('#page-sidebar') + .contains(path[0]) + .then(($navItem) => { + if ($navItem.attr('aria-expanded') !== 'true') { + cy.wrap($navItem).click({force: true}); + } + }); + if (path.length === 2) { + cy.get('#page-sidebar') + .contains(path[1]) + .click({force: true}); + } + }); + + Cypress.Commands.add('changeNamespace', (namespace: string) => { + cy.log('Changing Namespace to: ' + namespace); + cy.wait(2000); + cy.get('body').then(($body) => { + const hasNamespaceBarDropdown = $body.find('[data-test-id="'+LegacyTestIDs.NamespaceBarDropdown+'"]').length > 0; + if (hasNamespaceBarDropdown) { + cy.byLegacyTestID(LegacyTestIDs.NamespaceBarDropdown).find('button').scrollIntoView().should('be.visible'); + cy.byLegacyTestID(LegacyTestIDs.NamespaceBarDropdown).find('button').scrollIntoView().should('be.visible').click({force: true}); + } else { + cy.get(Classes.NamespaceDropdown).scrollIntoView().should('be.visible'); + cy.get(Classes.NamespaceDropdown).scrollIntoView().should('be.visible').click({force: true}); + } + }); + cy.get('body').then(($body) => { + const hasShowSystemSwitch = $body.find('[data-test="'+DataTestIDs.NamespaceDropdownShowSwitch+'"]').length > 0; + if (hasShowSystemSwitch) { + cy.get('[data-test="'+DataTestIDs.NamespaceDropdownShowSwitch+'"]').then(($element)=> { + if ($element.attr('data-checked-state') !== 'true') { + cy.byTestID(DataTestIDs.NamespaceDropdownShowSwitch).siblings('span').eq(0).should('be.visible'); + cy.byTestID(DataTestIDs.NamespaceDropdownShowSwitch).siblings('span').eq(0).should('be.visible').click({force: true}); + } + }); + } + }); + cy.byTestID(DataTestIDs.NamespaceDropdownTextFilter).type(namespace, {delay: 100}); + cy.byTestID(DataTestIDs.NamespaceDropdownMenuLink).contains(namespace).should('be.visible'); + cy.byTestID(DataTestIDs.NamespaceDropdownMenuLink).contains(namespace).should('be.visible').click({force: true}); + cy.log('Namespace changed to: ' + namespace); + }); + + Cypress.Commands.add('aboutModal', () => { + cy.log('Getting OCP version'); + if (Cypress.env('LOGIN_USERNAME') === 'kubeadmin') { + cy.byTestID(DataTestIDs.MastHeadHelpIcon).should('be.visible'); + cy.byTestID(DataTestIDs.MastHeadHelpIcon).should('be.visible').click({force: true}); + cy.byTestID(DataTestIDs.MastHeadApplicationItem).contains('About').should('be.visible').click(); + cy.byAriaLabel('About modal').find('div[class*="co-select-to-copy"]').eq(0).should('be.visible').then(($ocpversion) => { + cy.log('OCP version: ' + $ocpversion.text()); + }); + cy.byAriaLabel('Close Dialog').should('be.visible').click(); + } + + }); + + Cypress.Commands.overwrite('log', (log, ...args) => { + if (Cypress.browser.isHeadless && Cypress.env('DEBUG')) { + // Log to the terminal using the custom task + return cy.task('log', args, { log: false }).then(() => { + // The original cy.log is still executed but its output is hidden from the + // command log in headless mode + return log(...args); + }); + } else { + // In headed mode, use the original cy.log behavior + return log(...args); + } + }); + + Cypress.Commands.add('podImage', (pod: string, namespace: string) => { + cy.log('Get pod image'); + cy.clickNavLink(['Workloads', 'Pods']); + cy.changeNamespace(namespace); + cy.byTestID('page-heading').contains('Pods').should('be.visible'); + cy.wait(5000); + // Check for DataViewFilters component using Cypress's built-in retry-ability + cy.get('body').then(($body) => { + const hasDataViewFilters = $body.find('[data-ouia-component-id="DataViewFilters"]').length > 0; + if (hasDataViewFilters) { + cy.byOUIAID('DataViewFilters').find('button').contains('Status').scrollIntoView().should('be.visible').click(); + cy.byOUIAID('OUIA-Generated-Menu').find('button').contains('Name').scrollIntoView().should('be.visible').click(); + cy.byAriaLabel('Name filter').scrollIntoView().should('be.visible').type(pod); + } else { + cy.byTestID('name-filter-input').should('be.visible').type(pod); + } + }); + cy.get(`a[data-test^="${pod}"]`).eq(0).as('podLink').click(); + cy.get('@podLink').should('be.visible').click(); + cy.byPFRole('rowgroup').find('td').eq(1).scrollIntoView().should('be.visible').then(($td) => { + cy.log('Pod image: ' + $td.text()); + }); + cy.log('Get pod image completed'); + }); + + diff --git a/web/cypress/support/e2e.ts b/web/cypress/support/e2e.ts index b755c86f6..264d1085d 100644 --- a/web/cypress/support/e2e.ts +++ b/web/cypress/support/e2e.ts @@ -1,2 +1,38 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import './commands'; +import '@cypress/grep'; + +import './commands/selector-commands'; +import './commands/auth-commands'; +import './commands/utility-commands'; +import './commands/log-commands'; + +export const checkErrors = () => + cy.window().then((win) => { + assert.isTrue(!win.windowError, win.windowError); + }); + + // Ignore benign ResizeObserver errors globally so they don't fail tests +// See: https://docs.cypress.io/api/cypress-api/catalog-of-events#Uncaught-Exceptions +Cypress.on('uncaught:exception', (err) => { + const message = err?.message || String(err || ''); + if ( + message.includes('ResizeObserver loop limit exceeded') || + message.includes('ResizeObserver loop completed with undelivered notifications') || + message.includes('ResizeObserver') || + message.includes('Cannot read properties of undefined') || + message.includes('Unauthorized') || + message.includes('Bad Gateway') || + message.includes(`Cannot read properties of null (reading 'default')`) || + message.includes(`(intermediate value) is not a function`) + ) { + console.warn('Ignored frontend exception:', err.message); + return false; + } + // allow other errors to fail the test +}); + + + +Cypress.on('uncaught:exception', (err) => { + console.error("Uncaught error:", err.message); + return false; +}); diff --git a/web/cypress/support/index.ts b/web/cypress/support/index.ts new file mode 100644 index 000000000..2a9dd2dbf --- /dev/null +++ b/web/cypress/support/index.ts @@ -0,0 +1,35 @@ +import '@cypress/grep'; + +import './selectors'; +import './commands/selector-commands'; +import './commands/auth-commands'; +import './commands/operator-commands'; +import './commands/incident-commands'; +import './commands/utility-commands'; +import './incidents_prometheus_query_mocks'; +import './commands/virtualization-commands'; + +export const checkErrors = () => + cy.window().then((win) => { + assert.isTrue(!win.windowError, win.windowError); + }); + + // Ignore benign ResizeObserver errors globally so they don't fail tests +// See: https://docs.cypress.io/api/cypress-api/catalog-of-events#Uncaught-Exceptions +Cypress.on('uncaught:exception', (err) => { + const message = err?.message || String(err || ''); + if ( + message.includes('ResizeObserver loop limit exceeded') || + message.includes('ResizeObserver loop completed with undelivered notifications') || + message.includes('ResizeObserver') || + message.includes('Cannot read properties of undefined') || + message.includes('Unauthorized') || + message.includes('Bad Gateway') || + message.includes(`Cannot read properties of null (reading 'default')`) || + message.includes(`(intermediate value) is not a function`) + ) { + console.warn('Ignored frontend exception:', err.message); + return false; + } + // allow other errors to fail the test +}); diff --git a/web/cypress/support/scripts/findLogsInLokistack.sh b/web/cypress/support/scripts/findLogsInLokistack.sh new file mode 100644 index 000000000..b9a63c4a5 --- /dev/null +++ b/web/cypress/support/scripts/findLogsInLokistack.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +#Author: anli@redhat.com +#Description: Validate logs appears until timeout. the script will search tenant applicaiton, infrastructure and audit if tenanet is not specified. +# +set -x +QUERY=${1:-'{log_type="application"}'} +QUERY=${QUERY//[[:space:]]/} #remove all spaces +TIMEOUT=${2:-120} # seconds + +URL=$(oc get route logging-loki -n openshift-logging -o jsonpath='{.spec.host}') +TOKEN=$(oc whoami -t) +WINDOW="300" # seconds +STEP="30s" +INTERVAL="10" + +start_time=$(date +%s) +echo "Waiting until logs appear..." + +function queryLogsInTenant() +{ + tenant=${1:-application} + result=$(curl -s -G -k -H "Authorization: Bearer ${TOKEN}" -H "X-Scope-OrgID:${tenant}" https://${URL}/api/logs/v1/${tenant}/loki/api/v1/query_range \ + --data-urlencode "query=count_over_time(${QUERY} [${WINDOW}s])" | jq '.data.result|length') + + if [ "$result" -gt 0 ]; then + echo "Logs appeared ($result)" + exit 0 + fi +} +############## Main ################# +start_time=$(date +%s) +while true; do + now=$(date +%s) + ##exit 0, if one return true + tenant="" + + if [[ $QUERY =~ log_type=\"application\" ]]; then + echo "query tenant application" + tenant="application" + queryLogsInTenant ${tenant} + fi + + if [[ $QUERY =~ log_type=\"infrastructure\" ]] || [[ $QUERY =~ namespace=\"default\" ]] || [[ $QUERY =~ namespace=\"openshift\" ]] || [[ $QUERY =~ namespace=\"kube- ]] || [[ $QUERY =~ namespaceu=\"openshift- ]] ; then + echo "query tenant infrastructure" + tenant="infrastructure" + queryLogsInTenant ${tenant} + fi + + if [[ $QUERY =~ log_type=\"audit\" ]]; then + echo "query tenant audit" + tenant="audit" + queryLogsInTenant ${tenant} + fi + + if [[ $tenant == "" ]]; then + echo "query all tenants" + queryLogsInTenant "application" + queryLogsInTenant "infrastructure" + queryLogsInTenant "audit" + fi + + if [ $((now-start_time)) -ge "$TIMEOUT" ]; then + echo "Timeout waiting for logs" + exit 1 + fi + + sleep "$INTERVAL" +done diff --git a/web/cypress/views/nav.ts b/web/cypress/views/nav.ts new file mode 100644 index 000000000..01c0f74d5 --- /dev/null +++ b/web/cypress/views/nav.ts @@ -0,0 +1,31 @@ +import { Classes } from '../fixtures/data-test'; +export const nav = { + sidenav: { + clickNavLink: (path: string[]) => { + cy.log('Click navLink - ' + `${path}`); + cy.clickNavLink(path); + }, + switcher: { + changePerspectiveTo: (perspective: string) => { + cy.log('Switch perspective - ' + `${perspective}`); + cy.byLegacyTestID('perspective-switcher-toggle').scrollIntoView().should('be.visible'); + cy.byLegacyTestID('perspective-switcher-toggle').scrollIntoView().should('be.visible').click({force: true}); + cy.byLegacyTestID('perspective-switcher-menu-option').contains(perspective).should('be.visible'); + cy.byLegacyTestID('perspective-switcher-menu-option').contains(perspective).should('be.visible').click({force: true}); + }, + shouldHaveText: (perspective: string) => { + cy.log('Should have text - ' + `${perspective}`); + cy.byLegacyTestID('perspective-switcher-toggle').contains(perspective).should('be.visible'); + } + } + }, + tabs: { + /** + * Switch to a tab by name + * @param tabname - The name of the tab to switch to + */ + switchTab: (tabname: string) => { + cy.get(Classes.HorizontalNav).contains(tabname).should('be.visible').click(); + } +} +}; diff --git a/web/cypress/views/tour.ts b/web/cypress/views/tour.ts new file mode 100644 index 000000000..9884cede1 --- /dev/null +++ b/web/cypress/views/tour.ts @@ -0,0 +1,17 @@ +export const guidedTour = { + close: () => { + cy.get('body').then(($body) => { + if ($body.find(`[data-test="guided-tour-modal"]`).length > 0) { + cy.byTestID('tour-step-footer-secondary').contains('Skip tour').click(); + } + }); + }, + + closeKubevirtTour: () => { + cy.get('body').then(($body) => { + if ($body.find(`[aria-label="Welcome modal"]`).length > 0) { + cy.get('[aria-label="Close"]').should('be.visible').click(); + } + }); + }, + }; \ No newline at end of file diff --git a/web/cypress/views/utils.ts b/web/cypress/views/utils.ts new file mode 100644 index 000000000..50164850b --- /dev/null +++ b/web/cypress/views/utils.ts @@ -0,0 +1,43 @@ +export function clickIfExist(element) { + cy.get('body').then((body) => { + if (body.find(element).length > 0) { + cy.get(element).click(); + } + }); +} + +export function getValFromElement(selector: string) { + cy.log('Get Val from Element'); + cy.get(selector).should('be.visible'); + const elementText = cy.get(selector).invoke('val'); + return elementText; +}; + +export function getTextFromElement(selector: string) { + cy.log('Get Text from Element'); + cy.get(selector).should('be.visible'); + const elementText = cy.get(selector).invoke('text'); + return elementText; +}; + +// PatternFly version detection and abstraction +export function getPFVersion() { + // Detect PatternFly version from document classes or CSS variables + const htmlElement = Cypress.$('html')[0]; + if (htmlElement) { + const classes = htmlElement.className; + const versionMatch = classes.match(/pf-(v\d+)/); + if (versionMatch) { + return versionMatch[1]; + } + } + // Fallback to checking for CSS variables + const style = getComputedStyle(document.documentElement); + if (style.getPropertyValue('--pf-v6-global--FontSize--md')) { + return 'v6'; + } else if (style.getPropertyValue('--pf-v5-global--FontSize--md')) { + return 'v5'; + } + // Default to current version + return 'v6'; +}; \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index bf94bb89b..5843729db 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -16,11 +16,13 @@ "@patternfly/react-table": "^5.4.15", "@patternfly/react-virtualized-extension": "^5.1.0", "i18next": "^22.4.12", + "mocha-junit-reporter": "^2.2.1", "react-i18next": "^11.18.6", "react-router-dom-v5-compat": "^6.30.0" }, "devDependencies": { "@cypress/code-coverage": "^3.10.0", + "@cypress/grep": "^3.1.3", "@jsdevtools/coverage-istanbul-loader": "^3.0.5", "@openshift-console/dynamic-plugin-sdk": "^4.19.0-prerelease.1", "@openshift-console/dynamic-plugin-sdk-webpack": "4.19", @@ -39,7 +41,9 @@ "copy-webpack-plugin": "^13.0.1", "css-loader": "^6.7.1", "cypress": "^14.5.3", + "cypress-multi-reporters": "^1.4.0", "cypress-terminal-report": "^3.5.2", + "cypress-wait-until": "^3.0.2", "eslint": "^8.44.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0", @@ -49,6 +53,8 @@ "i18next-http-backend": "^2.2.0", "i18next-parser": "^9.3.0", "jest": "^30.1.3", + "junit-report-merger": "^3.0.6", + "mochawesome": "^6.1.1", "nyc": "^15.1.0", "prettier": "^2.6.0", "react": "17.0.2", @@ -2041,6 +2047,21 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@cypress/grep": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@cypress/grep/-/grep-3.1.5.tgz", + "integrity": "sha512-dbLKP9wGLId+TwTRFDcWVcr9AvJ06W3K7dVeJzLONiPbI5/XJh2mDZvnoyJlAz+VZxdwe0+nejk/CPmuphuzkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "find-test-names": "^1.19.0", + "globby": "^11.0.4" + }, + "peerDependencies": { + "cypress": ">=10" + } + }, "node_modules/@cypress/request": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", @@ -2765,7 +2786,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -2783,7 +2803,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2796,7 +2815,6 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2809,14 +2827,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -2834,7 +2850,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -2850,7 +2865,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -4095,6 +4109,58 @@ "node": ">= 8" } }, + "node_modules/@oozcitak/dom": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", + "integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/@openshift-console/dynamic-plugin-sdk": { "version": "4.19.0-prerelease.1", "resolved": "https://registry.npmjs.org/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-4.19.0-prerelease.1.tgz", @@ -4412,7 +4478,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -6024,7 +6089,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6034,7 +6098,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6536,7 +6599,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bare-events": { @@ -6968,6 +7030,13 @@ "node": "10.* || >= 12.*" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC", + "peer": true + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -7231,7 +7300,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -7269,6 +7337,15 @@ "node": ">=10" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", @@ -7720,7 +7797,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -7733,7 +7809,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -8152,7 +8227,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -8167,12 +8241,20 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/css-loader": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", @@ -8324,6 +8406,23 @@ "node": "^18.0.0 || ^20.0.0 || >=22.0.0" } }, + "node_modules/cypress-multi-reporters": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", + "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" + } + }, "node_modules/cypress-terminal-report": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/cypress-terminal-report/-/cypress-terminal-report-3.5.2.tgz", @@ -8368,6 +8467,13 @@ "node": ">=10" } }, + "node_modules/cypress-wait-until": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-3.0.2.tgz", + "integrity": "sha512-iemies796dD5CgjG5kV0MnpEmKSH+s7O83ZoJLVzuVbZmm4lheMsZqAVT73hlMx4QlkwhxbyUzhOBUOZwoOe0w==", + "dev": true, + "license": "MIT" + }, "node_modules/cypress/node_modules/ci-info": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", @@ -8658,6 +8764,16 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -8669,7 +8785,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -9051,8 +9166,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ecc-jsbn": { "version": "0.1.2", @@ -9095,7 +9209,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -9459,7 +9572,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -10459,6 +10571,26 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-test-names": { + "version": "1.29.19", + "resolved": "https://registry.npmjs.org/find-test-names/-/find-test-names-1.29.19.tgz", + "integrity": "sha512-fSO2GXgOU6dH+FdffmRXYN/kLdnd8zkBGIZrKsmAdfLSFUUDLpDFF7+F/h+wjmjDWQmMgD8hPfJZR+igiEUQHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@babel/plugin-syntax-jsx": "^7.27.1", + "acorn-walk": "^8.2.0", + "debug": "^4.3.3", + "simple-bin-help": "^1.8.0", + "tinyglobby": "^0.2.13" + }, + "bin": { + "find-test-names": "bin/find-test-names.js", + "print-tests": "bin/print-tests.js", + "update-test-count": "bin/update-test-count.js" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -10477,7 +10609,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, "license": "BSD-3-Clause", "bin": { "flat": "cli.js" @@ -10732,6 +10863,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fsu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", + "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", + "dev": true, + "license": "MIT" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -10793,7 +10931,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -11163,7 +11300,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11279,7 +11415,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, "license": "MIT", "bin": { "he": "bin/he" @@ -12040,6 +12175,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -12150,7 +12291,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12328,7 +12468,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12494,7 +12633,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -12592,7 +12730,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -12735,7 +12872,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -14973,6 +15109,54 @@ "node": ">=4.0" } }, + "node_modules/junit-report-merger": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-3.0.6.tgz", + "integrity": "sha512-6oziSTxC7MjO3yhkkokbO6EXTDwAtmgjozZgk8EaLu54re3xoeLyc2/DUyEamcF4Pl+nPXnnaVpqo4s+W64h0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.2.11", + "xmlbuilder2": "3.0.2" + }, + "bin": { + "jrm": "cli.js", + "junit-report-merger": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/junit-report-merger/node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/junit-report-merger/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -15170,6 +15354,34 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -15195,7 +15407,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -15351,6 +15562,17 @@ "node": ">= 0.4" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -15514,12 +15736,26 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mktemp": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.4.0.tgz", @@ -15529,13 +15765,686 @@ "node": ">0.9" } }, - "node_modules/ms": { + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "license": "MIT", + "peer": true, + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha-junit-reporter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", + "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" + }, + "peerDependencies": { + "mocha": ">=2.2.5" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mochawesome": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-6.3.1.tgz", + "integrity": "sha512-G2J7Le8ap+0222otJQEUVFs7RYzphiIk21NzaBZE2dbyHJ2+9aai+V2cV7lreEKigDpwQ+SXeiiBH9KQlrkaAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "diff": "^5.0.0", + "json-stringify-safe": "^5.0.1", + "lodash.isempty": "^4.4.0", + "lodash.isfunction": "^3.0.9", + "lodash.isobject": "^3.0.2", + "lodash.isstring": "^4.0.1", + "mochawesome-report-generator": "^5.2.0", + "strip-ansi": "^6.0.0", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "mocha": ">=7" + } + }, + "node_modules/mochawesome-report-generator": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-5.2.0.tgz", + "integrity": "sha512-DDY/3jSkM/VrWy0vJtdYOf6qBLdaPaLcI7rQmBVbnclIX7AKniE1Rhz3T/cMT/7u54W5EHNo1z84z7efotq/Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "dateformat": "^3.0.2", + "escape-html": "^1.0.3", + "fs-extra": "^7.0.0", + "fsu": "^1.0.2", + "lodash.isfunction": "^3.0.8", + "opener": "^1.5.2", + "prop-types": "^15.7.2", + "tcomb": "^3.2.17", + "tcomb-validation": "^3.3.0", + "validator": "^10.11.0", + "yargs": "^13.2.2" + }, + "bin": { + "marge": "bin/cli.js" + } + }, + "node_modules/mochawesome-report-generator/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/mochawesome-report-generator/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/mochawesome-report-generator/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mochawesome-report-generator/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true, "license": "MIT" }, + "node_modules/mochawesome-report-generator/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/mochawesome-report-generator/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mochawesome-report-generator/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/mochawesome-report-generator/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mochawesome-report-generator/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/mochawesome-report-generator/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/mochawesome/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -15986,6 +16895,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -16118,7 +17037,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/param-case": { @@ -16252,7 +17170,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16292,7 +17209,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -16309,7 +17225,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -16347,7 +17262,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -16808,7 +17722,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -17404,7 +18317,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -17640,7 +18552,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/safe-push-apply": { @@ -17812,7 +18723,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -18092,7 +19002,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -18105,7 +19014,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18203,6 +19111,16 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-bin-help": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/simple-bin-help/-/simple-bin-help-1.8.0.tgz", + "integrity": "sha512-0LxHn+P1lF5r2WwVB/za3hLRIsYoLaNq1CXqjbrs3ZvLuvlWnRKrUjEWzV7umZL7hpQ7xULiQMV+0iXdRa5iFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -18517,7 +19435,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -18533,7 +19450,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -18646,7 +19562,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18660,7 +19575,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18693,7 +19607,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -18723,7 +19636,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -18786,6 +19698,23 @@ "node": ">=6" } }, + "node_modules/tcomb": { + "version": "3.2.29", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", + "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tcomb-validation": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tcomb": "^3.0.0" + } + }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -19806,6 +20735,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -20806,7 +21745,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -20993,11 +21931,17 @@ "dev": true, "license": "MIT" }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -21016,7 +21960,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -21103,6 +22046,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xmlbuilder2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", + "integrity": "sha512-h4MUawGY21CTdhV4xm3DG9dgsqyhDkZvVJBx88beqX8wJs3VgyGQgAn5VreHuae6unTQxh115aMK5InCVmOIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oozcitak/dom": "1.15.10", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + }, + "engines": { + "node": ">=12.0" + } + }, + "node_modules/xmlbuilder2/node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -21153,11 +22133,62 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/yargs/node_modules/yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", @@ -21197,7 +22228,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, @@ -22521,6 +23551,17 @@ } } }, + "@cypress/grep": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@cypress/grep/-/grep-3.1.5.tgz", + "integrity": "sha512-dbLKP9wGLId+TwTRFDcWVcr9AvJ06W3K7dVeJzLONiPbI5/XJh2mDZvnoyJlAz+VZxdwe0+nejk/CPmuphuzkQ==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "find-test-names": "^1.19.0", + "globby": "^11.0.4" + } + }, "@cypress/request": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", @@ -22921,7 +23962,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "requires": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -22934,26 +23974,22 @@ "ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==" }, "ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -22964,7 +24000,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -22973,7 +24008,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "requires": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -23848,6 +24882,42 @@ "fastq": "^1.6.0" } }, + "@oozcitak/dom": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", + "integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==", + "dev": true, + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "dev": true, + "requires": { + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "dev": true, + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==", + "dev": true + }, "@openshift-console/dynamic-plugin-sdk": { "version": "4.19.0-prerelease.1", "resolved": "https://registry.npmjs.org/@openshift-console/dynamic-plugin-sdk/-/dynamic-plugin-sdk-4.19.0-prerelease.1.tgz", @@ -24090,7 +25160,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true }, "@pkgr/core": { @@ -25275,14 +26344,12 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -25621,8 +26688,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bare-events": { "version": "2.4.2", @@ -25934,6 +27000,12 @@ "symlink-or-copy": "^1.3.1" } }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "peer": true + }, "browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -26090,7 +27162,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -26111,6 +27182,11 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==" + }, "check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", @@ -26433,7 +27509,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -26441,8 +27516,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "colorette": { "version": "2.0.19", @@ -26751,7 +27825,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -26761,11 +27834,15 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" } } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==" + }, "css-loader": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz", @@ -26927,6 +28004,16 @@ } } }, + "cypress-multi-reporters": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", + "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "lodash": "^4.17.21" + } + }, "cypress-terminal-report": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/cypress-terminal-report/-/cypress-terminal-report-3.5.2.tgz", @@ -26961,6 +28048,12 @@ } } }, + "cypress-wait-until": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-3.0.2.tgz", + "integrity": "sha512-iemies796dD5CgjG5kV0MnpEmKSH+s7O83ZoJLVzuVbZmm4lheMsZqAVT73hlMx4QlkwhxbyUzhOBUOZwoOe0w==", + "dev": true + }, "d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -27086,6 +28179,12 @@ "integrity": "sha512-dlLD5rKaKxpFdnjrs+5azHDFOPEu4ANy/LTh04A1DTzMM7qoajmKCBc8pkKRFT41CNzw+4gQh79X5C+Jq27HAw==", "dev": true }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, "dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -27096,7 +28195,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "requires": { "ms": "^2.1.3" } @@ -27346,8 +28444,7 @@ "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "ecc-jsbn": { "version": "0.1.2", @@ -27380,8 +28477,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -27665,8 +28761,7 @@ "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -28382,6 +29477,20 @@ "pkg-dir": "^4.1.0" } }, + "find-test-names": { + "version": "1.29.19", + "resolved": "https://registry.npmjs.org/find-test-names/-/find-test-names-1.29.19.tgz", + "integrity": "sha512-fSO2GXgOU6dH+FdffmRXYN/kLdnd8zkBGIZrKsmAdfLSFUUDLpDFF7+F/h+wjmjDWQmMgD8hPfJZR+igiEUQHQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.27.2", + "@babel/plugin-syntax-jsx": "^7.27.1", + "acorn-walk": "^8.2.0", + "debug": "^4.3.3", + "simple-bin-help": "^1.8.0", + "tinyglobby": "^0.2.13" + } + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -28395,8 +29504,7 @@ "flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" }, "flat-cache": { "version": "3.0.4", @@ -28565,6 +29673,12 @@ "dev": true, "optional": true }, + "fsu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", + "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", + "dev": true + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -28606,8 +29720,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.3.0", @@ -28871,8 +29984,7 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-own-prop": { "version": "2.0.0", @@ -28943,8 +30055,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "heimdalljs": { "version": "0.2.6", @@ -29473,6 +30584,11 @@ "has-tostringtag": "^1.0.2" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -29533,8 +30649,7 @@ "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-generator-fn": { "version": "2.1.0", @@ -29633,8 +30748,7 @@ "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, "is-plain-obj": { "version": "3.0.0", @@ -29729,8 +30843,7 @@ "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, "is-valid-glob": { "version": "1.0.0", @@ -29787,8 +30900,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", @@ -29893,7 +31005,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", "@pkgjs/parseargs": "^0.11.0" @@ -31471,6 +32582,40 @@ "object.values": "^1.1.6" } }, + "junit-report-merger": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/junit-report-merger/-/junit-report-merger-3.0.6.tgz", + "integrity": "sha512-6oziSTxC7MjO3yhkkokbO6EXTDwAtmgjozZgk8EaLu54re3xoeLyc2/DUyEamcF4Pl+nPXnnaVpqo4s+W64h0Q==", + "dev": true, + "requires": { + "fast-glob": "3.2.11", + "xmlbuilder2": "3.0.2" + }, + "dependencies": { + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -31609,6 +32754,30 @@ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true }, + "lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "dev": true + }, + "lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true + }, + "lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -31631,7 +32800,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -31739,6 +32907,16 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -31853,8 +33031,12 @@ "minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" }, "mktemp": { "version": "0.4.0", @@ -31862,11 +33044,482 @@ "integrity": "sha512-IXnMcJ6ZyTuhRmJSjzvHSRhlVPiN9Jwc6e59V0bEJ0ba6OBeX2L0E+mRN1QseeOF4mM+F1Rit6Nh7o+rl2Yn/A==", "dev": true }, + "mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "peer": true, + "requires": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "peer": true + }, + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "peer": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "peer": true, + "requires": { + "ms": "^2.1.3" + } + }, + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "peer": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "peer": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "peer": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "peer": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "peer": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "peer": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "peer": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "peer": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "peer": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "peer": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "peer": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, + "mocha-junit-reporter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", + "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", + "requires": { + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" + } + }, + "mochawesome": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-6.3.1.tgz", + "integrity": "sha512-G2J7Le8ap+0222otJQEUVFs7RYzphiIk21NzaBZE2dbyHJ2+9aai+V2cV7lreEKigDpwQ+SXeiiBH9KQlrkaAQ==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "diff": "^5.0.0", + "json-stringify-safe": "^5.0.1", + "lodash.isempty": "^4.4.0", + "lodash.isfunction": "^3.0.9", + "lodash.isobject": "^3.0.2", + "lodash.isstring": "^4.0.1", + "mochawesome-report-generator": "^5.2.0", + "strip-ansi": "^6.0.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true + } + } + }, + "mochawesome-report-generator": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-5.2.0.tgz", + "integrity": "sha512-DDY/3jSkM/VrWy0vJtdYOf6qBLdaPaLcI7rQmBVbnclIX7AKniE1Rhz3T/cMT/7u54W5EHNo1z84z7efotq/Eg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "dateformat": "^3.0.2", + "escape-html": "^1.0.3", + "fs-extra": "^7.0.0", + "fsu": "^1.0.2", + "lodash.isfunction": "^3.0.8", + "opener": "^1.5.2", + "prop-types": "^15.7.2", + "tcomb": "^3.2.17", + "tcomb-validation": "^3.3.0", + "validator": "^10.11.0", + "yargs": "^13.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "multicast-dns": { "version": "7.2.5", @@ -32174,6 +33827,12 @@ "wsl-utils": "^0.1.0" } }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, "optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -32264,8 +33923,7 @@ "package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "param-case": { "version": "3.0.4", @@ -32364,8 +34022,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-is-absolute": { "version": "1.0.1", @@ -32395,7 +34052,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "requires": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -32404,8 +34060,7 @@ "lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" } } }, @@ -32436,8 +34091,7 @@ "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -32734,7 +34388,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -33172,8 +34825,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" }, "require-from-string": { "version": "2.0.2", @@ -33322,8 +34974,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-push-apply": { "version": "1.0.0", @@ -33456,7 +35107,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -33680,7 +35330,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -33688,8 +35337,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "shell-quote": { "version": "1.8.1", @@ -33751,6 +35399,12 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "simple-bin-help": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/simple-bin-help/-/simple-bin-help-1.8.0.tgz", + "integrity": "sha512-0LxHn+P1lF5r2WwVB/za3hLRIsYoLaNq1CXqjbrs3ZvLuvlWnRKrUjEWzV7umZL7hpQ7xULiQMV+0iXdRa5iFg==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -33997,7 +35651,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -34008,7 +35661,6 @@ "version": "npm:string-width@4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -34088,7 +35740,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -34097,7 +35748,6 @@ "version": "npm:strip-ansi@6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -34117,8 +35767,7 @@ "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, "style-loader": { "version": "3.3.1", @@ -34131,7 +35780,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -34169,6 +35817,21 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, + "tcomb": { + "version": "3.2.29", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", + "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", + "dev": true + }, + "tcomb-validation": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", + "dev": true, + "requires": { + "tcomb": "^3.0.0" + } + }, "teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -34836,6 +36499,12 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "dev": true + }, "value-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", @@ -35515,7 +37184,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -35642,11 +37310,16 @@ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, + "workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "peer": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -35657,7 +37330,6 @@ "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -35709,6 +37381,36 @@ } } }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, + "xmlbuilder2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", + "integrity": "sha512-h4MUawGY21CTdhV4xm3DG9dgsqyhDkZvVJBx88beqX8wJs3VgyGQgAn5VreHuae6unTQxh115aMK5InCVmOIKw==", + "dev": true, + "requires": { + "@oozcitak/dom": "1.15.10", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + }, + "dependencies": { + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -35761,8 +37463,39 @@ "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "peer": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "peer": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "peer": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "peer": true + } + } }, "yauzl": { "version": "2.10.0", @@ -35783,8 +37516,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, "yup": { "version": "0.32.11", diff --git a/web/package.json b/web/package.json index 307a1012c..e20f3aea3 100644 --- a/web/package.json +++ b/web/package.json @@ -21,7 +21,7 @@ "lint:tsc": "tsc --noEmit", "cypress:open": "cypress open", "cypress:run": "cypress run", - "cypress:run:ci": "NO_COLOR=1 npx cypress run --browser electron", + "cypress:run:ci": "NO_COLOR=1 npx cypress run --spec cypress/e2e/integration/*.cy.ts --browser electron", "test": "concurrently -n \"unit,e2e\" --kill-others-on-fail \"npm run test:unit\" \"npm run test:e2e\"", "test:unit": "TZ=UTC jest --config jest.config.js", "test:e2e": "./scripts/run-cypress.sh", @@ -32,6 +32,7 @@ }, "devDependencies": { "@cypress/code-coverage": "^3.10.0", + "@cypress/grep": "^3.1.3", "@jsdevtools/coverage-istanbul-loader": "^3.0.5", "@openshift-console/dynamic-plugin-sdk": "^4.19.0-prerelease.1", "@openshift-console/dynamic-plugin-sdk-webpack": "4.19", @@ -50,7 +51,9 @@ "copy-webpack-plugin": "^13.0.1", "css-loader": "^6.7.1", "cypress": "^14.5.3", + "cypress-multi-reporters": "^1.4.0", "cypress-terminal-report": "^3.5.2", + "cypress-wait-until": "^3.0.2", "eslint": "^8.44.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0", @@ -60,6 +63,8 @@ "i18next-http-backend": "^2.2.0", "i18next-parser": "^9.3.0", "jest": "^30.1.3", + "junit-report-merger": "^3.0.6", + "mochawesome": "^6.1.1", "nyc": "^15.1.0", "prettier": "^2.6.0", "react": "17.0.2", @@ -101,6 +106,7 @@ "@patternfly/react-table": "^5.4.15", "@patternfly/react-virtualized-extension": "^5.1.0", "i18next": "^22.4.12", + "mocha-junit-reporter": "^2.2.1", "react-i18next": "^11.18.6", "react-router-dom-v5-compat": "^6.30.0" }, diff --git a/web/scripts/run-cypress-logging.sh b/web/scripts/run-cypress-logging.sh new file mode 100755 index 000000000..27313fdb7 --- /dev/null +++ b/web/scripts/run-cypress-logging.sh @@ -0,0 +1,231 @@ +#!/usr/bin/env bash +set +x +# Author: anli@redhat.com +# Description: Run Logging UI test using the given users. +# prerequisite: +# clusterlogging are deployed, appplication, infrastructure and audit logs are sent to lokistack. +# The pod produce logs in namespaces log-test-app1,log-test-app2 unceasingly. +# (Note: In prow, the step openshift-observability-enable-cluster-logging can prepare test data) +# The test need at least two users in the given IDP. +# The function enable_idp_htpasswd can create htpasswd IPD with five users +# You can also provide IDP using Environment CYPRESS_LOGIN_IDP,CYPRESS_LOGIN_USERS. +# The Environment KUBECONFIG must be exported. +# The CYPRESS_SPEC can be used to specify spec. The default value is cypress/e2e/logging/*.ts +# The CYPRESS_TAG can be used to filter cases by tag +# + +## Add htpasswd IDP and Users +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UI_USERS="" + +function enable_idp_htpasswd() +{ + echo "## Create htpasswd IDP users" + htpass_file="/tmp/uihtpasswd" + uiusers_file="/tmp/uihtpusers" + + idp_list=$(oc get oauth cluster -o jsonpath='{.spec.identityProviders}') + if [[ $idp_list =~ "uiauto-htpasswd-idp" ]];then + # using existing idp if the user can login + echo "The idp uiauto-htpasswd-idp had been created" + if [[ -f $uiusers_file ]];then + echo "Verify if user can login uiauto-htpasswd-idp" + UI_USERS=$(cat $uiusers_file) + echo "get users from ${uiusers_file}" + first_record=${UI_USERS%%,*} + first_passwd=${first_record##*:} + cp $KUBECONFIG /tmp/normal_kubeconfig || exit 1 + oc login --username=uiauto-test-1 --password=${first_passwd} --kubeconfig=/tmp/normal_kubeconfig >/dev/null 2>&1 + if [[ $? == 0 ]];then + echo "Login the idp succesed, the users are in $uiusers_file" + echo "Enable IDP uiauto-htpasswd-idp succesfully" + return 0 + else + echo "Can not login the idp, please remove uiauto-htpasswd-idp from oauth/cluster and re-run this script" + exit 1 + fi + else + echo "Can not find users, please remove uiauto-htpasswd-idp from oauth/cluster and re-run this script" + exit 1 + fi + fi + + echo "Create new users and add uiauto-htpasswd-idpuiauto-htpasswd-idp" + #Create users with random password and save users + for i in $(seq 1 5); do + username="uiauto-test-${i}" + password=$(tr $uiusers_file + echo "Users are store in ${UI_USERS}" + + # record current generation number + gen_number=$(oc -n openshift-authentication get deployment oauth-openshift -o jsonpath='{.metadata.generation}') + + # add users to cluster + oc -n openshift-config create secret generic uiauto-htpass-secret || true + oc -n openshift-config set data secret/uiauto-htpass-secret --from-file=htpasswd=${htpass_file} -n openshift-config || exit 1 + + idp_list=$(oc get oauth cluster -o jsonpath='{.spec.identityProviders}') + if [[ $idp_list == "" || $idp_list == "{}" ]];then + oc patch oauth cluster --type='json' -p='[{"op": "add", "path": "/spec/identityProviders", "value": [{"type": "HTPasswd", "name": "uiauto-htpasswd-idp", "mappingMethod": "claim", "htpasswd":{"fileData":{"name": "uiauto-htpass-secret"}}}]}]' || exit 1 + else + oc patch oauth cluster --type='json' -p='[{"op": "add", "path": "/spec/identityProviders/-", "value": {"type": "HTPasswd", "name": "uiauto-htpasswd-idp", "mappingMethod": "claim", "htpasswd":{"fileData":{"name": "uiauto-htpass-secret"}}}}]' || exit 1 + fi + + echo "Wait up to 5 minutes for new idp take effect" + expected_replicas=$(oc -n openshift-authentication get deployment oauth-openshift -o jsonpath='{.spec.replicas}') + count=1 + while [[ $count -le 6 ]]; do + echo "try the ${count} time " + available_replicas=$(oc -n openshift-authentication get deployment oauth-openshift -o jsonpath='{.status.availableReplicas}') + new_gen_number=$(oc get -n openshift-authentication deployment oauth-openshift -o jsonpath='{.metadata.generation}') + if [[ $expected_replicas == "$available_replicas" && $((new_gen_number)) -gt $((gen_number)) ]]; then + break + else + sleep 30s + fi + (( count=count+1 )) + done + + echo "Verify if uiauto-htpasswd-idp works" + echo "Login as the new user" + cp $KUBECONFIG /tmp/normal_kubeconfig || exit 1 + first_record=${UI_USERS%%,*} + first_passwd=${first_record##*:} + + echo "oc login -u uiauto-test-1 -p --config=/tmp/normal_kubeconfig" + oc login --username=uiauto-test-1 --password=${first_passwd} --kubeconfig=/tmp/normal_kubeconfig >/dev/null 2>&1 || exit 1 + echo "Enable IDP uiauto-htpasswd-idp succesfully" +} + +function check_clusterlogging(){ + echo "## Verify test data are ready for Logging UI Test" + + echo "Check if the clusterlogging are are ready" + lokistack_name=$(oc get lokistack -n openshift-logging -o jsonpath={.items[0].metadata.name}) + if [[ $lokistack_name == "" ]]; then + echo "No lokistack can be found in openshift-logging namespace" + exit 1 + fi + echo "Warnig, lokistack ${lokistack_name} is selected, please confirm if that is the one you are using in openshift-logging" + oc -n openshift-logging wait pod --for=condition=ready -l app.kubernetes.io/instance=${lokistack_name} || exit 1 + oc -n openshift-logging wait pod --for=condition=ready -l app.kubernetes.io/component=collector || exit 1 + + + echo "Check if there are running test pods in log-test-app1 and log-test-app2" + oc -n log-test-app1 wait pod --for=condition=ready -l test=centos-logtest || exit 1 + oc -n log-test-app2 wait pod --for=condition=ready -l test=centos-logtest || exit 1 + + echo "Check if logs can be found in lokistack" + lokistack_route=$(oc -n openshift-logging get route ${lokistack_name} -n openshift-logging -o json |jq '.spec.host' -r) + oc -n openshift-logging create sa lokistack-query >/dev/null 2>&1 + oc -n openshift-logging adm policy add-cluster-role-to-user cluster-admin -z lokistack-query + oc -n openshift-logging adm policy add-cluster-role-to-user cluster-logging-application-view -z lokistack-query + oc -n openshift-logging adm policy add-cluster-role-to-user cluster-logging-audit-view -z lokistack-query + oc -n openshift-logging adm policy add-cluster-role-to-user cluster-logging-infrastructure-view -z lokistack-query + + bearer_token=$(oc -n openshift-logging create token lokistack-query) + + echo "Verify infrastructure logs in lokistack" + rm /tmp/loki_query.txt + curl -s -G -k -H "Authorization: Bearer ${bearer_token}" https://${lokistack_route}/api/logs/v1/infrastructure/loki/api/v1/query_range --data-urlencode 'query={log_type="infrastructure"}' --data-urlencode 'limit=1' -o /tmp/loki_query.txt + if [[ $(cat /tmp/loki_query.txt |jq '.data.result|length') == 1 ]]; then + echo "Found infrastructure logs" + else + echo "Exit, can not find infrastructure logs" + cat /tmp/loki_query.txt + exit 1 + fi + + echo "Verify application logs in lokistack" + rm /tmp/loki_query.txt + curl -s -G -k -H "Authorization: Bearer ${bearer_token}" https://${lokistack_route}/api/logs/v1/application/loki/api/v1/query_range --data-urlencode 'query={log_type="application"}' --data-urlencode 'limit=1' -o /tmp/loki_query.txt + if [[ $(cat /tmp/loki_query.txt |jq '.data.result|length') == 1 ]]; then + echo "Found application logs" + else + echo "Exit, can not find application logs" + cat /tmp/loki_query.txt + fi + + echo "Verify audit logs in lokistack" + rm /tmp/loki_query.txt + curl -s -G -k -H "Authorization: Bearer ${bearer_token}" https://${lokistack_route}/api/logs/v1/audit/loki/api/v1/query_range --data-urlencode 'query={log_type="audit"}' --data-urlencode 'limit=1' -o /tmp/loki_query.txt + if [[ $(cat /tmp/loki_query.txt |jq '.data.result|length') == 1 ]]; then + echo "Found audit logs" + else + echo "Exit, can not find audit logs" + cat /tmp/loki_query.txt + exit 1 + fi +} + +########Main################### +if [[ $KUBECONFIG == "" ]]; then + echo "Exit, you must expose the Environment KUBECONFIG" + exit 1 +fi + +export CYPRESS_BASE_URL="https://$(oc get route console -n openshift-console -o jsonpath={.spec.host})" +export CYPRESS_OPENSHIFT_VERSION=$(oc version -o json |jq -r '.openshiftVersion'|cut -f 1,2 -d.) +clusterlogging_csv=$(oc -n openshift-logging get csv -l "operators.coreos.com/cluster-logging.openshift-logging" -o jsonpath='{.items[0].metadata.name}') +if [[ $clusterlogging_csv == "" ]];then + echo "can not find the cluster-logging csv" + exit 1 +fi +clusterlogging_csv_version=${clusterlogging_csv#cluster-logging.v} +export CYPRESS_CLUSTERLOGGING_VERSION=$(echo $clusterlogging_csv_version|cut -d. -f1,2) + +coo_csv=$(oc get csv -l olm.copiedFrom"="openshift-cluster-observability-operator -o jsonpath='{.items[0].metadata.name}') +export CYPRESS_COO_VERSION=${coo_csv//cluster-observability-operator.v} + +data_mode=$(oc get uiplugin logging -o jsonpath='{.spec.logging.schema}') +if [[ "$data_mode" == "" ]];then + data_mode="viaq" +fi +export CYPRESS_CLUSTERLOGGING_DATAMODE=${data_mode} + +check_clusterlogging + +if [[ "$CYPRESS_LOGIN_IDP" == "" || "$CYPRESS_LOGIN_USERS" == "" ]];then + enable_idp_htpasswd + export CYPRESS_LOGIN_IDP=uiauto-htpasswd-idp + export CYPRESS_LOGIN_USERS=$UI_USERS +fi +if [[ $CYPRESS_LOGIN_USERS == "" ]];then + echo "Please set correct Env CYPRESS_LOGIN_USERS and CYPRESS_LOGIN_IDP or leave these two Env unset" + exit 1 +fi + +echo "export KUBECONFIG=${KUBECONFIG}" +echo "export CYPRESS_BASE_URL=$CYPRESS_BASE_URL" +echo "export CYPRESS_LOGIN_IDP=$CYPRESS_LOGIN_IDP" +echo "export CYPRESS_LOGIN_USERS=$CYPRESS_LOGIN_USERS" +echo "export CYPRESS_OPENSHIFT_VERSION=$CYPRESS_OPENSHIFT_VERSION" +echo "export CYPRESS_CLUSTERLOGGING_VERSION=$CYPRESS_CLUSTERLOGGING_VERSION" +echo "export CYPRESS_CLUSTERLOGGING_DATAMODE=$CYPRESS_CLUSTERLOGGING_DATAMODE" +echo "export CYPRESS_COO_VERSION=${CYPRESS_COO_VERSION}" + +echo "## Execute Cypress cases" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd $script_dir/../ + +cypress_args="" +if [[ "$CYPRESS_SPEC" == "" ]];then + cypress_args=" --spec $(ls ${SCRIPT_DIR}/../cypress/e2e/logging/*.ts|paste -sd ',' -)" +else + cypress_args=" --spec ${CYPRESS_SPEC}" +fi +if [[ "$CYPRESS_TAG" != "" ]]; then + cypress_args="$cypress_args --env grep=${CYPRESS_TAG// /}" +fi +echo "npx cypress run --e2e ${cypress_args}" +npx cypress run --e2e ${cypress_args}