Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate PDF report for cypress + environment management improvements #1079

Merged
merged 23 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
90ed448
removed obsolete msalv2-login_obsolete.ts
roicarrera Oct 18, 2024
ab435c4
updated documentation
roicarrera Oct 21, 2024
a045724
Merge pull request #4 from roicarrera/feature/otp_login_for_cypress
roicarrera Oct 21, 2024
0c2709f
updated CHANGELOG.md
roicarrera Oct 21, 2024
f41f1b2
added baseurl management based on environment
roicarrera Oct 23, 2024
4373a46
add example of environment variables based on environment
roicarrera Oct 23, 2024
de05223
Merge branch 'master' into master
roicarrera Oct 28, 2024
cd122b6
Merge pull request #5 from roicarrera/feature/cypress-env-improvements
roicarrera Nov 4, 2024
ab65f5a
added mochawesome reporter + multi reporters
roicarrera Nov 4, 2024
c5ca704
applied dependabot changes from ods
roicarrera Nov 4, 2024
53d2bc8
Merge pull request #6 from roicarrera/feature/generate-pdf-report
roicarrera Nov 4, 2024
ae8e16f
Merge branch 'master' into backup-branch
roicarrera Nov 5, 2024
3dca248
Merge pull request #7 from roicarrera/backup-branch
roicarrera Nov 5, 2024
0fc0fb7
update CHANGELOG.md
roicarrera Nov 6, 2024
920717b
Merge branch 'master' of https://github.com/roicarrera/ods-quickstarters
roicarrera Nov 6, 2024
cf20766
adressed comments in PR
roicarrera Nov 11, 2024
9f1bd9e
small fixes
roicarrera Nov 11, 2024
3e35b85
only run installation tests in prod
roicarrera Nov 14, 2024
3f51c8a
updated documentation
roicarrera Nov 15, 2024
1948dd2
fixed withCredentials block
roicarrera Nov 20, 2024
c3d8976
default video to false
roicarrera Dec 5, 2024
a12c8b0
Update e2e-cypress/files/README.md
roicarrera Dec 5, 2024
29ec4bb
Update e2e-cypress/files/README.md
roicarrera Dec 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- Replaced centos8 repository for AlmaLinux 8 due to deprecation ([#1063](https://github.com/opendevstack/ods-quickstarters/pull/1063))
- 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))
- Generate PDF report for cypress and improved environment management ([#1079](https://github.com/opendevstack/ods-quickstarters/pull/1079))

### Added

Expand Down
48 changes: 34 additions & 14 deletions e2e-cypress/Jenkinsfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ odsComponentPipeline(
// 'release/': 'test'
]
) { context ->

// Note: Testing in the production environment is not recommended as it can lead to unintended consequences,
// including potential downtime, data corruption, or exposure of sensitive information.
// This block is designed to skip tests in the production environment to avoid these risks.
// If you choose to enable testing in production, do so at your own risk and take all necessary precautions.
if (context.environment == 'prod') {
metmajer marked this conversation as resolved.
Show resolved Hide resolved
currentBuild.result = 'SUCCESS'
echo 'Skipping the entire test build for production environment'
return
}

def targetDirectory = "${context.projectId}/${context.componentId}/${context.gitBranch.replaceAll('/', '-')}/${context.buildNumber}"

stageTest(context)
Expand All @@ -49,27 +60,32 @@ odsComponentPipeline(

def stageTest(def context) {
stage('Integration Test') {
// OPTIONAL: load environment variables for Azure SSO with MSALv2; please adapt variable names to your OpenShift config
// Define your DEV and QA base URLs in a config map in OpenShift; please adapt variable names to your OpenShift config
metmajer marked this conversation as resolved.
Show resolved Hide resolved
// sh "oc project <project-with-configured-secrets>"
// 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 two 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()
]

def baseUrl = baseUrls.get(context.environment ?: 'dev', 'https://www.w3schools.com') // default to W3Schools for demo purposes, replace with your own default

// Example for loading environment variables for Azure SSO; please adapt variable names to your OpenShift config,
// making sure to precede the variable names with the environment name in lowercase (e.g., dev_username, dev_password,
// test_username, test_password, etc.)
// cypressUser = sh(returnStdout: true, script:"oc get secret e2euser -o jsonpath='{.data.${context.environment}_username}' | base64 -d")
// cypressPassword = sh(returnStdout: true, script:"oc get secret e2euser -o jsonpath='{.data.${context.environment}_password}' | base64 -d")
metmajer marked this conversation as resolved.
Show resolved Hide resolved

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}",
"CYPRESS_BASE_URL=${baseUrl}",
]) {
sh 'npm install'
def status = sh(script: 'npm run e2e', returnStatus: true)
Expand All @@ -79,16 +95,20 @@ def stageTest(def context) {
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 'npx ts-node ./pdf-generator.ts'
zip zipFile: 'cypress/pdf.zip', archive: false, dir: 'build/test-results/mochawesome/pdf'
archiveArtifacts artifacts: 'cypress/pdf.zip', fingerprint: true, daysToKeep: 2, numToKeep: 3

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
}

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 (fileExists('build/test-results/screenshots')) {
zip zipFile: 'screenshots.zip', archive: false, dir: 'build/test-results/screenshots'
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
stash(name: "acceptance-test-screenshots-${context.componentId}-${context.buildNumber}", includes: 'screenshots.zip', allowEmpty: true)
archiveArtifacts artifacts: 'screenshots.zip', fingerprint: true, daysToKeep: 2, numToKeep: 3
}

return status
Expand Down
24 changes: 18 additions & 6 deletions e2e-cypress/files/cypress-acceptance.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@ import { defineConfig } from 'cypress'
import setupNodeEvents from './plugins/index.js'
export default defineConfig({
//projectId: '[Your project ID from Cypress cloud]',
reporter: 'reporters/custom-reporter.js',
reporter: 'cypress-multi-reporters',
reporterOptions: {
mochaFile: 'build/test-results/acceptance-junit-[hash].xml',
toConsole: true,
reporterEnabled: 'mochawesome,./reporters/custom-reporter.js',
mochawesomeReporterOptions: {
reportDir: 'build/test-results/mochawesome',
reportFilename: 'acceptance-mochawesome',
charts: true,
html: true,
timestamp: true,
json: true
},
reportersCustomReporterJsReporterOptions: {
mochaFile: 'build/test-results/acceptance-junit-[hash].xml',
toConsole: true,
},
},
e2e: {
baseUrl: 'https://www.w3schools.com',
baseUrl: process.env.CYPRESS_BASE_URL,
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
fixturesFolder: "fixtures",
specPattern: 'tests/acceptance/*.cy.ts',
supportFile: "support/e2e.ts",
viewportWidth: 1376,
viewportHeight: 660,
screenshotsFolder: 'build/test-results/screenshots',
viewportWidth: 1280,
viewportHeight: 720,
experimentalModifyObstructiveThirdPartyCode:true,
video: true,
async setupNodeEvents(on, config) {
Expand Down
24 changes: 18 additions & 6 deletions e2e-cypress/files/cypress-installation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@ import { defineConfig } from 'cypress'
import setupNodeEvents from './plugins/index.js'
export default defineConfig({
//projectId: '[Your project ID from Cypress cloud]',
reporter: 'reporters/custom-reporter.js',
reporter: 'cypress-multi-reporters',
reporterOptions: {
mochaFile: 'build/test-results/installation-junit-[hash].xml',
toConsole: true,
reporterEnabled: 'mochawesome,./reporters/custom-reporter.js',
mochawesomeReporterOptions: {
reportDir: 'build/test-results/mochawesome',
reportFilename: 'installation-mochawesome',
charts: true,
html: true,
timestamp: true,
json: true
},
reportersCustomReporterJsReporterOptions: {
mochaFile: 'build/test-results/installation-junit-[hash].xml',
toConsole: true,
},
},
e2e: {
baseUrl: 'https://www.w3schools.com',
baseUrl: process.env.CYPRESS_BASE_URL,
fixturesFolder: "fixtures",
specPattern: 'tests/installation/*.cy.ts',
supportFile: "support/e2e.ts",
viewportWidth: 1376,
viewportHeight: 660,
screenshotsFolder: 'build/test-results/screenshots',
viewportWidth: 1280,
viewportHeight: 720,
experimentalModifyObstructiveThirdPartyCode:true,
video: true,
async setupNodeEvents(on, config) {
Expand Down
24 changes: 18 additions & 6 deletions e2e-cypress/files/cypress-integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,30 @@ import { defineConfig } from 'cypress'
import setupNodeEvents from './plugins/index.js'
export default defineConfig({
//projectId: '[Your project ID from Cypress cloud]',
reporter: 'reporters/custom-reporter.js',
reporter: 'cypress-multi-reporters',
reporterOptions: {
mochaFile: 'build/test-results/integration-junit-[hash].xml',
toConsole: true,
reporterEnabled: 'mochawesome,./reporters/custom-reporter.js',
cypressMochawesomeReporterReporterOptions: {
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
reportDir: 'build/test-results/mochawesome',
reportFilename: 'integration-mochawesome',
charts: true,
html: true,
timestamp: true,
json: true
},
reportersCustomReporterJsReporterOptions: {
mochaFile: 'build/test-results/integration-junit-[hash].xml',
toConsole: true,
},
},
e2e: {
baseUrl: 'https://www.w3schools.com',
baseUrl: process.env.CYPRESS_BASE_URL,
fixturesFolder: "fixtures",
specPattern: 'tests/integration/*.cy.ts',
supportFile: "support/e2e.ts",
viewportWidth: 1376,
viewportHeight: 660,
screenshotsFolder: 'build/test-results/screenshots',
viewportWidth: 1280,
viewportHeight: 720,
experimentalModifyObstructiveThirdPartyCode:true,
video: true,
async setupNodeEvents(on, config) {
Expand Down
24 changes: 18 additions & 6 deletions e2e-cypress/files/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
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.CYPRESS_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,
async setupNodeEvents(on, config) {
Expand Down
3 changes: 3 additions & 0 deletions e2e-cypress/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
"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",
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
"typescript": "^5.5.4"
}
}
68 changes: 68 additions & 0 deletions e2e-cypress/files/pdf-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as puppeteer from 'puppeteer';
import * as path from 'path';
import * as fs from 'fs';

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 isLocal = process.env.NODE_ENV === 'local';

(async () => {
try {
const files = await fs.promises.readdir('build/test-results/mochawesome/');
cschweikert marked this conversation as resolved.
Show resolved Hide resolved

for (const file of files) {

if (!fs.existsSync(path.resolve(__dirname, 'build/test-results/mochawesome/', 'pdf')))
fs.mkdirSync(path.resolve(__dirname, 'build/test-results/mochawesome/', 'pdf'));
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
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, 'build/test-results/mochawesome/', 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;
}
}
`
});

await page.pdf({
path: path.resolve(__dirname, 'build/test-results/mochawesome/', '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);
}
})();
6 changes: 6 additions & 0 deletions e2e-cypress/files/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const setupNodeEvents: NonNullable<Cypress.ConfigOptions['setupNodeEvents']> = (
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;
11 changes: 11 additions & 0 deletions e2e-cypress/files/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
addScreenshot(title: string, screenshot: string);
}
}
11 changes: 11 additions & 0 deletions e2e-cypress/files/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './commands';
const addContext = require('mochawesome/addContext');

export const consoleLogs: string[] = [];

Expand All @@ -15,3 +16,13 @@ afterEach(function() {

consoleLogs.splice(0);
})

Cypress.Commands.add('addScreenshot', (title: string, screenshot: string) => {
cschweikert marked this conversation as resolved.
Show resolved Hide resolved
cy.on('test:after:run', (attributes) => {
// The context needs the screenshot path relative to the build/test-results/mochawesome folder
addContext({ test: attributes }, {
title: title,
value: screenshot
});
});
})
11 changes: 0 additions & 11 deletions e2e-cypress/files/support/login-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,3 @@ const validateLocalStorage = (localStorage: Record<string, unknown>) =>
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();
}
}
}
Loading