Skip to content

Commit

Permalink
Merge branch 'master' into deprecation-node16-terraform
Browse files Browse the repository at this point in the history
  • Loading branch information
BraisVQ authored Dec 9, 2024
2 parents a55eaed + a7464d1 commit c6018eb
Show file tree
Hide file tree
Showing 15 changed files with 278 additions and 140 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 16 additions & 5 deletions docs/modules/quickstarters/pages/e2e-cypress.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

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

stageTest(context)
Expand All @@ -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 <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 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
}
}
}
25 changes: 5 additions & 20 deletions e2e-cypress/files/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,23 @@ 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.

In order to generate one xml report per test type (installation, integration and acceptance) we use the junit-report-merger tool. See also the `junit-...-report` tasks in `package.json`.

## 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

Expand Down
37 changes: 16 additions & 21 deletions e2e-cypress/files/cypress-acceptance.config.ts
Original file line number Diff line number Diff line change
@@ -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
// },
})
});
37 changes: 16 additions & 21 deletions e2e-cypress/files/cypress-installation.config.ts
Original file line number Diff line number Diff line change
@@ -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
// },
})
});
37 changes: 16 additions & 21 deletions e2e-cypress/files/cypress-integration.config.ts
Original file line number Diff line number Diff line change
@@ -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
// },
})
});
26 changes: 19 additions & 7 deletions e2e-cypress/files/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -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);
},
Expand Down
Loading

0 comments on commit c6018eb

Please sign in to comment.