commit 57e500eb8ae8154f4ec845008f40b14f8e5897af Author: mildred Date: Mon Mar 4 20:57:54 2024 +0100 First commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..54ffbcf Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da26fdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/vc_schemas/ +.vscode +.github diff --git a/README.md b/README.md new file mode 100644 index 0000000..beebfda --- /dev/null +++ b/README.md @@ -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' 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' 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.] + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ff7d0d5 --- /dev/null +++ b/package-lock.json @@ -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 + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2d907af --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..55b8ad7 --- /dev/null +++ b/playwright.config.ts @@ -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, + // }, +}); diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..65a7894 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/constants/constants.ts b/src/constants/constants.ts new file mode 100644 index 0000000..b399189 --- /dev/null +++ b/src/constants/constants.ts @@ -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' } + ]; + diff --git a/src/constants/credential_fields.ts b/src/constants/credential_fields.ts new file mode 100644 index 0000000..39aa91e --- /dev/null +++ b/src/constants/credential_fields.ts @@ -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... +}; \ No newline at end of file diff --git a/src/constants/env_constants.ts b/src/constants/env_constants.ts new file mode 100644 index 0000000..3f579db --- /dev/null +++ b/src/constants/env_constants.ts @@ -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/" \ No newline at end of file diff --git a/src/data_stores/credentials_data_store.ts b/src/data_stores/credentials_data_store.ts new file mode 100644 index 0000000..122e21f --- /dev/null +++ b/src/data_stores/credentials_data_store.ts @@ -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', +} + diff --git a/src/data_stores/usersDataInitialization.ts b/src/data_stores/usersDataInitialization.ts new file mode 100644 index 0000000..5c00f20 --- /dev/null +++ b/src/data_stores/usersDataInitialization.ts @@ -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' + } + +]; \ No newline at end of file diff --git a/src/interfaces/User.ts b/src/interfaces/User.ts new file mode 100644 index 0000000..374ad27 --- /dev/null +++ b/src/interfaces/User.ts @@ -0,0 +1,8 @@ +export interface User { + firstName: string; + lastName: string; + email: string; + membershipType: string; + startDate: string; + endDate: string; +} \ No newline at end of file diff --git a/src/interfaces/credential_interfaces.ts b/src/interfaces/credential_interfaces.ts new file mode 100644 index 0000000..c82ed90 --- /dev/null +++ b/src/interfaces/credential_interfaces.ts @@ -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 \ No newline at end of file diff --git a/src/page-objects/AD_AddMembershipPage.ts b/src/page-objects/AD_AddMembershipPage.ts new file mode 100644 index 0000000..3f30a9a --- /dev/null +++ b/src/page-objects/AD_AddMembershipPage.ts @@ -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 { + + 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; + } + +} + diff --git a/src/page-objects/AD_BasicUserInfoSectionInPage.ts b/src/page-objects/AD_BasicUserInfoSectionInPage.ts new file mode 100644 index 0000000..9e3d220 --- /dev/null +++ b/src/page-objects/AD_BasicUserInfoSectionInPage.ts @@ -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; + } + } +} diff --git a/src/page-objects/AD_DashboardPage.ts b/src/page-objects/AD_DashboardPage.ts new file mode 100644 index 0000000..2995197 --- /dev/null +++ b/src/page-objects/AD_DashboardPage.ts @@ -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; + } + } + +} \ No newline at end of file diff --git a/src/page-objects/AD_ImportDataPage.ts b/src/page-objects/AD_ImportDataPage.ts new file mode 100644 index 0000000..a5965f3 --- /dev/null +++ b/src/page-objects/AD_ImportDataPage.ts @@ -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 { + 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; + } + } + +} + diff --git a/src/page-objects/AD_ImportTemplatePage.ts b/src/page-objects/AD_ImportTemplatePage.ts new file mode 100644 index 0000000..0d7edb6 --- /dev/null +++ b/src/page-objects/AD_ImportTemplatePage.ts @@ -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 { + 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; + } + } + +} \ No newline at end of file diff --git a/src/page-objects/AD_LeftMenuAdminPage.ts b/src/page-objects/AD_LeftMenuAdminPage.ts new file mode 100644 index 0000000..6dba4c2 --- /dev/null +++ b/src/page-objects/AD_LeftMenuAdminPage.ts @@ -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/"]'; + } +} \ No newline at end of file diff --git a/src/page-objects/AD_MembershipSectionPage.ts b/src/page-objects/AD_MembershipSectionPage.ts new file mode 100644 index 0000000..a4d76ff --- /dev/null +++ b/src/page-objects/AD_MembershipSectionPage.ts @@ -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 { + 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); + } + } +} diff --git a/src/page-objects/AD_TemplatesPage.ts b/src/page-objects/AD_TemplatesPage.ts new file mode 100644 index 0000000..85bbc6d --- /dev/null +++ b/src/page-objects/AD_TemplatesPage.ts @@ -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 { + + 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 { + 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; + } + } +} \ No newline at end of file diff --git a/src/page-objects/AD_UserPersonalInformationPage.ts b/src/page-objects/AD_UserPersonalInformationPage.ts new file mode 100644 index 0000000..f3ef16b --- /dev/null +++ b/src/page-objects/AD_UserPersonalInformationPage.ts @@ -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 { + + 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; + } + } + +} + + diff --git a/src/page-objects/AD_ViewImportedDataPage.ts b/src/page-objects/AD_ViewImportedDataPage.ts new file mode 100644 index 0000000..c231fa3 --- /dev/null +++ b/src/page-objects/AD_ViewImportedDataPage.ts @@ -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 { + + 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 { + 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 { + 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; + } + } + +} \ No newline at end of file diff --git a/src/page-objects/AD_ViewRolesPage.ts b/src/page-objects/AD_ViewRolesPage.ts new file mode 100644 index 0000000..7636fb4 --- /dev/null +++ b/src/page-objects/AD_ViewRolesPage.ts @@ -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; + } + } + +} \ No newline at end of file diff --git a/src/page-objects/AD_ViewServicesPage.ts b/src/page-objects/AD_ViewServicesPage.ts new file mode 100644 index 0000000..ee5bfbd --- /dev/null +++ b/src/page-objects/AD_ViewServicesPage.ts @@ -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;; + } +} \ No newline at end of file diff --git a/src/page-objects/AD_ViewUsersPage.ts b/src/page-objects/AD_ViewUsersPage.ts new file mode 100644 index 0000000..bb50bc6 --- /dev/null +++ b/src/page-objects/AD_ViewUsersPage.ts @@ -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 { + 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; + } + } + +} \ No newline at end of file diff --git a/src/page-objects/COMM_LoginPage.ts b/src/page-objects/COMM_LoginPage.ts new file mode 100644 index 0000000..427fbc9 --- /dev/null +++ b/src/page-objects/COMM_LoginPage.ts @@ -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 { + 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; + } + } +} + diff --git a/src/page-objects/US_LeftMenuUserPage.ts b/src/page-objects/US_LeftMenuUserPage.ts new file mode 100644 index 0000000..c36264e --- /dev/null +++ b/src/page-objects/US_LeftMenuUserPage.ts @@ -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 + } + + +} \ No newline at end of file diff --git a/src/page-objects/US_ViewCredentialPage.ts b/src/page-objects/US_ViewCredentialPage.ts new file mode 100644 index 0000000..e010da9 --- /dev/null +++ b/src/page-objects/US_ViewCredentialPage.ts @@ -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; + + } + +} diff --git a/src/page-objects/US_ViewMyCredentialsPage.ts b/src/page-objects/US_ViewMyCredentialsPage.ts new file mode 100644 index 0000000..8b1fd81 --- /dev/null +++ b/src/page-objects/US_ViewMyCredentialsPage.ts @@ -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 { + + // 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; + } + } + +} \ No newline at end of file diff --git a/src/steps.ts b/src/steps.ts new file mode 100644 index 0000000..8a9006c --- /dev/null +++ b/src/steps.ts @@ -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(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 { + 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']) +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..1e3ed50 --- /dev/null +++ b/src/utils.ts @@ -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 { + 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; +} \ No newline at end of file diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/tests/.DS_Store differ diff --git a/tests/00-COMM-loginFunctionality.spec.ts b/tests/00-COMM-loginFunctionality.spec.ts new file mode 100644 index 0000000..d7e3bf0 --- /dev/null +++ b/tests/00-COMM-loginFunctionality.spec.ts @@ -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(); + }) + +}) \ No newline at end of file diff --git a/tests/01-AD-leftmenuNavigation.spec.ts b/tests/01-AD-leftmenuNavigation.spec.ts new file mode 100644 index 0000000..b668b6e --- /dev/null +++ b/tests/01-AD-leftmenuNavigation.spec.ts @@ -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 }) => { + }) + +}) \ No newline at end of file diff --git a/tests/02-AD-manageUsers.spec.ts b/tests/02-AD-manageUsers.spec.ts new file mode 100644 index 0000000..54599cf --- /dev/null +++ b/tests/02-AD-manageUsers.spec.ts @@ -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(); + } + }); + +}) diff --git a/tests/03-COMM-templatesAndDataFiles.spec.ts b/tests/03-COMM-templatesAndDataFiles.spec.ts new file mode 100644 index 0000000..650b884 --- /dev/null +++ b/tests/03-COMM-templatesAndDataFiles.spec.ts @@ -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 \ No newline at end of file diff --git a/tests/events_messages.json b/tests/events_messages.json new file mode 100644 index 0000000..5dee06d --- /dev/null +++ b/tests/events_messages.json @@ -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" + } + + ] \ No newline at end of file diff --git a/vc_excel/.DS_Store b/vc_excel/.DS_Store new file mode 100644 index 0000000..d58d5f2 Binary files /dev/null and b/vc_excel/.DS_Store differ diff --git a/vc_excel/course-credential-empty.xlsx b/vc_excel/course-credential-empty.xlsx new file mode 100644 index 0000000..8ffea58 Binary files /dev/null and b/vc_excel/course-credential-empty.xlsx differ diff --git a/vc_excel/course-credential.xlsx b/vc_excel/course-credential.xlsx new file mode 100644 index 0000000..8ffea58 Binary files /dev/null and b/vc_excel/course-credential.xlsx differ diff --git a/vc_excel/device-purchase-empty.xlsx b/vc_excel/device-purchase-empty.xlsx new file mode 100644 index 0000000..a9f9c8a Binary files /dev/null and b/vc_excel/device-purchase-empty.xlsx differ diff --git a/vc_excel/device-purchase.xlsx b/vc_excel/device-purchase.xlsx new file mode 100644 index 0000000..a9f9c8a Binary files /dev/null and b/vc_excel/device-purchase.xlsx differ diff --git a/vc_excel/e-operator-claim-empty.xlsx b/vc_excel/e-operator-claim-empty.xlsx new file mode 100644 index 0000000..2909e10 Binary files /dev/null and b/vc_excel/e-operator-claim-empty.xlsx differ diff --git a/vc_excel/e-operator-claim.xlsx b/vc_excel/e-operator-claim.xlsx new file mode 100644 index 0000000..2909e10 Binary files /dev/null and b/vc_excel/e-operator-claim.xlsx differ diff --git a/vc_excel/federation-membership-empty.xlsx b/vc_excel/federation-membership-empty.xlsx new file mode 100644 index 0000000..e2cc7b5 Binary files /dev/null and b/vc_excel/federation-membership-empty.xlsx differ diff --git a/vc_excel/federation-membership.xlsx b/vc_excel/federation-membership.xlsx new file mode 100644 index 0000000..e2cc7b5 Binary files /dev/null and b/vc_excel/federation-membership.xlsx differ diff --git a/vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx b/vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx new file mode 100644 index 0000000..88909c1 Binary files /dev/null and b/vc_excel/financial-vulnerability-alienUser-with-user5Data.xlsx differ diff --git a/vc_excel/financial-vulnerability-empty.xlsx b/vc_excel/financial-vulnerability-empty.xlsx new file mode 100644 index 0000000..ed6c91a Binary files /dev/null and b/vc_excel/financial-vulnerability-empty.xlsx differ diff --git a/vc_excel/financial-vulnerability-with-alien-columns.xlsx b/vc_excel/financial-vulnerability-with-alien-columns.xlsx new file mode 100644 index 0000000..72b31bb Binary files /dev/null and b/vc_excel/financial-vulnerability-with-alien-columns.xlsx differ diff --git a/vc_excel/financial-vulnerability-with-required-empty.xlsx b/vc_excel/financial-vulnerability-with-required-empty.xlsx new file mode 100644 index 0000000..f8f6cda Binary files /dev/null and b/vc_excel/financial-vulnerability-with-required-empty.xlsx differ diff --git a/vc_excel/financial-vulnerability-without-required-columns.xlsx b/vc_excel/financial-vulnerability-without-required-columns.xlsx new file mode 100644 index 0000000..5b3a358 Binary files /dev/null and b/vc_excel/financial-vulnerability-without-required-columns.xlsx differ diff --git a/vc_excel/financial-vulnerability.xlsx b/vc_excel/financial-vulnerability.xlsx new file mode 100644 index 0000000..33b8db0 Binary files /dev/null and b/vc_excel/financial-vulnerability.xlsx differ diff --git a/vc_excel/membership-card-empty.xlsx b/vc_excel/membership-card-empty.xlsx new file mode 100644 index 0000000..f6eb736 Binary files /dev/null and b/vc_excel/membership-card-empty.xlsx differ diff --git a/vc_excel/membership-card.xlsx b/vc_excel/membership-card.xlsx new file mode 100644 index 0000000..4531ffe Binary files /dev/null and b/vc_excel/membership-card.xlsx differ