diff --git a/e2e/README.md b/e2e/README.md index 23b6e39324..2ee0d9b9f5 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -75,7 +75,7 @@ npm test -- --headed Or, for [Portalicious](../interfaces/Portalicious/)-specific tests: ```shell -npm run test:portalicious +npm run test:portalicious ``` ### Using the VS Code-extension diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 9c55c4ca95..651d1502b1 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -389,6 +389,7 @@ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "playwright": "1.50.1" }, @@ -1131,9 +1132,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/e2e/portalicious/pages/BasePage.ts b/e2e/portalicious/pages/BasePage.ts index 190f6883d7..2d845d34dc 100644 --- a/e2e/portalicious/pages/BasePage.ts +++ b/e2e/portalicious/pages/BasePage.ts @@ -14,6 +14,7 @@ class BasePage { readonly formError: Locator; readonly toast: Locator; readonly chooseFileButton: Locator; + readonly dialog: Locator; constructor(page: Page) { this.page = page; @@ -32,6 +33,7 @@ class BasePage { this.chooseFileButton = this.page.getByRole('button', { name: 'Choose file', }); + this.dialog = this.page.getByRole('alertdialog'); } async openSidebar() { @@ -43,7 +45,9 @@ class BasePage { await this.sidebar.getByRole('link', { name: pageName }).click(); } - async navigateToProgramPage(pageName: string) { + async navigateToProgramPage( + pageName: 'Registrations' | 'Payments' | 'Monitoring' | 'Team', + ) { await this.projectHeader.getByRole('tab', { name: pageName }).click(); } diff --git a/e2e/portalicious/pages/RegistrationActivityLogPage.ts b/e2e/portalicious/pages/RegistrationActivityLogPage.ts index fca27cbeb7..d0f1c6399c 100644 --- a/e2e/portalicious/pages/RegistrationActivityLogPage.ts +++ b/e2e/portalicious/pages/RegistrationActivityLogPage.ts @@ -2,15 +2,14 @@ import { expect } from '@playwright/test'; import { Page } from 'playwright'; import TableComponent from '@121-e2e/portalicious/components/TableComponent'; -import BasePage from '@121-e2e/portalicious/pages/BasePage'; -class RegistrationActivityLogPage extends BasePage { - readonly page: Page; +import RegistrationBasePage from './RegistrationBasePage'; + +class RegistrationActivityLogPage extends RegistrationBasePage { readonly table: TableComponent; constructor(page: Page) { super(page); - this.page = page; this.table = new TableComponent(page); } diff --git a/e2e/portalicious/pages/RegistrationBasePage.ts b/e2e/portalicious/pages/RegistrationBasePage.ts new file mode 100644 index 0000000000..6de1d87ece --- /dev/null +++ b/e2e/portalicious/pages/RegistrationBasePage.ts @@ -0,0 +1,36 @@ +import { Locator, Page } from 'playwright'; +import { expect } from 'playwright/test'; + +import BasePage from './BasePage'; + +abstract class RegistrationBasePage extends BasePage { + readonly duplicateChip: Locator; + readonly duplicatesBanner: Locator; + + constructor(page: Page) { + super(page); + this.duplicateChip = this.page.getByTestId('duplicate-chip'); + this.duplicatesBanner = this.page.getByTestId('duplicates-banner'); + } + + async goToRegistrationPage( + page: 'Activity log' | 'Personal information' | 'Debit cards', + ) { + await this.page.getByRole('tab', { name: page }).click(); + } + + async assertDuplicateWith({ duplicateName }: { duplicateName: string }) { + await this.assertDuplicateStatus({ status: 'Duplicate' }); + + await expect(this.duplicatesBanner).toBeVisible(); + await expect(this.duplicatesBanner).toContainText('Duplicated with:'); + await expect(this.duplicatesBanner).toContainText(duplicateName); + } + + async assertDuplicateStatus({ status }: { status: string }) { + await expect(this.duplicateChip).toBeVisible(); + await expect(this.duplicateChip).toContainText(status); + } +} + +export default RegistrationBasePage; diff --git a/e2e/portalicious/pages/RegistrationPersonalInformationPage.ts b/e2e/portalicious/pages/RegistrationPersonalInformationPage.ts new file mode 100644 index 0000000000..d7156a8c89 --- /dev/null +++ b/e2e/portalicious/pages/RegistrationPersonalInformationPage.ts @@ -0,0 +1,40 @@ +import { Locator, Page } from 'playwright'; +import { expect } from 'playwright/test'; + +import RegistrationBasePage from './RegistrationBasePage'; + +class RegistrationPersonalInformationPage extends RegistrationBasePage { + readonly editInformationButton: Locator; + readonly editInformationReasonField: Locator; + + constructor(page: Page) { + super(page); + this.editInformationButton = this.page.getByRole('button', { + name: 'Edit information', + }); + this.editInformationReasonField = this.page.getByLabel( + 'Write a reason for the update', + ); + } + + async editRegistration({ + field, + value, + reason = 'E2E test', + }: { + field: string; + value: string; + reason?: string; + }) { + await this.editInformationButton.click(); + await this.page.getByLabel(field).fill(value); + await this.page.getByRole('button', { name: 'Save' }).click(); + await this.editInformationReasonField.fill(reason); + await this.dialog.getByRole('button', { name: 'Save' }).click(); + + // this re-appears after the save has been successful + await expect(this.editInformationButton).toBeVisible(); + } +} + +export default RegistrationPersonalInformationPage; diff --git a/e2e/portalicious/pages/RegistrationsPage.ts b/e2e/portalicious/pages/RegistrationsPage.ts index adbbac8f8d..8991807505 100644 --- a/e2e/portalicious/pages/RegistrationsPage.ts +++ b/e2e/portalicious/pages/RegistrationsPage.ts @@ -7,6 +7,8 @@ import * as XLSX from 'xlsx'; import TableComponent from '@121-e2e/portalicious/components/TableComponent'; import BasePage from '@121-e2e/portalicious/pages/BasePage'; +import { expectedSortedArraysToEqual } from '../utils'; + const expectedColumnsSelectedRegistrationsExport = [ 'referenceId', 'id', @@ -505,6 +507,11 @@ class RegistrationsPage extends BasePage { validateExactRowCount, ); } + + async assertDuplicateColumnValues(expectedValues: string[]) { + const duplicateColumnValues = await this.table.getTextArrayFromColumn(5); + expectedSortedArraysToEqual(duplicateColumnValues, expectedValues); + } } export default RegistrationsPage; diff --git a/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBadges.spec.ts b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBadges.spec.ts new file mode 100644 index 0000000000..86e8c0e2b7 --- /dev/null +++ b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBadges.spec.ts @@ -0,0 +1,56 @@ +import { test } from '@playwright/test'; + +import { SeedScript } from '@121-service/src/scripts/enum/seed-script.enum'; +import { seedIncludedRegistrations } from '@121-service/test/helpers/registration.helper'; +import { + getAccessToken, + resetDB, +} from '@121-service/test/helpers/utility.helper'; +import { registrationsPV } from '@121-service/test/registrations/pagination/pagination-data'; + +import HomePage from '@121-e2e/portalicious/pages/HomePage'; +import LoginPage from '@121-e2e/portalicious/pages/LoginPage'; +import RegistrationsPage from '@121-e2e/portalicious/pages/RegistrationsPage'; + +test.beforeEach(async ({ page }) => { + await resetDB(SeedScript.nlrcMultiple); + const programIdPV = 2; + + const accessToken = await getAccessToken(); + await seedIncludedRegistrations(registrationsPV, programIdPV, accessToken); + + // Login + const loginPage = new LoginPage(page); + await page.goto('/'); + await loginPage.login( + process.env.USERCONFIG_121_SERVICE_EMAIL_ADMIN, + process.env.USERCONFIG_121_SERVICE_PASSWORD_ADMIN, + ); +}); + +test('[33854] Validate that duplicate badges are present in the UI', async ({ + page, +}) => { + const homePage = new HomePage(page); + const registrations = new RegistrationsPage(page); + + const projectTitle = 'NLRC Direct Digital Aid Program (PV)'; + + await test.step('Select program', async () => { + await homePage.selectProgram(projectTitle); + }); + + await test.step('Wait for registrations to load', async () => { + const allRegistrationsCount = registrationsPV.length; + await registrations.waitForLoaded(allRegistrationsCount); + }); + + await test.step('Verify contents of duplicate column', async () => { + await registrations.assertDuplicateColumnValues([ + 'Unique', + 'Duplicate', + 'Duplicate', + 'Unique', + ]); + }); +}); diff --git a/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBanner.spec.ts b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBanner.spec.ts new file mode 100644 index 0000000000..0b9bad2a2b --- /dev/null +++ b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateBanner.spec.ts @@ -0,0 +1,111 @@ +import { expect, test } from '@playwright/test'; + +import { SeedScript } from '@121-service/src/scripts/enum/seed-script.enum'; +import { seedIncludedRegistrations } from '@121-service/test/helpers/registration.helper'; +import { + getAccessToken, + resetDB, +} from '@121-service/test/helpers/utility.helper'; +import { registrationsPV } from '@121-service/test/registrations/pagination/pagination-data'; + +import HomePage from '@121-e2e/portalicious/pages/HomePage'; +import LoginPage from '@121-e2e/portalicious/pages/LoginPage'; +import RegistrationActivityLogPage from '@121-e2e/portalicious/pages/RegistrationActivityLogPage'; +import RegistrationsPage from '@121-e2e/portalicious/pages/RegistrationsPage'; + +test.beforeEach(async ({ page }) => { + const programIdPV = 2; + await resetDB(SeedScript.nlrcMultiple); + + const accessToken = await getAccessToken(); + await seedIncludedRegistrations(registrationsPV, programIdPV, accessToken); + + // Login + const loginPage = new LoginPage(page); + await page.goto('/'); + await loginPage.login( + process.env.USERCONFIG_121_SERVICE_EMAIL_ADMIN, + process.env.USERCONFIG_121_SERVICE_PASSWORD_ADMIN, + ); +}); + +test('[33855] Validate that "Duplicate" banner is displayed in overview of duplicated registrations', async ({ + page, +}) => { + const homePage = new HomePage(page); + const registrations = new RegistrationsPage(page); + const registrationActivityLogPage = new RegistrationActivityLogPage(page); + + const projectTitle = 'NLRC Direct Digital Aid Program (PV)'; + + const duplicateRegistrationA = registrationsPV[1]; // 'Jan Janssen' + const duplicateRegistrationB = registrationsPV[2]; // 'Joost Herlembach' + const uniqueRegistration = registrationsPV[0]; // 'Gemma Houtenbos' + + await test.step('Select program', async () => { + await homePage.selectProgram(projectTitle); + }); + + await test.step('Wait for registrations to load', async () => { + const allRegistrationsCount = registrationsPV.length; + await registrations.waitForLoaded(allRegistrationsCount); + }); + + await test.step('Open registration page', async () => { + await registrations.goToRegistrationByName({ + registrationName: duplicateRegistrationA.fullName, + }); + }); + + await test.step('View banner with duplicate', async () => { + await registrationActivityLogPage.assertDuplicateWith({ + duplicateName: duplicateRegistrationB.fullName, + }); + }); + + await test.step('Verify link to duplicate works', async () => { + const duplicateBLink = + await registrationActivityLogPage.duplicatesBanner.getByRole('link', { + name: duplicateRegistrationB.fullName, + }); + + await expect(duplicateBLink).toBeVisible(); + await duplicateBLink.click(); + }); + + await test.step('Verify new tab is opened and contains link to orignial duplicate', async () => { + await page.waitForTimeout(2000); //waitForNavigation and waitForLoadState do not work in this case + + const pages = await page.context().pages(); + + await expect(pages).toHaveLength(2); + + const registrationActivityLogPageForDuplicateB = + new RegistrationActivityLogPage(pages[1]); + + await registrationActivityLogPageForDuplicateB.assertDuplicateWith({ + duplicateName: duplicateRegistrationA.fullName, + }); + }); + + await test.step('Navigate back to registrations table', async () => { + await page.bringToFront(); + await page.goBack(); + }); + + await test.step('Open registration page for unique registration', async () => { + await registrations.goToRegistrationByName({ + registrationName: uniqueRegistration.fullName, + }); + }); + + await test.step('Verify no banner is displayed for unique registration', async () => { + await expect( + registrationActivityLogPage.duplicatesBanner, + ).not.toBeVisible(); + + await registrationActivityLogPage.assertDuplicateStatus({ + status: 'Unique', + }); + }); +}); diff --git a/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChanges.spec.ts b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChanges.spec.ts new file mode 100644 index 0000000000..75ddb1d55d --- /dev/null +++ b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChanges.spec.ts @@ -0,0 +1,101 @@ +import { expect, test } from '@playwright/test'; + +import { SeedScript } from '@121-service/src/scripts/enum/seed-script.enum'; +import { seedIncludedRegistrations } from '@121-service/test/helpers/registration.helper'; +import { + getAccessToken, + resetDB, +} from '@121-service/test/helpers/utility.helper'; +import { registrationsPV } from '@121-service/test/registrations/pagination/pagination-data'; + +import HomePage from '@121-e2e/portalicious/pages/HomePage'; +import LoginPage from '@121-e2e/portalicious/pages/LoginPage'; +import RegistrationActivityLogPage from '@121-e2e/portalicious/pages/RegistrationActivityLogPage'; +import RegistrationPersonalInformationPage from '@121-e2e/portalicious/pages/RegistrationPersonalInformationPage'; +import RegistrationsPage from '@121-e2e/portalicious/pages/RegistrationsPage'; + +test.beforeEach(async ({ page }) => { + const programIdPV = 2; + await resetDB(SeedScript.nlrcMultiple); + + const accessToken = await getAccessToken(); + await seedIncludedRegistrations(registrationsPV, programIdPV, accessToken); + + // Login + const loginPage = new LoginPage(page); + await page.goto('/'); + await loginPage.login( + process.env.USERCONFIG_121_SERVICE_EMAIL_ADMIN, + process.env.USERCONFIG_121_SERVICE_PASSWORD_ADMIN, + ); +}); + +test('[33856] After the data change of duplicate registration, both registrations get unique badge', async ({ + page, +}) => { + const homePage = new HomePage(page); + const registrations = new RegistrationsPage(page); + const registrationActivityLogPage = new RegistrationActivityLogPage(page); + const registrationPersonalInformationPage = + new RegistrationPersonalInformationPage(page); + + const projectTitle = 'NLRC Direct Digital Aid Program (PV)'; + + const duplicateRegistration = registrationsPV[1]; // 'Jan Janssen' + + await test.step('Select program', async () => { + await homePage.selectProgram(projectTitle); + }); + + await test.step('Wait for registrations to load', async () => { + const allRegistrationsCount = registrationsPV.length; + await registrations.waitForLoaded(allRegistrationsCount); + }); + + await test.step('Open registration page and verify banner is present', async () => { + await registrations.goToRegistrationByName({ + registrationName: duplicateRegistration.fullName, + }); + + await expect(registrationActivityLogPage.duplicatesBanner).toBeVisible(); + }); + + await test.step('Edit registration to make it unique', async () => { + await registrationActivityLogPage.goToRegistrationPage( + 'Personal information', + ); + + await registrationPersonalInformationPage.editRegistration({ + field: 'Phone Number', + value: '11111', + }); + + await registrationPersonalInformationPage.editRegistration({ + field: 'WhatsApp Nr.', + value: '11111', + }); + }); + + await test.step('Verify banner has disappeared and registration is now unique', async () => { + await expect( + registrationActivityLogPage.duplicatesBanner, + ).not.toBeVisible(); + + await registrationActivityLogPage.assertDuplicateStatus({ + status: 'Unique', + }); + }); + + await test.step('Navigate back to registrations table', async () => { + await registrationActivityLogPage.navigateToProgramPage('Registrations'); + }); + + await test.step('Verify all registrations are unique now', async () => { + await registrations.assertDuplicateColumnValues([ + 'Unique', + 'Unique', + 'Unique', + 'Unique', + ]); + }); +}); diff --git a/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChangesFor3Registrations.spec.ts b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChangesFor3Registrations.spec.ts new file mode 100644 index 0000000000..219b780fe2 --- /dev/null +++ b/e2e/portalicious/tests/ViewAndManagePeopleAffected/ValidateDuplicateChangesFor3Registrations.spec.ts @@ -0,0 +1,127 @@ +import { expect, test } from '@playwright/test'; + +import { SeedScript } from '@121-service/src/scripts/enum/seed-script.enum'; +import { seedIncludedRegistrations } from '@121-service/test/helpers/registration.helper'; +import { + getAccessToken, + resetDB, +} from '@121-service/test/helpers/utility.helper'; +import { registrationsPV } from '@121-service/test/registrations/pagination/pagination-data'; + +import HomePage from '@121-e2e/portalicious/pages/HomePage'; +import LoginPage from '@121-e2e/portalicious/pages/LoginPage'; +import RegistrationActivityLogPage from '@121-e2e/portalicious/pages/RegistrationActivityLogPage'; +import RegistrationPersonalInformationPage from '@121-e2e/portalicious/pages/RegistrationPersonalInformationPage'; +import RegistrationsPage from '@121-e2e/portalicious/pages/RegistrationsPage'; + +const duplicateRegistration = registrationsPV[1]; // 'Jan Janssen' +// making sure we have 3 duplicate registrations +const extraDuplicate = { + ...duplicateRegistration, + referenceId: '11111', + fullName: 'Mr. Extra Duplicate', +}; + +const seededRegistrations = [...registrationsPV, extraDuplicate]; + +test.beforeEach(async ({ page }) => { + const programIdPV = 2; + await resetDB(SeedScript.nlrcMultiple); + + const accessToken = await getAccessToken(); + + await seedIncludedRegistrations( + seededRegistrations, + programIdPV, + accessToken, + ); + + // Login + const loginPage = new LoginPage(page); + await page.goto('/'); + await loginPage.login( + process.env.USERCONFIG_121_SERVICE_EMAIL_ADMIN, + process.env.USERCONFIG_121_SERVICE_PASSWORD_ADMIN, + ); +}); + +test('[33879] After the data change of 1 out of 3 duplicates, only 1 registration gets unique badge', async ({ + page, +}) => { + const homePage = new HomePage(page); + const registrations = new RegistrationsPage(page); + const registrationActivityLogPage = new RegistrationActivityLogPage(page); + const registrationPersonalInformationPage = + new RegistrationPersonalInformationPage(page); + + const projectTitle = 'NLRC Direct Digital Aid Program (PV)'; + + const duplicateRegistration = registrationsPV[1]; // 'Jan Janssen' + + await test.step('Select program', async () => { + await homePage.selectProgram(projectTitle); + }); + + await test.step('Wait for registrations to load', async () => { + const allRegistrationsCount = seededRegistrations.length; + await registrations.waitForLoaded(allRegistrationsCount); + }); + + await test.step('Verify we have three duplicate registrations', async () => { + await registrations.assertDuplicateColumnValues([ + 'Unique', + 'Duplicate', + 'Duplicate', + 'Unique', + 'Duplicate', + ]); + }); + + await test.step('Open registration page and verify banner is present', async () => { + await registrations.goToRegistrationByName({ + registrationName: duplicateRegistration.fullName, + }); + + await expect(registrationActivityLogPage.duplicatesBanner).toBeVisible(); + }); + + await test.step('Edit registration to make it unique', async () => { + await registrationActivityLogPage.goToRegistrationPage( + 'Personal information', + ); + + await registrationPersonalInformationPage.editRegistration({ + field: 'Phone Number', + value: '11111', + }); + + await registrationPersonalInformationPage.editRegistration({ + field: 'WhatsApp Nr.', + value: '11111', + }); + }); + + await test.step('Verify banner has disappeared and registration is now unique', async () => { + await expect( + registrationActivityLogPage.duplicatesBanner, + ).not.toBeVisible(); + + await registrationActivityLogPage.assertDuplicateStatus({ + status: 'Unique', + }); + }); + + await test.step('Navigate back to registrations table', async () => { + await registrationActivityLogPage.navigateToProgramPage('Registrations'); + }); + + await test.step('Verify that we now have 2 duplicate registrations', async () => { + await registrations.assertDuplicateColumnValues([ + 'Unique', + 'Unique', + 'Duplicate', + 'Unique', + 'Duplicate', + ]); + }); +}); diff --git a/interfaces/Portalicious/src/app/components/registration-page-layout/components/registration-duplicates-banner/registration-duplicates-banner.component.html b/interfaces/Portalicious/src/app/components/registration-page-layout/components/registration-duplicates-banner/registration-duplicates-banner.component.html index b97b6d60c1..960e64ed07 100644 --- a/interfaces/Portalicious/src/app/components/registration-page-layout/components/registration-duplicates-banner/registration-duplicates-banner.component.html +++ b/interfaces/Portalicious/src/app/components/registration-page-layout/components/registration-duplicates-banner/registration-duplicates-banner.component.html @@ -5,6 +5,7 @@ 'border-blue-500 bg-blue-100 text-blue-700': loading(), 'border-red-500 bg-red-100 text-red-700': !loading(), }" + data-testid="duplicates-banner" > }