From fed43898efe84b65ef640b8d77ec5d71289eceeb Mon Sep 17 00:00:00 2001 From: mythilytm Date: Wed, 16 Apr 2025 19:47:11 -0400 Subject: [PATCH 01/11] split caselist and caseview tests --- e2e-tests/caseList.ts | 1 + .../ui-tests/playwright.ui-test.config.ts | 2 +- e2e-tests/ui-tests/tests/case-list.spec.ts | 29 +++++++---- e2e-tests/ui-tests/tests/case-view.spec.ts | 51 +++++++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 e2e-tests/ui-tests/tests/case-view.spec.ts diff --git a/e2e-tests/caseList.ts b/e2e-tests/caseList.ts index 3a1fd401bd..b7f5a203a6 100644 --- a/e2e-tests/caseList.ts +++ b/e2e-tests/caseList.ts @@ -33,6 +33,7 @@ export type CaseSectionForm> = { export const caseList = (page: Page) => { const caseListPage = page.locator('div.Twilio-ViewCollection'); + console.log('Case List table is visible.'); const selectors = { caseListRowIdButton: caseListPage.locator( diff --git a/e2e-tests/ui-tests/playwright.ui-test.config.ts b/e2e-tests/ui-tests/playwright.ui-test.config.ts index 7ce8ff8eb5..0dbb516b2a 100644 --- a/e2e-tests/ui-tests/playwright.ui-test.config.ts +++ b/e2e-tests/ui-tests/playwright.ui-test.config.ts @@ -29,7 +29,7 @@ const config: PlaywrightTestConfig = { // Browser proxy option is required for Chromium on Windows launchOptions: { proxy: { server: `https://per-context` } }, ignoreHTTPSErrors: true, - // headless: false, // For local debugging + headless: false, // For local debugging }, testDir: './tests', timeout: 60000, diff --git a/e2e-tests/ui-tests/tests/case-list.spec.ts b/e2e-tests/ui-tests/tests/case-list.spec.ts index 56a03aa8c1..bd813d1bbd 100644 --- a/e2e-tests/ui-tests/tests/case-list.spec.ts +++ b/e2e-tests/ui-tests/tests/case-list.spec.ts @@ -84,18 +84,25 @@ test.describe.serial('Case List', () => { await scanFilterDialogue('Counselor'); await scanFilterDialogue('createdAtFilter'); await scanFilterDialogue('updatedAtFilter'); - await caseListPage.openFirstCaseButton(); - const caseHomeAccessibilityScanResults = await new AxeBuilder({ page }) - .include('div.Twilio-View-case-list') - .analyze(); - expect(caseHomeAccessibilityScanResults.violations).toEqual([]); - warnViolations(caseHomeAccessibilityScanResults, `the case home page`); - await caseListPage.editCase(); - const caseEditAccessibilityScanResults = await new AxeBuilder({ page }) + }); + + test('Case list accessibility: screen reader labels', async () => { + await page.goto('/case-list', { waitUntil: 'networkidle' }); + // Check that filter buttons have aria-label or accessible name + const filterButtons = await page.$$('[data-testid^="CaseList-Filter-"]'); + for (const btn of filterButtons) { + const ariaLabel = await btn.getAttribute('aria-label'); + const label = await btn.evaluate((el) => el.textContent?.trim() || ''); + expect(ariaLabel || label.length > 0).toBeTruthy(); + } + }); + + test('Case list accessibility: color contrast and ARIA compliance', async () => { + await page.goto('/case-list', { waitUntil: 'networkidle' }); + const results = await new AxeBuilder({ page }) .include('div.Twilio-View-case-list') + .withTags(['wcag2aa', 'wcag2a', 'section508']) .analyze(); - //expect(caseHomeAccessibilityScanResults.violations).toEqual([]); - warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); - await caseListPage.closeModal(); + expect(results.violations).toEqual([]); }); }); diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts new file mode 100644 index 0000000000..580adad5e9 --- /dev/null +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -0,0 +1,51 @@ +import { expect, Page, test } from '@playwright/test'; +import * as mockServer from '../flex-in-a-box/proxied-endpoints'; +import '../flex-in-a-box/local-resources'; +import hrmCases from '../aselo-service-mocks/hrm/cases'; +import hrmPermissions from '../aselo-service-mocks/hrm/permissions'; +import { caseList } from '../../caseList'; +import AxeBuilder from '@axe-core/playwright'; +import { aseloPage } from '../aselo-service-mocks/aselo-page'; +import type { AxeResults } from 'axe-core'; + +function warnViolations(results: AxeResults, componentDescription: string) { + if (results.violations.length) { + console.warn( + `${results.violations.length} accessibility violations found in ${componentDescription}.`, + ); + } +} + +test.describe.serial('Case View', () => { + let page: Page; + const cases = hrmCases(); + const permissions = hrmPermissions(); + + test.beforeAll(async ({ browser }) => { + await mockServer.start(); + page = await aseloPage(browser); + await cases.mockCaseEndpoints(page); + await permissions.mockPermissionEndpoint(page); + await page.goto('/case-list', { waitUntil: 'networkidle' }); + }); + + test.afterAll(async () => { + await mockServer.stop(); + }); + + test('Case view and edit passes AXE scan', async () => { + const caseListPage = caseList(page); + await caseListPage.openFirstCaseButton(); + const caseHomeAccessibilityScanResults = await new AxeBuilder({ page }) + .include('div.Twilio-View-case-list') + .analyze(); + expect(caseHomeAccessibilityScanResults.violations).toEqual([]); + warnViolations(caseHomeAccessibilityScanResults, `the case home page`); + await caseListPage.editCase(); + const caseEditAccessibilityScanResults = await new AxeBuilder({ page }) + .include('div.Twilio-View-case-list') + .analyze(); + warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); + await caseListPage.closeModal(); + }); +}); From 8cd3c71bf41f65116cf5367bdcf30f836a1a1d80 Mon Sep 17 00:00:00 2001 From: mythilytm Date: Thu, 17 Apr 2025 10:50:55 -0400 Subject: [PATCH 02/11] more separation of caselist and caseview for ui-tests --- e2e-tests/case.ts | 51 +++++++++++ e2e-tests/caseList.ts | 100 ++++----------------- e2e-tests/ui-tests/tests/case-list.spec.ts | 3 + e2e-tests/ui-tests/tests/case-view.spec.ts | 5 +- 4 files changed, 73 insertions(+), 86 deletions(-) diff --git a/e2e-tests/case.ts b/e2e-tests/case.ts index 5248111975..57a52757b6 100644 --- a/e2e-tests/case.ts +++ b/e2e-tests/case.ts @@ -80,3 +80,54 @@ export const caseHome = (page: Page) => { saveCaseAndEnd, }; }; + +// Add utility functions moved from caseList.ts +export async function viewClosePrintView(page: Page) { + const openPrintButton = page.locator('[data-testid="CasePrint-Button"]'); + await openPrintButton.waitFor({ state: 'visible' }); + await openPrintButton.click(); + console.log('Opened Case Print'); + const closePrintButton = page.locator('[data-testid="NavigableContainer-CloseCross"]'); + await closePrintButton.waitFor({ state: 'visible' }); + await closePrintButton.click(); + console.log('Close Case Print'); +} + +export async function editCase(page: Page) { + const editButton = page.locator('[data-testid="Case-EditButton"]'); + await editButton.waitFor({ state: 'visible' }); + await editButton.click(); +} + +export async function updateCaseSummary(page: Page) { + const summaryInput = page.locator('[data-testid="CaseSummary-Input"]'); + await summaryInput.waitFor({ state: 'visible' }); + await summaryInput.fill('Updated summary'); + const saveButton = page.locator('[data-testid="CaseSummary-SaveButton"]'); + await saveButton.waitFor({ state: 'visible' }); + await saveButton.click(); +} + +export async function verifyCaseSummaryUpdated(page: Page) { + const summaryContent = page.locator('[data-testid="CaseSummary-Content"]'); + await summaryContent.waitFor({ state: 'visible' }); + expect(await summaryContent.textContent()).toContain('Updated summary'); +} + +export async function verifyCasePrintButtonIsVisible(page: Page) { + const printButton = page.locator('[data-testid="CasePrint-Button"]'); + await printButton.waitFor({ state: 'visible' }); + expect(await printButton.isVisible()).toBe(true); +} + +export async function verifyCategoryTooltipIsVisible(page: Page) { + const tooltip = page.locator('[data-testid="Category-Tooltip"]'); + await tooltip.waitFor({ state: 'visible' }); + expect(await tooltip.isVisible()).toBe(true); +} + +export async function closeModal(page: Page) { + const closeCaseButton = page.locator('[data-testid="NavigableContainer-CloseCross"]'); + await closeCaseButton.waitFor({ state: 'visible' }); + await closeCaseButton.click(); +} diff --git a/e2e-tests/caseList.ts b/e2e-tests/caseList.ts index b7f5a203a6..5fdfba2853 100644 --- a/e2e-tests/caseList.ts +++ b/e2e-tests/caseList.ts @@ -111,6 +111,21 @@ export const caseList = (page: Page) => { console.log(`Filtered cases by: ${filter} filter with selection of: ${option}`); } + async function verifyCaseIdsAreInListInOrder(expectedIds: string[]) { + const rows = await page.locator('tr[data-testid^="CaseList-TableRow"]').all(); + + const ids = await Promise.all( + rows.map(async (row) => { + const button = row.locator('[data-testid="CaseList-CaseID-Button"]'); + const buttonText = (await button.textContent())?.trim() || ''; + const caseId = buttonText.replace(/OpenCase/, '').trim(); //extract case id + return caseId; + }), + ); + + expect(ids).toEqual(expectedIds); + } + //Open Case async function openFirstCaseButton() { const openCaseButton = selectors.openFirstCaseButton; @@ -122,94 +137,11 @@ export const caseList = (page: Page) => { return caseHome(page); } - //Check print view - // TODO: Move to case.ts - async function viewClosePrintView() { - const openPrintButton = selectors.casePrintButton; - await openPrintButton.waitFor({ state: 'visible' }); - await openPrintButton.click(); - console.log('Opened Case Print'); - - const closePrintButton = selectors.modalCloseButton; - await closePrintButton.waitFor({ state: 'visible' }); - await closePrintButton.click(); - console.log('Close Case Print'); - } - - //Edit Case - // TODO: Move to case.ts - async function editCase() { - const editCaseButton = selectors.caseEditButton; - await editCaseButton.waitFor({ state: 'visible' }); - await expect(editCaseButton).toContainText('Edit'); - await editCaseButton.click(); - console.log('Edit Case'); - } - - const currentTime = new Date(); - - // Add/Update Summary - // TODO: Move to case.ts - async function updateCaseSummary() { - const summaryTextArea = selectors.caseSummaryTextArea; - await summaryTextArea.waitFor({ state: 'visible' }); - await summaryTextArea.fill(`E2E Case Summary Test Edited on ${currentTime}`); - - const updateCaseButton = selectors.updateCaseButton; - await updateCaseButton.waitFor({ state: 'visible' }); - await expect(updateCaseButton).toContainText('Save'); - const responsePromise = page.waitForResponse('**/cases/**'); - await updateCaseButton.click(); - await responsePromise; - - console.log('Updated Case Summary'); - } - - // Verify case summary update - async function verifyCaseSummaryUpdated() { - const summaryText = selectors.caseSummaryText; - await summaryText.waitFor({ state: 'visible' }); - await expect(summaryText).toContainText(`E2E Case Summary Test Edited on ${currentTime}`); - } - - async function verifyCasePrintButtonIsVisible() { - const printButton = selectors.casePrintButton; - await printButton.waitFor({ state: 'visible' }); - await expect(printButton).toBeVisible(); - } - - async function verifyCategoryTooltipIsVisible() { - const categoryTooltip = selectors.categoryTooltip; - await categoryTooltip.waitFor({ state: 'visible' }); - await expect(categoryTooltip).toBeVisible(); - } - - async function verifyCaseIdsAreInListInOrder(ids: string[]) { - await selectors.caseListRowIdButton.first().waitFor({ state: 'visible' }); - const caseListIdButtons = await selectors.caseListRowIdButton.all(); - expect(caseListIdButtons.length).toBe(ids.length); - await Promise.all(caseListIdButtons.map((l, idx) => expect(l).toContainText(ids[idx]))); - } - - //Close Modal (probably can move this to more generic navigation file now we have more standardised navigation) - async function closeModal() { - const closeCaseButton = selectors.modalCloseButton; - await closeCaseButton.waitFor({ state: 'visible' }); - await closeCaseButton.click(); - } - return { openFilter, closeFilter, filterCases, - openFirstCaseButton, - viewClosePrintView, - editCase, - updateCaseSummary, - verifyCaseSummaryUpdated, - verifyCasePrintButtonIsVisible, - verifyCategoryTooltipIsVisible, - closeModal, verifyCaseIdsAreInListInOrder, + openFirstCaseButton, }; }; diff --git a/e2e-tests/ui-tests/tests/case-list.spec.ts b/e2e-tests/ui-tests/tests/case-list.spec.ts index bd813d1bbd..1345ea1c2b 100644 --- a/e2e-tests/ui-tests/tests/case-list.spec.ts +++ b/e2e-tests/ui-tests/tests/case-list.spec.ts @@ -45,6 +45,9 @@ test.describe.serial('Case List', () => { test('Case list loads items', async () => { await page.goto('/case-list', { waitUntil: 'networkidle' }); + await expect(page.locator('div.Twilio-View-case-list')).toBeVisible(); + + await page.waitForTimeout(10000); await caseList(page).verifyCaseIdsAreInListInOrder( cases .getMockCases() diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts index 580adad5e9..e1ea75c7b4 100644 --- a/e2e-tests/ui-tests/tests/case-view.spec.ts +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -4,6 +4,7 @@ import '../flex-in-a-box/local-resources'; import hrmCases from '../aselo-service-mocks/hrm/cases'; import hrmPermissions from '../aselo-service-mocks/hrm/permissions'; import { caseList } from '../../caseList'; +import { editCase, closeModal } from '../../case'; import AxeBuilder from '@axe-core/playwright'; import { aseloPage } from '../aselo-service-mocks/aselo-page'; import type { AxeResults } from 'axe-core'; @@ -41,11 +42,11 @@ test.describe.serial('Case View', () => { .analyze(); expect(caseHomeAccessibilityScanResults.violations).toEqual([]); warnViolations(caseHomeAccessibilityScanResults, `the case home page`); - await caseListPage.editCase(); + await editCase(page); const caseEditAccessibilityScanResults = await new AxeBuilder({ page }) .include('div.Twilio-View-case-list') .analyze(); warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); - await caseListPage.closeModal(); + await closeModal(page); }); }); From 17e71b523d1d1355b8889cd18d16e71cacbdc843 Mon Sep 17 00:00:00 2001 From: mythilytm Date: Thu, 17 Apr 2025 14:02:38 -0400 Subject: [PATCH 03/11] in progress --- .../ui-tests/playwright.ui-test.config.ts | 2 +- e2e-tests/ui-tests/tests/case-view.spec.ts | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/e2e-tests/ui-tests/playwright.ui-test.config.ts b/e2e-tests/ui-tests/playwright.ui-test.config.ts index 0dbb516b2a..7ce8ff8eb5 100644 --- a/e2e-tests/ui-tests/playwright.ui-test.config.ts +++ b/e2e-tests/ui-tests/playwright.ui-test.config.ts @@ -29,7 +29,7 @@ const config: PlaywrightTestConfig = { // Browser proxy option is required for Chromium on Windows launchOptions: { proxy: { server: `https://per-context` } }, ignoreHTTPSErrors: true, - headless: false, // For local debugging + // headless: false, // For local debugging }, testDir: './tests', timeout: 60000, diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts index e1ea75c7b4..0942ac5647 100644 --- a/e2e-tests/ui-tests/tests/case-view.spec.ts +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -49,4 +49,76 @@ test.describe.serial('Case View', () => { warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); await closeModal(page); }); + + // 2. Case Home & Navigation + + // test('Case Home displays details and navigation buttons', async () => { + // const caseListPage = caseList(page); + // await caseListPage.openFirstCaseButton(); + // // Check for summary, timeline, print, close, and save & end buttons + // await expect(page.locator('textarea[data-testid="summary"]')).toBeVisible(); + // // await expect(page.locator('button[data-testid="CasePrint-Button"]')).toBeVisible(); + // await expect(page.locator('button[data-testid="BottomBar-SaveCaseAndEnd"]')).toBeVisible(); + // // Timeline section + // await expect(page.locator('div[data-testid^="Case-Timeline"]')).toBeVisible(); + // }); + + // 3. Section and Timeline UI + + // test('Timeline and section list render with correct preview fields', async () => { + // const caseListPage = caseList(page); + // await caseListPage.openFirstCaseButton(); + // // Check for timeline and section rows + // await expect(page.locator('div[data-testid^="Case-Timeline"]')).toBeVisible(); + // // Check for at least one section row and "View" button + // await expect( + // page.locator('button[data-testid^="Case-InformationRow-ViewButton"]'), + // ).toBeVisible(); + // }); + + // 4. Section Item View/Edit + + // test('View and edit a section item, check ActionHeader and Edit button', async () => { + // const caseListPage = caseList(page); + // await caseListPage.openFirstCaseButton(); + // // Click first "View" button in section list + // await page.locator('button[data-testid^="Case-InformationRow-ViewButton"]').first().click(); + // // ActionHeader should be visible + // await expect(page.locator('[data-testid="Case-ActionHeaderAdded"]')).toBeVisible(); + // // Edit button should be visible and clickable + // const editButton = page.locator('button[data-testid="Case-EditButton"]'); + // if (await editButton.count()) { + // await expect(editButton).toBeVisible(); + // await editButton.click(); + // // Save or cancel edit to reset state + // await page.locator('button[data-testid="Case-AddEditItemScreen-SaveItem"]').click(); + // } + // }); + + // 5. Case Tags and Categories + + // test('Case tags display correct labels, colors, and tooltip', async () => { + // const caseListPage = caseList(page); + // await caseListPage.openFirstCaseButton(); + // // Tags should be visible + // await expect(page.locator('[data-testid^="CaseDetails-Category"]')).toBeVisible(); + // // Tooltip appears on hover + // await page.hover('[data-testid^="CaseDetails-Category"]'); + // await expect(page.locator('[data-testid="CaseDetails-CategoryTooltip"]')).toBeVisible(); + // }); + + // 6. Dialogs and Modals + + test('Close Case dialog opens and buttons function', async () => { + const caseListPage = caseList(page); + await caseListPage.openFirstCaseButton(); + // Open Close dialog (adjust selector as needed) + await page.click('button[data-testid="Case-EditCaseScreen-CloseCaseButton"]'); + await expect(page.locator('[data-testid="CloseCaseDialog"]')).toBeVisible(); + // Test Save and Don't Save buttons + await page.click('button[data-testid="BottomBar-DontSave"]'); + // Reopen and test Save button + await page.click('button[data-testid="Case-EditCaseScreen-CloseCaseButton"]'); + await page.click('button[data-testid="BottomBar-Save"]'); + }); }); From 9879e116979f6371ca6021cc5e396e511e84e135 Mon Sep 17 00:00:00 2001 From: mythilytm Date: Mon, 21 Apr 2025 12:47:39 -0400 Subject: [PATCH 04/11] revert to working condition --- e2e-tests/ui-tests/tests/case-view.spec.ts | 88 ++++------------------ 1 file changed, 16 insertions(+), 72 deletions(-) diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts index 0942ac5647..4390ae1b22 100644 --- a/e2e-tests/ui-tests/tests/case-view.spec.ts +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -1,3 +1,19 @@ +/** + * Copyright (C) 2021-2023 Technology Matters + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + import { expect, Page, test } from '@playwright/test'; import * as mockServer from '../flex-in-a-box/proxied-endpoints'; import '../flex-in-a-box/local-resources'; @@ -49,76 +65,4 @@ test.describe.serial('Case View', () => { warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); await closeModal(page); }); - - // 2. Case Home & Navigation - - // test('Case Home displays details and navigation buttons', async () => { - // const caseListPage = caseList(page); - // await caseListPage.openFirstCaseButton(); - // // Check for summary, timeline, print, close, and save & end buttons - // await expect(page.locator('textarea[data-testid="summary"]')).toBeVisible(); - // // await expect(page.locator('button[data-testid="CasePrint-Button"]')).toBeVisible(); - // await expect(page.locator('button[data-testid="BottomBar-SaveCaseAndEnd"]')).toBeVisible(); - // // Timeline section - // await expect(page.locator('div[data-testid^="Case-Timeline"]')).toBeVisible(); - // }); - - // 3. Section and Timeline UI - - // test('Timeline and section list render with correct preview fields', async () => { - // const caseListPage = caseList(page); - // await caseListPage.openFirstCaseButton(); - // // Check for timeline and section rows - // await expect(page.locator('div[data-testid^="Case-Timeline"]')).toBeVisible(); - // // Check for at least one section row and "View" button - // await expect( - // page.locator('button[data-testid^="Case-InformationRow-ViewButton"]'), - // ).toBeVisible(); - // }); - - // 4. Section Item View/Edit - - // test('View and edit a section item, check ActionHeader and Edit button', async () => { - // const caseListPage = caseList(page); - // await caseListPage.openFirstCaseButton(); - // // Click first "View" button in section list - // await page.locator('button[data-testid^="Case-InformationRow-ViewButton"]').first().click(); - // // ActionHeader should be visible - // await expect(page.locator('[data-testid="Case-ActionHeaderAdded"]')).toBeVisible(); - // // Edit button should be visible and clickable - // const editButton = page.locator('button[data-testid="Case-EditButton"]'); - // if (await editButton.count()) { - // await expect(editButton).toBeVisible(); - // await editButton.click(); - // // Save or cancel edit to reset state - // await page.locator('button[data-testid="Case-AddEditItemScreen-SaveItem"]').click(); - // } - // }); - - // 5. Case Tags and Categories - - // test('Case tags display correct labels, colors, and tooltip', async () => { - // const caseListPage = caseList(page); - // await caseListPage.openFirstCaseButton(); - // // Tags should be visible - // await expect(page.locator('[data-testid^="CaseDetails-Category"]')).toBeVisible(); - // // Tooltip appears on hover - // await page.hover('[data-testid^="CaseDetails-Category"]'); - // await expect(page.locator('[data-testid="CaseDetails-CategoryTooltip"]')).toBeVisible(); - // }); - - // 6. Dialogs and Modals - - test('Close Case dialog opens and buttons function', async () => { - const caseListPage = caseList(page); - await caseListPage.openFirstCaseButton(); - // Open Close dialog (adjust selector as needed) - await page.click('button[data-testid="Case-EditCaseScreen-CloseCaseButton"]'); - await expect(page.locator('[data-testid="CloseCaseDialog"]')).toBeVisible(); - // Test Save and Don't Save buttons - await page.click('button[data-testid="BottomBar-DontSave"]'); - // Reopen and test Save button - await page.click('button[data-testid="Case-EditCaseScreen-CloseCaseButton"]'); - await page.click('button[data-testid="BottomBar-Save"]'); - }); }); From 69310583e60314a92e2d1ab51346e5ba2b52ee75 Mon Sep 17 00:00:00 2001 From: mythilytm Date: Mon, 21 Apr 2025 13:00:28 -0400 Subject: [PATCH 05/11] revert to working condition --- e2e-tests/ui-tests/tests/case-list.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e-tests/ui-tests/tests/case-list.spec.ts b/e2e-tests/ui-tests/tests/case-list.spec.ts index 1345ea1c2b..da37d4702a 100644 --- a/e2e-tests/ui-tests/tests/case-list.spec.ts +++ b/e2e-tests/ui-tests/tests/case-list.spec.ts @@ -45,8 +45,7 @@ test.describe.serial('Case List', () => { test('Case list loads items', async () => { await page.goto('/case-list', { waitUntil: 'networkidle' }); - await expect(page.locator('div.Twilio-View-case-list')).toBeVisible(); - + await page.waitForSelector('div.Twilio-View-case-list', { state: 'visible', timeout: 10000 }); await page.waitForTimeout(10000); await caseList(page).verifyCaseIdsAreInListInOrder( cases From e4e51b6cf93ae78d16da6fd6ab181ad3d92c25ed Mon Sep 17 00:00:00 2001 From: mythilytm Date: Mon, 21 Apr 2025 15:31:40 -0400 Subject: [PATCH 06/11] add case view ui tests - view and edit overview --- e2e-tests/case.ts | 2 +- e2e-tests/ui-tests/tests/case-list.spec.ts | 31 +++-- e2e-tests/ui-tests/tests/case-view.spec.ts | 128 +++++++++++++++++---- 3 files changed, 123 insertions(+), 38 deletions(-) diff --git a/e2e-tests/case.ts b/e2e-tests/case.ts index 57a52757b6..8518966888 100644 --- a/e2e-tests/case.ts +++ b/e2e-tests/case.ts @@ -93,7 +93,7 @@ export async function viewClosePrintView(page: Page) { console.log('Close Case Print'); } -export async function editCase(page: Page) { +export async function clickEditCase(page: Page) { const editButton = page.locator('[data-testid="Case-EditButton"]'); await editButton.waitFor({ state: 'visible' }); await editButton.click(); diff --git a/e2e-tests/ui-tests/tests/case-list.spec.ts b/e2e-tests/ui-tests/tests/case-list.spec.ts index da37d4702a..4f2e049c16 100644 --- a/e2e-tests/ui-tests/tests/case-list.spec.ts +++ b/e2e-tests/ui-tests/tests/case-list.spec.ts @@ -31,6 +31,14 @@ test.describe.serial('Case List', () => { let page: Page; const cases = hrmCases(); const permissions = hrmPermissions(); + // Define the filters array for easy access and modification + const filters: Filter[] = [ + 'Status', + 'Categories', + 'Counselor', + 'createdAtFilter', + 'updatedAtFilter', + ]; test.beforeAll(async ({ browser }) => { await mockServer.start(); @@ -43,9 +51,12 @@ test.describe.serial('Case List', () => { await mockServer.stop(); }); - test('Case list loads items', async () => { + test.beforeEach(async () => { await page.goto('/case-list', { waitUntil: 'networkidle' }); await page.waitForSelector('div.Twilio-View-case-list', { state: 'visible', timeout: 10000 }); + }); + + test('Case list loads items', async () => { await page.waitForTimeout(10000); await caseList(page).verifyCaseIdsAreInListInOrder( cases @@ -81,15 +92,12 @@ test.describe.serial('Case List', () => { await caseListPage.openFilter(filter); }; - await scanFilterDialogue('Status'); - await scanFilterDialogue('Categories'); - await scanFilterDialogue('Counselor'); - await scanFilterDialogue('createdAtFilter'); - await scanFilterDialogue('updatedAtFilter'); + for (const filter of filters) { + await scanFilterDialogue(filter); + } }); test('Case list accessibility: screen reader labels', async () => { - await page.goto('/case-list', { waitUntil: 'networkidle' }); // Check that filter buttons have aria-label or accessible name const filterButtons = await page.$$('[data-testid^="CaseList-Filter-"]'); for (const btn of filterButtons) { @@ -98,13 +106,4 @@ test.describe.serial('Case List', () => { expect(ariaLabel || label.length > 0).toBeTruthy(); } }); - - test('Case list accessibility: color contrast and ARIA compliance', async () => { - await page.goto('/case-list', { waitUntil: 'networkidle' }); - const results = await new AxeBuilder({ page }) - .include('div.Twilio-View-case-list') - .withTags(['wcag2aa', 'wcag2a', 'section508']) - .analyze(); - expect(results.violations).toEqual([]); - }); }); diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts index 4390ae1b22..f9da2eed4f 100644 --- a/e2e-tests/ui-tests/tests/case-view.spec.ts +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -20,18 +20,9 @@ import '../flex-in-a-box/local-resources'; import hrmCases from '../aselo-service-mocks/hrm/cases'; import hrmPermissions from '../aselo-service-mocks/hrm/permissions'; import { caseList } from '../../caseList'; -import { editCase, closeModal } from '../../case'; +import { clickEditCase, closeModal } from '../../case'; import AxeBuilder from '@axe-core/playwright'; import { aseloPage } from '../aselo-service-mocks/aselo-page'; -import type { AxeResults } from 'axe-core'; - -function warnViolations(results: AxeResults, componentDescription: string) { - if (results.violations.length) { - console.warn( - `${results.violations.length} accessibility violations found in ${componentDescription}.`, - ); - } -} test.describe.serial('Case View', () => { let page: Page; @@ -50,19 +41,114 @@ test.describe.serial('Case View', () => { await mockServer.stop(); }); - test('Case view and edit passes AXE scan', async () => { - const caseListPage = caseList(page); - await caseListPage.openFirstCaseButton(); - const caseHomeAccessibilityScanResults = await new AxeBuilder({ page }) - .include('div.Twilio-View-case-list') + test.beforeEach(async () => { + await page.goto('/case-list', { waitUntil: 'networkidle' }); + await page.waitForSelector('div.Twilio-View-case-list', { state: 'visible', timeout: 10000 }); + await caseList(page).openFirstCaseButton(); + await page.waitForSelector('div[data-testid="CaseHome-CaseDetailsComponent"]', { + state: 'visible', + timeout: 10000, + }); + }); + + test('Case view page is loaded and passes AXE scan', async () => { + const caseViewScanResults = await new AxeBuilder({ page }) + .include('div[data-testid="CaseHome-CaseDetailsComponent"]') + .analyze(); + expect(caseViewScanResults.violations).toEqual([]); + }); + + test('Case view page is accessible and has case information and overview elements', async () => { + const checkElementAccessibility = async (testId: string, attributeName: string) => { + await expect(page.getByTestId(testId)).toBeVisible(); + const element = page.getByTestId(testId); + expect(await element.getAttribute(attributeName)).toBeTruthy(); + }; + + const checkElementHasVisibleLabel = async (testId: string) => { + const input = page.getByTestId(testId); + const labelId = await input.getAttribute('aria-labelledby'); + + if (labelId) { + const label = page.locator(`#${labelId}`); + await expect(label).toBeVisible(); + } + }; + + await checkElementAccessibility('Case-DetailsHeaderCaseId', 'id'); + await expect(page.getByTestId('Case-DetailsHeaderCounselor')).toBeVisible(); + const caseOverviewIds = ['CaseDetailsStatusLabel', 'childIsAtRisk', 'createdAt', 'updatedAt']; + + for (const fieldId of caseOverviewIds) { + await checkElementAccessibility(`Case-CaseOverview-${fieldId}`, 'aria-labelledby'); + await checkElementHasVisibleLabel(`Case-CaseOverview-${fieldId}`); + } + + const caseViewScanResults = await new AxeBuilder({ page }) + .include('div[data-testid="CaseHome-CaseDetailsComponent"]') .analyze(); - expect(caseHomeAccessibilityScanResults.violations).toEqual([]); - warnViolations(caseHomeAccessibilityScanResults, `the case home page`); - await editCase(page); - const caseEditAccessibilityScanResults = await new AxeBuilder({ page }) - .include('div.Twilio-View-case-list') + expect(caseViewScanResults.violations).toEqual([]); + + await closeModal(page); + }); + + test('Case overview edit form opens and supports keyboard navigation', async () => { + await clickEditCase(page); + await expect(page.getByTestId('Case-EditCaseOverview')).toBeVisible(); + + const getActiveElement = () => { + return ( + document.activeElement?.getAttribute('id') || + document.activeElement?.getAttribute('data-testid') + ); + }; + const activeElement = await page.evaluate(getActiveElement); + expect(activeElement).toBeTruthy(); + + const formControls = [ + { selector: '#status', type: 'select' }, + { selector: '#childIsAtRisk', type: 'checkbox' }, + { selector: '#followUpDate', type: 'date' }, + { selector: '#reportDate', type: 'date' }, + { selector: '#operatingArea', type: 'select' }, + { selector: '#priority', type: 'select' }, + { selector: '#summary', type: 'textarea' }, + { selector: '[data-testid="Case-EditCaseScreen-SaveItem"]', type: 'button' }, + ]; + + await page.focus(formControls[0].selector); + + for (let i = 1; i < formControls.length; i++) { + if (i > 0 && formControls[i - 1].type === 'date') { + await page.keyboard.press('Tab'); // day + await page.keyboard.press('Tab'); // year + await page.keyboard.press('Tab'); // calendar icon/next field + } else { + await page.keyboard.press('Tab'); + } + + const focusedElement = await page.evaluate(getActiveElement); + + const expectedId = formControls[i].selector.startsWith('#') + ? formControls[i].selector.substring(1) + : formControls[i].selector.match(/data-testid="([^"]+)"/)?.[1]; + + expect(focusedElement).toBe(expectedId); + } + + await closeModal(page); + }); + + test('Case overview edit form meets accessibility requirements', async () => { + await clickEditCase(page); + await expect(page.getByTestId('Case-EditCaseOverview')).toBeVisible(); + + const caseEditScanResults = await new AxeBuilder({ page }) + .include('div[data-testid="Case-EditCaseOverview"]') .analyze(); - warnViolations(caseEditAccessibilityScanResults, `the case summary edit page`); + expect(caseEditScanResults.violations).toEqual([]); await closeModal(page); + await expect(page.getByTestId('Case-EditCaseOverview')).not.toBeVisible(); + await expect(page.getByTestId('CaseHome-CaseDetailsComponent')).toBeVisible(); }); }); From 37bbaaff45c9cbd3eeac6f6664e690ac2cc9c52a Mon Sep 17 00:00:00 2001 From: mythilytm Date: Mon, 21 Apr 2025 15:45:38 -0400 Subject: [PATCH 07/11] add case view data test ids for e2e --- e2e-tests/ui-tests/playwright.ui-test.config.ts | 2 +- e2e-tests/ui-tests/tests/case-view.spec.ts | 10 +++++++++- .../components/case/caseOverview/EditCaseOverview.tsx | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/e2e-tests/ui-tests/playwright.ui-test.config.ts b/e2e-tests/ui-tests/playwright.ui-test.config.ts index 7ce8ff8eb5..0dbb516b2a 100644 --- a/e2e-tests/ui-tests/playwright.ui-test.config.ts +++ b/e2e-tests/ui-tests/playwright.ui-test.config.ts @@ -29,7 +29,7 @@ const config: PlaywrightTestConfig = { // Browser proxy option is required for Chromium on Windows launchOptions: { proxy: { server: `https://per-context` } }, ignoreHTTPSErrors: true, - // headless: false, // For local debugging + headless: false, // For local debugging }, testDir: './tests', timeout: 60000, diff --git a/e2e-tests/ui-tests/tests/case-view.spec.ts b/e2e-tests/ui-tests/tests/case-view.spec.ts index f9da2eed4f..91eea376af 100644 --- a/e2e-tests/ui-tests/tests/case-view.spec.ts +++ b/e2e-tests/ui-tests/tests/case-view.spec.ts @@ -94,6 +94,10 @@ test.describe.serial('Case View', () => { test('Case overview edit form opens and supports keyboard navigation', async () => { await clickEditCase(page); + await page.waitForSelector('[data-testid="Case-EditCaseOverview"]', { + state: 'visible', + timeout: 10000, + }); await expect(page.getByTestId('Case-EditCaseOverview')).toBeVisible(); const getActiveElement = () => { @@ -139,8 +143,12 @@ test.describe.serial('Case View', () => { await closeModal(page); }); - test('Case overview edit form meets accessibility requirements', async () => { + test('Case overview edit form meets accessibility requirements for screen reader', async () => { await clickEditCase(page); + await page.waitForSelector('[data-testid="Case-EditCaseOverview"]', { + state: 'visible', + timeout: 10000, + }); await expect(page.getByTestId('Case-EditCaseOverview')).toBeVisible(); const caseEditScanResults = await new AxeBuilder({ page }) diff --git a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx index faa197b83c..0ec4112298 100644 --- a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx +++ b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx @@ -231,6 +231,7 @@ const EditCaseOverview: React.FC = ({ }; return ( +
= ({ /> +
); }; From c068b375158999b85d496cc4084813a8254b10a5 Mon Sep 17 00:00:00 2001 From: mythilytm Date: Mon, 21 Apr 2025 15:53:00 -0400 Subject: [PATCH 08/11] linter --- .../case/caseOverview/EditCaseOverview.tsx | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx index 0ec4112298..facc6c5b89 100644 --- a/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx +++ b/plugin-hrm-form/src/components/case/caseOverview/EditCaseOverview.tsx @@ -232,42 +232,42 @@ const EditCaseOverview: React.FC = ({ return (
- - - - - - - {l} - {r} - - - -
- - -