From 03b9606feb899e05a3e4d91e5c02ad83e2fdf249 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 09:52:14 -0700 Subject: [PATCH 01/20] initial playwright commit --- .github/workflows/playwright.yml | 28 ++++++++++++ e2e/.env-template | 5 +++ e2e/.gitignore | 5 +++ e2e/env.ts | 9 ++++ e2e/functions/login.ts | 23 ++++++++++ e2e/package.json | 17 +++++++ e2e/playwright.config.ts | 77 ++++++++++++++++++++++++++++++++ e2e/tests/login.spec.ts | 12 +++++ 8 files changed, 176 insertions(+) create mode 100644 .github/workflows/playwright.yml create mode 100644 e2e/.env-template create mode 100644 e2e/.gitignore create mode 100644 e2e/env.ts create mode 100644 e2e/functions/login.ts create mode 100644 e2e/package.json create mode 100644 e2e/playwright.config.ts create mode 100644 e2e/tests/login.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..9e0e33549 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,28 @@ +# TODO: This is the default from Playwright. Needs changes to work in our pipeline if we want that. +# name: Playwright Tests +# on: +# push: +# branches: [ main, master ] +# pull_request: +# branches: [ main, master ] +# jobs: +# test: +# timeout-minutes: 60 +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - uses: actions/setup-node@v4 +# with: +# node-version: lts/* +# - name: Install dependencies +# run: npm ci +# - name: Install Playwright Browsers +# run: npx playwright install --with-deps +# - name: Run Playwright tests +# run: npx playwright test +# - uses: actions/upload-artifact@v4 +# if: ${{ !cancelled() }} +# with: +# name: playwright-report +# path: playwright-report/ +# retention-days: 30 diff --git a/e2e/.env-template b/e2e/.env-template new file mode 100644 index 000000000..273e6429d --- /dev/null +++ b/e2e/.env-template @@ -0,0 +1,5 @@ +BASE_URL= +BCSC_USERNAME= +BCSC_PASSWORD= +BCEID_USERNAME= +BCEID_PASSWORD= diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 000000000..68c5d18f0 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/e2e/env.ts b/e2e/env.ts new file mode 100644 index 000000000..7eb73cbe2 --- /dev/null +++ b/e2e/env.ts @@ -0,0 +1,9 @@ +import dotenv from 'dotenv'; + +dotenv.config() + +export const BASE_URL = process.env.URL ?? 'localhost:3000'; +export const BCSC_USERNAME = process.env.BCSC_USERNAME ?? ''; +export const BCSC_PASSWORD = process.env.BCSC_PASSWORD ?? ''; +export const BCEID_USERNAME = process.env.BCEID_USERNAME ?? ''; +export const BCEID_PASSWORD = process.env.BCEID_PASSWORD ?? ''; diff --git a/e2e/functions/login.ts b/e2e/functions/login.ts new file mode 100644 index 000000000..8178a43a0 --- /dev/null +++ b/e2e/functions/login.ts @@ -0,0 +1,23 @@ +import {BASE_URL, BCEID_PASSWORD, BCEID_USERNAME, BCSC_PASSWORD, BCSC_USERNAME} from '../env'; + +export const loginBCServicesCard = async ({ page }) => { + await page.goto(BASE_URL); + await page.getByRole('button', { name: 'Login' }).click(); + await page.getByRole('link', { name: 'BC Services Card' }).click(); + await page.getByLabel('Log in with Test with').click(); + await page.getByLabel('Email or username').click(); + await page.getByLabel('Email or username').fill(BCSC_USERNAME); + await page.getByLabel('Password').click(); + await page.getByLabel('Password').fill(BCSC_PASSWORD); + await page.getByRole('button', { name: 'Continue' }).click(); +}; + +export const loginBCeID = async ({ page }) => { + await page.goto(BASE_URL); + await page.getByRole('button', { name: 'Login' }).click(); + await page.getByRole('link', { name: 'Basic or Business BCeID' }).click(); + await page.locator('#user').fill(BCEID_USERNAME); + await page.getByLabel('Password').click(); + await page.getByLabel('Password').fill(BCEID_PASSWORD); + await page.getByRole('button', { name: 'Continue' }).click(); +}; diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 000000000..912a7940b --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,17 @@ +{ + "name": "e2e", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "1.48.0", + "@types/node": "22.7.5" + }, + "dependencies": { + "dotenv": "16.4.5" + } +} diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts new file mode 100644 index 000000000..5233031ce --- /dev/null +++ b/e2e/playwright.config.ts @@ -0,0 +1,77 @@ +import { defineConfig, devices } from '@playwright/test'; +import { BASE_URL } from './env'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/e2e/tests/login.spec.ts b/e2e/tests/login.spec.ts new file mode 100644 index 000000000..16d00f821 --- /dev/null +++ b/e2e/tests/login.spec.ts @@ -0,0 +1,12 @@ +import { test, expect } from '@playwright/test'; +import { loginBCServicesCard, loginBCeID } from '../functions/login'; + +test('log in with BC Services Card', async ({ page }) => { + await loginBCServicesCard({ page }); + await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); +}); + +test('log in with BCeID', async ({ page }) => { + await loginBCeID({ page }); + await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); +}); From 2fec5479817a15ecef07e3d32f1098f393b3f121 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 10:26:41 -0700 Subject: [PATCH 02/20] add test for no access pages --- e2e/tests/noAccess.spec.ts | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 e2e/tests/noAccess.spec.ts diff --git a/e2e/tests/noAccess.spec.ts b/e2e/tests/noAccess.spec.ts new file mode 100644 index 000000000..2f9cd98d2 --- /dev/null +++ b/e2e/tests/noAccess.spec.ts @@ -0,0 +1,66 @@ +/** + * Testing results when the user has no access to PIMS due to their missing role or non-Active status. + */ + +import {test, expect} from "@playwright/test"; +import { loginBCServicesCard } from "../functions/login"; + +const createSelfResponse = (props?) => ({ + "CreatedById": "00000000-0000-0000-0000-000000000000", + "CreatedOn": "2023-07-25T00:32:41.938Z", + "UpdatedById": "a619c656-b263-4a17-9b87-03459f43b349", + "UpdatedOn": "2024-10-09T23:49:47.650Z", + "Id": "a619c656-b263-4a17-9b87-03459f43b352", + "Username": "a619c656b2634a179b8703459f43b529@idir", + "FirstName": "Joe", + "MiddleName": null, + "LastName": "Tester", + "Email": "user.email@gov.bc.ca", + "Position": "Developer", + "IsDisabled": false, + "Note": null, + "LastLogin": "2024-10-09T16:49:47.629Z", + "ApprovedById": "80e0111a-b56c-48dd-b55e-f99f2205c4bd", + "ApprovedOn": "2023-08-26T03:03:47.911Z", + "KeycloakUserId": "a619c656-b263-4a17-9b87-03459f43b352", + "AgencyId": 2, + "RoleId": "00000000-0000-0000-0000-000000000000", + "Status": "Active", + ...props, +}) + +test('user without a role get the no-role page', async ({ page }) => { + await page.route('**/users/self', route => route.fulfill({ + status: 200, + body: JSON.stringify(createSelfResponse({ RoleId: undefined })), + })); + await loginBCServicesCard({ page }); + await expect(page.getByRole('heading', { name: 'Awaiting Role' })).toBeVisible(); +}) + +test('user with On Hold status should get ____ page', async ({ page }) => { + await page.route('**/users/self', route => route.fulfill({ + status: 200, + body: JSON.stringify(createSelfResponse({ Status: 'OnHold' })), + })); + await loginBCServicesCard({ page }); + await expect(page.getByRole('heading', { name: 'Access Pending' })).toBeVisible(); +}) + +test('user with Disabled status should get ____ page', async ({ page }) => { + await page.route('**/users/self', route => route.fulfill({ + status: 200, + body: JSON.stringify(createSelfResponse({ Status: 'Disabled' })), + })); + await loginBCServicesCard({ page }); + await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); +}) + +test('user with Denied status should get ____ page', async ({ page }) => { + await page.route('**/users/self', route => route.fulfill({ + status: 200, + body: JSON.stringify(createSelfResponse({ Status: 'Denied' })), + })); + await loginBCServicesCard({ page }); + await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); +}) From 806561ad12f6ec69c451d8ff69dc2666af3f081c Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 11:14:21 -0700 Subject: [PATCH 03/20] separate self mock --- e2e/functions/mockRequests.ts | 52 +++++++++++++++++++++++++++++++++++ e2e/tests/noAccess.spec.ts | 47 +++++-------------------------- 2 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 e2e/functions/mockRequests.ts diff --git a/e2e/functions/mockRequests.ts b/e2e/functions/mockRequests.ts new file mode 100644 index 000000000..72cd36d96 --- /dev/null +++ b/e2e/functions/mockRequests.ts @@ -0,0 +1,52 @@ +import { Page } from "@playwright/test"; + +interface User { + CreatedById: string; + CreatedOn: string; + UpdatedById: string; + UpdatedOn: string; + Id: string; + Username: string; + FirstName: string; + MiddleName: string | null; + LastName: string; + Email: string; + Position: string; + Note: string | null; + LastLogin: string; + ApprovedById: string; + ApprovedOn: string; + KeycloakUserId: string; + AgencyId: number; + RoleId: string | undefined; + Status: string; +} +const createSelfResponse = (props?: Partial): User => ({ + "CreatedById": "00000000-0000-0000-0000-000000000000", + "CreatedOn": "2023-07-25T00:32:41.938Z", + "UpdatedById": "a619c656-b263-4a17-9b87-03459f43b349", + "UpdatedOn": "2024-10-09T23:49:47.650Z", + "Id": "a619c656-b263-4a17-9b87-03459f43b352", + "Username": "a619c656b2634a179b8703459f43b529@idir", + "FirstName": "Joe", + "MiddleName": null, + "LastName": "Tester", + "Email": "user.email@gov.bc.ca", + "Position": "Developer", + "Note": null, + "LastLogin": "2024-10-09T16:49:47.629Z", + "ApprovedById": "80e0111a-b56c-48dd-b55e-f99f2205c4bd", + "ApprovedOn": "2023-08-26T03:03:47.911Z", + "KeycloakUserId": "a619c656-b263-4a17-9b87-03459f43b352", + "AgencyId": 2, + "RoleId": "00000000-0000-0000-0000-000000000000", + "Status": "Active", + ...props, +}) + +export const mockSelf = async (page: Page, self?: Partial, status: number = 200) => { + await page.route('**/users/self', route => route.fulfill({ + status, + body: JSON.stringify(createSelfResponse(self)), + })) +}; diff --git a/e2e/tests/noAccess.spec.ts b/e2e/tests/noAccess.spec.ts index 2f9cd98d2..31b83f4b0 100644 --- a/e2e/tests/noAccess.spec.ts +++ b/e2e/tests/noAccess.spec.ts @@ -1,66 +1,33 @@ /** * Testing results when the user has no access to PIMS due to their missing role or non-Active status. + * This might be unnecessary, as we are essentially mocking the API response anyway. */ -import {test, expect} from "@playwright/test"; +import { test, expect } from "@playwright/test"; import { loginBCServicesCard } from "../functions/login"; +import { mockSelf } from "../functions/mockRequests"; -const createSelfResponse = (props?) => ({ - "CreatedById": "00000000-0000-0000-0000-000000000000", - "CreatedOn": "2023-07-25T00:32:41.938Z", - "UpdatedById": "a619c656-b263-4a17-9b87-03459f43b349", - "UpdatedOn": "2024-10-09T23:49:47.650Z", - "Id": "a619c656-b263-4a17-9b87-03459f43b352", - "Username": "a619c656b2634a179b8703459f43b529@idir", - "FirstName": "Joe", - "MiddleName": null, - "LastName": "Tester", - "Email": "user.email@gov.bc.ca", - "Position": "Developer", - "IsDisabled": false, - "Note": null, - "LastLogin": "2024-10-09T16:49:47.629Z", - "ApprovedById": "80e0111a-b56c-48dd-b55e-f99f2205c4bd", - "ApprovedOn": "2023-08-26T03:03:47.911Z", - "KeycloakUserId": "a619c656-b263-4a17-9b87-03459f43b352", - "AgencyId": 2, - "RoleId": "00000000-0000-0000-0000-000000000000", - "Status": "Active", - ...props, -}) test('user without a role get the no-role page', async ({ page }) => { - await page.route('**/users/self', route => route.fulfill({ - status: 200, - body: JSON.stringify(createSelfResponse({ RoleId: undefined })), - })); + await mockSelf(page, { RoleId: undefined }); await loginBCServicesCard({ page }); await expect(page.getByRole('heading', { name: 'Awaiting Role' })).toBeVisible(); }) test('user with On Hold status should get ____ page', async ({ page }) => { - await page.route('**/users/self', route => route.fulfill({ - status: 200, - body: JSON.stringify(createSelfResponse({ Status: 'OnHold' })), - })); + await mockSelf(page, { Status: 'OnHold' }); await loginBCServicesCard({ page }); await expect(page.getByRole('heading', { name: 'Access Pending' })).toBeVisible(); }) test('user with Disabled status should get ____ page', async ({ page }) => { - await page.route('**/users/self', route => route.fulfill({ - status: 200, - body: JSON.stringify(createSelfResponse({ Status: 'Disabled' })), - })); + await mockSelf(page, { Status: 'Disabled' }); await loginBCServicesCard({ page }); await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); }) test('user with Denied status should get ____ page', async ({ page }) => { - await page.route('**/users/self', route => route.fulfill({ - status: 200, - body: JSON.stringify(createSelfResponse({ Status: 'Denied' })), - })); + await mockSelf(page, { Status: 'Denied' }); await loginBCServicesCard({ page }); await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); }) From 8408fd73feb73be7ddb39a1145b7243f7c4141f7 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 11:25:07 -0700 Subject: [PATCH 04/20] comments --- e2e/functions/login.ts | 5 +++++ e2e/functions/mockRequests.ts | 13 +++++++++++++ e2e/tests/login.spec.ts | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/e2e/functions/login.ts b/e2e/functions/login.ts index 8178a43a0..bc523ee59 100644 --- a/e2e/functions/login.ts +++ b/e2e/functions/login.ts @@ -1,5 +1,10 @@ +/** + * Functions for automating user logins. + */ + import {BASE_URL, BCEID_PASSWORD, BCEID_USERNAME, BCSC_PASSWORD, BCSC_USERNAME} from '../env'; +// Will only work in DEV/TEST environments export const loginBCServicesCard = async ({ page }) => { await page.goto(BASE_URL); await page.getByRole('button', { name: 'Login' }).click(); diff --git a/e2e/functions/mockRequests.ts b/e2e/functions/mockRequests.ts index 72cd36d96..c0d0309d0 100644 --- a/e2e/functions/mockRequests.ts +++ b/e2e/functions/mockRequests.ts @@ -21,6 +21,12 @@ interface User { RoleId: string | undefined; Status: string; } + +/** + * For creating a user object normally returned by the /self route + * @param props A Partial user object + * @returns + */ const createSelfResponse = (props?: Partial): User => ({ "CreatedById": "00000000-0000-0000-0000-000000000000", "CreatedOn": "2023-07-25T00:32:41.938Z", @@ -44,6 +50,13 @@ const createSelfResponse = (props?: Partial): User => ({ ...props, }) +/** + * Use this at the start of a test to set your user information if needed. + * Otherwise, your user information will reflect the actual user credentials used in testing. + * @param page Page of e2e test. + * @param self A partial object with user attributes. + * @param status An optional status number. Defaults to 200. + */ export const mockSelf = async (page: Page, self?: Partial, status: number = 200) => { await page.route('**/users/self', route => route.fulfill({ status, diff --git a/e2e/tests/login.spec.ts b/e2e/tests/login.spec.ts index 16d00f821..0b7b21a37 100644 --- a/e2e/tests/login.spec.ts +++ b/e2e/tests/login.spec.ts @@ -1,3 +1,7 @@ +/** + * Testing login options. + */ + import { test, expect } from '@playwright/test'; import { loginBCServicesCard, loginBCeID } from '../functions/login'; From f5e672b7ae432bdb710d3a8d41e0069240299c84 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 11:26:40 -0700 Subject: [PATCH 05/20] remove playwright workflow --- .github/workflows/playwright.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/playwright.yml diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index 9e0e33549..000000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,28 +0,0 @@ -# TODO: This is the default from Playwright. Needs changes to work in our pipeline if we want that. -# name: Playwright Tests -# on: -# push: -# branches: [ main, master ] -# pull_request: -# branches: [ main, master ] -# jobs: -# test: -# timeout-minutes: 60 -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v4 -# - uses: actions/setup-node@v4 -# with: -# node-version: lts/* -# - name: Install dependencies -# run: npm ci -# - name: Install Playwright Browsers -# run: npx playwright install --with-deps -# - name: Run Playwright tests -# run: npx playwright test -# - uses: actions/upload-artifact@v4 -# if: ${{ !cancelled() }} -# with: -# name: playwright-report -# path: playwright-report/ -# retention-days: 30 From 214c732ed44fbb638ff360dd4cbb8f91a78a30c4 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 11:29:03 -0700 Subject: [PATCH 06/20] fix env base_url --- e2e/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/env.ts b/e2e/env.ts index 7eb73cbe2..2c124a240 100644 --- a/e2e/env.ts +++ b/e2e/env.ts @@ -2,7 +2,7 @@ import dotenv from 'dotenv'; dotenv.config() -export const BASE_URL = process.env.URL ?? 'localhost:3000'; +export const BASE_URL = process.env.BASE_URL ?? 'localhost:3000'; export const BCSC_USERNAME = process.env.BCSC_USERNAME ?? ''; export const BCSC_PASSWORD = process.env.BCSC_PASSWORD ?? ''; export const BCEID_USERNAME = process.env.BCEID_USERNAME ?? ''; From 39141896cbf36e8c000cbca0825d7a2cbbcc18fa Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 11:45:50 -0700 Subject: [PATCH 07/20] add idir to login tests --- e2e/.env-template | 2 ++ e2e/env.ts | 2 ++ e2e/functions/login.ts | 12 +++++++++++- e2e/tests/login.spec.ts | 7 ++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/e2e/.env-template b/e2e/.env-template index 273e6429d..645d95f36 100644 --- a/e2e/.env-template +++ b/e2e/.env-template @@ -3,3 +3,5 @@ BCSC_USERNAME= BCSC_PASSWORD= BCEID_USERNAME= BCEID_PASSWORD= +IDIR_USERNAME= +IDIR_PASSWORD= diff --git a/e2e/env.ts b/e2e/env.ts index 2c124a240..a4adb4eaf 100644 --- a/e2e/env.ts +++ b/e2e/env.ts @@ -7,3 +7,5 @@ export const BCSC_USERNAME = process.env.BCSC_USERNAME ?? ''; export const BCSC_PASSWORD = process.env.BCSC_PASSWORD ?? ''; export const BCEID_USERNAME = process.env.BCEID_USERNAME ?? ''; export const BCEID_PASSWORD = process.env.BCEID_PASSWORD ?? ''; +export const IDIR_USERNAME = process.env.IDIR_USERNAME ?? ''; +export const IDIR_PASSWORD = process.env.IDIR_PASSWORD ?? ''; diff --git a/e2e/functions/login.ts b/e2e/functions/login.ts index bc523ee59..d598cc81b 100644 --- a/e2e/functions/login.ts +++ b/e2e/functions/login.ts @@ -2,7 +2,7 @@ * Functions for automating user logins. */ -import {BASE_URL, BCEID_PASSWORD, BCEID_USERNAME, BCSC_PASSWORD, BCSC_USERNAME} from '../env'; +import {BASE_URL, BCEID_PASSWORD, BCEID_USERNAME, BCSC_PASSWORD, BCSC_USERNAME, IDIR_PASSWORD, IDIR_USERNAME} from '../env'; // Will only work in DEV/TEST environments export const loginBCServicesCard = async ({ page }) => { @@ -26,3 +26,13 @@ export const loginBCeID = async ({ page }) => { await page.getByLabel('Password').fill(BCEID_PASSWORD); await page.getByRole('button', { name: 'Continue' }).click(); }; + +export const loginIDIR = async ({ page }) => { + await page.goto(BASE_URL); + await page.getByRole('button', { name: 'Login' }).click(); + await page.getByRole('link', { name: 'IDIR' }).click(); + await page.locator('#user').fill(IDIR_USERNAME); + await page.getByLabel('Password').click(); + await page.getByLabel('Password').fill(IDIR_PASSWORD); + await page.getByRole('button', { name: 'Continue' }).click(); +}; diff --git a/e2e/tests/login.spec.ts b/e2e/tests/login.spec.ts index 0b7b21a37..4ee8943ec 100644 --- a/e2e/tests/login.spec.ts +++ b/e2e/tests/login.spec.ts @@ -3,7 +3,7 @@ */ import { test, expect } from '@playwright/test'; -import { loginBCServicesCard, loginBCeID } from '../functions/login'; +import { loginBCServicesCard, loginBCeID, loginIDIR } from '../functions/login'; test('log in with BC Services Card', async ({ page }) => { await loginBCServicesCard({ page }); @@ -14,3 +14,8 @@ test('log in with BCeID', async ({ page }) => { await loginBCeID({ page }); await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); }); + +test('log in with IDIR', async ({ page }) => { + await loginIDIR({ page }); + await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); +}); From 765eff5cd7fca151c20254a368ba52aee7053926 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 12:03:28 -0700 Subject: [PATCH 08/20] change mockSelf behaviour --- e2e/functions/mockRequests.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/e2e/functions/mockRequests.ts b/e2e/functions/mockRequests.ts index c0d0309d0..61970658d 100644 --- a/e2e/functions/mockRequests.ts +++ b/e2e/functions/mockRequests.ts @@ -58,8 +58,14 @@ const createSelfResponse = (props?: Partial): User => ({ * @param status An optional status number. Defaults to 200. */ export const mockSelf = async (page: Page, self?: Partial, status: number = 200) => { - await page.route('**/users/self', route => route.fulfill({ - status, - body: JSON.stringify(createSelfResponse(self)), - })) + // Make the actual call, but then edit it with any partial user passed in before it gets back to the page + await page.route('**/users/self', async route => { + const response = await route.fetch(); + let json = await response.json(); + json = { + ...json, + ...self + } + await route.fulfill({ response, json }); + }) }; From e33c6b4eabe11ea70a5f7f0408a3f192cf0e0200 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 13:15:39 -0700 Subject: [PATCH 09/20] Add readme file --- e2e/README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 e2e/README.md diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..c39bf11f3 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,20 @@ +# Playwright End-to-End Testing + +End-to-end (e2e) testing involves testing the entire stack of the application, including the frontend (react-app), API (express-api), and database. + +These tests should focus on user paths and should mimic how the user navigates the site. + +## Setup + +1. Run `npm i` to install dependencies. +2. Run `npx playwright install` to install Playwright browsers. +3. Create and populate a `.env` file based on the `.env-template` file. + +For the `BASE_URL`, start with `localhost:` for local testing. + +Change BASE_URL to target other environments as needed. + +## Run Tests + +- `npx playwright test`: Runs tests in headless mode. +- `npx playwright test --ui`: Starts the Playwright UI. From 2488e117e873ec3f0b34ab0e333faf933981e846 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 9 Oct 2024 14:33:39 -0700 Subject: [PATCH 10/20] checking map controls --- e2e/README.md | 2 +- e2e/tests/map/clusters.spec.ts | 46 +++++++++++++++++++ react-app/src/components/map/ParcelMap.tsx | 1 + .../map/propertyRow/PropertyRow.tsx | 1 + .../src/components/map/sidebar/MapSidebar.tsx | 4 +- 5 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 e2e/tests/map/clusters.spec.ts diff --git a/e2e/README.md b/e2e/README.md index c39bf11f3..279c1a185 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -8,7 +8,7 @@ These tests should focus on user paths and should mimic how the user navigates t 1. Run `npm i` to install dependencies. 2. Run `npx playwright install` to install Playwright browsers. -3. Create and populate a `.env` file based on the `.env-template` file. +3. Create and populate a `.env` file based on the `.env-template` file. For the `BASE_URL`, start with `localhost:` for local testing. diff --git a/e2e/tests/map/clusters.spec.ts b/e2e/tests/map/clusters.spec.ts new file mode 100644 index 000000000..04c5422c5 --- /dev/null +++ b/e2e/tests/map/clusters.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from "@playwright/test"; +import { BASE_URL } from "../../env"; +import { loginIDIR } from "../../functions/login"; + +test('user can view all parts of map', async ({ page }) => { + await page.goto(BASE_URL); + await loginIDIR({ page }); + // Leaflet map is visible + await expect(page.locator('#parcel-map')).toBeVisible(); + // Controls on map are visible + await expect(page.getByRole('link', { name: 'Draw a polygon' })).toBeVisible(); + await expect(page.getByLabel('Zoom in')).toBeVisible(); + // At least one property is rendered as a cluster + await expect(page.locator('.cluster-marker').first()).toBeVisible(); +}) + +test('sidebar controls work as expected (not filter)', async ({ page }) => { + await page.goto(BASE_URL); + await loginIDIR({ page }); + // Leaflet map is visible + await expect(page.locator('#parcel-map')).toBeVisible(); + // Sidebar is visible + await expect(page.locator('#map-sidebar')).toBeVisible(); + // Sidebar has properties (also ensures that properties were loaded first) + await expect(page.locator('.property-row').first()).toBeVisible(); + + // Can click sidebar navigation and it affects value + await expect(page.locator('#map-sidebar')).toContainText('1 of'); + // Up + await page.locator('#sidebar-increment').click({timeout: 10000}); + await expect(page.locator('#map-sidebar')).toContainText('2 of'); + // Down + await page.locator('#sidebar-decrement').click(); + await expect(page.locator('#map-sidebar')).toContainText('1 of'); + // Down again + await page.locator('#sidebar-decrement').click(); + await expect(page.locator('#map-sidebar')).toContainText('1 of'); + + // Can hide and unhide sidebar + await page.locator('#sidebar-button-close').click(); + // Sidebar should be off screen + await expect(page.locator('#map-sidebar')).not.toBeInViewport(); + // Click again and see sidebar + await page.locator('#sidebar-button').click(); + await expect(page.locator('#map-sidebar')).toBeInViewport(); +}) diff --git a/react-app/src/components/map/ParcelMap.tsx b/react-app/src/components/map/ParcelMap.tsx index a07130a35..04cc668ba 100644 --- a/react-app/src/components/map/ParcelMap.tsx +++ b/react-app/src/components/map/ParcelMap.tsx @@ -302,6 +302,7 @@ const ParcelMap = (props: ParcelMapProps) => { {loadProperties ? : <>} { return ( window.open(`/properties/${propertyType}/${id}`)} sx={{ cursor: 'pointer', diff --git a/react-app/src/components/map/sidebar/MapSidebar.tsx b/react-app/src/components/map/sidebar/MapSidebar.tsx index 1e42bccd3..d6d652558 100644 --- a/react-app/src/components/map/sidebar/MapSidebar.tsx +++ b/react-app/src/components/map/sidebar/MapSidebar.tsx @@ -100,6 +100,7 @@ const MapSidebar = (props: MapSidebarProps) => { { if (pageIndex > 0) { @@ -114,6 +115,7 @@ const MapSidebar = (props: MapSidebarProps) => { fontSize={'0.8em'} >{`${pageIndex + 1} of ${formatNumber(Math.max(Math.ceil(propertiesInBounds.length / propertyPageSize), 1))} (${formatNumber(propertiesInBounds.length)} items)`} { if (pageIndex + 1 < Math.ceil(propertiesInBounds.length / propertyPageSize)) { @@ -125,7 +127,7 @@ const MapSidebar = (props: MapSidebarProps) => { - setSidebarOpen(false)}> + setSidebarOpen(false)} id="sidebar-button-close"> From 5702d3fdf51f620e302e55f8ab4c9ff89dff9d9a Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Thu, 10 Oct 2024 09:29:44 -0700 Subject: [PATCH 11/20] additional map tests --- e2e/tests/map.spec.ts | 131 ++++++++++++++++++ e2e/tests/map/clusters.spec.ts | 46 ------ .../src/components/map/sidebar/MapSidebar.tsx | 3 +- react-app/src/contexts/snackbarContext.tsx | 1 + 4 files changed, 134 insertions(+), 47 deletions(-) create mode 100644 e2e/tests/map.spec.ts delete mode 100644 e2e/tests/map/clusters.spec.ts diff --git a/e2e/tests/map.spec.ts b/e2e/tests/map.spec.ts new file mode 100644 index 000000000..643686fcd --- /dev/null +++ b/e2e/tests/map.spec.ts @@ -0,0 +1,131 @@ +import { test, expect } from "@playwright/test"; +import { BASE_URL } from "../env"; +import { loginIDIR } from "../functions/login"; + +test('user can view all parts of map', async ({ page }) => { + await page.goto(BASE_URL); + await loginIDIR({ page }); + // Leaflet map is visible + await expect(page.locator('#parcel-map')).toBeVisible(); + // Controls on map are visible + await expect(page.getByRole('link', { name: 'Draw a polygon' })).toBeVisible(); + await expect(page.getByLabel('Zoom in')).toBeVisible(); + // At least one property is rendered as a cluster + await expect(page.locator('.cluster-marker').first()).toBeVisible(); +}) + +test('sidebar controls work as expected (not filter)', async ({ page }) => { + await page.goto(BASE_URL); + await loginIDIR({ page }); + // Leaflet map is visible + await expect(page.locator('#parcel-map')).toBeVisible(); + // Sidebar is visible + await expect(page.locator('#map-sidebar')).toBeVisible(); + // Sidebar has properties (also ensures that properties were loaded first) + await expect(page.locator('.property-row').first()).toBeVisible(); + + // Can click sidebar navigation and it affects value + await expect(page.locator('#sidebar-count')).toContainText('1 of'); + // Up + await page.locator('#sidebar-increment').click(); + await expect(page.locator('#sidebar-count')).toContainText('2 of'); + // Down + await page.locator('#sidebar-decrement').click(); + await expect(page.locator('#sidebar-count')).toContainText('1 of'); + // Down again + await page.locator('#sidebar-decrement').click(); + await expect(page.locator('#sidebar-count')).toContainText('1 of'); + + // Can hide and unhide sidebar + await page.locator('#sidebar-button-close').click(); + // Sidebar should be off screen + await expect(page.locator('#map-sidebar')).not.toBeInViewport(); + // Click again and see sidebar + await page.locator('#sidebar-button').click(); + await expect(page.locator('#map-sidebar')).toBeInViewport(); +}) + +test('user can filter map properties', async ({ page }) => { + const getSnackbarCount = async () => { + const snackbarCount = await page.locator('#snackbar-popup').textContent() + return parseInt(snackbarCount?.split(' ').at(0)!) + } + + await page.goto(BASE_URL); + await loginIDIR({ page }); + // Leaflet map is visible + await expect(page.locator('#parcel-map')).toBeVisible(); + // Sidebar has properties (also ensures that properties were loaded first) + await expect(page.locator('.property-row').first()).toBeVisible(); + // Get starting number of properties + const startingNumber = await getSnackbarCount() + + // Open filter + await page.locator('#map-filter-open').click(); + await expect(page.locator('#map-filter-container')).toBeInViewport(); + + // Enter values in the filters and check if the number goes lower each time + await page.getByLabel('Agencies').click(); + await page.getByRole('combobox', { name: 'Agencies' }).fill('real'); + await page.getByRole('listbox', { name: 'Agencies' }).locator('span').click(); + await page.getByRole('button', { name: 'Filter' }).click(); + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Filter' }).click(), + ]); + const afterAgencies = await getSnackbarCount(); + expect(afterAgencies).toBeLessThan(startingNumber); + + await page.locator('div').filter({ hasText: /^Property Types$/ }).getByLabel('Open').click(); + await page.getByText('Land', { exact: true }).click(); + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Filter' }).click(), + ]); const afterPropertyTypes = await getSnackbarCount(); + expect(afterPropertyTypes).toBeLessThan(afterAgencies); + + await page.locator('div').filter({ hasText: /^Regional Districts$/ }).getByLabel('Open').click(); + await page.getByLabel('Regional Districts').fill('cap'); + await page.getByText('Capital Regional District').click(); + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Filter' }).click(), + ]); const afterRegionalDistricts = await getSnackbarCount(); + expect(afterRegionalDistricts).toBeLessThan(afterPropertyTypes); + + await page.locator('div').filter({ hasText: /^Administrative Areas$/ }).getByLabel('Open').click(); + await page.getByLabel('Administrative Areas').fill('vic'); + await page.getByRole('option', { name: 'Victoria' }).click(); + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Filter' }).click(), + ]); const afterAdminAreas = await getSnackbarCount(); + expect(afterAdminAreas).toBeLessThan(afterRegionalDistricts); + + await page.getByLabel('Classifications').click(); + await page.getByText('Core Operational').click(); + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Filter' }).click(), + ]); const afterClassifications = await getSnackbarCount(); + expect(afterClassifications).toBeLessThan(afterAdminAreas); + + // Clear and get back original number + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/search/geo')), + page.getByRole('button', { name: 'Clear' }).click(), + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + ]); + const afterClear = await getSnackbarCount(); + expect(afterClear).toEqual(startingNumber); + + // Check visibility of filter when opening and closing sidebar + await page.locator('#sidebar-button-close').click(); + await expect(page.locator('#map-filter-container')).not.toBeInViewport(); + // Return sidebar/filter + await page.locator('#sidebar-button').getByRole('img').click(); + await expect(page.locator('#map-filter-container')).toBeInViewport(); + // Close filter + await page.locator('#map-sidebar').getByRole('button').first().click(); + await expect(page.locator('#map-filter-container')).not.toBeInViewport(); +}) diff --git a/e2e/tests/map/clusters.spec.ts b/e2e/tests/map/clusters.spec.ts deleted file mode 100644 index 04c5422c5..000000000 --- a/e2e/tests/map/clusters.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { test, expect } from "@playwright/test"; -import { BASE_URL } from "../../env"; -import { loginIDIR } from "../../functions/login"; - -test('user can view all parts of map', async ({ page }) => { - await page.goto(BASE_URL); - await loginIDIR({ page }); - // Leaflet map is visible - await expect(page.locator('#parcel-map')).toBeVisible(); - // Controls on map are visible - await expect(page.getByRole('link', { name: 'Draw a polygon' })).toBeVisible(); - await expect(page.getByLabel('Zoom in')).toBeVisible(); - // At least one property is rendered as a cluster - await expect(page.locator('.cluster-marker').first()).toBeVisible(); -}) - -test('sidebar controls work as expected (not filter)', async ({ page }) => { - await page.goto(BASE_URL); - await loginIDIR({ page }); - // Leaflet map is visible - await expect(page.locator('#parcel-map')).toBeVisible(); - // Sidebar is visible - await expect(page.locator('#map-sidebar')).toBeVisible(); - // Sidebar has properties (also ensures that properties were loaded first) - await expect(page.locator('.property-row').first()).toBeVisible(); - - // Can click sidebar navigation and it affects value - await expect(page.locator('#map-sidebar')).toContainText('1 of'); - // Up - await page.locator('#sidebar-increment').click({timeout: 10000}); - await expect(page.locator('#map-sidebar')).toContainText('2 of'); - // Down - await page.locator('#sidebar-decrement').click(); - await expect(page.locator('#map-sidebar')).toContainText('1 of'); - // Down again - await page.locator('#sidebar-decrement').click(); - await expect(page.locator('#map-sidebar')).toContainText('1 of'); - - // Can hide and unhide sidebar - await page.locator('#sidebar-button-close').click(); - // Sidebar should be off screen - await expect(page.locator('#map-sidebar')).not.toBeInViewport(); - // Click again and see sidebar - await page.locator('#sidebar-button').click(); - await expect(page.locator('#map-sidebar')).toBeInViewport(); -}) diff --git a/react-app/src/components/map/sidebar/MapSidebar.tsx b/react-app/src/components/map/sidebar/MapSidebar.tsx index d6d652558..ca6d26d27 100644 --- a/react-app/src/components/map/sidebar/MapSidebar.tsx +++ b/react-app/src/components/map/sidebar/MapSidebar.tsx @@ -94,7 +94,7 @@ const MapSidebar = (props: MapSidebarProps) => { {/* Sidebar Header */} - setFilterOpen(!filterOpen)}> + setFilterOpen(!filterOpen)} id="map-filter-open"> @@ -111,6 +111,7 @@ const MapSidebar = (props: MapSidebarProps) => { {`${pageIndex + 1} of ${formatNumber(Math.max(Math.ceil(propertiesInBounds.length / propertyPageSize), 1))} (${formatNumber(propertiesInBounds.length)} items)`} diff --git a/react-app/src/contexts/snackbarContext.tsx b/react-app/src/contexts/snackbarContext.tsx index 0a787c06e..ade5391fd 100644 --- a/react-app/src/contexts/snackbarContext.tsx +++ b/react-app/src/contexts/snackbarContext.tsx @@ -111,6 +111,7 @@ const SnackBarContextProvider = (props: ISnackBarContext) => { {children} Date: Thu, 10 Oct 2024 12:02:32 -0700 Subject: [PATCH 12/20] add some agency table tests --- e2e/functions/mockRequests.ts | 28 ---------------- e2e/tests/agencies.spec.ts | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 e2e/tests/agencies.spec.ts diff --git a/e2e/functions/mockRequests.ts b/e2e/functions/mockRequests.ts index 61970658d..47102cb96 100644 --- a/e2e/functions/mockRequests.ts +++ b/e2e/functions/mockRequests.ts @@ -22,34 +22,6 @@ interface User { Status: string; } -/** - * For creating a user object normally returned by the /self route - * @param props A Partial user object - * @returns - */ -const createSelfResponse = (props?: Partial): User => ({ - "CreatedById": "00000000-0000-0000-0000-000000000000", - "CreatedOn": "2023-07-25T00:32:41.938Z", - "UpdatedById": "a619c656-b263-4a17-9b87-03459f43b349", - "UpdatedOn": "2024-10-09T23:49:47.650Z", - "Id": "a619c656-b263-4a17-9b87-03459f43b352", - "Username": "a619c656b2634a179b8703459f43b529@idir", - "FirstName": "Joe", - "MiddleName": null, - "LastName": "Tester", - "Email": "user.email@gov.bc.ca", - "Position": "Developer", - "Note": null, - "LastLogin": "2024-10-09T16:49:47.629Z", - "ApprovedById": "80e0111a-b56c-48dd-b55e-f99f2205c4bd", - "ApprovedOn": "2023-08-26T03:03:47.911Z", - "KeycloakUserId": "a619c656-b263-4a17-9b87-03459f43b352", - "AgencyId": 2, - "RoleId": "00000000-0000-0000-0000-000000000000", - "Status": "Active", - ...props, -}) - /** * Use this at the start of a test to set your user information if needed. * Otherwise, your user information will reflect the actual user credentials used in testing. diff --git a/e2e/tests/agencies.spec.ts b/e2e/tests/agencies.spec.ts new file mode 100644 index 000000000..66e30bc7b --- /dev/null +++ b/e2e/tests/agencies.spec.ts @@ -0,0 +1,62 @@ +import { test, expect, Page } from '@playwright/test' +import { BASE_URL } from '../env'; +import { loginIDIR } from '../functions/login'; +import { mockSelf } from '../functions/mockRequests'; + +const getToAgencies = async (page: Page) => { + await page.goto(BASE_URL); + await mockSelf(page, { RoleId: "00000000-0000-0000-0000-000000000000" }); // guarantee admin status + await loginIDIR({ page }); + + await page.getByRole('heading', { name: 'Administration' }).click(); + await page.getByRole('menuitem', { name: 'Agencies' }).click(); + await page.getByText('Agencies Overview'); +} + +test('user can navigate to agencies page as an admin', async ({ page }) => { + await getToAgencies(page); + await expect(page.getByText('Agencies Overview')).toBeVisible(); +}) + +test('user can filter using the keyword search', async ({ page }) => { + await getToAgencies(page); + + await page.getByTestId('SearchIcon').click(); + await page.getByPlaceholder('Search...').click(); + await page.getByPlaceholder('Search...').fill('real'); + await expect(page.getByRole('gridcell', { name: 'Real Property Division' })).toBeVisible(); + await page.getByLabel('Clear Filter').click(); + await expect(page.getByPlaceholder('Search...')).toBeEmpty(); + await expect(page.getByLabel('Clear Filter')).not.toBeVisible(); +}) + +test('user can filter using the select dropdown', async ({ page }) => { + await getToAgencies(page); + + await page.getByText('All Agencies').click(); + await page.getByRole('option', { name: 'Disabled', exact: true }).click(); + await expect(page.getByTestId('FilterAltIcon')).toBeVisible(); // The icon in the column header + await expect(page.getByLabel('Clear Filter')).toBeVisible(); + await page.getByLabel('Clear Filter').click(); + await expect(page.getByLabel('Clear Filter')).not.toBeVisible(); +}) + +test('user can filter using the column filter', async ({ page }) => { + await getToAgencies(page); + + await page.getByText('Name', { exact: true }).hover(); + await page.getByRole('button', { name: 'Menu' }).click(); + await page.getByRole('menuitem', { name: 'Filter' }).click(); + await page.getByPlaceholder('Filter value').click(); + await page.getByPlaceholder('Filter value').fill('real'); + await page.getByText('Agencies Overview (1 rows)All').click(); // To get filter off screen + await expect(page.getByRole('gridcell', { name: 'Real Property Division' })).toBeVisible(); +}) + +test('user select a row and go to that agency', async ({ page }) => { + await getToAgencies(page); + + await page.getByRole('gridcell', { name: 'Advanced Education & Skills' }).first().click(); + await expect(page.getByText('Agency Details')).toBeVisible(); + await expect(page.getByText('Advanced Education & Skills')).toBeVisible(); +}) From 85381591cdc8b62c95a91d87bbd0e40a506770b7 Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Thu, 10 Oct 2024 12:09:48 -0700 Subject: [PATCH 13/20] update readme --- e2e/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/README.md b/e2e/README.md index 279c1a185..cc1222cb1 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -18,3 +18,4 @@ Change BASE_URL to target other environments as needed. - `npx playwright test`: Runs tests in headless mode. - `npx playwright test --ui`: Starts the Playwright UI. +- `npx playwright codegen `: Starts the Codegen test maker. You can use this to capture movements in a browser and construct tests. From ce9a91754a2a910d7bbfd4b2dc9a8850d18a2c2d Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Thu, 10 Oct 2024 12:12:50 -0700 Subject: [PATCH 14/20] agencies comments --- e2e/tests/agencies.spec.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/e2e/tests/agencies.spec.ts b/e2e/tests/agencies.spec.ts index 66e30bc7b..ac9e22d44 100644 --- a/e2e/tests/agencies.spec.ts +++ b/e2e/tests/agencies.spec.ts @@ -1,3 +1,8 @@ +/** + * Testing navigating to Agency Table and using some filter functions. + * There are some assumptions that you will have some standard agencies populated. + */ + import { test, expect, Page } from '@playwright/test' import { BASE_URL } from '../env'; import { loginIDIR } from '../functions/login'; @@ -25,6 +30,7 @@ test('user can filter using the keyword search', async ({ page }) => { await page.getByPlaceholder('Search...').click(); await page.getByPlaceholder('Search...').fill('real'); await expect(page.getByRole('gridcell', { name: 'Real Property Division' })).toBeVisible(); + // Clear filter and make sure it is empty await page.getByLabel('Clear Filter').click(); await expect(page.getByPlaceholder('Search...')).toBeEmpty(); await expect(page.getByLabel('Clear Filter')).not.toBeVisible(); @@ -35,6 +41,7 @@ test('user can filter using the select dropdown', async ({ page }) => { await page.getByText('All Agencies').click(); await page.getByRole('option', { name: 'Disabled', exact: true }).click(); + // Not clear what to look for here to guarantee that only Disabled agencies show await expect(page.getByTestId('FilterAltIcon')).toBeVisible(); // The icon in the column header await expect(page.getByLabel('Clear Filter')).toBeVisible(); await page.getByLabel('Clear Filter').click(); @@ -57,6 +64,7 @@ test('user select a row and go to that agency', async ({ page }) => { await getToAgencies(page); await page.getByRole('gridcell', { name: 'Advanced Education & Skills' }).first().click(); + // These two expects together should confirm user is on correct details page await expect(page.getByText('Agency Details')).toBeVisible(); await expect(page.getByText('Advanced Education & Skills')).toBeVisible(); }) From 4ff1ee5e778ac10f70cdd032e9f1a487719f54df Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Thu, 10 Oct 2024 13:45:08 -0700 Subject: [PATCH 15/20] no webkit, less flakey property count --- e2e/playwright.config.ts | 10 +++++----- e2e/tests/map.spec.ts | 25 +++++++++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 5233031ce..c8ebbd24c 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -41,11 +41,11 @@ export default defineConfig({ name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, + // Webkit not compatible with Keycloak package + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, /* Test against mobile viewports. */ // { diff --git a/e2e/tests/map.spec.ts b/e2e/tests/map.spec.ts index 643686fcd..844f2ef7c 100644 --- a/e2e/tests/map.spec.ts +++ b/e2e/tests/map.spec.ts @@ -46,9 +46,9 @@ test('sidebar controls work as expected (not filter)', async ({ page }) => { }) test('user can filter map properties', async ({ page }) => { - const getSnackbarCount = async () => { - const snackbarCount = await page.locator('#snackbar-popup').textContent() - return parseInt(snackbarCount?.split(' ').at(0)!) + const getPropertyCount = async () => { + const sidebarCount = await page.locator('#sidebar-count').textContent(); + return parseInt(sidebarCount?.match(/\((.+)\)/)?.at(1)?.split(' ').at(0)?.replaceAll(',', '')!) } await page.goto(BASE_URL); @@ -58,7 +58,7 @@ test('user can filter map properties', async ({ page }) => { // Sidebar has properties (also ensures that properties were loaded first) await expect(page.locator('.property-row').first()).toBeVisible(); // Get starting number of properties - const startingNumber = await getSnackbarCount() + const startingNumber = await getPropertyCount() // Open filter await page.locator('#map-filter-open').click(); @@ -72,8 +72,9 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time ]); - const afterAgencies = await getSnackbarCount(); + const afterAgencies = await getPropertyCount(); expect(afterAgencies).toBeLessThan(startingNumber); await page.locator('div').filter({ hasText: /^Property Types$/ }).getByLabel('Open').click(); @@ -81,7 +82,8 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - ]); const afterPropertyTypes = await getSnackbarCount(); + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + ]); const afterPropertyTypes = await getPropertyCount(); expect(afterPropertyTypes).toBeLessThan(afterAgencies); await page.locator('div').filter({ hasText: /^Regional Districts$/ }).getByLabel('Open').click(); @@ -90,7 +92,8 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - ]); const afterRegionalDistricts = await getSnackbarCount(); + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + ]); const afterRegionalDistricts = await getPropertyCount(); expect(afterRegionalDistricts).toBeLessThan(afterPropertyTypes); await page.locator('div').filter({ hasText: /^Administrative Areas$/ }).getByLabel('Open').click(); @@ -99,7 +102,8 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - ]); const afterAdminAreas = await getSnackbarCount(); + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + ]); const afterAdminAreas = await getPropertyCount(); expect(afterAdminAreas).toBeLessThan(afterRegionalDistricts); await page.getByLabel('Classifications').click(); @@ -107,7 +111,8 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - ]); const afterClassifications = await getSnackbarCount(); + new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + ]); const afterClassifications = await getPropertyCount(); expect(afterClassifications).toBeLessThan(afterAdminAreas); // Clear and get back original number @@ -116,7 +121,7 @@ test('user can filter map properties', async ({ page }) => { page.getByRole('button', { name: 'Clear' }).click(), new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time ]); - const afterClear = await getSnackbarCount(); + const afterClear = await getPropertyCount(); expect(afterClear).toEqual(startingNumber); // Check visibility of filter when opening and closing sidebar From 5eba1fcb75aace8ac93a02bf1a5753b72e949299 Mon Sep 17 00:00:00 2001 From: Dylan Barkowsky <37922247+dbarkowsky@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:46:58 -0700 Subject: [PATCH 16/20] Update e2e/.gitignore Co-authored-by: TaylorFries <78506153+TaylorFries@users.noreply.github.com> --- e2e/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/.gitignore b/e2e/.gitignore index 68c5d18f0..a86a3040c 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -3,3 +3,4 @@ node_modules/ /playwright-report/ /blob-report/ /playwright/.cache/ +*.env From 5ea972523f3a01f8676c9fddcda655c9f159b912 Mon Sep 17 00:00:00 2001 From: taylorfries Date: Wed, 16 Oct 2024 11:54:31 -0700 Subject: [PATCH 17/20] updates to playwright config --- e2e/playwright.config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index c8ebbd24c..1515fcf58 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -13,6 +13,9 @@ import { BASE_URL } from './env'; * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ + expect: { + timeout: 10000, + }, testDir: './tests', /* Run tests in files in parallel */ fullyParallel: true, @@ -28,6 +31,7 @@ export default defineConfig({ use: { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + screenshot: 'only-on-failure', }, /* Configure projects for major browsers */ From 2996c5d18dd0f4a0f0283b61c9c679130d5cbeba Mon Sep 17 00:00:00 2001 From: dbarkowsky Date: Wed, 16 Oct 2024 13:51:15 -0700 Subject: [PATCH 18/20] add additional test delays --- e2e/functions/login.ts | 14 +++++++++++--- e2e/playwright.config.ts | 3 +-- e2e/tests/agencies.spec.ts | 2 +- e2e/tests/login.spec.ts | 6 +++--- e2e/tests/map.spec.ts | 27 +++++++++++++++++---------- e2e/tests/noAccess.spec.ts | 8 ++++---- 6 files changed, 37 insertions(+), 23 deletions(-) diff --git a/e2e/functions/login.ts b/e2e/functions/login.ts index d598cc81b..2fcae2dec 100644 --- a/e2e/functions/login.ts +++ b/e2e/functions/login.ts @@ -2,14 +2,18 @@ * Functions for automating user logins. */ +import { Page } from '@playwright/test'; import {BASE_URL, BCEID_PASSWORD, BCEID_USERNAME, BCSC_PASSWORD, BCSC_USERNAME, IDIR_PASSWORD, IDIR_USERNAME} from '../env'; // Will only work in DEV/TEST environments -export const loginBCServicesCard = async ({ page }) => { +export const loginBCServicesCard = async (page: Page) => { await page.goto(BASE_URL); await page.getByRole('button', { name: 'Login' }).click(); + await page.waitForLoadState('networkidle'); await page.getByRole('link', { name: 'BC Services Card' }).click(); + await page.waitForLoadState('networkidle'); await page.getByLabel('Log in with Test with').click(); + await page.waitForLoadState('networkidle'); await page.getByLabel('Email or username').click(); await page.getByLabel('Email or username').fill(BCSC_USERNAME); await page.getByLabel('Password').click(); @@ -17,20 +21,24 @@ export const loginBCServicesCard = async ({ page }) => { await page.getByRole('button', { name: 'Continue' }).click(); }; -export const loginBCeID = async ({ page }) => { +export const loginBCeID = async (page: Page) => { await page.goto(BASE_URL); await page.getByRole('button', { name: 'Login' }).click(); + await page.waitForLoadState('networkidle'); await page.getByRole('link', { name: 'Basic or Business BCeID' }).click(); + await page.waitForLoadState('networkidle'); await page.locator('#user').fill(BCEID_USERNAME); await page.getByLabel('Password').click(); await page.getByLabel('Password').fill(BCEID_PASSWORD); await page.getByRole('button', { name: 'Continue' }).click(); }; -export const loginIDIR = async ({ page }) => { +export const loginIDIR = async (page: Page) => { await page.goto(BASE_URL); await page.getByRole('button', { name: 'Login' }).click(); + await page.waitForLoadState('networkidle'); await page.getByRole('link', { name: 'IDIR' }).click(); + await page.waitForLoadState('networkidle'); await page.locator('#user').fill(IDIR_USERNAME); await page.getByLabel('Password').click(); await page.getByLabel('Password').fill(IDIR_PASSWORD); diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 1515fcf58..7c6c3cfec 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -1,5 +1,4 @@ import { defineConfig, devices } from '@playwright/test'; -import { BASE_URL } from './env'; /** * Read environment variables from file. @@ -33,7 +32,7 @@ export default defineConfig({ trace: 'on-first-retry', screenshot: 'only-on-failure', }, - + /* Configure projects for major browsers */ projects: [ { diff --git a/e2e/tests/agencies.spec.ts b/e2e/tests/agencies.spec.ts index ac9e22d44..80b0091bb 100644 --- a/e2e/tests/agencies.spec.ts +++ b/e2e/tests/agencies.spec.ts @@ -11,7 +11,7 @@ import { mockSelf } from '../functions/mockRequests'; const getToAgencies = async (page: Page) => { await page.goto(BASE_URL); await mockSelf(page, { RoleId: "00000000-0000-0000-0000-000000000000" }); // guarantee admin status - await loginIDIR({ page }); + await loginIDIR(page); await page.getByRole('heading', { name: 'Administration' }).click(); await page.getByRole('menuitem', { name: 'Agencies' }).click(); diff --git a/e2e/tests/login.spec.ts b/e2e/tests/login.spec.ts index 4ee8943ec..e620292bf 100644 --- a/e2e/tests/login.spec.ts +++ b/e2e/tests/login.spec.ts @@ -6,16 +6,16 @@ import { test, expect } from '@playwright/test'; import { loginBCServicesCard, loginBCeID, loginIDIR } from '../functions/login'; test('log in with BC Services Card', async ({ page }) => { - await loginBCServicesCard({ page }); + await loginBCServicesCard(page); await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); }); test('log in with BCeID', async ({ page }) => { - await loginBCeID({ page }); + await loginBCeID(page); await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); }); test('log in with IDIR', async ({ page }) => { - await loginIDIR({ page }); + await loginIDIR(page); await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); }); diff --git a/e2e/tests/map.spec.ts b/e2e/tests/map.spec.ts index 844f2ef7c..f5e574228 100644 --- a/e2e/tests/map.spec.ts +++ b/e2e/tests/map.spec.ts @@ -4,8 +4,9 @@ import { loginIDIR } from "../functions/login"; test('user can view all parts of map', async ({ page }) => { await page.goto(BASE_URL); - await loginIDIR({ page }); + await loginIDIR(page); // Leaflet map is visible + await page.waitForLoadState(); await expect(page.locator('#parcel-map')).toBeVisible(); // Controls on map are visible await expect(page.getByRole('link', { name: 'Draw a polygon' })).toBeVisible(); @@ -16,8 +17,9 @@ test('user can view all parts of map', async ({ page }) => { test('sidebar controls work as expected (not filter)', async ({ page }) => { await page.goto(BASE_URL); - await loginIDIR({ page }); + await loginIDIR(page); // Leaflet map is visible + await page.waitForLoadState(); await expect(page.locator('#parcel-map')).toBeVisible(); // Sidebar is visible await expect(page.locator('#map-sidebar')).toBeVisible(); @@ -28,12 +30,15 @@ test('sidebar controls work as expected (not filter)', async ({ page }) => { await expect(page.locator('#sidebar-count')).toContainText('1 of'); // Up await page.locator('#sidebar-increment').click(); + await page.waitForLoadState('networkidle'); await expect(page.locator('#sidebar-count')).toContainText('2 of'); // Down await page.locator('#sidebar-decrement').click(); + await page.waitForLoadState('networkidle'); await expect(page.locator('#sidebar-count')).toContainText('1 of'); // Down again await page.locator('#sidebar-decrement').click(); + await page.waitForLoadState('networkidle'); await expect(page.locator('#sidebar-count')).toContainText('1 of'); // Can hide and unhide sidebar @@ -52,8 +57,9 @@ test('user can filter map properties', async ({ page }) => { } await page.goto(BASE_URL); - await loginIDIR({ page }); + await loginIDIR(page); // Leaflet map is visible + await page.waitForLoadState(); await expect(page.locator('#parcel-map')).toBeVisible(); // Sidebar has properties (also ensures that properties were loaded first) await expect(page.locator('.property-row').first()).toBeVisible(); @@ -72,7 +78,7 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); const afterAgencies = await getPropertyCount(); expect(afterAgencies).toBeLessThan(startingNumber); @@ -82,8 +88,9 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time - ]); const afterPropertyTypes = await getPropertyCount(); + page.waitForTimeout(1000) // Otherwise counter doesn't update in time + ]); + const afterPropertyTypes = await getPropertyCount(); expect(afterPropertyTypes).toBeLessThan(afterAgencies); await page.locator('div').filter({ hasText: /^Regional Districts$/ }).getByLabel('Open').click(); @@ -92,7 +99,7 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); const afterRegionalDistricts = await getPropertyCount(); expect(afterRegionalDistricts).toBeLessThan(afterPropertyTypes); @@ -102,7 +109,7 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); const afterAdminAreas = await getPropertyCount(); expect(afterAdminAreas).toBeLessThan(afterRegionalDistricts); @@ -111,7 +118,7 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); const afterClassifications = await getPropertyCount(); expect(afterClassifications).toBeLessThan(afterAdminAreas); @@ -119,7 +126,7 @@ test('user can filter map properties', async ({ page }) => { await Promise.all([ page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Clear' }).click(), - new Promise(resolve => setTimeout(resolve, 1000)) // Otherwise snackbar doesn't open in time + page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); const afterClear = await getPropertyCount(); expect(afterClear).toEqual(startingNumber); diff --git a/e2e/tests/noAccess.spec.ts b/e2e/tests/noAccess.spec.ts index 31b83f4b0..44af29116 100644 --- a/e2e/tests/noAccess.spec.ts +++ b/e2e/tests/noAccess.spec.ts @@ -10,24 +10,24 @@ import { mockSelf } from "../functions/mockRequests"; test('user without a role get the no-role page', async ({ page }) => { await mockSelf(page, { RoleId: undefined }); - await loginBCServicesCard({ page }); + await loginBCServicesCard(page); await expect(page.getByRole('heading', { name: 'Awaiting Role' })).toBeVisible(); }) test('user with On Hold status should get ____ page', async ({ page }) => { await mockSelf(page, { Status: 'OnHold' }); - await loginBCServicesCard({ page }); + await loginBCServicesCard(page); await expect(page.getByRole('heading', { name: 'Access Pending' })).toBeVisible(); }) test('user with Disabled status should get ____ page', async ({ page }) => { await mockSelf(page, { Status: 'Disabled' }); - await loginBCServicesCard({ page }); + await loginBCServicesCard(page); await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); }) test('user with Denied status should get ____ page', async ({ page }) => { await mockSelf(page, { Status: 'Denied' }); - await loginBCServicesCard({ page }); + await loginBCServicesCard(page); await expect(page.getByRole('heading', { name: 'Account Inactive' })).toBeVisible(); }) From 468fc5462f71ff424e008b007f06b2bab6de159f Mon Sep 17 00:00:00 2001 From: taylorfries Date: Thu, 17 Oct 2024 07:43:21 -0700 Subject: [PATCH 19/20] updates from waitForTimeout to waitFor specific element --- e2e/tests/login.spec.ts | 1 + e2e/tests/map.spec.ts | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/e2e/tests/login.spec.ts b/e2e/tests/login.spec.ts index e620292bf..d631206db 100644 --- a/e2e/tests/login.spec.ts +++ b/e2e/tests/login.spec.ts @@ -12,6 +12,7 @@ test('log in with BC Services Card', async ({ page }) => { test('log in with BCeID', async ({ page }) => { await loginBCeID(page); + await page.getByRole('button', { name: 'Logout' }).waitFor(); await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible(); }); diff --git a/e2e/tests/map.spec.ts b/e2e/tests/map.spec.ts index f5e574228..97db1d732 100644 --- a/e2e/tests/map.spec.ts +++ b/e2e/tests/map.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, Page } from "@playwright/test"; import { BASE_URL } from "../env"; import { loginIDIR } from "../functions/login"; @@ -51,7 +51,8 @@ test('sidebar controls work as expected (not filter)', async ({ page }) => { }) test('user can filter map properties', async ({ page }) => { - const getPropertyCount = async () => { + const getPropertyCount = async (page: Page) => { + await page.locator('#sidebar-count').waitFor(); const sidebarCount = await page.locator('#sidebar-count').textContent(); return parseInt(sidebarCount?.match(/\((.+)\)/)?.at(1)?.split(' ').at(0)?.replaceAll(',', '')!) } @@ -64,7 +65,7 @@ test('user can filter map properties', async ({ page }) => { // Sidebar has properties (also ensures that properties were loaded first) await expect(page.locator('.property-row').first()).toBeVisible(); // Get starting number of properties - const startingNumber = await getPropertyCount() + const startingNumber = await getPropertyCount(page) // Open filter await page.locator('#map-filter-open').click(); @@ -80,7 +81,7 @@ test('user can filter map properties', async ({ page }) => { page.getByRole('button', { name: 'Filter' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time ]); - const afterAgencies = await getPropertyCount(); + const afterAgencies = await getPropertyCount(page); expect(afterAgencies).toBeLessThan(startingNumber); await page.locator('div').filter({ hasText: /^Property Types$/ }).getByLabel('Open').click(); @@ -89,8 +90,8 @@ test('user can filter map properties', async ({ page }) => { page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time - ]); - const afterPropertyTypes = await getPropertyCount(); + ]); + const afterPropertyTypes = await getPropertyCount(page); expect(afterPropertyTypes).toBeLessThan(afterAgencies); await page.locator('div').filter({ hasText: /^Regional Districts$/ }).getByLabel('Open').click(); @@ -100,7 +101,7 @@ test('user can filter map properties', async ({ page }) => { page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time - ]); const afterRegionalDistricts = await getPropertyCount(); + ]); const afterRegionalDistricts = await getPropertyCount(page); expect(afterRegionalDistricts).toBeLessThan(afterPropertyTypes); await page.locator('div').filter({ hasText: /^Administrative Areas$/ }).getByLabel('Open').click(); @@ -110,7 +111,7 @@ test('user can filter map properties', async ({ page }) => { page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time - ]); const afterAdminAreas = await getPropertyCount(); + ]); const afterAdminAreas = await getPropertyCount(page); expect(afterAdminAreas).toBeLessThan(afterRegionalDistricts); await page.getByLabel('Classifications').click(); @@ -119,7 +120,7 @@ test('user can filter map properties', async ({ page }) => { page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Filter' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time - ]); const afterClassifications = await getPropertyCount(); + ]); const afterClassifications = await getPropertyCount(page); expect(afterClassifications).toBeLessThan(afterAdminAreas); // Clear and get back original number @@ -127,17 +128,20 @@ test('user can filter map properties', async ({ page }) => { page.waitForResponse(resp => resp.url().includes('/search/geo')), page.getByRole('button', { name: 'Clear' }).click(), page.waitForTimeout(1000) // Otherwise counter doesn't update in time - ]); - const afterClear = await getPropertyCount(); + ]); + const afterClear = await getPropertyCount(page); expect(afterClear).toEqual(startingNumber); // Check visibility of filter when opening and closing sidebar await page.locator('#sidebar-button-close').click(); + await page.locator('#map-filter-container').waitFor(); await expect(page.locator('#map-filter-container')).not.toBeInViewport(); // Return sidebar/filter await page.locator('#sidebar-button').getByRole('img').click(); await expect(page.locator('#map-filter-container')).toBeInViewport(); // Close filter + await page.locator('#map-sidebar').getByRole('button').first().waitFor(); await page.locator('#map-sidebar').getByRole('button').first().click(); + await page.locator('#map-filter-container').waitFor(); await expect(page.locator('#map-filter-container')).not.toBeInViewport(); }) From f3c04dc5a0488f366989bbcc87b4f585e100dd3e Mon Sep 17 00:00:00 2001 From: taylorfries Date: Thu, 17 Oct 2024 09:30:25 -0700 Subject: [PATCH 20/20] more waitFor and waitForTimeout --- e2e/tests/agencies.spec.ts | 2 +- e2e/tests/map.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/tests/agencies.spec.ts b/e2e/tests/agencies.spec.ts index 80b0091bb..a5fa2e580 100644 --- a/e2e/tests/agencies.spec.ts +++ b/e2e/tests/agencies.spec.ts @@ -12,7 +12,7 @@ const getToAgencies = async (page: Page) => { await page.goto(BASE_URL); await mockSelf(page, { RoleId: "00000000-0000-0000-0000-000000000000" }); // guarantee admin status await loginIDIR(page); - + await page.getByRole('heading', { name: 'Administration' }).waitFor(); await page.getByRole('heading', { name: 'Administration' }).click(); await page.getByRole('menuitem', { name: 'Agencies' }).click(); await page.getByText('Agencies Overview'); diff --git a/e2e/tests/map.spec.ts b/e2e/tests/map.spec.ts index 97db1d732..eef820aeb 100644 --- a/e2e/tests/map.spec.ts +++ b/e2e/tests/map.spec.ts @@ -134,7 +134,7 @@ test('user can filter map properties', async ({ page }) => { // Check visibility of filter when opening and closing sidebar await page.locator('#sidebar-button-close').click(); - await page.locator('#map-filter-container').waitFor(); + await page.waitForTimeout(500); await expect(page.locator('#map-filter-container')).not.toBeInViewport(); // Return sidebar/filter await page.locator('#sidebar-button').getByRole('img').click(); @@ -142,6 +142,6 @@ test('user can filter map properties', async ({ page }) => { // Close filter await page.locator('#map-sidebar').getByRole('button').first().waitFor(); await page.locator('#map-sidebar').getByRole('button').first().click(); - await page.locator('#map-filter-container').waitFor(); + await page.waitForTimeout(500); await expect(page.locator('#map-filter-container')).not.toBeInViewport(); })