Adding request issuance for all types of credentials

This commit is contained in:
mildred 2024-03-28 20:22:36 +01:00
parent 657c7c962a
commit 4a52f28cff
20 changed files with 759 additions and 432 deletions

View File

@ -42,8 +42,6 @@ for the final product.
To get started with the IdHub testing project, you need to have the following prerequisites installed on your system: 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/). - **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 ### Installation
@ -51,10 +49,9 @@ To clone the repository and install the project dependencies, follow these steps
1. Open your terminal or command prompt. 1. Open your terminal or command prompt.
2. Navigate to the directory where you want to clone the project. 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. 3. Run `git clone https://gitea.pangea.org/trustchain-oc1-orchestral/IdHub_E2E_testing.git` to clone the repository.
4. Navigate into the project directory using `cd idHub_testing`. 4. Navigate into the project directory using `cd idHub_testing`.
5. Install the project dependencies: 5. Installing playwright using `npm install -g playwright`
- Installing global dependencies: `npm install -g playwright`
- Installing project dependencies: `npm install` - Installing project dependencies: `npm install`
- Setting up environment variables: [TODO] - Setting up environment variables: [TODO]
@ -73,7 +70,7 @@ This command runs the test suite using Playwright, executing all tests defined i
### Writing Tests: When writing new tests, it's important to follow the established test structure. Here's a brief guide: ### 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`. - **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`. - **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, 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. - **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: An example of a simple test might look like this:
@ -115,19 +112,32 @@ The 'loginAs<User>' function in `steps.ts` use the 'LoginPage' page object, wher
## Project Directory Structure ## Project Directory Structure
### src directory ### src/constants directory
- **constants directory:**: Describe....
- **data_stores directory**: Describe... The **constants.ts** file defines a collection of constants used for various purposes within a project, including file paths, JSON schema names and URLs, Excel file names for testing, schema names and types as they appear in the Idhub Admin interface, user alert messages, status types, and membership types. These constants are likely used across the project for consistent references to file paths, schema identifiers, and other configuration details, ensuring that changes to these values only need to be made in one place.
- **interfaces directory**: Describe
- **page-objects directory**: Describe The **credentials_fields.ts** file is designed to store and manage constant values related to the fields or properties of the diferent types of credentials defined in the pilots.
- **steps.ts**: Describe
- **utils.ts**: Describe ### src/data_stores directory
The **credential_data_store.ts** file serves as a data store for different types of credentials, each represented as an array of objects. This structure is particularly useful for testing or simulating a database of credentials without the need for a backend service. Each type of credential, such as FinancialVulnerabilityCredential_data, MembershipCard_data, FederationMembership_data, CourseCredential_data, and EOperatorClaim_data, is populated with example data. The file also includes a section for undefined credential data (CREDENTIAL_TYPE_DATASTORE_UNDEFINED), as mock data for handle cases where credential data is not available.
The **users_data_store.ts** file contains simulated user data for testing purposes.
### src/interfaces directory
The **credential_interfaces.ts** file defines the interfaces that represent the structure of different types of credentials. Each interface specifies the fields that a credential object must have, along with the type of each field.
### src/page-objects directory
Here there are the Page Objects files corresponding to each tested page in the application. 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).
### src/steps.ts file contains reusable steps within that helps to maintain the tests and promotes code reuse.
### tests directory ### 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. 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 ### vc_excel directory
describe A set of excel data files for each type of credentials, used to test the data file import functionality to enable credentials.
## Pipeline integration ## Pipeline integration

View File

@ -87,6 +87,8 @@ export const ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY_EOC = "line 3: 'leg
export const ALERT_USER_CREATED_SUCESSFULLY_MESSAGE = 'The account was created successfully' 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.' export const ERROR_INCORRECT_EMAIL_PASSWORD = 'Please enter a correct Email address and password. Note that both fields may be case-sensitive.'
export const ENABLED_ST = "Enabled"
export const ISSUED_ST = "Issued"
//Memberships //Memberships
export const MEMBERSHIP_TYPES = [ export const MEMBERSHIP_TYPES = [

View File

@ -13,7 +13,7 @@ export const CREDENTIAL_TYPES_FIELDS_LIST = {
'financialVulnerabilityScore', 'financialVulnerabilityScore',
'amountCoveredByOtherAids', 'amountCoveredByOtherAids',
'connectivityOptionList', 'connectivityOptionList',
'assessmentDate' 'assessmentDate',
] as string[], ] as string[],
"MembershipCard": [ "MembershipCard": [

View File

@ -22,13 +22,12 @@ CREDENTIAL_TYPES_DATA_STORE.FinancialVulnerabilityCredential_data = [{
financialVulnerabilityScore: '4', financialVulnerabilityScore: '4',
amountCoveredByOtherAids: '0', amountCoveredByOtherAids: '0',
connectivityOptionList: 'Fibra', connectivityOptionList: 'Fibra',
assessmentDate: '2024-01-01' assessmentDate: '2024-01-01',
}, },
{ {
firstName: 'Ludwing Van', firstName: 'Ludwing Van',
lastName: 'Beethoven', lastName: 'Beethoven',
email: 'user2@example.org', email: 'user2@example.org',
phoneNumber: 'undefined',
identityDocType: 'DNI', identityDocType: 'DNI',
identityNumber: '76677667Q', identityNumber: '76677667Q',
streetAddress: 'C/ Fontanals', streetAddress: 'C/ Fontanals',
@ -36,8 +35,7 @@ CREDENTIAL_TYPES_DATA_STORE.FinancialVulnerabilityCredential_data = [{
socialWorkerSurname: 'Ruíz', socialWorkerSurname: 'Ruíz',
financialVulnerabilityScore: '6', financialVulnerabilityScore: '6',
amountCoveredByOtherAids: '30', amountCoveredByOtherAids: '30',
connectivityOptionList: 'undefined', assessmentDate: '2024-01-03',
assessmentDate: '2024-01-03'
}, },
{ {
firstName: 'Franz', firstName: 'Franz',
@ -52,7 +50,7 @@ CREDENTIAL_TYPES_DATA_STORE.FinancialVulnerabilityCredential_data = [{
financialVulnerabilityScore: '8', financialVulnerabilityScore: '8',
amountCoveredByOtherAids: '10', amountCoveredByOtherAids: '10',
connectivityOptionList: 'Fibra', connectivityOptionList: 'Fibra',
assessmentDate: '2023-10-04' assessmentDate: '2023-10-04',
}, },
{ {
firstName: 'Barbara', firstName: 'Barbara',
@ -67,7 +65,7 @@ CREDENTIAL_TYPES_DATA_STORE.FinancialVulnerabilityCredential_data = [{
financialVulnerabilityScore: '9', financialVulnerabilityScore: '9',
amountCoveredByOtherAids: '0', amountCoveredByOtherAids: '0',
connectivityOptionList: 'Fibra', connectivityOptionList: 'Fibra',
assessmentDate: '2024-01-04' assessmentDate: '2024-01-04',
} }
]; ];
@ -86,7 +84,7 @@ CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
firstName: "Wolfgang Amadeus", firstName: "Wolfgang Amadeus",
lastName: "Mozart", lastName: "Mozart",
role: "Conductor", role: "Conductor",
email: "user1@example.org" email: "user1@example.org",
}, },
{ {
organisation: "Pangea", organisation: "Pangea",
@ -99,7 +97,7 @@ CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
firstName: "Ludwing Van", firstName: "Ludwing Van",
lastName: "Beethoven", lastName: "Beethoven",
role: "Composer", role: "Composer",
email: "user2@example.org" email: "user2@example.org",
}, },
{ {
organisation: "Pangea", organisation: "Pangea",
@ -113,7 +111,7 @@ CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
firstName: "Franz", firstName: "Franz",
lastName: "Schubert", lastName: "Schubert",
role: "Chamber musician", role: "Chamber musician",
email: "user3@example.org" email: "user3@example.org",
}, },
{ {
organisation: "Pangea", organisation: "Pangea",
@ -127,7 +125,7 @@ CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
firstName: "Barbara", firstName: "Barbara",
lastName: "Strozzi", lastName: "Strozzi",
role: "Composer and Singer", role: "Composer and Singer",
email: "user4@example.org" email: "user4@example.org",
}, },
{ {
organisation: "Pangea", organisation: "Pangea",
@ -141,7 +139,7 @@ CREDENTIAL_TYPES_DATA_STORE.MembershipCard_data = [
firstName: "Clara", firstName: "Clara",
lastName: "Schumann", lastName: "Schumann",
role: "Composer and Pianist", role: "Composer and Pianist",
email: "user5@example.org" email: "user5@example.org",
} }
]; ];
@ -238,7 +236,7 @@ CREDENTIAL_TYPES_DATA_STORE.CourseCredential_data = [
courseFramework: "European Qualifications Framework (EQF) Level 6", courseFramework: "European Qualifications Framework (EQF) Level 6",
courseCredits: "5", courseCredits: "5",
dateOfAssessment: "2020-12-20", dateOfAssessment: "2020-12-20",
evidenceAssessment: "final exam" evidenceAssessment: "final exam",
}, },
{ {
firstName: "Ludwing Van", firstName: "Ludwing Van",
@ -257,7 +255,7 @@ CREDENTIAL_TYPES_DATA_STORE.CourseCredential_data = [
courseLevel: "Undergraduate", courseLevel: "Undergraduate",
courseCredits: "7", courseCredits: "7",
dateOfAssessment: "2020-12-20", dateOfAssessment: "2020-12-20",
evidenceAssessment: "presence" evidenceAssessment: "presence",
}, },
{ {
firstName: "Barbara", firstName: "Barbara",
@ -276,7 +274,7 @@ CREDENTIAL_TYPES_DATA_STORE.CourseCredential_data = [
courseFramework: "Classical music", courseFramework: "Classical music",
courseCredits: "10", courseCredits: "10",
dateOfAssessment: "2020-12-20", dateOfAssessment: "2020-12-20",
evidenceAssessment: "presence" evidenceAssessment: "presence",
} }
]; ];
@ -317,90 +315,104 @@ export const CREDENTIAL_TYPE_DATASTORE_UNDEFINED = {
CourseCredential_data_undefined: {} as Record<string, any>, CourseCredential_data_undefined: {} as Record<string, any>,
EOperatorClaim_data_undefined: {} as Record<string, any> EOperatorClaim_data_undefined: {} as Record<string, any>
//TODO añadir otros tipos de credenciales
} }
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.FinancialVulnerabilityCredential_data_undefined = { CREDENTIAL_TYPE_DATASTORE_UNDEFINED.FinancialVulnerabilityCredential_data_undefined = {
firstName: 'undefined_data', credentialSubject: {
lastName: 'undefined_data', firstName: 'undefined_data',
email: 'undefined_data', lastName: 'undefined_data',
phoneNumber: 'undefined_data', email: 'undefined_data',
identityDocType: 'undefined_data', phoneNumber: 'undefined_data',
identityNumber: 'undefined_data', identityDocType: 'undefined_data',
streetAddress: 'undefined_data', identityNumber: 'undefined_data',
socialWorkerName: 'undefined_data', streetAddress: 'undefined_data',
socialWorkerSurname: 'undefined_data', socialWorkerName: 'undefined_data',
financialVulnerabilityScore: 'undefined_data', socialWorkerSurname: 'undefined_data',
amountCoveredByOtherAids: 'undefined_data', financialVulnerabilityScore: 'undefined_data',
connectivityOptionList: 'undefined_data', amountCoveredByOtherAids: 'undefined_data',
assessmentDate: 'undefined_data' connectivityOptionList: 'undefined_data',
assessmentDate: 'undefined_data'
},
status: 'undefined_data'
} }
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.MembershipCard_data_undefined = { CREDENTIAL_TYPE_DATASTORE_UNDEFINED.MembershipCard_data_undefined = {
organisation: 'undefined_data', credentialSubject: {
membershipType: 'undefined_data', organisation: 'undefined_data',
membershipId: 'undefined_data', membershipType: 'undefined_data',
affiliatedSince: 'undefined_data', membershipId: 'undefined_data',
affiliatedUntil: 'undefined_data', affiliatedSince: 'undefined_data',
typeOfPerson: 'undefined_data', affiliatedUntil: 'undefined_data',
identityDocType: 'undefined_data', typeOfPerson: 'undefined_data',
identityNumber: 'undefined_data', identityDocType: 'undefined_data',
firstName: 'undefined_data', identityNumber: 'undefined_data',
lastName: 'undefined_data', firstName: 'undefined_data',
role: 'undefined_data', lastName: 'undefined_data',
email: 'undefined_data' role: 'undefined_data',
email: 'undefined_data',
},
status: 'undefined_data'
} }
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.FederationMembership_data_undefined = { CREDENTIAL_TYPE_DATASTORE_UNDEFINED.FederationMembership_data_undefined = {
federation: 'undefined_data', credentialSubject: {
legalName: 'undefined_data', federation: 'undefined_data',
shortName: 'undefined_data', legalName: 'undefined_data',
registrationIdentifier: 'undefined_data', shortName: 'undefined_data',
publicRegistry: 'undefined_data', registrationIdentifier: 'undefined_data',
streetAddress: 'undefined_data', publicRegistry: 'undefined_data',
postCode: 'undefined_data', streetAddress: 'undefined_data',
city: 'undefined_data', postCode: 'undefined_data',
taxReference: 'undefined_data', city: 'undefined_data',
membershipType: 'undefined_data', taxReference: 'undefined_data',
membershipStatus: 'undefined_data', membershipType: 'undefined_data',
membershipId: 'undefined_data', membershipStatus: 'undefined_data',
membershipSince: 'undefined_data', membershipId: 'undefined_data',
email: 'undefined_data', membershipSince: 'undefined_data',
phone: 'undefined_data', email: 'undefined_data',
website: 'undefined_data', phone: 'undefined_data',
evidence: 'undefined_data', website: 'undefined_data',
certificationDate: 'undefined_data' evidence: 'undefined_data',
certificationDate: 'undefined_data',
},
status: 'undefined_data'
} }
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.CourseCredential_data_undefined = { CREDENTIAL_TYPE_DATASTORE_UNDEFINED.CourseCredential_data_undefined = {
firstName: 'undefined_data', credentialSubject: {
lastName: 'undefined_data', firstName: 'undefined_data',
email: 'undefined_data', lastName: 'undefined_data',
personalIdentifier: 'undefined_data', email: 'undefined_data',
issuedDate: 'undefined_data', personalIdentifier: 'undefined_data',
modeOfInstruction: 'undefined_data', issuedDate: 'undefined_data',
courseDuration: 'undefined_data', modeOfInstruction: 'undefined_data',
courseDays: 'undefined_data', courseDuration: 'undefined_data',
courseName: 'undefined_data', courseDays: 'undefined_data',
courseDescription: 'undefined_data', courseName: 'undefined_data',
gradingScheme: 'undefined_data', courseDescription: 'undefined_data',
scoreAwarded: 'undefined_data', gradingScheme: 'undefined_data',
qualificationAwarded: 'undefined_data', scoreAwarded: 'undefined_data',
courseLevel: 'undefined_data', qualificationAwarded: 'undefined_data',
courseFramework: 'undefined_data', courseLevel: 'undefined_data',
courseCredits: 'undefined_data', courseFramework: 'undefined_data',
dateOfAssessment: 'undefined_data', courseCredits: 'undefined_data',
evidenceAssessment: 'undefined_data', dateOfAssessment: 'undefined_data',
evidenceAssessment: 'undefined_data',
},
status: 'undefined_data'
} }
CREDENTIAL_TYPE_DATASTORE_UNDEFINED.EOperatorClaim_data_undefined = { CREDENTIAL_TYPE_DATASTORE_UNDEFINED.EOperatorClaim_data_undefined = {
legalName: 'undefined_data', credentialSubject: {
role: 'undefined_data', legalName: 'undefined_data',
email: 'undefined_data', role: 'undefined_data',
accreditedBy: 'undefined_data', email: 'undefined_data',
operatorNumber: 'undefined_data', accreditedBy: 'undefined_data',
limitJurisdiction: 'undefined_data', operatorNumber: 'undefined_data',
accreditedFor: 'undefined_data' limitJurisdiction: 'undefined_data',
accreditedFor: 'undefined_data',
},
status: 'undefined_data'
} }

View File

@ -0,0 +1,88 @@
import { Page, Locator, expect } from "@playwright/test"
export class AddIdentityPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly label: Locator
readonly dropdownType: Locator
readonly saveButton: Locator
readonly cancelButton: Locator
public constructor(page: Page) {
this.page = page;
this.primaryTitle = this.page.getByRole('heading', { name: 'My wallet' })
this.secondaryTitle = this.page.getByRole('heading', { name: ' Add a new Identity (DID)' })
this.label = this.page.getByPlaceholder('Label')
this.dropdownType = this.page.getByLabel('Type')
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 getDropdownType() {
try {
return this.dropdownType;
} catch (error) {
console.error("Failed to get dropdown DID Type value:", error);
throw error;
}
}
async getLabel() {
try {
return this.label;
} catch (error) {
console.error("Failed to get Label 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 addDid(label: string, didType: string) {
try {
(await this.getLabel()).fill(label);
(await this.getDropdownType()).selectOption({ label: didType });
(await this.getSaveButton()).click();
} catch (error) {
console.error("Failed adding new did: ", error);
throw error;
}
}
}

View File

@ -0,0 +1,105 @@
import { Page, Locator, expect } from "@playwright/test"
export class CredentialRequestPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly drowdownDid: Locator
readonly dropdownCredential: Locator
readonly requestButton: Locator
readonly cancelButton: Locator
public constructor(page: Page) {
this.page = page;
this.primaryTitle = this.page.getByRole('heading', { name: 'My wallet' })
this.secondaryTitle = this.page.getByRole('heading', { name: ' Credential Request' })
this.drowdownDid = this.page.getByLabel('Did')
this.dropdownCredential = this.page.getByLabel('Credential')
this.requestButton = this.page.getByRole('button', { name: 'Request' })
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.drowdownDid;
} catch (error) {
console.error("Failed to get dropdown DID value:", error);
throw error;
}
}
async getDropdownCredential() {
try {
return this.dropdownCredential;
} catch (error) {
console.error("Failed to get dropdown Credential value:", error);
throw error;
}
}
async getRequestButton() {
try {
return this.requestButton;
} catch (error) {
console.error("Failed to get the 'Request' button:", error);
throw error;
}
}
async getCancelButton() {
try {
return this.cancelButton;
} catch (error) {
console.error("Failed to get the 'Cancel' button:", error);
throw error;
}
}
async clickRequestButton() {
try {
(await this.getRequestButton()).click();
} catch (error) {
console.error("Failed to click request button: ", error);
throw error;
}
}
async clickCancelButton() {
try {
(await this.getCancelButton()).click();
} catch (error) {
console.error("Failed to click cancel did: ", error);
throw error;
}
}
async requestCredential(did: string, credential: string) {
try {
console.log("DId", did);
(await this.getDropdownDid()).selectOption({ label: did });
(await this.getDropdownCredential()).selectOption({ label: credential });
(await this.clickRequestButton());
} catch (error) {
console.error("Failed to request a credential: ", error);
throw error;
}
}
}

View File

@ -0,0 +1,52 @@
import { Page, Locator } from "@playwright/test"
export class IdentitiesPage {
readonly page: Page
readonly primaryTitle: Locator
readonly secondaryTitle: Locator
readonly addIdentityButton: Locator
constructor(page: Page) {
this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'My wallet' });
this.secondaryTitle = page.getByRole('heading', { name: ' Identities (DIDs)' });
this.addIdentityButton = page.getByRole('link', { name: 'Add Identity ' });
}
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 primary title:", error);
throw error;
}
}
async getAddIdentityButton() {
try {
return this.addIdentityButton;
} catch (error) {
console.error("Failed to get Add Identity button:", error);
throw error;
}
}
async clickAddIdentityButton() {
try {
return (await this.getAddIdentityButton()).click();
} catch (error) {
console.error("Failed to get click credential button:", error);
throw error;
}
}
}

View File

@ -6,11 +6,13 @@ export class ViewCredentialPage {
readonly page: Page readonly page: Page
readonly primaryTitle: Locator readonly primaryTitle: Locator
readonly secondaryTitle: Locator readonly secondaryTitle: Locator
readonly requestCredentialButton: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page; this.page = page;
this.primaryTitle = page.getByRole('heading', { name: 'My wallet' }); this.primaryTitle = page.getByRole('heading', { name: 'My wallet' });
this.secondaryTitle = page.getByRole('heading', { name: ' Credential' }); this.requestCredentialButton = page.getByRole('link', { name: 'Request credential' });
} }
async getPrimaryTitle() { async getPrimaryTitle() {
try { try {
@ -21,20 +23,32 @@ export class ViewCredentialPage {
} }
} }
async getSecondaryTitle() { async getRequestCredentialButton() {
try { try {
return await this.secondaryTitle.innerText(); return this.requestCredentialButton;
} catch (error) { } catch (error) {
console.error("Failed to get secondary title:", error); console.error("Failed to get request credential button:", error);
throw error; throw error;
} }
} }
async clickRequestCredentialButton() {
try {
return (await this.getRequestCredentialButton()).click();
} catch (error) {
console.error("Failed to get click credential button:", error);
throw error;
}
}
async buildACredentialFromInputValues(credentialType: string) { async buildACredentialFromInputValues(credentialType: string) {
try { try {
const elementsWithLabel = await this.page.locator('.col-3 strong').all(); const elementsWithLabel = await this.page.locator('.col-3 strong').all();
const elementsWithValue = await this.page.locator('.col.bg-light.text-secondary').all(); const elementsWithValue = await this.page.locator('.col.bg-light.text-secondary').all();
let credential = initialize_with_undefined_values(credentialType); let credential = initialize_with_undefined_values(credentialType);
let labels: string[] = []; let labels: string[] = [];
@ -48,17 +62,20 @@ export class ViewCredentialPage {
if (value != null && label != null && label != 'Id' && label != "Issuance date" && label != "Status") { if (value != null && label != null && label != 'Id' && label != "Issuance date" && label != "Status") {
labels.push(label); labels.push(label);
values.push(value); values.push(value);
} else {
if (label === "Status"){
credential.status = value;
}
} }
} }
credential = setCredentialValues(labels, values, credentialType); credential.credentialSubject = setCredentialValues(labels, values, credentialType);
return credential; return credential;
} catch (error) { } catch (error) {
console.error("Failed to build the credential from input values:", error); console.error("Failed to build the credential from input values:", error);
throw error; throw error;
} }
}//end function }//end function

View File

@ -1,4 +1,5 @@
import { Page, Locator } from "@playwright/test" import { Page, Locator } from "@playwright/test"
import { ISSUED_ST } from "../constants/constants"
export class ViewMyCredentialsPage { export class ViewMyCredentialsPage {
@ -87,9 +88,7 @@ export class ViewMyCredentialsPage {
(await this.getIssuedOnTitle()).click(); (await this.getIssuedOnTitle()).click();
} }
async gotoViewEnabledCredentialPage(type: string) { async gotoViewCredentialPage(type: string, status: string) {
let status = 'Enabled';
// Locator for all rows // Locator for all rows
const rowLocator = this.page.locator('tr'); const rowLocator = this.page.locator('tr');
@ -97,23 +96,19 @@ export class ViewMyCredentialsPage {
const typeFilteredRows = rowLocator.filter({ hasText: type }); const typeFilteredRows = rowLocator.filter({ hasText: type });
// Rows with the fourth column having the specified status // Rows with the fourth column having the specified status
const statusFilteredRows = typeFilteredRows.filter({ hasText: status }); //const statusFilteredRows = typeFilteredRows.filter({ hasText: status });
// Manually filter rows based on the fourth column's text content
// only the rows matching both criteria let statusFilteredRows = [];
// Check if the row exists for (const row of await typeFilteredRows.all()) {
if (await statusFilteredRows.count() > 0) { const fourthColumnText = await row.locator('td:nth-child(4)').textContent();
// Click to view the credential if (fourthColumnText === status) {
await statusFilteredRows.first().locator('i.bi.bi-eye').click(); await row.locator('i.bi.bi-eye').click();
} else { break;
console.log(`No row found with type '${type}' and status '${status}'.`); }
} }
} }
async enabledCredentialExistInMyCredentials(type: string): Promise<boolean> { async credentialExistInMyCredentials(type: string, status: string): Promise<boolean> {
let status = 'Enabled';
// Locator for all rows // Locator for all rows
const rowLocator = this.page.locator('tr'); const rowLocator = this.page.locator('tr');

View File

@ -1,9 +1,9 @@
import { expect, Page } from '@playwright/test' import { expect, Page } from '@playwright/test'
import { appendRandomNumberToFilename, copyFile, deleteFile } from './utils' import { appendRandomNumberToInput, copyFile, deleteFile } from './utils'
import { CREDENTIAL_TYPE_DATASTORE_UNDEFINED, CREDENTIAL_TYPES_DATA_STORE } from './data_stores/credentials_data_store' import { CREDENTIAL_TYPE_DATASTORE_UNDEFINED, CREDENTIAL_TYPES_DATA_STORE } from './data_stores/credentials_data_store'
import { CREDENTIAL_TYPES_FIELDS_LIST} from './constants/credential_fields' import { CREDENTIAL_TYPES_FIELDS_LIST } from './constants/credential_fields'
import { LogInPage } from './page-objects/COMM_LoginPage' import { LogInPage } from './page-objects/COMM_LoginPage'
import { ADMIN_EMAIL, ADMIN_K, ENCRYPTION_KEY, USER_K } from './constants/env_constants' import { ADMIN_EMAIL, ADMIN_K, ENCRYPTION_KEY, USER_K } from './constants/env_constants'
import { LeftMenuAdminPage } from './page-objects/AD_LeftMenuAdminPage' import { LeftMenuAdminPage } from './page-objects/AD_LeftMenuAdminPage'
@ -18,8 +18,12 @@ import { User } from './interfaces/User'
import { EncryptionKeyPage } from './page-objects/AD_EncryptionKeyPage' import { EncryptionKeyPage } from './page-objects/AD_EncryptionKeyPage'
import { DataProtectionPage } from './page-objects/COMM_DataProtectionPage' import { DataProtectionPage } from './page-objects/COMM_DataProtectionPage'
import { ImportDataPage } from './page-objects/AD_ImportDataPage' import { ImportDataPage } from './page-objects/AD_ImportDataPage'
import { PATH_FILES_TO_IMPORT } from './constants/constants' import { ENABLED_ST, ISSUED_ST, PATH_FILES_TO_IMPORT } from './constants/constants'
import { fail } from 'assert' import { fail } from 'assert'
import { ViewCredentialPage } from './page-objects/US_ViewCredentialPage'
import { AddIdentityPage } from './page-objects/US_AddIdentityPage'
import { IdentitiesPage } from './page-objects/US_IdentitiesPage'
import { CredentialRequestPage } from './page-objects/US_CredentialRequestPage'
export async function loginAsAdmin(page: Page, url: string) { export async function loginAsAdmin(page: Page, url: string) {
@ -141,6 +145,18 @@ export async function clickMyCredentialsOnLeftMenu(page: Page) {
} }
} }
export async function clickIdentitiesOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuUserPage(page);
const identitiesLink = await leftMenuPage.getIdentitiesLink();
await identitiesLink.click()
return { identitiesLink }
} catch (error) {
console.error(`Failed to access Identities option on left menu: `);
throw error;
}
}
export async function clickManageRolesOnLeftMenu(page: Page) { export async function clickManageRolesOnLeftMenu(page: Page) {
try { try {
const leftMenuPage = new LeftMenuAdminPage(page); const leftMenuPage = new LeftMenuAdminPage(page);
@ -213,6 +229,18 @@ export async function clickViewCredentialsOnLeftMenu(page: Page) {
} }
} }
export async function clickViewOrganizationWalletOnLeftMenu(page: Page) {
try {
const leftMenuPage = new LeftMenuAdminPage(page);
const viewOrganisationalWalletLink = await leftMenuPage.getOrganisationalWalletLink();
await viewOrganisationalWalletLink.click()
return { viewOrganisationalWalletLink }
} catch (error) {
console.error(`Failed to access Organisational wallet on left menu: `);
throw error;
}
}
export async function clickViewSchemaOnLeftMenu(page: Page, templateName: string) { export async function clickViewSchemaOnLeftMenu(page: Page, templateName: string) {
try { try {
const templatesPage = new TemplatesPage(page) const templatesPage = new TemplatesPage(page)
@ -240,19 +268,20 @@ export async function gotoBasicInfoPageOfTheUser(page: Page, user: User) {
await viewUsersPage.gotoUserInformationPage(user) await viewUsersPage.gotoUserInformationPage(user)
} }
export async function gotoViewEnabledCredential(page: Page, credentialType: string) { export async function gotoViewCredential(page: Page, credentialType: string, status: string) {
const myCredentialsPage = new ViewMyCredentialsPage(page) const myCredentialsPage = new ViewMyCredentialsPage(page)
//Access the specific enabled credential that is expected to be in the user wallet! //Access the specific enabled credential that is expected to be in the user wallet!
const { myCredentialsLink } = await clickMyCredentialsOnLeftMenu(page); const { myCredentialsLink } = await clickMyCredentialsOnLeftMenu(page);
try { try {
expect(await myCredentialsPage.enabledCredentialExistInMyCredentials(credentialType)).toBeTruthy(); expect(await myCredentialsPage.credentialExistInMyCredentials(credentialType, status)).toBeTruthy();
} catch (error) { } catch (error) {
console.error('Failed accessing the enabled credential of type ${credentialType}', error); console.error('Failed accessing the enabled credential of type ${credentialType}', error);
} }
await myCredentialsPage.gotoViewEnabledCredentialPage(credentialType); await myCredentialsPage.gotoViewCredentialPage(credentialType, status);
} }
export async function checkIfTheInformationIsValid(page: Page, user: User) { export async function checkIfTheInformationIsValid(page: Page, user: User) {
const userPersonalInformationPage = new UserPersonalInformationPage(page) const userPersonalInformationPage = new UserPersonalInformationPage(page)
@ -264,9 +293,8 @@ export async function checkIfTheInformationIsValid(page: Page, user: User) {
} }
export function expectedCredentialSubjectForUser(email: string, credentialType: string){ export function expectedCredentialSubjectForUser(email: string, credentialType: string) {
//export function expectedCredentialSubjectForUser<T extends FinancialVulnerabilityCredential_fields, MembershipCard_fields, NGOFederationMembership_fields>(email: string, credentialType: string): FinancialVulnerabilityCredential_fields | MembershipCard_fields | NGOFederationMembership_fields | undefined {
try { try {
const testingDataForCredential = CREDENTIAL_TYPES_DATA_STORE[credentialType + '_data']; const testingDataForCredential = CREDENTIAL_TYPES_DATA_STORE[credentialType + '_data'];
@ -300,7 +328,7 @@ export async function checkFileName(page: Page, fileName: string): Promise<strin
const viewImportedDataPage = new ViewImportedDataPage(page); const viewImportedDataPage = new ViewImportedDataPage(page);
let fileInTheList = await viewImportedDataPage.fileIsAvailableInView(fileName); let fileInTheList = await viewImportedDataPage.fileIsAvailableInView(fileName);
if (fileInTheList) { if (fileInTheList) {
let newName = appendRandomNumberToFilename(fileName); let newName = appendRandomNumberToInput(fileName);
//rename the file //rename the file
copyFile(fileName, newName); copyFile(fileName, newName);
return newName; return newName;
@ -327,10 +355,10 @@ export async function loadIfJsonSchemaNotAvailable(page: Page, jsonSchema: strin
export function setCredentialValues(labels: string[], values: string[], credentialType: string): any { export function setCredentialValues(labels: string[], values: string[], credentialType: string): any {
try { try {
const listOfFields = CREDENTIAL_TYPES_FIELDS_LIST[credentialType]; const listOfFields = CREDENTIAL_TYPES_FIELDS_LIST[credentialType];
// Initialize an empty object to store the credential // Initialize an empty object to store the credential
let credential: any = {}; let credential: any = {};
// Iterate over labels and values // Iterate over labels and values
for (let i = 0; i < labels.length; i++) { for (let i = 0; i < labels.length; i++) {
@ -455,3 +483,58 @@ export async function testImportDataFile_SadPath(page: Page, fileToImport: strin
fail(); fail();
} }
} }
export async function test_ViewAndCheckEnabledCredentialbyUser(page: Page, schemaType: string, user: string) {
const enabledCredentialView = new ViewCredentialPage(page);
// View the credential in status 'Enabled'
await gotoViewCredential(page, schemaType, ENABLED_ST);
//Get the credential subject values of the credential visualized on the screen and compare to the model
let metaCredential = await enabledCredentialView.buildACredentialFromInputValues(schemaType);
let actualCredential = metaCredential.credentialSubject;
let status = metaCredential.status;
let expectedCredential = await expectedCredentialSubjectForUser(user, schemaType);
expect(actualCredential).toEqual(expectedCredential);
expect(status).toEqual(ENABLED_ST);
}
export async function test_RequestAndCheckIssuedCredentialByUser(page: Page, schemaType: string, user: string) {
const identitiesPage = new IdentitiesPage(page);
const addIdentityPage = new AddIdentityPage(page);
const credentialView = new ViewCredentialPage(page);
const credentialRequestPage = new CredentialRequestPage(page);
await clickIdentitiesOnLeftMenu(page);
//Add a new identity
await identitiesPage.clickAddIdentityButton();
let did = appendRandomNumberToInput('did');
await addIdentityPage.addDid(did, 'Web');
// View the Financial Vulnerabilty Credential in status 'Enabled' for the user
await gotoViewCredential(page, schemaType, ENABLED_ST);
// Request the credential
await credentialView.clickRequestCredentialButton();
await credentialRequestPage.requestCredential(did, schemaType);
await clickMyCredentialsOnLeftMenu(page);
await gotoViewCredential(page, schemaType, ISSUED_ST);
//Get the credential subject values of the credential visualized on the screen and compare to the model
let metaCredential = await credentialView.buildACredentialFromInputValues(schemaType);
let actualCredential = metaCredential.credentialSubject;
let status = metaCredential.status;
let expectedCredential = await expectedCredentialSubjectForUser(user, schemaType);
expect(actualCredential).toEqual(expectedCredential);
expect(status).toEqual(ISSUED_ST);
}

View File

@ -106,16 +106,16 @@ export async function deleteFile(fileName: string): Promise<void> {
} }
} }
export function appendRandomNumberToFilename(filename: string) { export function appendRandomNumberToInput(input: string) {
// Generate a random number between 100 and 999 // Generate a random number between 100 and 999
const randomNumber = Math.floor(Math.random() * (999 - 100 + 1)) + 100; const randomNumber = Math.floor(Math.random() * (999 - 100 + 1)) + 100;
// Extract the extension from the filename // Extract the extension from the filename
const extensionIndex = filename.lastIndexOf('.'); const extensionIndex = input.lastIndexOf('.');
const extension = extensionIndex !== -1 ? filename.slice(extensionIndex) : ''; const extension = extensionIndex !== -1 ? input.slice(extensionIndex) : '';
// Remove the extension from the original filename // Remove the extension from the original filename
const basename = extensionIndex !== -1 ? filename.slice(0, extensionIndex) : filename; const basename = extensionIndex !== -1 ? input.slice(0, extensionIndex) : input;
// Append the random number and the extension to form the new filename // Append the random number and the extension to form the new filename
return `${basename}${randomNumber}${extension}`; return `${basename}${randomNumber}${extension}`;

View File

@ -4,7 +4,7 @@ import { ViewUsersPage } from '../src/page-objects/AD_ViewUsersPage';
import { BasicUserInfoSectionPage } from '../src/page-objects/AD_BasicUserInfoSectionInPage'; import { BasicUserInfoSectionPage } from '../src/page-objects/AD_BasicUserInfoSectionInPage';
import { ViewRolesPage } from '../src/page-objects/AD_ViewRolesPage'; import { ViewRolesPage } from '../src/page-objects/AD_ViewRolesPage';
import { ViewServicesPage } from '../src/page-objects/AD_ViewServicesPage'; import { ViewServicesPage } from '../src/page-objects/AD_ViewServicesPage';
import { clickAddUserOnLeftMenu, clickDashboardOnLeftMenu, clickManageRolesOnLeftMenu, clickManageServicesOnLeftMenu, clickViewUsersOnLeftMenu, loginAsAdmin } from '../src/steps'; import { clickAddUserOnLeftMenu, clickDashboardOnLeftMenu, clickManageRolesOnLeftMenu, clickManageServicesOnLeftMenu, clickViewCredentialsOnLeftMenu, clickViewOrganizationWalletOnLeftMenu, clickViewUsersOnLeftMenu, loginAsAdmin } from '../src/steps';
import { URL_IDHUB } from '../src/constants/env_constants'; import { URL_IDHUB } from '../src/constants/env_constants';
/** /**
@ -70,13 +70,31 @@ test.describe('Leftside Menu navigation test', () => {
//TODO: credentials, templates.... //TODO: credentials, templates....
test.skip('LEFTMENU -> Credentials', async ({ page }) => { test('LEFTMENU -> Credentials', async ({ page }) => {
const leftMenu = new LeftMenuAdminPage(page);
(await leftMenu.getCredentialsLink()).click();
//Navigate to "View Credentials"
await clickViewCredentialsOnLeftMenu(page);
await expect(page).toHaveTitle('Credential management IdHub');
//Navigate to "Organization's wallet"
await clickViewOrganizationWalletOnLeftMenu(page);
}) })
test.skip('LEFTMENU -> Templates', async ({ page }) => { test('LEFTMENU -> Templates', async ({ page }) => {
//Navigate to "Templates"
const leftMenu = new LeftMenuAdminPage(page);
(await leftMenu.getTemplatesLink()).click();
await expect(page).toHaveTitle('Template management IdHub');
}) })
test.skip('LEFTMENU -> Data', async ({ page }) => { test('LEFTMENU -> Data', async ({ page }) => {
//Navigate to "Data"
const leftMenu = new LeftMenuAdminPage(page);
(await leftMenu.getDataLink()).click();
await expect(page).toHaveTitle('Data file management IdHub');
}) })
}) })

View File

@ -82,41 +82,6 @@ test.describe('USER Section Tests', () => {
}); });
/**
* 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)
// 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 * ADD a user (basic information) and validate it after addition
* UPDATE their basic information and validate it after modification * UPDATE their basic information and validate it after modification

View File

@ -1,14 +1,13 @@
import { test, expect} from '@playwright/test' import { test } from '@playwright/test';
import { loginAsAdmin, loginAsUser, gotoViewEnabledCredential, expectedCredentialSubjectForUser, testImportDataFile_SadPath, testImportDataFile_HappyPath } from '../src/steps'; import { loginAsAdmin, loginAsUser, testImportDataFile_SadPath, testImportDataFile_HappyPath, test_ViewAndCheckEnabledCredentialbyUser, test_RequestAndCheckIssuedCredentialByUser } from '../src/steps';
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js' import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL, USER3_EMAIL } from '../src/constants/env_constants';
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, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, FILE_TO_IMPORT_FVC, FILE_TO_IMPORT_FVC_EMPTY, FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_FVC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_FVC, SCHEMA_FVC, SCHEMA_TYPE_FVC } from '../src/constants/constants'; import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, FILE_TO_IMPORT_FVC, FILE_TO_IMPORT_FVC_EMPTY, FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_FVC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_FVC, SCHEMA_FVC, SCHEMA_TYPE_FVC } from '../src/constants/constants';
/** /**
* Checking Admin Data Section: view the lists of files imported, import data, ... * Testing Admin->Data Section functionality with the Financial Vulnerability Credential (FVC)
*/ */
test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability Credential', () => { test.describe('ADMIN-> DATA -> Import FVC excel files', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB); await loginAsAdmin(page, URL_IDHUB);
@ -20,11 +19,12 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
}) })
/** /**
* Load an excel file - Happy Path * Load an excel file - Happy Path
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed, * Expected behavior:
* and the file appears in the imported files view. * - the file is loaded, the message: "The file was imported successfully!" is displayed.
* - the file appears in the imported files view.
*/ */
test('DATA -> Import data file - Happy path - Financial Vulnerability Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import FVC excel file - Happy Path', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_FVC; const fileToImport = FILE_TO_IMPORT_FVC;
const jsonSchema = JSON_SCHEMA_FVC; const jsonSchema = JSON_SCHEMA_FVC;
@ -34,13 +34,13 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
}); });
/** /**
* Load an excel file - Sad Path: * Load an excel file - Sad Path:
* Try to load a well-formatted excel file but without data. * Try to load a well-formatted excel file but without data.
* Expected behavior: The error message: "The file you try to import is empty" * 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 }) => { test('ADMIN-> DATA -> Import FVC excel file - Sad path (file well formatted but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_FVC_EMPTY; const fileToImport = FILE_TO_IMPORT_FVC_EMPTY;
const jsonSchema = JSON_SCHEMA_FVC; const jsonSchema = JSON_SCHEMA_FVC;
@ -56,8 +56,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
* Try to load a bad formatted file, without required data. * Try to load a bad formatted file, without required data.
*/ */
test('DATA -> Import data file - Sad path (bad formatted file, without required columns) - Financial Vulnerability Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import FVC excel file - Sad path (bad formatted file, without required columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS; const fileToImport = FILE_TO_IMPORT_FVC_WITHOUT_REQUIRED_COLUMNS;
const jsonSchema = JSON_SCHEMA_FVC; const jsonSchema = JSON_SCHEMA_FVC;
const schema = SCHEMA_FVC; const schema = SCHEMA_FVC;
@ -73,7 +72,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
* Expected behavior: The error message: "line 2: Additional properties are not allowed ('alien1', 'alien2' were unexpected)" * Expected behavior: The error message: "line 2: Additional properties are not allowed ('alien1', 'alien2' were unexpected)"
*/ */
test('DATA -> Import data file - Sad path (bad formatted file, with alien columns) - Financial Vulnerability Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import FVC excel file - Sad path (bad formatted file, with alien columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS; const fileToImport = FILE_TO_IMPORT_FVC_WITH_ALIEN_COLUMNS;
const jsonSchema = JSON_SCHEMA_FVC; const jsonSchema = JSON_SCHEMA_FVC;
const schema = SCHEMA_FVC; const schema = SCHEMA_FVC;
@ -89,7 +88,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
* Expected behavior: The error message: "line 3: 'firstName' is a required property" * Expected behavior: The error message: "line 3: 'firstName' is a required property"
*/ */
test('DATA -> Import data file - Sad path (file with required columns present but empty) - Financial Vulnerability Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import FVC excel file - Sad path (file with required columns present but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_FVC_WITH_REQUIRED_EMPTY; const fileToImport = FILE_TO_IMPORT_FVC_WITH_REQUIRED_EMPTY;
const jsonSchema = JSON_SCHEMA_FVC; const jsonSchema = JSON_SCHEMA_FVC;
@ -102,11 +101,8 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Financial Vulnerability
}) //end describe }) //end describe
test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND FINANCIAL VULNERABILITY CRED', () => { test.describe('USER -> My Credentials - enable and issue credentials', () => {
test.beforeEach(async ({ page }) => {
await loginAsUser(page, USER1_EMAIL, URL_IDHUB);
})
test.afterEach(async ({ page }) => { test.afterEach(async ({ page }) => {
await page.click('.logout'); await page.click('.logout');
await page.close(); await page.close();
@ -114,24 +110,39 @@ test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND
}) })
/** /**
* PRE-CONDITIONS: the admin has enabled a credential of type 'Financial Vulnerabitity' for USER1_EMAIL * PRE-CONDITIONS: the admin has enabled sucessfully a credential in the previous test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* This is true, if the before test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* SUMMARY: * SUMMARY:
* - Check if the user1 can visualize the credentials that has been enabled in "My Credentials" * - Check if the user can visualize the credentials that has been enabled in "My Credentials"
* - Check the fields displayed when user click "View" Credential * - Check that the fields displayed in "View" Credential are the expected ones
*/ */
test('USER Credentials -> My Credentials -> View enabled Financial Vulnerability Credential', async ({ page }) => { test('USER -> My Credentials -> View FVC enabled Credential for user1', async ({ page }) => {
// View the Financial Vulnerabilty Credential in status 'Enabled' for the user
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 on the screen and compare to the model let schemaType = SCHEMA_TYPE_FVC;
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_FVC); let user = USER1_EMAIL;
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_FVC);
expect(actualCredential).toEqual(expectedCredential); await loginAsUser(page, user, URL_IDHUB);
await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
}); });
test('USER -> My Credentials -> View FVC enabled Credential for user2', async ({ page }) => {
let schemaType = SCHEMA_TYPE_FVC;
let user = USER2_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
});
test('USER -> My Credentials -> Request the issuance of a FVC for user3', async ({ page }) => {
let schemaType = SCHEMA_TYPE_FVC;
let user = USER3_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_RequestAndCheckIssuedCredentialByUser(page, schemaType, user);
});
}) })

View File

@ -1,14 +1,13 @@
import { test, expect } from '@playwright/test' import { test} from '@playwright/test'
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js' import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL, USER3_EMAIL } from '../src/constants/env_constants';
import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL } from '../src/constants/env_constants'; import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_MC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, ENABLED_ST, FILE_TO_IMPORT_MC, FILE_TO_IMPORT_MC_2, FILE_TO_IMPORT_MC_EMPTY, FILE_TO_IMPORT_MC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_MC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_MC_WITH_REQUIRED_EMPTY, ISSUED_ST, JSON_SCHEMA_MC, SCHEMA_MC, SCHEMA_TYPE_MC } from '../src/constants/constants';
import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_MC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, FILE_TO_IMPORT_MC, FILE_TO_IMPORT_MC_2, FILE_TO_IMPORT_MC_EMPTY, FILE_TO_IMPORT_MC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_MC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_MC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_MC, SCHEMA_MC, SCHEMA_TYPE_MC } from '../src/constants/constants'; import { loginAsAdmin, loginAsUser, testImportDataFile_HappyPath, testImportDataFile_SadPath, test_RequestAndCheckIssuedCredentialByUser, test_ViewAndCheckEnabledCredentialbyUser } from '../src/steps.js';
import { expectedCredentialSubjectForUser, gotoViewEnabledCredential, loginAsAdmin, loginAsUser, testImportDataFile_HappyPath, testImportDataFile_SadPath } from '../src/steps.js';
/** /**
* Checking data section: view the lists of files imported, import data, delete... * Testing Admin->Data Section functionality with the Membership Credential (MC)
*/ */
test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential', () => { test.describe('ADMIN-> DATA -> Import MC excel files', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB); await loginAsAdmin(page, URL_IDHUB);
@ -19,12 +18,13 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
}) })
/** /**
* Load of an excel file - Happy Path: * Load an excel file - Happy Path
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed, * Expected behavior:
* and the file appears in the imported files view. * - the file is loaded, the message: "The file was imported successfully!" is displayed.
* - the file appears in the imported files view.
*/ */
test('DATA -> Import data file - Happy path - Membership Card Credential - membership_card.xlsx ', async ({ page }) => { test('ADMIN-> DATA -> Import MC excel file - Happy Path', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_MC; const fileToImport = FILE_TO_IMPORT_MC;
const jsonSchema = JSON_SCHEMA_MC; const jsonSchema = JSON_SCHEMA_MC;
@ -40,7 +40,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
* Expected behavior: The error message: "The file you try to import is empty" * 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) - Membership Card Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import MC excel file - Sad path (file well formatted but empty', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_MC_EMPTY; const fileToImport = FILE_TO_IMPORT_MC_EMPTY;
const jsonSchema = JSON_SCHEMA_MC; const jsonSchema = JSON_SCHEMA_MC;
@ -51,7 +51,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
}); });
test('DATA -> Import data file - Sad path (bad formatted file, without required columns) - Membership Card Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import MC excel file - Sad path (bad formatted file, without required columns', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_MC_WITHOUT_REQUIRED_COLUMNS; const fileToImport = FILE_TO_IMPORT_MC_WITHOUT_REQUIRED_COLUMNS;
const jsonSchema = JSON_SCHEMA_MC; const jsonSchema = JSON_SCHEMA_MC;
@ -62,7 +62,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
}); });
test('DATA -> Import data file - Sad path (bad formatted file, with alien columns) - Membership Card Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import MC excel file - Sad path (bad formatted file, with alien columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_MC_WITH_ALIEN_COLUMNS; const fileToImport = FILE_TO_IMPORT_MC_WITH_ALIEN_COLUMNS;
const jsonSchema = JSON_SCHEMA_MC; const jsonSchema = JSON_SCHEMA_MC;
@ -73,7 +73,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
}); });
test('DATA -> Import data file - Sad path (file with required columns present but empty) - Membership Card Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import MC excel file - Sad path (file with required columns present but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_MC_WITH_REQUIRED_EMPTY; const fileToImport = FILE_TO_IMPORT_MC_WITH_REQUIRED_EMPTY;
const jsonSchema = JSON_SCHEMA_MC; const jsonSchema = JSON_SCHEMA_MC;
@ -86,7 +86,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Membership Credential',
}) })
test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND Membership Card Credential', () => { test.describe('USER -> My Credentials - enable and issue credentials', () => {
test.afterEach(async ({ page }) => { test.afterEach(async ({ page }) => {
await page.click('.logout'); await page.click('.logout');
@ -94,36 +94,41 @@ test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND
}) })
/** /**
* PRE-CONDITIONS: the admin has enabled a credential of type 'Membership Card' for USER1_EMAIL * PRE-CONDITIONS: the admin has enabled sucessfully a credential in the previous test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* This is true, if the before test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* SUMMARY: * SUMMARY:
* - Check if the user1 can visualize the credentials that has been enabled in "My Credentials" * - Check if the user can visualize the credentials that has been enabled in "My Credentials"
* - Check the fields displayed when user click "View" Credential * - Check that the fields displayed in "View" Credential are the expected ones
*/ */
test('USER Credentials -> My Credentials -> View enabled Membership Card - for user1@example.org', async ({ page }) => {
// View the Membership Card Credential in status 'Enabled' for the user
await loginAsUser(page, USER1_EMAIL, URL_IDHUB);
await gotoViewEnabledCredential(page, SCHEMA_TYPE_MC);
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 test('USER -> My Credentials -> View MC enabled Credential for user1', async ({ page }) => {
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_MC);
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_MC); let schemaType = SCHEMA_TYPE_MC;
expect(actualCredential).toEqual(expectedCredential); let user = USER1_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
}); });
test('USER Credentials -> My Credentials -> View enabled Membership Card - for user2@example.org', async ({ page }) => { test('USER -> My Credentials -> View MC enabled Credential for user3', async ({ page }) => {
// View the Membership Card Credential in status 'Enabled' for the user
await loginAsUser(page, USER2_EMAIL, URL_IDHUB); let schemaType = SCHEMA_TYPE_MC;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_MC); let user = USER3_EMAIL;
const enabledCredentialView = new ViewCredentialPage(page);
//Check that required fields exist and have a valid value in the current enabled credential await loginAsUser(page, user, URL_IDHUB);
await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
});
test('USER -> My Credentials -> Request the issuance of a MC for user3', async ({ page }) => {
let schemaType = SCHEMA_TYPE_MC;
let user = USER3_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_RequestAndCheckIssuedCredentialByUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_MC);
let expectedCredential = await expectedCredentialSubjectForUser(USER2_EMAIL, SCHEMA_TYPE_MC);
expect(actualCredential).toEqual(expectedCredential);
}); });
}) })

View File

@ -1,14 +1,13 @@
import { test, expect } from '@playwright/test' import { test} from '@playwright/test'
import { loginAsAdmin, loginAsUser, gotoViewEnabledCredential, expectedCredentialSubjectForUser, testImportDataFile_HappyPath, testImportDataFile_SadPath } from '../src/steps'; import { loginAsAdmin, loginAsUser, testImportDataFile_HappyPath, testImportDataFile_SadPath, test_RequestAndCheckIssuedCredentialByUser, test_ViewAndCheckEnabledCredentialbyUser } from '../src/steps';
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js' import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL, USER3_EMAIL } from '../src/constants/env_constants';
import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL } from '../src/constants/env_constants';
import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_NGO_FM, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY_NGO_FM, FILE_TO_IMPORT_NGO_FM, FILE_TO_IMPORT_NGO_FM_EMPTY, FILE_TO_IMPORT_NGO_FM_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_NGO_FM_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_NGO_FM_WITH_REQUIRED_EMPTY, JSON_SCHEMA_NGO_FM, SCHEMA_NGO_FM, SCHEMA_TYPE_NGO_FM } from '../src/constants/constants'; import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_NGO_FM, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY_NGO_FM, FILE_TO_IMPORT_NGO_FM, FILE_TO_IMPORT_NGO_FM_EMPTY, FILE_TO_IMPORT_NGO_FM_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_NGO_FM_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_NGO_FM_WITH_REQUIRED_EMPTY, JSON_SCHEMA_NGO_FM, SCHEMA_NGO_FM, SCHEMA_TYPE_NGO_FM } from '../src/constants/constants';
/** /**
* Checking data section: view the lists of files imported, import data, delete... * Testing Admin->Data Section functionality with the NGO Federation Membership Credential (NGO_FM)
*/ */
test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membership', () => { test.describe('ADMIN-> DATA -> Import NGO_FM excel files', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB); await loginAsAdmin(page, URL_IDHUB);
@ -20,12 +19,13 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
/** /**
* Load an excel file - Happy Path * Load an excel file - Happy Path
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed, * Expected behavior:
* and the file appears in the imported files view. * - the file is loaded, the message: "The file was imported successfully!" is displayed.
*/ * - the file appears in the imported files view.
*/
test('DATA -> Import data file - Happy path - NGO Federation Membership Credential - federation-membership.xlsx ', async ({ page }) => { test('DATA -> ADMIN-> DATA -> Import NGO_FM excel file - Happy Path', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_NGO_FM; const fileToImport = FILE_TO_IMPORT_NGO_FM;
const jsonSchema = JSON_SCHEMA_NGO_FM; const jsonSchema = JSON_SCHEMA_NGO_FM;
@ -42,7 +42,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
* Expected behavior: The error message: "The file you try to import is empty" * 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) - NGO Federation Membership Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import NGO_FM excel file - Sad path (file well formatted but empty', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_NGO_FM_EMPTY; const fileToImport = FILE_TO_IMPORT_NGO_FM_EMPTY;
const jsonSchema = JSON_SCHEMA_NGO_FM; const jsonSchema = JSON_SCHEMA_NGO_FM;
@ -53,7 +53,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
}); });
test('DATA -> Import data file - Sad path (file bad formatted, without required columns) - NGO Federation Membership Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import NGO excel file - Sad path (bad formatted file, without required columns', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_NGO_FM_WITHOUT_REQUIRED_COLUMNS; const fileToImport = FILE_TO_IMPORT_NGO_FM_WITHOUT_REQUIRED_COLUMNS;
const jsonSchema = JSON_SCHEMA_NGO_FM; const jsonSchema = JSON_SCHEMA_NGO_FM;
@ -64,7 +64,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
}); });
test('DATA -> Import data file - Sad path (file bad formatted, with alien columns) - NGO Federation Membership Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import NGO_FM excel file - Sad path (bad formatted file, with alien columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_NGO_FM_WITH_ALIEN_COLUMNS; const fileToImport = FILE_TO_IMPORT_NGO_FM_WITH_ALIEN_COLUMNS;
const jsonSchema = JSON_SCHEMA_NGO_FM; const jsonSchema = JSON_SCHEMA_NGO_FM;
@ -75,7 +75,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
}); });
test('DATA -> Import data file - Sad path (file with required columns present but empty) - NGO Federation Membership Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import NGO_FM excel file - Sad path (file with required columns present but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_NGO_FM_WITH_REQUIRED_EMPTY; const fileToImport = FILE_TO_IMPORT_NGO_FM_WITH_REQUIRED_EMPTY;
const jsonSchema = JSON_SCHEMA_NGO_FM; const jsonSchema = JSON_SCHEMA_NGO_FM;
@ -88,7 +88,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with NGO Federation Membersh
}) //end describe }) //end describe
test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND NGO Federation Membership Credential', () => { test.describe('USER -> My Credentials - enable and issue credentials', () => {
test.afterEach(async ({ page }) => { //este se ejecutará despues de cada test test.afterEach(async ({ page }) => { //este se ejecutará despues de cada test
await page.click('.logout'); await page.click('.logout');
@ -96,38 +96,38 @@ test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND
}) })
/** /**
* PRE-CONDITIONS: the admin has enabled a credential of type 'NGO Federation Membership' for USER1_EMAIL * PRE-CONDITIONS: the admin has enabled sucessfully a credential in the previous test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* This is true, if the before test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* SUMMARY: * SUMMARY:
* - Check if the user1 can visualize the credentials that has been enabled in "My Credentials" * - Check if the user can visualize the credentials that has been enabled in "My Credentials"
* - Check the fields displayed when user click "View" Credential * - Check that the fields displayed in "View" Credential are the expected ones
*/ */
test('USER Credentials -> My Credentials -> View enabled NGO Federation Membership Credential - for user1@example.org', async ({ page }) => { test('USER -> My Credentials -> View NGO_FM enabled Credential for user1', async ({ page }) => {
// View the NGO Federation Membership Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_NGO_FM;
await loginAsUser(page, USER1_EMAIL, URL_IDHUB); let user = USER1_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_NGO_FM);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_NGO_FM);
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_NGO_FM);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER Credentials -> My Credentials -> View enabled NGO Federation Membership Credential - for user2@example.org', async ({ page }) => { test('USER -> My Credentials -> View NGO_FM enabled Credential for user2', async ({ page }) => {
// View the NGO Federation Membership Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_NGO_FM;
await loginAsUser(page, USER2_EMAIL, URL_IDHUB); let user = USER2_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_NGO_FM);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_NGO_FM);
let expectedCredential = await expectedCredentialSubjectForUser(USER2_EMAIL, SCHEMA_TYPE_NGO_FM);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER -> My Credentials -> Request the issuance of a NGO_FM for user3', async ({ page }) => {
let schemaType = SCHEMA_NGO_FM;
let user = USER3_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_RequestAndCheckIssuedCredentialByUser(page, schemaType, user);
});
}) })

View File

@ -1,14 +1,13 @@
import { test, expect } from '@playwright/test' import { test} from '@playwright/test'
import { loginAsAdmin, loginAsUser, gotoViewEnabledCredential, expectedCredentialSubjectForUser, testImportDataFile_HappyPath, testImportDataFile_SadPath } from '../src/steps'; import { loginAsAdmin, loginAsUser,testImportDataFile_HappyPath, testImportDataFile_SadPath, test_RequestAndCheckIssuedCredentialByUser, test_ViewAndCheckEnabledCredentialbyUser } from '../src/steps';
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js' import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL, USER3_EMAIL } from '../src/constants/env_constants';
import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL } from '../src/constants/env_constants';
import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_CC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, FILE_TO_IMPORT_CC, FILE_TO_IMPORT_CC_EMPTY, FILE_TO_IMPORT_CC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_CC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_CC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_CC, SCHEMA_CC, SCHEMA_TYPE_CC } from '../src/constants/constants'; import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_CC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY, FILE_TO_IMPORT_CC, FILE_TO_IMPORT_CC_EMPTY, FILE_TO_IMPORT_CC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_CC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_CC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_CC, SCHEMA_CC, SCHEMA_TYPE_CC } from '../src/constants/constants';
/** /**
* Checking data section: view the lists of files imported, import data, delete... * Testing Admin->Data Section functionality with the NGO Course Credential (CC)
*/ */
test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', () => { test.describe('ADMIN-> DATA -> Import CC excel files', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB); await loginAsAdmin(page, URL_IDHUB);
@ -18,13 +17,14 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
await page.close(); await page.close();
}) })
/** /**
* Load of an excel file - Happy Path: * Load an excel file - Happy Path
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed, * Expected behavior:
* and the file appears in the imported files view. * - the file is loaded, the message: "The file was imported successfully!" is displayed.
*/ * - the file appears in the imported files view.
*/
test('DATA -> Import data file - Happy path - Course Credential - course-credential.xlsx ', async ({ page }) => { test('ADMIN-> DATA -> Import CC excel file - Happy Path', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_CC; const fileToImport = FILE_TO_IMPORT_CC;
const jsonSchema = JSON_SCHEMA_CC; const jsonSchema = JSON_SCHEMA_CC;
@ -40,7 +40,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
* Expected behavior: The error message: "The file you try to import is empty" * 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) - Course Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import CC excel file - Sad path (file well formatted but empty', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_CC_EMPTY; const fileToImport = FILE_TO_IMPORT_CC_EMPTY;
const jsonSchema = JSON_SCHEMA_CC; const jsonSchema = JSON_SCHEMA_CC;
@ -51,7 +51,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
}); });
test('DATA -> Import data file - Sad path (bad formatted file, without required columns) - Course Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import CC excel file - Sad path (bad formatted file, without required columns', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_CC_WITHOUT_REQUIRED_COLUMNS; const fileToImport = FILE_TO_IMPORT_CC_WITHOUT_REQUIRED_COLUMNS;
const jsonSchema = JSON_SCHEMA_CC; const jsonSchema = JSON_SCHEMA_CC;
@ -62,7 +62,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
}); });
test('DATA -> Import data file - Sad path (bad formatted file, with alien columns) - Course Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import CC excel file - Sad path (bad formatted file, with alien columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_CC_WITH_ALIEN_COLUMNS; const fileToImport = FILE_TO_IMPORT_CC_WITH_ALIEN_COLUMNS;
@ -75,7 +75,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
}); });
test('DATA -> Import data file - Sad path (file with required columns present but empty) - Course Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import CC excel file - Sad path (file with required columns present but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_CC_WITH_REQUIRED_EMPTY; const fileToImport = FILE_TO_IMPORT_CC_WITH_REQUIRED_EMPTY;
const jsonSchema = JSON_SCHEMA_CC; const jsonSchema = JSON_SCHEMA_CC;
@ -88,7 +88,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with Course Credential', ()
}) })
test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND Course Credential', () => { test.describe('USER -> My Credentials - enable and issue credentials', () => {
test.afterEach(async ({ page }) => { test.afterEach(async ({ page }) => {
await page.click('.logout'); await page.click('.logout');
@ -96,38 +96,38 @@ test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND
}) })
/** /**
* PRE-CONDITIONS: the admin has enabled a credential of type 'Course' for USER1_EMAIL * PRE-CONDITIONS: the admin has enabled sucessfully a credential in the previous test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* This is true, if the before test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* SUMMARY: * SUMMARY:
* - Check if the user1 can visualize the credentials that has been enabled in "My Credentials" * - Check if the user can visualize the credentials that has been enabled in "My Credentials"
* - Check the fields displayed when user click "View" Credential * - Check that the fields displayed in "View" Credential are the expected ones
*/ */
test('USER Credentials -> My Credentials -> View enabled Course Credential - for user1@example.org', async ({ page }) => { test('USER -> My Credentials -> View CC enabled Credential for user1', async ({ page }) => {
// View the Course Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_CC;
await loginAsUser(page, USER1_EMAIL, URL_IDHUB); let user = USER1_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_CC);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_CC);
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_CC);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER Credentials -> My Credentials -> View enabled Course Credential - for user2@example.org', async ({ page }) => { test('USER -> My Credentials -> View CC enabled Credential for user2', async ({ page }) => {
// View the Course Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_CC;
await loginAsUser(page, USER2_EMAIL, URL_IDHUB); let user = USER2_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_CC);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_CC);
let expectedCredential = await expectedCredentialSubjectForUser(USER2_EMAIL, SCHEMA_TYPE_CC);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER -> My Credentials -> Request the issuance of a CC for user3', async ({ page }) => {
let schemaType = SCHEMA_CC;
let user = USER3_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_RequestAndCheckIssuedCredentialByUser(page, schemaType, user);
});
}) })

View File

@ -1,14 +1,13 @@
import { test, expect } from '@playwright/test' import { test} from '@playwright/test'
import { loginAsAdmin, loginAsUser, gotoViewEnabledCredential, expectedCredentialSubjectForUser, testImportDataFile_HappyPath, testImportDataFile_SadPath } from '../src/steps'; import { loginAsAdmin, loginAsUser, testImportDataFile_HappyPath, testImportDataFile_SadPath, test_RequestAndCheckIssuedCredentialByUser, test_ViewAndCheckEnabledCredentialbyUser } from '../src/steps';
import { ViewCredentialPage } from '../src/page-objects/US_ViewCredentialPage.js' import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL, USER3_EMAIL } from '../src/constants/env_constants';
import { URL_IDHUB, USER1_EMAIL, USER2_EMAIL } from '../src/constants/env_constants'; import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_EOC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY_EOC,FILE_TO_IMPORT_EOC, FILE_TO_IMPORT_EOC_EMPTY, FILE_TO_IMPORT_EOC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_EOC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_EOC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_EOC, SCHEMA_EOC, SCHEMA_TYPE_EOC } from '../src/constants/constants';
import { ALERT_FILE_TO_IMPORT_IS_EMPTY, ALERT_FILE_TO_IMPORT_WITHOUT_REQUIRED_COLUMS_EOC, ALERT_FILE_TO_IMPORT_WITH_ALIEN_COLUMS, ALERT_FILE_TO_IMPORT_WITH_REQUIRED_COLUMS_EMPTY_EOC, FILE_TO_IMPORT_EOC, FILE_TO_IMPORT_EOC_EMPTY, FILE_TO_IMPORT_EOC_WITHOUT_REQUIRED_COLUMNS, FILE_TO_IMPORT_EOC_WITH_ALIEN_COLUMNS, FILE_TO_IMPORT_EOC_WITH_REQUIRED_EMPTY, JSON_SCHEMA_EOC, SCHEMA_EOC, SCHEMA_TYPE_EOC } from '../src/constants/constants';
/** /**
* Checking data section: view the lists of files imported, import data, delete... * Testing Admin->Data Section functionality with the E-Operator Claim Credential (EOP)
*/ */
test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Credential', () => { test.describe('ADMIN-> DATA -> Import EOP excel files', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await loginAsAdmin(page, URL_IDHUB); await loginAsAdmin(page, URL_IDHUB);
@ -18,14 +17,14 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
await page.close(); await page.close();
}) })
/**
/**
* Load an excel file - Happy Path * Load an excel file - Happy Path
* Expected behavior: the file is loaded, the message:"The file was imported successfully!" is displayed, * Expected behavior:
* and the file appears in the imported files view. * - the file is loaded, the message: "The file was imported successfully!" is displayed.
* - the file appears in the imported files view.
*/ */
test('DATA -> Import data file - Happy path - E-Operator Claim Credential - e-operator-claim.xlsx ', async ({ page }) => { test('ADMIN-> DATA -> Import EOC excel file - Happy Path', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_EOC; const fileToImport = FILE_TO_IMPORT_EOC;
const jsonSchema = JSON_SCHEMA_EOC; const jsonSchema = JSON_SCHEMA_EOC;
@ -40,7 +39,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
* Expected behavior: The error message: "The file you try to import is empty" * 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) - E-Operator Claim Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import EOC excel file - Sad path (file well formatted but empty', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_EOC_EMPTY; const fileToImport = FILE_TO_IMPORT_EOC_EMPTY;
const jsonSchema = JSON_SCHEMA_EOC; const jsonSchema = JSON_SCHEMA_EOC;
@ -56,7 +55,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
* Try to load a bad formatted file, without required data. * Try to load a bad formatted file, without required data.
*/ */
test('DATA -> Import data file - Sad path (bad formatted file, without required columns) - E-Operator Claim Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import EOC excel file - Sad path (bad formatted file, without required columns', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_EOC_WITHOUT_REQUIRED_COLUMNS; const fileToImport = FILE_TO_IMPORT_EOC_WITHOUT_REQUIRED_COLUMNS;
const jsonSchema = JSON_SCHEMA_EOC; const jsonSchema = JSON_SCHEMA_EOC;
@ -67,7 +66,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
}); });
test('DATA -> Import data file - Sad path (file bad formatted, with alien columns) - E-Operator Claim Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import EOC excel file - Sad path (bad formatted file, with alien columns)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_EOC_WITH_ALIEN_COLUMNS; const fileToImport = FILE_TO_IMPORT_EOC_WITH_ALIEN_COLUMNS;
const jsonSchema = JSON_SCHEMA_EOC; const jsonSchema = JSON_SCHEMA_EOC;
@ -78,7 +77,7 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
}); });
test('DATA -> Import data file - Sad path (file with required columns present but empty) - E-Operator Claim Credential ', async ({ page }) => { test('ADMIN-> DATA -> Import EOC excel file - Sad path (file with required columns present but empty)', async ({ page }) => {
const fileToImport = FILE_TO_IMPORT_EOC_WITH_REQUIRED_EMPTY; const fileToImport = FILE_TO_IMPORT_EOC_WITH_REQUIRED_EMPTY;
const jsonSchema = JSON_SCHEMA_EOC; const jsonSchema = JSON_SCHEMA_EOC;
@ -91,46 +90,46 @@ test.describe('ADMIN-> DATA Section Tests - Testing with E-Operator Claim Creden
}) })
test.describe('USER -> Credentials Section Tests - testing with USER1_EMAIL AND E-Operator Claim Credential', () => { test.describe('USER -> My Credentials - enable and issue credentials', () => {
test.afterEach(async ({ page }) => { //este se ejecutará despues de cada test test.afterEach(async ({ page }) => { //este se ejecutará despues de cada test
await page.click('.logout'); await page.click('.logout');
await page.close(); await page.close();
}) })
/** /**
* PRE-CONDITIONS: the admin has enabled a credential of type 'E-Operator Claim' for USER1_EMAIL * PRE-CONDITIONS: the admin has enabled sucessfully a credential in the previous test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* This is true, if the before test (DATA -> Import data- HAPPY PATH has been passed sucessfully)
* SUMMARY: * SUMMARY:
* - Check if the user1 can visualize the credentials that has been enabled in "My Credentials" * - Check if the user can visualize the credentials that has been enabled in "My Credentials"
* - Check the fields displayed when user click "View" Credential * - Check that the fields displayed in "View" Credential are the expected ones
*/ */
test('USER Credentials -> My Credentials -> View enabled E-Operator Claim Credential - for user1@example.org' , async ({ page }) => { test('USER -> My Credentials -> View EOC enabled Credential for user1', async ({ page }) => {
// View the E-Operator Claim Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_EOC;
await loginAsUser(page, USER1_EMAIL, URL_IDHUB); let user = USER1_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_EOC);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_EOC);
let expectedCredential = await expectedCredentialSubjectForUser(USER1_EMAIL, SCHEMA_TYPE_EOC);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER Credentials -> My Credentials -> View enabled E-Operator Claim Credential - for user2@example.org' , async ({ page }) => { test('USER -> My Credentials -> View EOC enabled Credential for user2', async ({ page }) => {
// View the E-Operator Claim Credential in status 'Enabled' for the user let schemaType = SCHEMA_TYPE_EOC;
await loginAsUser(page, USER2_EMAIL, URL_IDHUB); let user = USER2_EMAIL;
await gotoViewEnabledCredential(page, SCHEMA_TYPE_EOC);
const enabledCredentialView = new ViewCredentialPage(page); await loginAsUser(page, user, URL_IDHUB);
//Check that required fields exist and have a valid value in the current enabled credential await test_ViewAndCheckEnabledCredentialbyUser(page, schemaType, user);
//Get the credential subject values of the credential visualized in the screen and compare to the model
let actualCredential = await enabledCredentialView.buildACredentialFromInputValues(SCHEMA_TYPE_EOC);
let expectedCredential = await expectedCredentialSubjectForUser(USER2_EMAIL, SCHEMA_TYPE_EOC);
expect(actualCredential).toEqual(expectedCredential);
}); });
test('USER -> My Credentials -> Request the issuance of a EOC for user3', async ({ page }) => {
let schemaType = SCHEMA_EOC;
let user = USER3_EMAIL;
await loginAsUser(page, user, URL_IDHUB);
await test_RequestAndCheckIssuedCredentialByUser(page, schemaType, user);
});
}) })

View File

@ -1,35 +0,0 @@
[
{
"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"
}
]

Binary file not shown.