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

Improve cypress SUT environment management #1072

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -26,6 +26,7 @@
- inf-terraform-[aws|azure]: bump terraform versions, pre-commit-hooks, library versions ([#1036](https://github.com/opendevstack/ods-quickstarters/pull/1036))
- jenkins-agent-terraform-2408: add jenkins agent terraform-2408, add go-task, go for experimental terratest, add python-3.12 (drop python-3.8), use tenv for terraform (tofu) version management, ruby version 3.3.4, terraform 1.9.4 ([#1036](https://github.com/opendevstack/ods-quickstarters/pull/1036))
- Replaced centos8 repository for RockyLinux 8 due to deprecation in terraform agents ([#1067](https://github.com/opendevstack/ods-quickstarters/pull/1036))
- Added new function to cypress to log into applications using MFA ([#1070](https://github.com/opendevstack/ods-quickstarters/pull/1070))

### Added

Expand Down
14 changes: 13 additions & 1 deletion docs/modules/quickstarters/pages/e2e-cypress.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,21 @@ RUN chgrp -R 0 $HOME && \
USER 1001
----

== Configuring Microsoft Account with Google Authenticator

To use the login with MFA function, the following requirements have to be met:

1. A testing account must be created with access to the application under test.

2. The account must have MFA enabled.

3. The MFA method used must be OTP (One Time Password).

4. The secret key generated during the setup process must be used for the OTP generation. In the case of microsoft, it can be retrieved by clicking the "Can't scan image" button during the setup process.

== Cypress Cloud

To use Cypress Cloud within the Cypress QuickSarter, follow these steps:
To use Cypress Cloud within the Cypress QuickStarter, follow these steps:

1. **Create a project in Cypress Cloud.** Access Cypress Cloud by following this link (https://cloud.cypress.io/), and create a project. This project will be used to store your Cypress tests and results.

Expand Down
33 changes: 25 additions & 8 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') {
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,25 +60,31 @@ 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
// OPTIONAL: load environment variables for Azure SSO; 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

// 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>"
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

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}"
// "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 Down
5 changes: 4 additions & 1 deletion e2e-cypress/files/cypress-acceptance.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default defineConfig({
toConsole: true,
},
e2e: {
baseUrl: 'https://www.w3schools.com',
baseUrl: process.env.CYPRESS_BASE_URL,
fixturesFolder: "fixtures",
specPattern: 'tests/acceptance/*.cy.ts',
supportFile: "support/e2e.ts",
Expand All @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
5 changes: 4 additions & 1 deletion e2e-cypress/files/cypress-installation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default defineConfig({
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",
Expand All @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
5 changes: 4 additions & 1 deletion e2e-cypress/files/cypress-integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default defineConfig({
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",
Expand All @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
5 changes: 4 additions & 1 deletion e2e-cypress/files/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default defineConfig({
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",
Expand All @@ -19,4 +19,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
1 change: 1 addition & 0 deletions e2e-cypress/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"junit-report-merger": "^7.0.0",
"mocha-junit-reporter": "^2.2.1",
"npm-run-all": "^4.1.5",
"otplib": "^12.0.1",
"rimraf": "^6.0.1",
"sharp": "^0.33.5",
"typescript": "^5.5.4"
Expand Down
7 changes: 5 additions & 2 deletions e2e-cypress/files/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
// ***********************************************

import { addGenericLoginCommands } from './generic-login';
import { addMsalv2LoginCommand } from './msalv2-login';
import { addLoginToAADWithMFA, addLoginToAAD, addGetTOTP, addSessionLoginWithMFA } from './login-functions';

addGenericLoginCommands();
addMsalv2LoginCommand();
addGetTOTP();
addSessionLoginWithMFA();
addLoginToAAD();
addLoginToAADWithMFA();
70 changes: 1 addition & 69 deletions e2e-cypress/files/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,4 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup
function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})
log.snapshot('before')

loginViaAAD(username, password)

log.snapshot('after')
log.end()
})
import './commands';

export const consoleLogs: string[] = [];

Expand Down
120 changes: 120 additions & 0 deletions e2e-cypress/files/support/login-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup

import { authenticator } from 'otplib';

function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
export function addLoginToAAD() {
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
cy.session([username], () => {
const log = Cypress.log({
autoEnd: false,
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
});
log.snapshot('before');

loginViaAAD(username, password);

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)

log.snapshot('after');
log.end();
});
});
}

export function addSessionLoginWithMFA() {
Cypress.Commands.add('sessionLoginWithMFA', (username: string, password: string) => {
cy.session('login', () => cy.loginToAADWithMFA(username, password), {
validate: () => cy.getAllLocalStorage().should(validateLocalStorage),
cacheAcrossSpecs: true
})
})
}

export function addLoginToAADWithMFA() {
Cypress.Commands.add('loginToAADWithMFA', (username: string, password: string) => {
loginViaAAD(username, password);
cy.getTOTP().then((otp1) => {
const objOTP = { otp: otp1 }
cy.origin('https://login.microsoftonline.com/', { args: objOTP }, ({ otp }) => {
cy.get("[name='otc']").type(otp)
cy.get('input[type="submit"]').click()
})
})
})
}

export function addGetTOTP() {
Cypress.Commands.add('getTOTP', () => {
return authenticator.generate(Cypress.env('otp_secret'))
})
}

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