diff --git a/playwright-e2e/dsm/component/filters/sections/customize-view.ts b/playwright-e2e/dsm/component/filters/sections/customize-view.ts index 2985f43795..a190740f4c 100644 --- a/playwright-e2e/dsm/component/filters/sections/customize-view.ts +++ b/playwright-e2e/dsm/component/filters/sections/customize-view.ts @@ -96,4 +96,14 @@ export class CustomizeView { private columnPathXPath(columnName: string): string { return `/ul/li/mat-checkbox[label[.//*[text()[normalize-space()='${columnName}']]]]`; } + + public async isColumnGroupDisplayed(columnGroupName: string): Promise { + //Note: Some studies have column groups that share the same name + const numberOfMatches = await this.page.locator(`//button[contains(@data-toggle, 'dropdown') and contains(., '${columnGroupName}')]`).count(); + if (numberOfMatches > 0) { + return true; + } else { + return false; + } + } } diff --git a/playwright-e2e/dsm/component/filters/sections/search/search-enums.ts b/playwright-e2e/dsm/component/filters/sections/search/search-enums.ts index 541ac63087..a409df0487 100644 --- a/playwright-e2e/dsm/component/filters/sections/search/search-enums.ts +++ b/playwright-e2e/dsm/component/filters/sections/search/search-enums.ts @@ -12,3 +12,76 @@ export enum CustomViewColumns { RESEARCH_CONSENT_FORM = 'Research Consent Form Columns', SAMPLE = 'Sample Columns', } + +/* Participant Column group related enums */ + +export enum ParticipantColumns { + CONSENT_BLOOD = 'Consent Blood', + CONSENT_TISSUE = 'Consent Tissue', + COUNTRY = 'Country', + DATE_OF_BIRTH = 'Date of Birth', + DATE_OF_MAJORITY = 'Date of Majority', + DDP = 'DDP', + DIAGNOSIS_MONTH = 'Diagnosis Month', + DIAGNOSIS_YEAR = 'Diagnosis Year', + DO_NOT_CONTACT = 'Do Not Contact', + EMAIL = 'Email', + FILE_UPLOAD_TIME = 'File Upload Time', + FIRST_NAME = 'First Name', + LAST_NAME = 'Last Name', + PARTICIPANT_ID = 'Participant ID', + PREFERRED_LANGUAGE = 'Preferred Language', + REGISTRATION_DATE = 'Registration Date', + SHORT_ID = 'Short ID', + STATUS = 'Status', + UPLOADED_FILE_NAME = 'Uploaded File Name', +} + +export enum EnrollmentStatus { + REGISTERED = 'Registered', + EXITED_BEFORE_ENROLLMENT = 'Exited before Enrollment', + EXITED_AFTER_ENROLLMENT = 'Exited after Enrollment', + ENROLLED = 'Enrolled', + LOST_TO_FOLLOWUP = 'Lost to Followup', + COMPLETED = 'Completed', +} + +/* Sample Column group related enums */ + +export enum SampleColumns { + COLLABORATOR_PARTICIPANT_ID = 'Collaborator Participant ID', + COLLECTION_DATE = 'Collection Date', + MF_CODE = 'MF code', + NORMAL_COLLABORATOR_SAMPLE_ID = 'Normal Collaborator Sample ID', + SAMPLE_DEACTIVATION = 'Sample Deactivation', + SAMPLE_NOTES = 'Sample Notes', + SAMPLE_RECEIVED = 'Sample Received', + SAMPLE_SENT = 'Sample Sent', + SAMPLE_TYPE = 'Sample Type', + SEQUENCING_RESTRICTION = 'Sequencing Restriction', + STATUS = 'Status', + TRACKING_IN = 'Tracking-in', + TRACKING_OUT = 'Tracking-out', +} + +export enum KitStatus { + WAITING_ON_GP = 'Waiting on GP', + GP_MANUAL_LABEL = 'GP manual Label', + DEACTIVATED = 'Deactivated', + SHIPPED = 'Shipped', + RECEIVED = 'Received', +} + +/* Contact Information Columns group enums */ + +export enum ContactInformation { + CITY = 'City', + COUNTRY = 'Country', + MAIL_TO_NAME = 'Mail To Name', + PHONE = 'Phone', + STATE = 'State', + STREET_ONE = 'Street 1', + STREET_TWO = 'Street 2', + VALID = 'Valid', + ZIP = 'Zip', +} diff --git a/playwright-e2e/dsm/component/filters/sections/search/search.ts b/playwright-e2e/dsm/component/filters/sections/search/search.ts index 5b0b4a5c4a..d17ec0d440 100644 --- a/playwright-e2e/dsm/component/filters/sections/search/search.ts +++ b/playwright-e2e/dsm/component/filters/sections/search/search.ts @@ -1,9 +1,11 @@ import { expect, Locator, Page } from '@playwright/test'; import DatePicker from 'dsm/component/date-picker'; import { CheckboxConfig, DateConfig, RadioButtonConfig, TextConfig } from 'dsm/component/filters/sections/search/search-types'; -import { AdditionalFilter } from 'dsm/component/filters/sections/search/search-enums'; +import { AdditionalFilter, EnrollmentStatus, KitStatus, ParticipantColumns, SampleColumns } from 'dsm/component/filters/sections/search/search-enums'; import { waitForNoSpinner, waitForResponse } from 'utils/test-utils'; import { logError } from 'utils/log-utils'; +import { KitTypeEnum } from 'dsm/component/kitType/enums/kitType-enum'; +import { ParticipantListTable } from 'dsm/component/tables/participant-list-table'; export class Search { private readonly enUSDateRegExp = new RegExp(/\b(0[1-9]|1[012])([/])(0[1-9]|[12]\d|3[01])\2(\d{4})/); @@ -155,6 +157,79 @@ export class Search { return isDisabled || false; } + public async searchForParticipantsWithEnrollmentStatus(enrollmentStatuses: EnrollmentStatus[]): Promise { + await this.open(); + await this.checkboxes(ParticipantColumns.STATUS, { checkboxValues: enrollmentStatuses }); + await this.search(); + } + + public async searchForKitSampleStatus(kitTypes: KitTypeEnum[], kitStatus: KitStatus): Promise { + await this.open(); + await this.checkboxes(SampleColumns.SAMPLE_TYPE, { checkboxValues: kitTypes }); + await this.radioButton(SampleColumns.STATUS, { radioButtonValue: kitStatus }); + await this.search(); + } + + public async searchForParticipantWithUnsentSalivaKit(): Promise { + const [filterListReponse] = await Promise.all([ + this.page.waitForResponse(response => response.url().includes('/ui/filterList') && response.status() === 200), + this.searchForKitSampleStatus([KitTypeEnum.SALIVA], KitStatus.WAITING_ON_GP), + ]); + let responseBody = JSON.parse(await filterListReponse.text()); + + const participantListTable = new ParticipantListTable(this.page); + const amountOfRowsDisplayed = await participantListTable.rowsCount; + let validTestParticipantShortIDFound; + + //Check for a participant with a saliva kit that neither has scanDate or deactivatedDate + for (let index = 0; index < amountOfRowsDisplayed; index++) { + if (await participantListTable.onLastPage()) { + console.log('On last page'); + break; + } + console.log(`Participant ${index}'s information: ${responseBody.participants[index].esData.profile.hruid}`); + const kitInformation = responseBody.participants[index].kits; + const amountOfKits = kitInformation.length; + console.log(`Participant kit amount: ${kitInformation.length}`); + for (let kitNumber = 0; kitNumber < amountOfKits; kitNumber++) { + console.log(`Participant kit info: ${kitInformation[kitNumber].kitTypeName}`); + if ((kitInformation[kitNumber].kitTypeName) === KitTypeEnum.SALIVA) { + if (kitInformation[kitNumber].scanDate === undefined && kitInformation[kitNumber].deactivatedDate === undefined) { + console.log(`ALERT: Valid participant found!`); + validTestParticipantShortIDFound = responseBody.participants[index].esData.profile.hruid; + break; + } + } + } + + if (validTestParticipantShortIDFound) { + break; + } + + if (this.isReadyToGoToTheNextPage(index, amountOfRowsDisplayed)) { + const [nextResponse] = await Promise.all([ + this.page.waitForResponse(response => response.url().includes('/ui/filterList') && response.status() === 200), + await participantListTable.nextPage(), + ]); + await participantListTable.waitForReady(); + responseBody = JSON.parse(await nextResponse.text()); + index = -1; + console.log('Should be on the next page now'); + } + } + return validTestParticipantShortIDFound; + } + + /** + * Determines if it's time to go to the next page based on the current index used in a for-loop & whether the last row was looked at + * @param index Current index being used in a for-loop in a different method + * @param amountOfRowsDisplayed The amount of rows currently displayed in the DSM Participant List + * @returns if it's time to turn the page + */ + private isReadyToGoToTheNextPage(index: number, amountOfRowsDisplayed: number): boolean { + return (index > 0) && (index % (amountOfRowsDisplayed - 1) === 0); + } + /* Locators */ private checkboxLocator(columnName: string, checkboxName: string): Locator { diff --git a/playwright-e2e/dsm/component/navigation/enums/accessibility-enum.ts b/playwright-e2e/dsm/component/navigation/enums/accessibility-enum.ts new file mode 100644 index 0000000000..e0d44ced40 --- /dev/null +++ b/playwright-e2e/dsm/component/navigation/enums/accessibility-enum.ts @@ -0,0 +1,4 @@ +export enum ScreenReaderText { + CURRENT_PAGE = `You're on page`, + GENERAL_PAGE = `page`, +} diff --git a/playwright-e2e/dsm/component/tables/participant-list-table.ts b/playwright-e2e/dsm/component/tables/participant-list-table.ts index 5488e054dd..2abd2a07df 100644 --- a/playwright-e2e/dsm/component/tables/participant-list-table.ts +++ b/playwright-e2e/dsm/component/tables/participant-list-table.ts @@ -7,6 +7,7 @@ import { getDate, offsetDaysFromToday } from 'utils/date-utils'; 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 { ScreenReaderText } from 'dsm/component/navigation/enums/accessibility-enum'; export class ParticipantListTable extends Table { private readonly _participantPage: ParticipantPage = new ParticipantPage(this.page); @@ -112,11 +113,49 @@ export class ParticipantListTable extends Table { return this.page.locator(`//table/tbody/tr`).nth(position); } + /** + * Returns the number of the last page of the participant list + * @type pagebuttonContents - 0 based- string array that splits "page" and {page number} + * @returns The number of the last page button e.g. if there are 3 pages, this method returns: 3 + */ + public async getLastPageNumber(): Promise { + const lastPageButton = this.lastPage; + const pageButtonContents = (await lastPageButton.innerText()).split(ScreenReaderText.GENERAL_PAGE); + const pageNumber = pageButtonContents[1].trim(); + return parseInt(pageNumber); + } + + /** + * Return the current page number + * @returns @type pagebuttonContents - 0 based- string array that splits "You're on page" and {page number} + */ + public async getCurrentPageNumber(): Promise { + const currentPageButton = this.currentPage; + const pageButtonContents = (await currentPageButton.innerText()).split(ScreenReaderText.CURRENT_PAGE); + const pageNumber = pageButtonContents[1].trim(); + return parseInt(pageNumber); + } + + public async onLastPage(): Promise { + if (await this.getCurrentPageNumber() === await this.getLastPageNumber()) { + return true; + } + return false; + } + /* Locators */ public get rowsCount(): Promise { return this.getRowsCount(); } + public get lastPage(): Locator { + return this.page.locator(`//li[contains(@class, 'pagination-next')]/preceding-sibling::li[1]`); + } + + public get currentPage(): Locator { + return this.page.locator(`//li[contains(@class, 'current')]`); + } + /* XPaths */ private participantDataByXPath(columnName: string, columnValue: string, xColumnName: string): string { return ( diff --git a/playwright-e2e/dsm/component/tabs/enums/oncHistory-enum.ts b/playwright-e2e/dsm/component/tabs/enums/oncHistory-enum.ts new file mode 100644 index 0000000000..678f613949 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/enums/oncHistory-enum.ts @@ -0,0 +1,58 @@ +export enum OncHistoryRequestStatus { + 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 Decalcification { + NITRIC_ACID = `Nitric Acid (includes Perenyi's fluid)`, + HYDROCHLORIC_ACID = `Hydrochloric Acid (includes Von Ebner's solution)`, + FORMIC_ACID = `Formic Acid (includes Evans/Kajian, Kristensen/Gooding/Stewart)`, + ACID_NOS = 'Acid NOS', + EDTA = 'EDTA', + SAMPLE_NOT_DECALCIFIED = 'Sample not decalcified', + OTHER = 'Other', + UNKNOWN = 'Unknown', + IMMUNOCAL = 'Immunocal/Soft Decal', + BLANK = '', +} + +export enum GeneralAnswer { + YES = 'Yes', + NO = 'No', + UNKNOWN = 'Unknown', + BLANK = '', +} + +/* Lists the placements of certain columns in the onc history. Note: Number assumes 0-based/counting from zero */ +export enum OncHistoryColumn { + DATE_OF_PX = 2, + TYPE_OF_PX = 3, + LOCATION_OF_PX = 4, + HISTOLOGY = 5, + ACCESSION_NUMBER = 6, + FACILITY = 7, + PHONE = 8, + FAX = 9, + DESTRUCTION_POLICY = 10, + OS2_LOCAL_CONTROL = 11, + LMS_TUMOR_SIZE = 11, + LMS_SLIDES_TO_REQUEST = 12, + OS2_TUMOR_SIZE = 12, + LMS_FACILITY_WHERE_SAMPLE_REVIEWED = 13, + OS2_BLOCKS_WITH_TUMOR = 11, + LMS_SLIDES_TOTAL = 14, + LMS_BLOCKS_TO_REQUEST = 15, + OS2_NECROSIS = 14, + LMS_TREATMENT_EFFECT = 16, + OS2_FFPE = 16, + OS2_DECALCIFICATION = 17, + VIABLE_TUMOR = 17, + LMS_NECROSIS = 18, + REQUEST_STATUS = 21, +} 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/model/oncHistoryDetailModel.ts b/playwright-e2e/dsm/component/tabs/model/oncHistoryDetailModel.ts new file mode 100644 index 0000000000..3d42ced067 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/model/oncHistoryDetailModel.ts @@ -0,0 +1,29 @@ +import { Decalcification, GeneralAnswer, OncHistoryRequestStatus } from 'dsm/component/tabs/enums/oncHistory-enum' + +export interface OncHistoryDetail { + oncHistoryID?: number, + requestOncHistoryDetail(): Promise, + accessTissueRequestPage(): Promise, + dateOfPX?: string, + typeOfPX?: string, + locationOfPX?: string, + histology?: string, + accessionNumber?: string, + facility?: string, + phone?: string, + fax?: string, + destructionPolicy?: string | number, + localControl?: GeneralAnswer, + decalcification?: Decalcification, + ffpe?: GeneralAnswer, + blocksWithTumor?: string, + tumorSize?: string, + blockToRequest?: string, + necrosis?: string, + viableTumor?: string, + slidesToRequest?: string, + facilityWhereSampleWasReviewed?: string, + slidesTotal?: string, + treatmentEffect?: string, + setOncHistoryRequestStatus(status: OncHistoryRequestStatus): Promise, +} diff --git a/playwright-e2e/dsm/component/tabs/oncHistoryDetailRow.ts b/playwright-e2e/dsm/component/tabs/oncHistoryDetailRow.ts new file mode 100644 index 0000000000..6f65379b00 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/oncHistoryDetailRow.ts @@ -0,0 +1,596 @@ +import { OncHistoryDetail } from 'dsm/component/tabs/model/oncHistoryDetailModel'; +import { Locator, Page, expect } from '@playwright/test'; +import { Decalcification, GeneralAnswer, OncHistoryColumn, OncHistoryRequestStatus } from './enums/oncHistory-enum'; +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; + +export default class OncHistoryDetailRow implements OncHistoryDetail { + private _oncHistoryID!: number; + private _dateOfPX!: string; + private _typeOfPX!: string; + private _locationOfPX!: string; + private _histology!: string; + private _accessionNumber!: string; + private _facility!: string; + private _phone!: string; + private _fax!: string; + private _destructionPolicy!: string; + private _localControl!: GeneralAnswer; + private _decalcification!: Decalcification; + private _ffpe!: GeneralAnswer; + private _blocksWithTumor!: string; + private _tumorSize!: string; + private _necrosis!: string; + private _viableTumor!: string; + private _slidesToRequest!: string; + private _facilityWhereSampleWasReviewed!: string; + private _slidesTotal!: string; + private _blocksToRequest!: string; + private _treatmentEffect!: string; + + constructor(protected readonly page: Page) { + this._oncHistoryID; + this._dateOfPX; + this._typeOfPX; + this._locationOfPX; + this._histology; + this._accessionNumber; + this._facility; + this._phone; + this._fax; + this._destructionPolicy; + this._localControl; + this._decalcification; + this._ffpe; + this._blocksWithTumor; + this._tumorSize; + this._necrosis; + this._viableTumor; + this._slidesToRequest; + this._facilityWhereSampleWasReviewed; + this._slidesTotal; + } + + /* Getters and Setters */ + + private set oncHistoryID(id: number) { + this._oncHistoryID = id; + } + + public get oncHistoryID(): number { + return this._oncHistoryID; + } + + private async setOncHistoryDetailID(currentRow: Locator): Promise { + const oncHistoryDetailID = parseInt(await currentRow.getAttribute('id') as string); + this.oncHistoryID = oncHistoryDetailID; + } + + public set dateOfPX(diagnosisDate: string) { + this._dateOfPX = diagnosisDate; + } + + public get dateOfPX(): string { + return this._dateOfPX as string; + } + + public async fillDateOfPXField(currentRow: Locator, dateOfPX: string): Promise { + this._dateOfPX = dateOfPX; + const dateOfPXField = currentRow.locator(`//td[${OncHistoryColumn.DATE_OF_PX}]//mat-form-field//input`); + await dateOfPXField.fill(this._dateOfPX); + await dateOfPXField.blur(); + } + + public set typeOfPX(diagnosisType: string) { + this._typeOfPX = diagnosisType; + } + + public get typeOfPX(): string { + return this._typeOfPX; + } + + public async fillTypeOfPXField(currentRow: Locator, typeOfPX: string): Promise { + this._typeOfPX = typeOfPX; + const typeOfPXField = currentRow.locator(`//td[${OncHistoryColumn.TYPE_OF_PX}]//mat-form-field//textarea`); + await typeOfPXField.fill(this._typeOfPX); + } + + public set locationOfPX(location: string) { + this._locationOfPX = location; + } + + public get locationOfPX(): string { + return this._locationOfPX; + } + + public async fillLocationOfPXField(currentRow: Locator, locationOfPX: string): Promise { + this._locationOfPX = locationOfPX; + const locationOfPXField = currentRow.locator(`//td[${OncHistoryColumn.LOCATION_OF_PX}]//mat-form-field//input`); + await locationOfPXField.fill(this._locationOfPX); + } + + public set histology(histology: string) { + this._histology = histology; + } + + public get histology(): string { + return this._histology; + } + + public async fillHistologyField(currentRow: Locator, histology: string): Promise { + this._histology = histology; + const histologyField = currentRow.locator(`//td[${OncHistoryColumn.HISTOLOGY}]//mat-form-field//textarea`); + await histologyField.fill(this._histology); + } + + public set accessionNumber(accessionNumber: string) { + this._accessionNumber = accessionNumber; + } + + public get accessionNumber(): string { + return this._accessionNumber; + } + + public async fillAccessionNumberField(currentRow: Locator, accessionNumber: string): Promise { + this._accessionNumber = accessionNumber; + const accessionNumberField = currentRow.locator(`//td[${OncHistoryColumn.ACCESSION_NUMBER}]//mat-form-field//input`); + await accessionNumberField.fill(this._accessionNumber); + } + + public set facility(facility: string) { + this._facility = facility; + } + + public get facility(): string { + return this._facility; + } + + public async fillFacilityNameField(currentRow: Locator, facilityName: string): Promise { + this._facility = facilityName; + const facilityNameField = currentRow.locator(`//td[${OncHistoryColumn.FACILITY}]//mat-form-field//input`); + await facilityNameField.fill(this._facility); + } + + public set phone(phoneNumber: string) { + this._phone = phoneNumber; + } + + public get phone(): string { + return this._phone; + } + + public async fillFacilityPhoneNumberField(currentRow: Locator, phone: string): Promise { + this._phone = phone; + const facilityPhoneNumberField = currentRow.locator(`//td[${OncHistoryColumn.PHONE}]//mat-form-field//input`); + await facilityPhoneNumberField.fill(this._phone); + } + + public set fax(faxNumber: string) { + this._fax = faxNumber; + } + + public get fax(): string { + return this._fax; + } + + public async fillFacilityFaxNumberField(currentRow: Locator, fax: string): Promise { + this._fax = fax; + const facilityfaxNumberField = currentRow.locator(`//td[${OncHistoryColumn.FAX}]//mat-form-field//input`); + await facilityfaxNumberField.fill(this._fax); + } + + public set destructionPolicy(amountOfYears: number | string) { + this._destructionPolicy = amountOfYears as string; + } + + public get destructionPolicy(): string { + return this._destructionPolicy; + } + + public async fillDestructionPolicyField(currentRow: Locator, destructionPolicy: string): Promise { + this._destructionPolicy = destructionPolicy; + const destructionPolicyField = currentRow.locator(`//td[${OncHistoryColumn.DESTRUCTION_POLICY}]//mat-form-field//input`); + await destructionPolicyField.fill(this._destructionPolicy); + } + + public set localControl(answer: GeneralAnswer) { + this._localControl = answer; + } + + public get localControl(): GeneralAnswer { + return this._localControl; + } + + public async fillLocalControlDropdown(currentRow: Locator, sampleIsFromLocalControl: GeneralAnswer): Promise { + this._localControl = sampleIsFromLocalControl; + const localcontrolDropdown = currentRow.locator(`//td[${OncHistoryColumn.OS2_LOCAL_CONTROL}]//mat-select`); + await localcontrolDropdown.click(); + const answer = this._localControl as string; + await localcontrolDropdown.filter({ hasText: answer }).click(); + } + + public set decalcification(answer: Decalcification) { + this._decalcification = answer; + } + + public get decalcification(): Decalcification { + return this._decalcification; + } + + public async fillDecalcificationDropdown(currentRow: Locator, decalcification: Decalcification): Promise { + this._decalcification = decalcification; + const decalcificationDropdown = currentRow.locator(`//td[${OncHistoryColumn.OS2_DECALCIFICATION}]//mat-select`); + await decalcificationDropdown.click(); + const answer = this._decalcification as string; + await decalcificationDropdown.filter({ hasText: answer }).click(); + } + + public set ffpe(answer: GeneralAnswer) { + this._ffpe = answer; + } + + public get ffpe(): GeneralAnswer { + return this.ffpe; + } + + public async fillFFPEDropdown(currentRow: Locator, ffpe: GeneralAnswer): Promise { + this._ffpe = ffpe; + const ffpeDropdown = currentRow.locator(`//td[${OncHistoryColumn.OS2_FFPE}]//mat-select`); + await ffpeDropdown.click(); + const answer = this._ffpe as string; + await ffpeDropdown.filter({ hasText: answer }).click(); + } + + public set blocksWithTumor(answer: string) { + this._blocksWithTumor = answer; + } + + public get blocksWithTumor(): string { + return this._blocksWithTumor; + } + + public async fillBlocksWithTumorField(currentRow: Locator, tumorDescription: string): Promise { + this._blocksWithTumor = tumorDescription; + const blocksWithTumorField = currentRow.locator(`//td[${OncHistoryColumn.OS2_BLOCKS_WITH_TUMOR}]//mat-form-field//input`); + await blocksWithTumorField.fill(this._blocksWithTumor); + } + + public set tumorSize(sizeDescription: string) { + this._tumorSize = sizeDescription; + } + + public get tumorSize(): string { + return this._tumorSize; + } + + public async fillTumorSizeField(currentRow: Locator, studyName: StudyEnum, tumorDescription: string): Promise { + let tumorSizeField; + this._tumorSize = tumorDescription; + switch (studyName) { + case StudyEnum.OSTEO2: + tumorSizeField = currentRow.locator(`//td[${OncHistoryColumn.OS2_TUMOR_SIZE}]//textarea`); + break; + case StudyEnum.LMS: + tumorSizeField = currentRow.locator(`//td[${OncHistoryColumn.LMS_TUMOR_SIZE}]//textarea`); + break; + default: + //Throw error since only OS2 and LMS have a tumor size field + throw new Error(`Study name ${studyName} was used in fillTumorSizeField(). Only OS2 or LMS should be used!`); + } + await tumorSizeField.fill(this._tumorSize); + } + + public set blockToRequest(blockDescription: string) { + this._blocksToRequest = blockDescription; + } + + public get blockToRequest(): string { + return this._blocksToRequest; + } + + public async fillBlockToRequestField(currentRow: Locator, requestDescription: string): Promise { + this._blocksToRequest = requestDescription; + const blockToRequestField = currentRow.locator(`//td[${OncHistoryColumn.LMS_BLOCKS_TO_REQUEST}]//mat-form-field//input`); + await blockToRequestField.fill(this._blocksToRequest); + } + + public set necrosis(necrosisDescription: string) { + this._necrosis = necrosisDescription; + } + + public get necrosis(): string { + return this.necrosis; + } + + public async fillNecrosisField(currentRow: Locator, studyName: StudyEnum, necrosisDescription: string): Promise { + let necrosisField; + this._necrosis = necrosisDescription; + switch (studyName) { + case StudyEnum.OSTEO2: + necrosisField = currentRow.locator(`//td[${OncHistoryColumn.OS2_NECROSIS}]//mat-form-field//input`); + break; + case StudyEnum.LMS: + necrosisField = currentRow.locator(`//td[${OncHistoryColumn.LMS_NECROSIS}]//mat-form-field//input`); + break; + default: + //Throw error since only OS2 and LMS have a tumor size field + throw new Error(`Study name ${studyName} was used in fillNecrosisField(). Only OS2 or LMS should be used!`); + } + await necrosisField.fill(this._necrosis); + } + + public set viableTumor(tumorDescription: string) { + this._viableTumor = tumorDescription; + } + + public get viableTumor(): string { + return this._viableTumor; + } + + public async fillViableTumorField(currentRow: Locator, viableTumorDescription: string): Promise { + this._viableTumor = viableTumorDescription; + const viableTumorField = currentRow.locator(`//td[${OncHistoryColumn.VIABLE_TUMOR}]//mat-form-field//input`); + await viableTumorField.fill(this._viableTumor); + } + + public set slidesToRequest(requestDescription: string) { + this._slidesToRequest = requestDescription; + } + + public get slidesToRequest(): string { + return this._slidesToRequest; + } + + public async fillSlidesToRequestField(currentRow: Locator, slides: string): Promise { + this._slidesToRequest = slides; + const slidesToRequestField = currentRow.locator(`//td[${OncHistoryColumn.LMS_SLIDES_TO_REQUEST}]//mat-form-field//input`); + await slidesToRequestField.fill(this._slidesToRequest); + } + + public set facilityWhereSampleWasReviewed(facilityName: string) { + this._facilityWhereSampleWasReviewed = facilityName; + } + + public get facilityWhereSampleWasReceived(): string { + return this._facilityWhereSampleWasReviewed; + } + + public async fillFacilityWhereSampleWasReceivedField(currentRow: Locator, facility: string): Promise { + this._facilityWhereSampleWasReviewed = facility; + const facilityField = currentRow.locator(`//td[${OncHistoryColumn.LMS_FACILITY_WHERE_SAMPLE_REVIEWED}]//mat-form-field//input`); + await facilityField.fill(this._facilityWhereSampleWasReviewed); + } + + public set slidesTotal(slideDescription: string) { + this._slidesTotal = slideDescription; + } + + public get slidesTotal(): string { + return this._slidesTotal; + } + + public async fillSlidesTotalField(currentRow: Locator, slidesDescription: string): Promise { + this._slidesTotal = slidesDescription; + const slidesTotalField = currentRow.locator(`//td[${OncHistoryColumn.LMS_SLIDES_TOTAL}]//mat-form-field//input`); + await slidesTotalField.fill(this._slidesToRequest); + } + + public set treatmentEffect(effectOfTreatment: string) { + this._treatmentEffect = effectOfTreatment; + } + + public get treatmentEffect(): string { + return this._treatmentEffect; + } + + public async fillTreatmentEffectField(currentRow: Locator, treatmentEffect: string): Promise { + this._treatmentEffect = treatmentEffect; + const treatmentEffecrField = currentRow.locator(`//td[${OncHistoryColumn.LMS_TREATMENT_EFFECT}]//mat-form-field//input`); + await treatmentEffecrField.fill(this._treatmentEffect); + } + + public async inputOncHistory(opts: { + dateOfPX: string, + accessionNumber: string, + facility: string, + phone: string, + fax: string, + typeOfPX?: string, + locationOfPX?: string, + histology?: string, + destructionPolicy?: string + }): Promise { + const { dateOfPX, accessionNumber, facility, phone, fax, typeOfPX = '', locationOfPX = '', histology = '', destructionPolicy = '' } = opts; + const row = await this.getNextAvailableRow(); + await this.fillDateOfPXField(row, dateOfPX); + await this.fillAccessionNumberField(row, accessionNumber); + await this.fillFacilityNameField(row, facility); + await this.fillFacilityPhoneNumberField(row, phone); + await this.fillFacilityFaxNumberField(row, fax); + await this.fillTypeOfPXField(row, typeOfPX); + await this.fillLocationOfPXField(row, locationOfPX); + await this.fillHistologyField(row, histology); + await this.fillDestructionPolicyField(row, destructionPolicy); + await this.setOncHistoryDetailID(row); //Ids are given to each row afer at least 1 input in any field in that row + } + + public async inputOncHistoryOsteo(opts: { + dateOfPX: string, + accessionNumber: string, + facility: string, + phone: string, + fax: string, + typeOfPX?: string, + locationOfPX?: string, + histology?: string, + destructionPolicy?: string, + blocksWithTumor?: string, + tumorSize?: string, + localControl?: GeneralAnswer, + necrosis?: string, + viableTumor?: string, + ffpe?: GeneralAnswer, + decalcification?: Decalcification, + blocksToRequest?: string + }): Promise { + const row = await this.getNextAvailableRow(); + const { + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX = '', + locationOfPX = '', + histology = '', + destructionPolicy = '', + blocksWithTumor = '', + tumorSize = '', + localControl = GeneralAnswer.BLANK, + necrosis = '', + viableTumor = '', + ffpe = GeneralAnswer.BLANK, + decalcification = Decalcification.BLANK, + blocksToRequest = '' + } = opts; + await this.fillDateOfPXField(row, dateOfPX); + await this.fillAccessionNumberField(row, accessionNumber); + await this.fillFacilityNameField(row, facility); + await this.fillFacilityPhoneNumberField(row, phone); + await this.fillFacilityFaxNumberField(row, fax); + await this.fillTypeOfPXField(row, typeOfPX); + await this.fillLocationOfPXField(row, locationOfPX); + await this.fillHistologyField(row, histology); + await this.fillDestructionPolicyField(row, destructionPolicy); + await this.fillBlocksWithTumorField(row, blocksWithTumor); + await this.fillTumorSizeField(row, StudyEnum.OSTEO2, tumorSize); + await this.fillLocalControlDropdown(row, localControl); + await this.fillNecrosisField(row, StudyEnum.OSTEO2, necrosis); + await this.fillViableTumorField(row, viableTumor); + await this.fillFFPEDropdown(row, ffpe); + await this.fillDecalcificationDropdown(row, decalcification); + await this.fillBlockToRequestField(row, blocksToRequest); + await this.setOncHistoryDetailID(row); //Ids are given to each row afer at least 1 input in any field in that row + } + + public async inputOncHistoryLMS(opts: { + dateOfPX: string, + accessionNumber: string, + facility: string, + phone: string, + fax: string, + typeOfPX?: string, + locationOfPX?: string, + histology?: string, + destructionPolicy?: string, + tumorSize?: string, + slidesToRequest?: string, + facilityWhereSampleWasReviewed?: string, + slidesTotal?: string, + blocksToRequest?: string, + treatmentEffect?: string, + viableTumor?: string, + necrosis?: string + }): Promise { + const row = await this.getNextAvailableRow(); + const { + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX = '', + locationOfPX = '', + histology = '', + destructionPolicy = '', + tumorSize = '', + slidesToRequest = '', + facilityWhereSampleWasReviewed = '', + slidesTotal = '', + blocksToRequest = '', + treatmentEffect = '', + viableTumor = '', + necrosis = '' + } = opts; + await this.fillDateOfPXField(row, dateOfPX); + await this.fillAccessionNumberField(row, accessionNumber); + await this.fillFacilityNameField(row, facility); + await this.fillFacilityPhoneNumberField(row, phone); + await this.fillFacilityFaxNumberField(row, fax); + await this.fillTypeOfPXField(row, typeOfPX); + await this.fillLocationOfPXField(row, locationOfPX); + await this.fillHistologyField(row, histology); + await this.fillDestructionPolicyField(row, destructionPolicy); + await this.fillTumorSizeField(row, StudyEnum.LMS, tumorSize); + await this.fillSlidesToRequestField(row, slidesToRequest); + await this.fillFacilityWhereSampleWasReceivedField(row, facilityWhereSampleWasReviewed); + await this.fillSlidesTotalField(row, slidesTotal); + await this.fillBlockToRequestField(row, blocksToRequest); + await this.fillTreatmentEffectField(row, treatmentEffect); + await this.fillViableTumorField(row, viableTumor); + await this.fillNecrosisField(row, StudyEnum.LMS, necrosis); + await this.setOncHistoryDetailID(row); //Ids are given to each row afer at least 1 input in any field in that row + } + + /* Basic onc history related functions */ + /** + * Clicks the checkbox that appears in an onc history row as soon as the row is set to Request status + */ + public async requestOncHistoryDetail(): Promise { + const checkbox = await this.getRequestOncHistoryDetailCheckbox(); + await checkbox.check(); + } + + public async accessTissueRequestPage(): Promise { + const button = this.getTissueRequestPageButton(); + await button.click(); + } + + public async setOncHistoryRequestStatus(status: OncHistoryRequestStatus): Promise { + const id = this.assertOncHistoryIDIsAssigned(); + const statusDropdownOptions = this.page.locator(`//tr[@id=${id}]//td[contains(@class, 'request-td')]//mat-select`); + await statusDropdownOptions.filter({ hasText: status }).click(); + } + + public async getOncHistoryRequestStatus(): Promise { + const id = this.assertOncHistoryIDIsAssigned(); + const statusDropdownOptions = this.page.locator(`//tr[@id=${id}]//td[contains(@class, 'request-td')]//mat-select`); + const status = await statusDropdownOptions.innerText(); + return status; + } + + /* Locators */ + + + /* Onc History specific helper methods */ + private async getNextAvailableRow(): Promise { + const availableRows = this.page.locator('//app-onc-history-detail//tbody//tr').all(); + const amountOfRows = (await availableRows).length; + const row = this.page.locator(`//app-onc-history-detail//tbody//tr[${amountOfRows}]`); //Gets the latest row that is likely empty & ready to go + return row; + } + + private async getRequestOncHistoryDetailCheckbox(): Promise { + const id = this.assertOncHistoryIDIsAssigned(); + + //Checkbox only appears if the onc history status is set to Request + const requestStatus = await this.getOncHistoryRequestStatus(); + expect(requestStatus, `ERROR: Onc History id ${id}'s status is ${requestStatus}, not Request; Onc History request checkbox cannot be seen`). + toBe(OncHistoryRequestStatus.REQUEST as string); + + const checkbox = this.page.locator(`//tr[@id='${id}']//button[@tooltip='Tissue information']/preceding-sibling::mat-checkbox`); + return checkbox; + } + + private getTissueRequestPageButton(): Locator { + const id = this.assertOncHistoryIDIsAssigned(); + const button = this.page.locator(`//tr[@id='${id}']//button[@tooltip='Tissue information']`); + return button; + } + + private assertOncHistoryIDIsAssigned(): number { + //First check to make sure that the onc history deatil has an assigned id + const id = this._oncHistoryID; + expect(id, 'ERROR: Onc History id is null').not.toBeNull(); + return id; + } +} diff --git a/playwright-e2e/dsm/component/tabs/oncHistoryTab.ts b/playwright-e2e/dsm/component/tabs/oncHistoryTab.ts new file mode 100644 index 0000000000..c93fb86677 --- /dev/null +++ b/playwright-e2e/dsm/component/tabs/oncHistoryTab.ts @@ -0,0 +1,146 @@ +import {Locator, Page} from '@playwright/test'; +import OncHistoryDetailRow from './oncHistoryDetailRow'; +import { Decalcification, GeneralAnswer } from './enums/oncHistory-enum'; +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; + +export default class OncHistoryTab { + constructor(private readonly page: Page) {} + + public get addOncHistory() { + const page = this.page; + let oncHistoryDetails: OncHistoryDetailRow[]; + + return new class { + async createOncHistoryDetail(opts: { + study: StudyEnum, + dateOfPX: string, + accessionNumber: string, + facility: string, + phone: string, + fax: string, + typeOfPX?: string, + locationOfPX?: string, + histology?: string, + destructionPolicy?: string, + localControl?: GeneralAnswer, + decalcification?: Decalcification, + ffpe?: GeneralAnswer, + blocksWithTumor?: string, + tumorSize?: string, + blocksToRequest?: string, + necrosis?: string, + viableTumor?: string, + slidesToRequest?: string, + facilityWhereSampleWasReviewed?: string, + slidesTotal?: string, + treatmentEffect?: string + }): Promise { + const { + study, + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX = '', + locationOfPX = '', + histology = '', + destructionPolicy = '', + localControl = GeneralAnswer.BLANK, + decalcification = Decalcification.BLANK, + ffpe = GeneralAnswer.BLANK, + blocksWithTumor = '', + tumorSize = '', + blocksToRequest = '', + necrosis = '', + viableTumor = '', + slidesToRequest = '', + facilityWhereSampleWasReviewed = '', + slidesTotal = '', + treatmentEffect = '' + } = opts; + const row = new OncHistoryDetailRow(page); + switch (study) { + case StudyEnum.OSTEO2: + //Handle OS2 (CMI Clinical Study) + await row.inputOncHistoryOsteo({ + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX, + locationOfPX, + histology, + destructionPolicy, + blocksWithTumor, + tumorSize, + localControl, + necrosis, + viableTumor, + ffpe, + decalcification, + blocksToRequest + }); + break; + case StudyEnum.LMS: + //Handle LMS (CMI Clinical Study) + await row.inputOncHistoryLMS({ + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX, + locationOfPX, + histology, + destructionPolicy, + tumorSize, + slidesToRequest, + facilityWhereSampleWasReviewed, + slidesTotal, + blocksToRequest, + treatmentEffect, + viableTumor, + necrosis + }); + break; + default: + //Handle CMI Research studies + await row.inputOncHistory({ + dateOfPX, + accessionNumber, + facility, + phone, + fax, + typeOfPX, + locationOfPX, + histology, + destructionPolicy, + }); + break; + } + } + } + } + + public async clickDownloadRequestDocuments(): Promise { + const documentsButton = this.getDownloadRequestDocumentsButton(); + await documentsButton.click(); + } + + public async clickDownloadPDFBundle(): Promise { + const bundleButton = this.getDownloadPDFBundleButton(); + await bundleButton.click(); + } + + /* Locators */ + + public getDownloadRequestDocumentsButton(): Locator { + return this.page.getByRole('button', { name: 'Download Request Documents' }); + } + + public getDownloadPDFBundleButton(): Locator { + return this.page.getByRole('button', { name: 'Download PDF Bundle' }); + } +} diff --git a/playwright-e2e/dsm/pages/participant-list-page.ts b/playwright-e2e/dsm/pages/participant-list-page.ts index ce2493ee8c..f82a432f23 100644 --- a/playwright-e2e/dsm/pages/participant-list-page.ts +++ b/playwright-e2e/dsm/pages/participant-list-page.ts @@ -8,6 +8,8 @@ import Checkbox from 'dss/component/checkbox'; import { waitForNoSpinner, waitForResponse } from 'utils/test-utils'; import { Filters } from 'dsm/component/filters/filters'; import { ParticipantListTable } from 'dsm/component/tables/participant-list-table'; +import { KitTypeEnum } from 'dsm/component/kitType/enums/kitType-enum'; +import { CustomViewColumns, EnrollmentStatus, KitStatus, ParticipantColumns } from 'dsm/component/filters/sections/search/search-enums'; export default class ParticipantListPage { private readonly PAGE_TITLE: string = 'Participant List'; @@ -227,6 +229,44 @@ export default class ParticipantListPage { await waitForNoSpinner(this.page); } + /** + * Searches for a participant that has not had a specific kit type sent out + * @param kitType the kit type being searched for to make sure the participant has no history e.g. they have not had a saliva kit sent or received + * @returns the short id of the matched participant + */ + async findParticipantWithoutSentKitType(kitType: KitTypeEnum): Promise { + const participantListTable = this.participantListTable; + + const searchPanel = this.filters.searchPanel; + await searchPanel.searchForParticipantsWithEnrollmentStatus([EnrollmentStatus.ENROLLED]); + expect(await participantListTable.rowsCount).toBeGreaterThanOrEqual(1); + + const customizeViewPanel = this.filters.customizeViewPanel; + await customizeViewPanel.open(); + expect(await customizeViewPanel.isColumnGroupDisplayed(CustomViewColumns.SAMPLE), 'ERROR: Sample Columns group is not displayed').toBe(true); + + await customizeViewPanel.deselectColumns('Participant Columns', ['Status']); //To reduce confusion when Sample Columns -> Status is added and filtered + await customizeViewPanel.selectColumns('Sample Columns', ['Sample Type', 'Status']); + await searchPanel.open(); + let validTestParticipantShortID = ''; + + switch (kitType) { + case KitTypeEnum.SALIVA: { + //Many study groups e.g. CMI have a saliva kit that gets automatically created and be found in Kits w/o Label; usually has Waiting on GP status + validTestParticipantShortID = await searchPanel.searchForParticipantWithUnsentSalivaKit(); + break; + } + case KitTypeEnum.BLOOD: { + //TODO Fill the case for blood kits; Note: blood kits are not usually automatically created + break; + } + default: { + break; + } + } + return validTestParticipantShortID; + } + async findParticipantForKitUpload(): Promise { const participantListTable = this.participantListTable; diff --git a/playwright-e2e/dsm/pages/participant-page/participant-page.ts b/playwright-e2e/dsm/pages/participant-page/participant-page.ts index d3c3630037..6da6c0a9cc 100644 --- a/playwright-e2e/dsm/pages/participant-page/participant-page.ts +++ b/playwright-e2e/dsm/pages/participant-page/participant-page.ts @@ -3,6 +3,8 @@ 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 { KitUploadInfo } from 'dsm/pages/kitUpload-page/models/kitUpload-model'; +import ContactInformationTab from 'dsm/component/tabs/contactInformationTab'; export default class ParticipantPage { private readonly PAGE_TITLE: string = 'Participant Page'; @@ -93,6 +95,41 @@ export default class ParticipantPage { private async readMainCheckboxValueFor(key: MainInfoEnum) { return await this.page.locator(this.getMainCheckboxValueInfoXPath(key)).isChecked(); } + + /** + * Checks a participant's Contact Information tab in order to make a KitUploadInfo object + * @param shortID participant's short id + * @returns KitUploadInfo object that can be used for kit upload + */ + public async getContactInformation(shortID: string): Promise { + //Check to make sure only the intended participant's contact information is used + const pageShortID = await this.getShortId(); + expect(pageShortID, `ERROR - Wrong participant page: Currently on ${pageShortID}'s participant page, not ${shortID}'s`).toEqual(shortID); + + //Check for Contact Information tab since not all studies have it + const hasContactInformationTab = await this.isTabVisible(TabEnum.CONTACT_INFORMATION); + expect(hasContactInformationTab, `Participant ${shortID} does not have a Contact Information Tab`).toBe(true); + const kitUploadInfo = new KitUploadInfo( + shortID, + await this.getFirstName(), + await this.getLastName(), + ); + + //If participant's address is valid, use that - else just use the test address which is already set + if (hasContactInformationTab) { + const contactInformationTab = await this.clickTab(TabEnum.CONTACT_INFORMATION); + const hasValidAddress = JSON.parse(await contactInformationTab.getValid()); + if (hasValidAddress) { + kitUploadInfo.street1 = await contactInformationTab.getStreet1(); + kitUploadInfo.city = await contactInformationTab.getCity(); + kitUploadInfo.postalCode = await contactInformationTab.getZip(); + kitUploadInfo.state = await contactInformationTab.getState(); + kitUploadInfo.country = await contactInformationTab.getCountry(); + } + } + return kitUploadInfo; + } + /* ---- */ /* Locators */ diff --git a/playwright-e2e/tests/dsm/samplesReceivedFlow/samples-received-first-saliva-kit-and-then-tumor-sample.spec.ts b/playwright-e2e/tests/dsm/samplesReceivedFlow/samples-received-first-saliva-kit-and-then-tumor-sample.spec.ts new file mode 100644 index 0000000000..dd9d0254fc --- /dev/null +++ b/playwright-e2e/tests/dsm/samplesReceivedFlow/samples-received-first-saliva-kit-and-then-tumor-sample.spec.ts @@ -0,0 +1,166 @@ +import { KitTypeEnum } from 'dsm/component/kitType/enums/kitType-enum'; +import { StudyEnum } from 'dsm/component/navigation/enums/selectStudyNav-enum'; +import { Navigation } from 'dsm/component/navigation/navigation'; +import HomePage from 'dsm/pages/home-page'; +import { WelcomePage } from 'dsm/pages/welcome-page'; +import { test } from 'fixtures/dsm-fixture'; +import {login} from 'authentication/auth-dsm'; +import Select from 'dss/component/select'; +import ParticipantListPage from 'dsm/pages/participant-list-page'; +import { StudyNavEnum } from 'dsm/component/navigation/enums/studyNav-enum'; +import { ParticipantListTable } from 'dsm/component/tables/participant-list-table'; +import KitsWithoutLabelPage from 'dsm/pages/kitsInfo-pages/kitsWithoutLabel-page'; +import { SamplesNavEnum } from 'dsm/component/navigation/enums/samplesNav-enum'; +import { KitUploadInfo } from 'dsm/pages/kitUpload-page/models/kitUpload-model'; +import { expect } from '@playwright/test'; +import KitUploadPage from 'dsm/pages/kitUpload-page/kitUpload-page'; +import InitialScanPage from 'dsm/pages/scanner-pages/initialScan-page'; +import { KitsColumnsEnum } from 'dsm/pages/kitsInfo-pages/enums/kitsColumns-enum'; +import FinalScanPage from 'dsm/pages/scanner-pages/finalScan-page'; +import { TabEnum } from 'dsm/component/tabs/enums/tab-enum'; +import OncHistoryTab from 'dsm/component/tabs/oncHistoryTab'; +import { Decalcification, GeneralAnswer } from 'dsm/component/tabs/enums/oncHistory-enum'; + +test.describe('Samples Received Event - Recieved saliva kit first and then tumor sample', () => { + const studies = [StudyEnum.OSTEO2, StudyEnum.LMS]; + let welcomePage: WelcomePage; + let navigation: Navigation; + let homePage: HomePage; + let kitUploadInfo: KitUploadInfo; + + const kitType = KitTypeEnum.SALIVA; + const expectedKitTypes = [KitTypeEnum.SALIVA, KitTypeEnum.BLOOD]; + + test.beforeEach(({ page, request }) => { + navigation = new Navigation(page, request); + welcomePage = new WelcomePage(page); + homePage = new HomePage(page); + }); + + for (const study of studies) { + test(`@${study} - Verify SAMPLES_RECEIVED occurs if the saliva kit is received before the tumor sample`, async ({ page }, testInfo) => { + const testResultDirectory = testInfo.outputDir; + + //Select the study + await new Select(page, { label: 'Select study' }).selectOption(`${study}`); + + //Find a participant who does not have any sent or received saliva or blood kits - who also has at least 1 physician's information in medical release + const participantListPage = await navigation.selectFromStudy(StudyNavEnum.PARTICIPANT_LIST); + await participantListPage.assertPageTitle(); + await participantListPage.waitForReady(); + + const participantShortID = await participantListPage.findParticipantWithoutSentKitType(KitTypeEnum.SALIVA); + console.log(`Planned test participant: ${participantShortID}`); + + //Get the participant information that is needed to do a kit upload + await participantListPage.reloadWithDefaultFilter(); + await participantListPage.filterListByShortId(participantShortID); + + const participantListTable = new ParticipantListTable(page); + const amountOfParticipants = await participantListTable.rowsCount; + expect(amountOfParticipants).toBe(1); //Placement of the participant in the participant list after filtering + + const participantPage = await participantListTable.openParticipantPageAt(amountOfParticipants - 1); //Count starts from zero + kitUploadInfo = await participantPage.getContactInformation(participantShortID); + + //Deactivate existing saliva kit(s) + const kitsWithoutLabelPage = await navigation.selectFromSamples(SamplesNavEnum.KITS_WITHOUT_LABELS); + await kitsWithoutLabelPage.waitForLoad(); + await kitsWithoutLabelPage.assertPageTitle(); + await kitsWithoutLabelPage.selectKitType(kitType); + await kitsWithoutLabelPage.assertCreateLabelsBtn(); + await kitsWithoutLabelPage.assertReloadKitListBtn(); + await kitsWithoutLabelPage.assertTableHeader(); + await kitsWithoutLabelPage.deactivateAllKitsFor(participantShortID); + + //Upload a saliva kit for the participant + const kitUploadPage = await navigation.selectFromSamples(SamplesNavEnum.KIT_UPLOAD); + await kitUploadPage.waitForLoad(); + await kitUploadPage.assertPageTitle(); + await kitUploadPage.assertDisplayedKitTypes(expectedKitTypes); + await kitUploadPage.selectKitType(kitType); + await kitUploadPage.assertBrowseBtn(); + await kitUploadPage.assertUploadKitsBtn(); + await kitUploadPage.uploadFile(kitType, [kitUploadInfo], study, testResultDirectory); + + //Go to Kits w/o Label in order to get the Shipping ID a.k.a DSM Label + await navigation.selectFromSamples(SamplesNavEnum.KITS_WITHOUT_LABELS); + await kitsWithoutLabelPage.waitForLoad(); + await kitsWithoutLabelPage.selectKitType(kitType); + await kitsWithoutLabelPage.search(KitsColumnsEnum.SHORT_ID, participantShortID); + const shippingID = (await kitsWithoutLabelPage.getData(KitsColumnsEnum.SHIPPING_ID)).trim(); + + //Send out the saliva kit - Note: PE-CGS saliva kit flow is: Kit Upload -> Kits w/o Label -> Initial Scan -> Final Scan + const initialScanPage = await navigation.selectFromSamples(SamplesNavEnum.INITIAL_SCAN); + await initialScanPage.assertPageTitle(); + const kitLabel = `kit-${crypto.randomUUID().toString().substring(0, 10)}`; //Saliva PE-CGS kit labels must be 14 chars + await initialScanPage.fillScanPairs([kitLabel, participantShortID]); + await initialScanPage.save(); + + const finalScan = await navigation.selectFromSamples(SamplesNavEnum.FINAL_SCAN); + await finalScan.assertPageTitle(); + await finalScan.fillScanPairs([kitLabel, shippingID]); + await finalScan.save(); + + //Go to the participant's participant page + await navigation.selectFromStudy(StudyNavEnum.PARTICIPANT_LIST); + await participantListPage.filterListByShortId(participantShortID); + await participantListTable.openParticipantPageAt(0); + + //Go into their Onc History tab + await participantPage.clickTab(TabEnum.ONC_HISTORY); + + //Input a new row of onc history data, where at least the following is added: Date of PX + Accession Number; Afterwards change request status to 'Request' + const oncHistoryTab = new OncHistoryTab(page); + /*await oncHistoryTab.addOncHistory.createOncHistoryDetail({ + study, + dateOfPX: '09/08/2023', + accessionNumber: 'AccessionExampleNumber123', + facility: 'MGH', + phone: '123-456-7890', + fax: '111-222-3333', + });*/ + await oncHistoryTab.addOncHistory.createOncHistoryDetail({ + study, + dateOfPX: '09/08/2023', + accessionNumber: 'AccessionExampleNumber123', + facility: 'MGH', + phone: '123-456-7890', + fax: '111-222-3333', + typeOfPX: 'Type of PX Playwright Example', + locationOfPX: 'Unknown location', + histology: 'Histology example', + destructionPolicy: 'indefinitely', + localControl: GeneralAnswer.YES, + decalcification: Decalcification.NITRIC_ACID, + ffpe: GeneralAnswer.UNKNOWN, + blocksWithTumor: 'Blocks with Tumor Description', + tumorSize: 'Description of tumor size', + blocksToRequest: 'Description of blocks to request', + necrosis: 'Unknown necrosis percentage', + viableTumor: 'Unknown amount of viable tumor', + slidesToRequest: 'Description of slides to request', + facilityWhereSampleWasReviewed: 'Tufts', + slidesTotal: 'Description of total amount of slides', + treatmentEffect: 'Description of extensive treatment effect' + }); + + //Verify the Request checkbox appears + + //Verify that Download Request Documents button is interactable and can be used + + //Go into Onc History Detail a.k.a Tissue Information a.k.a Tissue Request page + + //Verify the onc history's Request Status is 'Sent' + + //Make sure the following details are added: + //Fax Sent, Tissue Received, Gender, Materials Received (note: only use USS or Scrolls), SM-IDs, Tumor Collaborator Sample ID, Date sent to GP + + //Receive the saliva kit + + //Receive the tumor sample + + //Verify the Germline Consent Addendum activity has been created for the participant + }); + } +});