From 9a5760a298fa787487fa816a3bca3e74d840a72f Mon Sep 17 00:00:00 2001 From: Goeme Nthomiwa Date: Tue, 8 Oct 2024 09:34:04 -0700 Subject: [PATCH] feat: geo 870 e2e admin report search and locking end to end test (#801) --- admin-frontend/e2e/pages/admin-portal-page.ts | 17 +- .../pages/announcements/announcements-page.ts | 15 +- .../e2e/pages/reports/search-reports-page.ts | 191 ++++++++++++++++++ admin-frontend/e2e/reports.spec.ts | 33 +++ .../src/components/ReportSearchFilters.vue | 10 +- 5 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 admin-frontend/e2e/pages/reports/search-reports-page.ts create mode 100644 admin-frontend/e2e/reports.spec.ts diff --git a/admin-frontend/e2e/pages/admin-portal-page.ts b/admin-frontend/e2e/pages/admin-portal-page.ts index ec2005e4d..fa0c69633 100644 --- a/admin-frontend/e2e/pages/admin-portal-page.ts +++ b/admin-frontend/e2e/pages/admin-portal-page.ts @@ -1,6 +1,8 @@ import { Page, expect } from '@playwright/test'; import { User } from '../auth.setup'; import { PagePaths } from '../utils'; +import { DateTimeFormatter, ZonedDateTime, ZoneId } from '@js-joda/core'; +import { Locale } from '@js-joda/locale_en'; export class AdminPortalPage { constructor(public readonly page: Page) {} @@ -27,7 +29,7 @@ export class AdminPortalPage { await expect( this.page.getByRole('link', { name: 'Announcements' }), ).toBeVisible(); - // TODO: bring it back after the + // TODO: bring it back after the // await expect( // this.page.getByRole('link', { name: 'User Management' }), // ).toBeVisible(); @@ -53,4 +55,17 @@ export class AdminPortalPage { await this.page.waitForTimeout(1000); await this.page.waitForURL(PagePaths.LOGOUT); } + + formatDate( + inDateStr: string, + inFormatter = DateTimeFormatter.ISO_DATE_TIME, + outFormatter = DateTimeFormatter.ofPattern('MMM d, yyyy').withLocale( + Locale.CANADA, + ), + ) { + const date = ZonedDateTime.parse(inDateStr, inFormatter); + const localTz = ZoneId.systemDefault(); + const dateInLocalTz = date.withZoneSameInstant(localTz); + return outFormatter.format(dateInLocalTz); + } } diff --git a/admin-frontend/e2e/pages/announcements/announcements-page.ts b/admin-frontend/e2e/pages/announcements/announcements-page.ts index 56c417885..3ce2dc69a 100644 --- a/admin-frontend/e2e/pages/announcements/announcements-page.ts +++ b/admin-frontend/e2e/pages/announcements/announcements-page.ts @@ -1,8 +1,6 @@ import { expect, Locator, Page } from 'playwright/test'; import { PagePaths } from '../../utils'; import { AdminPortalPage } from '../admin-portal-page'; -import { DateTimeFormatter, ZonedDateTime, ZoneId } from '@js-joda/core'; -import { Locale } from '@js-joda/locale_en'; import { AnnouncementStatus } from '../../types'; export class AnnouncementsPage extends AdminPortalPage { @@ -202,16 +200,5 @@ export class AnnouncementsPage extends AdminPortalPage { return getAnnouncementResponse; } - private formatDate( - inDateStr: string, - inFormatter = DateTimeFormatter.ISO_DATE_TIME, - outFormatter = DateTimeFormatter.ofPattern('MMM d, yyyy').withLocale( - Locale.CANADA, - ), - ) { - const date = ZonedDateTime.parse(inDateStr, inFormatter); - const localTz = ZoneId.systemDefault(); - const dateInLocalTz = date.withZoneSameInstant(localTz); - return outFormatter.format(dateInLocalTz); - } + } diff --git a/admin-frontend/e2e/pages/reports/search-reports-page.ts b/admin-frontend/e2e/pages/reports/search-reports-page.ts new file mode 100644 index 000000000..065dbb42f --- /dev/null +++ b/admin-frontend/e2e/pages/reports/search-reports-page.ts @@ -0,0 +1,191 @@ +import { expect, Locator, Page } from 'playwright/test'; +import { AdminPortalPage } from '../admin-portal-page'; +import { PagePaths } from '../../utils'; +import { groupBy } from 'lodash'; + +export class SearchReportsPage extends AdminPortalPage { + static PATH = PagePaths.REPORTS; + searchInput: Locator; + searchButton: Locator; + filterButton: Locator; + + static async visit( + page: Page, + ): Promise<{ searchReportsPage: SearchReportsPage; reports: any[] }> { + const searchResponse = SearchReportsPage.waitForSearchResults(page); + await page.goto(SearchReportsPage.PATH); + const searchReportsPage = new SearchReportsPage(page); + await searchReportsPage.setup(); + const response = await searchResponse; + const { reports } = await response.json(); + return { searchReportsPage, reports }; + } + + async setup(): Promise { + super.setup(); + this.searchInput = await this.page.getByLabel('Search by company name'); + this.searchButton = await this.page.getByRole('button', { name: 'Search' }); + this.filterButton = await this.page.getByRole('button', { + name: 'Filter', + }); + await this.expectElementToBeVisible(this.searchInput); + await this.expectElementToBeVisible(this.searchButton); + await this.expectElementToBeVisible(this.filterButton); + } + + async searchReports(companyName: string) { + await this.searchInput.fill(companyName); + return this.clickSearchButton(); + } + + async filterReports(is_unlocked: boolean) { + await this.filterButton.click(); + const reportYearInput = await this.page.getByLabel('Report Year'); + const statusButton = await this.page.getByLabel('Locked/Unlocked'); + + await expect(reportYearInput).toBeVisible(); + await expect(statusButton).toBeVisible(); + + await reportYearInput.click({ force: true, button: 'right' }); + const currentYear = new Date().getFullYear(); + await this.page.waitForTimeout(2000); + const option = await this.page.getByLabel(`Year: ${currentYear}`); + await option.click(); + const lockStatus = is_unlocked ? 'Unlocked' : 'Locked'; + await this.page.waitForTimeout(2000); + await statusButton.click({ force: true, button: 'right' }); + const lockOption = await this.page.getByText(lockStatus, { exact: true }); + await lockOption.click(); + + const filterResponse = SearchReportsPage.waitForSearchResults(this.page); + const applyButton = await this.page.getByRole('button', { name: 'Apply' }); + await applyButton.click(); + const response = await filterResponse; + const { reports } = await response.json(); + + return reports; + } + + async clickSearchButton() { + const searchResponse = SearchReportsPage.waitForSearchResults(this.page); + await this.searchButton.click(); + const response = await searchResponse; + const { reports } = await response.json(); + return reports; + } + + async searchAndVerifyReports(companyName: string, validatedLength = true) { + const reports = await this.searchReports(companyName); + if (!validatedLength) { + expect(reports.length).toBeGreaterThan(0); + } else { + expect(reports.length).toBe(1); + } + const report = reports[0]; + const { company_name } = report.pay_transparency_company; + await this.expectElementToBeVisible( + await this.page.getByText(company_name).first(), + ); + await this.expectElementToBeVisible( + await this.page.getByText(this.formatDate(report.create_date)).first(), + ); + await this.expectElementToBeVisible( + await this.page.getByText(report.naics_code).first(), + ); + const employeeCount = report.employee_count_range.employee_count_range; + await this.expectElementToBeVisible( + await this.page.getByText(employeeCount).first(), + ); + await this.expectElementToBeVisible( + await this.page.getByText(report.reporting_year, { exact: true }).first(), + ); + + await this.expectElementToBeVisible(await this.getOpenReportButton()); + await this.expectElementToBeVisible(await this.getLockReportButton()); + await this.expectElementToBeVisible(await this.getReportHistoryButton()); + + return report; + } + + async toggleReportLockAndverify(report) { + const lockButton = await this.getLockReportButton(); + const lockResponse = this.waitForReportLock(report.report_id); + await lockButton.click(); + const confirmButton = await this.page.getByRole('button', { + name: report.is_unlocked ? 'Yes, lock' : 'Yes, unlock', + }); + await confirmButton.click(); + const response = await lockResponse; + const patchedReport = await response.json(); + expect(patchedReport.is_unlocked).toBe(!report.is_unlocked); + } + + private async getOpenReportButton() { + const button = await this.page.getByRole('button', { name: 'Open report' }); + await this.expectElementToBeVisible(button.first()); + return button.first(); + } + + private async getLockReportButton() { + const button = await this.page.getByRole('button', { name: 'Lock report' }); + await this.expectElementToBeVisible(button.first()); + return button.first(); + } + + private async getReportHistoryButton() { + const button = await this.page.getByRole('button', { + name: 'Admin action history', + }); + await this.expectElementToBeVisible(button.first()); + return button.first(); + } + + static waitForSearchResults(page) { + return page.waitForResponse((res) => { + return ( + res.url().includes('/admin-api/v1/reports') && res.status() === 200 + ); + }); + } + + private waitForReportLock(reportId) { + console.log(`/admin-api/v1/reports/${reportId}`); + return this.page.waitForResponse((res) => { + return ( + res.url().includes(`/admin-api/v1/reports/${reportId}`) && + res.status() === 200 && + res.request().method() === 'PATCH' + ); + }); + } + + private waitForHistory(reportId) { + return this.page.waitForResponse((res) => { + return ( + res + .url() + .includes(`/admin-api/v1/reports/${reportId}/admin-action-history`) && + res.status() === 200 + ); + }); + } + + private async expectElementToBeVisible(element: Locator) { + await expect(element).toBeVisible(); + } + + static getCompanyNameWithOneReport(reports, isUnlocked = true) { + const groups = groupBy(reports, 'pay_transparency_company.company_name'); + console.log( + Object.keys(groups).find( + (key) => + groups[key].length === 1 && groups[key][0].is_unlocked === isUnlocked, + ), + ); + const companyName = Object.keys(groups).find( + (key) => + groups[key].length === 1 && groups[key][0].is_unlocked === isUnlocked, + ); + return companyName; + } +} diff --git a/admin-frontend/e2e/reports.spec.ts b/admin-frontend/e2e/reports.spec.ts new file mode 100644 index 000000000..eaa2a333a --- /dev/null +++ b/admin-frontend/e2e/reports.spec.ts @@ -0,0 +1,33 @@ +import { expect, test } from '@playwright/test'; +import { SearchReportsPage } from './pages/reports/search-reports-page'; + +test.describe('Reports', () => { + test('search reports', async ({ page }) => { + const { searchReportsPage: reportsPage, reports } = + await SearchReportsPage.visit(page); + const title = reports[0].pay_transparency_company.company_name; + await reportsPage.searchAndVerifyReports(title, false); + }); + + test.describe.serial('lock and unlock report', async () => { + test('lock', async ({ page }) => { + const { searchReportsPage: reportsPage } = + await SearchReportsPage.visit(page); + const reports = await reportsPage.filterReports(true); + expect(reports.length).toBeGreaterThan(0); + const title = reports[0].pay_transparency_company.company_name; + const report = await reportsPage.searchAndVerifyReports(title); + await reportsPage.toggleReportLockAndverify(report); + }); + + test('unlock', async ({ page }) => { + const { searchReportsPage: reportsPage } = + await SearchReportsPage.visit(page); + const reports = await reportsPage.filterReports(false); + expect(reports.length).toBeGreaterThan(0); + const title = reports[0].pay_transparency_company.company_name; + const report = await reportsPage.searchAndVerifyReports(title); + await reportsPage.toggleReportLockAndverify(report); + }); + }); +}); diff --git a/admin-frontend/src/components/ReportSearchFilters.vue b/admin-frontend/src/components/ReportSearchFilters.vue index f15af2acf..74dff1c5d 100644 --- a/admin-frontend/src/components/ReportSearchFilters.vue +++ b/admin-frontend/src/components/ReportSearchFilters.vue @@ -118,13 +118,19 @@
Year