diff --git a/playwright-e2e/dsm/component/filters/sections/search/search.ts b/playwright-e2e/dsm/component/filters/sections/search/search.ts index 2d64efe9db..89cc9a7384 100644 --- a/playwright-e2e/dsm/component/filters/sections/search/search.ts +++ b/playwright-e2e/dsm/component/filters/sections/search/search.ts @@ -182,7 +182,7 @@ export class Search { /* XPaths */ - private get openButtonXPath(): string { + public get openButtonXPath(): string { return ( "//div[text()[normalize-space()='Search'] and button[.//*[local-name()='svg' and @data-icon='search']/*[local-name()='path']]]/button" ); diff --git a/playwright-e2e/dsm/component/modal.ts b/playwright-e2e/dsm/component/modal.ts index b1b3e56058..8babb22844 100644 --- a/playwright-e2e/dsm/component/modal.ts +++ b/playwright-e2e/dsm/component/modal.ts @@ -2,6 +2,7 @@ import { Locator, Page } from '@playwright/test'; import Button from 'dss/component/button'; import Input from 'dss/component/input'; import Radiobutton from 'dss/component/radiobutton'; +import { waitForNoSpinner } from 'utils/test-utils'; export default class Modal { private readonly rootSelector: Locator; @@ -31,6 +32,15 @@ export default class Modal { return this.headerLocator().innerText(); } + async getBodyText(): Promise { + return this.bodyLocator().innerText(); + } + + async close(): Promise { + await this.getButton({ label: 'Close' }).click(); + await waitForNoSpinner(this.page); + } + public getButton(opts: { label?: string | RegExp; ddpTestID?: string }): Button { const { label, ddpTestID } = opts; return new Button(this.page, { label, ddpTestID, root: this.toLocator() }); diff --git a/playwright-e2e/dsm/component/tables/participant-list-table.ts b/playwright-e2e/dsm/component/tables/participant-list-table.ts index 4d121f0d06..a21de08125 100644 --- a/playwright-e2e/dsm/component/tables/participant-list-table.ts +++ b/playwright-e2e/dsm/component/tables/participant-list-table.ts @@ -4,9 +4,9 @@ import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import {ParticipantsListPaginator} from 'lib/component/dsm/paginators/participantsListPaginator'; import {rows} from 'lib/component/dsm/paginators/types/rowsPerPage'; import { getDate, offsetDaysFromToday } from 'utils/date-utils'; -import { AdditionalFilter } from '../filters/sections/search/search-enums'; +import { waitForNoSpinner } from 'utils/test-utils'; +import { AdditionalFilter } from 'dsm/component/filters/sections/search/search-enums'; import ParticipantListPage from 'dsm/pages/participant-list-page'; -import addColumnsToParticipantList from 'dsm/pages/participant-list-page'; export class ParticipantListTable extends Table { private readonly _participantPage: ParticipantPage = new ParticipantPage(this.page); @@ -34,6 +34,7 @@ export class ParticipantListTable extends Table { public async openParticipantPageAt(position: number): Promise { await this.getParticipantAt(position).click(); + await waitForNoSpinner(this.page); await this._participantPage.assertPageTitle(); return this._participantPage; } diff --git a/playwright-e2e/dsm/component/tables/previous-surveys-table.ts b/playwright-e2e/dsm/component/tables/previous-surveys-table.ts new file mode 100644 index 0000000000..21d2ccce6f --- /dev/null +++ b/playwright-e2e/dsm/component/tables/previous-surveys-table.ts @@ -0,0 +1,8 @@ +import { Page } from '@playwright/test'; +import Table from 'dss/component/table'; + +export default class PreviousSurveysTable extends Table { + constructor(page: Page) { + super(page, { cssClassAttribute: '.table' }); + } +} diff --git a/playwright-e2e/dsm/pages/dsm-page-base.ts b/playwright-e2e/dsm/pages/dsm-page-base.ts new file mode 100644 index 0000000000..d99e75a87d --- /dev/null +++ b/playwright-e2e/dsm/pages/dsm-page-base.ts @@ -0,0 +1,17 @@ +import { Page } from '@playwright/test'; +import { waitForNoSpinner } from 'utils/test-utils'; + +export default abstract class DsmPageBase { + protected readonly page: Page; + protected readonly baseUrl: string | undefined; + + protected constructor(page: Page, baseURL?: string) { + this.page = page; + this.baseUrl = baseURL; + } + + async reload(): Promise { + await this.page.reload(); + await waitForNoSpinner(this.page); + } +} diff --git a/playwright-e2e/dsm/pages/follow-up-survey-page.ts b/playwright-e2e/dsm/pages/follow-up-survey-page.ts new file mode 100644 index 0000000000..990781daf9 --- /dev/null +++ b/playwright-e2e/dsm/pages/follow-up-survey-page.ts @@ -0,0 +1,67 @@ +import { APIRequestContext, expect, Page } from '@playwright/test'; +import { MiscellaneousEnum } from 'dsm/component/navigation/enums/miscellaneousNav-enum'; +import { Navigation } from 'dsm/component/navigation/navigation'; +import PreviousSurveysTable from 'dsm/component/tables/previous-surveys-table'; +import { WelcomePage } from 'dsm/pages/welcome-page'; +import Button from 'dss/component/button'; +import Input from 'dss/component/input'; +import Select from 'dss/component/select'; +import { waitForNoSpinner } from 'utils/test-utils'; + +export default class FollowUpSurveyPage { + private readonly _table: PreviousSurveysTable = new PreviousSurveysTable(this.page); + + static async goto(page: Page, study: string, request: APIRequestContext): Promise { + const welcomePage = new WelcomePage(page); + await welcomePage.selectStudy(study); + + const navigation = new Navigation(page, request); + await navigation.selectMiscellaneous(MiscellaneousEnum.FOLLOW_UP_SURVEY); + const followUpPage = new FollowUpSurveyPage(page); + await followUpPage.waitForReady(); + return followUpPage; + } + + constructor(private readonly page: Page) {} + + public get previousSurveysTable(): PreviousSurveysTable { + return this._table; + } + + public async waitForReady(): Promise { + await expect(this.page.locator('h1')).toHaveText(/^\s*Follow-Up Survey\s*$/); + await expect(this.select().toLocator()).toBeVisible({ timeout: 30000 }); + await waitForNoSpinner(this.page); + } + + public async selectSurvey(survey: string): Promise { + await this.select().selectOption(survey); + await waitForNoSpinner(this.page); + } + + public async participantId(id: string): Promise { + const input = new Input(this.page, { label: 'Participant ID' }); + await input.fill(id); + } + + public async reasonForFollowUpSurvey(reason: string): Promise { + const input = new Input(this.page, { label: 'Reason for Follow-Up Survey' }); + await input.fill(reason); + } + + public async createSurvey(): Promise { + const button = new Button(this.page, { label: 'Create Survey', root: 'app-survey' }); + await button.click(); + await waitForNoSpinner(this.page); + } + + public select(): Select { + return new Select(this.page, { label: 'Survey', root: 'app-survey' }); + } + + public async reloadTable(): Promise { + const button = new Button(this.page, { label: 'Reload', root: 'app-survey' }); + await button.click(); + await waitForNoSpinner(this.page); + } +} diff --git a/playwright-e2e/dsm/pages/mailing-list-page.ts b/playwright-e2e/dsm/pages/mailing-list-page.ts index b181df6158..48de167a70 100644 --- a/playwright-e2e/dsm/pages/mailing-list-page.ts +++ b/playwright-e2e/dsm/pages/mailing-list-page.ts @@ -1,4 +1,5 @@ import { Download, expect, Locator, Page } from '@playwright/test'; +import DsmPageBase from 'dsm/pages/dsm-page-base'; import Table from 'dss/component/table'; export const COLUMN = { @@ -8,11 +9,12 @@ export const COLUMN = { LAST_NAME: 'Last Name' } -export default class MailingListPage { +export default class MailingListPage extends DsmPageBase { private readonly title: string | RegExp; readonly downloadButton: Locator; - constructor(private readonly page: Page, study: string|RegExp) { + constructor(page: Page, study: string|RegExp) { + super(page); this.title = study; this.downloadButton = this.page.getByRole('button', { name: 'Download mailing list' }) } diff --git a/playwright-e2e/dsm/pages/participant-list-page.ts b/playwright-e2e/dsm/pages/participant-list-page.ts index a9b25babc4..9cac0234bb 100644 --- a/playwright-e2e/dsm/pages/participant-list-page.ts +++ b/playwright-e2e/dsm/pages/participant-list-page.ts @@ -21,7 +21,6 @@ export default class ParticipantListPage { const navigation = new Navigation(page, request); const participantListPage = await navigation.selectFromStudy(StudyNavEnum.PARTICIPANT_LIST); await participantListPage.waitForReady(); - await participantListPage.assertPageTitle(); const participantsTable = participantListPage.participantListTable; const rowsTotal = await participantsTable.rowLocator().count(); @@ -32,7 +31,9 @@ export default class ParticipantListPage { constructor(private readonly page: Page) {} public async waitForReady(): Promise { + await this.assertPageTitle(); await waitForNoSpinner(this.page); + await expect(this.page.locator(this.filters.searchPanel.openButtonXPath)).toBeVisible(); } public async selectAll(): Promise { diff --git a/playwright-e2e/dsm/pages/welcome-page.ts b/playwright-e2e/dsm/pages/welcome-page.ts index 502754d8df..46718869f0 100644 --- a/playwright-e2e/dsm/pages/welcome-page.ts +++ b/playwright-e2e/dsm/pages/welcome-page.ts @@ -1,5 +1,6 @@ -import { Page } from '@playwright/test'; +import { expect, Page } from '@playwright/test'; import Select from 'dss/component/select'; +import { waitForNoSpinner } from 'utils/test-utils'; export class WelcomePage { private readonly selectWidget: Select = new Select(this.page, { label: 'Select study' }); @@ -8,5 +9,8 @@ export class WelcomePage { public async selectStudy(studyName: string): Promise { await this.selectWidget.selectOption(studyName); + await waitForNoSpinner(this.page); + await expect(this.page.locator('h1')).toHaveText('Welcome to the DDP Study Management System'); + await expect(this.page.locator('h2')).toHaveText(`You have selected the ${studyName} study.`); } } diff --git a/playwright-e2e/dss/component/select.ts b/playwright-e2e/dss/component/select.ts index 40055a9840..898ee9c272 100644 --- a/playwright-e2e/dss/component/select.ts +++ b/playwright-e2e/dss/component/select.ts @@ -71,4 +71,32 @@ export default class Select extends WidgetBase { break; } } + + /** + * Finds all options in a Select or mat-select + * @returns {Promise} + */ + async getAllOptions(): Promise { + let options; + const tagName = await this.toLocator().evaluate((elem) => elem.tagName); + switch (tagName) { + case 'SELECT': + options = await this.toLocator().locator('option').allInnerTexts(); + break; + default: + // Click first to open mat-select dropdown + await this.toLocator().click(); + const ariaControlsId = await this.toLocator().getAttribute('aria-controls'); + if (!ariaControlsId) { + throw Error('ERROR: Cannot find attribute "aria-controls"'); + } + const dropdown = this.page.locator(`#${ariaControlsId}[role="listbox"]`); + options = await dropdown.locator('mat-option .mat-option-text').allInnerTexts(); + break; + } + if (!options) { + throw new Error(`Failed to find all options in Select or mat-select`); + } + return options!; + } } diff --git a/playwright-e2e/dss/component/table.ts b/playwright-e2e/dss/component/table.ts index 7fd77542a0..affb99d5b5 100644 --- a/playwright-e2e/dss/component/table.ts +++ b/playwright-e2e/dss/component/table.ts @@ -13,6 +13,7 @@ export default class Table { private readonly rowCss: string; private readonly cellCss: string; private readonly footerCss: string; + private readonly headerRowCss: string; private readonly nth: number; constructor(protected readonly page: Page, opts: { cssClassAttribute?: string; ddpTestID?: string; nth?: number } = {}) { @@ -23,14 +24,20 @@ export default class Table { : cssClassAttribute ? `table${cssClassAttribute}, mat-table${cssClassAttribute}` : 'table, mat-table, [role="table"]'; - this.headerCss = 'th, [role="columnheader"]'; + this.headerCss = 'thead th, [role="columnheader"]'; + this.headerRowCss = 'thead tr'; this.rowCss = '[role="row"]:not([mat-header-row]):not(mat-header-row), tbody tr'; this.cellCss = '[role="cell"], td'; this.footerCss = 'tfoot tr'; } - async waitForReady() { - await expect(this.tableLocator().locator(this.headerCss).first()).toBeVisible(); + async exists(): Promise { + return await this.tableLocator().count() === 1; + } + + async waitForReady(timeout?: number) { + await this.tableLocator().waitFor({ state: 'attached' }); + await expect(this.tableLocator().locator(this.headerCss).first()).toBeVisible({ timeout }); expect(await this.rowLocator().count()).toBeGreaterThanOrEqual(1); } @@ -76,7 +83,6 @@ export default class Table { const { exactMatch = true } = opts; // Find the search column header index - const columnNames = await this.getHeaderNames(); const columnIndex = await this.getHeaderIndex(searchHeader, { exactMatch }); if (columnIndex === -1) { return null; // Not found @@ -126,6 +132,12 @@ export default class Table { return rowText; } + /** + * Finds text in row underneath specified column name + * @param {number} row It's zero based, nth(0) selects the first row. + * @param {string} column + * @returns {Promise} + */ async getRowText(row: number, column: string): Promise { // Find column index const columns = await this.getHeaderNames(); @@ -187,14 +199,15 @@ export default class Table { async sort(column: string, order: SortOrder): Promise { const header = this.getHeaderByName(RegExp(column)); + await expect(header).toBeVisible(); const headerLink = header.locator('a'); - if (await headerLink.count() > 0) { await headerLink.click(); } else { await header.click(); } - + await waitForNoSpinner(this.page); + await expect(header).toBeVisible({ timeout: 30000 }); let ariaLabel = await header.locator('span').last().getAttribute('aria-label'); if (ariaLabel) { if (ariaLabel !== order) { @@ -249,6 +262,18 @@ export default class Table { } } + async searchByColumn(column1Name: string, value1: string, opts?: { column2Name: string, value2: string }): Promise { + const column1Index = await this.getHeaderIndex(column1Name, { exactMatch: false }); + const input1 = this.page.locator(this.headerRowCss).nth(1).locator('th').nth(column1Index).locator('input.form-control'); + await input1.fill(value1); + if (opts) { + const column2Index = await this.getHeaderIndex(opts.column2Name, { exactMatch: false }); + const input2 = this.page.locator(this.headerRowCss).nth(1).locator('th').nth(column2Index).locator('input.form-control'); + await input2.fill(opts.value2); + } + await waitForNoSpinner(this.page); + } + private parseForNumber(text: string): number | null { const numericalStr = text.match(/(\d|\.)+/g); if (numericalStr) { diff --git a/playwright-e2e/dss/pages/atcp/atcp-dashboard-page.ts b/playwright-e2e/dss/pages/atcp/atcp-dashboard-page.ts index 682ea60b2e..c1558677b5 100644 --- a/playwright-e2e/dss/pages/atcp/atcp-dashboard-page.ts +++ b/playwright-e2e/dss/pages/atcp/atcp-dashboard-page.ts @@ -1,6 +1,7 @@ import { expect, Page } from '@playwright/test'; import Table from 'dss/component/table'; import { AtcpPageBase } from 'dss/pages/atcp/atcp-page-base'; +import { waitForNoSpinner } from 'utils/test-utils'; export default class AtcpDashboardPage extends AtcpPageBase { constructor(page: Page) { @@ -9,8 +10,12 @@ export default class AtcpDashboardPage extends AtcpPageBase { async waitForReady(): Promise { await super.waitForReady(); - await expect(this.page).toHaveURL(/\/dashboard/); - await expect(this.page.locator('h1.title')).toHaveText('Thank you for joining the Global A-T Family Data Platform!'); + await Promise.all([ + expect(this.page).toHaveURL(/\/dashboard/), + expect(this.page.locator('h1.title')).toHaveText('Thank you for joining the Global A-T Family Data Platform!'), + ]) + await waitForNoSpinner(this.page); + await this.getTable().waitForReady(); } getTable(): Table { diff --git a/playwright-e2e/dss/pages/lms/lms-home-page.ts b/playwright-e2e/dss/pages/lms/lms-home-page.ts index b0f6957ca8..ff9e5b10da 100644 --- a/playwright-e2e/dss/pages/lms/lms-home-page.ts +++ b/playwright-e2e/dss/pages/lms/lms-home-page.ts @@ -14,11 +14,13 @@ export default class LmsHomePage extends LmsPageBase { } async waitForReady(): Promise { - await expect(this.page).toHaveTitle('Leiomyosarcoma Project'); - await expect(this.countMeInButton.first()).toBeVisible(); - await expect(this.learnMoreButton).toBeVisible(); - await expect(this.getLogInButton()).toBeVisible(); await waitForNoSpinner(this.page); + await Promise.all([ + expect(this.page).toHaveTitle('Leiomyosarcoma Project'), + expect(this.countMeInButton.first()).toBeVisible(), + expect(this.learnMoreButton).toBeVisible(), + expect(this.getLogInButton()).toBeVisible() + ]) } async countMeIn(): Promise { diff --git a/playwright-e2e/dss/pages/page-base.ts b/playwright-e2e/dss/pages/page-base.ts index 95d1febccc..fe5cb4cf75 100644 --- a/playwright-e2e/dss/pages/page-base.ts +++ b/playwright-e2e/dss/pages/page-base.ts @@ -45,8 +45,8 @@ export default abstract class PageBase implements PageInterface { async waitForReady(): Promise { await this.page.waitForLoadState('networkidle'); - await waitForNoSpinner(this.page); await expect(this.page).toHaveTitle(/\D+/); + await waitForNoSpinner(this.page); } /** diff --git a/playwright-e2e/tests/atcp/atcp-adult-self-consent-enrollment.spec.ts b/playwright-e2e/tests/atcp/atcp-adult-self-consent-enrollment.spec.ts index da90e28144..90b8623377 100644 --- a/playwright-e2e/tests/atcp/atcp-adult-self-consent-enrollment.spec.ts +++ b/playwright-e2e/tests/atcp/atcp-adult-self-consent-enrollment.spec.ts @@ -274,7 +274,6 @@ test.describe('ATCP adult self-consent enrollment', () => { await dashboardPage.waitForReady(); await expect(page.locator('h2.subtitle')).toHaveScreenshot('atcp-dashboard-thank-you-message.png'); - await expect(page.locator('.registration-status')).toHaveScreenshot('atcp-dashboard-registration-status.png'); const expectedHeaders = ['Form', 'Summary', 'Created', 'Status', 'Actions']; const table = dashboardPage.getTable(); diff --git a/playwright-e2e/tests/dsm/atcp/atcp-receive-kit.spec.ts b/playwright-e2e/tests/dsm/atcp/atcp-receive-kit.spec.ts index e5ccaa5f07..75731be456 100644 --- a/playwright-e2e/tests/dsm/atcp/atcp-receive-kit.spec.ts +++ b/playwright-e2e/tests/dsm/atcp/atcp-receive-kit.spec.ts @@ -11,7 +11,6 @@ import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import AtcpSearchPage, { SearchByField } from 'dsm/pages/samples/search-page'; import { WelcomePage } from 'dsm/pages/welcome-page'; import Radiobutton from 'dss/component/radiobutton'; -import { SortOrder } from 'dss/component/table'; import { test } from 'fixtures/dsm-fixture'; import { getUtcDate } from 'utils/date-utils'; import { generateAlphaNumeric } from 'utils/faker-utils'; @@ -36,35 +35,35 @@ test.describe('Receive Kit', () => { await welcomePage.selectStudy(study); }); - test(`Receive genome sample kit for ${study} @dsm @${study} @functional`, async ({page, request}) => { + test(`Receive genome sample kit for ${study} @dsm @${study} @functional`, async ({page}) => { // Instead using UI table filter and search, it is much quicker and more accurate to intercept DSM API request to find the right participant to use. // Find a Playwright test user that does not have GENOME_STUDY_SPIT_KIT_BARCODE. await page.route('**/*', async (route, request): Promise => { - if (!shortId) { - // only search for shortId one time to avoid duplicated searching - let participantShortId; - const regex = new RegExp(/realm=.*participantList/i); - if (request.url().match(regex)) { - const response = await route.fetch(); - const json = JSON.parse(await response.text()); - for (const i in json.participants) { - const profile = json.participants[i].esData.profile; - if (!profile.firstName.includes('E2E')) { - continue; - } - participantShortId = profile.hruid; - const participantData = json.participants[i].esData.dsm.participantData; - for (const dataId in participantData) { - const data = participantData[dataId].data as string; - if (data.includes('GENOME_STUDY_SPIT_KIT_BARCODE')) { + const regex = new RegExp(/applyFilter\?realm=.*&parent=participantList/i); + if (!shortId && request.url().match(regex)) { + console.log(`Intercepting API request ${request.url()} for a E2E participant`); + const response = await route.fetch(); + const json = JSON.parse(await response.text()); + for (const i in json.participants) { + let participantShortId; + const profile = json.participants[i].esData.profile; + const participantData = json.participants[i].esData.dsm.participantData; + if (!profile.firstName.includes('E2E')) { + continue; + } + participantShortId = profile.hruid; + for (const dataId in participantData) { + if ((participantData[dataId].fieldTypeId as string) === 'AT_GROUP_GENOME_STUDY') { + if ((participantData[dataId].data as string).indexOf('GENOME_STUDY_SPIT_KIT_BARCODE') !== -1) { participantShortId = null; + break; } } - if (participantShortId) { - shortId = participantShortId; - console.log('short id: ', shortId); - break; // finished searching for a participant who is Playwright automation test created and does not have genome study kit barcode - } + } + if (participantShortId) { + shortId = participantShortId; + console.log('short id: ', shortId); + break; // finished searching for a participant who is Playwright automation test created and does not have genome study kit barcode } } } @@ -75,8 +74,6 @@ test.describe('Receive Kit', () => { participantListPage = await navigation.selectFromStudy(StudyNavEnum.PARTICIPANT_LIST); await participantListPage.waitForReady(); - const participantsTable = participantListPage.participantListTable; - // Search for a participant that meets the search criteria const customizeViewPanel = participantListPage.filters.customizeViewPanel; await customizeViewPanel.open(); @@ -89,25 +86,23 @@ test.describe('Receive Kit', () => { await searchPanel.checkboxes('Status', { checkboxValues: ['Registered', 'Enrolled'] }); await searchPanel.text('Sample kit barcode for genome study', { additionalFilters: [AdditionalFilter.EMPTY] }); await searchPanel.search(); - - // Sort Registration Date from latest to oldest - await participantsTable.sort('Registration Date', SortOrder.ASC); }); await test.step('Verify participant detail on Participant page', async () => { - // Open the Participant page - const participantsTable = participantListPage.participantListTable; + const searchPanel = participantListPage.filters.searchPanel; + await searchPanel.clear(); await participantListPage.filterListByShortId(shortId); - logGenomeStudySampleKitReceived(shortId) const row = 0; + const participantsTable = participantListPage.participantListTable; const status = await participantsTable.getParticipantDataAt(row, 'Status'); expect(status).toMatch(/Enrolled|Registered/); const rowShortId = await participantsTable.getParticipantDataAt(row, 'Short ID'); expect(rowShortId).toBe(shortId); const registrationDate = await participantsTable.getParticipantDataAt(row, 'Registration Date', { exactMatch: false }); + // Open the Participant page participantPage = await participantsTable.openParticipantPageAt(row); expect(await participantPage.getStatus()).toBe(status); @@ -131,7 +126,6 @@ test.describe('Receive Kit', () => { const row = 0; expect(await table.getRowText(row, 'DDP-Realm')).toBe(studyShortName(study).shortName); expect(await table.getRowText(row, 'Short ID')).toBe(shortId); - expect(await table.getRowText(row, 'Collaborator Participant ID')).toBeTruthy(); expect(await table.getRowText(row, 'MF barcode')).toBe(newBarcode); expect(await table.getRowText(row, 'Short ID')).toBe(shortId); @@ -145,6 +139,8 @@ test.describe('Receive Kit', () => { await test.step('Verify participant detail has updated on Participant page', async () => { participantListPage = await navigation.selectFromStudy(StudyNavEnum.PARTICIPANT_LIST); + await participantListPage.waitForReady(); + await participantListPage.filterListByShortId(shortId); await participantListPage.participantListTable.openParticipantPageAt(0); diff --git a/playwright-e2e/tests/dsm/cohort-tag.spec.ts b/playwright-e2e/tests/dsm/cohort-tag.spec.ts index 2e0fb5944c..460035c2c1 100644 --- a/playwright-e2e/tests/dsm/cohort-tag.spec.ts +++ b/playwright-e2e/tests/dsm/cohort-tag.spec.ts @@ -26,8 +26,9 @@ test.describe('Cohort tags', () => { if (!shortId) { // only search for shortId one time to avoid duplicated searching let participantShortId; - const regex = new RegExp(/(applyFilter|filterList)\?realm=.*&parent=participantList/i); + const regex = new RegExp(/applyFilter\?realm=.*&parent=participantList/i); if (request.url().match(regex)) { + console.log(`Intercepting API request ${request.url()} for a E2E participant`); const response = await route.fetch(); const json = JSON.parse(await response.text()); for (const i in json.participants) { @@ -70,7 +71,7 @@ test.describe('Cohort tags', () => { await customizeViewPanel.selectColumns('Cohort Tags Columns', ['Cohort Tag Name']); // Search participant by Short ID - console.log(`Participant Short ID: ${shortId}`); + // console.log(`Participant Short ID: ${shortId}`); await participantListPage.filterListByShortId(shortId); const participantListTable = participantListPage.participantListTable; diff --git a/playwright-e2e/tests/dsm/mailing-list/pancan-mail-list-download.spec.ts b/playwright-e2e/tests/dsm/mailing-list/pancan-mail-list-download.spec.ts index c759b4caaf..2528e2bd02 100644 --- a/playwright-e2e/tests/dsm/mailing-list/pancan-mail-list-download.spec.ts +++ b/playwright-e2e/tests/dsm/mailing-list/pancan-mail-list-download.spec.ts @@ -71,9 +71,7 @@ test.describe.serial('Join Pancan Mailing List', () => { const dateInJson = getDate(new Date(parseInt(item.dateCreated) * 1000)); // Transform to dd/mm/yyyy const emailInJson = item.email; const finding = lodash.filter(rows, row => row.email === emailInJson && row.dateCreated === dateInJson); - expect(finding.length, - `Matching record for email: "${emailInJson}" and dateCreated: "${dateInJson}" in downloaded csv file.`) - .toEqual(1); + expect(finding.length).toEqual(1); }); // Verify Mailing List table @@ -87,9 +85,10 @@ test.describe.serial('Join Pancan Mailing List', () => { // Verify new Pancan participant email is found inside table. // To handle any delay, retry blocks of code until email is found successfully. await expect(async () => { + await mailingListPage.reload(); // page reload to trigger new request await table.sort(COLUMN.DATE, SortOrder.DESC); // Sorting to get newest record to display first const cell = await table.findCell(COLUMN.EMAIL, newEmail, COLUMN.EMAIL); - await expect(cell, `Matching email ${newEmail} in Mailing List table`).toBeTruthy(); + await expect(cell).toBeTruthy(); }).toPass(); // Verify date signed up is found inside table diff --git a/playwright-e2e/tests/dsm/mailing-list/rgp-mail-list-download.spec.ts b/playwright-e2e/tests/dsm/mailing-list/rgp-mail-list-download.spec.ts index a803a116fc..237bdb8cf5 100644 --- a/playwright-e2e/tests/dsm/mailing-list/rgp-mail-list-download.spec.ts +++ b/playwright-e2e/tests/dsm/mailing-list/rgp-mail-list-download.spec.ts @@ -89,7 +89,6 @@ test.describe.serial('When an interested participant does NOT meet participation const emailInJson = item.email; const finding = lodash.filter(rows, row => { if (row.email === emailInJson) { - console.log(row.email, row.dateCreated); return row.dateCreated === dateInJson } return false; diff --git a/playwright-e2e/tests/dsm/miscellaneous/create-follow-up-survey.spec.ts b/playwright-e2e/tests/dsm/miscellaneous/create-follow-up-survey.spec.ts new file mode 100644 index 0000000000..b94b71ebca --- /dev/null +++ b/playwright-e2e/tests/dsm/miscellaneous/create-follow-up-survey.spec.ts @@ -0,0 +1,95 @@ +import { expect } from '@playwright/test'; +import Modal from 'dsm/component/modal'; +import { test } from 'fixtures/dsm-fixture'; +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; +import FollowUpSurveyPage from 'dsm/pages/follow-up-survey-page'; +import { getDate } from 'utils/date-utils'; +import { generateAlphaNumeric, generateRandomNum } from 'utils/faker-utils'; + + +test.describe('Create Follow-Up Survey', () => { + const studies = [StudyEnum.PANCAN, StudyEnum.PROSTATE, StudyEnum.ESC]; + let followupSurveyPage: FollowUpSurveyPage; + + test(`FAMILY_HISTORY (NONREPEATING) in @pancan @dsm @functional`, async ({ page, request }) => { + followupSurveyPage = await FollowUpSurveyPage.goto(page, StudyEnum.PANCAN, request); + await followupSurveyPage.waitForReady(); + + await followupSurveyPage.selectSurvey('FAMILY_HISTORY (NONREPEATING)'); + const previousSurveysTable = followupSurveyPage.previousSurveysTable; + await previousSurveysTable.waitForReady(60 * 1000); + + const participantId = await previousSurveysTable.getRowText(0, 'Participant ID'); + expect(participantId).not.toBeNull(); + + // Fill out participant ID and reason + await followupSurveyPage.participantId(participantId!); + await followupSurveyPage.reasonForFollowUpSurvey(`playwright testing ${getDate()}`); + await followupSurveyPage.createSurvey(); + + // Modal window informs user survey won't be triggered again + const modal = new Modal(page); + const description = await modal.getHeader(); + expect(description).toBe( + "Survey was already triggered for following participants.\nSelected survey was of type 'NONREPEATING' therefore DSM won't trigger again."); + const body = await modal.getBodyText(); + expect(body).toBe(participantId); + await modal.close(); + + await previousSurveysTable.searchByColumn('Participant ID', participantId!); + await expect(previousSurveysTable.rowLocator()).toHaveCount(1); + }); + + for (const study of studies) { + const survey = surveysForStudy(study); + test(`${survey} in @${study} @dsm @functional`, async ({ page, request }) => { + followupSurveyPage = await FollowUpSurveyPage.goto(page, study, request); + await followupSurveyPage.waitForReady(); + + await followupSurveyPage.selectSurvey(survey); + const previousSurveysTable = followupSurveyPage.previousSurveysTable; + const rowsCount = await previousSurveysTable.rowLocator().count(); + expect(rowsCount).toBeGreaterThanOrEqual(1); + + // Find any participant ID to create new survey (repeating) + const randRowIndex = generateRandomNum(0, rowsCount); + const participantId = await previousSurveysTable.getRowText(randRowIndex, 'Participant ID'); + expect(participantId).not.toBeNull(); + + // Create new survey by fill out participant ID and reason + const reason = `playwright testing ${generateAlphaNumeric()}`; + await followupSurveyPage.participantId(participantId!); + await followupSurveyPage.reasonForFollowUpSurvey(reason); + await followupSurveyPage.createSurvey(); + + // Verify new survey created + const responsePromise = page.waitForResponse(resp => resp.url().includes('surveyName=')); + await followupSurveyPage.reloadTable(); + const response = await responsePromise; + + const json = JSON.parse(await response.text()); + const filterResult = json.filter((item: { surveyInfo: { participantId: string | null; }; reason: string; }) => { + return item.surveyInfo.participantId === participantId && item.reason === reason + }) + await expect(filterResult.length).toEqual(1); + }); + } + + function surveysForStudy(study: string): string { + let survey: string; + switch (study) { + case StudyEnum.PANCAN: + survey = 'BLOOD_CONSENT (REPEATING)'; + break; + case StudyEnum.PROSTATE: + survey = 'FOLLOWUP (REPEATING)'; + break; + case StudyEnum.ESC: + survey = 'FOLLOWUPCONSENT (REPEATING)'; + break; + default: + throw new Error(`Survey study "${study}" is undefined`); + } + return survey; + } +}) diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts new file mode 100644 index 0000000000..a658d8d8ad --- /dev/null +++ b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts @@ -0,0 +1,118 @@ +import { expect } from '@playwright/test'; +import { test } from 'fixtures/dsm-fixture'; +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; +import FollowUpSurveyPage from 'dsm/pages/follow-up-survey-page'; + + +test.describe('Follow-Up Surveys', () => { + let followupSurveyPage: FollowUpSurveyPage; + + const studies = [StudyEnum.PANCAN, StudyEnum.ANGIO, StudyEnum.OSTEO, StudyEnum.LMS, StudyEnum.OSTEO2, StudyEnum.PROSTATE, StudyEnum.ESC]; + + for (const study of studies) { + test(`Shows list of follow-up surveys configured in @${study} @dsm @functional`, async ({ page, request }) => { + followupSurveyPage = await FollowUpSurveyPage.goto(page, study, request); + await followupSurveyPage.waitForReady(); + + const configuredSurveys = surveysForStudy(followupSurveyPage, study); + const actualSurveyOptions = await followupSurveyPage.select().getAllOptions(); + expect(actualSurveyOptions).toEqual(configuredSurveys); + }); + + test(`Shows previous triggered surveys in @${study} @dsm @functional`, async ({ page, request }) => { + followupSurveyPage = await FollowUpSurveyPage.goto(page, study, request); + await followupSurveyPage.waitForReady(); + await expect(page.locator('app-survey ul')).toHaveScreenshot(`${study}-instructions.png`); + + // Shows list of previous triggered surveys + await selectSurveyForStudy(followupSurveyPage, study); + const previousSurveysTable = followupSurveyPage.previousSurveysTable; + await previousSurveysTable.waitForReady(60 * 1000); + + // Save rows count + const rowsCount = await previousSurveysTable.rowLocator().count(); + + // Check search by column should be working + const row1ParticipantId = await previousSurveysTable.getRowText(0, 'Participant ID'); + const row1ShortId = await previousSurveysTable.getRowText(0, 'Short ID'); + + expect(row1ParticipantId).not.toBeNull(); + expect(row1ShortId).not.toBeNull(); + + // Depending on REPEATING OR NONREPEATING surveys, rows count from search could be 1 or greater. + // Search Participant ID found in first row + await previousSurveysTable.searchByColumn('Participant ID', row1ParticipantId!); + expect(await previousSurveysTable.getRowText(0, 'Short ID')).toBe(row1ShortId); + + // Clear search + await previousSurveysTable.searchByColumn('Participant ID', ''); + await expect(previousSurveysTable.rowLocator()).toHaveCount(rowsCount); + + // If there are more than 1 rows, fetch values in second row + if (rowsCount > 1) { + const row2ParticipantId = await previousSurveysTable.getRowText(1, 'Participant ID'); + const row2ShortId = await previousSurveysTable.getRowText(1, 'Short ID'); + + expect(row2ParticipantId).not.toBeNull(); + expect(row2ShortId).not.toBeNull(); + + // Search Participant ID found in second row + await previousSurveysTable.searchByColumn('Participant ID', row2ParticipantId!); + expect(await previousSurveysTable.getRowText(0, 'Short ID')).toBe(row2ShortId); + } + }); + } + + // Different surveys for different studies + async function selectSurveyForStudy(followupSurveyPage: FollowUpSurveyPage, study: string): Promise { + let survey: string; + switch (study) { + case StudyEnum.PANCAN: + survey = 'FAMILY_HISTORY (NONREPEATING)'; + break; + case StudyEnum.ANGIO: + survey = 'followupconsent (REPEATING)'; + break; + case StudyEnum.OSTEO: + case StudyEnum.LMS: + case StudyEnum.OSTEO2: + survey = 'SOMATIC_RESULTS (REPEATING)'; + break; + case StudyEnum.PROSTATE: + survey = 'FOLLOWUP (REPEATING)'; + break; + case StudyEnum.ESC: + survey = 'FOLLOWUPCONSENT (REPEATING)'; + break; + default: + throw new Error(`Study ${study} is undefined`); + } + await followupSurveyPage.selectSurvey(survey); + } + + function surveysForStudy(followupSurveyPage: FollowUpSurveyPage, study: string): string[] { + let configuredSurveys: string[]; + switch (study) { + case StudyEnum.PANCAN: + configuredSurveys = ['BLOOD_CONSENT (REPEATING)', 'FAMILY_HISTORY (NONREPEATING)', 'DIET_LIFESTYLE (NONREPEATING)']; + break; + case StudyEnum.ANGIO: + configuredSurveys = ['followupconsent (REPEATING)']; + break; + case StudyEnum.OSTEO: + case StudyEnum.LMS: + case StudyEnum.OSTEO2: + configuredSurveys = ['SOMATIC_RESULTS (REPEATING)']; + break; + case StudyEnum.PROSTATE: + configuredSurveys = ['FOLLOWUP (REPEATING)']; + break; + case StudyEnum.ESC: + configuredSurveys = ['FOLLOWUPCONSENT (REPEATING)']; + break; + default: + throw new Error(`Survey study "${study}" is undefined`); + } + return configuredSurveys; + } +}) diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-darwin.png new file mode 100644 index 0000000000..97c4bb6462 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-linux.png new file mode 100644 index 0000000000..28371ac1d5 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Angio-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-darwin.png new file mode 100644 index 0000000000..a7e8706ae4 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-linux.png new file mode 100644 index 0000000000..28371ac1d5 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/ESC-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-darwin.png new file mode 100644 index 0000000000..5150e9c70b Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-linux.png new file mode 100644 index 0000000000..28371ac1d5 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Leiomyosarcoma-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-darwin.png new file mode 100644 index 0000000000..5150e9c70b Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-linux.png new file mode 100644 index 0000000000..28371ac1d5 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/OS-PE-CGS-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-darwin.png new file mode 100644 index 0000000000..a7e8706ae4 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-linux.png new file mode 100644 index 0000000000..28371ac1d5 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Osteo-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-darwin.png new file mode 100644 index 0000000000..97c4bb6462 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-linux.png new file mode 100644 index 0000000000..8259a0aaac Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/PanCan-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-darwin.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-darwin.png new file mode 100644 index 0000000000..a7e8706ae4 Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-darwin.png differ diff --git a/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-linux.png b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-linux.png new file mode 100644 index 0000000000..8259a0aaac Binary files /dev/null and b/playwright-e2e/tests/dsm/miscellaneous/show-follow-up-survey.spec.ts-snapshots/Prostate-instructions-chromium-linux.png differ diff --git a/playwright-e2e/tests/dsm/participant-list/filter-custom-view.spec.ts b/playwright-e2e/tests/dsm/participant-list/filter-custom-view.spec.ts index a0b31eb539..6fa188fec8 100644 --- a/playwright-e2e/tests/dsm/participant-list/filter-custom-view.spec.ts +++ b/playwright-e2e/tests/dsm/participant-list/filter-custom-view.spec.ts @@ -5,7 +5,8 @@ import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; import ParticipantListPage from 'dsm/pages/participant-list-page'; import { offsetDaysFromToday } from 'utils/date-utils'; -test.describe('Participants list search and filter', () => { +// Test is broken until bug is fixed. https://broadworkbench.atlassian.net/browse/PEPPER-1031 +test.describe.fixme('Participants list search and filter', () => { const studies = [StudyEnum.LMS, StudyEnum.OSTEO2]; for (const study of studies) { diff --git a/playwright-e2e/tests/dsm/participant-list/filter-registration-date.spec.ts b/playwright-e2e/tests/dsm/participant-list/filter-registration-date.spec.ts index d8c67e9545..33e9e06c98 100644 --- a/playwright-e2e/tests/dsm/participant-list/filter-registration-date.spec.ts +++ b/playwright-e2e/tests/dsm/participant-list/filter-registration-date.spec.ts @@ -25,7 +25,7 @@ test.describe('Participants Search', () => { // Save Registration Date found on first row for use in search const registrationDate = await participantsTable.getParticipantDataAt(0, MainInfoEnum.REGISTRATION_DATE); const randomDate = getDate(new Date(registrationDate)); // Returns a formatted date mm/dd/yyyy - console.log('Search by registration date: ', randomDate); + // console.log('Search by registration date: ', randomDate); // Search by random date const searchPanel = participantListPage.filters.searchPanel; @@ -34,7 +34,7 @@ test.describe('Participants Search', () => { await searchPanel.search(); const numParticipants1 = await participantsTable.numOfParticipants(); - console.log(`Search by Registration Date ${randomDate} returns ${numParticipants1} participants`); + // console.log(`Search by Registration Date ${randomDate} returns ${numParticipants1} participants`); expect(numParticipants1).toBeGreaterThanOrEqual(1); diff --git a/playwright-e2e/tests/lms/lms-parental-consent-enrollment.spec.ts b/playwright-e2e/tests/lms/lms-parental-consent-enrollment.spec.ts index f2f0cf7006..2ab7bb013d 100644 --- a/playwright-e2e/tests/lms/lms-parental-consent-enrollment.spec.ts +++ b/playwright-e2e/tests/lms/lms-parental-consent-enrollment.spec.ts @@ -319,11 +319,11 @@ test.describe.serial('LMS Child Enrollment', () => { await expect(page.locator('.infobox_dashboard')).toHaveScreenshot('lms-dashboard-message.png'); await expect(dashboardPage.getTable().tableLocator()).toHaveScreenshot('lms-dashboard-table-1.png'); - - // Log out - await dashboardPage.getLogOutButton().click(); - await homePage.waitForReady(); }) + + // Log out + await dashboardPage.getLogOutButton().click(); + await homePage.waitForReady(); }); test('New participant sign in @visual @enrollment @lms', async ({ page }) => { diff --git a/playwright-e2e/tests/rgp/child-enrollment.spec.ts b/playwright-e2e/tests/rgp/child-enrollment.spec.ts index 50672d77b4..5ff2987b84 100644 --- a/playwright-e2e/tests/rgp/child-enrollment.spec.ts +++ b/playwright-e2e/tests/rgp/child-enrollment.spec.ts @@ -61,7 +61,6 @@ test.describe('Child Enrollment', () => { await assertProgressActiveItem(page, '1'); const content = page.locator('.ddp-content'); await expect(content).toHaveCount(3); - expect(await content.first().screenshot()).toMatchSnapshot('content-1.png'); expect(await content.nth(1).screenshot()).toMatchSnapshot('content-2.png'); expect(await content.nth(2).screenshot()).toMatchSnapshot('content-3.png'); diff --git a/playwright-e2e/utils/test-utils.ts b/playwright-e2e/utils/test-utils.ts index 70841a334d..0aadf6d024 100644 --- a/playwright-e2e/utils/test-utils.ts +++ b/playwright-e2e/utils/test-utils.ts @@ -15,7 +15,8 @@ const { SITE_PASSWORD } = process.env; export async function waitForNoSpinner(page: Page, opts: { timeout?: number } = {}): Promise { const { timeout = 60 * 1000 } = opts; - await page.locator('[data-icon="spinner"].fa-spin, mat-spinner[role="progressbar"]').waitFor({ state: 'hidden', timeout }); + const locator = page.locator('[data-icon="spinner"].fa-spin, mat-spinner[role="progressbar"]'); + await locator.first().waitFor({ state: 'hidden', timeout }); // if more than one spinners are found, only wait for first one to disappear. } export async function waitForResponse(page: Page, {uri, status = 200, timeout = 30000}: WaitForResponse): Promise {