diff --git a/e2e/anvil/anvil-backpages.spec.ts b/e2e/anvil/anvil-backpages.spec.ts deleted file mode 100644 index 7f5bbbc5b..000000000 --- a/e2e/anvil/anvil-backpages.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { test } from "@playwright/test"; -import { - testBackpageAccess, - testBackpageDetails, - testExportBackpage, -} from "../testFunctions"; -import { ANVIL_TABS } from "./anvil-tabs"; - -//TODO: this should probably check a feature flag instead -//eslint-disable-next-line sonarjs/no-skipped-tests -- disabled since Export to Terra is disabled on AnVIL -test.skip("Smoke test `Export to Terra` button on the first available dataset", async ({ - context, - page, -}) => { - const testResult = await testExportBackpage( - context, - page, - ANVIL_TABS.DATASETS - ); - if (!testResult) { - test.fail(); - } -}); - -//TODO: this should probably check a feature flag instead -//eslint-disable-next-line sonarjs/no-skipped-tests -- disabled since Export to Terra is disabled on AnVIL -test.skip("Check access controls on the datasets backpages work for the first two tabs", async ({ - page, -}) => { - const testResult = await testBackpageAccess(page, ANVIL_TABS.DATASETS); - if (!testResult) { - test.fail(); - } -}); - -test("Check that information on the backpages matches information in the data tables", async ({ - page, -}) => { - const testResult = await testBackpageDetails(page, ANVIL_TABS.DATASETS); - if (!testResult) { - test.fail(); - } -}); diff --git a/e2e/anvil/anvil-dataset.spec.ts b/e2e/anvil/anvil-dataset.spec.ts new file mode 100644 index 000000000..f7191d149 --- /dev/null +++ b/e2e/anvil/anvil-dataset.spec.ts @@ -0,0 +1,161 @@ +import { expect, Locator, Page, test } from "@playwright/test"; +import { + BUTTON_TEXT_ANALYZE_IN_TERRA, + BUTTON_TEXT_EXPORT, + BUTTON_TEXT_REQUEST_ACCESS, + BUTTON_TEXT_REQUEST_FILE_MANIFEST, + CHIP_TEXT_ACCESS_GRANTED, + CHIP_TEXT_ACCESS_REQUIRED, + DatasetAccess, +} from "./common/constants"; +import { + MUI_ALERT_ROOT, + MUI_BUTTON_GROUP_ROOT, + MUI_TABLE_CELL_ROOT, + MUI_TABLE_ROOT, + MUI_TABLE_ROW_ROOT, +} from "../features/common/constants"; +import { ROUTE_MANIFEST_DOWNLOAD } from "../../site-config/anvil-cmg/dev/export/constants"; + +const { describe } = test; + +describe.parallel("Dataset", () => { + test.beforeEach(async ({ page }) => { + await goToDatasetsList(page); + }); + + test("displays request access button", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_REQUIRED); + + // Confirm export button is visible. + const exportButton = getLinkWithText(page, BUTTON_TEXT_REQUEST_ACCESS); + await expect(exportButton).toBeVisible(); + }); + + test("displays export button", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_GRANTED); + + // Confirm export button is visible. + const exportButton = getLinkWithText(page, BUTTON_TEXT_EXPORT); + await expect(exportButton).toBeVisible(); + }); + + test("displays export method", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_GRANTED); + + // Confirm export button is visible and click it. + await clickLink(page, BUTTON_TEXT_EXPORT); + + // Confim Terra and file manifest export methods are listed. + await expect( + getLinkWithText(page, BUTTON_TEXT_ANALYZE_IN_TERRA) + ).toBeVisible(); + await expect( + getLinkWithText(page, BUTTON_TEXT_REQUEST_FILE_MANIFEST) + ).toBeVisible(); + }); + + test("displays download file manifest", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_GRANTED); + + // Confirm export button is visible and click it. + await clickLink(page, BUTTON_TEXT_EXPORT); + + // Confirm file manifest export method is visible and click it. + await clickLink(page, BUTTON_TEXT_REQUEST_FILE_MANIFEST); + + // Confirm the file manifest page is loaded: check there are two buttons + // (one for download, one for copy to clipboard). + const buttons = page.locator(`${MUI_BUTTON_GROUP_ROOT} button`); + + // Ensure there are exactly two buttons. + await expect(buttons).toHaveCount(2); + + // Ensure both buttons are visible. + await expect(buttons.nth(0)).toBeVisible(); + await expect(buttons.nth(1)).toBeVisible(); + }); + + test("displays login to download file manifest", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_REQUIRED); + + // Navigate to the export file manifest page. + const currentUrl = page.url(); + await page.goto(`${currentUrl}${ROUTE_MANIFEST_DOWNLOAD}`); + + // Confirm the login alert is displayed. + await expect( + page.locator( + `${MUI_ALERT_ROOT}:has-text("To download this dataset manifest, please sign in and, if necessary, request access.")` + ) + ).toBeVisible(); + }); + + test("displays download analyze in Terra", async ({ page }) => { + await goToDataset(page, CHIP_TEXT_ACCESS_GRANTED); + + // Confirm export button is visible and click it. + await clickLink(page, BUTTON_TEXT_EXPORT); + + // Confirm Terra export method is visible and click it. + await clickLink(page, BUTTON_TEXT_ANALYZE_IN_TERRA); + + // Confirm the analyze in Terra page is loaded: check the "coming soon" + // message is displayed. + await expect( + page.locator(`${MUI_ALERT_ROOT}:has-text("under development")`) + ).toBeVisible(); + }); +}); + +/** + * Click the link wit the given text. + * @param page - Playwright page object. + * @param buttonText - The text of the button to click. + */ +async function clickLink(page: Page, buttonText: string): Promise { + await getLinkWithText(page, buttonText).click(); +} + +/** + * Return the link with the given text. + * @param page - Playwright page object. + * @param buttonText - The text of the button to find. + * @returns - Playwright locator object for the dataset export button + */ +function getLinkWithText(page: Page, buttonText: string): Locator { + return page.locator(`a:has-text("${buttonText}")`); +} + +/** + * Navigate to the datasets list. + * @param page - Playwright page object. + */ +async function goToDatasetsList(page: Page): Promise { + if (page.url() !== "/") { + await page.goto("/"); + } +} + +/** + * Select a dataset with the given access from the datasets list and navigate to it. + * @param page - Playwright page object. + * @param access - The access of the dataset, either "Granted" or "Required" + */ +async function goToDataset(page: Page, access: DatasetAccess): Promise { + // Find a dataset that user has access to. + const datasetRow = page + .locator( + `${MUI_TABLE_ROOT} ${MUI_TABLE_ROW_ROOT}:has(${MUI_TABLE_CELL_ROOT}:has-text("${access}"))` + ) + .first(); + await expect(datasetRow).toBeVisible(); // Confirm at least one dataset has been found. + const datasetLink = datasetRow.locator( + `${MUI_TABLE_CELL_ROOT}:first-child a` + ); + const datasetTitle = await datasetLink.innerText(); + await datasetLink.click(); + + // Wait for the dataset detail page to load (specifically the dataset title). + await page.waitForSelector(`h1:has-text("${datasetTitle}")`); +} diff --git a/e2e/anvil/common/constants.tsx b/e2e/anvil/common/constants.tsx new file mode 100644 index 000000000..27b05a69b --- /dev/null +++ b/e2e/anvil/common/constants.tsx @@ -0,0 +1,10 @@ +export const CHIP_TEXT_ACCESS_GRANTED = "Granted"; +export const CHIP_TEXT_ACCESS_REQUIRED = "Required"; +export const BUTTON_TEXT_ANALYZE_IN_TERRA = "Analyze in Terra"; +export const BUTTON_TEXT_EXPORT = "Export"; +export const BUTTON_TEXT_REQUEST_ACCESS = "Request Access"; +export const BUTTON_TEXT_REQUEST_FILE_MANIFEST = "Request File Manifest"; + +export type DatasetAccess = + | typeof CHIP_TEXT_ACCESS_GRANTED + | typeof CHIP_TEXT_ACCESS_REQUIRED; diff --git a/e2e/features/common/constants.ts b/e2e/features/common/constants.ts new file mode 100644 index 000000000..bc57e390e --- /dev/null +++ b/e2e/features/common/constants.ts @@ -0,0 +1,5 @@ +export const MUI_ALERT_ROOT = ".MuiAlert-root"; +export const MUI_BUTTON_GROUP_ROOT = ".MuiButtonGroup-root"; +export const MUI_TABLE_CELL_ROOT = ".MuiTableCell-root"; +export const MUI_TABLE_ROOT = ".MuiTable-root"; +export const MUI_TABLE_ROW_ROOT = ".MuiTableRow-root"; diff --git a/e2e/testFunctions.ts b/e2e/testFunctions.ts index 2ec752b21..b194b7268 100644 --- a/e2e/testFunctions.ts +++ b/e2e/testFunctions.ts @@ -1,9 +1,5 @@ -import { BrowserContext, expect, Locator, Page } from "@playwright/test"; -import { - BackpageHeader, - ColumnDescription, - TabDescription, -} from "./testInterfaces"; +import { expect, Locator, Page } from "@playwright/test"; +import { ColumnDescription, TabDescription } from "./testInterfaces"; /* eslint-disable sonarjs/no-duplicate-string -- ignoring duplicate strings here */ @@ -804,20 +800,20 @@ export async function testDeselectFiltersThroughSearchBar( } } -/** - * 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 Playwright locator object to the first link to a backpage with the specified access - */ -const getBackpageLinkLocatorByAccess = (page: Page, access: string): Locator => - page - .getByRole("row") - .filter({ has: page.getByRole("cell", { name: access }) }) - .first() - .getByRole("cell") - .first() - .getByRole("link"); +// /** +// * 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 Playwright locator object to the first link to a backpage with the specified access +// */ +// const getBackpageLinkLocatorByAccess = (page: Page, access: string): Locator => +// page +// .getByRole("row") +// .filter({ has: page.getByRole("cell", { name: access }) }) +// .first() +// .getByRole("cell") +// .first() +// .getByRole("link"); /** * Make an export request that leaves only the minimal number of checkboxes selected @@ -864,147 +860,6 @@ const makeMinimalExportRequest = async ( await exportRequestButtonLocator.click(); }; -/** - * 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 - * @returns - true if the test passes, false if the test fails but does not fail an assertion - */ -export async function testExportBackpage( - context: BrowserContext, - page: Page, - tab: TabDescription -): Promise { - if ( - tab.backpageExportButtons === undefined || - tab.backpageAccessTags === undefined - ) { - // Fail if this test u on a tab without defined backpages - return false; - } - // 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 - // dispatchEvent necessary because the table loading component sometimes interrupts a click event - 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.getByText(tab.backpageExportButtons.requestLandingMessage) - ).toBeVisible(); - await expect(page).toHaveURL(tab.backpageExportButtons.exportUrlRegExp); - await expect(page.getByRole("checkbox").first()).toBeVisible(); - const exportRequestButtonLocator = page.getByRole("button", { - name: tab.backpageExportButtons.exportRequestButtonText, - }); - // Complete the export request form - await makeMinimalExportRequest(page, exportRequestButtonLocator); - await expect( - page.getByText(tab.backpageExportButtons.actionLandingMessage, { - exact: true, - }) - ).toBeVisible({ timeout: TIMEOUT_EXPORT_REQUEST }); - 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(); - return true; -} - -/** - * 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 object to test on - * @returns - true if the test passes, false if the test fails but does not fail an assertion - */ -export async function testBackpageAccess( - page: Page, - tab: TabDescription -): Promise { - if ( - tab.backpageExportButtons === undefined || - tab.backpageAccessTags === undefined - ) { - // Fail if this test is run on a tab without defined backpages - return false; - } - // 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(); - // dispatchEvent necessary because the table loading component sometimes interrupts a click event - 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(); - // dispatchEvent necessary because the table loading component sometimes interrupts a click event - 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(); - return true; -} - /** * 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 @@ -1050,102 +905,6 @@ const hoverAndGetText = async ( return cellText.trim(); }; -const FOOTER_LINK_NAME = "Privacy"; - -/** - * 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 - * @returns - true if the test passes, false if the test fails due to an issue with the tab configuration - */ -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 - console.log( - "BACKPAGE DETAILS error: tab is not set up with backpage info, so test cannot continue" - ); - return false; - } - await page.goto(tab.url); - // Enable test columns - await testSelectableColumns(page, tab); - const headers: { header: string; value: string }[] = []; - const preselectedColumnObjectArray = Array.from( - Object.values(tab.preselectedColumns) - ); - const selectableColumnObjectArray = Array.from( - Object.values(tab.selectableColumns) - ); - const combinedColumns = preselectedColumnObjectArray.concat( - selectableColumnObjectArray - ); - 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 nonOverlappingElement = page.getByRole("link", { - name: FOOTER_LINK_NAME, - }); - const tableEntryText = await hoverAndGetText( - page, - columnObject, - 0, - i, - nonOverlappingElement - ); - // 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 - console.log( - "BACKPAGE DETAILS error: backpageHeaders is configured incorrectly, so test cannot continue" - ); - return false; - } - 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(); - } - return true; -} - /** * Test that the Bulk Download index export workflow can be initiated and results in a download on the specified tab * @param page - a Playwright page object