diff --git a/.circleci/export-playwright-env.sh b/.circleci/export-playwright-env.sh index 4b6da62a5f..61a51e2acd 100755 --- a/.circleci/export-playwright-env.sh +++ b/.circleci/export-playwright-env.sh @@ -9,6 +9,12 @@ echo "export SITE_PASSWORD=$sitePwd" >> playwright-env/envvars export bspToken=$(vault read --format=json secret/pepper/test/v1/e2e | jq -r ".data.bsp | .[] | select(.env==\"$ENV\") | .token") echo "export BSP_TOKEN=$bspToken" >> playwright-env/envvars +export pubsubTopicName=$(vault read --format=json secret/pepper/test/v1/e2e | jq -r ".data.mercury | .[] | select(.env==\"$ENV\") | .pubsubTopicName") +echo "export MERCURY_PUBSUB_TOPIC_NAME=$pubsubTopicName" >> playwright-env/envvars + +export pubsubProjectId=$(vault read --format=json secret/pepper/test/v1/e2e | jq -r ".data.mercury | .[] | select(.env==\"$ENV\") | .pubsubProjectId") +echo "export MERCURY_PUBSUB_PROJECT_ID=$pubsubProjectId" >> playwright-env/envvars + export dsmUser1=$(vault read --format=json secret/pepper/test/v1/e2e | jq -r ".data.users | .[] | select(.app==\"dsm\") | .users[0] | .userName") export dsmUser1Password=$(vault read --format=json secret/pepper/test/v1/e2e | jq -r ".data.users | .[] | select(.app==\"dsm\") | .users[0] | .password") echo "export DSM_USER1_EMAIL=$dsmUser1" >> playwright-env/envvars diff --git a/playwright-e2e/.env.sample b/playwright-e2e/.env.sample index d609e69d6c..4b33a5c3fe 100644 --- a/playwright-e2e/.env.sample +++ b/playwright-e2e/.env.sample @@ -4,6 +4,8 @@ SITE_PASSWORD= # PEPPER API_BASE_URL=https://pepper-dev.datadonationplatform.org +MERCURY_PUBSUB_TOPIC_NAME= +MERCURY_PUBSUB_PROJECT_ID= # ATCP ATCP_BASE_URL=https://atcp.dev.datadonationplatform.org diff --git a/playwright-e2e/config/.env.dev b/playwright-e2e/config/.env.dev index ff3c29f60b..a6cbc06927 100644 --- a/playwright-e2e/config/.env.dev +++ b/playwright-e2e/config/.env.dev @@ -4,6 +4,8 @@ SITE_PASSWORD= # PEPPER API_BASE_URL=https://pepper-dev.datadonationplatform.org +MERCURY_PUBSUB_TOPIC_NAME= +MERCURY_PUBSUB_PROJECT_ID= # ATCP ATCP_BASE_URL=https://atcp.dev.datadonationplatform.org diff --git a/playwright-e2e/config/.env.test b/playwright-e2e/config/.env.test index 83fb1f40d3..7fd89d7d64 100644 --- a/playwright-e2e/config/.env.test +++ b/playwright-e2e/config/.env.test @@ -4,6 +4,8 @@ SITE_PASSWORD= # PEPPER API_BASE_URL=https://pepper-test.datadonationplatform.org +MERCURY_PUBSUB_TOPIC_NAME= +MERCURY_PUBSUB_PROJECT_ID= # ATCP ATCP_BASE_URL=https://atcp.test.datadonationplatform.org diff --git a/playwright-e2e/dsm/enums.ts b/playwright-e2e/dsm/enums.ts index bccd77d1b5..2adddaf0fb 100644 --- a/playwright-e2e/dsm/enums.ts +++ b/playwright-e2e/dsm/enums.ts @@ -11,16 +11,17 @@ export enum KitSampleType { } export enum DataFilter { - NOT_EMPTY = 'Not Empty', - EXACT_MATCH = 'Exact Match', - RANGE = 'Range', EMPTY = 'Empty', ENROLLED = 'Enrolled', - REQUEST = 'Request', - RECEIVED = 'Received', + EXACT_MATCH = 'Exact Match', + LOST_TO_FOLLOWUP = 'Lost to Followup', NO = 'No', - YES = 'Yes', + NOT_EMPTY = 'Not Empty', + RANGE = 'Range', + RECEIVED = 'Received', REGISTERED = 'Registered', + REQUEST = 'Request', + YES = 'Yes', } export enum FieldSettingInputType { @@ -787,3 +788,24 @@ export enum ParticipantListPageOptions { SAVE_CURRENT_VIEW = 'Save Current View', SAVED_FILTERS = 'Saved Filters', } + +export enum SequencingOrderColumn { + SAMPLE_TYPE = 'Sample Type', + SAMPLE = 'Sample', + SAMPLE_STATUS = 'Sample Status', + COLLECTION_DATE = 'Collection Date', + LATEST_SEQUENCING_ORDER_DATE = 'Latest Sequencing Order Date', + LATEST_ORDER_STATUS = 'Latest Order Status', + LATEST_ORDER_NUMBER = 'Latest Order Number', + LATEST_PDO_NUMBER = 'Latest PDO Number', +} + +export enum ClinicalOrdersColumn { + SHORT_ID = 'Short ID', + SAMPLE_TYPE = 'Sample Type', + SAMPLE = 'Sample', + ORDER_NUMBER = 'Order Number', + ORDER_DATE = 'Order Date', + STATUS = 'Status', + STATUS_DETAIL = 'Status Detail' +} diff --git a/playwright-e2e/dsm/pages/clinical-orders-page.ts b/playwright-e2e/dsm/pages/clinical-orders-page.ts index d28eddeb31..5ee6749d5a 100644 --- a/playwright-e2e/dsm/pages/clinical-orders-page.ts +++ b/playwright-e2e/dsm/pages/clinical-orders-page.ts @@ -2,20 +2,30 @@ import { expect, Locator, Page } from '@playwright/test'; export default class ClinicalOrdersPage { protected PAGE_TITLE = 'Clinical Orders'; + private readonly DOWNLOAD_INFO = 'Download list includes additional fields "Order Message & Status Message"'; constructor(private readonly page: Page) {} public async waitForReady(): Promise { const downloadListButton = this.getDownloadListButton(); const reloadListButton = this.getReloadListButton(); + const downloadHelpText = this.getDownloadHelpText(); const clinicalOrdersTable = await this.getClinicalOrdersTable(); const amountOfClinicalOrders = clinicalOrdersTable.length; await expect(downloadListButton, 'Clinical Orders Page -> Download List button is not visible').toBeVisible(); await expect(reloadListButton, 'Clinical Orders Page -> Reload List button is not visible').toBeVisible(); + await expect(downloadHelpText, `Clinical Orders Page -> help text: "${this.DOWNLOAD_INFO}" is not visible`).toBeVisible(); expect.soft(amountOfClinicalOrders).toBeGreaterThanOrEqual(1); //Just in case there's times where there aren't clinical orders } + public getClinicalOrderRow(opts: { sampleType: 'Normal' | 'Tumor', orderNumber: string }): Locator { + const { sampleType, orderNumber } = opts; + return this.page.locator( + `//app-clinical-page//td[contains(text(), '${orderNumber}')]/preceding-sibling::td[contains(text(), '${sampleType}')]/parent::tr` + ); + } + /* Locators */ private getDownloadListButton(): Locator { return this.page.getByRole('button', { name: 'Download list' }); @@ -28,4 +38,8 @@ export default class ClinicalOrdersPage { private async getClinicalOrdersTable(): Promise { return this.page.locator('//table//tbody//tr').all(); } + + private getDownloadHelpText(): Locator { + return this.page.getByText(this.DOWNLOAD_INFO); + } } diff --git a/playwright-e2e/dsm/pages/participant-list-page.ts b/playwright-e2e/dsm/pages/participant-list-page.ts index c5c037cd6b..f3755e0ad3 100644 --- a/playwright-e2e/dsm/pages/participant-list-page.ts +++ b/playwright-e2e/dsm/pages/participant-list-page.ts @@ -730,4 +730,14 @@ export default class ParticipantListPage extends DsmPageBase { const { optionName } = opts; return this.page.locator(`//text()[normalize-space()='${optionName}']/preceding-sibling::button`); } + + public async refreshParticipantListUsingShortID(opts: { ID: string }): Promise { + const { ID } = opts; + const searchPanel = this.filters.searchPanel; + await searchPanel.open(); + const shortIDField = this.page.locator(`//app-filter-column//input[contains(@data-placeholder, 'Short ID')]`); + await expect(shortIDField, 'Search Panel -> Short ID column is not visible, it may need to be added to the Participant List').toBeVisible(); + await searchPanel.text(Label.SHORT_ID, { textValue: ID }); + await searchPanel.search(); + } } diff --git a/playwright-e2e/dsm/pages/tablist/sequencing-order-tab.ts b/playwright-e2e/dsm/pages/tablist/sequencing-order-tab.ts index 97f7e16d22..6444b6084b 100644 --- a/playwright-e2e/dsm/pages/tablist/sequencing-order-tab.ts +++ b/playwright-e2e/dsm/pages/tablist/sequencing-order-tab.ts @@ -1,12 +1,14 @@ import { Locator, Page, expect } from '@playwright/test'; -import { getDate } from 'utils/date-utils'; +import { getDate, getDateinISOFormat, getToday } from 'utils/date-utils'; import TabBase from './tab-base'; -import { Tab } from 'dsm/enums'; +import { SequencingOrderColumn, Tab } from 'dsm/enums'; +import { getColumnHeaderIndex } from 'utils/test-utils'; export default class SequeuncingOrderTab extends TabBase { private readonly SAMPLE_ROW_XPATH = '//app-sequencing-order//tr'; private readonly DATE_FIELD_XPATH = `//input[@data-placeholder='mm/dd/yyyy']`; private readonly NOT_ELIGIBLE_DUE_TO_RESIDENCE = `Error: Participant lives in New York or Canada and is not eligible for clinical sequencing`; + private readonly PLACE_CLINICAL_ORDER_MODAL_TEXT = `Are you sure you want to place a clinical sequencing order for the following samples:`; constructor(page: Page) { super(page, Tab.SEQUENCING_ORDER); @@ -38,11 +40,35 @@ export default class SequeuncingOrderTab extends TabBase { await expect(placeOrderButton).not.toBeVisible(); } + public async assertPlaceOrderButtonDisplayed(): Promise { + const placeOrderButton = this.getPlaceOrderButton(); + await placeOrderButton.scrollIntoViewIfNeeded(); + await expect(placeOrderButton, 'The Place Order button is not visible to the current DSM user').toBeVisible(); + } + public async assertParticipantNotEligibleForClinicalSequencing(): Promise { const validationMessage = this.page.getByText(this.NOT_ELIGIBLE_DUE_TO_RESIDENCE); await expect(validationMessage).toBeVisible(); } + public async assertClinicalOrderModalDisplayed(): Promise { + const placeOrderModal = this.page.locator(`//div[contains(text(), '${this.PLACE_CLINICAL_ORDER_MODAL_TEXT}')]`); + await placeOrderModal.scrollIntoViewIfNeeded(); + await expect(placeOrderModal, 'Place Order modal is not visible - clinical order cannot be placed').toBeVisible(); + } + + public async closeClinicalOrderModal(): Promise { + const button = this.page.locator(`//div[@class='modal-content']/div[@class='modal-footer']//button[normalize-space(text())='Close']`); + await expect(button, 'Clinical Order modal -> [Close] button is not visible').toBeVisible(); + await button.click(); + } + + public async submitClinicalOrder(): Promise { + const button = this.page.locator(`//div[@class='modal-content']/div[@class='modal-footer']//button[normalize-space(text())='Submit']`); + await expect(button, 'Clinical Order modal -> [Submit] button is not visible').toBeVisible(); + await button.click(); + } + public async fillAvailableCollectionDateFields(opts: {canPlaceClinicalOrder?: boolean}): Promise { const { canPlaceClinicalOrder = true } = opts; const collectionDateIndex = canPlaceClinicalOrder ? 5 : 4; //Index depending on whether test user has permission to place clinical order @@ -95,6 +121,25 @@ export default class SequeuncingOrderTab extends TabBase { return this.toLocator.getByRole('button', { name: 'Place order' }); } + public async selectSampleCheckbox(sample: Locator): Promise { + const checkbox = sample.locator('//mat-checkbox'); + await checkbox.click(); + } + + public async fillCollectionDateIfNeeded(normalSample: Locator): Promise { + const collectionDateColumnIndex = await getColumnHeaderIndex(SequencingOrderColumn.COLLECTION_DATE, this.page); + const unfilledCollectionDateColumn = normalSample.locator(`//td[${collectionDateColumnIndex}]/app-field-datepicker//input`); + if (await unfilledCollectionDateColumn.isVisible()) { + await normalSample.locator(`//td[${collectionDateColumnIndex}]//button[normalize-space(text())='Today']`).click(); + + //Assert that the correct date was inputted + const today = getToday(); + const collectionDateInISOFormat = getDateinISOFormat(today); + const collectionDateColumn = normalSample.locator(`//td[${collectionDateColumnIndex}]`); + await expect(collectionDateColumn).toHaveText(collectionDateInISOFormat); + } + } + private getCheckboxOfSample(sample: Locator): Locator { return sample.locator('//mat-checkbox'); } diff --git a/playwright-e2e/package-lock.json b/playwright-e2e/package-lock.json index 54fe27bd61..2f89888a6f 100644 --- a/playwright-e2e/package-lock.json +++ b/playwright-e2e/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@google-cloud/local-auth": "^2.1.1", + "@google-cloud/pubsub": "^4.7.1", "@googleapis/gmail": "^1.1.1", "@types/lodash": "^4.14.191", "@types/node": "^20.11.5", @@ -136,6 +137,155 @@ "node": ">=12.0.0" } }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/precise-date": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/precise-date/-/precise-date-4.0.0.tgz", + "integrity": "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/pubsub": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-4.7.1.tgz", + "integrity": "sha512-g6RN//1WjEg5pUqAT1xJZDFmhC5nitFVHYyS52THSl7VKubWv+T9cHkeHp/+cc1HDUbkMRjs5hwVSoa0bRR+8Q==", + "license": "Apache-2.0", + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/precise-date": "^4.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "@opentelemetry/api": "~1.9.0", + "@opentelemetry/semantic-conventions": "~1.26.0", + "arrify": "^2.0.0", + "extend": "^3.0.2", + "google-auth-library": "^9.3.0", + "google-gax": "^4.3.3", + "heap-js": "^2.2.0", + "is-stream-ended": "^0.1.4", + "lodash.snakecase": "^4.1.1", + "p-defer": "^3.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/pubsub/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@googleapis/gmail": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@googleapis/gmail/-/gmail-1.2.0.tgz", @@ -147,6 +297,37 @@ "node": ">=12.0.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.2.tgz", + "integrity": "sha512-DWp92gDD7/Qkj7r8kus6/HCINeo3yPZWZ3paKgDgsbKbSpoxKg1yvN8xe2Q8uE3zOsPe3bX8FQX2+XValq2yTw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -180,6 +361,16 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -215,6 +406,24 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.26.0.tgz", + "integrity": "sha512-U9PJlOswJPSgQVPI+XEuNLElyFWkb0hAiMg+DExD9V0St03X2lPHGMdxMY/LrVmoukuIpXJ12oyrOtEZ4uXFkw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.41.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.0.tgz", @@ -230,6 +439,70 @@ "node": ">=16" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", @@ -242,6 +515,15 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/adm-zip": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz", @@ -251,6 +533,12 @@ "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, "node_modules/@types/file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.5.tgz", @@ -274,6 +562,12 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, "node_modules/@types/mailparser": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.1.tgz", @@ -292,12 +586,44 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", @@ -492,6 +818,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -551,7 +889,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -560,7 +897,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -749,12 +1085,13 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, "node_modules/axios": { - "version": "1.6.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", - "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -811,12 +1148,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -869,11 +1207,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -884,8 +1235,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1121,6 +1471,18 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1138,6 +1500,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/encoding-japanese": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", @@ -1146,6 +1514,15 @@ "node": ">=8.10.0" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1250,6 +1627,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1550,6 +1936,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1634,10 +2029,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1682,9 +2078,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -1692,6 +2088,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1810,6 +2207,15 @@ "node": ">=12" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -1949,6 +2355,113 @@ "node": ">=12" } }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-gax/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/google-gax/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/google-p12-pem": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", @@ -2126,6 +2639,15 @@ "he": "bin/he" } }, + "node_modules/heap-js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/heap-js/-/heap-js-2.5.0.tgz", + "integrity": "sha512-kUGoI3p7u6B41z/dp33G6OaL7J4DRqRYwVmeIlwLClx7yaaAy7hoDExnuejTKtuDwfcatGmddHDEOjf6EyIxtQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/html-to-text": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", @@ -2159,6 +2681,20 @@ "entities": "^4.4.0" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -2243,8 +2779,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.5", @@ -2364,6 +2899,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2393,6 +2937,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2460,6 +3005,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "license": "MIT" + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -2682,27 +3233,50 @@ "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==" }, "node_modules/libmime": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz", - "integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.5.tgz", + "integrity": "sha512-nSlR1yRZ43L3cZCiWEw7ali3jY29Hz9CQQ96Oy+sSspYnIP5N54ucOPHqooBsXzwrX1pwn13VUE05q4WmzfaLg==", + "license": "MIT", "dependencies": { - "encoding-japanese": "2.0.0", + "encoding-japanese": "2.1.0", "iconv-lite": "0.6.3", - "libbase64": "1.2.1", - "libqp": "2.0.1" + "libbase64": "1.3.0", + "libqp": "2.1.0" } }, + "node_modules/libmime/node_modules/encoding-japanese": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.1.0.tgz", + "integrity": "sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==", + "license": "MIT", + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/libmime/node_modules/libbase64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", + "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==", + "license": "MIT" + }, + "node_modules/libmime/node_modules/libqp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.0.tgz", + "integrity": "sha512-O6O6/fsG5jiUVbvdgT7YX3xY3uIadR6wEZ7+vy9u7PKHAlSEB6blvC1o5pHBjgsi95Uo0aiBBdkyFecj6jtb7A==", + "license": "MIT" + }, "node_modules/libqp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz", "integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==" }, "node_modules/linkify-it": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", - "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/locate-path": { @@ -2725,12 +3299,30 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2743,19 +3335,30 @@ } }, "node_modules/mailparser": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.6.5.tgz", - "integrity": "sha512-nteTpF0Khm5JLOnt4sigmzNdUH/6mO7PZ4KEnvxf4mckyXYFFhrtAWZzbq/V5aQMH+049gA7ZjfLdh+QiX2Uqg==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.1.tgz", + "integrity": "sha512-RCnBhy5q8XtB3mXzxcAfT1huNqN93HTYYyL6XawlIKycfxM/rXPg9tXoZ7D46+SgCS1zxKzw+BayDQSvncSTTw==", + "license": "MIT", "dependencies": { - "encoding-japanese": "2.0.0", + "encoding-japanese": "2.1.0", "he": "1.2.0", "html-to-text": "9.0.5", "iconv-lite": "0.6.3", - "libmime": "5.2.1", - "linkify-it": "4.0.1", + "libmime": "5.3.5", + "linkify-it": "5.0.0", "mailsplit": "5.4.0", - "nodemailer": "6.9.3", - "tlds": "1.240.0" + "nodemailer": "6.9.13", + "punycode.js": "2.3.1", + "tlds": "1.252.0" + } + }, + "node_modules/mailparser/node_modules/encoding-japanese": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.1.0.tgz", + "integrity": "sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==", + "license": "MIT", + "engines": { + "node": ">=8.10.0" } }, "node_modules/mailsplit": { @@ -2789,12 +3392,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2922,9 +3526,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/nodemailer": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.3.tgz", - "integrity": "sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==", + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } @@ -2937,6 +3542,15 @@ "node": "*" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -3022,7 +3636,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3059,6 +3672,15 @@ "node": ">= 0.8.0" } }, + "node_modules/p-defer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-3.0.0.tgz", + "integrity": "sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3218,6 +3840,42 @@ "node": ">= 0.8.0" } }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3234,6 +3892,15 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -3268,6 +3935,20 @@ } ] }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", @@ -3377,6 +4058,15 @@ "uuid": "bin/uuid" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", @@ -3403,6 +4093,20 @@ "node": ">=4" } }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3625,6 +4329,44 @@ "node": ">=0.10.0" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", @@ -3674,7 +4416,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -3703,6 +4444,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3727,6 +4474,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3734,9 +4497,10 @@ "dev": true }, "node_modules/tlds": { - "version": "1.240.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.240.0.tgz", - "integrity": "sha512-1OYJQenswGZSOdRw7Bql5Qu7uf75b+F3HFBXbqnG/ifHa0fev1XcG+3pJf3pA/KC6RtHQzfKgIf1vkMlMG7mtQ==", + "version": "1.252.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.252.0.tgz", + "integrity": "sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ==", + "license": "MIT", "bin": { "tlds": "bin.js" } @@ -3746,6 +4510,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3929,9 +4694,10 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" }, "node_modules/unbox-primitive": { "version": "1.0.2", @@ -3974,6 +4740,12 @@ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -4062,11 +4834,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/xlsx": { "version": "0.20.1", @@ -4081,11 +4869,47 @@ "node": ">=0.8" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/playwright-e2e/package.json b/playwright-e2e/package.json index 2c2b3499dc..626515c2d0 100644 --- a/playwright-e2e/package.json +++ b/playwright-e2e/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@google-cloud/local-auth": "^2.1.1", + "@google-cloud/pubsub": "^4.7.1", "@googleapis/gmail": "^1.1.1", "@types/lodash": "^4.14.191", "@types/node": "^20.11.5", diff --git a/playwright-e2e/playwright.config.ts b/playwright-e2e/playwright.config.ts index 10b48f4149..9c0b5a69e2 100644 --- a/playwright-e2e/playwright.config.ts +++ b/playwright-e2e/playwright.config.ts @@ -36,7 +36,8 @@ const testConfig: PlaywrightTestConfig = { scale: 'css', // Account for minor difference in text rendering and resolution between headless and headed mode threshold: 1, - maxDiffPixelRatio: 0.5 + maxDiffPixels: 1 + //maxDiffPixelRatio: 0.5 } }, /* Run tests in files in parallel */ diff --git a/playwright-e2e/tests/dsm/clinical-orders/place-clinical-order-in-mercury.spec.ts b/playwright-e2e/tests/dsm/clinical-orders/place-clinical-order-in-mercury.spec.ts new file mode 100644 index 0000000000..2d862133cd --- /dev/null +++ b/playwright-e2e/tests/dsm/clinical-orders/place-clinical-order-in-mercury.spec.ts @@ -0,0 +1,673 @@ +import { expect, Locator } from '@playwright/test'; +import { ParticipantListTable } from 'dsm/component/tables/participant-list-table'; +import { ClinicalOrdersColumn, CustomizeView, DataFilter, Label, SequencingOrderColumn, Tab } from 'dsm/enums'; +import { Navigation, Samples, Study, StudyName } from 'dsm/navigation'; +import ParticipantListPage from 'dsm/pages/participant-list-page'; +import ParticipantPage from 'dsm/pages/participant-page'; +import SequeuncingOrderTab from 'dsm/pages/tablist/sequencing-order-tab'; +import Select from 'dss/component/select'; +import { test } from 'fixtures/dsm-fixture'; +import { getDateEasternTimeZone, getDateMonthAbbreviated, getToday, getTodayInEastCoastDateTimeZone, toLocalTime } from 'utils/date-utils'; +import { getColumnDataForRow, studyShortName } from 'utils/test-utils'; +import { PubSub } from '@google-cloud/pubsub'; +import ClinicalOrdersPage from 'dsm/pages/clinical-orders-page'; +import { logInfo } from 'utils/log-utils'; + +const pecgsStudies = [StudyName.OSTEO2]; //Checking OS2 first - LMS runs into PEPPER-1511 more; TODO: Add LMS once PEPPER-1511 is fixed +const MERCURY_PUBSUB_TOPIC_NAME = process.env.MERCURY_PUBSUB_TOPIC_NAME as string; +const MERCURY_PUBSUB_PROJECT_ID = process.env.MERCURY_PUBSUB_PROJECT_ID as string; + +test.describe.serial('Verify that clinical orders can be placed in mercury @dsm @functional', () => { + const approvedOrderStatus = 'Approved'; + const orderStatusDetail = 'Successfully created order via Playwright'; + let navigation: Navigation; + let shortID: string; + let participantEnrollmentStatus: DataFilter; + let participantPage: ParticipantPage; + let sequencingOrderTab: SequeuncingOrderTab; + let normalSample: Locator; + let tumorSample: Locator; + let sampleNameNormal: string; + let sampleNameTumor: string; + let previousLatestOrderNumberTumor: string; + let latestOrderNumberTumor: string; + let previousLatestOrderNumberNormal: string; + let latestOrderNumberNormal: string; + let orderDateNormal: string; + let orderDateTumor: string; + + for (const study of pecgsStudies) { + test(`${study}: Verify a clinical order can be placed for a participant with Enrolled status`, async ({ page, request }) => { + navigation = new Navigation(page, request); + await new Select(page, { label: 'Select study' }).selectOption(study); + + const participantListPage = await navigation.selectFromStudy(Study.PARTICIPANT_LIST); + await participantListPage.waitForReady(); + const participantListTable = participantListPage.participantListTable; + + await test.step('Chose an enrolled participant that will get a clinical order placed', async () => { + participantEnrollmentStatus = DataFilter.ENROLLED; + shortID = await findParticipantForGermlineSequencing({ + enrollmentStatus: participantEnrollmentStatus, + participantList: participantListPage, + participantTable: participantListTable, + studyName: study + }); + //shortID = 'PTPGLV';//checking LMS ptp + + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + }); + + await test.step('Make sure that the Sequencing Order tab is visible', async () => { + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + }); + + await test.step('Place a clinical order using the Sequencing Order tab', async () => { + normalSample = await sequencingOrderTab.getFirstAvailableNormalSample(); + await sequencingOrderTab.selectSampleCheckbox(normalSample); + sampleNameNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.SAMPLE, page); + await sequencingOrderTab.fillCollectionDateIfNeeded(normalSample); + previousLatestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for normal sample: ${previousLatestOrderNumberNormal}`); + + tumorSample = await sequencingOrderTab.getFirstAvailableTumorSample(); + await sequencingOrderTab.selectSampleCheckbox(tumorSample); + sampleNameTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.SAMPLE, page); + previousLatestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for tumor sample: ${previousLatestOrderNumberTumor}`); + + await sequencingOrderTab.assertPlaceOrderButtonDisplayed(); + await sequencingOrderTab.placeOrder(); + + await sequencingOrderTab.assertClinicalOrderModalDisplayed(); + await sequencingOrderTab.submitClinicalOrder(); + }); + + /* NOTE: Need to go from Participant Page -> Participant List -> (refresh) -> Participant Page in order to see the new info */ + await test.step('Verify that the Latest Sequencing Order Date and Latest Order Number have been updated', async () => { + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + await participantListTable.openParticipantPageAt({ position: 0 }); + + await participantPage.waitForReady(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + //Verify that Latest Sequencing Order Date is the current date and Latest Order Number has received new input + const today = getToday(); + + orderDateNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateNormal.trim()).toBe(today); + + orderDateTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateTumor.trim()).toBe(today); + + latestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberNormal).not.toBe(previousLatestOrderNumberNormal); + + latestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberTumor).not.toBe(previousLatestOrderNumberTumor); + }); + + await test.step('Place an order in mercury', async () => { + const message = createMercuryOrderMessage({ + latestOrderNumber: latestOrderNumberTumor, + orderStatus: approvedOrderStatus, + orderDetails: orderStatusDetail + }); + await placeMercuryOrder(MERCURY_PUBSUB_TOPIC_NAME, message); + }); + + await test.step('Verify that the mercury order was successfully placed', async () => { + //Check that Latest Order Status, Latest PDO Number are not empty for both Normal and Tumor samples - tab needs info refreshed in order to see changes + await expect(async () => { + //Occasionally, a couple of seconds are needed before the info shows up in DSM UI + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + /* Checking the Normal sample's info */ + const latestOrderStatusNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusNormal).toBe(approvedOrderStatus); + const latestPDONumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberNormal).toContain(`Made-by-Playwright-on`); + + /* Checking the Tumor sample's info */ + const latestOrderStatusTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusTumor).toBe(approvedOrderStatus); + const latestPDONumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberTumor).toContain(`Made-by-Playwright-on`); + }).toPass({ + intervals: [20_000], + timeout: 60_000 + }); + }); + + await test.step('Verify that the mercury order can be seen in Samples -> Clinical Orders', async () => { + //Check that info of the newest clinical order can be seen in Clinical Orders page + const clinicalOrderPage = await navigation.selectFromSamples(Samples.CLINICAL_ORDERS); + await clinicalOrderPage.waitForReady(); + + const clinicalOrderNormal = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Normal', orderNumber: latestOrderNumberNormal }); + const clinicalOrderTumor = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Tumor', orderNumber: latestOrderNumberTumor }); + + // Check that each sample's info is present: Short ID, Sample Type, Sample, Order Number, Order Date, Status, Status Detail + /* Normal Sample */ + const normalShortID = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SHORT_ID, page); + expect(normalShortID).toBe(shortID); + + const normalSampleType = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(normalSampleType).toBe('Normal'); + + const normalSampleName = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE, page); + expect(normalSampleName).toBe(sampleNameNormal); + + const normalOrderNumber = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(normalOrderNumber).toBe(latestOrderNumberNormal); + + const normalOrderDate = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_DATE, page); + expect(normalOrderDate).toBe(orderDateNormal); + + const normalOrderStatus = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS, page); + expect(normalOrderStatus).toBe(approvedOrderStatus); + + const normalStatusDetail = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(normalStatusDetail).toBe(orderStatusDetail); + + /* Tumor Sample */ + const tumorShortID = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SHORT_ID, page); + expect(tumorShortID).toBe(shortID); + + const tumorSampleType = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(tumorSampleType).toBe('Tumor'); + + const tumorSampleName = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE, page); + expect(tumorSampleName).toBe(sampleNameTumor); + + const tumorOrderNumber = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(tumorOrderNumber).toBe(latestOrderNumberTumor); + + const tumorOrderDate = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_DATE, page); + expect(tumorOrderDate).toBe(orderDateTumor); + + const tumorOrderStatus = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS, page); + expect(tumorOrderStatus).toBe(approvedOrderStatus); + + const tumorStatusDetail = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(tumorStatusDetail).toBe(orderStatusDetail); + }); + }) + + test(`${study}: Verify a clinical order can be placed for a participant with Lost-to-FollowUp status`, async ({ page, request }) => { + navigation = new Navigation(page, request); + await new Select(page, { label: 'Select study' }).selectOption(study); + + const participantListPage = await navigation.selectFromStudy(Study.PARTICIPANT_LIST); + await participantListPage.waitForReady(); + const participantListTable = participantListPage.participantListTable; + + await test.step('Chose an enrolled participant that will get a clinical order placed', async () => { + participantEnrollmentStatus = DataFilter.LOST_TO_FOLLOWUP; + shortID = await findParticipantForGermlineSequencing({ + enrollmentStatus: participantEnrollmentStatus, + participantList: participantListPage, + participantTable: participantListTable, + studyName: study, + usePediatricParticipant: true + }); + + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + }); + + await test.step('Make sure that the Sequencing Order tab is visible', async () => { + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + }); + + await test.step('Place a clinical order using the Sequencing Order tab', async () => { + normalSample = await sequencingOrderTab.getFirstAvailableNormalSample(); + await sequencingOrderTab.selectSampleCheckbox(normalSample); + sampleNameNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.SAMPLE, page); + await sequencingOrderTab.fillCollectionDateIfNeeded(normalSample); + previousLatestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for normal sample: ${previousLatestOrderNumberNormal}`); + + tumorSample = await sequencingOrderTab.getFirstAvailableTumorSample(); + await sequencingOrderTab.selectSampleCheckbox(tumorSample); + sampleNameTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.SAMPLE, page); + previousLatestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for tumor sample: ${previousLatestOrderNumberTumor}`); + + await sequencingOrderTab.assertPlaceOrderButtonDisplayed(); + await sequencingOrderTab.placeOrder(); + + await sequencingOrderTab.assertClinicalOrderModalDisplayed(); + await sequencingOrderTab.submitClinicalOrder(); + }); + + /* NOTE: Need to go from Participant Page -> Participant List -> (refresh) -> Participant Page in order to see the new info */ + await test.step('Verify that the Latest Sequencing Order Date and Latest Order Number have been updated', async () => { + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + await participantListTable.openParticipantPageAt({ position: 0 }); + + await participantPage.waitForReady(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + //Verify that Latest Sequencing Order Date is the current date and Latest Order Number has received new input + const today = getToday(); + + orderDateNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateNormal.trim()).toBe(today); + + orderDateTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateTumor.trim()).toBe(today); + + latestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberNormal).not.toBe(previousLatestOrderNumberNormal); + + latestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberTumor).not.toBe(previousLatestOrderNumberTumor); + }); + + await test.step('Place an order in mercury', async () => { + const message = createMercuryOrderMessage({ + latestOrderNumber: latestOrderNumberTumor, + orderStatus: approvedOrderStatus, + orderDetails: orderStatusDetail + }); + await placeMercuryOrder(MERCURY_PUBSUB_TOPIC_NAME, message); + }); + + await test.step('Verify that the mercury order was successfully placed', async () => { + //Check that Latest Order Status, Latest PDO Number are not empty for both Normal and Tumor samples - tab needs info refreshed in order to see changes + await expect(async () => { + //Occasionally, a couple of seconds are needed before the info shows up in DSM UI + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + /* Checking the Normal sample's info */ + const latestOrderStatusNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusNormal).toBe(approvedOrderStatus); + const latestPDONumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberNormal).toContain(`Made-by-Playwright-on`); + + /* Checking the Tumor sample's info */ + const latestOrderStatusTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusTumor).toBe(approvedOrderStatus); + const latestPDONumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberTumor).toContain(`Made-by-Playwright-on`); + }).toPass({ + intervals: [20_000], + timeout: 60_000 + }); + }); + + await test.step('Verify that the mercury order can be seen in Samples -> Clinical Orders', async () => { + //Check that info of the newest clinical order can be seen in Clinical Orders page + const clinicalOrderPage = await navigation.selectFromSamples(Samples.CLINICAL_ORDERS); + await clinicalOrderPage.waitForReady(); + + const clinicalOrderNormal = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Normal', orderNumber: latestOrderNumberNormal }); + const clinicalOrderTumor = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Tumor', orderNumber: latestOrderNumberTumor }); + + // Check that each sample's info is present: Short ID, Sample Type, Sample, Order Number, Order Date, Status, Status Detail + /* Normal Sample */ + const normalShortID = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SHORT_ID, page); + expect(normalShortID).toBe(shortID); + + const normalSampleType = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(normalSampleType).toBe('Normal'); + + const normalSampleName = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE, page); + expect(normalSampleName).toBe(sampleNameNormal); + + const normalOrderNumber = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(normalOrderNumber).toBe(latestOrderNumberNormal); + + const normalOrderDate = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_DATE, page); + expect(normalOrderDate).toBe(orderDateNormal); + + const normalOrderStatus = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS, page); + expect(normalOrderStatus).toBe(approvedOrderStatus); + + const normalStatusDetail = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(normalStatusDetail).toBe(orderStatusDetail); + + /* Tumor Sample */ + const tumorShortID = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SHORT_ID, page); + expect(tumorShortID).toBe(shortID); + + const tumorSampleType = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(tumorSampleType).toBe('Tumor'); + + const tumorSampleName = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE, page); + expect(tumorSampleName).toBe(sampleNameTumor); + + const tumorOrderNumber = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(tumorOrderNumber).toBe(latestOrderNumberTumor); + + const tumorOrderDate = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_DATE, page); + expect(tumorOrderDate).toBe(orderDateTumor); + + const tumorOrderStatus = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS, page); + expect(tumorOrderStatus).toBe(approvedOrderStatus); + + const tumorStatusDetail = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(tumorStatusDetail).toBe(orderStatusDetail); + }); + }) + + test(`${study}: Verify a clinical order can be placed for a lost to follwup participant located in U.S. territory`, async ({ page, request }) => { + navigation = new Navigation(page, request); + await new Select(page, { label: 'Select study' }).selectOption(study); + + const participantListPage = await navigation.selectFromStudy(Study.PARTICIPANT_LIST); + await participantListPage.waitForReady(); + const participantListTable = participantListPage.participantListTable; + + await test.step('Chose a lost-to-followup participant that will get a clinical order placed', async () => { + participantEnrollmentStatus = DataFilter.LOST_TO_FOLLOWUP; + shortID = await findParticipantForGermlineSequencing({ + enrollmentStatus: participantEnrollmentStatus, + participantList: participantListPage, + participantTable: participantListTable, + studyName: study, + usePediatricParticipant: true, + residenceInUSTerritory: true + }); + + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + }); + + await test.step('Make sure that the Sequencing Order tab is visible', async () => { + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + }); + + await test.step('Place a clinical order using the Sequencing Order tab', async () => { + normalSample = await sequencingOrderTab.getFirstAvailableNormalSample(); + await sequencingOrderTab.selectSampleCheckbox(normalSample); + sampleNameNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.SAMPLE, page); + await sequencingOrderTab.fillCollectionDateIfNeeded(normalSample); + previousLatestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for normal sample: ${previousLatestOrderNumberNormal}`); + + tumorSample = await sequencingOrderTab.getFirstAvailableTumorSample(); + await sequencingOrderTab.selectSampleCheckbox(tumorSample); + sampleNameTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.SAMPLE, page); + previousLatestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + logInfo(`previous Latest Order Number for tumor sample: ${previousLatestOrderNumberTumor}`); + + await sequencingOrderTab.assertPlaceOrderButtonDisplayed(); + await sequencingOrderTab.placeOrder(); + + await sequencingOrderTab.assertClinicalOrderModalDisplayed(); + await sequencingOrderTab.submitClinicalOrder(); + }); + + /* NOTE: Need to go from Participant Page -> Participant List -> (refresh) -> Participant Page in order to see the new info */ + await test.step('Verify that the Latest Sequencing Order Date and Latest Order Number have been updated', async () => { + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + await participantListTable.openParticipantPageAt({ position: 0 }); + + await participantPage.waitForReady(); + sequencingOrderTab = await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + //Verify that Latest Sequencing Order Date is the current date and Latest Order Number has received new input + const today = getToday(); + + orderDateNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateNormal.trim()).toBe(today); + + orderDateTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_SEQUENCING_ORDER_DATE, page); + expect(orderDateTumor.trim()).toBe(today); + + latestOrderNumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberNormal).not.toBe(previousLatestOrderNumberNormal); + + latestOrderNumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_NUMBER, page); + expect(latestOrderNumberTumor).not.toBe(previousLatestOrderNumberTumor); + }); + + await test.step('Place an order in mercury', async () => { + const message = createMercuryOrderMessage({ + latestOrderNumber: latestOrderNumberTumor, + orderStatus: approvedOrderStatus, + orderDetails: orderStatusDetail + }); + await placeMercuryOrder(MERCURY_PUBSUB_TOPIC_NAME, message); + }); + + await test.step('Verify that the mercury order was successfully placed', async () => { + //Check that Latest Order Status, Latest PDO Number are not empty for both Normal and Tumor samples - tab needs info refreshed in order to see changes + //NOTE: occasionally needs a couple more seconds before the expected statuses are displayed + await expect(async () => { + await participantPage.backToList(); + await participantListPage.refreshParticipantListUsingShortID({ ID: shortID }); + participantPage = await participantListTable.openParticipantPageAt({ position: 0 }); + await participantPage.waitForReady(); + + await participantPage.tablist(Tab.SEQUENCING_ORDER).isVisible(); + await participantPage.tablist(Tab.SEQUENCING_ORDER).click(); + await sequencingOrderTab.waitForReady(); + + /* Checking the Normal sample's info */ + const latestOrderStatusNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusNormal).toBe(approvedOrderStatus); + const latestPDONumberNormal = await getColumnDataForRow(normalSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberNormal).toContain(`Made-by-Playwright-on`); + + /* Checking the Tumor sample's info */ + const latestOrderStatusTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_ORDER_STATUS, page); + expect(latestOrderStatusTumor).toBe(approvedOrderStatus); + const latestPDONumberTumor = await getColumnDataForRow(tumorSample, SequencingOrderColumn.LATEST_PDO_NUMBER, page); + expect(latestPDONumberTumor).toContain(`Made-by-Playwright-on`); + }).toPass({ + intervals: [20_000], + timeout: 60_000 + }); + }); + + await test.step('Verify that the mercury order can be seen in Samples -> Clinical Orders', async () => { + //Check that info of the newest clinical order can be seen in Clinical Orders page + const clinicalOrderPage = await navigation.selectFromSamples(Samples.CLINICAL_ORDERS); + await clinicalOrderPage.waitForReady(); + + const clinicalOrderNormal = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Normal', orderNumber: latestOrderNumberNormal }); + const clinicalOrderTumor = clinicalOrderPage.getClinicalOrderRow({ sampleType: 'Tumor', orderNumber: latestOrderNumberTumor }); + + // Check that each sample's info is present: Short ID, Sample Type, Sample, Order Number, Order Date, Status, Status Detail + /* Normal Sample */ + const normalShortID = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SHORT_ID, page); + expect(normalShortID).toBe(shortID); + + const normalSampleType = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(normalSampleType).toBe('Normal'); + + const normalSampleName = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.SAMPLE, page); + expect(normalSampleName).toBe(sampleNameNormal); + + const normalOrderNumber = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(normalOrderNumber).toBe(latestOrderNumberNormal); + + const normalOrderDate = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.ORDER_DATE, page); + expect(normalOrderDate).toBe(orderDateNormal); + + const normalOrderStatus = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS, page); + expect(normalOrderStatus).toBe(approvedOrderStatus); + + const normalStatusDetail = await getColumnDataForRow(clinicalOrderNormal, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(normalStatusDetail).toBe(orderStatusDetail); + + /* Tumor Sample */ + const tumorShortID = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SHORT_ID, page); + expect(tumorShortID).toBe(shortID); + + const tumorSampleType = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE_TYPE, page); + expect(tumorSampleType).toBe('Tumor'); + + const tumorSampleName = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.SAMPLE, page); + expect(tumorSampleName).toBe(sampleNameTumor); + + const tumorOrderNumber = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_NUMBER, page); + expect(tumorOrderNumber).toBe(latestOrderNumberTumor); + + const tumorOrderDate = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.ORDER_DATE, page); + expect(tumorOrderDate).toBe(orderDateTumor); + + const tumorOrderStatus = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS, page); + expect(tumorOrderStatus).toBe(approvedOrderStatus); + + const tumorStatusDetail = await getColumnDataForRow(clinicalOrderTumor, ClinicalOrdersColumn.STATUS_DETAIL, page); + expect(tumorStatusDetail).toBe(orderStatusDetail); + }); + }) + } +}) + +async function findParticipantForGermlineSequencing(opts: { + enrollmentStatus: DataFilter.ENROLLED | DataFilter.LOST_TO_FOLLOWUP, + participantList: ParticipantListPage, + participantTable: ParticipantListTable, + studyName: StudyName, + usePediatricParticipant?: boolean, + residenceInUSTerritory?: boolean +}): Promise { + const { enrollmentStatus, participantList, participantTable, studyName, usePediatricParticipant = false, residenceInUSTerritory = false } = opts; + + const studyInformation = studyShortName(studyName); + let participantPrefix = studyInformation.playwrightPrefixAdult; + if (usePediatricParticipant) { + participantPrefix = studyInformation.playwrightPrefixChild; + } + + const customizeViewPanel = participantList.filters.customizeViewPanel; + await customizeViewPanel.open(); + await customizeViewPanel.selectColumns(CustomizeView.SAMPLE, [Label.STATUS]); + await customizeViewPanel.selectColumns(CustomizeView.CLINICAL_ORDERS, [ + Label.CLINICAL_ORDER_DATE, + Label.CLINICAL_ORDER_ID, + Label.CLINICAL_ORDER_PDO_NUMBER, + Label.CLINICAL_ORDER_STATUS, + Label.CLINICAL_ORDER_STATUS_DATE + ]); + await customizeViewPanel.selectColumns(CustomizeView.CONTACT_INFORMATION, [Label.CITY, Label.COUNTRY]); + await customizeViewPanel.close(); + + const searchPanel = participantList.filters.searchPanel; + await searchPanel.open(); + await searchPanel.text(Label.FIRST_NAME, { textValue: participantPrefix, additionalFilters: [DataFilter.EXACT_MATCH], exactMatch: false }); + await searchPanel.checkboxes(Label.STATUS, { checkboxValues: [enrollmentStatus] }); + await searchPanel.dates(Label.CLINICAL_ORDER_DATE, { additionalFilters: [DataFilter.NOT_EMPTY] }); + await searchPanel.text(Label.CLINICAL_ORDER_ID, { additionalFilters: [DataFilter.NOT_EMPTY] }); + + if (residenceInUSTerritory) { + //Location used: Yigo, Guam + await searchPanel.text(Label.CITY, { textValue: 'YIGO' }); + await searchPanel.text(Label.COUNTRY, { textValue: 'GU' }); + } else { + await searchPanel.text(Label.CITY, { textValue: 'CAMBRIDGE' }); + await searchPanel.text(Label.COUNTRY, { textValue: 'US' }); + } + + await searchPanel.search({ uri: 'filterList' }); + await searchPanel.search({ uri: 'filterList' }); + + const numberOfReturnedParticipants = await participantTable.getRowsCount(); + expect(numberOfReturnedParticipants).toBeGreaterThanOrEqual(1); + + //Enrollment Status and Kit Status have the same name in the Participant List Table, so take out Enrollment Status + await customizeViewPanel.open(); + await customizeViewPanel.deselectColumns(CustomizeView.PARTICIPANT, [Label.STATUS]); + await customizeViewPanel.close(); + + //Randomly chose a participant to get a clinical order who had previously had an order placed - and does not have deactivated kits (due to the bug: PEPPER-1511) + /*const randomizedParticipantRows = await participantTable.randomizeRows(); + const rowNumber = randomizedParticipantRows[0];*/ + let participantChosen = false; + let rowNumber = 0; + let shortID = ''; + while (!participantChosen) { + const randomizedParticipantRows = await participantTable.randomizeRows(); + rowNumber = randomizedParticipantRows[0]; + const kitStatus = (await participantTable.getParticipantDataAt(rowNumber, Label.STATUS)).trim(); + logInfo(`Kit Status: ${kitStatus}`); + if (!kitStatus.includes(`Deactivated`) || studyName === StudyName.OSTEO2) { + participantChosen = true; + shortID = await participantTable.getParticipantDataAt(rowNumber, Label.SHORT_ID); + logInfo(`short id without a deactivated kit: ${shortID}`); + } + } + + //const shortID = await participantTable.getParticipantDataAt(rowNumber, Label.SHORT_ID); + //shortID = 'PTPGLV'; + logInfo(`Participant chosen for clinical order: ${shortID}`); + + return shortID; +} + +function createMercuryOrderMessage(opts: { + latestOrderNumber: string, + orderStatus?: string, + orderDetails?: string +}): string { + const { latestOrderNumber, orderStatus = 'Approved', orderDetails = 'Successfully created order' } = opts; + const readableDate = getTodayInEastCoastDateTimeZone(); + + const message = ` + { + "status": { + "orderID":"${latestOrderNumber}", + "orderStatus": "${orderStatus}", + "details": "${orderDetails}", + "pdoKey": "Made-by-Playwright-on ${readableDate}", + "json": "PDO-123" + } + }`; + + const messageObject = JSON.parse(message); + logInfo(`Resulting pubsub message is:\n ${JSON.stringify(messageObject)}`); + return JSON.stringify(messageObject); +} + +async function placeMercuryOrder(topicNameOrID: string, messsage: string): Promise { + const pubsubClient = new PubSub({projectId: MERCURY_PUBSUB_PROJECT_ID}); + const dataBuffer = Buffer.from(messsage); + logInfo(`Topic name or id: ${topicNameOrID}`); + logInfo(`Project ID: ${MERCURY_PUBSUB_PROJECT_ID}`); + + try { + const messageID = await pubsubClient + .topic(topicNameOrID) + .publishMessage({ data: dataBuffer }); + logInfo(`Message ${messageID} was published`); + } catch (error) { + logInfo(`Received error while publishing: ${(error as Error).message}`); + process.exitCode = 1; + } +} diff --git a/playwright-e2e/utils/date-utils.ts b/playwright-e2e/utils/date-utils.ts index cc1cd1e4ac..2449277975 100644 --- a/playwright-e2e/utils/date-utils.ts +++ b/playwright-e2e/utils/date-utils.ts @@ -149,3 +149,7 @@ export const calculateAge = (month: string, day: string, year: string): number = return resultAge; }; + +export const getTodayInEastCoastDateTimeZone = (): string => { + return new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }); +} diff --git a/playwright-e2e/utils/test-utils.ts b/playwright-e2e/utils/test-utils.ts index 89ba0c1b21..74a9232197 100644 --- a/playwright-e2e/utils/test-utils.ts +++ b/playwright-e2e/utils/test-utils.ts @@ -367,3 +367,16 @@ export function totalNumberOfOccurences(opts: { arrayToSearch: string[], wordToS } return numberOfOccurrences; } + +export async function getColumnHeaderIndex(columnName: string, page: Page): Promise { + const precedingColumns = page.locator(`//th[normalize-space(text())='${columnName}']/preceding-sibling::th`); + const columnIndex = await precedingColumns.count() + 1; + return columnIndex; +} + +export async function getColumnDataForRow(row: Locator, columnName: string, page: Page): Promise { + const columnIndex = await getColumnHeaderIndex(columnName, page); + const cellContent = await row.locator(`//td[${columnIndex}]`).textContent() as string; + console.log(`Data under ${columnName} column: ${cellContent}`); + return cellContent.trim(); +}