First commit

This commit is contained in:
mildred 2024-03-04 20:57:54 +01:00
commit 57e500eb8a
56 changed files with 3664 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/vc_schemas/
.vscode
.github

136
README.md Normal file
View File

@ -0,0 +1,136 @@
# IdHub E2E Testing Project
## Table of Contents
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Usage](#usage)
- [Test Structure](#test-structure)
- [License](#license)
## Introduction
The Orchestral project focus on developing the IdHub service, which facilitates organisations (acting as issuers or verifiers) and beneficiaries (acting as
subjects and credential holders) to issue, exchange, and verify data in the form of
verifiable credentials for credible and flexible access to benefits or services.
The Administration module enables administrators to manage users and roles, handle
aspects such as the creation of organisational Decentralized Identifiers (DIDs),
management of credentials issued by the organisation, and upload the information for
the issuance of credentials to users (including credential schemes and data).
Conversely, the User module equips users to manage their personal information, create
an identity (DID), request the issuance of a credential, and present these credentials to entities within our user community. This module operates as a user wallet.
The application's backend is responsible for issuing credentials upon user request
through the user module. Meanwhile, the idHub can function as a credential verifier
and engage in dialogues with other idHub instances that operate as user wallets by
implementing the OIDC4VP protocol that we have developed. Consequently, the
IdHub is multifaceted, capable of functioning as an issuer, wallet or verifier.
For E2E automated testing, we use Playwright, a tool for end-to-end testing, to execute user flow simulations and validate the IdHub behaviour under controlled conditions. Playwright is designed for automating browser interactions, making it ideal for end-to-end testing.
The data used in our tests is pre-configurated or generated dynamically during the execution of the tests and is purely fictitious. This approach allows us to create a controlled testing environment isolated from real-world data, ensuring the integrity and reliability of ourtests.
The testing to be executed is grounded in the acceptance criteria defined within the
user stories during the requirements phase. These criteria are designed to match
specific user stories, providing clear, straightforward requirements that must be met
for the final product.
## Getting Started
### Prerequisites
To get started with the IdHub testing project, you need to have the following prerequisites installed on your system:
- **Node.js**: Ensure you have Node.js version 14 or later installed. You can check your version by running `node -v` in your terminal. If you need to install or update Node.js, visit the [official Node.js website](https://nodejs.org/).
- **TypeScript**: The project is written in TypeScript. You should have TypeScript version 4.0 or later. You can install it globally using npm by running `npm install -g typescript`.
- **Playwright**: Playwright is the tool used for end-to-end testing. You can install it by running `npm install playwright`.
- **Installation**: Step-by-step instructions on how to clone the repository and install dependencies.
### Installation
To clone the repository and install the project dependencies, follow these steps:
1. Open your terminal or command prompt.
2. Navigate to the directory where you want to clone the project.
3. Run `git clone https://github.com/idHub_testing.git` to clone the repository.
4. Navigate into the project directory using `cd idHub_testing`.
5. Install the project dependencies:
- Installing global dependencies: `npm install -g playwright`
- Installing project dependencies: `npm install`
- Setting up environment variables: [TODO]
## Usage
### Running Tests
To run the tests, navigate to the project directory in your terminal and execute the following command:
```bash
npm test
```
This command runs the test suite using Playwright, executing all tests defined in the project.
### Writing Tests: When writing new tests, it's important to follow the established test structure. Here's a brief guide:
- **Test Files**: Place your test files in the `tests` directory, following the naming convention `test-name.spec.ts`.
- **Page Objects**: Use the Page Object Model (POM) pattern for organizing your tests. Each page in your application should have a corresponding Page Object file, e.g., `COMM_loginPage.ts`, `AD_ViewUsersPage.ts`, `US_ViewMyCredentialsPage.ts` (prefix 'AD_' for a page in the admin interface, prefix 'US_' for a page in the user interface, prefix 'COMM_' for common pages). The page objects are stored in the directory `src/page-objects`.
- **Step Definitions**: Define reusable steps within the `steps.ts` This helps in maintaining the tests and promotes code reuse. The `steps.ts` is stored in the `src` directory.
An example of a simple test might look like this:
```typescript
test('Successful login as user', async ({ page }) => {
await loginAsUser(page, USER1_EMAIL, URL_IDHUB);
await expect.soft(page).toHaveTitle('Dashboard IdHub');
})
```
The 'loginAs<User>' function, is defined in `steps.ts` as follow:
```typescript
export async function loginAsUser(page: Page, userEmail: string, url: string) {
try {
const loginPage = new LogInPage(page);
await loginPage.visit(url);
await loginPage.login(userEmail, USER_K);
const currentTitle = await page.title();
if (currentTitle === 'Data Protection IdHub') {
// Code to accept terms and conditions
await page.click('#id_accept_privacy');
await page.click('#id_accept_legal');
await page.click('#id_accept_cookies');
await page.click('a[type="button"]');
}
await expect(page).toHaveTitle('Dashboard IdHub');
} catch (error) {
console.error(`Failed to login as user: `);
throw error;
}
}
```
The 'loginAs<User>' function in `steps.ts` use the 'LoginPage' page object, where are defined all the user related actions (e.g., visit, login, etc.).
## Project Directory Structure
### src directory
- **constants directory:**: Describe....
- **data_stores directory**: Describe...
- **interfaces directory**: Describe
- **page-objects directory**: Describe
- **steps.ts**: Describe
- **utils.ts**: Describe
### tests directory
The tests directory is where all test files are stored. These test files contain the actual tests that Playwright will execute to validate the functionality of the application. The configuration for the tests directory and other related settings are defined in the Playwright configuration file, typically named playwright.config.ts.
### vc_excel directory
describe
## License
[Include information about the project's license.]

91
package-lock.json generated Normal file
View File

@ -0,0 +1,91 @@
{
"name": "idhub-test",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "idhub-test",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.40.1",
"@types/node": "^20.10.6"
}
},
"node_modules/@playwright/test": {
"version": "1.40.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz",
"integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==",
"dev": true,
"dependencies": {
"playwright": "1.40.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@types/node": {
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz",
"integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.40.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz",
"integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==",
"dev": true,
"dependencies": {
"playwright-core": "1.40.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.40.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz",
"integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
}
}

14
package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "idhub-test",
"version": "1.0.0",
"description": "Testing of IdHub modules",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "Orchestral team",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.40.1",
"@types/node": "^20.10.6"
}
}

77
playwright.config.ts Normal file
View File

@ -0,0 +1,77 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
/*
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
*/
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,61 @@
/*Constants for TEMPLATES testing*/
//JSON_SCHEMA_FVC: A string representing the filename of the financial vulnerability JSON schema.
export const JSON_SCHEMA_FVC = "financial-vulnerability.json"
//JSON_SCHEMA_ID_FVC: A URL pointing to the online location of the financial vulnerability JSON schema.
export const JSON_SCHEMA_ID_FVC = "https://idhub.pangea.org/vc_schemas/financial-vulnerability.json"
/*Files used in DATA IMPORT testing*/
//PATH_FILES_TO_IMPORT: A string representing the path to the directory where Excel files are located for data import testing.
export const PATH_FILES_TO_IMPORT = '/vc_excel/'
//Financial Vulnerability Credential
export const FILE_TO_IMPORT_FVC = 'financial-vulnerability.xlsx'
export const FILE_TO_IMPORT_FVC_EMPTY = 'financial-vulnerability-empty.xlsx'
export const FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS = 'financial-vulnerability-without-required-columns.xlsx'
export const FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS = 'financial-vulnerability-with-alien-columns.xlsx'
//"Schema name" as appears in the Idhub User interface for FVC
export const SCHEMA_FVC = 'Financial Vulnerability Credential'
//"Schema type" as appears in the Idhub User interface for FVC
export const SCHEMA_TYPE_FVC = 'FinancialVulnerabilityCredential'
//Membership Card
export const FILE_TO_IMPORT_MC = 'membership-card.xlsx';
export const SCHEMA_MC = 'Membership Card'
export const SCHEMA_TYPE_MC = 'MembershipCardCredential' //Revisar este valor en la interfaz
//NGOFederationMembership
export const FILE_TO_IMPORT_FM = 'federation-membership.xlsx';
export const SCHEMA_FM = 'Federation Membership'
export const SCHEMA_TYPE_FM = 'FederationMembership' //Revisar este valor en la interfaz (en Type)
//Course Credential
export const FILE_TO_IMPORT_CC = 'course-credential.xlsx';
export const SCHEMA_CC = 'Course Credential'
export const SCHEMA_TYPE_CC = 'CourseCredential' //Revisar este valor en la interfaz
//Messages
export const ALERT_FILE_IMPORTED_SUCCESSFULLY = "The file was imported successfully!"
export const ALERT_FILE_TO_IMPORT_IS_EMPTY = "The file you try to import is empty"
export const ALERT_FILE_IS_BADLY_FORMATTED = "This file is badly formatted!"
export const ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS = "line 1: 'email' is a required property"
export const ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS = "line 1: Additional properties are not allowed ('alien1', 'alien2' were unexpected)"
export const ALERT_USER_CREATED_SUCESSFULLY_MESSAGE = 'The account was created successfully'
export const ERROR_INCORRECT_EMAIL_PASSWORD = 'Please enter a correct Email address and password. Note that both fields may be case-sensitive.'
//Memberships
export const MEMBERSHIP_TYPES = [
{ id: '1', type: 'Beneficiary' },
{ id: '2', type: 'Employee' },
{ id: '3', type: 'Member' }
];

View File

@ -0,0 +1,41 @@
export const CREDENTIAL_TYPES_FIELDS_LIST = {
"FinancialVulnerabilityCredential": [
'firstName',
'lastName',
'email',
'phoneNumber',
'identityDocType',
'identityNumber',
'streetAddress',
'socialWorkerName',
'socialWorkerSurname',
'financialVulnerabilityScore',
'amountCoveredByOtherAids',
'connectivityOptionList',
'assessmentDate'
] as string[],
// TODO Add other type credentials...
};
export const CREDENTIAL_TYPES_REQUIRED_FIELDS_LIST = {
"FinancialVulnerabilityCredential": [
"id",
"firstName",
"lastName",
"email",
"identityDocType",
"identityNumber",
"streetAddress",
"socialWorkerName",
"socialWorkerSurname",
"financialVulnerabilityScore",
"amountCoveredByOtherAids",
"assessmentDate"
] as string[],
// TODO Add other type credentials...
};

View File

@ -0,0 +1,18 @@
/*Login*/
export const ADMIN_EMAIL = "idhub_admin@pangea.org"
export const ADMIN_K = "1234"
export const KO_ADMIN_K = "876"
//export const URL_IDHUB = "https://idhub-autotest.demo.pangea.org"
export const URL_IDHUB = "https://idhub-nightly.demo.pangea.org"
export const USER1_EMAIL = "user1@example.org"
export const USER2_EMAIL = "user2@example.org"
export const USER3_EMAIL = "user3@example.org"
export const USER4_EMAIL = "user4@example.org"
export const USER5_EMAIL = "user5@example.org"
export const USER_K = "1234"
export const KO_USER_K = "876"
export const URL_PASS_RESET = URL_IDHUB + "/auth/password_reset/"
export const URL_SCHEMAS = URL_IDHUB + "/admin/schemas/"

View File

@ -0,0 +1,109 @@
import { CourseCredential_fields, FinancialVulnerabilityCredential_fields, MembershipCard_fields, NGOFederationMembership_fields } from "../interfaces/credential_interfaces";
// List of entries in data store for the different types of credentials
export const CREDENTIAL_TYPES_DATA_STORE = {
FinancialVulnerabilityCredential_data: [] as FinancialVulnerabilityCredential_fields[],
MembershipCard_data: [] as MembershipCard_fields[],
NGOFederationMembership_data: [] as NGOFederationMembership_fields[],
CourseCredential_data: [] as CourseCredential_fields[],
//TODO añadir otros tipos de credenciales
};
//Testing data for financialVulnerabilityCredential
CREDENTIAL_TYPES_DATA_STORE.FinancialVulnerabilityCredential_data = [{
firstName: 'Wolfgang Amadeus',
lastName: 'Mozart',
email: 'user1@example.org',
phoneNumber: '678567456',
identityDocType: 'DNI',
identityNumber: '45678900V',
streetAddress: 'C/ Tallers 1',
socialWorkerName: 'Ana',
socialWorkerSurname: 'Fernández',
financialVulnerabilityScore: '4',
amountCoveredByOtherAids: '0',
connectivityOptionList: 'Fibra',
assessmentDate: '2024-01-01'
},
{
firstName: 'Ludwing Van',
lastName: 'Beethoven',
email: 'user2@example.org',
phoneNumber: 'undefined',
identityDocType: 'DNI',
identityNumber: '76677667Q',
streetAddress: 'C/ Fontanals',
socialWorkerName: 'Pedro',
socialWorkerSurname: 'Ruíz',
financialVulnerabilityScore: '6',
amountCoveredByOtherAids: '30',
connectivityOptionList: 'undefined',
assessmentDate: '2024-01-03'
},
{
firstName: 'Franz',
lastName: 'Schubert',
email: 'user3@example.org',
phoneNumber: '609897645',
identityDocType: 'DNI',
identityNumber: '77777777A',
streetAddress: 'C/Miro',
socialWorkerName: 'Maria',
socialWorkerSurname: 'Sánchez',
financialVulnerabilityScore: '8',
amountCoveredByOtherAids: '10',
connectivityOptionList: 'Fibra',
assessmentDate: '2023-10-04'
},
{
firstName: 'Barbara',
lastName: 'Strozzi',
email: 'user4@example.org',
phoneNumber: '654899876',
identityDocType: 'NIE',
identityNumber: '987876765X',
streetAddress: 'C/ Picasso',
socialWorkerName: 'Claudia',
socialWorkerSurname: 'Puig',
financialVulnerabilityScore: '9',
amountCoveredByOtherAids: '0',
connectivityOptionList: 'Fibra',
assessmentDate: '2024-01-04'
}
];
CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
//TODO: fill with data
];
CREDENTIAL_TYPES_DATA_STORE.NGOFederationMembership_data = [
//TODO: fill with data
];
CREDENTIAL_TYPES_DATA_STORE.CourseCredential_data = [
//TODO: fill with data
];
export const CREDENTIAL_TYPE_DATASTORE_UNDEFINED = {
FinancialVulnerabilityCredential_data_undefined: {} as FinancialVulnerabilityCredential_fields
//TODO añadir otros tipos de credenciales
}
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.FinancialVulnerabilityCredential_data_undefined = {
firstName: 'undefined_data',
lastName: 'undefined_data',
email: 'undefined_data',
phoneNumber: 'undefined_data',
identityDocType: 'undefined_data',
identityNumber: 'undefined_data',
streetAddress: 'undefined_data',
socialWorkerName: 'undefined_data',
socialWorkerSurname: 'undefined_data',
financialVulnerabilityScore: 'undefined_data',
amountCoveredByOtherAids: 'undefined_data',
connectivityOptionList: 'undefined_data',
assessmentDate: 'undefined_data',
}

View File

@ -0,0 +1,82 @@
import { User } from "../interfaces/User";
export const users: User[] = [
{
firstName: 'John',
lastName: 'Smith',
email: 'john.smith@example.org',
membershipType: '1',
startDate: '2023-01-01',
endDate: '2024-01-01'
},
{
firstName: 'David',
lastName: 'Roberts',
email: 'david.roberts@example.org',
membershipType: '2',
startDate: '2022-01-01',
endDate: '2025-01-01',
},
{
firstName: 'Julia',
lastName: 'Pierce',
email: 'julia.pierce@example.org',
membershipType: '3',
startDate: '2023-01-01',
endDate: '2025-01-01'
},
{
firstName: 'Laura',
lastName: 'Rey',
email:'laura.rey@example.org',
membershipType: '3',
startDate: '2021-01-01',
endDate: '2025-01-01'
}
];
export const testingUsers: User[] = [
{
firstName: 'Wolfgang Amadeus',
lastName: 'Mozart',
email: 'user1@example.org',
membershipType: '1',
startDate: '2023-01-01',
endDate: '2024-01-01'
},
{
firstName: 'Ludwing Van',
lastName: 'Beethoven',
email: 'user2@example.org',
membershipType: '2',
startDate: '2022-01-01',
endDate: '2025-01-01',
},
{
firstName: 'Franz',
lastName: 'Schubert',
email: 'user3@example.org',
membershipType: '3',
startDate: '2023-01-01',
endDate: '2025-01-01'
},
{
firstName: 'Barbara',
lastName: 'Strozzi',
email:'user4@example.org',
membershipType: '1',
startDate: '2021-01-01',
endDate: '2025-01-01'
},
{
firstName: 'Clara',
lastName: 'Schumann',
email:'user5@example.org',
membershipType: '3',
startDate: '2021-01-01',
endDate: '2025-01-01'
}
];

8
src/interfaces/User.ts Normal file
View File

@ -0,0 +1,8 @@
export interface User {
firstName: string;
lastName: string;
email: string;
membershipType: string;
startDate: string;
endDate: string;
}

View File

@ -0,0 +1,74 @@
export interface FinancialVulnerabilityCredential_fields {
firstName: string;
lastName: string;
email: string;
phoneNumber: string;
identityDocType: string
identityNumber: string;
streetAddress: string;
socialWorkerName: string;
socialWorkerSurname: string;
financialVulnerabilityScore: string;
amountCoveredByOtherAids: string;
connectivityOptionList: string;
assessmentDate: string;
}
export interface MembershipCard_fields {
organisation: string;
membershipType: string;
membershipId: string;
affiliatedSince: string;
affiliatedUntil: string;
typeOfPerson: 'natural' | 'legal';
identityDocType: string;
identityNumber: string;
firstName: string;
lastName: string;
role: string;
email: string;
}
export interface NGOFederationMembership_fields {
federation: string;
legalName: string;
shortName: string;
registrationIdentifier: string;
publicRegistry: string;
streetAddress: string;
postCode: string;
city: string;
taxReference: string;
membershipType: string;
membershipStatus: string;
membershipId: string;
membershipSince: string; // string in YYYY-MM-DD format
email: string;
phone: string;
website: string;
evidence: string;
certificationDate: string; // string in YYYY-MM-DD format
}
export interface CourseCredential_fields {
firstName: string;
lastName: string;
email: string;
personalIdentifier: string;
issuedDate: string; // a string in YYYY-MM-DD format
modeOfInstruction: string;
courseDuration: number;
courseDays: number;
courseName: string;
courseDescription: string;
gradingScheme: string;
scoreAwarded: number;
qualificationAwarded: "A+" | "A" | "B" | "C" | "D";
courseLevel: string;
courseFramework: string;
courseCredits: number;
dateOfAssessment: string; // a string in YYYY-MM-DD format
evidenceAssessment: string;
}
//TODO complete

View File

@ -0,0 +1,152 @@
import { Page, Locator } from "@playwright/test"
import { ALERT_USER_CREATED_SUCESSFULLY_MESSAGE } from "../constants/constants"
export class AddMembershipPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly typeOfMembership: Locator
readonly startDate: Locator
readonly endDate: Locator
readonly saveButton: Locator
readonly cancelButton: Locator
readonly addMembershipButton: Locator
public constructor(page: Page) {
this.page = page;
this.primaryTitle = this.page.getByRole('heading', { name: 'User management' })
this.secondaryTitle = this.page.getByRole('heading', { name: 'Associate a membership to the user' })
this.typeOfMembership = this.page.getByLabel('Type of membership')
this.startDate = this.page.getByPlaceholder('Start date')
this.endDate = this.page.getByPlaceholder('End date')
this.saveButton = this.page.getByRole('button', { name: 'Save' })
this.cancelButton = this.page.getByRole('link', { name: 'Cancel' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getTypeOfMembership() {
try {
return this.typeOfMembership;
} catch (error) {
console.error("Failed to get Type of membership:", error);
throw error;
}
}
async getStartDate() {
try {
return this.startDate;
} catch (error) {
console.error("Failed to get Start date:", error);
throw error;
}
}
async getEndDate() {
try {
return this.endDate;
} catch (error) {
console.error("Failed to get End date:", error);
throw error;
}
}
async selectSaveButton() {
try {
await this.saveButton.click()
} catch (error) {
console.error("Failed to select Save button:", error);
throw error;
}
}
async selectCancelButton() {
try {
await this.cancelButton.click()
} catch (error) {
console.error("Failed to select Cancel button:", error);
throw error;
}
}
async addUserMembership(membershipType: string, startDate: string, endDate: string) {
try {
await this.page.locator('alert alert-success alert-dismissible fade show mt-3').isVisible()
await this.typeOfMembership.selectOption(membershipType)
await this.startDate.fill(startDate);
await this.endDate.fill(endDate);
await this.saveButton.click();
} catch (error) {
console.error("Failed to add user membership:", error);
throw error;
}
}
async addNewUserMembership(membershipType: string, startDate: string, endDate: string) {
try {
await this.typeOfMembership.selectOption(membershipType)
await this.startDate.fill(startDate);
await this.endDate.fill(endDate);
await this.saveButton.click();
} catch (error) {
console.error("Failed to add a new user membership:", error);
throw error;
}
}
async updateExistingUserMembership(page: Page, membershipToChange: string, newMembership: string, newStartDate: string, newEndDate: string) {
try {
await this.typeOfMembership.selectOption(newMembership);
await this.startDate.fill(newStartDate);
await this.endDate.fill(newEndDate);
await this.saveButton.click();
} catch (error) {
console.error("Failed to update a user membership:", error);
throw error;
}
}
async alertUserCreationMessageIsValid(): Promise<boolean> {
await this.page.waitForSelector('.alert.alert-success.alert-dismissible');
const element = await this.page.$('.alert.alert-success.alert-dismissible');
if (element !== null) {
const isVisible = await element.isVisible();
if (isVisible) {
const text = await element.innerText();
console.log(text);
if (text === ALERT_USER_CREATED_SUCESSFULLY_MESSAGE) {
return true;
}
}
}
return false;
}
}

View File

@ -0,0 +1,125 @@
import { Page, Locator } from "@playwright/test";
export class BasicUserInfoSectionPage {
readonly page: Page;
readonly primaryTitle: Locator;
readonly secondaryTitle: Locator;
readonly firstName: Locator;
readonly lastName: Locator;
readonly email: Locator;
readonly isAdminCheckbox: Locator;
readonly saveButton: Locator;
readonly cancelButton: Locator;
constructor(page: Page) {
this.page = page;
this.primaryTitle = this.page.getByRole('heading', { name: 'User management' })
this.secondaryTitle = this.page.getByRole('heading', { name: ' Add user' })
this.firstName = this.page.locator('input[placeholder="First name"]')
this.lastName = this.page.locator('input[placeholder="Last name"]')
this.email = this.page.locator('input[placeholder="Email address"]')
this.isAdminCheckbox = this.page.locator('#id_is_admin')
this.saveButton = this.page.getByRole('button', { name: 'Save' })
this.cancelButton = this.page.getByRole('link', { name: 'Cancel' })
}
async getPrimaryTitleText() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title text:", error);
throw error;
}
}
async getSecondaryTitleText() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title text:", error);
throw error;
}
}
async getFirstName() {
try {
return await this.firstName.inputValue();
} catch (error) {
console.error("Failed to get first name:", error);
throw error;
}
}
async getLastName() {
try {
return await this.lastName.inputValue();
} catch (error) {
console.error("Failed to get last name:", error);
throw error;
}
}
async getEmail() {
try {
return await this.email.inputValue();
} catch (error) {
console.error("Failed to get email:", error);
throw error;
}
}
async checkIsAdmin() {
try {
await this.isAdminCheckbox.check();
} catch (error) {
console.error("Failed to check 'is admin' checkbox:", error);
throw error;
}
}
async uncheckIsAdmin() {
try {
await this.isAdminCheckbox.uncheck();
} catch (error) {
console.error("Failed to uncheck 'is admin' checkbox:", error);
throw error;
}
}
async addUserBasicInfo(newFirstname: string, newLastname: string, newEmail: string, isAdmin: boolean) {
try {
console.log("Adding user with email: ", newEmail);
await this.firstName.fill(newFirstname);
await this.lastName.fill(newLastname);
await this.email.fill(newEmail);
if (isAdmin) {
await this.checkIsAdmin();
} else {
await this.uncheckIsAdmin();
}
await this.saveButton.click();
} catch (error) {
console.error("Failed to add user basic info:", error);
throw error;
}
}
async updateUserBasicInfo(newFirstname: string, newLastname: string, newEmail: string, isAdmin: boolean) {
try {
console.log("Modifying user with email: ", newEmail);
await this.firstName.fill(newFirstname);
await this.lastName.fill(newLastname);
await this.email.fill(newEmail);
if (isAdmin) {
await this.checkIsAdmin();
} else {
await this.uncheckIsAdmin();
}
await this.saveButton.click();
} catch (error) {
console.error("Failed to update user basic info:", error);
throw error;
}
}
}

View File

@ -0,0 +1,66 @@
import { Page, Locator, expect } from "@playwright/test"
export class DashboardPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly eventTitle: Locator
readonly descriptionTitle: Locator
readonly dateTitle: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Dashboard' })
this.secondaryTitle = page.getByRole('heading', { name: 'Events' })
this.eventTitle = page.getByRole('link', { name: 'Event' })
this.descriptionTitle = page.getByRole('link', { name: 'Event' })
this.dateTitle = page.getByRole('link', { name: 'Date' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getEventTitle() {
try {
return this.eventTitle;
} catch (error) {
console.error("Failed to get event title:", error);
throw error;
}
}
async getDescriptionTitle() {
try {
return this.descriptionTitle;
} catch (error) {
console.error("Failed to get description title:", error);
throw error;
}
}
async getDateTitle() {
try {
return this.dateTitle;
} catch (error) {
console.error("Failed to get date title:", error);
throw error;
}
}
}

View File

@ -0,0 +1,123 @@
import { Page, Locator, expect } from "@playwright/test"
export class ImportDataPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly dropdownDID: Locator
readonly dropdownSchema: Locator
readonly fileToImport: Locator
readonly saveButton: Locator
readonly cancelButton: Locator
public constructor(page: Page) {
this.page = page;
this.primaryTitle = this.page.getByRole('heading', { name: 'Data file management' })
this.secondaryTitle = this.page.getByRole('heading', { name: 'Import' })
//this.dropdownDID = this.page.locator('label[for="id_did"]')
//this.dropdownSchema = this.page.locator('label[for="id_schema"]')
this.dropdownDID = this.page.getByLabel('Did')
this.dropdownSchema = this.page.getByLabel('Schema')
this.fileToImport = this.page.getByLabel('File to import')
this.saveButton = this.page.getByRole('button', { name: 'Save' })
this.cancelButton = this.page.getByRole('link', { name: 'Cancel' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getDropdownDID() {
try {
return this.dropdownDID;
} catch (error) {
console.error("Failed to get dropdown DID value:", error);
throw error;
}
}
async getDropdownSchema() {
try {
return this.dropdownSchema;
} catch (error) {
console.error("Failed to get dropdown Schema value:", error);
throw error;
}
}
async getFileToImport() {
try {
return this.fileToImport;
} catch (error) {
console.error("Failed to get file to import value:", error);
throw error;
}
}
async getSaveButton() {
try {
return this.saveButton;
} catch (error) {
console.error("Failed to get the 'Save' button:", error);
throw error;
}
}
async getCancelButton() {
try {
return this.cancelButton;
} catch (error) {
console.error("Failed to get the 'Cancel' button:", error);
throw error;
}
}
async importFile(schema: string, fileToImport: string, did: string) {
try {
console.log('Importing the file from the current working directory: ${process.cwd()}');
await (await this.getDropdownDID()).selectOption({ label: did });
await (await this.getDropdownSchema()).selectOption({ label: schema });
await (await this.getFileToImport()).click();
await (await this.getFileToImport()).setInputFiles(process.cwd() + fileToImport);
await (await this.getSaveButton()).click();
// await (await this.getCancelButton()).click();
} catch (error) {
console.error("Failed to import file:", error);
throw error;
}
}
async alertFileImportedUnsuccessfully(expectedMessage: string): Promise<boolean> {
try {
const element = this.page.locator('.alert.alert-danger.alert-dismissible');
if (element) {
const isVisible = await element.isVisible();
if (isVisible) {
const text = await element.innerText();
console.log(text);
expect(text).toBe(expectedMessage);
}
}
return false;
} catch (error) {
console.error("Failed to check for unsuccessful file import alert:", error);
throw error;
}
}
}

View File

@ -0,0 +1,65 @@
import { Page, Locator } from "@playwright/test"
export class ImportTemplatePage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly availableTemplatesTitle: Locator
readonly importTemplateButton: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Template management' });
this.secondaryTitle = page.getByRole('heading', { name: 'Import template' });
this.availableTemplatesTitle = page.getByRole('heading', { name: 'Available templates' });
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async gotoImportTemplate(template: String) {
try {
const row = this.page.locator(`tr:has-text('${template}')`);
await row.locator('i.bi.bi-plus-circle').click();
} catch (error) {
console.error("Failed trying to load the import schema page:", error);
throw error;
}
}
async schemaIsAvailableToImport(schemaName: string): Promise<boolean> {
try {
console.log(`Checking if template ${schemaName} exists`);
// Wait for the table to appear and contain at least one row
await this.page.waitForFunction(() => document.querySelectorAll('tr').length > 0, { timeout: 10000 });
const templateExists = await this.page.$$eval('td:nth-child(2)', (tds, schemaName) => {
return tds.some(td => (td as HTMLElement).innerText === schemaName);
}, schemaName);
return templateExists;
} catch (error) {
console.error("Failed checking if the schema is available to import:", error);
throw error;
}
}
}

View File

@ -0,0 +1,89 @@
import { Page, Locator } from "@playwright/test"
export class LeftMenuAdminPage {
readonly page: Page;
readonly dashboardLink: Locator
readonly usersLink: Locator
readonly viewUsersLink: Locator
readonly addUsersLink: Locator
readonly rolesLink: Locator
readonly manageRolesLink: Locator
readonly manageServicesLink: Locator
readonly credentialsLink: Locator
readonly viewCredentialsLink: Locator
readonly organisationWalletLink: Locator
readonly templatesLink: Locator
readonly dataLink: Locator
public constructor(page: Page) {
this.page = page;
this.dashboardLink = this.page.getByRole('link', { name: ' Dashboard' })
this.usersLink = this.page.getByRole('link', { name: ' Users' })
this.rolesLink = this.page.getByRole('link', { name: ' Roles' })
this.credentialsLink = this.page.getByRole('link', { name: ' Credentials' })
this.templatesLink = this.page.getByRole('link', { name: ' Templates' })
this.dataLink = this.page.getByRole('link', { name: ' Data' })
this.addUsersLink = this.page.getByRole('link', { name: 'Add user'})
this.viewUsersLink = this.page.getByRole('link', { name: 'View users' })
this.manageRolesLink = this.page.getByRole('link', { name: 'Manage roles' })
this.manageServicesLink = this.page.getByRole('link', { name: 'Manage services' })
this.credentialsLink = this.page.getByRole('link', { name: 'Credentials' })
this.viewCredentialsLink = this.page.getByRole('link', { name: 'View credentials' })
this.organisationWalletLink = this.page.getByRole('link', { name: 'View credentials' })
this.templatesLink = this.page.getByRole('link', { name: 'Templates' })
this.dataLink = this.page.getByRole('link', { name: ' Data' })
}
async getDashboardLink() {
return this.dashboardLink
}
async getUsersLink() {
return this.usersLink
}
async getAddUserLink() {
return this.addUsersLink
}
async getViewUsersLink() {
return this.viewUsersLink
}
async getRolesLink() {
return this.rolesLink
}
async getManageRolesLink() {
return this.manageRolesLink
}
async getManageServicesLink() {
return this.manageServicesLink
}
async getCredentialsLink() {
return this.credentialsLink
}
async getViewCredentialsLink() {
return this.viewCredentialsLink
}
async getOrganisationalWalletLink() {
return this.organisationWalletLink
}
async getTemplatesLink() {
return this.templatesLink
}
async getDataLink() {
return this.dataLink
}
async getDashboardLinkLocator(){
return '.admin.nav-link[href="/admin/dashboard/"]';
}
}

View File

@ -0,0 +1,125 @@
import { Page, Locator } from "@playwright/test";
import { getMembershipTypeById } from "../utils";
export class MembershipSectionPage {
readonly page: Page;
readonly membershipTitle: Locator;
readonly fromTitle: Locator;
readonly toTitle: Locator;
readonly addMembershipButton: Locator;
constructor(page: Page) {
this.page = page;
this.membershipTitle = page.getByRole('button', { name: 'Membership' });
this.fromTitle = page.getByRole('button', { name: 'From' });
this.toTitle = page.getByRole('button', { name: 'To' });
this.addMembershipButton = this.page.getByRole('link', { name: 'Add membership' })
}
async getMembershipTitle() {
try {
return this.membershipTitle;
} catch (error) {
console.error("Failed to get Membership title:", error);
throw error;
}
}
async getFromTitle() {
try {
return this.fromTitle;
} catch (error) {
console.error("Failed to getFrom title:", error);
throw error;
}
}
async getToTitle() {
try {
return this.toTitle;
} catch (error) {
console.error("Failed to get To title:", error);
throw error;
}
}
async getAddMembershipButton() {
try {
return this.addMembershipButton;
} catch (error) {
console.error("Failed to get Add Membership button:", error);
throw error;
}
}
async selectAddMembershipButton() {
try {
return (await this.getAddMembershipButton()).click();
} catch (error) {
console.error("Failed to click Add Membership button:", error);
throw error;
}
}
async membershipExist(page: Page, membershipType: string): Promise<boolean> {
try {
const ms = getMembershipTypeById(membershipType);
const membershipExists = await page.$$eval('tr', (rows, ms) => {
// Find the row with membershipType
const membershipRow = rows.find(row => Array.from(row.children).some((child: Node & ChildNode) => (child as HTMLElement).innerText === ms));
return !!membershipRow;
}, ms);
return membershipExists;
} catch (error) {
console.error("The selected membershipType does not exist: ", error);
throw error;
}
}
async gotoModifyMembershipPage(page: Page, membershipToChange: string) {
try {
const ms = getMembershipTypeById(membershipToChange);
await page.$$eval('tr', (rows, ms) => {
// Find the row with membershipType
const membershipRow = rows.find(row => Array.from(row.children).some((child: Node & ChildNode) => (child as HTMLElement).innerText === ms));
// Get the "Edit" button from the row and click it
if (membershipRow) {
const editButton = membershipRow.querySelector('a[title="Edit"]') as HTMLElement;
if (editButton) {
editButton.click();
}
} else {
throw new Error(`Membership row with membershipType ${ms} not found.`);
}
}, ms);
} catch (error) {
console.error("The selected membershipType does not exist: ", error);
}
}
async gotoDeleteSpecificMembership(page: Page, membershipType: string) {
try {
const ms = getMembershipTypeById(membershipType)
await page.$$eval('tr', (rows, ms) => {
// Find the row with membershipType
const membershipRow = rows.find(row => Array.from(row.children).some((child: Node & ChildNode) => (child as HTMLElement).innerText === ms));
// Get the "Delete" button from the row and click it
if (membershipRow) {
const deleteButton = membershipRow.querySelector('a[title="Delete"]') as HTMLElement;
if (deleteButton) {
deleteButton.click();
}
} else {
throw new Error(`Membership row with membershipType ${ms} not found.`);
}
}, ms);
} catch (error) {
console.error("The selected membershipType does not exist: ", error);
}
}
}

View File

@ -0,0 +1,209 @@
import { Page, Locator, expect } from "@playwright/test"
import { clickViewSchemaOnLeftMenu } from "../steps"
import { URL_SCHEMAS } from "../constants/env_constants"
export class TemplatesPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly createdTitle: Locator
readonly fileSchemaTitle: Locator
readonly nameTitle: Locator
readonly descriptionTitle: Locator
readonly viewSchema: Locator
readonly deleteSchema: Locator
readonly addTemplateButton: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Template management' });
this.secondaryTitle = page.getByRole('heading', { name: 'View credential templates' });
this.createdTitle = page.getByRole('button', { name: 'Created' })
this.fileSchemaTitle = page.getByRole('button', { name: 'File schema' })
this.nameTitle = page.getByRole('button', { name: 'Name' })
this.descriptionTitle = page.getByRole('button', { name: 'Description' })
this.addTemplateButton = page.getByRole('link', { name: 'Add template ' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getCreatedTitle() {
try {
return this.createdTitle;
} catch (error) {
console.error("Failed to get Created title:", error);
throw error;
}
}
async getFileSchemaTitle() {
try {
return this.getFileSchemaTitle;
} catch (error) {
console.error("Failed to get File schema title:", error);
throw error;
}
}
async getNameTitle() {
try {
return this.nameTitle;
} catch (error) {
console.error("Failed to get Name title:", error);
throw error;
}
}
async getDescriptionTitle() {
try {
return this.descriptionTitle;
} catch (error) {
console.error("Failed to get Description title:", error);
throw error;
}
}
async getaddTemplateButton() {
try {
return this.addTemplateButton;
} catch (error) {
console.error("Failed to get add template button", error);
throw error;
}
}
async checkSchemaNamesAgainstCorrespondingJSON(page: Page): Promise<boolean> {
try {
// Get the count of table rows
const rowCount = await page.locator('table tbody tr').count();
for (let i = 0; i < rowCount; i++) {
// Get the second cell (file schema name) of the current row
console.log("Checking schema name against JSON for element: ", i);
const fileSchemaCell = page.locator(`table tbody tr:nth-child(${i + 1}) td:nth-child(2)`);
const fileSchemaValue = await fileSchemaCell.innerText();
// Print the value of the second cell
console.log('Name of the schema: ' + fileSchemaValue);
// Click the corresponding element in the fifth cell (eye icon)
await clickViewSchemaOnLeftMenu(page, fileSchemaValue)
// Parse the JSON content of the page
const jsonContent = await page.evaluate(() => JSON.parse(document.body.innerText));
// Extract the last component of the $id path
const idUrl = new URL(jsonContent["$id"]);
const lastPathSegment = idUrl.pathname.split("/").pop();
console.log('lastPathSegment', lastPathSegment);
// Check if the last component of the $id path matches the second cell value (File schema)
expect(lastPathSegment).toBe(fileSchemaValue);
await page.goto(URL_SCHEMAS);
}
return true;
} catch (error) {
console.error("Failed checking the schema against json:", error);
throw error;
}
}
async schemaIsAvailableInView(schemaName: string): Promise<boolean> {
try {
console.log('Checking if template ${schemaName} exists');
// Wait for the table to appear and contain at least one row
await this.page.waitForFunction(() => document.querySelectorAll('tr').length > 0, { timeout: 10000 });
const templateExists = await this.page.$$eval('td:nth-child(2)', (tds, schemaName) => {
return tds.some(td => (td as HTMLElement).innerText === schemaName);
}, schemaName);
return templateExists;
} catch (error) {
console.error("A problem while loading the list of templates.", error);
throw error;
}
}
async gotoViewSchemaPage(template: String) {
try {
const row = this.page.locator(`tr:has-text('${template}')`);
await row.locator('i.bi.bi-eye').click();
} catch (error) {
console.error("Failed to go to the View schemas page", error);
throw error;
}
}
async gotoAddTemplatePage() {
try {
await this.addTemplateButton.click();
} catch (error) {
console.error("Failed to add the template: ", error);
throw error;
}
}
async gotoDeleteAndConfirmInModal(template: String) {
try {
const row = this.page.locator(`tr:has-text('${template}')`);
await row.locator('i.bi.bi-trash').click();
//Find the modal that corresponds to the specific template (see html)
const modal = this.page.locator(`div.modal:has-text("${template}")`)
.first();
// Ensure the modal is visible before trying to interact with it
await expect(modal).toBeVisible();
// Click the delete button within the modal
await modal.locator('.btn.btn-danger').click();
} catch (error) {
console.error("A problem while trying to delete the template.", error);
throw error;
}
}
async gotoDeleteAndCancelInModal(template: String) {
try {
const row = this.page.locator(`tr:has-text('${template}')`);
await row.locator('i.bi.bi-trash').click();
//Find the modal that corresponds to the specific template (see html)
const modal = this.page.locator(`div.modal:has-text("${template}")`)
.first();
// Ensure the modal is visible before trying to interact with it
await expect(modal).toBeVisible();
// Click the delete button within the modal
await modal.locator('.btn.btn-secondary').click();
} catch (error) {
console.error("A problem while trying to delete the template.", error);
throw error;
}
}
}

View File

@ -0,0 +1,108 @@
import { Page, Locator } from "@playwright/test"
import { User } from '../interfaces/User'
import { formatDate, getMembershipTypeById } from "../utils"
export class UserPersonalInformationPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly modifyButton: Locator
readonly deactivateButton: Locator
readonly deleteButton: Locator
public constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'User management' });
this.secondaryTitle = page.getByRole('heading', { name: ' User personal information' });
this.modifyButton = page.getByRole('link', { name: 'Modify' })
this.deactivateButton = page.getByRole('link', { name: 'Deactivate' })
this.deleteButton = page.getByRole('link', { name: 'Delete' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async isTheUserInfoValid(user: User): Promise<boolean> {
try {
const firstNameElement = await this.page.waitForSelector('.col-9.text-secondary', { timeout: 6000 });
const fn = firstNameElement ? await this.page.evaluate(el => (el as HTMLElement).innerText, firstNameElement) : '';
const lastNameElement = await this.page.waitForSelector('.card-body > div:nth-of-type(2) > .col-9.text-secondary', { timeout: 6000 });
const ln = lastNameElement ? await this.page.evaluate(el => (el as HTMLElement).innerText, lastNameElement) : '';
const emailElement = await this.page.waitForSelector('.card-body > div:nth-of-type(3) > .col-9.text-secondary', { timeout: 6000 });
const em = emailElement ? await this.page.evaluate(el => (el as HTMLElement).innerText, emailElement) : '';
const membershipTypeElement = await this.page.$$eval('tr > td:nth-of-type(1)', elements => elements.map(el => el.textContent ? el.textContent.trim() : ''));
const mst = membershipTypeElement[0];
const fromDateElement = await this.page.$$eval('tr > td:nth-of-type(2)', elements => elements.map(el => el.textContent ? el.textContent.trim() : ''));
const fromDate = fromDateElement[0];
const toDateElement = await this.page.$$eval('tr > td:nth-of-type(3)', elements => elements.map(el => el.textContent ? el.textContent.trim() : ''));
const toDate = toDateElement[0];
console.log(fn, ' ', ln, ' ', em, ' ', mst, ' ', fromDate, ' ', toDate);
console.log(user.firstName, ' ', user.lastName, ' ', user.email, ' ', getMembershipTypeById(user.membershipType), ' ', formatDate(user.startDate), ' ', formatDate(user.endDate));
if (user.firstName === fn && user.lastName === ln && user.email === em && getMembershipTypeById(user.membershipType) === mst && formatDate(user.startDate) === fromDate && formatDate(user.endDate) === toDate) {
return true
}
else { return false }
} catch (error) {
console.error("Failed to retrieve user information:", error);
throw error;
}
}
async selectModifyUser() {
try {
await this.modifyButton.click({ timeout: 60000 })
} catch (error) {
console.error("Failed to click the Modify button:", error);
throw error;
}
}
async selectDeleteUser() {
try {
await this.deleteButton.click();
} catch (error) {
console.error("Failed to click the Delete button:", error);
throw error;
}
}
async deleteUser(page: Page) {
try {
await this.deleteButton.click();
await this.page.waitForSelector('.modal-body', { timeout: 5000 });
await this.page.click('a.btn.btn-danger');//delete
//await page.click('button.btn.btn-secondary');//cancel
} catch (error) {
console.error("Failed to delete the user:", error);
throw error;
}
}
}

View File

@ -0,0 +1,155 @@
import { Page, Locator, expect } from "@playwright/test"
import { ALERT_FILE_IMPORTED_SUCCESSFULLY } from "../constants/constants"
export class ViewImportedDataPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly dateTitle: Locator
readonly fileTitle: Locator
readonly successTitle: Locator
readonly importDataButton: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Data file management' });
this.secondaryTitle = page.getByRole('heading', { name: 'Import data' });
this.dateTitle = page.getByRole('link', { name: 'Date' })
this.fileTitle = page.getByRole('link', { name: 'File' })
this.successTitle = page.getByRole('link', { name: 'Success' })
this.importDataButton = page.getByRole('link', { name: 'Import data ' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getDateTitle() {
try {
return this.dateTitle;
} catch (error) {
console.error("Failed to get Data title:", error);
throw error;
}
}
async getFileTitle() {
try {
return this.fileTitle;
} catch (error) {
console.error("Failed to get File title:", error);
throw error;
}
}
async getSuccessTitle() {
try {
return this.successTitle;
} catch (error) {
console.error("Failed to get Success title:", error);
throw error;
}
}
async getImportDataButton() {
try {
return this.importDataButton;
} catch (error) {
console.error("Failed to get Import Data button:", error);
throw error;
}
}
async gotoImportDataPage() {
try {
(await this.getImportDataButton()).click();
} catch (error) {
console.error("Failed to go to import data page:", error);
throw error;
}
}
async orderTableByDate() {
(await this.getDateTitle()).click();
}
async fileIsAvailableInView(fileName: string): Promise<boolean> {
try {
console.log(`Checking if file ${fileName} was previously loaded`);
// Wait for the table to appear and contain at least one row
await this.page.waitForFunction(() => document.querySelectorAll('tr').length > 0, { timeout: 10000 });
const fileExists = await this.page.$$eval('td:nth-child(2)', (tds, fileName) => {
return tds.some(td => (td as HTMLElement).innerText === fileName);
}, fileName);
return fileExists;
} catch (error) {
console.error("A problem while loading the list of files.", error);
throw error;
}
}
async isFileSuccessfullyLoaded(fileName: string): Promise<boolean> {
try {
const row = this.page.locator(`tr:has-text('${fileName}')`);
if (!row) {
console.log(`No row found with file value: ${fileName}`);
return false;
}
const thirdCell = row.locator('td:nth-child(3)');
if (thirdCell) {
const tickOrCross = await thirdCell.textContent();
if (tickOrCross === '✔') {
return true;
} else if (tickOrCross === '✘') {
return false;
}
}
return false;
} catch (error) {
console.error('Error occurred:', error);
return false;
}
}
async alertFileImportedSuccessfully(): Promise<boolean> {
try {
await this.page.waitForSelector('.alert.alert-success.alert-dismissible');
const element = await this.page.$('.alert.alert-success.alert-dismissible');
if (element !== null) {
const isVisible = await element.isVisible();
if (isVisible) {
const text = await element.innerText();
console.log(text);
expect(text).toBe(ALERT_FILE_IMPORTED_SUCCESSFULLY);
}
}
return false;
} catch (error) {
console.error("Failed to check for successful file import alert:", error);
throw error;
}
}
}

View File

@ -0,0 +1,37 @@
import { Page, Locator } from "@playwright/test"
export class ViewRolesPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly nameColumnTitle: Locator
readonly descriptionColumnTitle: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Access control management' });
this.secondaryTitle = page.getByRole('heading', { name: 'Manage roles' });
this.nameColumnTitle = this.page.getByRole('button', { name: 'Name' })
this.descriptionColumnTitle = this.page.getByRole('button', { name: 'Description' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
}

View File

@ -0,0 +1,41 @@
import { Page, Locator } from "@playwright/test"
export class ViewServicesPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly serviceColumnTitle: Locator
readonly descriptionColumnTitle: Locator
readonly roleColumnTitle: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'Access control management' });
this.secondaryTitle = page.getByRole('heading', { name: 'Manage services' });
this.serviceColumnTitle = this.page.getByRole('button', { name: 'Service' })
this.descriptionColumnTitle = this.page.getByRole('button', { name: 'Description' })
this.roleColumnTitle = this.page.getByRole('button', { name: 'Role' })
}
async getPrimaryTitle(){
return this.primaryTitle.innerText();
}
async getSecondaryTitle(){
return this.secondaryTitle.innerText();
}
async getServiceColumnTitle(){
return this.serviceColumnTitle;
}
async getDescriptionColumnTitle(){
return this.descriptionColumnTitle;
}
async getRoleColumnTitle(){
return this.roleColumnTitle;;
}
}

View File

@ -0,0 +1,89 @@
import { Page, Locator } from "@playwright/test"
import { User } from "../interfaces/User"
export class ViewUsersPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly lastNameColumnTitle: Locator
readonly firstNameColumnTitle: Locator
readonly emailColumnTitle: Locator
readonly membershipColumnTitle: Locator
readonly roleColumnTitle: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'User management' });
this.secondaryTitle = page.getByRole('heading', { name: ' View users' });
this.lastNameColumnTitle = this.page.getByRole('button', { name: 'Last name' })
this.firstNameColumnTitle = this.page.getByRole('button', { name: 'First name' })
this.emailColumnTitle = this.page.getByRole('button', { name: 'Email' })
this.membershipColumnTitle = this.page.getByRole('button', { name: 'Membership' })
this.roleColumnTitle = this.page.getByRole('button', { name: 'Role' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title text:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title text:", error);
throw error;
}
}
async userExists(email: string): Promise<boolean> {
try {
console.log('Checking the user with email', email);
// Wait for the table to appear and contain at least one row
await this.page.waitForFunction(() => document.querySelectorAll('tr').length > 0, { timeout: 10000 });
const userExists = await this.page.$$eval('td:nth-child(3)', (tds, email) => {
return tds.some(td => (td as HTMLElement).innerText === email);
}, email);
return userExists;
} catch (error) {
console.error("The user does not exist!", error);
throw error;
}
}
async gotoUserInformationPage(user: User) {
try {
let email = user.email;
const row = this.page.locator(`tr:has-text('${email}')`);
await row.locator('i.bi.bi-eye').click();
} catch (error) {
console.error("Failed to view the details of the user:", error);
throw error;
}
}
async deleteUser(email: string) {
try {
this.page.getByRole('row', { name: email }).getByRole('link').click()
this.page.getByRole('link', { name: 'Delete' }).click();
await this.page.waitForSelector('.modal-body', { timeout: 5000 });
await this.page.click('a.btn.btn-danger'); //delete
//await page.click('button.btn.btn-secondary');//cancel
} catch (error) {
console.error("Failed to delete the user:", error);
throw error;
}
}
}

View File

@ -0,0 +1,58 @@
import { type Page, type Locator } from '@playwright/test';
import { ERROR_INCORRECT_EMAIL_PASSWORD } from '../constants/constants';
export class LogInPage {
readonly page: Page;
readonly emailInputLocator: Locator
readonly passwordInputLocator: Locator
readonly signInButtonLocator: Locator
public constructor(page: Page) {
this.page = page;
this.emailInputLocator = page.getByPlaceholder('Email address')
this.passwordInputLocator = page.getByPlaceholder('Password')
this.signInButtonLocator = page.getByRole('button', { name: 'Log in' })
}
async visit(site: string) {
try {
await this.page.goto(site);
} catch (error) {
console.error('Failed to navigate to the site:', error);
throw error;
}
}
async login(email: string, password: string) {
try {
await this.emailInputLocator.fill(email);
await this.passwordInputLocator.fill(password);
await this.signInButtonLocator.click();
} catch (error) {
console.error('Login failed:', error);
throw new Error('Login failed');
}
}
async visitForgotPassword(site: string) {
try {
await this.page.goto(site);
} catch (error) {
console.error('Failed to navigate to the forgot password page:', error);
throw new error;
}
}
async errorMessageIsValid(): Promise<boolean> {
try {
const isVisible = await this.page.locator('.well.well-small.text-error').isVisible();
if (!isVisible) {
return false;
}
const errorText = await this.page.locator('.well.well-small.text-error').textContent();
return errorText === ERROR_INCORRECT_EMAIL_PASSWORD;
} catch (error) {
console.error('Failed to check error message:', error);
throw new error;
}
}
}

View File

@ -0,0 +1,59 @@
import { Page, Locator } from "@playwright/test"
export class LeftMenuUserPage {
readonly page: Page;
readonly dashboardLink: Locator
readonly myPersonalInfoLink: Locator
readonly myRolesLink: Locator
readonly myDataProtectionLink: Locator
readonly identitiesLink: Locator
readonly myCredentialsLink: Locator
readonly requestACredentialLink: Locator
readonly presentACredentialLink: Locator
public constructor(page: Page) {
this.page = page;
this.dashboardLink = this.page.getByRole('link', { name: ' Dashboard' })
this.myPersonalInfoLink = this.page.getByRole('link', { name: 'My personal information' })
this.myRolesLink = this.page.getByRole('link', { name: 'My roles' })
this.myDataProtectionLink = this.page.getByRole('link', { name: 'Data protection' })
this.identitiesLink = this.page.getByRole('link', { name: 'Identities (DIDs)' })
this.myCredentialsLink = this.page.getByRole('link', { name: 'My credentials' })
this.requestACredentialLink = this.page.getByRole('link', { name: 'Request a credential' })
this.presentACredentialLink = this.page.getByRole('link', { name: 'Present a credential' })
}
async getDashboardLink() {
return this.dashboardLink
}
async getmyPersonalInfoLink() {
return this.myPersonalInfoLink
}
async getMyRolesLink() {
return this.myRolesLink
}
async getMyDataProtectionLink() {
return this.myDataProtectionLink
}
async getIdentitiesLink() {
return this.identitiesLink
}
async getMyCredentialsLink() {
return this.myCredentialsLink
}
async getRequestACredentialLink() {
return this.requestACredentialLink
}
async getPresentACredentialLink() {
return this.presentACredentialLink
}
}

View File

@ -0,0 +1,58 @@
import { Page, Locator } from "@playwright/test"
import { initialize_with_undefined_values, setCredentialValues } from "../steps";
export class ViewCredentialPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'My wallet' });
this.secondaryTitle = page.getByRole('heading', { name: ' Credential' });
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async buildACredentialFromInputValues(credentialType: string) {
const elementsWithLabel = await this.page.locator('.col-3 strong').all();
const elementsWithValue = await this.page.locator('.col.bg-light.text-secondary').all();
let credential = initialize_with_undefined_values(credentialType);
let labels: string[] = [];
let values: string[] = [];
for (let i = 0; i < elementsWithLabel.length; i++) {
let label = await elementsWithLabel[i].textContent()
if (label) label = label.trim().replace(/:$/, '');
let value = await elementsWithValue[i].textContent()
if (value) value = value.trim();
if (value != null && label != null && label != 'Id' && label != "Issuance date" && label != "Status") {
labels.push(label);
values.push(value);
}
}
credential = setCredentialValues(labels, values, credentialType);
return credential;
}
}

View File

@ -0,0 +1,119 @@
import { Page, Locator } from "@playwright/test"
export class ViewMyCredentialsPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly typeTitle: Locator
readonly detailsTitle: Locator
readonly issuedOnTitle: Locator
readonly statusTitle: Locator
readonly viewCredentialButton: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'My wallet' });
this.secondaryTitle = page.getByRole('heading', { name: 'Credential management' });
this.typeTitle = page.getByRole('link', { name: 'Type' })
this.detailsTitle = page.getByRole('link', { name: 'Details' })
this.issuedOnTitle = page.getByRole('link', { name: 'Issued on' })
this.statusTitle = page.getByRole('link', { name: 'Status' })
this.viewCredentialButton = page.getByRole('link', { name: '' })
}
async getPrimaryTitle() {
try {
return await this.primaryTitle.innerText();
} catch (error) {
console.error("Failed to get primary title:", error);
throw error;
}
}
async getSecondaryTitle() {
try {
return await this.secondaryTitle.innerText();
} catch (error) {
console.error("Failed to get secondary title:", error);
throw error;
}
}
async getTypeTitle() {
try {
return this.typeTitle;
} catch (error) {
console.error("Failed to get Type title:", error);
throw error;
}
}
async getDetailsTitle() {
try {
return this.detailsTitle;
} catch (error) {
console.error("Failed to get Details title:", error);
throw error;
}
}
async getIssuedOnTitle() {
try {
return this.issuedOnTitle;
} catch (error) {
console.error("Failed to get Issue On title:", error);
throw error;
}
}
async getStatusTitle() {
try {
return this.statusTitle;
} catch (error) {
console.error("Failed to get Issue On title:", error);
throw error;
}
}
async orderTableByType() {
(await this.getTypeTitle()).click();
}
async orderTableByIssuedOn() {
(await this.getIssuedOnTitle()).click();
}
async gotoViewEnabledCredentialPage(type: string) {
// Find the row that has the specified type and status 'Enabled'
const row = this.page.locator(`tr:has(td:nth-child(1):has-text("${type}"), td:nth-child(4):has-text("Enabled"))`);
// Check if the row exists
if (await row.count() > 0) {
// Find the view credential button within the row and click it
await row.locator('i.bi.bi-eye').click();
} else {
console.log(`No row found with type '${type}' and status 'Enabled'.`);
}
}
async enabledCredentialExistInMyCredentials(type: string): Promise<boolean> {
// Find the row that has the specified type and status 'Enabled'
const row = this.page.locator(`tr:has(td:nth-child(1):has-text("${type}"), td:nth-child(4):has-text("Enabled"))`);
// Check if the row exists
if (await row.count() > 0) {
return true;
} else {
console.log(`No row found with type '${type}' and status 'Enabled'.`);
return false;
}
}
}

350
src/steps.ts Normal file
View File

@ -0,0 +1,350 @@
import { expect, Page } from '@playwright/test'
import { appendRandomNumberToFilename, copyFile } from './utils'
import { CREDENTIAL_TYPE_DATASTORE_UNDEFINED, CREDENTIAL_TYPES_DATA_STORE } from './data_stores/credentials_data_store'
import { FinancialVulnerabilityCredential_fields } from './interfaces/credential_interfaces'
import { CREDENTIAL_TYPES_FIELDS_LIST, CREDENTIAL_TYPES_REQUIRED_FIELDS_LIST } from './constants/credential_fields'
import { LogInPage } from './page-objects/COMM_LoginPage'
import { ADMIN_EMAIL, ADMIN_K, USER_K } from './constants/env_constants'
import { LeftMenuAdminPage } from './page-objects/AD_LeftMenuAdminPage'
import { LeftMenuUserPage } from './page-objects/US_LeftMenuUserPage'
import { TemplatesPage } from './page-objects/AD_TemplatesPage'
import { ViewUsersPage } from './page-objects/AD_ViewUsersPage'
import { ViewMyCredentialsPage } from './page-objects/US_ViewMyCredentialsPage'
import { UserPersonalInformationPage } from './page-objects/AD_UserPersonalInformationPage'
import { ImportTemplatePage } from './page-objects/AD_ImportTemplatePage'
import { ViewImportedDataPage } from './page-objects/AD_ViewImportedDataPage'
import { User } from './interfaces/User'
export async function loginAsAdmin(page: Page, url: string) {
try {
const loginPage = new LogInPage(page);
await loginPage.visit(url);
await loginPage.login(ADMIN_EMAIL, ADMIN_K);
let currentTitle = await page.title();
if (currentTitle === 'Encryption Key IdHub') {
//code to set Encription Key
page.getByPlaceholder('Key for encrypt the secrets').click();
await page.getByPlaceholder('Key for encrypt the secrets').fill('1234');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page).toHaveTitle('Data protection IdHub');
currentTitle = await page.title();
}
if (currentTitle === 'Data protection IdHub') {
// Code to accept terms and conditions
await page.click('#id_accept_privacy');
await page.click('#id_accept_legal');
await page.click('#id_accept_cookies');
await page.click('a[type="button"]');
}
await expect(page).toHaveTitle('Dashboard IdHub');
} catch (error) {
console.error(`Failed to login as admin: `);
throw error;
}
}
export async function loginAsUser(page: Page, userEmail: string, url: string) {
try {
const loginPage = new LogInPage(page);
await loginPage.visit(url);
await loginPage.login(userEmail, USER_K);
const currentTitle = await page.title();
if (currentTitle === 'Data Protection IdHub') {
// Code to accept terms and conditions
await page.click('#id_accept_privacy');
await page.click('#id_accept_legal');
await page.click('#id_accept_cookies');
await page.click('a[type="button"]');
}
await expect(page).toHaveTitle('Dashboard IdHub');
} catch (error) {
console.error(`Failed to login as user: `);
throw error;
}
}
export async function clickDashboardOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const dashboardLink = await leftMenuPage.getDashboardLink();
await dashboardLink.click({ timeout: 60000 });
return { dashboardLink }
} catch (error) {
console.error(`Failed to access Dashboard from left menu: `);
throw error;
}
}
export async function clickUsersOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const usersLink = await leftMenuPage.getUsersLink();
await usersLink.click()
return { usersLink }
} catch (error) {
console.error(`Failed to access Users option on left menu: `);
throw error;
}
}
export async function clickViewUsersOnLeftMenu(page: Page) {
try {
clickUsersOnLeftMenu(page);
const leftMenuPage = new LeftMenuAdminPage(page);
const viewUsersLink = await leftMenuPage.getViewUsersLink();
await viewUsersLink.click();
return { viewUsersLink }
} catch (error) {
console.error(`Failed to access View Users Option on left menu: `);
throw error;
}
}
export async function clickAddUserOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const addUserLink = await leftMenuPage.getAddUserLink();
await addUserLink.click();
return { addUserLink }
} catch (error) {
console.error(`Failed to access Add Users option on left menu: `);
throw error;
}
}
export async function clickMyCredentialsOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuUserPage(page);
const myCredentialsLink = await leftMenuPage.getMyCredentialsLink();
await myCredentialsLink.click();
return { myCredentialsLink }
} catch (error) {
console.error(`Failed to access My Credential option on left menu: `);
throw error;
}
}
export async function clickManageRolesOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const manageRolesLink = await leftMenuPage.getManageRolesLink();
await manageRolesLink.click()
return { manageRolesLink }
} catch (error) {
console.error(`Failed to access Manage Roles option on left menu: `);
throw error;
}
}
export async function clickManageServicesOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const manageRolesLink = await leftMenuPage.getManageServicesLink();
await manageRolesLink.click()
return { manageRolesLink }
} catch (error) {
console.error(`Failed to access Manage Services option on left menu: `);
throw error;
}
}
export async function clickTemplatesOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const templatesLink = await leftMenuPage.getTemplatesLink();
await templatesLink.click()
return { templatesLink }
} catch (error) {
console.error(`Failed to access Templates option on left menu: `);
throw error;
}
}
export async function clickDataOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const dataLink = await leftMenuPage.getDataLink();
await dataLink.click()
return { dataLink }
} catch (error) {
console.error(`Failed access Data on left menu: `);
throw error;
}
}
export async function clickCredentialsOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const credentialsLink = await leftMenuPage.getCredentialsLink();
await credentialsLink.click()
return { credentialsLink }
} catch (error) {
console.error(`Failed to access Credentials On left Menu: `);
throw error;
}
}
export async function clickViewCredentialsOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const viewCredentialsLink = await leftMenuPage.getViewCredentialsLink();
await viewCredentialsLink.click()
return { viewCredentialsLink }
} catch (error) {
console.error(`Failed to access View Credential on left menu: `);
throw error;
}
}
export async function clickViewSchemaOnLeftMenu(page: Page, templateName: string) {
try {
const templatesPage = new TemplatesPage(page)
//Access the specific template schema
const { templatesLink } = await clickTemplatesOnLeftMenu(page)
expect(await templatesPage.schemaIsAvailableInView(templateName)).toBeTruthy()
await templatesPage.gotoViewSchemaPage(templateName)
} catch (error) {
console.error(`Failed to access the Template option on left menu : `);
throw error;
}
}
export async function gotoBasicInfoPageOfTheUser(page: Page, user: User) {
const viewUsersPage = new ViewUsersPage(page)
//Access the specific UPI
const { viewUsersLink } = await clickViewUsersOnLeftMenu(page)
try {
expect(await viewUsersPage.userExists(user.email)).toBeTruthy()
} catch (error) {
console.error('Failed validating that user exists', error);
}
await viewUsersPage.gotoUserInformationPage(user)
}
export async function gotoViewEnabledCredential(page: Page, credentialType: string) {
const myCredentialsPage = new ViewMyCredentialsPage(page)
//Access the specific enabled credential
const { myCredentialsLink } = await clickMyCredentialsOnLeftMenu(page);
try {
expect(await myCredentialsPage.enabledCredentialExistInMyCredentials(credentialType)).toBeTruthy();
} catch (error) {
console.error('Failed accessing the enabled credential of type ${credentialType}', error);
}
await myCredentialsPage.gotoViewEnabledCredentialPage(credentialType);
}
export async function checkIfTheInformationIsValid(page: Page, user: User) {
console.log(user.firstName, "membership: ", user.membershipType, "sd: ", user.startDate)
const userPersonalInformationPage = new UserPersonalInformationPage(page)
try {
expect(await userPersonalInformationPage.isTheUserInfoValid(user)).toBeTruthy();
} catch (error) {
console.error('Failed validating the user information:', error);
}
}
export function expectedCredentialSubjectForUser<T extends FinancialVulnerabilityCredential_fields, MembershipCard_fields>(email: string, credentialType: string): FinancialVulnerabilityCredential_fields | MembershipCard_fields | undefined {
try {
const testingDataForCredential = CREDENTIAL_TYPES_DATA_STORE[credentialType + '_data'] as T[];
if (!testingDataForCredential) {
console.error(`No testing data found for credential type: ${credentialType}`);
return undefined;
}
console.log("testing Data: ", testingDataForCredential);
let credentialSubject = testingDataForCredential.find(user => user.email.toLowerCase() === email.toLowerCase());
return credentialSubject;
} catch (error) {
console.error('Failed retriving the expected credential:', error);
}
}
export async function importSchema(page: Page, jsonSchema: string) {
try {
const templatesPage = new TemplatesPage(page);
const importTemplatePage = new ImportTemplatePage(page)
await templatesPage.gotoAddTemplatePage();
expect(importTemplatePage.schemaIsAvailableToImport(jsonSchema)).toBeTruthy();
await importTemplatePage.gotoImportTemplate(jsonSchema);
return await templatesPage.schemaIsAvailableInView(jsonSchema);
} catch (error) {
console.error('Failed to import the schema:', error);
}
}
export async function checkFileName(page: Page, fileName: string): Promise<string> {
const viewImportedDataPage = new ViewImportedDataPage(page);
let fileInTheList = await viewImportedDataPage.fileIsAvailableInView(fileName);
if (fileInTheList) {
let newName = appendRandomNumberToFilename(fileName);
//rename the file
copyFile(fileName, newName);
return newName;
}
return fileName;
}
export async function loadIfJsonSchemaNotAvailable(page: Page, jsonSchema: string) {
try {
//Ensure that the json schema associated with the xls is available
const templatesPage = new TemplatesPage(page);
await clickTemplatesOnLeftMenu(page);
//check if the schema is available
let schemaAvailable = await templatesPage.schemaIsAvailableInView(jsonSchema);
if (!schemaAvailable) {
//import the schema
expect(await importSchema(page, jsonSchema)).toBeTruthy();
}
} catch (error) {
console.error('Failed to load schema:', error);
}
}
export function setCredentialValues(labels: string[], values: string[], credentialType: string): any {
// Get the interface type associated with the credentialType
const listOfFields = CREDENTIAL_TYPES_FIELDS_LIST[credentialType];
const listOfRequiredFields = CREDENTIAL_TYPES_REQUIRED_FIELDS_LIST[credentialType];
// Initialize an empty object to store the credential
let credential: any = {};
try {
// Iterate over labels and values
for (let i = 0; i < labels.length; i++) {
let label = labels[i];
let value = values[i];
// Check if the label exists in the interface type
label = label.charAt(0).toLowerCase() + label.slice(1);
if (listOfFields.includes(label)) {
credential[label] = value;
} else {
// If the label does not exist, add it as "unknown_data"
credential[label] = "unknown_data";
}
}
// Return the credential object
return credential;
} catch (error) {
console.error('Failed to set credential values:', error);
}
}
export function initialize_with_undefined_values(credentialType: string) {
return (CREDENTIAL_TYPE_DATASTORE_UNDEFINED[credentialType + '_data_undefined'])
}

131
src/utils.ts Normal file
View File

@ -0,0 +1,131 @@
import { MEMBERSHIP_TYPES, PATH_FILES_TO_IMPORT } from "./constants/constants";
import { unlink } from 'fs/promises';
export function createUsersRandomList(num: number) {
let randomUsers: { firstName: string; lastName: string; email: string; membershipType: string; startDate: string; endDate: string }[] = []
for (let i = 0; i < num; i++) {
let randomName = generateRandomName()
let fn = randomName.split(' ')[0]
let ln = randomName.split(' ')[1]
let em = fn.toLowerCase() + '.' + ln.toLowerCase() + '@example.org';
randomUsers.push({
firstName: fn,
lastName: ln,
email: em,
membershipType: '2',
startDate: '2022-01-01',
endDate: '2030-01-01'
});
}
let uniqueRandomUsers = randomUsers.filter((value, index, array) =>
index === array.findIndex(item =>
item.firstName === value.firstName &&
item.lastName === value.lastName &&
item.email === value.email
)
);
return uniqueRandomUsers
}
function generateRandomName() {
var firstNames = ["Jan", "Pablo", "Ana", "Mari", "Jean", "Li", "Pau", "Glori", "Olga", "Sasha", "Diego", "Carla"];
var lastNames = ["Garcia", "Sartre", "Cuevas", "Harrison", "Peña", "López", "Zapatero", "Dueñas", "Serrat", "Borgoñoz", "Suarez", "Craig"];
var randomFirstName = firstNames[Math.floor(Math.random() * firstNames.length)];
var randomLastName = lastNames[Math.floor(Math.random() * lastNames.length)];
return `${randomFirstName} ${randomLastName}`;
}
export function formatDate(dateStr: string) {
// Create a new Date object from the string
const date = new Date(dateStr);
// Define the names of the months
const monthNames = ["Jan.", "Feb.", "March", "April", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."];
// Extract the day, month, and year
const day = date.getDate();
const monthIndex = date.getMonth();
const year = date.getFullYear();
// Return the formatted date
return `${monthNames[monthIndex]} ${day}, ${year}`;
}
export async function copyFile(source: string, target: string) {
console.log("copyFile....");
const fs = require('fs').promises;
const path = require('path');
let sourceFilePath = path.join(PATH_FILES_TO_IMPORT, source);
let targetFilePath = path.join(PATH_FILES_TO_IMPORT, target);
console.log(`Current working directory: ${process.cwd()}`);
sourceFilePath = path.join(process.cwd(), sourceFilePath);
targetFilePath = path.join(process.cwd(), targetFilePath);
//
try {
await fs.access(sourceFilePath);
} catch (error) {
console.error(`Error accessing file: ${sourceFilePath}`);
throw error;
}
try {
await fs.copyFile(sourceFilePath, targetFilePath);
} catch (error) {
console.error(`Error renaming file: ${sourceFilePath} -> ${targetFilePath}`);
throw error;
}
let existsNewFile: boolean;
try {
await fs.access(targetFilePath);
existsNewFile = true;
} catch (error) {
existsNewFile = false;
}
if (!existsNewFile) {
throw new Error(`Failed to rename file: ${sourceFilePath} to ${targetFilePath}`);
}
}
export async function deleteFile(fileName: string): Promise<void> {
const path = require('path');
try {
let filePath = path.join(PATH_FILES_TO_IMPORT, fileName);
filePath = path.join(process.cwd(), filePath);
await unlink(filePath);
console.log(`File deleted: ${filePath}`);
} catch (error) {
console.error(`Error deleting file: ${fileName}. Error: ${error}`);
}
}
export function appendRandomNumberToFilename(filename: string) {
// Generate a random number between 100 and 999
const randomNumber = Math.floor(Math.random() * (999 - 100 + 1)) + 100;
// Extract the extension from the filename
const extensionIndex = filename.lastIndexOf('.');
const extension = extensionIndex !== -1 ? filename.slice(extensionIndex) : '';
// Remove the extension from the original filename
const basename = extensionIndex !== -1 ? filename.slice(0, extensionIndex) : filename;
// Append the random number and the extension to form the new filename
return `${basename}${randomNumber}${extension}`;
}
export function getMembershipTypeById(id: string) {
const item = MEMBERSHIP_TYPES.find(item => item.id === id);
return item ? item.type : null;
}

BIN
tests/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,38 @@
import { test, expect, Page } from '@playwright/test'
import { LogInPage } from '../src/page-objects/COMM_LoginPage.js'
import { loginAsAdmin, loginAsUser } from '../src/steps';
import { ADMIN_EMAIL, KO_ADMIN_K, KO_USER_K, URL_IDHUB, URL_PASS_RESET, USER1_EMAIL } from '../src/constants/env_constants.js';
test.describe('Admin login functionality', () => {
test('Successful login as admin', async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB);
await expect.soft(page).toHaveTitle('Dashboard IdHub');
})
test('Unsuccessful login as admin', async ({ page }) => {
const loginPage = new LogInPage(page)
await loginPage.visit(URL_IDHUB);
await loginPage.login(ADMIN_EMAIL, KO_ADMIN_K)
await expect.soft(loginPage.errorMessageIsValid()).toBeTruthy();
})
test('Navigate to Forgot password page from login page', async ({ page }) => {
const loginPage = new LogInPage(page)
await loginPage.visitForgotPassword(URL_PASS_RESET)
await expect.soft(page).toHaveTitle('Password reset IdHub');
})
})
test.describe('User login functionality', () => {
test('Successful login as user', async ({ page }) => {
await loginAsUser(page, USER1_EMAIL, URL_IDHUB);
await expect.soft(page).toHaveTitle('Dashboard IdHub');
//TODO: check email at the right corner
})
test('Unsuccessful login as user', async ({ page }) => {
const loginPage = new LogInPage(page)
await loginPage.visit(URL_IDHUB);
await loginPage.login(USER1_EMAIL, KO_USER_K)
await expect.soft(loginPage.errorMessageIsValid()).toBeTruthy();
})
})

View File

@ -0,0 +1,78 @@
import { test, expect } from '@playwright/test'
import { LeftMenuAdminPage } from '../src/page-objects/AD_LeftMenuAdminPage'
import { ViewUsersPage } from '../src/page-objects/AD_ViewUsersPage';
import { BasicUserInfoSectionPage } from '../src/page-objects/AD_BasicUserInfoSectionInPage';
import { ViewRolesPage } from '../src/page-objects/AD_ViewRolesPage';
import { ViewServicesPage } from '../src/page-objects/AD_ViewServicesPage';
import { clickAddUserOnLeftMenu, clickDashboardOnLeftMenu, clickManageRolesOnLeftMenu, clickManageServicesOnLeftMenu, clickViewUsersOnLeftMenu, loginAsAdmin } from '../src/steps';
import { URL_IDHUB } from '../src/constants/env_constants';
/**
* Check the navigation options on the Left Menu
*/
test.describe('Leftside Menu navigation test', () => {
test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB);
})
test('LEFTMENU -> Dashboard', async ({ page }) => {
await clickDashboardOnLeftMenu(page)
await expect(page).toHaveTitle('Dashboard IdHub');
})
test('LEFTMENU -> Users', async ({ page }) => {
await clickViewUsersOnLeftMenu(page);
await expect(page).toHaveTitle('User management IdHub');
//Check titles
const viewUsersPage = new ViewUsersPage(page);
expect(await viewUsersPage.getPrimaryTitle()).toEqual("User management");
expect(await viewUsersPage.getSecondaryTitle()).toEqual(" View users");
//Navigate to "Add user"
const basicInfoPage = new BasicUserInfoSectionPage(page);
await clickAddUserOnLeftMenu(page);
//Check titles
expect(await basicInfoPage.getPrimaryTitleText()).toEqual("User management");
expect(await basicInfoPage.getSecondaryTitleText()).toEqual(" Add user");
})
test('LEFTMENU -> Roles and Services', async ({ page }) => {
//Navigate to "Roles"
const leftMenu = new LeftMenuAdminPage(page);
(await leftMenu.getRolesLink()).click();
//Navigate to "Manage roles"
await clickManageRolesOnLeftMenu(page);
await expect(page).toHaveTitle('Access control management IdHub');
//Check titles
const viewRolesPage = new ViewRolesPage(page);
expect(await viewRolesPage.getPrimaryTitle()).toEqual("Access control management");
expect(await viewRolesPage.getSecondaryTitle()).toEqual("Manage roles");
//Navigate to "Manage services"
await clickManageServicesOnLeftMenu(page);
await expect(page).toHaveTitle('Access control management IdHub');
//Check titles
const viewServicesPage = new ViewServicesPage(page);
expect(await viewServicesPage.getPrimaryTitle()).toEqual("Access control management");
expect(await viewServicesPage.getSecondaryTitle()).toEqual("Manage services");
})
//TODO: credentials, templates....
test.skip('LEFTMENU -> Credentials', async ({ page }) => {
})
test.skip('LEFTMENU -> Templates', async ({ page }) => {
})
test.skip('LEFTMENU -> Data', async ({ page }) => {
})
})

View File

@ -0,0 +1,325 @@
import { test, expect, Page } from '@playwright/test'
import { BasicUserInfoSectionPage } from '../src/page-objects/AD_BasicUserInfoSectionInPage'
import { LeftMenuAdminPage } from '../src/page-objects/AD_LeftMenuAdminPage'
import { ViewUsersPage } from '../src/page-objects/AD_ViewUsersPage'
import { AddMembershipPage } from '../src/page-objects/AD_AddMembershipPage'
import { UserPersonalInformationPage } from '../src/page-objects/AD_UserPersonalInformationPage'
import { MembershipSectionPage } from '../src/page-objects/AD_MembershipSectionPage'
import { testingUsers, users } from '../src/data_stores/usersDataInitialization'
import { createUsersRandomList } from '../src/utils'
import { URL_IDHUB } from '../src/constants/env_constants'
import { checkIfTheInformationIsValid, clickAddUserOnLeftMenu, clickViewUsersOnLeftMenu, gotoBasicInfoPageOfTheUser, loginAsAdmin } from '../src/steps'
/**
* Testing Administrator's interface - USERS section functionality
*/
test.describe('USER Section Tests', () => {
test.beforeEach(async ({ page }) => { //executed at the beginning of each test
const leftMenu = new LeftMenuAdminPage(page);
await loginAsAdmin(page, URL_IDHUB);
(await leftMenu.getUsersLink()).click()
})
/**
* UPDATE the basic data for default testing users user1..user5
*/
test('USERS -> Modify personal data for testing users (user<#>.example.org) with meaningful basic data', async ({ page }) => {
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const userPersonalInformationPage = new UserPersonalInformationPage(page);
for (let user of testingUsers) {
console.log("Modifying user: ", user)
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await basicInfoPage.updateUserBasicInfo(user.firstName, user.lastName, user.email, false);
console.log("Updated basic data for user: ", user.email)
}
});
/**
* Add membership data for default testing users user1..user5
*/
test('USERS -> Modify membership data for testing users (user<#>.example.org)', async ({ page }) => {
// Initialize pages
const userPersonalInformationPage = new UserPersonalInformationPage(page);
const membershipSectionPage = new MembershipSectionPage(page);
const addMembershipPage = new AddMembershipPage(page);
for (let user of testingUsers) {
console.log("Modifying membership for user: ", user)
// Update the membership
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await membershipSectionPage.selectAddMembershipButton();
await addMembershipPage.addNewUserMembership('2', '2022-01-12', '2026-01-12')
console.log("Create a new membership for user: ", user.email)
// Validate the updated user data
await gotoBasicInfoPageOfTheUser(page, user);
expect(await membershipSectionPage.membershipExist(page, '2')).toBeTruthy()
}
});
/**
* Assign user5 as Idhub administrator
*/
test('USERS -> Modify personal data -> Assign user5 as Idhub administrator ', async ({ page }) => {
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const userPersonalInformationPage = new UserPersonalInformationPage(page);
console.log("Modifying user: ", testingUsers[4]);
let user = testingUsers[4];
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await basicInfoPage.updateUserBasicInfo(user.firstName, user.lastName, user.email, true);
console.log("Updated basic data for user: ", user.email)
});
/**
* Create a list of random users
* ADD the USER (basic info) to the idhub
* Validate infomation after addition
*/
test.skip('USERS -> Add user: Add random users with basic data', async ({ page }) => {
const randomUsers = createUsersRandomList(6)
for (let user of randomUsers) {
console.log(user)
}
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const membershipPage = new AddMembershipPage(page);
// Navigate to the Add User page
const { addUserLink } = await clickAddUserOnLeftMenu(page);
// Add the list of users
for (let user of randomUsers) {
await basicInfoPage.addUserBasicInfo(user.firstName, user.lastName, user.email, false);
expect(await membershipPage.alertUserCreationMessageIsValid()).toBeTruthy();
await membershipPage.addUserMembership(user.membershipType, user.startDate, user.endDate);
await addUserLink.click();
}
// Check if the users are visible in 'View users' panel
const viewUsersPage = new ViewUsersPage(page);
const { viewUsersLink } = await clickViewUsersOnLeftMenu(page);
for (let user of randomUsers) {
expect(await viewUsersPage.userExists(user.email)).toBeTruthy();
await viewUsersLink.click();
}
});
/**
* ADD a user (basic information) and validate it after addition
* UPDATE their basic information and validate it after modification
* DELETE the USER added before
*/
test('USERS -> Update basic user information for a user', async ({ page }) => {
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const userPersonalInformationPage = new UserPersonalInformationPage(page);
const membershipPage = new AddMembershipPage(page);
// Define user data
let user = {
firstName: 'James',
lastName: 'Swift',
email: 'james.swift@example.org',
membershipType: '1',
startDate: '2023-01-01',
endDate: '2024-01-01'
};
// Generate updated user data
const fn = user.firstName.concat(' Junior');
const ln = user.lastName.concat(' Anderson');
const em = fn.toLowerCase().replace(/\s/g, '') + '.' + ln.toLowerCase().replace(/\s/g, '') + '@example.org';
let updateData = {
firstName: fn,
lastName: ln,
email: em,
membershipType: user.membershipType,
startDate: user.startDate,
endDate: user.endDate
};
// Navigate to the Add User page
const { addUserLink } = await clickAddUserOnLeftMenu(page);
// Create a new user
await basicInfoPage.addUserBasicInfo(user.firstName, user.lastName, user.email, false);
expect(await membershipPage.alertUserCreationMessageIsValid()).toBeTruthy();
await membershipPage.addUserMembership(user.membershipType, user.startDate, user.endDate);
await addUserLink.click();
console.log("Created user: ", user.email)
// Validate the newly created user
await gotoBasicInfoPageOfTheUser(page, user);
await checkIfTheInformationIsValid(page, user);
// Update the user data
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await basicInfoPage.updateUserBasicInfo(updateData.firstName, updateData.lastName, updateData.email, false);
console.log("Updated basic data for user: ", user.email)
// Validate the updated user data
await gotoBasicInfoPageOfTheUser(page, updateData);
await checkIfTheInformationIsValid(page, updateData);
//Delete the user
await gotoBasicInfoPageOfTheUser(page, updateData);
await userPersonalInformationPage.deleteUser(page);
console.log("Deleted user: ", user.email)
});
/**
* ADD a user (basic info) and membership
* UPDATE the MEMBERSHIP information and validate after modification
* ADD a second MEMBERSHIP and validate
* DELETE the first added MEMBERSHIP and validate
* Delete the user
*/
test('USERS -> Add, modify, delete a membershipType', async ({ page }) => {
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const userPersonalInformationPage = new UserPersonalInformationPage(page);
const addMembershipPage = new AddMembershipPage(page);
const membershipSectionPage = new MembershipSectionPage(page);
// Navigate to the Add User page
const { addUserLink } = await clickAddUserOnLeftMenu(page);
// Define user data
let user = {
firstName: 'Caroline',
lastName: 'Harari',
email: 'caroline.harari' + '@example.org',
membershipType: '1',
startDate: '2023-01-01',
endDate: '2024-01-01'
};
let updateData = {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
membershipType: '2',
startDate: '2024-01-04',
endDate: '2025-04-04'
};
// Create a new user
await basicInfoPage.addUserBasicInfo(user.firstName, user.lastName, user.email, false);
expect(await addMembershipPage.alertUserCreationMessageIsValid()).toBeTruthy();
await addMembershipPage.addUserMembership(user.membershipType, user.startDate, user.endDate);
await addUserLink.click();
console.log("Created user: ", user.email)
// Validate the newly created user
await gotoBasicInfoPageOfTheUser(page, user);
await checkIfTheInformationIsValid(page, user);
// Update the membership
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await membershipSectionPage.gotoModifyMembershipPage(page, user.membershipType);
await addMembershipPage.updateExistingUserMembership(page, user.membershipType, updateData.membershipType, updateData.startDate, updateData.endDate)
console.log("Updated membership for user: ", user.email)
// Validate the updated user data
await gotoBasicInfoPageOfTheUser(page, updateData);
await checkIfTheInformationIsValid(page, updateData);
//Add a second membership
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await membershipSectionPage.selectAddMembershipButton();
await addMembershipPage.addNewUserMembership('3', '2022-01-12', '2025-01-12');
console.log("Updated with second membership for user: ", user.email);
// Validate the updated user data
await gotoBasicInfoPageOfTheUser(page, updateData);
expect(await membershipSectionPage.membershipExist(page, '3')).toBeTruthy()
//Delete the first membership
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.selectModifyUser();
await membershipSectionPage.gotoDeleteSpecificMembership(page, updateData.membershipType)
console.log("Deleted membership for user: ", user.email)
// Validate the updated user data
await gotoBasicInfoPageOfTheUser(page, updateData);
expect(await membershipSectionPage.membershipExist(page, updateData.membershipType)).toBeFalsy()
//Delete the user
await gotoBasicInfoPageOfTheUser(page, updateData);
await userPersonalInformationPage.deleteUser(page);
console.log("Deleted user: ", user.email)
});
/**
* Create a pre-defined group of users
* Add the users (basic information) to the idhub
* Validate the users information after addition
*/
test('USERS -> Add, Modify and delete users: Add a group of users, with basic info and membership, then delete', async ({ page }) => {
// Initialize pages
const basicInfoPage = new BasicUserInfoSectionPage(page);
const membershipPage = new AddMembershipPage(page);
const userPersonalInformationPage = new UserPersonalInformationPage(page);
for (let user of users) {
console.log(user)
}
// Navigate to the Add User page
const { addUserLink } = await clickAddUserOnLeftMenu(page);
// Add the list of users
for (let user of users) {
await basicInfoPage.addUserBasicInfo(user.firstName, user.lastName, user.email, false);
expect(await membershipPage.alertUserCreationMessageIsValid()).toBeTruthy();
await membershipPage.addUserMembership(user.membershipType, user.startDate, user.endDate);
await addUserLink.click();
}
// Check if the users are visible in 'View users'
const viewUsersPage = new ViewUsersPage(page);
const { viewUsersLink } = await clickViewUsersOnLeftMenu(page);
for (let user of users) {
expect(await viewUsersPage.userExists(user.email)).toBeTruthy();
await viewUsersLink.click();
}
// Delete all the users in the list
await clickViewUsersOnLeftMenu(page);
for (let user of users) {
await gotoBasicInfoPageOfTheUser(page, user);
await userPersonalInformationPage.deleteUser(page);
}
await clickViewUsersOnLeftMenu(page);
for (let user of users) {
expect(await viewUsersPage.userExists(user.email)).toBeFalsy();
}
});
})

View File

@ -0,0 +1,310 @@
import { test, expect} from '@playwright/test'
import { TemplatesPage } from '../src/page-objects/AD_TemplatesPage'
import { ViewImportedDataPage } from '../src/page-objects/AD_ViewImportedDataPage'
import { ImportDataPage } from '../src/page-objects/AD_ImportDataPage'
import { checkFileName, clickDataOnLeftMenu, clickTemplatesOnLeftMenu, loadIfJsonSchemaNotAvailable, loginAsAdmin, loginAsUser, gotoViewEnabledCredential, expectedCredentialSubjectForUser } from '../src/steps';
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js'
import { fail } from 'assert'
import { URL_IDHUB, USER1_EMAIL } from '../src/constants/env_constants';
import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, FILE_TO_IMPORT_FVC, FILE_TO_IMPORT_FVC_EMPTY, FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS, JSON_SCHEMA_FVC, JSON_SCHEMA_ID_FVC, PATH_FILES_TO_IMPORT, SCHEMA_FVC, SCHEMA_TYPE_FVC } from '../src/constants/constants';
import { deleteFile } from '../src/utils';
/**
* Checking template section: view the lists of templates, import schema, delete schema, view json
*/
test.describe('TEMPLATES Section Tests ', () => {
test.beforeEach(async ({ page }) => { //este se ejecutará antes de cada test
await loginAsAdmin(page, URL_IDHUB);
})
/**
* For every row in the templates view,
* extract the name of the template schema (second cell) and compare it against the
* las element of the $id in the corresponding json file.
*/
test('TEMPLATES -> View credential templates -> compare each template name against last element of $id in JSON', async ({ page }) => {
// Navigate to the page with the table
const templatesPage = new TemplatesPage(page);
await clickTemplatesOnLeftMenu(page);
expect(await templatesPage.checkSchemaNamesAgainstCorrespondingJSON(page)).toBeTruthy();
})
/**
* Check a specific template in the list of templates
* If the template schema is not available, the schema is imported
* Once the template schema is in the list, verify the element id in the json file
*/
test('TEMPLATES -> View schema/import for Financial Vulnerability Credential', async ({ page }) => {
const templatesPage = new TemplatesPage(page);
//Load the schema if it is not loaded
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
//validate $id in the json template
await templatesPage.gotoViewSchemaPage(JSON_SCHEMA_FVC);
const jsonContent = await page.evaluate(() => JSON.parse(document.body.innerText));
// Check if the JSON elements exist
expect(jsonContent["$id"]).toBe(JSON_SCHEMA_ID_FVC);
})
/**
* Check a specific template in the list of templates
* If the template schema is not available, the schema is imported and verify the operation
* Try to delete the schema, but cancel the operation
* Try to delete the schema, confirm the operation and verify the operation
*/
test('TEMPLATES -> Delete schema', async ({ page }) => {
//Access the specific template schema
const templatesPage = new TemplatesPage(page);
//check if the schema is imported
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
/*Try to delete the schema and then cancel in the modal*/
await templatesPage.gotoDeleteAndCancelInModal(JSON_SCHEMA_FVC);
/*Verify the schema was imported*/
expect(await templatesPage.schemaIsAvailableInView(JSON_SCHEMA_FVC)).toBeTruthy();
/*Try to delete the schema and thenn confirm the operation*/
await templatesPage.gotoDeleteAndConfirmInModal(JSON_SCHEMA_FVC);
/*Verify the schema was deleted*/
expect(await templatesPage.schemaIsAvailableInView(JSON_SCHEMA_FVC)).toBeFalsy();
})
})
/**
* Checking data section: view the lists of files imported, import data, delete...
*/
test.describe('DATA Section Tests', () => {
test.beforeEach(async ({ page }) => { //este se ejecutará antes de cada test
await loginAsAdmin(page, URL_IDHUB);
})
/**
* Load of an excel file - Happy Path:
* Check if file was loaded before. If yes, copy the original file (financial-vulnerability-credential.xlxs) to a new valid with random name
* If the template schema (associated with the type of credential to be enabled) is not available, import it and verify the operation
* Load the associated data file (xls) and check that appears in the list of imported data files
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed,
* and the file appears in the imported files view.
*/
test('DATA -> Import data file - Happy path - Financial Vulnerability Credential ', async ({ page }) => {
const viewImportedDataPage = new ViewImportedDataPage(page);
const importDataPage = new ImportDataPage(page);
await clickDataOnLeftMenu(page);
// If a file with a FILE_TO_IMPORT was previouly loaded, rename it
let fileName = FILE_TO_IMPORT_FVC;
fileName = await checkFileName(page, fileName);
// Check if the json schema associated with the file is loaded to be used
// If not, load it in templates
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
//Go to the Data option on leftmenu
await clickDataOnLeftMenu(page);
//Select the import button and go to the import data page
await viewImportedDataPage.gotoImportDataPage();
//Import excel file to enable a 'Financial vulnerability credential'for testing users
console.log("File to import: ", fileName, " with schema: ", SCHEMA_FVC);
await importDataPage.importFile(SCHEMA_FVC, PATH_FILES_TO_IMPORT + fileName, "Default");
let actual = await viewImportedDataPage.alertFileImportedSuccessfully();
//Rename the default excel file with the original name FILE_TO_IMPORT in the directoy PATH_FILES_TO_IMPORT
if (fileName != FILE_TO_IMPORT_FVC) {
await deleteFile(fileName);
}
if (actual) {
//Check if the file is in the list of imported files sucessfully
await clickDataOnLeftMenu(page);
await page.getByRole('link', { name: 'Date' }).click();
await page.getByRole('link', { name: 'Date' }).click();
expect(await viewImportedDataPage.fileIsAvailableInView(fileName)).toBeTruthy();
expect(await viewImportedDataPage.isFileSuccessfullyLoaded(fileName)).toBeTruthy();
} else {
console.log("Unexpected result loading the file. Test failed.")
fail();
}
});
/**
* Load of an excel file - Sad Path:
* Check if file was loaded before. If yes, copy the original to the new one with random name.
* If the template schema (associated with the type of credential to be enabled) is not available, import it and verify the operation
* Try to load a well-formatted excel file but without data.
* Expected behavior: The error message: "The file you try to import is empty"
*/
test('DATA -> Import data file - Sad path (file well formatted but empty) - Financial Vulnerability Credential ', async ({ page }) => {
const viewImportedDataPage = new ViewImportedDataPage(page);
const importDataPage = new ImportDataPage(page);
await clickDataOnLeftMenu(page);
// If a file with a FILE_TO_IMPORT was previously loaded, rename it
let fileName = await checkFileName(page, FILE_TO_IMPORT_FVC_EMPTY);
// Check if the json schema associated with the file is loaded to be used
// If not, load it in templates
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
//Go to the Data option on leftmenu
await clickDataOnLeftMenu(page);
//Select the import button and go to the import data page
await viewImportedDataPage.gotoImportDataPage();
//Import excel file to enable a 'Financial vulnerability credential'. The file has no data.
console.log("File to import: ", fileName, " with schema: ", SCHEMA_FVC);
await importDataPage.importFile(SCHEMA_FVC, PATH_FILES_TO_IMPORT + fileName, "Default");
let actual = await importDataPage.alertFileImportedUnsuccessfully(ALERT_FILE_TO_IMPORT_IS_EMPTY);
//Rename the default excel file with the original name FILE_TO_IMPORT in the directoy PATH_FILES_TO_IMPORT
if (fileName != FILE_TO_IMPORT_FVC_EMPTY) {
await deleteFile(fileName);
}
if (actual) {
//Check if the file is in the list of imported files as failed
await clickDataOnLeftMenu(page);
await page.getByRole('link', { name: 'Date' }).click();
await page.getByRole('link', { name: 'Date' }).click();
expect(await viewImportedDataPage.fileIsAvailableInView(fileName)).toBeTruthy();
expect(await viewImportedDataPage.isFileSuccessfullyLoaded(fileName)).toBeFalsy();
} else {
console.log("Unexpected result loading an empty file. Test failed.");
fail();
}
});
test('DATA -> Import data file - Sad path (file bad formatted, without required columns) - Financial Vulnerability Credential ', async ({ page }) => {
const viewImportedDataPage = new ViewImportedDataPage(page);
const importDataPage = new ImportDataPage(page);
await clickDataOnLeftMenu(page);
// If a file with a FILE_TO_IMPORT was previously loaded, rename it
let fileName = await checkFileName(page, FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS);
// Check if the json schema associated with the file is loaded to be used
// If not, load it in templates
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
//Go to the Data option on leftmenu
await clickDataOnLeftMenu(page);
//Select the import button and go to the import data page
await viewImportedDataPage.gotoImportDataPage();
//Import excel file to enable a 'Financial vulnerability credential'. The file has no data.
console.log("File to import: ", fileName, " with schema: ", SCHEMA_FVC);
await importDataPage.importFile(SCHEMA_FVC, PATH_FILES_TO_IMPORT + fileName, "Default");
let actual = await importDataPage.alertFileImportedUnsuccessfully(ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS);
//Rename the default excel file with the original name FILE_TO_IMPORT in the directoy PATH_FILES_TO_IMPORT
if (fileName != FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS) {
await deleteFile(fileName);
}
if (actual) {
//Check if the file is in the list of imported files as failed
await clickDataOnLeftMenu(page);
await page.getByRole('link', { name: 'Date' }).click();
await page.getByRole('link', { name: 'Date' }).click();
expect(await viewImportedDataPage.fileIsAvailableInView(fileName)).toBeTruthy();
expect(await viewImportedDataPage.isFileSuccessfullyLoaded(fileName)).toBeFalsy();
} else {
console.log("Unexpected result loading an empty file. Test failed.");
fail();
}
});
test('DATA -> Import data file - Sad path (file bad formatted, with alien columns) - Financial Vulnerability Credential ', async ({ page }) => {
const viewImportedDataPage = new ViewImportedDataPage(page);
const importDataPage = new ImportDataPage(page);
await clickDataOnLeftMenu(page);
// If a file with a FILE_TO_IMPORT was previously loaded, rename it
let fileName = await checkFileName(page, FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS);
// Check if the json schema associated with the file is loaded to be used
// If not, load it in templates
await loadIfJsonSchemaNotAvailable(page, JSON_SCHEMA_FVC);
//Go to the Data option on leftmenu
await clickDataOnLeftMenu(page);
//Select the import button and go to the import data page
await viewImportedDataPage.gotoImportDataPage();
//Import excel file to enable a 'Financial vulnerability credential'. The file has no data.
console.log("File to import: ", fileName, " with schema: ", SCHEMA_FVC);
await importDataPage.importFile(SCHEMA_FVC, PATH_FILES_TO_IMPORT + fileName, "Default");
let actual = await importDataPage.alertFileImportedUnsuccessfully(ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS);
//Rename the default excel file with the original name FILE_TO_IMPORT in the directoy PATH_FILES_TO_IMPORT
if (fileName != FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS) {
await deleteFile(fileName);
}
if (actual) {
//Check if the file is in the list of imported files as failed
await clickDataOnLeftMenu(page);
await page.getByRole('link', { name: 'Date' }).click();
await page.getByRole('link', { name: 'Date' }).click();
expect(await viewImportedDataPage.fileIsAvailableInView(fileName)).toBeTruthy();
expect(await viewImportedDataPage.isFileSuccessfullyLoaded(fileName)).toBeFalsy();
} else {
console.log("Unexpected result loading an empty file. Test failed.");
fail();
}
});
}) //end describe
test.describe('USER Credentials Section Tests', () => {
test.beforeEach(async ({ page }) => { //este se ejecutará antes de cada test
await loginAsUser(page, USER1_EMAIL, URL_IDHUB);
})
/**
* Check if the user1 can visualize the credentials that has been enabled in "My Credentials"
* Check the fields displayed when user click "View" Credential
*/
test('USER Credentials -> My Credentials -> View enabled Financial Vulnerability Credential', async ({ page }) => {
// View the Financial Vulnerabilty Credential in status 'Enabled' for the user
const credentialStatus = "Enabled"
await gotoViewEnabledCredential(page, SCHEMA_TYPE_FVC);
const enabledCredentialView = new ViewCredentialPage(page);
//Check that required fields exist and have a valid value in the current enabled credential
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_FVC);
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_FVC);
expect(actualCredential).toEqual(expectedCredential);
});
}) //end describe
//Añadir test con otros tipos de credenciales

View File

@ -0,0 +1,35 @@
[
{
"id": "EV_USR_REGISTERED",
"text": "The user {email} was registered: name: {name}, last name: {lastname}",
"admin": "true",
"user": "no"
},
{
"id": "EV_USR_WELCOME",
"text": "Welcome. You has been registered: name: {name}, last name: {lastname}",
"admin": "no",
"user": "true"
},
{
"id": "EV_DATA_FILE_IMPORTED_BY_ADMIN",
"text": "A new file was imported by admin: File:{name} Date:{dd/mm/yyyy}",
"admin": "true",
"user": "no"
},
{
"id": "EV_CREDENTIAL_ENABLED",
"text": "The credential of type '{type} was enabled for user {email}",
"admin": "true",
"user": "no"
},
{
"id": "EV_CREDENTIAL_CAN_BE_REQUESTED",
"text": "The credential of type '{type} was enabled for user {email}",
"admin": "no",
"user": "yes"
}
]

BIN
vc_excel/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.