From df364baafd5edce830882b4cad110351e2c1d207 Mon Sep 17 00:00:00 2001 From: aweng98 Date: Fri, 22 Sep 2023 12:56:53 -0400 Subject: [PATCH 1/9] playwright tissue test --- playwright-e2e/dsm/component/date-picker.ts | 2 +- .../filters/sections/search/search.ts | 4 + playwright-e2e/dsm/component/modal.ts | 2 +- playwright-e2e/dsm/component/smid.ts | 105 ++++++ .../dsm/component/tables/onc-history-table.ts | 296 ++++++++++++++++ ...ationTab.ts => contact-information-tab.ts} | 0 .../enums/onc-history-input-columns-enum.ts | 41 +++ .../dsm/component/tabs/enums/tab-enum.ts | 1 + .../interfaces/onc-history-inputs-types.ts | 20 ++ .../tabs/model/onc-history-inputs.ts | 24 ++ ...onModel.ts => sample-information-model.ts} | 0 .../dsm/component/tabs/onc-history-tab.ts | 114 ++++++ ...mationTab.ts => sample-information-tab.ts} | 4 +- playwright-e2e/dsm/component/tabs/tabs.ts | 6 +- playwright-e2e/dsm/component/tissue.ts | 235 ++++++++++++ .../participant-page/participant-page.ts | 15 + .../tissue/enums/tissue-information-enum.ts | 103 ++++++ .../interfaces/fill-tissue-interface.ts | 8 + .../tissue-information-interfaces.ts | 13 + .../dsm/pages/tissue/models/tissue-inputs.ts | 28 ++ .../pages/tissue/tissue-information-page.ts | 333 ++++++++++++++++++ playwright-e2e/dss/component/input.ts | 21 ++ playwright-e2e/dss/component/select.ts | 8 + playwright-e2e/dss/component/textarea.ts | 14 +- .../blood-kit-upload-flow.spec.ts | 4 +- .../saliva-kit-upload-flow.spec.ts | 4 +- .../tissue-request/cmi-tissue-request.spec.ts | 134 +++++++ 27 files changed, 1526 insertions(+), 13 deletions(-) create mode 100644 playwright-e2e/dsm/component/smid.ts create mode 100644 playwright-e2e/dsm/component/tables/onc-history-table.ts rename playwright-e2e/dsm/component/tabs/{contactInformationTab.ts => contact-information-tab.ts} (100%) create mode 100644 playwright-e2e/dsm/component/tabs/enums/onc-history-input-columns-enum.ts create mode 100644 playwright-e2e/dsm/component/tabs/interfaces/onc-history-inputs-types.ts create mode 100644 playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts rename playwright-e2e/dsm/component/tabs/model/{sampleInformationModel.ts => sample-information-model.ts} (100%) create mode 100644 playwright-e2e/dsm/component/tabs/onc-history-tab.ts rename playwright-e2e/dsm/component/tabs/{sampleInformationTab.ts => sample-information-tab.ts} (96%) create mode 100644 playwright-e2e/dsm/component/tissue.ts create mode 100644 playwright-e2e/dsm/pages/tissue/enums/tissue-information-enum.ts create mode 100644 playwright-e2e/dsm/pages/tissue/interfaces/fill-tissue-interface.ts create mode 100644 playwright-e2e/dsm/pages/tissue/interfaces/tissue-information-interfaces.ts create mode 100644 playwright-e2e/dsm/pages/tissue/models/tissue-inputs.ts create mode 100644 playwright-e2e/dsm/pages/tissue/tissue-information-page.ts create mode 100644 playwright-e2e/tests/dsm/tissue-request/cmi-tissue-request.spec.ts diff --git a/playwright-e2e/dsm/component/date-picker.ts b/playwright-e2e/dsm/component/date-picker.ts index 9fae7a0d16..aad888bd29 100644 --- a/playwright-e2e/dsm/component/date-picker.ts +++ b/playwright-e2e/dsm/component/date-picker.ts @@ -76,7 +76,7 @@ export default class DatePicker { await this.monthPicker().locator(this.clickableCell(), { hasText: monthName }).click(); // pick day of month - await this.dayPicker().locator(this.clickableCell(), { hasText: date }).click(); + await this.dayPicker().locator(this.clickableCell(), { hasText: date }).first().click(); // calendar close automatically return getDate(new Date(yyyy, month, parseInt(date))); diff --git a/playwright-e2e/dsm/component/filters/sections/search/search.ts b/playwright-e2e/dsm/component/filters/sections/search/search.ts index 5b0b4a5c4a..06a24461d4 100644 --- a/playwright-e2e/dsm/component/filters/sections/search/search.ts +++ b/playwright-e2e/dsm/component/filters/sections/search/search.ts @@ -33,6 +33,10 @@ export class Search { public async dates(columnName: string, { from: fromValue, to: toValue, additionalFilters }: Partial): Promise { await this.setAdditionalFilters(columnName, additionalFilters); + if (!fromValue && !toValue) { + return; + } + let fromDate!: string; let toDate!: string; diff --git a/playwright-e2e/dsm/component/modal.ts b/playwright-e2e/dsm/component/modal.ts index 158d45972d..f0eab19d5d 100644 --- a/playwright-e2e/dsm/component/modal.ts +++ b/playwright-e2e/dsm/component/modal.ts @@ -8,7 +8,7 @@ export default class Modal { private readonly rootSelector: Locator; constructor(private readonly page: Page) { - this.rootSelector = this.page.locator('.modal-dialog'); + this.rootSelector = this.page.locator('app-modal, .modal-dialog'); } public toLocator(): Locator { diff --git a/playwright-e2e/dsm/component/smid.ts b/playwright-e2e/dsm/component/smid.ts new file mode 100644 index 0000000000..0ecb741415 --- /dev/null +++ b/playwright-e2e/dsm/component/smid.ts @@ -0,0 +1,105 @@ +import { expect, Locator, Page } from '@playwright/test'; +import Input from 'dss/component/input'; +import { waitForResponse } from 'utils/test-utils'; +import Modal from './modal'; + +interface InputData { + value: string; + selectCheckbox?: boolean; +} + +export default class SMID { + private readonly modalComponent: Modal; + + constructor(private readonly page: Page, private readonly tissueIndex: number) { + this.modalComponent = new Modal(this.page); + } + + public async getValueAt(index: number): Promise { + const input = await this.getInputAt(index); + return input.currentValue(); + } + + public async fillInputs(inputData: (InputData | string)[]): Promise { + for (let i = 0; i < inputData.length; i++) { + const field = inputData[i]; + const value = typeof field === 'object' ? field.value : field; + const selectCheckbox = typeof field === 'object' ? field.selectCheckbox : false; + + const isInputVisible = await this.isInputVisible(i); + if (!isInputVisible) { + await this.addField(); + } + await this.fillField(value, i); + selectCheckbox && await this.selectCheckbox(i); + } + } + + public async deleteInputAt(index: number): Promise { + const deleteBtnLocator = this.fields.nth(index).locator('//td/button'); + await expect(deleteBtnLocator, `Delete button is not visible at index ${index}`).toBeVisible(); + + await deleteBtnLocator.click(); + } + + public async close(): Promise { + await this.clickModalBtn('close'); + } + + public async onlyKeepSelectedSMIDs(): Promise { + await this.clickModalBtn('Only keep selected SM-IDs'); + await waitForResponse(this.page, { uri: 'patch' }); + } + + /* Helper Functions */ + private async isInputVisible(index: number): Promise { + return this.fields.nth(index).isVisible(); + } + + private async addField(): Promise { + await this.addBtn.click(); + } + + private async fillField(value: string, index: number): Promise { + const fieldInput = await this.getInputAt(index); + const currentValue = await fieldInput.currentValue(); + if (currentValue.trim() !== value) { + await fieldInput.fillSimple(value); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async selectCheckbox(index: number): Promise { + const checkboxLocator = this.fields.nth(index).locator('//td/mat-checkbox'); + await expect(checkboxLocator, `Checkbox is not visible at index ${index}`).toBeVisible(); + await checkboxLocator.click(); + } + + private async getInputAt(index: number): Promise { + const fieldLocator = this.fields.nth(index).locator('//td/md-input-container'); + await expect(fieldLocator, `Input field is not visible at index ${index}`).toBeVisible(); + return new Input(this.page, { root: fieldLocator }); + } + + private async clickModalBtn(label: 'Only keep selected SM-IDs' | 'close'): Promise { + await this.modalComponent.getButton({ label }).click(); + } + + + /* Locators */ + private get modalBody(): Locator { + return this.modalComponent.bodyLocator(); + } + + private get modal(): Locator { + return this.page.locator(`(//app-tissue)[${this.tissueIndex + 1}]/app-modal`); + } + + private get fields(): Locator { + return this.modalBody.locator('//table/tr[td[md-input-container]]'); + } + + private get addBtn(): Locator { + return this.modalBody.locator(`//div[@class='app-modal-body']/button`); + } +} diff --git a/playwright-e2e/dsm/component/tables/onc-history-table.ts b/playwright-e2e/dsm/component/tables/onc-history-table.ts new file mode 100644 index 0000000000..18042965fd --- /dev/null +++ b/playwright-e2e/dsm/component/tables/onc-history-table.ts @@ -0,0 +1,296 @@ +import { expect, Locator, Page } from '@playwright/test'; +import Table from 'dss/component/table'; +import DatePicker from 'dsm/component/date-picker'; +import TextArea from 'dss/component/textarea'; +import { + InputTypeEnum, + OncHistoryInputColumnsEnum, + OncHistorySelectRequestEnum +} from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; +import { OncHistoryInputs } from 'dsm/component/tabs/model/onc-history-inputs'; +import { + OncHistoryInputsMapValue, + OncHistoryInputsTypes +} from 'dsm/component/tabs/interfaces/onc-history-inputs-types'; +import { waitForResponse } from 'utils/test-utils'; +import Select from 'dss/component/select'; +import TissueInformationPage from 'dsm/pages/tissue/tissue-information-page'; +import Button from 'dss/component/button'; +import { FillDate } from 'dsm/pages/tissue/interfaces/tissue-information-interfaces'; +import Input from 'dss/component/input'; +import Checkbox from 'dss/component/checkbox'; + +export default class OncHistoryTable extends Table { + private readonly tissueInformationPage: TissueInformationPage; + + constructor(protected readonly page: Page) { + super(page, { cssClassAttribute: '.table' }); + this.tissueInformationPage = new TissueInformationPage(this.page); + } + + public async openTissueInformationPage(index: number): Promise { + const button = new Button(this.page, { root: this.firstRequestColumn(index) }); + await button.click(); + return this.tissueInformationPage; + } + + public async selectRowAt(index: number): Promise { + const checkbox = new Checkbox(this.page, { root: this.firstRequestColumn(index) }); + await checkbox.click(); + } + + public async deleteRowAt(index: number): Promise { + const deleteRowBtn = this.deleteRowButton(index); + await expect(deleteRowBtn, 'Delete row button is not visible').toBeVisible(); + await deleteRowBtn.click(); + await waitForResponse(this.page, { uri: 'patch' }); + } + + public async getFieldValue(columnName: OncHistoryInputColumnsEnum, rowIndex = 0): Promise { + const cell = await this.checkColumnAndCellValidity(columnName, rowIndex); + const { + type: inputType, + }: OncHistoryInputsMapValue = OncHistoryInputs.get(columnName) as OncHistoryInputsMapValue; + + let value: Promise; + switch (inputType) { + case InputTypeEnum.INPUT: + value = new Input(this.page, { root: cell }).currentValue(); + break; + case InputTypeEnum.DATE: + value = new Input(this.page, { root: cell }).currentValue(); + break; + case InputTypeEnum.TEXTAREA: + value = new TextArea(this.page, { root: cell }).currentValue(); + break; + case InputTypeEnum.SELECT: + value = new Select(this.page, { root: cell }).currentValue(); + break; + default: + throw new Error(`Incorrect input type - ${inputType}`) + } + return value; + } + + public async fillNotes(note: string, index = 0): Promise { + const notesIcon = this.notesIconButton(index); + await expect(notesIcon, `Notes icon at ${index} index is not visible`).toBeVisible(); + await notesIcon.click(); + const notesModalContent = this.notesModalContent; + await expect(notesModalContent, `Notes modal at ${index} index is not visible`).toBeVisible(); + const textarea = new TextArea(this.page, { root: notesModalContent }); + const currentValue = await textarea.currentValue(); + if (currentValue.trim() !== note) { + await textarea.fill(note); + await waitForResponse(this.page, { uri: 'patch' }); + } + const saveAndCloseBtn = new Button(this.page, { root: notesModalContent, label: 'Save & Close' }); + await saveAndCloseBtn.click(); + } + + public async fillField(columnName: OncHistoryInputColumnsEnum, { + date, + select, + value, + lookupSelectIndex + }: OncHistoryInputsTypes, rowIndex = 0): Promise { + const cell = await this.checkColumnAndCellValidity(columnName, rowIndex); + + const { + type: inputType, + hasLookup + }: OncHistoryInputsMapValue = OncHistoryInputs.get(columnName) as OncHistoryInputsMapValue; + + switch (inputType) { + case InputTypeEnum.DATE: { + date && await this.fillDate(cell, date); + break; + } + case InputTypeEnum.INPUT: { + await this.fillInput(cell, String(value), hasLookup, lookupSelectIndex); + break; + } + case InputTypeEnum.TEXTAREA: { + await this.fillTextArea(cell, String(value), hasLookup, lookupSelectIndex); + break; + } + case InputTypeEnum.SELECT: { + await this.selectRequest(cell, select as OncHistorySelectRequestEnum); + break; + } + default: + throw new Error(`Incorrect input type - ${inputType}`) + } + } + + /* Helper Functions */ + private async fillInput(root: Locator, value: string | number, hasLookup: boolean, lookupSelectIndex = 0): Promise { + const inputElement = new Input(this.page, { root }); + const currentValue = await this.getCurrentValue(inputElement); + let actualValue = typeof value === 'number' ? value.toString() : value; + const maxLength = await inputElement.maxLength(); + + if (maxLength && actualValue.length > Number(maxLength)) { + actualValue = actualValue.slice(0, Number(maxLength)); + } + + if (currentValue !== actualValue) { + await inputElement.fillSimple(actualValue); + await waitForResponse(this.page, { uri: 'patch' }); + hasLookup && await this.lookup(lookupSelectIndex, true); + } + } + + private async fillDate(root: Locator, { date, today }: FillDate): Promise { + if (today) { + const todayBtn = new Button(this.page, { root, label: 'Today' }); + await todayBtn.click(); + await waitForResponse(this.page, { uri: 'patch' }); + } else if (date) { + const datePicker = new DatePicker(this.page, { root }); + await datePicker.open(); + await datePicker.pickDate(date); + await datePicker.close(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async fillTextArea(root: Locator, value: string | number, hasLookup: boolean, lookupSelectIndex = 0): Promise { + const textarea = new TextArea(this.page, { root }); + const currentValue = await this.getCurrentValue(textarea); + let actualValue = typeof value === 'number' ? value.toString() : value; + const maxLength = await textarea.maxLength(); + + if (maxLength && actualValue.length > Number(maxLength)) { + actualValue = actualValue.slice(0, Number(maxLength)); + } + + if (currentValue !== actualValue) { + await textarea.fill(actualValue, false); + await textarea.blur(); + await waitForResponse(this.page, { uri: 'patch' }); + hasLookup && await this.lookup(lookupSelectIndex); + } + } + + private async selectRequest(root: Locator, selectRequest: OncHistorySelectRequestEnum): Promise { + const matSelect = this.activeSelectedRequestListItem(root); + const selectedValue = await matSelect.textContent(); + if (selectedValue?.trim() !== selectRequest) { + const selectInput = new Select(this.page, { root }); + await selectInput.selectOption(selectRequest); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async lookup(selectIndex = 0, isFacility = false): Promise { + const lookupList = this.lookupList; + const count = await lookupList.count(); + if (count > 0 && selectIndex < count) { + if (isFacility) { + const isComplete = (await lookupList.nth(selectIndex).locator('span').count()) === 3; + expect(isComplete, `Lookup value is not complete at provided index - ${selectIndex}`) + .toBeTruthy() + } + await lookupList.nth(selectIndex).click(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async getCurrentValue(element: Input | Select | TextArea): Promise { + const currentValue = await element.currentValue(); + const isDisabled = await element.isDisabled(); + expect(isDisabled, `Input field is disabled`).toBeFalsy(); + return currentValue?.trim(); + } + + /* Assertions */ + public async assertRowSelectionCheckbox(index = 0): Promise { + const checkbox = this.firstRequestColumn(index).locator('//mat-checkbox'); + await expect(checkbox, 'Row selection checkbox is not visible').toBeVisible(); + } + + + /* Locators */ + private get lookupList(): Locator { + return this.page.locator(this.lookupListXPath); + } + + private activeSelectedRequestListItem(root: Locator): Locator { + return root.locator('mat-select').locator('span.mat-select-min-line'); + } + + private notesIconButton(index = 0): Locator { + return this.row(index).locator('td').locator('//button[.//*[name()=\'svg\' and @data-icon=\'comment-alt\']]'); + } + + private get notesModalContent(): Locator { + return this.page.locator('app-onc-history-detail').locator('app-modal').locator('.modal-content'); + } + + private deleteRowButton(index = 0): Locator { + return this.row(index).locator('td').last().getByRole('button'); + } + + private firstRequestColumn(index: number): Locator { + return this.row(index).locator('td').first(); + } + + private row(index: number): Locator { + return this.page.locator(this.tableXPath + this.rowXPath).nth(index); + } + + private column(columnName: OncHistoryInputColumnsEnum): Locator { + return this.page.locator(this.columnXPath(columnName)); + } + + private td(columnName: OncHistoryInputColumnsEnum): Locator { + return this.page.locator(this.tdXPath(columnName)); + } + + /* Assertions */ + private async checkColumnAndCellValidity(columnName: OncHistoryInputColumnsEnum, rowIndex: number): Promise { + let column = this.column(columnName); + if (await column.count() > 1) { + column = column.nth(1); + } + await expect(column, `Onc History Table - the column ${columnName} doesn't exist`) + .toBeVisible(); + + const cell: Locator = this.td(columnName).nth(rowIndex); + await expect(cell, `Kits Table - more than one or no column was found with the name ${columnName}`) + .toHaveCount(1); + + return cell; + } + + + /* XPaths */ + private get tableXPath(): string { + return `//app-onc-history-detail//table[contains(@class,'table')]` + } + + private get rowXPath(): string { + return '/tbody/tr' + } + + private get lookupListXPath(): string { + return '//app-lookup/div/ul/li' + } + + private tdXPath(columnName: OncHistoryInputColumnsEnum): string { + return `${this.tableXPath}${this.rowXPath}//td[position()=${this.columnPositionXPath(columnName)}]`; + } + + private columnPositionXPath(columnName: OncHistoryInputColumnsEnum): string { + return `count(${this.columnXPath(columnName)}/preceding-sibling::th)+1`; + } + + private columnXPath(columnName: OncHistoryInputColumnsEnum): string { + return `${this.headerXPath}/th[descendant-or-self::*[text()[normalize-space()='${columnName}']]]`; + } + + private get headerXPath(): string { + return '//table/thead/tr[1]' + } +} diff --git a/playwright-e2e/dsm/component/tabs/contactInformationTab.ts b/playwright-e2e/dsm/component/tabs/contact-information-tab.ts similarity index 100% rename from playwright-e2e/dsm/component/tabs/contactInformationTab.ts rename to playwright-e2e/dsm/component/tabs/contact-information-tab.ts diff --git a/playwright-e2e/dsm/component/tabs/enums/onc-history-input-columns-enum.ts b/playwright-e2e/dsm/component/tabs/enums/onc-history-input-columns-enum.ts new file mode 100644 index 0000000000..a21e940e73 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/enums/onc-history-input-columns-enum.ts @@ -0,0 +1,41 @@ +export enum OncHistoryInputColumnsEnum { + NOTES = 'Notes', + DATE_OF_PX = 'Date of PX', + TYPE_OF_PX = 'Type of PX', + LOCATION_OF_PX = 'Location of PX', + HISTOLOGY = 'Histology', + ACCESSION_NUMBER = 'Accession Number', + FACILITY = 'Facility', + PHONE = 'Phone', + FAX = 'Fax', + DESTRUCTION_POLICY = 'Destruction Policy (years)', + ONC_HISTORY_DATE = 'Onc History Date', + TUMOR_SIZE = 'Tumor Size', + SLIDES_TO_REQUEST = 'Slides to Request', + FACILITY_WHERE_SAMPLE_WAS_REVIEWED = 'Facility where the sample was reviewed', + TOTAL_NUMBER_SLIDES_MENTIONED = 'Total number of slides mentioned', + BLOCK_TO_REQUEST = 'Block to Request', + EXTENSIVE_TREATMENT_EFFECT = 'Extensive Treatment Effect', + VIABLE_TUMOR = '% Viable Tumor', + NECROSIS = '% Necrosis', + VOCAB_CHECK = 'VOCAB_CHECK', + REQUEST = 'Request' +} + +export enum OncHistorySelectRequestEnum { + NEEDS_REVIEW = 'Needs Review', + DONT_REQUEST = "Don't Request", + ON_HOLD = 'On Hold', + REQUEST = 'Request', + SENT = 'Sent', + RECEIVED = 'Received', + RETURNED = 'Returned', + UNABLE_TO_OBTAIN = 'Unable To Obtain' +} + +export enum InputTypeEnum { + TEXTAREA = 'textarea-field', + INPUT = 'input-field', + DATE = 'date-field', + SELECT = 'select-field' +} diff --git a/playwright-e2e/dsm/component/tabs/enums/tab-enum.ts b/playwright-e2e/dsm/component/tabs/enums/tab-enum.ts index 609b113c70..473cbe0925 100644 --- a/playwright-e2e/dsm/component/tabs/enums/tab-enum.ts +++ b/playwright-e2e/dsm/component/tabs/enums/tab-enum.ts @@ -4,4 +4,5 @@ export enum TabEnum { INVITAE = 'Invitae', SAMPLE_INFORMATION = 'Sample Information', SURVEY_DATA = 'Survey Data', + ONC_HISTORY = 'Onc History', } diff --git a/playwright-e2e/dsm/component/tabs/interfaces/onc-history-inputs-types.ts b/playwright-e2e/dsm/component/tabs/interfaces/onc-history-inputs-types.ts new file mode 100644 index 0000000000..a478e70de9 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/interfaces/onc-history-inputs-types.ts @@ -0,0 +1,20 @@ +import { InputTypeEnum, OncHistorySelectRequestEnum } from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; +import { FillDate } from 'dsm/pages/tissue/interfaces/tissue-information-interfaces'; + +export interface OncHistoryInputsTypes { + value?: string | number; + lookupSelectIndex?: number; + select?: OncHistorySelectRequestEnum, + date?: FillDate, +} + +export interface OncHistoryInputsMapValue { + type: InputTypeEnum; + hasLookup: boolean; +} + +export interface OncHistoryDateInput { + yyyy?: number, + month?: number, + dayOfMonth?: number +} diff --git a/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts b/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts new file mode 100644 index 0000000000..2efd3dc3b8 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts @@ -0,0 +1,24 @@ +import { InputTypeEnum, OncHistoryInputColumnsEnum } from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; +import { OncHistoryInputsMapValue } from 'dsm/component/tabs/interfaces/onc-history-inputs-types'; + +export const OncHistoryInputs = new Map() + .set(OncHistoryInputColumnsEnum.DATE_OF_PX, { type: InputTypeEnum.DATE, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.TYPE_OF_PX, { type: InputTypeEnum.TEXTAREA, hasLookup: true }) + .set(OncHistoryInputColumnsEnum.LOCATION_OF_PX, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.HISTOLOGY, { type: InputTypeEnum.TEXTAREA, hasLookup: true }) + .set(OncHistoryInputColumnsEnum.ACCESSION_NUMBER, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.FACILITY, { type: InputTypeEnum.INPUT, hasLookup: true }) + .set(OncHistoryInputColumnsEnum.PHONE, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.FAX, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.DESTRUCTION_POLICY, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.TUMOR_SIZE, { type: InputTypeEnum.TEXTAREA, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.SLIDES_TO_REQUEST, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.FACILITY_WHERE_SAMPLE_WAS_REVIEWED, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.TOTAL_NUMBER_SLIDES_MENTIONED, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.BLOCK_TO_REQUEST, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.EXTENSIVE_TREATMENT_EFFECT, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.VIABLE_TUMOR, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.NECROSIS, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.VOCAB_CHECK, { type: InputTypeEnum.INPUT, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.ONC_HISTORY_DATE, { type: InputTypeEnum.DATE, hasLookup: false }) + .set(OncHistoryInputColumnsEnum.REQUEST, { type: InputTypeEnum.SELECT, hasLookup: false }) diff --git a/playwright-e2e/dsm/component/tabs/model/sampleInformationModel.ts b/playwright-e2e/dsm/component/tabs/model/sample-information-model.ts similarity index 100% rename from playwright-e2e/dsm/component/tabs/model/sampleInformationModel.ts rename to playwright-e2e/dsm/component/tabs/model/sample-information-model.ts diff --git a/playwright-e2e/dsm/component/tabs/onc-history-tab.ts b/playwright-e2e/dsm/component/tabs/onc-history-tab.ts new file mode 100644 index 0000000000..76af3b2faf --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/onc-history-tab.ts @@ -0,0 +1,114 @@ +import { expect, Locator, Page } from '@playwright/test'; +import OncHistoryTable from 'dsm/component/tables/onc-history-table'; +import { waitForResponse } from 'utils/test-utils'; +import Select from 'dss/component/select'; +import Button from 'dss/component/button'; + + +export default class OncHistoryTab { + private readonly oncHistoryTable: OncHistoryTable; + + constructor(private readonly page: Page) { + this.oncHistoryTable = new OncHistoryTable(this.page); + } + + public get table(): OncHistoryTable { + return this.oncHistoryTable; + } + + public async openFilesOrderModal(): Promise { + await this.page.getByText('Change order/files in Bundle').click(); + } + + public async selectFilesOrderAndDownload(nameOfDocument: string, order: number): Promise { + const modalContent = this.filesOrderModalBodyContent(nameOfDocument); + + await expect(modalContent, `Files order modal is not visible`).toBeVisible(); + + const orderSelection = new Select(this.page, { root: modalContent }); + await orderSelection.selectOption(order.toString()); + } + + public async downloadPDFBundleAfterOrder(): Promise { + const downloadPDFBundleBtnLocator = this.filesOrderModalFooter; + await expect(downloadPDFBundleBtnLocator, `Download PDF Bundle button is not visible`).toBeVisible(); + + const downloadPDFBundleBtn = new Button(this.page, { root: downloadPDFBundleBtnLocator, label: 'Download PDF Bundle' }); + await downloadPDFBundleBtn.click(); + + await waitForResponse(this.page, { uri: 'downloadPDF' }); + const downloadFinishedText = this.page.getByText('Download finished.'); + await expect(downloadFinishedText, 'Downloading finished is not visible').toBeVisible(); + } + + public async downloadPDFBundle(): Promise { + await this.downloadPDFFlow(this.downloadPDFBundleButton); + } + + public async downloadRequestDocuments(): Promise { + await this.downloadPDFFlow(this.downloadRequestDocumentsButton); + } + + + /* Helper Functions */ + + /* + @TODO: once the PDF file is fixed, this will be updated + */ + private async downloadPDFFlow(downloadBtn: Locator): Promise { + await expect(downloadBtn, `Download PDF Bundle Button is not visible`).toBeVisible(); + await expect(downloadBtn, `Download PDF Bundle Button is not enabled`).toBeEnabled(); + + await downloadBtn.click(); + await this.requestAnyway(); + const downloadingText = this.page.getByText('Downloading... This might take a while'); + await expect(downloadingText, 'Downloading text is not visible').toBeVisible(); + + await waitForResponse(this.page, { uri: 'downloadPDF' }); + + const downloadFinishedText = this.page.getByText('Download finished.'); + await expect(downloadFinishedText, 'Downloading finished is not visible').toBeVisible(); + } + + private async requestAnyway(): Promise { + const requestAnywayBtn = this.page.getByRole('button', { name: 'Request Anyway' }); + if (await requestAnywayBtn.isVisible()) { + await requestAnywayBtn.click(); + } + } + + private async downloadPDF(downloadBtn: Locator): Promise { + await downloadBtn.click(); + + const [downloadedPDF] = await Promise.all([ + this.page.waitForEvent('download'), + waitForResponse(this.page, { uri: 'downloadPDF' }), + this.requestAnyway() + ]); + + const fileName = downloadedPDF.suggestedFilename(); + const filePath = await downloadedPDF.path(); + await downloadedPDF.saveAs(fileName); + + return filePath; + } + + + /* Locators */ + private get downloadPDFBundleButton(): Locator { + return this.page.getByRole('button', { name: 'Download PDF Bundle' }); + } + + private get downloadRequestDocumentsButton(): Locator { + return this.page.getByRole('button', { name: 'Download Request Documents' }); + } + + private filesOrderModalBodyContent(name: string): Locator { + return this.page.locator(`//app-participant-page/app-modal//div[@class='modal-body']` + + `//table/tbody/tr[td[text()[normalize-space()='${name}']]]/td[1]`); + } + + private get filesOrderModalFooter(): Locator { + return this.page.locator(`//app-participant-page/app-modal//div[@class='modal-footer']`); + } +} diff --git a/playwright-e2e/dsm/component/tabs/sampleInformationTab.ts b/playwright-e2e/dsm/component/tabs/sample-information-tab.ts similarity index 96% rename from playwright-e2e/dsm/component/tabs/sampleInformationTab.ts rename to playwright-e2e/dsm/component/tabs/sample-information-tab.ts index de1bb81cb5..70190aafbc 100644 --- a/playwright-e2e/dsm/component/tabs/sampleInformationTab.ts +++ b/playwright-e2e/dsm/component/tabs/sample-information-tab.ts @@ -1,6 +1,6 @@ import {expect, Locator, Page} from '@playwright/test'; import {SampleInfoEnum} from 'dsm/component/tabs/enums/sampleInfo-enum'; -import SampleInformation from 'dsm/component/tabs/model/sampleInformationModel'; +import SampleInformation from 'dsm/component/tabs/model/sample-information-model'; import {KitTypeEnum} from 'dsm/component/kitType/enums/kitType-enum'; interface SampleInfo { @@ -89,7 +89,7 @@ export default class SampleInformationTab { await expect(this.page.locator(MFBarcodeXPath), `MFBarcode - '${MFBarcode}' can't be found`).toBeVisible(); - expect(await this.page.locator(MFBarcodeXPath + this.ancestorSampleTypeXPath(type)).textContent(), + expect(await this.page.locator(MFBarcodeXPath + this.ancestorSampleTypeXPath(type)).textContent(), `Provided MFBarcode (${MFBarcode}) has different sample type than - ${type}`) .toContain(type); } diff --git a/playwright-e2e/dsm/component/tabs/tabs.ts b/playwright-e2e/dsm/component/tabs/tabs.ts index 8341086c97..0a3b6c3528 100644 --- a/playwright-e2e/dsm/component/tabs/tabs.ts +++ b/playwright-e2e/dsm/component/tabs/tabs.ts @@ -1,14 +1,16 @@ import { expect, Locator, Page } from '@playwright/test'; import {TabEnum} from 'dsm/component/tabs/enums/tab-enum'; -import ContactInformationTab from 'dsm/component/tabs/contactInformationTab'; +import ContactInformationTab from 'dsm/component/tabs/contact-information-tab'; import GenomeStudyTab from 'dsm/component/tabs/genome-study-tab'; -import SampleInformationTab from 'dsm/component/tabs/sampleInformationTab'; +import SampleInformationTab from 'dsm/component/tabs/sample-information-tab'; +import OncHistoryTab from './onc-history-tab'; export default class Tabs { private readonly tabs = new Map([ [TabEnum.CONTACT_INFORMATION, new ContactInformationTab(this.page)], [TabEnum.SAMPLE_INFORMATION, new SampleInformationTab(this.page)], [TabEnum.GENOME_STUDY, new GenomeStudyTab(this.page)], + [TabEnum.ONC_HISTORY, new OncHistoryTab(this.page)], ]) constructor(private readonly page: Page) {} diff --git a/playwright-e2e/dsm/component/tissue.ts b/playwright-e2e/dsm/component/tissue.ts new file mode 100644 index 0000000000..d43af12da5 --- /dev/null +++ b/playwright-e2e/dsm/component/tissue.ts @@ -0,0 +1,235 @@ +import { waitForResponse } from 'utils/test-utils'; +import { expect, Locator, Page } from '@playwright/test'; +import { + SequencingResultsEnum, + SMIdEnum, + TissueDynamicFieldsEnum, + TissueTypesEnum, + TumorTypesEnum +} from 'dsm/pages/tissue/enums/tissue-information-enum'; +import TextArea from 'dss/component/textarea'; +import { + FillDate, + TissueInputsMapValue +} from 'dsm/pages/tissue/interfaces/tissue-information-interfaces'; +import Select from 'dss/component/select'; +import DatePicker from './date-picker'; +import Input from 'dss/component/input'; +import { FillTissue } from 'dsm/pages/tissue/interfaces/fill-tissue-interface'; +import { tissueInputs } from 'dsm/pages/tissue/models/tissue-inputs'; +import { InputTypeEnum } from './tabs/enums/onc-history-input-columns-enum'; +import Button from 'dss/component/button'; +import SMID from './smid'; + +export default class Tissue { + private readonly SMIDModal: SMID; + + constructor(private readonly page: Page, private readonly tissueIndex: number = 0) { + this.SMIDModal = new SMID(this.page, this.tissueIndex); + } + + public async getFieldValue(dynamicField: TissueDynamicFieldsEnum): Promise { + const { + type: inputType, + byText + } = tissueInputs.get(dynamicField) as TissueInputsMapValue; + + let value: Promise; + const inputLocator = await this.getField(dynamicField, byText); + + switch (inputType) { + case InputTypeEnum.INPUT: + value = new Input(this.page, { root: inputLocator }).currentValue(); + break; + case InputTypeEnum.DATE: + value = new Input(this.page, { root: inputLocator }).currentValue(); + break; + case InputTypeEnum.TEXTAREA: + value = new TextArea(this.page, { root: inputLocator }).currentValue(); + break; + case InputTypeEnum.SELECT: + value = new Select(this.page, { root: inputLocator }).currentValue(); + break; + default: + throw new Error(`Incorrect input type - ${inputType}`) + } + + return value; + } + + public async fillSMIDs(SMID: SMIdEnum): Promise { + const SMIDLocator = await this.getField(SMID); + const SMIDPlusBtn = new Button(this.page, { root: SMIDLocator }); + await SMIDPlusBtn.click(); + + return this.SMIDModal; + } + + public async fillField(dynamicField: TissueDynamicFieldsEnum, { + inputValue, + select, + dates + }: FillTissue): Promise { + const { + type: inputType, + hasLookup, + byText + } = tissueInputs.get(dynamicField) as TissueInputsMapValue; + + switch (inputType) { + case InputTypeEnum.DATE: { + dates && await this.fillDateFields(dynamicField, dates); + break; + } + case InputTypeEnum.INPUT: { + inputValue && await this.fillInputField(dynamicField, inputValue, byText, hasLookup); + break; + } + case InputTypeEnum.TEXTAREA: { + inputValue && await this.fillTextareaField(dynamicField, inputValue); + break; + } + case InputTypeEnum.SELECT: { + select && await this.selectField(dynamicField, select); + break; + } + default: + throw new Error(`Incorrect input type - ${inputType}`) + } + } + + /* Helper Functions */ + private async fillTextareaField(dynamicField: TissueDynamicFieldsEnum, value: string | number): Promise { + const textAreaLocator = await this.getField(dynamicField); + const textarea = new TextArea(this.page, { root: textAreaLocator }); + const currentValue = await this.getCurrentValue(dynamicField, textarea); + let actualValue = typeof value === 'number' ? value.toString() : value; + const maxLength = await textarea.maxLength(); + + if (maxLength && actualValue.length > Number(maxLength)) { + actualValue = actualValue.slice(0, Number(maxLength)); + } + + if (currentValue !== actualValue) { + await textarea.fill(actualValue, false); + await textarea.blur(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async selectField(dynamicField: TissueDynamicFieldsEnum, + selection: TumorTypesEnum | TissueTypesEnum | SequencingResultsEnum | 'Yes' | 'No') + : Promise { + const selectLocator = await this.getField(dynamicField); + const selectElement = new Select(this.page, { root: selectLocator }); + const currentValue = await this.getCurrentValue(dynamicField, selectElement); + + if (currentValue !== selection) { + await selectElement.selectOption(selection); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async fillInputField(dynamicField: TissueDynamicFieldsEnum, value: string | number, byText = false, hasLookup = false): Promise { + const inputLocator = await this.getField(dynamicField, byText); + const inputElement = new Input(this.page, { root: inputLocator }); + const currentValue = await this.getCurrentValue(dynamicField, inputElement); + let actualValue = typeof value === 'number' ? value.toString() : value; + const maxLength = await inputElement.maxLength(); + + if (maxLength && actualValue.length > Number(maxLength)) { + actualValue = actualValue.slice(0, Number(maxLength)); + } + + if (currentValue !== actualValue) { + if (!currentValue && dynamicField === TissueDynamicFieldsEnum.TUMOR_COLLABORATOR_SAMPLE_ID) { + await inputElement.focus(); + const dropDown = this.page.locator("//ul[contains(@class, 'Lookup--Dropdown')]/li"); + await dropDown.nth(0).click(); + } + await inputElement.fillSimple(actualValue); + await waitForResponse(this.page, { uri: 'patch' }); + hasLookup && await this.lookup(); + } + } + + private async fillDateFields(dynamicField: TissueDynamicFieldsEnum, value: FillDate): Promise { + const dateLocator = await this.getField(dynamicField); + await this.fillDates(dateLocator, value); + } + + private async fillDates(root: Locator, { date, today }: FillDate): Promise { + if (today) { + const todayBtn = new Button(this.page, { root, label: 'Today' }); + await todayBtn.click(); + await waitForResponse(this.page, { uri: 'patch' }); + } else if (date) { + const datePicker = new DatePicker(this.page, { root }); + await datePicker.open(); + await datePicker.pickDate(date); + await datePicker.close(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async lookup(selectIndex = 0): Promise { + const lookupList = this.lookupList; + const count = await lookupList.count(); + if (count > 0 && selectIndex < count) { + await lookupList.nth(selectIndex).click(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async getCurrentValue(dynamicField: TissueDynamicFieldsEnum, element: Input | Select | TextArea): Promise { + const currentValue = await element.currentValue(); + const isDisabled = await element.isDisabled(); + + expect(isDisabled, `'${dynamicField}' is disabled`).toBeFalsy(); + + return currentValue?.trim(); + } + + private async getField(dynamicField: TissueDynamicFieldsEnum | SMIdEnum, byText = false): Promise { + const fieldLocator = byText ? this.tissue.getByText(dynamicField) : this.findField(dynamicField); + await expect(fieldLocator, `'${dynamicField}' is not visible`).toBeVisible(); + + return fieldLocator; + } + + /* Locator */ + private findField(field: TissueDynamicFieldsEnum | SMIdEnum): Locator { + return this.tissue + .locator(this.fieldXPath(field)); + } + + private get tissue(): Locator { + return this.page.locator(this.tissueXPath) + .nth(this.tissueIndex); + } + + private get lookupList(): Locator { + return this.page.locator(this.lookupListXPath); + } + + /* XPaths */ + private fieldXPath(fieldName: TissueDynamicFieldsEnum | SMIdEnum): string { + return `//td[text()[normalize-space()='${fieldName}']]/following-sibling::td[1]` + } + + private get tissueXPath(): string { + return `${this.participantDynamicInformationTableXPath}//td[app-tissue]/app-tissue/div/table` + } + + private get participantDynamicInformationTableXPath(): string { + return `${this.pageXPath}/div/div[last()]/table[not(contains(@class, 'table'))]` + } + + private get pageXPath(): string { + return '//app-tissue-page' + } + + private get lookupListXPath(): string { + return '//app-lookup/div/ul/li' + } +} diff --git a/playwright-e2e/dsm/pages/participant-page/participant-page.ts b/playwright-e2e/dsm/pages/participant-page/participant-page.ts index d3c3630037..f423886318 100644 --- a/playwright-e2e/dsm/pages/participant-page/participant-page.ts +++ b/playwright-e2e/dsm/pages/participant-page/participant-page.ts @@ -3,6 +3,7 @@ import {waitForResponse} from 'utils/test-utils'; import {MainInfoEnum} from 'dsm/pages/participant-page/enums/main-info-enum'; import Tabs from 'dsm/component/tabs/tabs'; import {TabEnum} from 'dsm/component/tabs/enums/tab-enum'; +import Input from 'dss/component/input'; export default class ParticipantPage { private readonly PAGE_TITLE: string = 'Participant Page'; @@ -82,6 +83,16 @@ export default class ParticipantPage { return await this.tabs.clickTab(tabName) as T; } + public async oncHistoryCreatedDate(): Promise { + const oncHistoryCreatedLocator = this.oncHistoryCreated; + await expect(oncHistoryCreatedLocator, 'Onc History Created is not visible').toBeVisible(); + const inputField = new Input(this.page, { root: oncHistoryCreatedLocator }); + + return inputField.currentValue(); + } + + /* Helper functions */ + private async readMainTextInfoFor(key: MainInfoEnum) { return await this.page.locator(this.getMainTextInfoXPath(key)).textContent(); } @@ -100,6 +111,10 @@ export default class ParticipantPage { return this.page.locator('//table[.//td[contains(normalize-space(),"Participant Notes")]]//td/textarea'); } + private get oncHistoryCreated(): Locator { + return this.page.locator('//table//td[contains(text(),"Onc History Created")]/following-sibling::td'); + } + /* XPaths */ private getMainTextInfoXPath(info: MainInfoEnum) { return this.getMainInfoXPath(info); diff --git a/playwright-e2e/dsm/pages/tissue/enums/tissue-information-enum.ts b/playwright-e2e/dsm/pages/tissue/enums/tissue-information-enum.ts new file mode 100644 index 0000000000..5f7f7bd572 --- /dev/null +++ b/playwright-e2e/dsm/pages/tissue/enums/tissue-information-enum.ts @@ -0,0 +1,103 @@ +export enum TissueInformationEnum { + ASSIGNEE = 'Assignee', + SHORT_ID = 'Short ID', + FULL_NAME = 'Full Name', + DATE_OF_BIRTH = 'Date of Birth', + DATE_OF_MAJORITY = 'Date of Majority', + DATE_OF_DIAGNOSIS = 'Date Of Diagnosis', + DATE_OF_PX = 'Date of PX', + TYPE_OF_PX = 'Type of PX', + LOCATION_OF_PX = 'Location of PX', + HISTOLOGY = 'Histology', + ACCESSION_NUMBER = 'Accession Number', + FACILITY = 'Facility', + PHONE = 'Phone', + FAX = 'Fax', + DESTRUCTION_POLICY = 'Destruction Policy (years)', + ONC_HISTORY_DATE = 'Onc History Date', + TUMOR_SIZE = 'Tumor Size', + SLIDES_TO_REQUEST = 'Slides to Request', + FACILITY_WHERE_SAMPLE_WAS_REVIEWED = 'Facility where the sample was reviewed', + TOTAL_NUMBER_SLIDES_MENTIONED = 'Total number of slides mentioned', + BLOCK_TO_REQUEST = 'Block to Request', + EXTENSIVE_TREATMENT_EFFECT = 'Extensive Treatment Effect', + VIABLE_TUMOR = '% Viable Tumor', + NECROSIS = '% Necrosis', + VOCAB_CHECK = 'VOCAB_CHECK', + REQUEST = 'Request Status' +} + +export enum DynamicFieldsEnum { + FAX_SENT = 'Fax Sent', + TISSUE_RECEIVED = 'Tissue Received', + PROBLEM_WITH_TISSUE = 'Problem with Tissue?', + NOTES = 'Notes', + DESTRUCTION_POLICY = 'Destruction Policy (years)', + GENDER = 'Gender' +} + +export enum SMIdEnum { + USS_SM_IDS = 'USS SM-IDS', + SCROLLS_SM_IDS = 'scrolls SM-IDS', + H_E_SM_IDS = 'H&E SM-IDS', +} + +export enum TissueDynamicFieldsEnum { + NOTES = 'Notes', + MATERIALS_RECEIVED = 'Materials received', + USS = 'USS (unstained', + BLOCK = 'Block(s)', + H_E = 'H&E(s)', + SCROLL = 'Scroll(s)', + TISSUE_TYPE = 'Tissue Type', + PATHOLOGY_REPORT = 'Pathology Report', + TUMOR_TYPE = 'Tumor Type', + TISSUE_SITE = 'Tissue Site', + TUMOR_COLLABORATOR_SAMPLE_ID = 'Tumor Collaborator Sample ID', + SK_ID = 'SK ID', + FIRST_SM_ID = 'First SM ID', + SM_ID_FOR_H_E = 'SM ID for H&E', + DATE_SENT_TO_GP = 'Date sent to GP', + SEQUENCING_RESULTS = 'Sequencing Results', + EXPECTED_RETURN_DATE = 'Expected Return Date', + RETURN_DATE = 'Return Date', + BLOCK_TO_SHL = 'Block to SHL', + SCROLLS_BACK_FROM_SHL = 'Scrolls back from SHL', + BLOCK_ID_TO_SHL = 'Block ID to SHL', + TUMOR_PERCENTAGE_AS_REPORTED_BY_SHL = 'Tumor Percentage as reported by SHL', + SHL_WORK_NUMBER = 'SHL Work Number', + TRACKING_NUMBER = 'Tracking Number' +} + +export enum ProblemWithTissueEnum { + INSUFFICIENT_MATERIAL_PER_PATH = 'Insufficient material per path', + INSUFFICIENT_MATERIAL_PER_SHL = 'Insufficient material per SHL', + NO_E_SIGNATURES = 'No e signatures', + PATH_DEPARTMENT_POLICY = 'Path department policy', + PATH_DEPARTMENT_UNABLE_TO_LOCATE = 'Path department unable to locate', + TISSUE_DESTROYED = 'Tissue destroyed', + OTHER = 'Other', + NO_PROBLEM = 'No Problem' +} + +export enum TissueTypesEnum { + SLIDE = 'Slide', + BLOCK = 'Block', + SCROLLS = 'Scrolls' +} + +export enum TumorTypesEnum { + PRIMARY = 'Primary', + MET = 'Met', + RECURRENT = 'Recurrent', + UNKNOWN = 'Unknown' +} + +export enum SequencingResultsEnum { + FAILURE_AT_SHL = 'Failure at SHL', + ABANDONED_AT_GP = 'Abandoned at GP', + FAILED_PURITY = 'Failed Purity', + EXTERNAL_PATH_REVIEW_FAILED = 'External Path Review Failed', + SUCCESS = 'Success', + EMPTY = '' +} diff --git a/playwright-e2e/dsm/pages/tissue/interfaces/fill-tissue-interface.ts b/playwright-e2e/dsm/pages/tissue/interfaces/fill-tissue-interface.ts new file mode 100644 index 0000000000..fe4f7e57e6 --- /dev/null +++ b/playwright-e2e/dsm/pages/tissue/interfaces/fill-tissue-interface.ts @@ -0,0 +1,8 @@ +import { SequencingResultsEnum, TissueTypesEnum, TumorTypesEnum } from 'dsm/pages/tissue/enums/tissue-information-enum'; +import { FillDate } from './tissue-information-interfaces'; + +export interface FillTissue { + inputValue?: string | number; + select?: TumorTypesEnum | TissueTypesEnum | SequencingResultsEnum | 'Yes' | 'No'; + dates?: FillDate; +} diff --git a/playwright-e2e/dsm/pages/tissue/interfaces/tissue-information-interfaces.ts b/playwright-e2e/dsm/pages/tissue/interfaces/tissue-information-interfaces.ts new file mode 100644 index 0000000000..3710d0d277 --- /dev/null +++ b/playwright-e2e/dsm/pages/tissue/interfaces/tissue-information-interfaces.ts @@ -0,0 +1,13 @@ +import { OncHistoryDateInput } from 'dsm/component/tabs/interfaces/onc-history-inputs-types'; +import { InputTypeEnum } from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; + +export interface FillDate { + date?: OncHistoryDateInput; + today?: boolean; +} + +export interface TissueInputsMapValue { + type: InputTypeEnum; + hasLookup: boolean; + byText: boolean; +} diff --git a/playwright-e2e/dsm/pages/tissue/models/tissue-inputs.ts b/playwright-e2e/dsm/pages/tissue/models/tissue-inputs.ts new file mode 100644 index 0000000000..ea30ddd43e --- /dev/null +++ b/playwright-e2e/dsm/pages/tissue/models/tissue-inputs.ts @@ -0,0 +1,28 @@ +import { InputTypeEnum } from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; +import { TissueInputsMapValue } from 'dsm/pages/tissue/interfaces/tissue-information-interfaces'; +import { TissueDynamicFieldsEnum } from 'dsm/pages/tissue/enums/tissue-information-enum'; + +export const tissueInputs = new Map() + .set(TissueDynamicFieldsEnum.NOTES, { type: InputTypeEnum.TEXTAREA, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.USS, { type: InputTypeEnum.INPUT, hasLookup: false, byText: true }) + .set(TissueDynamicFieldsEnum.BLOCK, { type: InputTypeEnum.INPUT, hasLookup: false, byText: true }) + .set(TissueDynamicFieldsEnum.H_E, { type: InputTypeEnum.INPUT, hasLookup: false, byText: true }) + .set(TissueDynamicFieldsEnum.SCROLL, { type: InputTypeEnum.INPUT, hasLookup: false, byText: true }) + .set(TissueDynamicFieldsEnum.TISSUE_TYPE, { type: InputTypeEnum.SELECT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.EXPECTED_RETURN_DATE, { type: InputTypeEnum.DATE, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.RETURN_DATE, { type: InputTypeEnum.DATE, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.TRACKING_NUMBER, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.PATHOLOGY_REPORT, { type: InputTypeEnum.SELECT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.TUMOR_COLLABORATOR_SAMPLE_ID, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.BLOCK_TO_SHL, { type: InputTypeEnum.DATE, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.SCROLLS_BACK_FROM_SHL, { type: InputTypeEnum.DATE, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.SK_ID, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.FIRST_SM_ID, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.SM_ID_FOR_H_E, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.DATE_SENT_TO_GP, { type: InputTypeEnum.DATE, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.SEQUENCING_RESULTS, { type: InputTypeEnum.SELECT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.TUMOR_TYPE, { type: InputTypeEnum.SELECT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.TISSUE_SITE, { type: InputTypeEnum.INPUT, hasLookup: true, byText: false }) + .set(TissueDynamicFieldsEnum.BLOCK_ID_TO_SHL, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.TUMOR_PERCENTAGE_AS_REPORTED_BY_SHL, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }) + .set(TissueDynamicFieldsEnum.SHL_WORK_NUMBER, { type: InputTypeEnum.INPUT, hasLookup: false, byText: false }); diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts new file mode 100644 index 0000000000..75a69ce1fa --- /dev/null +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -0,0 +1,333 @@ +import { expect, Locator, Page } from '@playwright/test'; +import { DynamicFieldsEnum, ProblemWithTissueEnum, TissueInformationEnum } from './enums/tissue-information-enum'; +import DatePicker from 'dsm/component/date-picker'; +import { waitForResponse } from 'utils/test-utils'; +import { FillDate } from './interfaces/tissue-information-interfaces'; +import Select from 'dss/component/select'; +import TextArea from 'dss/component/textarea'; +import Tissue from 'dsm/component/tissue'; +import Checkbox from 'dss/component/checkbox'; +import Button from 'dss/component/button'; +import Input from 'dss/component/input'; + + +export default class TissueInformationPage { + private readonly PAGE_TITLE = 'Tissue Request'; + + constructor(private readonly page: Page) { } + + public async tissue(index = 0): Promise { + await this.tissuesCountCheck(index); + return new Tissue(this.page, index); + } + + public async backToParticipantPage(): Promise { + await this.page.getByText('<< back to Participant Page').click(); + await this.page.waitForLoadState('load'); + } + + public async backToList(): Promise { + await this.page.getByText('<< back to List').click(); + await this.page.waitForLoadState('load'); + } + + public async addTissue(): Promise { + const addTissueBtnLocator = this.addTissueButton; + await expect(addTissueBtnLocator, `Add Tissue button is not visible`).toBeVisible(); + const button = new Button(this.page, { root: addTissueBtnLocator }); + const isDisabled = await button.isDisabled(); + + if (isDisabled) { + throw new Error('Add tissue button is disabled'); + } + + await button.click(); + const tissuesCount = await this.tissuesCount; + return this.tissue(tissuesCount - 1); + } + + public async deleteTissueAt(tissueIndex: number): Promise { + await this.tissuesCountCheck(tissueIndex); + const deleteTissueBtnLocator = this.deleteTissueButton(tissueIndex); + await expect(deleteTissueBtnLocator, `Tissue at index '${tissueIndex}' is not visible`).toBeVisible(); + + const button = new Button(this.page, { root: deleteTissueBtnLocator }); + const isDisabled = await button.isDisabled(); + + if (isDisabled) { + throw new Error('Delete tissue button is disabled'); + } + + await button.click(); + } + + public async getParticipantInformation(name: TissueInformationEnum): Promise { + const participantInformation = this.participantInformation(name); + await expect(participantInformation, `Data can't be found on - ${name}`).toBeVisible(); + + const data = await participantInformation.textContent(); + return data?.trim() as string; + } + + public async getFaxSentDate(dateIndex = 0): Promise { + const faxSentLocator = this.locatorFor(DynamicFieldsEnum.FAX_SENT).locator('app-field-datepicker') + .nth(dateIndex); + + await expect(faxSentLocator, `Fax sent date is not visible at ${dateIndex} index`).toBeVisible(); + + const inputField = new Input(this.page, { root: faxSentLocator }); + return inputField.currentValue(); + } + + public async getTissueReceivedDate(): Promise { + const tissueReceivedLocator = this.locatorFor(DynamicFieldsEnum.TISSUE_RECEIVED); + + await expect(tissueReceivedLocator, 'Tissue Received Date is not visible').toBeVisible(); + + const inputField = new Input(this.page, { root: tissueReceivedLocator }); + return inputField.currentValue(); + } + + public async getNotes(): Promise { + const notesLocator = this.locatorFor(DynamicFieldsEnum.NOTES); + await expect(notesLocator, 'Notes textarea is not visible').toBeVisible(); + + const textarea = new TextArea(this.page, { root: notesLocator }); + return textarea.currentValue(); + } + + public async fillFaxSentDates(date1: FillDate, date2?: FillDate, date3?: FillDate): Promise { + await this.fillFaxSentDate(0, date1); + date2 && await this.fillFaxSentDate(1, date2); + date3 && await this.fillFaxSentDate(2, date3); + } + + public async fillTissueReceivedDate(date: FillDate): Promise { + const tissueReceivedLocator = this.locatorFor(DynamicFieldsEnum.TISSUE_RECEIVED); + await expect(tissueReceivedLocator, 'Tissue Received Date picker is not visible').toBeVisible(); + + await this.fillDate(tissueReceivedLocator, date); + } + + public async fillNotes(value: string): Promise { + const notesLocator = this.locatorFor(DynamicFieldsEnum.NOTES); + await expect(notesLocator, 'Notes textarea is not visible').toBeVisible(); + + const textarea = new TextArea(this.page, { root: notesLocator }); + const isTextareaDisabled = await textarea.isDisabled(); + const existingValue = await textarea.currentValue(); + + if (!isTextareaDisabled && existingValue !== value) { + await textarea.fill(value, false); + await textarea.blur(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + public async fillDestructionPolicy(value: number, keptIndefinitelySelection = false, applyToAll = false): Promise { + const destructionPolicyLocator = this.locatorFor(DynamicFieldsEnum.DESTRUCTION_POLICY); + await expect(destructionPolicyLocator, 'Destruction Policy Years is not visible').toBeVisible(); + + const destructionPolicyYears = new Input(this.page, { root: destructionPolicyLocator }); + const isInputDisabled = await destructionPolicyYears.isDisabled(); + + await this.checkCheckbox(destructionPolicyLocator, keptIndefinitelySelection); + + const existingValue = await destructionPolicyYears.currentValue(); + const isAllowedToEnterValue = !keptIndefinitelySelection && !isInputDisabled && existingValue.trim() !== value.toString(); + + if (isAllowedToEnterValue) { + await destructionPolicyYears.fillSimple(value.toString()); + await waitForResponse(this.page, { uri: 'patch' }); + } + + applyToAll && await this.applyToAll(destructionPolicyLocator); + } + + public async problemsWithTissue(newValue: ProblemWithTissueEnum, unableToObtainSelection = false): Promise { + const problemsWithTissueLocator = this.locatorFor(DynamicFieldsEnum.PROBLEM_WITH_TISSUE); + await expect(problemsWithTissueLocator, 'Problems with Tissue selection is not visible') + .toBeVisible(); + + const selectElement = new Select(this.page, { root: problemsWithTissueLocator }); + const selectedValue = await selectElement.currentValue(); + const isDisabled = await selectElement.isSelectDisabled(); + const allowSelection = !isDisabled && selectedValue?.trim() !== newValue; + + if (allowSelection) { + await selectElement.selectOption(newValue); + await waitForResponse(this.page, { uri: 'patch' }); + } + + await this.checkCheckbox(this.problemsWithTissueUnableToObtainCheckbox, unableToObtainSelection); + } + + public async selectGender(gender: 'Male' | 'Female'): Promise { + const genderLocator = this.locatorFor(DynamicFieldsEnum.GENDER); + await expect(genderLocator, 'Gender selection is not visible') + .toBeVisible(); + + const selectElement = new Select(this.page, { root: genderLocator }); + const selectedValue = await selectElement.currentValue(); + const isDisabled = await selectElement.isSelectDisabled(); + + if (!isDisabled && selectedValue?.trim() !== gender) { + await selectElement.selectOption(gender); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + /* Assertions */ + public async assertFaxSentDatesCount(count: number): Promise { + const faxSentDates = this.locatorFor(DynamicFieldsEnum.FAX_SENT).locator('app-field-datepicker'); + const dateInputs = await faxSentDates.count(); + expect(dateInputs, `Fax sent dates count doesn't equal ${count}`).toEqual(count); + } + + /* Helper Functions */ + private async applyToAll(root: Locator): Promise { + const applyToAllBtn = new Button(this.page, + { root, label: 'APPLY TO ALL', exactMatch: true } + ) + const isApplyToAllBtnDisabled = await applyToAllBtn.isDisabled(); + if (!isApplyToAllBtnDisabled) { + await applyToAllBtn.click(); + const modalBtn = new Button(this.page, + { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true } + ) + const isModalBtnDisabled = await modalBtn.isDisabled(); + if (!isModalBtnDisabled) { + await modalBtn.click(); + await waitForResponse(this.page, { uri: 'institutions', timeout: 40000 }); + + const successModalBtn = new Button(this.page, + { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }) + await successModalBtn.click(); + } + } + } + + private async fillFaxSentDate(dateIndex: number, date: FillDate): Promise { + const faxSentLocator = this.locatorFor(DynamicFieldsEnum.FAX_SENT).locator('app-field-datepicker') + .nth(dateIndex); + if (await faxSentLocator.isVisible()) { + await this.fillDate(faxSentLocator, date); + } + } + + private async fillDate(root: Locator, { date, today }: FillDate): Promise { + if (today) { + const todayBtn = new Button(this.page, { root, exactMatch: true, label: 'Today' }); + const isDisabled = await todayBtn.isDisabled(); + if (!isDisabled) { + await todayBtn.click(); + await waitForResponse(this.page, { uri: 'patch' }); + return; + } + } + if (date) { + const datePicker = new DatePicker(this.page, { root }); + await datePicker.open(); + await datePicker.pickDate(date); + await datePicker.close(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async checkCheckbox(root: Locator, check: boolean): Promise { + const checkbox = new Checkbox(this.page, { root }); + const isChecked = await checkbox.isChecked(); + const isDisabled = await checkbox.isDisabled(); + if (check && !isChecked && !isDisabled) { + await checkbox.check(); + await waitForResponse(this.page, { uri: 'patch' }); + } + if (!check && isChecked && !isDisabled) { + await checkbox.uncheck(); + await waitForResponse(this.page, { uri: 'patch' }); + } + } + + private async tissuesCountCheck(index: number): Promise { + const tissuesCount = await this.tissuesCount; + if (index > tissuesCount - 1 || index < 0) { + throw new Error(`Incorrect index number - ${index}`); + } + } + + private get tissuesCount(): Promise { + return this.page.locator(this.tissueXPath).count(); + } + + /* Assertions */ + public async assertPageTitle(): Promise { + const pageTitle = await this.pageTitle.textContent(); + expect(pageTitle?.trim()).toEqual(this.PAGE_TITLE); + } + + /* Locators */ + private get pageTitle(): Locator { + return this.page.locator(this.pageTitleXPath); + } + + private participantInformation(name: TissueInformationEnum) { + return this.page.locator(this.participantInformationXpath(name)); + } + + private locatorFor(dynamicFieldName: DynamicFieldsEnum): Locator { + return this.page.locator(this.XPathFor(dynamicFieldName)) + } + + private get problemsWithTissueUnableToObtainCheckbox(): Locator { + return this.page.locator(this.problemWithTissueUnableToObtainXPath); + } + + private deleteTissueButton(tissueIndex: number): Locator { + return this.page.locator(this.tissueXPath).nth(tissueIndex) + .locator('tr') + .nth(0); + } + + private get addTissueButton(): Locator { + return this.page.locator(this.participantDynamicInformationTableXPath) + .locator('tr') + .last(); + } + + /* XPaths */ + private participantInformationXpath(name: TissueInformationEnum) { + return `${this.participantInformationTableXPath}/tr[td[text()[normalize-space()='${name}']]]/td[2]` + } + + private XPathFor(dynamicFieldName: DynamicFieldsEnum): string { + return this.dynamicField(dynamicFieldName) + } + + private get tissueXPath(): string { + return `${this.participantDynamicInformationTableXPath}//td[app-tissue]/app-tissue/div/table`; + } + + private get problemWithTissueUnableToObtainXPath(): string { + return `${this.dynamicField(DynamicFieldsEnum.PROBLEM_WITH_TISSUE, 3)}`; + } + + private dynamicField(dynamicField: DynamicFieldsEnum, index = 2): string { + return `${this.participantDynamicInformationTableXPath}/tr[td[1][text()[normalize-space()='${dynamicField}']]]/td[${index}]` + } + + private get participantInformationTableXPath(): string { + return `${this.pageXPath}//table[contains(@class, 'table-condensed')]/tbody` + } + + private get participantDynamicInformationTableXPath(): string { + return `${this.pageXPath}/div/div[last()]/table[not(contains(@class, 'table'))]` + } + + private get pageTitleXPath(): string { + return `${this.pageXPath}/h1` + } + + private get pageXPath(): string { + return '//app-tissue-page' + } +} diff --git a/playwright-e2e/dss/component/input.ts b/playwright-e2e/dss/component/input.ts index 4fdcec95fb..529d96e35e 100644 --- a/playwright-e2e/dss/component/input.ts +++ b/playwright-e2e/dss/component/input.ts @@ -79,4 +79,25 @@ export default class Input extends WidgetBase { await expect(this.page.locator('button:visible', { hasText: 'Saving' })).toBeHidden(); } } + + public async fillSimple(value: string): Promise { + await this.toLocator().fill(value); + await this.toLocator().blur(); + } + + public async currentValue(): Promise { + return this.toLocator().inputValue(); + } + + public async blur(): Promise { + await this.toLocator().blur(); + } + + public async focus(): Promise { + await this.toLocator().focus(); + } + + public async maxLength(): Promise { + return this.toLocator().getAttribute('maxlength'); + } } diff --git a/playwright-e2e/dss/component/select.ts b/playwright-e2e/dss/component/select.ts index 2b4c8b577c..9779d0ba38 100644 --- a/playwright-e2e/dss/component/select.ts +++ b/playwright-e2e/dss/component/select.ts @@ -100,4 +100,12 @@ export default class Select extends WidgetBase { } return options!; } + + async currentValue(): Promise { + return (await this.toLocator().locator('span.mat-select-min-line').textContent()) as string; + } + + async isSelectDisabled(): Promise { + return (await this.toLocator().getAttribute('class'))?.includes('mat-select-disabled') as boolean; + } } diff --git a/playwright-e2e/dss/component/textarea.ts b/playwright-e2e/dss/component/textarea.ts index e5efcbefc2..66cf90d723 100644 --- a/playwright-e2e/dss/component/textarea.ts +++ b/playwright-e2e/dss/component/textarea.ts @@ -22,12 +22,12 @@ export default class TextArea extends WidgetBase { } } - async fill(value: string): Promise { + async fill(value: string, pressTab = true): Promise { await this.toLocator().fill(value); - await this.toLocator().press('Tab'); + pressTab && await this.toLocator().press('Tab'); } - async getText(): Promise { + async currentValue(): Promise { return this.toLocator().inputValue(); } @@ -38,4 +38,12 @@ export default class TextArea extends WidgetBase { await this.page.keyboard.press('Meta+A'); await this.page.keyboard.press('Backspace'); } + + async blur(): Promise { + await this.toLocator().blur(); + } + + public async maxLength(): Promise { + return this.toLocator().getAttribute('maxlength'); + } } diff --git a/playwright-e2e/tests/dsm/kitUploadFlow/blood-kit-upload-flow.spec.ts b/playwright-e2e/tests/dsm/kitUploadFlow/blood-kit-upload-flow.spec.ts index 96700eaed2..7eaed2b09e 100644 --- a/playwright-e2e/tests/dsm/kitUploadFlow/blood-kit-upload-flow.spec.ts +++ b/playwright-e2e/tests/dsm/kitUploadFlow/blood-kit-upload-flow.spec.ts @@ -8,7 +8,7 @@ import ParticipantListPage from 'dsm/pages/participant-list-page'; import {StudyNavEnum} from 'dsm/component/navigation/enums/studyNav-enum'; import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import {KitUploadInfo} from 'dsm/pages/kitUpload-page/models/kitUpload-model'; -import ContactInformationTab from 'dsm/component/tabs/contactInformationTab'; +import ContactInformationTab from 'dsm/component/tabs/contact-information-tab'; import {TabEnum} from 'dsm/component/tabs/enums/tab-enum'; import {SamplesNavEnum} from 'dsm/component/navigation/enums/samplesNav-enum'; import {KitTypeEnum} from 'dsm/component/kitType/enums/kitType-enum'; @@ -16,7 +16,7 @@ import KitUploadPage from 'dsm/pages/kitUpload-page/kitUpload-page'; import InitialScanPage from 'dsm/pages/scanner-pages/initialScan-page'; import FinalScanPage from 'dsm/pages/scanner-pages/finalScan-page'; import crypto from 'crypto'; -import SampleInformationTab from 'dsm/component/tabs/sampleInformationTab'; +import SampleInformationTab from 'dsm/component/tabs/sample-information-tab'; import {SampleInfoEnum} from 'dsm/component/tabs/enums/sampleInfo-enum'; import {SampleStatusEnum} from 'dsm/component/tabs/enums/sampleStatus-enum'; import KitsWithoutLabelPage from 'dsm/pages/kitsInfo-pages/kitsWithoutLabel-page'; diff --git a/playwright-e2e/tests/dsm/kitUploadFlow/saliva-kit-upload-flow.spec.ts b/playwright-e2e/tests/dsm/kitUploadFlow/saliva-kit-upload-flow.spec.ts index bd098b1940..9a567f94a5 100644 --- a/playwright-e2e/tests/dsm/kitUploadFlow/saliva-kit-upload-flow.spec.ts +++ b/playwright-e2e/tests/dsm/kitUploadFlow/saliva-kit-upload-flow.spec.ts @@ -8,7 +8,7 @@ import ParticipantListPage from 'dsm/pages/participant-list-page'; import {StudyNavEnum} from 'dsm/component/navigation/enums/studyNav-enum'; import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import {KitUploadInfo} from 'dsm/pages/kitUpload-page/models/kitUpload-model'; -import ContactInformationTab from 'dsm/component/tabs/contactInformationTab'; +import ContactInformationTab from 'dsm/component/tabs/contact-information-tab'; import {TabEnum} from 'dsm/component/tabs/enums/tab-enum'; import {SamplesNavEnum} from 'dsm/component/navigation/enums/samplesNav-enum'; import {KitTypeEnum} from 'dsm/component/kitType/enums/kitType-enum'; @@ -16,7 +16,7 @@ import KitUploadPage from 'dsm/pages/kitUpload-page/kitUpload-page'; import InitialScanPage from 'dsm/pages/scanner-pages/initialScan-page'; import FinalScanPage from 'dsm/pages/scanner-pages/finalScan-page'; import crypto from 'crypto'; -import SampleInformationTab from 'dsm/component/tabs/sampleInformationTab'; +import SampleInformationTab from 'dsm/component/tabs/sample-information-tab'; import {SampleInfoEnum} from 'dsm/component/tabs/enums/sampleInfo-enum'; import {SampleStatusEnum} from 'dsm/component/tabs/enums/sampleStatus-enum'; import KitsWithoutLabelPage from 'dsm/pages/kitsInfo-pages/kitsWithoutLabel-page'; diff --git a/playwright-e2e/tests/dsm/tissue-request/cmi-tissue-request.spec.ts b/playwright-e2e/tests/dsm/tissue-request/cmi-tissue-request.spec.ts new file mode 100644 index 0000000000..87be0dda54 --- /dev/null +++ b/playwright-e2e/tests/dsm/tissue-request/cmi-tissue-request.spec.ts @@ -0,0 +1,134 @@ +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; +import { test } from 'fixtures/dsm-fixture'; +import ParticipantListPage from 'dsm/pages/participant-list-page'; +import { AdditionalFilter } from 'dsm/component/filters/sections/search/search-enums'; +import ParticipantPage from 'dsm/pages/participant-page/participant-page'; +import { TabEnum } from 'dsm/component/tabs/enums/tab-enum'; +import OncHistoryTab from 'dsm/component/tabs/onc-history-tab'; +import { + OncHistoryInputColumnsEnum, + OncHistorySelectRequestEnum +} from 'dsm/component/tabs/enums/onc-history-input-columns-enum'; +import { expect } from '@playwright/test'; +import { getDate } from 'utils/date-utils'; +import { TissueDynamicFieldsEnum } from 'dsm/pages/tissue/enums/tissue-information-enum'; + +test.describe('Tissue Request Flow', () => { + const studies = [StudyEnum.PANCAN]; + + for (const study of studies) { + test(`Tissue Request Flow for ${study} study @dsm @feature`, async ({ page, request }) => { + const participantListPage = await ParticipantListPage.goto(page, study, request); + const customizeViewPanel = participantListPage.filters.customizeViewPanel; + const searchPanel = participantListPage.filters.searchPanel; + + await test.step('Search for the right participant', async () => { + await customizeViewPanel.open(); + await customizeViewPanel.selectColumns('Medical Record Columns', ['MR Problem']); + await customizeViewPanel.selectColumns('Participant - DSM Columns', ['Onc History Created']); + await customizeViewPanel.selectColumns('Research Consent Form Columns', ['Your Mailing Address *']); + + await searchPanel.open(); + await searchPanel.checkboxes('Status', { checkboxValues: ['Enrolled'] }); + await searchPanel.checkboxes('MR Problem', { checkboxValues: ['No'] }); + await searchPanel.dates('Onc History Created', { additionalFilters: [AdditionalFilter.EMPTY] }); + await searchPanel.text('Your Mailing Address *', { additionalFilters: [AdditionalFilter.NOT_EMPTY] }); + + await searchPanel.search(); + }) + + const participantListTable = participantListPage.participantListTable; + const participantPage: ParticipantPage = await participantListTable.openParticipantPageAt(0); + const oncHistoryTab = await participantPage.clickTab(TabEnum.ONC_HISTORY); + const oncHistoryTable = oncHistoryTab.table; + + await test.step('Update Onc History data - Facility', async () => { + await oncHistoryTable.fillField(OncHistoryInputColumnsEnum.FACILITY, { value: 'm', lookupSelectIndex: 1 }); + }) + + await test.step('Automatically updated Onc History Created date', async () => { + await participantPage.backToList(); + await participantListTable.openParticipantPageAt(0); + + const oncHistoryCreatedDate = participantPage.oncHistoryCreatedDate(); + expect(oncHistoryCreatedDate, 'Onc History Date has not been updated').toBeTruthy(); + }) + + await participantPage.clickTab(TabEnum.ONC_HISTORY); + + await test.step('Update Onc History data - Date of PX', async () => { + await oncHistoryTable.fillField(OncHistoryInputColumnsEnum.DATE_OF_PX, + { + date: { + date: { + yyyy: new Date().getFullYear(), + month: new Date().getMonth(), + dayOfMonth: new Date().getDate() + } + } + }); + }) + + await test.step('Update Onc History data - Type of PX', async () => { + await oncHistoryTable.fillField(OncHistoryInputColumnsEnum.TYPE_OF_PX, { value: 'a', lookupSelectIndex: 4 }); + }) + + await test.step('Update Onc History data - Request', async () => { + await oncHistoryTable.fillField(OncHistoryInputColumnsEnum.REQUEST, { select: OncHistorySelectRequestEnum.REQUEST }); + }) + + await test.step('Clicking Download PDF Bundle', async () => { + await oncHistoryTable.assertRowSelectionCheckbox(); + await oncHistoryTable.selectRowAt(0); + await oncHistoryTab.downloadPDFBundle(); + }) + + await test.step('Select Cover PDF - Download Request Documents', async () => { + await oncHistoryTable.selectRowAt(0); + await oncHistoryTab.downloadRequestDocuments(); + }) + + await participantPage.backToList(); + await participantListTable.openParticipantPageAt(0); + await participantPage.clickTab(TabEnum.ONC_HISTORY); + const tissueInformationPage = await oncHistoryTable.openTissueInformationPage(0); + + await test.step('Downloading Tissue Request Documents - Updates Fax Sent', async () => { + const faxSentDate1 = await tissueInformationPage.getFaxSentDate(); + const faxSentDate2 = await tissueInformationPage.getFaxSentDate(1); + const tissueReceivedDate = await tissueInformationPage.getTissueReceivedDate(); + expect(faxSentDate1.trim(), 'Fax sent date 1 is not set to today').toEqual(getDate()); + expect(faxSentDate2.trim(), 'Fax sent date 2 is not empty').toBe(''); + expect(tissueReceivedDate.trim(), 'Tissue received date is not empty').toBeFalsy(); + }) + + await test.step('Enter Tissue Received', async () => { + await tissueInformationPage.fillFaxSentDates({ today: true }, { today: true }); + await tissueInformationPage.fillTissueReceivedDate({ today: true }); + await tissueInformationPage.assertFaxSentDatesCount(2); + }) + + await test.step('Add Tissue Note', async () => { + await tissueInformationPage.fillNotes('Test tissue notes'); + }) + + await test.step('Add a destruction policy and click on Apply to All', async () => { + await tissueInformationPage.fillDestructionPolicy(2233, false, true); + }) + + await test.step('Add Material count', async () => { + const testValue = 21; + const tissue = await tissueInformationPage.tissue(); + await tissue.fillField(TissueDynamicFieldsEnum.USS, { inputValue: testValue }); + await tissue.fillField(TissueDynamicFieldsEnum.BLOCK, { inputValue: testValue }); + await tissue.fillField(TissueDynamicFieldsEnum.H_E, { inputValue: testValue }); + await tissue.fillField(TissueDynamicFieldsEnum.SCROLL, { inputValue: testValue }); + }) + + await test.step('Deleting OncHistory tab row', async () => { + await tissueInformationPage.addTissue(); + await tissueInformationPage.deleteTissueAt(1); + }) + }) + } +}); From 100fcb921c46e0de84ac50f5d2677d0a754b278d Mon Sep 17 00:00:00 2001 From: aweng98 Date: Fri, 22 Sep 2023 13:10:39 -0400 Subject: [PATCH 2/9] lint --- .../dsm/component/tabs/model/onc-history-inputs.ts | 2 +- playwright-e2e/dsm/component/tabs/sample-information-tab.ts | 2 +- playwright-e2e/dsm/component/tabs/tabs.ts | 4 ++-- .../dsm/pages/participant-page/rgp/family-member-tab.ts | 4 ++-- playwright-e2e/dsm/pages/tissue/tissue-information-page.ts | 6 +++--- playwright-e2e/dss/component/modal.ts | 1 - .../component/dsm/paginators/participantsListPaginator.ts | 5 +---- 7 files changed, 10 insertions(+), 14 deletions(-) diff --git a/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts b/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts index 2efd3dc3b8..01b1ad7c71 100644 --- a/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts +++ b/playwright-e2e/dsm/component/tabs/model/onc-history-inputs.ts @@ -21,4 +21,4 @@ export const OncHistoryInputs = new Map { const isNotEnteredVisible = await (this.tabs.get(TabEnum.CONTACT_INFORMATION) as ContactInformationTab) - .isNotEnteredVisible() + .isNotEnteredVisible(); return !isNotEnteredVisible; } diff --git a/playwright-e2e/dsm/pages/participant-page/rgp/family-member-tab.ts b/playwright-e2e/dsm/pages/participant-page/rgp/family-member-tab.ts index 0c760bdece..232b59e5e0 100644 --- a/playwright-e2e/dsm/pages/participant-page/rgp/family-member-tab.ts +++ b/playwright-e2e/dsm/pages/participant-page/rgp/family-member-tab.ts @@ -1,4 +1,4 @@ -import { expect, Locator, Page } from '@playwright/test'; +import { Locator, Page } from '@playwright/test'; import { FamilyMember } from 'dsm/component/tabs/enums/familyMember-enum'; import TextArea from 'dss/component/textarea'; import { waitForResponse } from 'utils/test-utils'; @@ -13,7 +13,7 @@ export default class FamilyMemberTab { private _lastName!: string; private _relationshipID!: string; private _familyID!: number; - private _relationToProband: FamilyMember; + private readonly _relationToProband: FamilyMember; private readonly page: Page; private readonly MIN_POSSIBLE_RELATIONSHIP_ID_VALUE: number = 0; private readonly MAX_POSSIBLE_RELATIONSHIP_ID_VALUE: number = 1000; diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index 75a69ce1fa..367ed7d97a 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -188,20 +188,20 @@ export default class TissueInformationPage { private async applyToAll(root: Locator): Promise { const applyToAllBtn = new Button(this.page, { root, label: 'APPLY TO ALL', exactMatch: true } - ) + ); const isApplyToAllBtnDisabled = await applyToAllBtn.isDisabled(); if (!isApplyToAllBtnDisabled) { await applyToAllBtn.click(); const modalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true } - ) + ); const isModalBtnDisabled = await modalBtn.isDisabled(); if (!isModalBtnDisabled) { await modalBtn.click(); await waitForResponse(this.page, { uri: 'institutions', timeout: 40000 }); const successModalBtn = new Button(this.page, - { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }) + { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); await successModalBtn.click(); } } diff --git a/playwright-e2e/dss/component/modal.ts b/playwright-e2e/dss/component/modal.ts index 9f0b65d342..f7abe1cb1d 100644 --- a/playwright-e2e/dss/component/modal.ts +++ b/playwright-e2e/dss/component/modal.ts @@ -6,7 +6,6 @@ export default class Modal { private readonly rootSelector: Locator; constructor(private readonly page: Page) { - this.page = page; this.rootSelector = this.page.locator('[aria-modal="true"][role="dialog"]'); } diff --git a/playwright-e2e/lib/component/dsm/paginators/participantsListPaginator.ts b/playwright-e2e/lib/component/dsm/paginators/participantsListPaginator.ts index 3236badf96..131a78142b 100644 --- a/playwright-e2e/lib/component/dsm/paginators/participantsListPaginator.ts +++ b/playwright-e2e/lib/component/dsm/paginators/participantsListPaginator.ts @@ -24,10 +24,7 @@ export class ParticipantsListPaginator { const nextLocator = this.page.locator(this.nextXPath); const isvisible = await nextLocator.isVisible(); const isDisabled = isvisible ? (await nextLocator.getAttribute('class'))?.includes('disabled') : true; - if (isDisabled) { - return false; - } - return true; + return !isDisabled; } public async previous(): Promise { From 5cd43183230f393a4f36d88e92a3e3916199a855 Mon Sep 17 00:00:00 2001 From: aweng98 Date: Fri, 22 Sep 2023 15:03:02 -0400 Subject: [PATCH 3/9] fix test --- .../dsm/pages/tissue/tissue-information-page.ts | 9 ++++----- playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index 367ed7d97a..6bd8710014 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -192,16 +192,15 @@ export default class TissueInformationPage { const isApplyToAllBtnDisabled = await applyToAllBtn.isDisabled(); if (!isApplyToAllBtnDisabled) { await applyToAllBtn.click(); - const modalBtn = new Button(this.page, - { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true } + const modalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true } ); + await modalBtn.toLocator().waitFor({ state: 'attached'}); const isModalBtnDisabled = await modalBtn.isDisabled(); if (!isModalBtnDisabled) { await modalBtn.click(); - await waitForResponse(this.page, { uri: 'institutions', timeout: 40000 }); + await waitForResponse(this.page, { uri: 'institutions' }); - const successModalBtn = new Button(this.page, - { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); + const successModalBtn = new Button(this.page,{ root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); await successModalBtn.click(); } } diff --git a/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts b/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts index fdede1d2f7..898f9069e2 100644 --- a/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts +++ b/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts @@ -15,6 +15,7 @@ import { v4 as uuid } from 'uuid'; import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import { WelcomePage } from 'dsm/pages/welcome-page'; import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; +import * as crypto from 'crypto'; test.describe.serial('DSM Family Enrollment Handling', () => { From 0e38b7d7eef9f0ad94fac2e572c6ab8ef819d155 Mon Sep 17 00:00:00 2001 From: aweng98 Date: Fri, 22 Sep 2023 16:09:12 -0400 Subject: [PATCH 4/9] lint --- playwright-e2e/dsm/pages/tissue/tissue-information-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index 6bd8710014..e18081365f 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -200,7 +200,7 @@ export default class TissueInformationPage { await modalBtn.click(); await waitForResponse(this.page, { uri: 'institutions' }); - const successModalBtn = new Button(this.page,{ root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); + const successModalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); await successModalBtn.click(); } } From 415a6adf2d68e10d21691868afafce148eeddb7b Mon Sep 17 00:00:00 2001 From: aweng98 Date: Fri, 22 Sep 2023 16:56:47 -0400 Subject: [PATCH 5/9] fix --- playwright-e2e/dsm/pages/tissue/tissue-information-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index e18081365f..81a881608a 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -201,7 +201,7 @@ export default class TissueInformationPage { await waitForResponse(this.page, { uri: 'institutions' }); const successModalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); - await successModalBtn.click(); + await successModalBtn.isVisible() && await successModalBtn.click(); } } } From 72d2955ad56016b53376073b6470029c7a9e6625 Mon Sep 17 00:00:00 2001 From: Kiara Westbrooks Date: Mon, 25 Sep 2023 11:33:15 -0400 Subject: [PATCH 6/9] moving waitForResponse to new location seems to help it pass more consistently --- playwright-e2e/dsm/pages/tissue/tissue-information-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index 81a881608a..bb5c5b0299 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -198,9 +198,9 @@ export default class TissueInformationPage { const isModalBtnDisabled = await modalBtn.isDisabled(); if (!isModalBtnDisabled) { await modalBtn.click(); - await waitForResponse(this.page, { uri: 'institutions' }); const successModalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); + await waitForResponse(this.page, { uri: 'institutions' }); await successModalBtn.isVisible() && await successModalBtn.click(); } } From 58e9fd885b1ea7cf6686913b4c67906fb44995d0 Mon Sep 17 00:00:00 2001 From: aweng98 Date: Mon, 25 Sep 2023 11:38:17 -0400 Subject: [PATCH 7/9] undo rgp test --- playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts b/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts index 898f9069e2..fdede1d2f7 100644 --- a/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts +++ b/playwright-e2e/tests/rgp/dsm-family-enrollment.spec.ts @@ -15,7 +15,6 @@ import { v4 as uuid } from 'uuid'; import ParticipantPage from 'dsm/pages/participant-page/participant-page'; import { WelcomePage } from 'dsm/pages/welcome-page'; import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; -import * as crypto from 'crypto'; test.describe.serial('DSM Family Enrollment Handling', () => { From bc9a5c9b04e2cdba29178d48294b417654121530 Mon Sep 17 00:00:00 2001 From: aweng98 Date: Mon, 25 Sep 2023 11:46:14 -0400 Subject: [PATCH 8/9] fix --- .../dsm/pages/tissue/tissue-information-page.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts index bb5c5b0299..ed34379e18 100644 --- a/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts +++ b/playwright-e2e/dsm/pages/tissue/tissue-information-page.ts @@ -192,16 +192,14 @@ export default class TissueInformationPage { const isApplyToAllBtnDisabled = await applyToAllBtn.isDisabled(); if (!isApplyToAllBtnDisabled) { await applyToAllBtn.click(); - const modalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true } - ); - await modalBtn.toLocator().waitFor({ state: 'attached'}); - const isModalBtnDisabled = await modalBtn.isDisabled(); + const yesBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Yes', exactMatch: true }); + await yesBtn.toLocator().waitFor({ state: 'attached'}); + const isModalBtnDisabled = await yesBtn.isDisabled(); if (!isModalBtnDisabled) { - await modalBtn.click(); - + await yesBtn.click(); const successModalBtn = new Button(this.page, { root: this.page.locator('app-modal'), label: 'Ok', exactMatch: true }); await waitForResponse(this.page, { uri: 'institutions' }); - await successModalBtn.isVisible() && await successModalBtn.click(); + await successModalBtn.click(); } } } From 03c4d001f2da2e7d59193c7ab10d59099247099b Mon Sep 17 00:00:00 2001 From: aweng98 Date: Mon, 25 Sep 2023 12:32:48 -0400 Subject: [PATCH 9/9] update kitUpload-page --- .../dsm/pages/kitUpload-page/kitUpload-page.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/playwright-e2e/dsm/pages/kitUpload-page/kitUpload-page.ts b/playwright-e2e/dsm/pages/kitUpload-page/kitUpload-page.ts index 17bed98149..11e4040163 100644 --- a/playwright-e2e/dsm/pages/kitUpload-page/kitUpload-page.ts +++ b/playwright-e2e/dsm/pages/kitUpload-page/kitUpload-page.ts @@ -20,9 +20,12 @@ export default class KitUploadPage { public async waitForReady(kitTypes?: KitTypeEnum[]): Promise { const knownKitTypes = kitTypes ?? this.expectedKitTypes; //Use the param kit types if provided, if they are not, then use the general expected kit types - await this.page.waitForLoadState('networkidle'); + await Promise.all([ + this.page.waitForLoadState(), + this.assertPageTitle() + ]); + await expect(this.skipAddressValidationCheckbox).toBeVisible(); await waitForNoSpinner(this.page); - await this.assertPageTitle(); await this.assertDisplayedKitTypes(knownKitTypes); } @@ -142,7 +145,11 @@ export default class KitUploadPage { } public async skipAddressValidation(value = false): Promise { - value && await this.page.locator('//mat-checkbox[.//*[@class="mat-checkbox-label" and text()="Skip address validation on upload"]]').click(); + value && await this.skipAddressValidationCheckbox.click(); + } + + public get skipAddressValidationCheckbox(): Locator { + return this.page.locator('//mat-checkbox[.//*[@class="mat-checkbox-label" and text()="Skip address validation on upload"]]'); } /* XPaths */