diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts b/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts index b92dd42ff..f507d7fe5 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts @@ -14,7 +14,7 @@ test("Expect clicking the studies tab to go to the correct url and to show all o page, }) => { const tab = anvilcatalogTabs.studies; - await page.goto(anvilcatalogTabs.consortia.url); + await page.goto(anvilcatalogTabs.workspaces.url); await testTab(page, tab); }); @@ -22,6 +22,6 @@ test("Expect clicking the workspaces tab to go to the correct url and to show al page, }) => { const tab = anvilcatalogTabs.workspaces; - await page.goto(anvilcatalogTabs.workspaces.url); + await page.goto(anvilcatalogTabs.consortia.url); await testTab(page, tab); }); diff --git a/explorer/e2e/anvil/anvil-backpages.spec.ts b/explorer/e2e/anvil/anvil-backpages.spec.ts new file mode 100644 index 000000000..593f958fc --- /dev/null +++ b/explorer/e2e/anvil/anvil-backpages.spec.ts @@ -0,0 +1,29 @@ +import { test } from "@playwright/test"; +import { + testBackpageAccess, + testBackpageDetails, + testExportBackpage, +} from "../testFunctions"; +import { anvilTabs } from "./anvil-tabs"; + +test.skip("Smoke test `Export to Terra` button on the first available dataset", async ({ + context, + page, +}) => { + test.setTimeout(120000); + await testExportBackpage(context, page, anvilTabs.datasets); +}); + +test.skip("Check access controls on the datasets backpages work for the first two tabs", async ({ + page, +}) => { + test.setTimeout(120000); + await testBackpageAccess(page, anvilTabs.datasets); +}); + +test("Check that information on the backpages matches information in the data tables", async ({ + page, +}) => { + test.setTimeout(120000); + await testBackpageDetails(page, anvilTabs.datasets); +}); diff --git a/explorer/e2e/anvil/anvil-pagination-content.spec.ts b/explorer/e2e/anvil/anvil-pagination-content.spec.ts index b5489b07a..2c4b90445 100644 --- a/explorer/e2e/anvil/anvil-pagination-content.spec.ts +++ b/explorer/e2e/anvil/anvil-pagination-content.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from "@playwright/test"; +import { getFirstRowNthColumnCellLocator } from "../testFunctions"; import { anvilTabs } from "./anvil-tabs"; const pageCountRegex = /Page [0-9]+ of [0-9]+/; @@ -16,13 +17,7 @@ test("Check forward and backwards pagination causes the page content to change o page.getByRole("tab").getByText(tab.tabName, { exact: true }) ).toHaveAttribute("aria-selected", "true", { timeout: 25000 }); - const firstElementTextLocator = page - .getByRole("rowgroup") - .nth(1) - .getByRole("row") - .nth(0) - .getByRole("cell") - .nth(0); + const firstElementTextLocator = getFirstRowNthColumnCellLocator(page, 0); // Should start on first page await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( diff --git a/explorer/e2e/anvil/anvil-pagination.spec.ts b/explorer/e2e/anvil/anvil-pagination.spec.ts index 63b3d7edf..15e6a7c4b 100644 --- a/explorer/e2e/anvil/anvil-pagination.spec.ts +++ b/explorer/e2e/anvil/anvil-pagination.spec.ts @@ -32,10 +32,10 @@ test("Check first page has disabled back and enabled forward pagination buttons ).toBeDisabled(); }); -test.setTimeout(300000); test("Check that forward pagination increments the current page and that page count stays static for the first five pages on the donors tab", async ({ page, }) => { + test.setTimeout(500000); // Should start on first page, and there should be multiple pages available await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( /Page 1 of [0-9]+/ diff --git a/explorer/e2e/anvil/anvil-sort.spec.ts b/explorer/e2e/anvil/anvil-sort.spec.ts index 17d0d5507..4f1b1111b 100644 --- a/explorer/e2e/anvil/anvil-sort.spec.ts +++ b/explorer/e2e/anvil/anvil-sort.spec.ts @@ -7,29 +7,34 @@ test.describe.configure({ mode: "parallel" }); test("Expect clicking each column header three times to keep the first text element visible on the Datasets tab", async ({ page, }) => { + test.setTimeout(180000); await testSortAzul(page, anvilTabs.datasets); }); test("Expect clicking each column header three times to keep the first text element visible on the Donors tab", async ({ page, }) => { + test.setTimeout(180000); await testSortAzul(page, anvilTabs.donors); }); test("Expect clicking each column header of each tab three times to keep the first text element visible on the BioSamples tab", async ({ page, }) => { + test.setTimeout(180000); await testSortAzul(page, anvilTabs.biosamples); }); test("Expect clicking each column header three times to keep the first text element visible on the Activities tab", async ({ page, }) => { + test.setTimeout(180000); await testSortAzul(page, anvilTabs.activities); }); test("Expect clicking each column header three times to keep the first text element visible on the Files tab", async ({ page, }) => { + test.setTimeout(180000); await testSortAzul(page, anvilTabs.files); }); diff --git a/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts b/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts index 8dc1289ae..a85c96f6d 100644 --- a/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts +++ b/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts @@ -2,42 +2,37 @@ import { test } from "@playwright/test"; import { testTab } from "../testFunctions"; import { anvilTabs } from "./anvil-tabs"; -test("Expect clicking the activities tab to go to the correct url and to show all of the relevant columns when selected", async ({ +test("Expect clicking the datasets tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilTabs.activities; - await page.goto(anvilTabs.datasets.url); - await testTab(page, tab); + await page.goto(anvilTabs.activities.url); + await testTab(page, anvilTabs.datasets); }); -test("Expect clicking the datasets tab to go to the correct url and to show all of the relevant columns when selected", async ({ +test("Expect clicking the activities tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilTabs.datasets; - await page.goto(anvilTabs.activities.url); - await testTab(page, tab); + await page.goto(anvilTabs.datasets.url); + await testTab(page, anvilTabs.activities); }); test("Expect clicking the files tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilTabs.files; await page.goto(anvilTabs.datasets.url); - await testTab(page, tab); + await testTab(page, anvilTabs.files); }); test("Expect clicking the donors tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilTabs.donors; await page.goto(anvilTabs.datasets.url); - await testTab(page, tab); + await testTab(page, anvilTabs.donors); }); test("Expect clicking the biosamples tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilTabs.biosamples; await page.goto(anvilTabs.datasets.url); - await testTab(page, tab); + await testTab(page, anvilTabs.biosamples); }); diff --git a/explorer/e2e/anvil/anvil-tabs.ts b/explorer/e2e/anvil/anvil-tabs.ts index 800190506..b7601f41b 100644 --- a/explorer/e2e/anvil/anvil-tabs.ts +++ b/explorer/e2e/anvil/anvil-tabs.ts @@ -4,6 +4,11 @@ import { TabCollectionKeys, TabDescription, } from "../testInterfaces"; +import { + ANVIL_COLUMN_NAMES, + ANVIL_DATASETS_BACKPAGE_HEADER_NAMES, + ANVIL_PLURALIZED_METADATA_LABELS, +} from "./constants"; export const anvilFilterNames: string[] = [ "Anatomical Site", @@ -30,22 +35,44 @@ export const ORGANISM_TYPE_INDEX = 8; export const PHENOTYPIC_SEX_INDEX = 9; export const REPORTED_ETHNICITY_INDEX = 10; +const anvilDatasetsPreselectedColumns = [ + { name: ANVIL_COLUMN_NAMES.DATASET, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ACCESS, sortable: false }, + { name: ANVIL_COLUMN_NAMES.IDENTIFIER, sortable: true }, + { name: ANVIL_COLUMN_NAMES.CONSENT_GROUP, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATA_MODALITY, sortable: true }, +]; +const anvilDatasetsSelectableColumns = [ + { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: ANVIL_PLURALIZED_METADATA_LABELS.PHENOTYPIC_SEX, + sortable: true, + }, + { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: ANVIL_PLURALIZED_METADATA_LABELS.PHENOTYPIC_SEX, + sortable: true, + }, +]; + export const anvilTabs: AnvilCMGTabCollection = { activities: { emptyFirstColumn: false, maxPages: 25, preselectedColumns: [ - { name: "Document Id", sortable: true }, - { name: "Activity Type", sortable: true }, - { name: "Data Modality", sortable: true }, - { name: "BioSample Type", sortable: true }, - { name: "Organism Type", sortable: true }, - { name: "Dataset", sortable: true }, + { name: ANVIL_COLUMN_NAMES.DOCUMENT_ID, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ACTIVITY_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATA_MODALITY, sortable: true }, + { name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATASET, sortable: true }, ], selectableColumns: [ - { name: "Phenotypic Sex", sortable: true }, - { name: "Reported Ethnicity", sortable: true }, - { name: "Diagnosis", sortable: true }, + { name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, sortable: true }, + { name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, ], tabName: "Activities", url: "/activities", @@ -54,36 +81,81 @@ export const anvilTabs: AnvilCMGTabCollection = { emptyFirstColumn: false, maxPages: 25, preselectedColumns: [ - { name: "BioSample Id", sortable: true }, - { name: "Anatomical Site", sortable: true }, - { name: "BioSample Type", sortable: true }, - { name: "Organism Type", sortable: true }, - { name: "Diagnosis", sortable: true }, - { name: "Dataset", sortable: true }, + { name: ANVIL_COLUMN_NAMES.BIOSAMPLE_ID, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ANATOMICAL_SITE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATASET, sortable: true }, ], selectableColumns: [ - { name: "Phenotypic Sex", sortable: true }, - { name: "Reported Ethnicity", sortable: true }, + { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: ANVIL_PLURALIZED_METADATA_LABELS.PHENOTYPIC_SEX, + sortable: true, + }, + { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: ANVIL_PLURALIZED_METADATA_LABELS.REPORTED_ETHNICITIES, + sortable: true, + }, ], tabName: "BioSamples", url: "/biosamples", }, datasets: { + backpageAccessTags: { + deniedLongName: "Access Required", + deniedShortName: "Required", + grantedLongName: "Access Granted", + grantedShortName: "Granted", + }, + backpageExportButtons: { + accessNotGrantedMessage: + "To export this dataset, please sign in and, if necessary, request access.", + detailsName: "Dataset Details", + exportActionButtonText: "Open Terra", + exportRequestButtonText: "Request Link", + exportTabName: "Export", + exportUrlRegExp: /\.*\/export-to-terra/, + firstLoadingMessage: "Your link will be ready shortly...", + newTabMessage: + "If you are a new user or returning user, click sign in to continue.", + secondLandingMessage: "Your Terra Workspace Link is Ready", + }, + backpageHeaders: [ + { + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DATASET_ID, + }, + { + correspondingColumn: anvilDatasetsPreselectedColumns[3], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.CONSENT_GROUP, + }, + { + correspondingColumn: anvilDatasetsPreselectedColumns[4], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.ORGANISM_TYPE, + }, + { + correspondingColumn: anvilDatasetsPreselectedColumns[5], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DIAGNOSIS, + }, + { + correspondingColumn: anvilDatasetsPreselectedColumns[6], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DATA_MODALITY, + }, + { + correspondingColumn: anvilDatasetsSelectableColumns[0], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.PHENOTYPIC_SEX, + }, + { + correspondingColumn: anvilDatasetsSelectableColumns[1], + name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.REPORTED_ETHNICITY, + }, + ], emptyFirstColumn: false, maxPages: 25, - preselectedColumns: [ - { name: "Dataset", sortable: true }, - { name: "Access", sortable: false }, - { name: "Identifier", sortable: true }, - { name: "Consent Group", sortable: true }, - { name: "Organism Type", sortable: true }, - { name: "Diagnosis", sortable: true }, - { name: "Data Modality", sortable: true }, - ], - selectableColumns: [ - { name: "Phenotypic Sex", sortable: true }, - { name: "Reported Ethnicity", sortable: true }, - ], + preselectedColumns: anvilDatasetsPreselectedColumns, + selectableColumns: anvilDatasetsSelectableColumns, tabName: "Datasets", url: "/datasets", }, @@ -91,12 +163,12 @@ export const anvilTabs: AnvilCMGTabCollection = { emptyFirstColumn: false, maxPages: 25, preselectedColumns: [ - { name: "Donor Id", sortable: true }, - { name: "Organism Type", sortable: true }, - { name: "Phenotypic Sex", sortable: true }, - { name: "Reported Ethnicity", sortable: true }, - { name: "Diagnosis", sortable: true }, - { name: "Dataset", sortable: true }, + { name: ANVIL_COLUMN_NAMES.DONOR_ID, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, sortable: true }, + { name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATASET, sortable: true }, ], selectableColumns: [], tabName: "Donors", @@ -106,18 +178,18 @@ export const anvilTabs: AnvilCMGTabCollection = { emptyFirstColumn: true, maxPages: 25, preselectedColumns: [ - { name: "Name", sortable: true }, - { name: "File Format", sortable: true }, - { name: "Size", sortable: true }, - { name: "DRS URI", sortable: false }, - { name: "Data Modality", sortable: true }, - { name: "Organism Type", sortable: true }, - { name: "Dataset", sortable: true }, + { name: ANVIL_COLUMN_NAMES.NAME, sortable: true }, + { name: ANVIL_COLUMN_NAMES.FILE_FORMAT, sortable: true }, + { name: ANVIL_COLUMN_NAMES.SIZE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DRS_URI, sortable: false }, + { name: ANVIL_COLUMN_NAMES.DATA_MODALITY, sortable: true }, + { name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DATASET, sortable: true }, ], selectableColumns: [ - { name: "Phenotypic Sex", sortable: true }, - { name: "Reported Ethnicity", sortable: true }, - { name: "Diagnosis", sortable: true }, + { name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, sortable: true }, + { name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, sortable: true }, + { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, ], tabName: "Files", url: "/files", diff --git a/explorer/e2e/anvil/constants.ts b/explorer/e2e/anvil/constants.ts new file mode 100644 index 000000000..bd38caa72 --- /dev/null +++ b/explorer/e2e/anvil/constants.ts @@ -0,0 +1,38 @@ +export const ANVIL_DATASETS_BACKPAGE_HEADER_NAMES = { + CONSENT_GROUP: "Consent group", + DATASET_ID: "Dataset Id", + DATA_MODALITY: "Data modality", + DIAGNOSIS: "Diagnosis", + ORGANISM_TYPE: "Organism type", + PHENOTYPIC_SEX: "Phenotypic sex", + REPORTED_ETHNICITY: "Reported ethnicity", +}; + +export const ANVIL_COLUMN_NAMES = { + ACCESS: "Access", + ACTIVITY_TYPE: "Activity Type", + ANATOMICAL_SITE: "Anatomical Site", + BIOSAMPLE_ID: "BioSample Id", + BIOSAMPLE_TYPE: "BioSample Type", + CONSENT_GROUP: "Consent Group", + DATASET: "Dataset", + DATA_MODALITY: "Data Modality", + DIAGNOSIS: "Diagnosis", + DOCUMENT_ID: "Document Id", + DONOR_ID: "Donor Id", + DRS_URI: "DRS URI", + FILE_FORMAT: "File Format", + IDENTIFIER: "Identifier", + NAME: "Name", + ORGANISM_TYPE: "Organism Type", + PHENOTYPIC_SEX: "Phenotypic Sex", + REPORTED_ETHNICITY: "Reported Ethnicity", + SIZE: "Size", +}; + +export const ANVIL_PLURALIZED_METADATA_LABELS = { + DATA_MODALITY: "data modalities", + DIAGNOSIS: "diagnoses", + PHENOTYPIC_SEX: "phenotypic sexes", + REPORTED_ETHNICITIES: "reported ethnicities", +}; diff --git a/explorer/e2e/testFunctions.ts b/explorer/e2e/testFunctions.ts index ef4f9021c..272f4b36f 100644 --- a/explorer/e2e/testFunctions.ts +++ b/explorer/e2e/testFunctions.ts @@ -1,27 +1,46 @@ -import { expect, Locator, Page } from "@playwright/test"; -import { TabDescription } from "./testInterfaces"; +import { BrowserContext, expect, Locator, Page } from "@playwright/test"; +import { + BackpageHeader, + ColumnDescription, + TabDescription, +} from "./testInterfaces"; /* eslint-disable sonarjs/no-duplicate-string -- ignoring duplicate strings here */ /** - * Get a locator to the cell in the first row's nth column + * Get a locator to the cell in the mth row's nth column * @param page - a Playwright page object + * @param rowIndex - the zero-indexed row to return * @param columnIndex - the zero-indexed column to return * @returns a Playwright locator object to the selected cell **/ -export const getFirstRowNthColumnCellLocator = ( +export const getMthRowNthColumnCellLocator = ( page: Page, + rowIndex: number, columnIndex: number ): Locator => { return page .getByRole("rowgroup") .nth(1) .getByRole("row") - .nth(0) + .nth(rowIndex) .getByRole("cell") .nth(columnIndex); }; +/** + * Get a locator to the cell in the first row's nth column + * @param page - a Playwright page object + * @param columnIndex - the zero-indexed column to return + * @returns a Playwright locator object to the selected cell + **/ +export const getFirstRowNthColumnCellLocator = ( + page: Page, + columnIndex: number +): Locator => { + return getMthRowNthColumnCellLocator(page, 0, columnIndex); +}; + /** * Tests that the tab url goes to a valid page and that the correct tab (and only * the correct tab) appears selected @@ -344,7 +363,7 @@ export async function testFilterPersistence( const filterNameMatch = (await filterToSelectLocator.innerText()) .trim() .match(/^\S*/); - if (filterNameMatch == null) { + if (!filterNameMatch) { // This means that the selected filter did not have any non-whitespace text // associated with it, making the test impossible to complete. console.log("ERROR: Filter name is blank, so the test cannot continue"); @@ -522,4 +541,303 @@ export async function testClearAll( await page.locator("body").click(); } } + +/** + * Get the first link to a backpage with specified backpage access + * @param page - a Playright page locator + * @param access - the string denoting the level of access desired + * @returns a Pla + */ +const getBackpageLinkLocatorByAccess = (page: Page, access: string): Locator => + page + .getByRole("row") + .filter({ has: page.getByRole("cell", { name: access }) }) + .first() + .getByRole("cell") + .first() + .getByRole("link"); + +/** + * Test the export process for the specified tab + * @param context - a Playwright browser context object + * @param page - a Playwright page object + * @param tab - the tab to test on + */ +export async function testExportBackpage( + context: BrowserContext, + page: Page, + tab: TabDescription +): Promise { + if ( + tab.backpageExportButtons === undefined || + tab.backpageAccessTags === undefined + ) { + // Fail if this test is ran on a tab without defined backpages + await expect(false); + return; + } + // Goto the specified tab + await page.goto(tab.url); + // Expect to find row with a granted status indicator + const grantedRowLocator = getBackpageLinkLocatorByAccess( + page, + tab.backpageAccessTags.grantedShortName + ); + await expect(grantedRowLocator).toBeVisible(); + // Click into the selected row + await grantedRowLocator.dispatchEvent("click"); + await expect( + page.getByText(tab.backpageExportButtons.detailsName) + ).toBeVisible(); + // Click the "Export" tab + await page + .getByText(tab.backpageExportButtons.exportTabName, { + exact: true, + }) + .click(); + await expect(page).toHaveURL(tab.backpageExportButtons.exportUrlRegExp); + await expect(page.getByRole("checkbox").first()).toBeVisible(); + const exportRequestButtonLocator = page.getByRole("button", { + name: tab.backpageExportButtons.exportRequestButtonText, + }); + await expect(exportRequestButtonLocator).toBeEnabled(); + // Select all checkboxes that are not in a table + const allNonTableCheckboxLocators = await page + .locator("input[type='checkbox']:not(table input[type='checkbox'])") + .all(); + for (const checkboxLocator of allNonTableCheckboxLocators) { + await checkboxLocator.click(); + await expect(checkboxLocator).toBeChecked(); + await expect(checkboxLocator).toBeEnabled({ timeout: 10000 }); + } + // Expect there to be exactly one table on the backpage + await expect(page.getByRole("table")).toHaveCount(1); + // Check the second checkbox in the table (this should be the checkbox after the "select all checkbox") + const tableLocator = page.getByRole("table"); + const allInTableCheckboxLocators = await tableLocator + .getByRole("checkbox") + .all(); + const secondCheckboxInTableLocator = allInTableCheckboxLocators[1]; + await secondCheckboxInTableLocator.click(); + await expect(secondCheckboxInTableLocator).toBeChecked(); + await expect(secondCheckboxInTableLocator).toBeEnabled({ timeout: 10000 }); + // Make sure that no other checkboxes are selected + const otherInTableCheckboxLocators = [ + allInTableCheckboxLocators[0], + ...allInTableCheckboxLocators.slice(2), + ]; + for (const otherCheckboxLocator of otherInTableCheckboxLocators) { + await expect(otherCheckboxLocator).not.toBeChecked(); + await expect(otherCheckboxLocator).toBeEnabled(); + } + // Click the Export Request button + await expect(exportRequestButtonLocator).toBeEnabled({ timeout: 10000 }); + await exportRequestButtonLocator.click(); + await expect( + page.getByText(tab.backpageExportButtons.firstLoadingMessage, { + exact: true, + }) + ).toBeVisible(); + await expect( + page.getByText(tab.backpageExportButtons.secondLandingMessage, { + exact: true, + }) + ).toBeVisible({ timeout: 60000 }); + const exportActionButtonLocator = page.getByRole("button", { + name: tab.backpageExportButtons?.exportActionButtonText, + }); + await expect(exportActionButtonLocator).toBeEnabled(); + // Click the Export Action Button and await a new browser tab + const newPagePromise = context.waitForEvent("page"); + await exportActionButtonLocator.click(); + const newPage = await newPagePromise; + // Expect the new browser tab to display the new tab content + await expect( + newPage.getByText(tab.backpageExportButtons?.newTabMessage) + ).toBeVisible(); +} + +/** + * Test that export access is available on entries where access shows as available + * and is not on entries where access shows as unavailable + * @param page - a Playwright page objext + * @param tab - the Tab to test on + */ +export async function testBackpageAccess( + page: Page, + tab: TabDescription +): Promise { + if ( + tab.backpageExportButtons === undefined || + tab.backpageAccessTags === undefined + ) { + // Fail if this test is ran on a tab without defined backpages + await expect(false); + return; + } + // Goto the specified tab + await page.goto(tab.url); + // Check that the first "Granted" tab has access granted + const grantedRowLocator = getBackpageLinkLocatorByAccess( + page, + tab.backpageAccessTags.grantedShortName + ); + await expect(grantedRowLocator).toBeVisible(); + await grantedRowLocator.dispatchEvent("click"); + await expect( + page.getByText(tab.backpageExportButtons.detailsName) + ).toBeVisible(); + await expect( + page.getByText(tab.backpageAccessTags.grantedLongName) + ).toBeVisible(); + await page + .getByText(tab.backpageExportButtons.exportTabName, { + exact: true, + }) + .click(); + await expect(page).toHaveURL(tab.backpageExportButtons.exportUrlRegExp); + await expect(page.getByRole("checkbox").first()).toBeVisible(); + const requestLinkButtonLocator = page.getByRole("button", { + name: tab.backpageExportButtons.exportRequestButtonText, + }); + await expect(requestLinkButtonLocator).toBeEnabled(); + // Go back to the table page + await page.getByRole("link", { name: tab.tabName }).click(); + // Check that the first "Required" tab does not have access granted + const deniedRowLocator = getBackpageLinkLocatorByAccess( + page, + tab.backpageAccessTags.deniedShortName + ); + await expect(deniedRowLocator).toBeVisible(); + await deniedRowLocator.dispatchEvent("click"); + await expect( + page.getByText(tab.backpageAccessTags.deniedLongName) + ).toBeVisible(); + await page + .getByText(tab.backpageExportButtons.exportTabName, { + exact: true, + }) + .click(); + await expect(page).toHaveURL(tab.backpageExportButtons.exportUrlRegExp); + await expect( + page.getByText(tab.backpageExportButtons.accessNotGrantedMessage, { + exact: true, + }) + ).toBeVisible(); +} + +/** + * Get the text from a cell by reading the tooltip if it appears to be an N-tag + * cell or by reading the text if it does not appear to be + * @param page - a Playwright Page object + * @param columnDescription - a columnDescription object for the column + * @param rowPosition - the zero-indexed position of the row + * @param columnPosition - the zero-indexed position of the column + * @returns - a Promise with the cell's text + */ +const hoverAndGetText = async ( + page: Page, + columnDescription: ColumnDescription | undefined, + rowPosition: number, + columnPosition: number +): Promise => { + const cellLocator = getMthRowNthColumnCellLocator( + page, + rowPosition, + columnPosition + ); + const cellText = await cellLocator.innerText(); + // Check if the cell appears to be an Ntag cell + if ( + !columnDescription !== undefined && + columnDescription?.pluralizedLabel !== undefined && + RegExp("\\s*[0-9]+ " + columnDescription.pluralizedLabel + "\\s*").test( + cellText + ) + ) { + // Hover over the text of the NTag cell + await cellLocator.locator("*").last().hover(); + // Read the tooltip + await page.getByRole("tooltip").waitFor(); + const outputText = (await page.getByRole("tooltip").innerText()).trim(); + // Hover over a different part of the page to ensure that the tooltip disappears + await page.getByRole("columnheader").first().hover(); + await expect(page.getByRole("tooltip")).toHaveCount(0); + // Return the tooltip contents + return outputText; + } + return cellText.trim(); +}; + +/** + * Check that the details in the backpage sidebar match information in the data table + * @param page - a Playwright page object + * @param tab - the tab to test on + */ +export async function testBackpageDetails( + page: Page, + tab: TabDescription +): Promise { + if ( + tab.backpageHeaders === undefined || + tab.backpageExportButtons === undefined + ) { + // If the tab is not set up with backpage info, fail the test + await expect(false); + return; + } + await page.goto(tab.url); + // Enable test columns + await testSelectableColumns(page, tab); + const headers: { header: string; value: string }[] = []; + const combinedColumns = tab.preselectedColumns.concat(tab.selectableColumns); + const filterString = (x: string | undefined): x is string => x !== undefined; + // Get the columns that correspond with a header on the backpage details + const backpageCorrespondingColumns: string[] = tab.backpageHeaders + .map((header) => header?.correspondingColumn?.name) + .filter(filterString) + .map((x) => x.trim()); + for (let i = 0; i < combinedColumns.length; i++) { + // Get the name of the current column + const columnHeaderName = ( + await page.getByRole("columnheader").nth(i).innerText() + ).trim(); + // If the selected column has an entry on the backpage + if (backpageCorrespondingColumns.includes(columnHeaderName)) { + // Get the object representing the current column + const columnObject = combinedColumns.find( + (x) => x.name == columnHeaderName + ); + // Get the entry text + const tableEntryText = await hoverAndGetText(page, columnObject, 0, i); + // Get the name of the corresponding header on the backpage + const correspondingHeaderName = tab.backpageHeaders.find( + (header: BackpageHeader) => + header?.correspondingColumn?.name === columnHeaderName + )?.name; + if (correspondingHeaderName === undefined) { + // Fail the test, because this means there is an incorrect configuration in the tab definition + await expect(false); + return; + } + headers.push({ header: correspondingHeaderName, value: tableEntryText }); + } + } + // Go to the backpage + await getMthRowNthColumnCellLocator(page, 0, 0).click(); + // Expect the details name to be visible + await expect( + page.getByText(tab.backpageExportButtons.detailsName) + ).toBeVisible(); + for (const headerValue of headers) { + // Expect the correct value to be below the correct header in the dataset values table + await expect( + page + .locator(`:below(:text('${headerValue.header}'))`) + .getByText(headerValue.value) + .first() + ).toBeVisible(); + } +} + /* eslint-enable sonarjs/no-duplicate-string -- Checking duplicate strings again*/ diff --git a/explorer/e2e/testInterfaces.ts b/explorer/e2e/testInterfaces.ts index ee5d786c3..e404c1b10 100644 --- a/explorer/e2e/testInterfaces.ts +++ b/explorer/e2e/testInterfaces.ts @@ -1,8 +1,11 @@ export interface TabDescription { + backpageAccessTags?: BackpageAccessTags; + backpageExportButtons?: BackpageExportButtons; + backpageHeaders?: BackpageHeader[]; emptyFirstColumn: boolean; maxPages?: number; - preselectedColumns: columnDescription[]; - selectableColumns: columnDescription[]; + preselectedColumns: ColumnDescription[]; + selectableColumns: ColumnDescription[]; tabName: string; url: string; } @@ -23,7 +26,32 @@ export interface AnvilCatalogTabCollection { export type TabCollectionKeys = keyof AnvilCMGTabCollection; -export interface columnDescription { +export interface ColumnDescription { name: string; + pluralizedLabel?: string; sortable: boolean; } + +export interface BackpageHeader { + correspondingColumn?: ColumnDescription; + name: string; +} + +export interface BackpageAccessTags { + deniedLongName: string; + deniedShortName: string; + grantedLongName: string; + grantedShortName: string; +} + +export interface BackpageExportButtons { + accessNotGrantedMessage: string; + detailsName: string; + exportActionButtonText: string; + exportRequestButtonText: string; + exportTabName: string; + exportUrlRegExp: RegExp; + firstLoadingMessage: string; + newTabMessage: string; + secondLandingMessage: string; +} diff --git a/explorer/e2e/testReadme.md b/explorer/e2e/testReadme.md index b73b47d9b..54a3510ab 100644 --- a/explorer/e2e/testReadme.md +++ b/explorer/e2e/testReadme.md @@ -70,6 +70,19 @@ through the actions taken as part of the test and view the impact on the web pag - Runs from "Datasets" to "Activities", "Activities" to "Datasets", "Datasets" to "Files", "Datasets" to "Donors", "Datasets" to "BioSamples" - `anvil-tabs-buttons.spec.ts` - All tests rely on correct lists of tabs, columns, and filters in `anvil-tabs.ts` +- Backpages (`anvil-backpages.spec.ts`) + - Test the export process on the "Datasets" tab + - Selects the first dataset that does not have access control enabled and goes to the export tab on its backpage. Then, selects one filter checkbox for each category, then finally selects the "Export to Terra" button + - Checks that buttons, text, and loading text appear as expected, and that a new tab appears when the "Export to Terra" button is pressed + - Relevant text constants are stored in `anvil-tabs.ts` + - Currently disabled as the export button is disabled + - Test that access control works properly on the "Datasets" tab + - Selects a non-access-controlled tab and checks that text associated with access being granted is present, then repeats for an access-controlled tab + - Relevant text constants are stored in `anvil-tabs.ts` + - Currently disabled as the export button is disabled + - Test that data in the sidebar of the "Datasets" tab is the same as the text displayed in the main table + - Enables all non-preselected columns and read values from all columns in the first row, including n-tag cells. Then selects the backpage for the first row and checks that all matching columns are reflected + - Requires a list of the values in the sidebar to be present in `anvil-tabs.ts` and that plural labels are defined there for any columns that include n-tag cells #### AnVIL-Catalog @@ -96,13 +109,5 @@ through the actions taken as part of the test and view the impact on the web pag - Sign in (probably smoke test / existence only for ease) - "Contact Us" form - Edit columns Button (already on AnVIL-Catalog) -- Datasets backpages - - Check that "Dataset details" match relevant fields in the table - - Check access control works properly - - Smoke test for "Request link" button - - Check boxes - - Press "Request link" button - - Press resulting button to go to Terra - - Check that url begins "anvil.terra.bio" - Smoke test buttons at bottom of screen (logos, help, privacy) - Smoke test "Help & Documentation" buttons