diff --git a/CHANGELOG.md b/CHANGELOG.md index 72160665..57d5153b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Improvements in the reporter for cypress ([#1042](https://github.com/opendevstack/ods-quickstarters/issues/1042)) - Added new function to cypress to log into applications using MFA ([#1070](https://github.com/opendevstack/ods-quickstarters/pull/1070)) - Removal of deprecated versions ([#1068](https://github.com/opendevstack/ods-quickstarters/issues/1068)) +- Generate PDF report for cypress and improved environment management ([#1079](https://github.com/opendevstack/ods-quickstarters/pull/1079)) ### Added diff --git a/docs/modules/quickstarters/pages/e2e-cypress.adoc b/docs/modules/quickstarters/pages/e2e-cypress.adoc index e2b8f65b..e44df68f 100644 --- a/docs/modules/quickstarters/pages/e2e-cypress.adoc +++ b/docs/modules/quickstarters/pages/e2e-cypress.adoc @@ -121,20 +121,31 @@ To use Cypress Cloud within the Cypress QuickStarter, follow these steps: 3. **Set the Cypress Record Key as an environment variable in Openshift.** To enable recording of your tests in Cypress Cloud, you will need to set the Cypress Record Key as an environment variable named CYPRESS_RECORD_KEY in Openshift. This key is provided by Cypress and is used to authenticate your tests and results. By setting it in Openshift, we ensure that the record functionality will only be used in official runs and not for local development. -4. **Modify the Jenkinsfile for using the record script.** In the Jenkinsfile, change the exeuction line: +4. **Modify the Jenkinsfile for using the record script.** In the Jenkinsfile, change the exeuction lines: [source,Jenkinsfile] ---- -def status = sh(script: 'npm run e2e', returnStatus: true) +status = sh(script: 'npm run e2e', returnStatus: true) ---- -for the following block of code, which will run the record script only when in master or in a release branch: +and +[source,Jenkinsfile] +---- +status = sh(script: 'npm run e2e:prod', returnStatus: true) +---- +for the following blocks of code, which will run the record script only when in master or in a release branch: [source,Jenkinsfile] ---- if (context.gitBranch == 'master' || context.gitBranch.startsWith('release/')) { - def status = sh(script: 'npm run e2e:jenkins:record', returnStatus: true) + status = sh(script: 'npm run e2e:jenkins:record', returnStatus: true) } else { - def status = sh(script: 'npm run e2e', returnStatus: true) + status = sh(script: 'npm run e2e', returnStatus: true) } ---- +and +[source,Jenkinsfile] +---- +status = sh(script: 'npm run e2e:jenkins:record', returnStatus: true) +---- +For the case of prod environment, the master or release check is not necessary, as the prod environment is only used in releases. **Only use this functionality in releases, not development.** It is important to note that Cypress Cloud is intended for use in releases, not development. This ensures that your tests are run against stable and reliable code, and that the Dashboard does not get overflooded with non-relevant tests. For the same reason, the Jenkinsfile is configured to only pass the record parameter when running in the master branch, or in a release. diff --git a/e2e-cypress/Jenkinsfile.template b/e2e-cypress/Jenkinsfile.template index dd5e5df3..2932b9e1 100644 --- a/e2e-cypress/Jenkinsfile.template +++ b/e2e-cypress/Jenkinsfile.template @@ -29,6 +29,7 @@ odsComponentPipeline( // 'release/': 'test' ] ) { context -> + def targetDirectory = "${context.projectId}/${context.componentId}/${context.gitBranch.replaceAll('/', '-')}/${context.buildNumber}" stageTest(context) @@ -44,54 +45,97 @@ odsComponentPipeline( ] ) } - + if (fileExists('cypress/videos.zip')) { + odsComponentStageUploadToNexus(context, + [ + distributionFile: 'cypress/videos.zip', + repository: 'leva-documentation', + repositoryType: 'raw', + targetDirectory: "${targetDirectory}" + ] + ) + } + if (fileExists('cypress/pdf.zip')) { + odsComponentStageUploadToNexus(context, + [ + distributionFile: 'cypress/pdf.zip', + repository: 'leva-documentation', + repositoryType: 'raw', + targetDirectory: "${targetDirectory}" + ] + ) + } } def stageTest(def context) { - stage('Integration Test') { - // OPTIONAL: load environment variables for Azure SSO with MSALv2; please adapt variable names to your OpenShift config + stage('Functional Tests') { + // Define your DEV and QA base URLs in a config map in OpenShift; please adapt variable names to your OpenShift config // sh "oc project " - // cypressUser = sh(returnStdout: true, script:"oc get secret e2euser -o jsonpath='{.data.USERNAME}' | base64 -d") - // cypressPassword = sh(returnStdout: true, script:"oc get secret e2euser -o jsonpath='{.data.PASSWORD}' | base64 -d") - // azureClientId = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_ID}' | base64 -d") - // azureClientSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_SECRET}' | base64 -d") - // azureTenantId = sh(returnStdout: true, script:"oc get configmaps azure -o jsonpath='{.data.AZURE_TENANT}'") // config map values are not base64 encoded - // authenticatorOTPSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.OTP_SECRET}' | base64 -d") + def baseUrls = [ + : // remove this line once you have defined the config map and uncommented the next three lines, it's only here to make the example default case work + // dev: sh(returnStdout: true, script:"oc get configmaps cypress-config -o jsonpath='{.data.DEV_BASE_URL}'").trim(), + // test: sh(returnStdout: true, script:"oc get configmaps cypress-config -o jsonpath='{.data.TEST_BASE_URL}'").trim() + // prod: sh(returnStdout: true, script:"oc get configmaps cypress-config -o jsonpath='{.data.PROD_BASE_URL}'").trim() + ] + + def baseUrl = baseUrls.get(context.environment ?: 'dev', 'https://www.w3schools.com') // default to W3Schools for demo purposes, replace with your own default withEnv(["TAGVERSION=${context.tagversion}", "NEXUS_HOST=${context.nexusHost}", "OPENSHIFT_PROJECT=${context.targetProject}", "OPENSHIFT_APP_DOMAIN=${context.getOpenshiftApplicationDomain()}", - // "CYPRESS_TENANT_ID=${azureTenantId}", - // "CYPRESS_CLIENT_ID=${azureClientId}", - // "CYPRESS_CLIENT_SECRET=${azureClientSecret}", - // "CYPRESS_USERNAME=${cypressUser}", - // "CYPRESS_PASSWORD=${cypressPassword}", - // "OTP_SECRET=${authenticatorOTPSecret}", "COMMIT_INFO_SHA=${context.gitCommit}", "BUILD_NUMBER=${context.buildNumber}", + "BASE_URL=${baseUrl}", ]) { - sh 'npm install' - def status = sh(script: 'npm run e2e', returnStatus: true) - sh 'npm run combine:reports' - junit(testResults:'build/test-results/*.xml', allowEmptyResults: true) - stash(name: "installation-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/installation-junit.xml', allowEmpty: true) - stash(name: "integration-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/integration-junit.xml', allowEmpty: true) - stash(name: "acceptance-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/acceptance-junit.xml', allowEmpty: true) + // For loading environment variables for Azure SSO; please adapt the secrets names to your OpenShift config, + // making sure to include in the secret names the environment name in lowercase (e.g., e2e-user-dev, e2e-user-test, + // etc.), in case of using different secrets for different environments. + // Secrets in OpenShift need to be created with the credential.sync.jenkins.openshift.io=true label to be available in Jenkins. + // These are examples for loading credentials from the testing user for logging into your application. + withCredentials([ + // usernamePassword(credentialsId: "${context.projectId}-cd-e2e-user", passwordVariable: 'CYPRESS_PASSWORD', usernameVariable: 'CYPRESS_USERNAME'), + // string(credentialsId: "${context.projectId}-cd-otp-secret", variable: 'OTP_SECRET') + ]) { + sh 'npm install' - if (fileExists('cypress/videos')) { - zip zipFile: 'cypress/videos.zip', archive: false, dir: 'cypress/videos' - stash(name: "acceptance-test-videos-${context.componentId}-${context.buildNumber}", includes: 'cypress/videos.zip', allowEmpty: true) - archiveArtifacts artifacts: 'cypress/videos.zip', fingerprint: true, daysToKeep: 2, numToKeep: 3 - } + // Note: Testing in the production environment is not enabled by default as it can lead to unintended consequences, + // including potential downtime, data corruption, or exposure of sensitive information. + // This block is designed to skip acceptance and integration tests in the production environment to avoid these risks. + // If you choose to enable these tests in production take all necessary precautions. This means verifying your + // preconditions, database access, fake data, API calls, etc. + // Remember that any test case in the installation folder will be executed in production. + def status + if (context.environment == 'prod') { + status = sh(script: 'npm run e2e:prod', returnStatus: true) + sh 'npm run junit-installation-report' + junit(testResults:'build/test-results/*.xml') + stash(name: "installation-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/installation-junit.xml', allowEmpty: true) + } else { + status = sh(script: 'npm run e2e', returnStatus: true) + sh 'npm run combine:reports' + junit(testResults:'build/test-results/*.xml') + stash(name: "installation-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/installation-junit.xml', allowEmpty: true) + stash(name: "integration-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/integration-junit.xml', allowEmpty: true) + stash(name: "acceptance-test-reports-junit-xml-${context.componentId}-${context.buildNumber}", includes: 'build/test-results/acceptance-junit.xml', allowEmpty: true) + } + + sh 'npm run generate:pdf' + zip zipFile: 'cypress/pdf.zip', archive: false, dir: 'build/test-results/mochawesome/pdf' + archiveArtifacts artifacts: 'cypress/pdf.zip', fingerprint: true + + if (fileExists('cypress/videos')) { + zip zipFile: 'cypress/videos.zip', archive: false, dir: 'cypress/videos' + } + + if (fileExists('build/test-results/screenshots')) { + zip zipFile: 'cypress/screenshots.zip', archive: false, dir: 'build/test-results/screenshots' + } - if (fileExists('cypress/screenshots')) { - zip zipFile: 'cypress/screenshots.zip', archive: false, dir: 'cypress/screenshots' - stash(name: "acceptance-test-screenshots-${context.componentId}-${context.buildNumber}", includes: 'cypress/screenshots.zip', allowEmpty: true) - archiveArtifacts artifacts: 'cypress/screenshots.zip', fingerprint: true, daysToKeep: 2, numToKeep: 3 + if (status != 0) { + unstable "Some tests have failed or encountered errors. Please check the logs for more details." + } } - - return status } } } diff --git a/e2e-cypress/files/README.md b/e2e-cypress/files/README.md index 89367eb3..3d213a40 100644 --- a/e2e-cypress/files/README.md +++ b/e2e-cypress/files/README.md @@ -10,29 +10,12 @@ Please note that each stage is executed with its own Cypress configuration file ## Running end-to-end tests -Run `npm run e2e` to execute all end-to-end tests via [Cypress](https://www.cypress.io) against the test instance of the front end. +Run `npm run e2e` to execute all end-to-end tests via [Cypress](https://www.cypress.io) against the test instance of the front end. In order to run the tests against different environments for releases, you can define the base URLs of each environment in a config map in OpenShift, and import them as environment variables in the `Jenkinsfile`. See an example on how to do this using `context.environment` in the `Jenkinsfile`. ## Local development Run `npm start` to develop the e2e tests. The tests will automatically rebuild and run, if you change any of the source files. Ideally the test will run against a local instance of the front end, e.g. `http://localhost:4200` for an Angular app. This destination is configurable in the `cypress.config.ts` file. -## How to upload images or videos to Nexus - -Screenshots are only taken when test fails. - 1. Open the Jenkinsfile - 2. Replace `stageTest(context)` by the following lines: - ``` - def status = stageTest(context) - if (status != 0) { - odsComponentStageUploadToNexus(context, - [ - distributionFile: 'tests/screenshots.zip', - repository: 'leva-documentation', - repositoryType: 'raw', - targetDirectory: "${targetDirectory}"]) - } - ``` - ## Reports From [Merging reports across spec files](https://docs.cypress.io/guides/tooling/reporters#Merging-reports-across-spec-files): each spec file is processed completely separately during each cypress run execution. Thus each spec run overwrites the previous report file. To preserve unique reports for each spec file, use the `[hash]` in the `mochaFile` filename. @@ -40,8 +23,10 @@ In order to generate one xml report per test type (installation, integration and ## E2e test user authentication -With Cypress 12 version is now available `cy.origin()` that allows you to handle redirections. This funcionality eases the login handling. -See `./support/e2e.ts` for a generic login example. +Starting version 12 of Cypress `cy.origin()` allows you to handle redirections. This functionality eases the login handling. +See `./support/login-functions.ts` for a generic login example. + +In order to load your testing user's credentials for this login with SSO, you need to create a secret in OpenShift with the label `credential.sync.jenkins.openshift.io=true` and import them as environment variables using the `withCredentials` block to keep them secure. Find an example on how to do this in the `Jenkinsfile`. ## Cypress Cloud diff --git a/e2e-cypress/files/cypress-acceptance.config.ts b/e2e-cypress/files/cypress-acceptance.config.ts index 8dbceffb..bdf1616e 100644 --- a/e2e-cypress/files/cypress-acceptance.config.ts +++ b/e2e-cypress/files/cypress-acceptance.config.ts @@ -1,26 +1,21 @@ -import { defineConfig } from 'cypress' -import setupNodeEvents from './plugins/index.js' +import { defineConfig } from 'cypress'; +import baseConfig from './cypress.config'; + export default defineConfig({ - //projectId: '[Your project ID from Cypress cloud]', - reporter: 'reporters/custom-reporter.js', + ...baseConfig, reporterOptions: { - mochaFile: 'build/test-results/acceptance-junit-[hash].xml', - toConsole: true, + ...baseConfig.reporterOptions, + mochawesomeReporterOptions: { + ...baseConfig.reporterOptions.mochawesomeReporterOptions, + reportFilename: 'acceptance-mochawesome', + }, + reportersCustomReporterJsReporterOptions: { + ...baseConfig.reporterOptions.reportersCustomReporterJsReporterOptions, + mochaFile: 'build/test-results/acceptance-junit-[hash].xml', + }, }, e2e: { - baseUrl: 'https://www.w3schools.com', - fixturesFolder: "fixtures", - specPattern: 'tests/acceptance/*.cy.ts', - supportFile: "support/e2e.ts", - viewportWidth: 1376, - viewportHeight: 660, - experimentalModifyObstructiveThirdPartyCode:true, - video: true, - async setupNodeEvents(on, config) { - return (await import('./plugins/index')).default(on, config); - }, + ...baseConfig.e2e, + specPattern: 'tests/acceptance/**/*.cy.ts', }, - // env: { - // otp_secret: process.env.OTP_SECRET - // }, -}) +}); diff --git a/e2e-cypress/files/cypress-installation.config.ts b/e2e-cypress/files/cypress-installation.config.ts index b49600bd..968d7cd0 100644 --- a/e2e-cypress/files/cypress-installation.config.ts +++ b/e2e-cypress/files/cypress-installation.config.ts @@ -1,26 +1,21 @@ -import { defineConfig } from 'cypress' -import setupNodeEvents from './plugins/index.js' +import { defineConfig } from 'cypress'; +import baseConfig from './cypress.config'; + export default defineConfig({ - //projectId: '[Your project ID from Cypress cloud]', - reporter: 'reporters/custom-reporter.js', + ...baseConfig, reporterOptions: { - mochaFile: 'build/test-results/installation-junit-[hash].xml', - toConsole: true, + ...baseConfig.reporterOptions, + mochawesomeReporterOptions: { + ...baseConfig.reporterOptions.mochawesomeReporterOptions, + reportFilename: 'installation-mochawesome', + }, + reportersCustomReporterJsReporterOptions: { + ...baseConfig.reporterOptions.reportersCustomReporterJsReporterOptions, + mochaFile: 'build/test-results/installation-junit-[hash].xml', + }, }, e2e: { - baseUrl: 'https://www.w3schools.com', - fixturesFolder: "fixtures", - specPattern: 'tests/installation/*.cy.ts', - supportFile: "support/e2e.ts", - viewportWidth: 1376, - viewportHeight: 660, - experimentalModifyObstructiveThirdPartyCode:true, - video: true, - async setupNodeEvents(on, config) { - return (await import('./plugins/index')).default(on, config); - }, + ...baseConfig.e2e, + specPattern: 'tests/installation/**/*.cy.ts', }, - // env: { - // otp_secret: process.env.OTP_SECRET - // }, -}) +}); diff --git a/e2e-cypress/files/cypress-integration.config.ts b/e2e-cypress/files/cypress-integration.config.ts index 2a333466..8f3b99b9 100644 --- a/e2e-cypress/files/cypress-integration.config.ts +++ b/e2e-cypress/files/cypress-integration.config.ts @@ -1,26 +1,21 @@ -import { defineConfig } from 'cypress' -import setupNodeEvents from './plugins/index.js' +import { defineConfig } from 'cypress'; +import baseConfig from './cypress.config'; + export default defineConfig({ - //projectId: '[Your project ID from Cypress cloud]', - reporter: 'reporters/custom-reporter.js', + ...baseConfig, reporterOptions: { - mochaFile: 'build/test-results/integration-junit-[hash].xml', - toConsole: true, + ...baseConfig.reporterOptions, + mochawesomeReporterOptions: { + ...baseConfig.reporterOptions.mochawesomeReporterOptions, + reportFilename: 'integration-mochawesome', + }, + reportersCustomReporterJsReporterOptions: { + ...baseConfig.reporterOptions.reportersCustomReporterJsReporterOptions, + mochaFile: 'build/test-results/integration-junit-[hash].xml', + }, }, e2e: { - baseUrl: 'https://www.w3schools.com', - fixturesFolder: "fixtures", - specPattern: 'tests/integration/*.cy.ts', - supportFile: "support/e2e.ts", - viewportWidth: 1376, - viewportHeight: 660, - experimentalModifyObstructiveThirdPartyCode:true, - video: true, - async setupNodeEvents(on, config) { - return (await import('./plugins/index')).default(on, config); - }, + ...baseConfig.e2e, + specPattern: 'tests/integration/**/*.cy.ts', }, - // env: { - // otp_secret: process.env.OTP_SECRET - // }, -}) +}); diff --git a/e2e-cypress/files/cypress.config.ts b/e2e-cypress/files/cypress.config.ts index df26ab30..dfd361f6 100644 --- a/e2e-cypress/files/cypress.config.ts +++ b/e2e-cypress/files/cypress.config.ts @@ -1,20 +1,32 @@ import { defineConfig } from 'cypress' import setupNodeEvents from './plugins/index.js' export default defineConfig({ - reporter: 'reporters/custom-reporter.js', + reporter: 'cypress-multi-reporters', reporterOptions: { - mochaFile: 'build/test-results/tests-[hash].xml', - toConsole: true, + reporterEnabled: 'mochawesome,./reporters/custom-reporter.js', + mochawesomeReporterOptions: { + reportDir: 'build/test-results/mochawesome', + reportFilename: 'mochawesome', + charts: true, + html: true, + timestamp: true, + json: true + }, + reportersCustomReporterJsReporterOptions: { + mochaFile: 'build/test-results/tests-[hash].xml', + toConsole: true, + }, }, e2e: { - baseUrl: 'https://www.w3schools.com', + baseUrl: process.env.BASE_URL || 'https://www.w3schools.com', fixturesFolder: "fixtures", specPattern: 'tests/**/*.cy.ts', supportFile: "support/e2e.ts", - viewportWidth: 1376, - viewportHeight: 660, + screenshotsFolder: 'build/test-results/screenshots', + viewportWidth: 1280, + viewportHeight: 720, experimentalModifyObstructiveThirdPartyCode: true, - video: true, + video: false, async setupNodeEvents(on, config) { return (await import('./plugins/index')).default(on, config); }, diff --git a/e2e-cypress/files/package.json b/e2e-cypress/files/package.json index 505063a4..9eca70a2 100644 --- a/e2e-cypress/files/package.json +++ b/e2e-cypress/files/package.json @@ -14,19 +14,25 @@ "junit-acceptance-report": "jrm build/test-results/acceptance-junit.xml 'build/test-results/acceptance-*.xml'", "delete-junit-results": "rimraf build/test-results", "e2e": "npm-run-all delete-junit-results cypress:run-installation cypress:run-integration cypress:run-acceptance", + "e2e:prod": "npm-run-all delete-junit-results cypress:run-installation", "combine:reports": "npm-run-all junit-installation-report junit-integration-report junit-acceptance-report", - "e2e:jenkins:record": "npm run delete-junit-results && npm run cypress:run-installation -- --record && npm run cypress:run-integration -- --record && npm run cypress:run-acceptance -- --record" + "e2e:jenkins:record": "npm run delete-junit-results && npm run cypress:run-installation -- --record && npm run cypress:run-integration -- --record && npm run cypress:run-acceptance -- --record", + "e2e:jenkins:record:prod": "npm run delete-junit-results && npm run cypress:run-installation -- --record", + "generate:pdf": "ts-node ./pdf-generator.ts" }, "private": true, "devDependencies": { "@types/node": "^22.4.1", "cypress": "^13.13.1", + "cypress-multi-reporters": "^1.6.4", "junit-report-merger": "^7.0.0", "mocha-junit-reporter": "^2.2.1", "npm-run-all": "^4.1.5", "otplib": "^12.0.1", + "puppeteer": "^23.5.1", "rimraf": "^6.0.1", "sharp": "^0.33.5", + "ts-node": "^10.9.2", "typescript": "^5.5.4" } } diff --git a/e2e-cypress/files/pdf-generator.ts b/e2e-cypress/files/pdf-generator.ts new file mode 100644 index 00000000..4922f58b --- /dev/null +++ b/e2e-cypress/files/pdf-generator.ts @@ -0,0 +1,72 @@ +import * as puppeteer from 'puppeteer'; +import * as path from 'path'; +import * as fs from 'fs'; +import baseConfig from './cypress.config'; + +async function expandReportTestCases(htmlPage: puppeteer.Page) { + await htmlPage.evaluate(() => { + const expandTestHeaders = document.querySelectorAll('[class^="test--header"], [class*="test--header"]'); + for (const testHeader of Array.from(expandTestHeaders)) { + (testHeader as HTMLElement).click(); + } + }); +} + +const mochawesomeDir = path.resolve(__dirname, baseConfig.reporterOptions.mochawesomeReporterOptions.reportDir); + +const isLocal = process.env.NODE_ENV === 'local'; + +(async () => { + try { + const files = await fs.promises.readdir(mochawesomeDir); + + for (const file of files) { + + if (!file.endsWith('.html')) { + continue; + } + + const executablePath = isLocal ? undefined : '/usr/bin/google-chrome'; + + const browser = await puppeteer.launch({ args: ['--no-sandbox'], + executablePath + }); + const page = await browser.newPage(); + const htmlFullFilePath = path.resolve(__dirname, mochawesomeDir, file); + + await page.goto(`file://${htmlFullFilePath}`, { waitUntil: 'networkidle2' }); + + await expandReportTestCases(page); + + const images = await page.$$('img'); + for (const image of images) { + await page.waitForFunction((img) => img.complete && img.naturalHeight !== 0, {}, image); + } + + await page.addStyleTag({ + content: ` + @media print { + [class*="navbar"] { + position: static !important; + } + } + ` + }); + + if (!fs.existsSync(path.resolve(__dirname, mochawesomeDir, 'pdf'))) + fs.mkdirSync(path.resolve(__dirname, mochawesomeDir, 'pdf')); + + await page.pdf({ + path: path.resolve(__dirname, mochawesomeDir, 'pdf/', file.replace('.html', '.pdf')), + format: 'A4', + printBackground: true, + }); + await browser.close(); + } + + console.log('PDF files generated successfully'); + + } catch (error) { + console.error('Error generating PDF:', error); + } +})(); diff --git a/e2e-cypress/files/plugins/index.ts b/e2e-cypress/files/plugins/index.ts index 95b3d9fd..a1a9c164 100644 --- a/e2e-cypress/files/plugins/index.ts +++ b/e2e-cypress/files/plugins/index.ts @@ -26,6 +26,12 @@ const setupNodeEvents: NonNullable = ( return await addEvidenceMetaToScreenshot(data); } }); + on('before:browser:launch', (browser: Cypress.Browser, launchOptions) => { + if (browser.isHeadless && (browser.name === 'chrome' || browser.name === 'edge')) { + launchOptions.args.push('--disable-gpu'); + } + return launchOptions; + }); }; export default setupNodeEvents; diff --git a/e2e-cypress/files/support/commands.ts b/e2e-cypress/files/support/commands.ts index 159e6ed8..b85f54cd 100644 --- a/e2e-cypress/files/support/commands.ts +++ b/e2e-cypress/files/support/commands.ts @@ -15,3 +15,14 @@ addGetTOTP(); addSessionLoginWithMFA(); addLoginToAAD(); addLoginToAADWithMFA(); + +declare global { + namespace Cypress { + interface Chainable<> { + loginToAAD(username: string, password: string); + loginToAADWithMFA(username: string, password: string); + sessionLoginWithMFA(username: string, password: string); + getTOTP(); + addContextPath(title: string, screenshot: string); + } + } diff --git a/e2e-cypress/files/support/e2e.ts b/e2e-cypress/files/support/e2e.ts index f19edb09..09aaab36 100644 --- a/e2e-cypress/files/support/e2e.ts +++ b/e2e-cypress/files/support/e2e.ts @@ -1,4 +1,5 @@ import './commands'; +const addContext = require('mochawesome/addContext'); export const consoleLogs: string[] = []; @@ -15,3 +16,13 @@ afterEach(function() { consoleLogs.splice(0); }) + +Cypress.Commands.add('addContextPath', (title: string, contextPath: string) => { + cy.on('test:after:run', (attributes) => { + // The context needs the path relative to the build/test-results/mochawesome folder + addContext({ test: attributes }, { + title: title, + value: contextPath + }); + }); +}) diff --git a/e2e-cypress/files/support/login-functions.ts b/e2e-cypress/files/support/login-functions.ts index caa67ce4..dd1e832f 100644 --- a/e2e-cypress/files/support/login-functions.ts +++ b/e2e-cypress/files/support/login-functions.ts @@ -107,14 +107,3 @@ const validateLocalStorage = (localStorage: Record) => Cypress._.some(localStorage, (value: unknown, key: string) => key.includes('CognitoIdentityServiceProvider'), ) - -declare global { - namespace Cypress { - interface Chainable<> { - loginToAAD(username: string, password: string); - loginToAADWithMFA(username: string, password: string); - sessionLoginWithMFA(username: string, password: string); - getTOTP(); - } - } -} diff --git a/e2e-cypress/files/support/test-evidence.ts b/e2e-cypress/files/support/test-evidence.ts index 6db14be4..ceb38559 100644 --- a/e2e-cypress/files/support/test-evidence.ts +++ b/e2e-cypress/files/support/test-evidence.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { isScreenshotEvidenceResult, ScreenshotEvidenceData } from "../plugins/screenshot.types"; import { consoleLogs } from "./e2e"; +import baseConfig from './cypress.config'; const logEvidence = (name: string, step: number, description: string, evidenceLogs: string[]) => { cy.url().then(url => { @@ -65,6 +66,10 @@ export const takeScreenshotEvidence = (testName: string, testStep: number, testS logEvidence(testName, testStep, description, [ `Stored screenshot "${path.basename(result.path)}" with hash (sha256) ${result.hash} taken at ${String(data.takenAt)} as evidence.` ]); + + // Create a relative path from the screenshots folder to the mochawesome test-results folder + const relativePath = path.relative(baseConfig.reporterOptions.mochawesomeReporterOptions.reportDir, result.path.replace(/^.*(build.*)$/, '$1')); + cy.addContextPath(`${testName} ${testStep} ${testSubStep}`, relativePath); }); }); };