Skip to content
Snippets Groups Projects
Commit 0ec9410a authored by OZGCloud's avatar OZGCloud
Browse files

OZG-6368 OZG-6589 create basic structure for Admin Client E2E testing

parent 42a551b3
Branches
Tags
No related merge requests found
Showing
with 539 additions and 53 deletions
......@@ -5,10 +5,10 @@
/tmp
/out-tsc
junit.xml
/apps/alfa-e2e/reports
/apps/alfa-e2e/reports_einheitlicher-ansprechpartner
/apps/alfa-e2e/recordings
/apps/alfa-e2e/cypress
/apps/*/reports
/apps/*/reports_einheitlicher-ansprechpartner
/apps/*/recordings
/apps/*/cypress
.scannerwork
test-report.xml
/.angular/cache/*
......
{
"baseUrl": "http://localhost:4301",
"env": {
"dbUrl": "mongodb://localhost:27018",
"database": "local",
"keycloakRealm": "by-e2e-tests-local-dev",
"keycloakUrl": "https://sso.dev.by.ozg-cloud.de/",
"keycloakClient": "admin"
},
"fileServerFolder": ".",
"fixturesFolder": "./src/fixtures",
"video": false,
"videosFolder": "./reports/videos",
"screenshotsFolder": "./reports/screenshots",
"chromeWebSecurity": false,
"reporter": "../../node_modules/cypress-mochawesome-reporter",
"defaultCommandTimeout": 10000,
"specPattern": "src/e2e/**/*.cy.{js,jsx,ts,tsx}",
"supportFile": "src/support/e2e.ts",
"testIsolation": false,
"reporterOptions": {
"html": false,
"json": true,
"reportDir": "./reports/mochawesome-report",
"reportFilename": "report",
"overwrite": true
}
}
\ No newline at end of file
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
const cypressConfig = require('./cypress.config.json');
const cypressEvents = require('./src/support/cypress-tasks.ts');
export default defineConfig({
e2e: nxE2EPreset(__filename, { cypressDir: 'src' }),
e2e: {
...nxE2EPreset(__dirname),
...cypressConfig,
setupNodeEvents(on, config) {
return cypressEvents(on, config);
},
},
retries: {
experimentalStrategy: 'detect-flake-and-pass-on-threshold',
experimentalOptions: {
maxRetries: 2,
passesRequired: 1,
},
openMode: true,
runMode: true,
},
});
version: '3'
volumes:
mongodb:
services:
mongodb:
image: mongo:7
ports:
- 27017:27017
volumes:
- mongodb:/data/db
healthcheck:
test: ['CMD', 'mongosh', '--eval', 'db.settings.find()']
interval: 10s
timeout: 5s
retries: 5
administration:
image: docker.ozg-sh.de/administration:${ADMINISTRATION_DOCKER_IMAGE:-snapshot-latest}
platform: linux/amd64
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILE:-local,remotekc}
- SPRING_DATA_MONGODB_URI=mongodb://mongodb:27017/config-db
ports:
- 8080:8080
depends_on:
mongodb:
condition: service_healthy
export class BuildInfoE2EComponent {
private readonly locatorVersion: string = 'build-version';
private readonly locatorBuildTime: string = 'build-time';
public getVersion() {
return cy.getTestElement(this.locatorVersion);
}
public getBuildTime() {
return cy.getTestElement(this.locatorBuildTime);
}
}
import { UserProfileE2EComponent } from './user-profile.component.e2e';
export class CurrentUserProfileE2EComponent {
private readonly locatorUserIconButton: string = 'user-icon-button';
private readonly locatorLogoutButton: string = 'logout-button';
private readonly locatorRoot: string = 'current-user';
public getRoot() {
return cy.getTestElement(this.locatorRoot);
}
public getUserProfile(): UserProfileE2EComponent {
return new UserProfileE2EComponent(this.locatorRoot);
}
public logout(): void {
this.getUserIconButton().click();
this.getLogoutButton().click();
}
public getUserIconButton() {
return cy.getTestElement(this.locatorUserIconButton);
}
private getLogoutButton() {
return cy.getTestElement(this.locatorLogoutButton);
}
}
export class UserProfileIconE2EComponent {
private readonly locatorAssignedIcon: string = 'user-profile-assigned';
private readonly locatorUnassignedIcon: string = 'user-profile-unassigned';
private readonly locatorErrorNotFoundIcon: string = 'user-profile-user-not-found';
private readonly locatorErrorServiceUnavailableIcon: string = 'user-profile-service-unavailable';
private readonly locatorUserProfileButton: string = 'user-profile-button-container';
constructor(private root: string) {}
public getRoot() {
return cy.getTestElement(this.root);
}
public getUnassignedIcon() {
return this.getRoot().findTestElementWithClass(this.locatorUnassignedIcon);
}
public getAssignedIcon() {
return this.getRoot().findTestElementWithClass(this.locatorAssignedIcon);
}
public getErrorResourceNotFoundIcon() {
return this.getRoot().findTestElementWithClass(this.locatorErrorNotFoundIcon);
}
public getErrorServiceUnavailableIcon() {
return this.getRoot().findTestElementWithClass(this.locatorErrorServiceUnavailableIcon);
}
public getButton() {
return this.getRoot().getTestElement(this.locatorUserProfileButton);
}
}
export class UserProfileSearchE2EComponent {
private readonly locatorInput: string = 'Bearbeiter-autocomplete-input';
private readonly locatorError: string = 'Bearbeiter-autocomplete-error';
private readonly locatorOptions: string = 'autocomplete-option';
constructor(private root: string) {}
public getRoot() {
return cy.getTestElement(this.root);
}
public getInput() {
return cy.getTestElement(this.locatorInput);
}
public getSearchOption(prefix: string) {
return cy.getTestElement(prefix + '-' + this.locatorOptions);
}
public getError() {
return cy.getTestElement(this.locatorError);
}
}
import { UserProfileIconE2EComponent } from './user-profile-icon.component.e2e';
import { UserProfileSearchE2EComponent } from './user-profile-search.component.e2e';
export class UserProfileE2EComponent {
private readonly locatorUserProfileName: string = 'user-profile-name';
constructor(private root: string) {}
public getRoot() {
return cy.getTestElement(this.root);
}
public getIconContainer(): UserProfileIconE2EComponent {
return new UserProfileIconE2EComponent(this.root);
}
public getSearchContainer(): UserProfileSearchE2EComponent {
return new UserProfileSearchE2EComponent(this.root);
}
public getName() {
return this.getRoot().findTestElementWithClass(this.locatorUserProfileName);
}
}
import { TOGGLE_ELEMENT } from '../../support/angular.util';
export class UserSettingsE2EComponent {
private readonly rootLocator: string = 'user-settings';
private readonly emailBenachrichtigungLocator: string = 'email-benachrichtigung';
private readonly darkModeLocator: string = 'dark-mode';
private readonly buttonLocator: string = 'icon-button';
public getRoot() {
return cy.getTestElementWithOid(this.rootLocator);
}
public getEmailBenachrichtigung(): ToggleE2EComponent {
return new ToggleE2EComponent(this.emailBenachrichtigungLocator);
}
public getDarkMode(): ToggleE2EComponent {
return new ToggleE2EComponent(this.darkModeLocator);
}
public getButton() {
return this.getRoot().findTestElementWithClass(this.buttonLocator);
}
}
export class ToggleE2EComponent {
private readonly rootLocator: string;
constructor(root: string) {
this.rootLocator = root;
}
public getRoot() {
return cy.getTestElementWithOid(this.rootLocator);
}
public getToggle() {
return this.getRoot().findElement(TOGGLE_ELEMENT);
}
}
import { getGreeting } from '../support/app.po';
describe('admin-e2e', () => {
beforeEach(() => cy.visit('/'));
it('should display welcome message', () => {
// Custom command example, see `../support/commands.ts` file
cy.login('my-email@something.com', 'myPassword');
// Function helper example, see `../support/app.po.ts` file
getGreeting().contains(/Welcome/);
});
});
import { BuildInfoE2EComponent } from 'apps/admin-e2e/src/components/buildinfo/buildinfo.e2e.component';
import { MainPage } from 'apps/admin-e2e/src/page-objects/main.po';
import { exist } from 'apps/admin-e2e/src/support/cypress.util';
import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util';
describe('Buildinfo', () => {
const mainPage: MainPage = new MainPage();
const buildInfo: BuildInfoE2EComponent = mainPage.getBuildInfo();
before(() => {
loginAsAriane();
});
after(() => {
// dropCollections();
});
describe('after login', () => {
it('should show ...', { defaultCommandTimeout: 30000 }, () => {
// waitForSpinnerToDisappear();
// exist(vorgangList.getRoot());
});
});
describe('buildinfo', () => {
it('should show version', () => {
exist(buildInfo.getVersion());
});
});
});
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
{
"name": "ariane",
"password": "Y9nk43yrQ_zzIPpfFU-I",
"firstName": "Ariane",
"lastName": "Administrator",
"fullName": "Ariane Administrator",
"email": "ariane.adminsitrator@ozg-sh.de",
"initials": "AA",
"dataTestId": "Ariane_Administrator",
"clientRoles": [],
"groups": [
"E2E Tests"
]
}
\ No newline at end of file
export interface UserE2E {
uuid: string;
id: string;
name: string;
password: string;
firstName: string;
lastName: string;
fullName: string;
initials: string;
dataTestId: string;
}
import { CurrentUserProfileE2EComponent } from '../components/user-profile/current-user-profile.component.e2e';
import { UserSettingsE2EComponent } from '../components/user-settings/user-settings.component.e2e';
export class HeaderE2EComponent {
private readonly locatorLogo: string = 'admin-logo';
private readonly locatorRoot: string = 'header';
private readonly userSettings: UserSettingsE2EComponent = new UserSettingsE2EComponent();
private readonly currentUserProfile: CurrentUserProfileE2EComponent =
new CurrentUserProfileE2EComponent();
public getRoot() {
return cy.getTestElement(this.locatorRoot);
}
public getLogo() {
return cy.getTestElement(this.locatorLogo);
}
public getUserSettings(): UserSettingsE2EComponent {
return this.userSettings;
}
public getCurrentUserProfile(): CurrentUserProfileE2EComponent {
return this.currentUserProfile;
}
}
import { BuildInfoE2EComponent } from '../components/buildinfo/buildinfo.e2e.component';
import { HeaderE2EComponent } from './header.po';
export class MainPage {
private readonly buildInfo: BuildInfoE2EComponent = new BuildInfoE2EComponent();
private readonly header: HeaderE2EComponent = new HeaderE2EComponent();
public getBuildInfo(): BuildInfoE2EComponent {
return this.buildInfo;
}
public getHeader(): HeaderE2EComponent {
return this.header;
}
}
export function waitForSpinnerToDisappear(): boolean {
return cy.getTestElementWithClass('spinner').should('not.exist');
}
export function waitforSpinnerToAppear(): void {
// exist(cy.getTestElementWithClass('spinner'));
}
import { containClass, mouseEnter, notContainClass } from './cypress.util';
export const TOGGLE_ELEMENT: string = 'mat-slide-toggle';
enum AngularClassesE2E {
MAT_CHECKED = 'mat-mdc-slide-toggle-checked',
MAT_BUTTONG_TOGGLE_CHECKED = 'mat-button-toggle-checked',
MAT_FOCUSED = 'mat-focused',
CDK_KEYBOARD_FOCUSED = 'cdk-keyboard-focused',
MAT_BADGE_HIDDEN = 'mat-badge-hidden',
}
enum AngularElementE2E {
MAT_FORM_FIELD = 'mat-form-field',
}
export function hasTooltip(element: any, value: string) {
mouseEnter(element);
element.get('mat-tooltip-component').contains(value);
// element.get(`div[title="${value}"]`);
}
export function isChecked(element: any) {
containClass(element, AngularClassesE2E.MAT_CHECKED);
}
export function isNotChecked(element: any) {
notContainClass(element, AngularClassesE2E.MAT_CHECKED);
}
export function isButtonToggleChecked(element: any) {
containClass(element, AngularClassesE2E.MAT_BUTTONG_TOGGLE_CHECKED);
}
export function isButtonToggleNotChecked(element: any) {
notContainClass(element, AngularClassesE2E.MAT_BUTTONG_TOGGLE_CHECKED);
}
export function getFormField(element: any) {
return element.find(AngularElementE2E.MAT_FORM_FIELD);
}
export function isMatFocused(element: any) {
containClass(element, AngularClassesE2E.MAT_FOCUSED);
}
export function isKeyboardFocused(element: any) {
containClass(element, AngularClassesE2E.CDK_KEYBOARD_FOCUSED);
}
export function expectIconWithoutBadge(element: any): void {
containClass(element, AngularClassesE2E.MAT_BADGE_HIDDEN);
}
export function expectIconWithBadge(element: any): void {
notContainClass(element, AngularClassesE2E.MAT_BADGE_HIDDEN);
}
export const getGreeting = () => cy.get('h1');
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// eslint-disable-next-line @typescript-eslint/no-namespace
enum HttpMethod {
POST = 'POST',
GET = 'GET',
}
const DATA_TEST_ID: string = 'data-test-id';
const DATA_TEST_CLASS: string = 'data-test-class';
const ACCES_TOKEN: string = 'access_token';
const ID_TOKEN: string = 'id_token';
enum Header {
CONTENT_TYPE = 'Content-Type',
AUTHORIZATION = 'Authorization',
}
const CYPRESS_CONFIG_BASE_URL: unknown = 'baseUrl';
enum CypressEnv {
KEYCLOAK_CLIENT = 'keycloakClient',
KEYCLOAK_REALM = 'keycloakRealm',
KEYCLOAK_URL = 'keycloakUrl',
SEARCH = 'search',
SMOCKER = 'smocker',
}
const CONTENT_TYPE_HEADER_VALUE: string = 'application/x-www-form-urlencoded';
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
getTestElementWithOid(oid: string, ...args);
getTestElement(selector: string, ...args);
getTestElementWithClass(selector: string, ...args);
findTestElementWithClass(selector: string, ...args);
findElement(selector: string);
login(user, password);
logout();
getUserInfo();
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
Cypress.Commands.add('getTestElement', (selector, ...args) => {
return cy.get(`[${DATA_TEST_ID}~="${selector}"]`, ...args);
});
Cypress.Commands.add('getTestElementWithClass', (selector, ...args) => {
console.log(
'Achtung: Potentiell nicht eindeutiges Ergebnis, weil eine data-test-class mit cy.get() von der DOM-Root aus gesucht wird.',
);
return cy.get(`[${DATA_TEST_CLASS}="${selector}"]`, ...args);
});
Cypress.Commands.add('getTestElementWithOid', (oid, ...args) => {
return cy.getTestElement(oid, ...args);
});
Cypress.Commands.add(
'findTestElementWithClass',
{ prevSubject: true },
(subject: any, selector) => {
return subject.find(`[${DATA_TEST_CLASS}="${selector}"]`);
},
);
Cypress.Commands.add('findElement', { prevSubject: true }, (subject: any, selector: string) => {
return subject.find(selector);
});
Cypress.Commands.add('login', (user: string, password: string) => {
cy.session(user, () => {
cy.request(buildLoginRequest(user, password)).then((response) => handleLoginResponse(response));
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
});
function buildLoginRequest(user: string, password: string): any {
return {
method: HttpMethod.POST,
followRedirect: false,
url: `${getKeycloakBaseRealmUrl()}/token`,
headers: {
[Header.CONTENT_TYPE]: CONTENT_TYPE_HEADER_VALUE,
},
body: buildLoginRequestBody(user, password),
};
}
function buildLoginRequestBody(user: string, password: string): any {
return {
client_id: Cypress.env(CypressEnv.KEYCLOAK_CLIENT),
username: user,
password: password,
grant_type: 'password',
redirect_uri: Cypress.config(CYPRESS_CONFIG_BASE_URL),
response_mode: 'fragment',
response_type: 'code',
scope: 'openid',
};
}
function handleLoginResponse(response): void {
const authorization: any = `bearer ${response.body.access_token}`;
cy.visit('', authorization);
window.sessionStorage.setItem(ACCES_TOKEN, response.body.access_token);
window.sessionStorage.setItem(ID_TOKEN, response.body.id_token);
cy.setCookie('XSRF-TOKEN', response.body.session_state);
}
Cypress.Commands.add('getUserInfo', () => {
return cy.request({
method: HttpMethod.GET,
url: `${getKeycloakBaseRealmUrl()}/userinfo`,
headers: {
[Header.AUTHORIZATION]: `bearer ${window.sessionStorage.getItem(ACCES_TOKEN)}`,
},
});
});
Cypress.Commands.add('logout', () => {
cy.request({
method: HttpMethod.GET,
url: `${getKeycloakBaseRealmUrl()}/logout`,
headers: {
[Header.CONTENT_TYPE]: CONTENT_TYPE_HEADER_VALUE,
},
body: {
refresh_token: window.sessionStorage.getItem(ID_TOKEN),
},
failOnStatusCode: false,
}).then(() => {
window.sessionStorage.clear();
cy.visit('');
});
});
function getKeycloakBaseRealmUrl(): string {
return `${Cypress.env(CypressEnv.KEYCLOAK_URL)}realms/${Cypress.env(CypressEnv.KEYCLOAK_REALM)}/protocol/openid-connect`;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment