diff --git a/.tekton/integration-tests/lightspeed-console-pre-commit.yaml b/.tekton/integration-tests/lightspeed-console-pre-commit.yaml index 832f7f27..169aa908 100644 --- a/.tekton/integration-tests/lightspeed-console-pre-commit.yaml +++ b/.tekton/integration-tests/lightspeed-console-pre-commit.yaml @@ -129,8 +129,8 @@ spec: description: "commit sha to be used in console tests" script: | dnf -y install jq - echo -n "$(jq -r --arg component_name "lightspeed-console" '.components[] | select(.name == $component_name) | .containerImage' <<< "$SNAPSHOT")" > $(step.results.console-image.path) - echo -n "$(jq -r --arg component_name "lightspeed-console" '.components[] | select(.name == $component_name) | .source.git.revision' <<< "$SNAPSHOT")" > $(step.results.commit.path) + echo -n "$(jq -r --arg component_name "lightspeed-console-4-19" '.components[] | select(.name == $component_name) | .containerImage' <<< "$SNAPSHOT")" > $(step.results.console-image.path) + echo -n "$(jq -r --arg component_name "lightspeed-console-4-19" '.components[] | select(.name == $component_name) | .source.git.revision' <<< "$SNAPSHOT")" > $(step.results.commit.path) - name: lint description: Run lint checks runAfter: @@ -359,3 +359,23 @@ spec: value: main - name: pathInRepo value: stepactions/fail-if-any-step-failed/0.1/fail-if-any-step-failed.yaml + finally: + - name: export-logs-for-retention + taskRef: + resolver: git + params: + - name: url + value: https://github.com/konflux-ci/tekton-integration-catalog.git + - name: revision + value: main + - name: pathInRepo + value: tasks/export-logs/0.1/export-logs-to-quay.yaml + params: + - name: pipeline-run-name + value: $(context.pipelineRun.name) + - name: namespace + value: $(context.pipelineRun.namespace) + - name: quay-repo + value: "quay.io/openshift-lightspeed/ols-console-artifacts" + - name: artifact-credentials-secret + value: ols-konflux-artifacts-bot \ No newline at end of file diff --git a/cypress.config.ts b/cypress.config.ts index 1ea9873d..120aa4b3 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,8 +1,79 @@ import { defineConfig } from 'cypress'; import { plugin as cypressGrepPlugin } from '@cypress/grep/plugin'; +import { execFileSync } from 'child_process'; import * as fs from 'fs'; +import * as path from 'path'; import * as console from 'console'; +const ARTIFACTS_DIR = './gui_test_screenshots/artifacts'; +const OLS_NAMESPACE = 'openshift-lightspeed'; +const OC_TIMEOUT = 30000; + +const CLUSTER_RESOURCES = [ + 'pods', + 'services', + 'deployments', + 'replicasets', + 'routes', + 'rolebindings', + 'serviceaccounts', + 'olsconfig', + 'clusterserviceversion', + 'installplan', + 'configmap', +]; + +function runOC(args: string[], kubeconfigPath: string): string | null { + const argv = kubeconfigPath ? [...args, '--kubeconfig', kubeconfigPath] : args; + try { + return execFileSync('oc', argv, { + encoding: 'utf-8', + timeout: OC_TIMEOUT, + }); + } catch (e: unknown) { + console.error(`oc ${args.slice(0, 3).join(' ')} failed: ${e}`); + return null; + } +} + +function gatherClusterArtifacts(kubeconfigPath: string) { + const clusterDir = path.join(ARTIFACTS_DIR, 'cluster'); + const podLogsDir = path.join(clusterDir, 'podlogs'); + fs.mkdirSync(podLogsDir, { recursive: true }); + + for (const resource of CLUSTER_RESOURCES) { + const output = runOC(['get', resource, '-n', OLS_NAMESPACE, '-o', 'yaml'], kubeconfigPath); + if (output) { + fs.writeFileSync(path.join(clusterDir, `${resource}.yaml`), output); + } + } + + // Pod logs + const podsJson = runOC(['get', 'pods', '-n', OLS_NAMESPACE, '-o', 'json'], kubeconfigPath); + if (podsJson) { + try { + const pods = JSON.parse(podsJson); + for (const pod of pods.items || []) { + const podName = pod.metadata?.name; + const containers = (pod.spec?.containers || []).map((c: { name: string }) => c.name); + for (const container of containers) { + const logs = runOC( + ['logs', `pod/${podName}`, '-c', container, '-n', OLS_NAMESPACE], + kubeconfigPath, + ); + if (logs) { + fs.writeFileSync(path.join(podLogsDir, `${podName}-${container}.log`), logs); + } + } + } + } catch (e: unknown) { + console.error(`Failed to parse pod JSON: ${e}`); + } + } + + console.log(`Cluster artifacts gathered in ${clusterDir}`); +} + export default defineConfig({ screenshotsFolder: './gui_test_screenshots/cypress/screenshots', screenshotOnRunFailure: true, @@ -72,13 +143,17 @@ export default defineConfig({ console.table(data); return null; }, + gatherClusterArtifacts() { + gatherClusterArtifacts(config.env.KUBECONFIG_PATH || ''); + return null; + }, }); cypressGrepPlugin(config); - on('after:spec', (spec: Cypress.Spec, results: CypressCommandLine.RunResult) => { + 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'), + 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 diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index dfff9974..39382626 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -4,6 +4,16 @@ import { register } from '@cypress/grep'; register(); +Cypress.on('uncaught:exception', (err) => { + // The console defines __load_plugin_entry__ via webpack module federation + // before loading plugin bundles, but race conditions during page load can + // cause the plugin script to execute before the global is set. + // This is a console-internal timing issue, not a test failure. + if (err.message?.includes('__load_plugin_entry__')) { + return false; + } +}); + // Collect browser console errors and warnings for output after each test const browserLogs = []; diff --git a/tests/tests/lightspeed-install.cy.ts b/tests/tests/lightspeed-install.cy.ts index 699d35c5..f0cdc8ec 100644 --- a/tests/tests/lightspeed-install.cy.ts +++ b/tests/tests/lightspeed-install.cy.ts @@ -355,6 +355,9 @@ describe('OLS UI', () => { }); after(() => { + // Gather cluster artifacts before cleanup so resources still exist + cy.task('gatherClusterArtifacts'); + if (Cypress.env('SKIP_OLS_SETUP')) { cy.task('log', 'Skip OLS uninstall because CYPRESS_SKIP_OLS_SETUP is true'); } else {