diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-select-tabs.spec.ts b/explorer/e2e/anvil-catalog/anvilcatalog-select-tabs.spec.ts index 87e60043f..01f0152d8 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-select-tabs.spec.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-select-tabs.spec.ts @@ -3,32 +3,32 @@ import { testPreSelectedColumns, testSelectableColumns, } from "../testFunctions"; -import { anvilcatalogTabs } from "./anvilcatalog-tabs"; +import { ANVIL_CATALOG_TABS } from "./anvilcatalog-tabs"; test("Expect the checkboxes in the 'Edit Columns' menu to add those columns to the tab in the Consortia tab", async ({ page, }) => { - const tab = anvilcatalogTabs.consortia; + const tab = ANVIL_CATALOG_TABS.CONSORTIA; await testSelectableColumns(page, tab); }); -test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the consortia tab", async ({ +test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the Consortia tab", async ({ page, }) => { - const tab = anvilcatalogTabs.consortia; + const tab = ANVIL_CATALOG_TABS.CONSORTIA; await testPreSelectedColumns(page, tab); }); -test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the studies tab", async ({ +test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the Studies tab", async ({ page, }) => { - const tab = anvilcatalogTabs.studies; + const tab = ANVIL_CATALOG_TABS.STUDIES; await testPreSelectedColumns(page, tab); }); -test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the workspaces tab", async ({ +test("Expect the checkboxes for preselected columns in the 'Edit Columns' menu to be checked and disabled on the Workspaces tab", async ({ page, }) => { - const tab = anvilcatalogTabs.workspaces; + const tab = ANVIL_CATALOG_TABS.WORKSPACES; await testPreSelectedColumns(page, tab); }); diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-sort.spec.ts b/explorer/e2e/anvil-catalog/anvilcatalog-sort.spec.ts index bd2a12ff1..c94385d81 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-sort.spec.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-sort.spec.ts @@ -1,23 +1,29 @@ import { test } from "@playwright/test"; import { testSortCatalog } from "../testFunctions"; -import { anvilcatalogTabs } from "./anvilcatalog-tabs"; - -test.describe.configure({ mode: "parallel" }); - +import { ANVIL_CATALOG_TABS } from "./anvilcatalog-tabs"; test("On the Consortia tab, expect clicking the column header (the sort button) to keep the first element of the column visible", async ({ page, }) => { - await testSortCatalog(page, anvilcatalogTabs.consortia); + const testResult = await testSortCatalog(page, ANVIL_CATALOG_TABS.CONSORTIA); + if (!testResult) { + test.fail(); + } }); test("On the Studies tab, expect clicking the column header (the sort button) to keep the first element of the column visible", async ({ page, }) => { - await testSortCatalog(page, anvilcatalogTabs.studies); + const testResult = await testSortCatalog(page, ANVIL_CATALOG_TABS.STUDIES); + if (!testResult) { + test.fail(); + } }); test("On the Workspaces tab, expect clicking the column header (the sort button) to keep the first element of the column visible", async ({ page, }) => { - await testSortCatalog(page, anvilcatalogTabs.workspaces); + const testResult = await testSortCatalog(page, ANVIL_CATALOG_TABS.WORKSPACES); + if (!testResult) { + test.fail(); + } }); diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts b/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts index f507d7fe5..792d24989 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-tabs-buttons.spec.ts @@ -1,27 +1,29 @@ import { test } from "@playwright/test"; import { testTab } from "../testFunctions"; -import { anvilcatalogTabs } from "./anvilcatalog-tabs"; +import { ANVIL_CATALOG_TABS } from "./anvilcatalog-tabs"; -test("Expect clicking the consortia tab to go to the correct url and to show all of the relevant columns when selected", async ({ +test("Expect clicking the Consortia tab from the Studies tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilcatalogTabs.consortia; - await page.goto(anvilcatalogTabs.studies.url); - await testTab(page, tab); + await testTab(page, ANVIL_CATALOG_TABS.STUDIES, ANVIL_CATALOG_TABS.CONSORTIA); }); -test("Expect clicking the studies tab to go to the correct url and to show all of the relevant columns when selected", async ({ +test("Expect clicking the Studies tab from the Workspaces tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilcatalogTabs.studies; - await page.goto(anvilcatalogTabs.workspaces.url); - await testTab(page, tab); + await testTab( + page, + ANVIL_CATALOG_TABS.WORKSPACES, + ANVIL_CATALOG_TABS.STUDIES + ); }); -test("Expect clicking the workspaces tab to go to the correct url and to show all of the relevant columns when selected", async ({ +test("Expect clicking the Workspaces tab from the Consortia tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - const tab = anvilcatalogTabs.workspaces; - await page.goto(anvilcatalogTabs.consortia.url); - await testTab(page, tab); + await testTab( + page, + ANVIL_CATALOG_TABS.CONSORTIA, + ANVIL_CATALOG_TABS.WORKSPACES + ); }); diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-tabs.ts b/explorer/e2e/anvil-catalog/anvilcatalog-tabs.ts index b7e7f610c..6e3e035f3 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-tabs.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-tabs.ts @@ -1,69 +1,42 @@ /* eslint-disable sonarjs/no-duplicate-string -- ignoring duplicate strings here */ - import { AnvilCatalogTabCollection, TabDescription } from "../testInterfaces"; +import { + ANVIL_CATALOG_CONSORTIA_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_CATALOG_CONSORTIA_SELECTABLE_COLUMNS_BY_NAME, + ANVIL_CATALOG_STUDIES_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_CATALOG_STUDIES_SELECTABLE_COLUMNS_BY_NAME, + ANVIL_CATALOG_WORKSPACES_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_CATALOG_WORKSPACES_SELECTABLE_COLUMNS_BY_NAME, +} from "./constants"; -export const anvilcatalogTabs: AnvilCatalogTabCollection = { - consortia: { +export const ANVIL_CATALOG_TABS: AnvilCatalogTabCollection = { + CONSORTIA: { emptyFirstColumn: false, - preselectedColumns: [ - { name: "Consortium", sortable: true }, - { name: "dbGap Id", sortable: true }, - { name: "Consent Codes", sortable: true }, - { name: "Disease (indication)", sortable: true }, - { name: "Data Type", sortable: true }, - { name: "Study Design", sortable: true }, - { name: "Participants", sortable: true }, - { name: "Size (TB)", sortable: true }, - ], - selectableColumns: [ - { name: "Study", sortable: true }, - { name: "Workspaces", sortable: true }, - ], + preselectedColumns: ANVIL_CATALOG_CONSORTIA_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_CATALOG_CONSORTIA_SELECTABLE_COLUMNS_BY_NAME, tabName: "Consortia", url: "/data/consortia", }, - studies: { + STUDIES: { emptyFirstColumn: false, - preselectedColumns: [ - { name: "Study", sortable: true }, - { name: "dbGap Id", sortable: true }, - { name: "Consortium", sortable: true }, - { name: "Consent Codes", sortable: true }, - { name: "Disease (indication)", sortable: true }, - { name: "Data Type", sortable: true }, - { name: "Study Design", sortable: true }, - { name: "Workspaces", sortable: true }, - { name: "Participants", sortable: true }, - { name: "Size (TB)", sortable: true }, - ], - selectableColumns: [], + preselectedColumns: ANVIL_CATALOG_STUDIES_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_CATALOG_STUDIES_SELECTABLE_COLUMNS_BY_NAME, tabName: "Studies", url: "/data/studies", }, - workspaces: { + WORKSPACES: { emptyFirstColumn: false, - preselectedColumns: [ - { name: "Consortium", sortable: true }, - { name: "Terra Workspace", sortable: true }, - { name: "Study", sortable: true }, - { name: "dbGap Id", sortable: true }, - { name: "Consent Code", sortable: true }, - { name: "Disease (indication)", sortable: true }, - { name: "Data Type", sortable: true }, - { name: "Study Design", sortable: false }, - { name: "Participants", sortable: true }, - { name: "Size (TB)", sortable: true }, - ], - selectableColumns: [], + preselectedColumns: ANVIL_CATALOG_WORKSPACES_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_CATALOG_WORKSPACES_SELECTABLE_COLUMNS_BY_NAME, tabName: "Workspaces", url: "/data/workspaces", }, }; -export const anvilCatalogTabList: TabDescription[] = [ - anvilcatalogTabs.consortia, - anvilcatalogTabs.studies, - anvilcatalogTabs.workspaces, +export const ANVIL_CATALOG_TAB_LIST: TabDescription[] = [ + ANVIL_CATALOG_TABS.CONSORTIA, + ANVIL_CATALOG_TABS.STUDIES, + ANVIL_CATALOG_TABS.WORKSPACES, ]; /* eslint-enable sonarjs/no-duplicate-string -- Checking duplicate strings again*/ diff --git a/explorer/e2e/anvil-catalog/anvilcatalog-url.spec.ts b/explorer/e2e/anvil-catalog/anvilcatalog-url.spec.ts index 4cdc21ab4..a443e7021 100644 --- a/explorer/e2e/anvil-catalog/anvilcatalog-url.spec.ts +++ b/explorer/e2e/anvil-catalog/anvilcatalog-url.spec.ts @@ -1,24 +1,27 @@ import { test } from "@playwright/test"; import { testUrl } from "../testFunctions"; -import { anvilCatalogTabList, anvilcatalogTabs } from "./anvilcatalog-tabs"; +import { + ANVIL_CATALOG_TABS, + ANVIL_CATALOG_TAB_LIST, +} from "./anvilcatalog-tabs"; test("Expect the consortia tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilcatalogTabs.consortia; - await testUrl(page, tab, anvilCatalogTabList); + const tab = ANVIL_CATALOG_TABS.CONSORTIA; + await testUrl(page, tab, ANVIL_CATALOG_TAB_LIST); }); test("Expect the studies tab to appear as selected when the corresponding url is accessedb", async ({ page, }) => { - const tab = anvilcatalogTabs.studies; - await testUrl(page, tab, anvilCatalogTabList); + const tab = ANVIL_CATALOG_TABS.STUDIES; + await testUrl(page, tab, ANVIL_CATALOG_TAB_LIST); }); test("Expect the workspaces tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilcatalogTabs.workspaces; - await testUrl(page, tab, anvilCatalogTabList); + const tab = ANVIL_CATALOG_TABS.WORKSPACES; + await testUrl(page, tab, ANVIL_CATALOG_TAB_LIST); }); diff --git a/explorer/e2e/anvil-catalog/constants.ts b/explorer/e2e/anvil-catalog/constants.ts new file mode 100644 index 000000000..317a15740 --- /dev/null +++ b/explorer/e2e/anvil-catalog/constants.ts @@ -0,0 +1,209 @@ +export const ANVIL_CATALOG_COLUMN_NAMES = { + CONSENT_CODE: "Consent Code", + CONSENT_CODES: "Consent Codes", + CONSORTIUM: "Consortium", + DATA_TYPE: "Data Type", + DBGAP_ID: "dbGap Id", + DISEASE_INDICATION: "Disease (indication)", + PARTICIPANTS: "Participants", + SIZE_TB: "Size (TB)", + STUDY: "Study", + STUDY_DESIGN: "Study Design", + TERRA_WORKSPACE: "Terra Workspace", + WORKSPACES: "Workspaces", +}; + +export const PLURALIZED_METADATA_LABEL = { + [ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM]: "networks", + [ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID]: "dbGap ids", + [ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES]: "consent codes", + [ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION]: "diseases", + [ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE]: "data types", + [ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN]: "study designs", + [ANVIL_CATALOG_COLUMN_NAMES.WORKSPACES]: "workspaces", + [ANVIL_CATALOG_COLUMN_NAMES.STUDY]: "studies", + [ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODE]: "consent codes", + [ANVIL_CATALOG_COLUMN_NAMES.TERRA_WORKSPACE]: "workspaces", +}; + +export const ANVIL_CATALOG_CONSORTIA_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN]: { + name: ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS]: { + name: ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS, + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB]: { + name: ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB, + sortable: true, + }, +}; + +export const ANVIL_CATALOG_CONSORTIA_SELECTABLE_COLUMNS_BY_NAME = { + Study: { + name: "Study", + pluralizedLabel: "studies", + sortable: true, + }, + Workspaces: { + name: "Workspaces", + pluralizedLabel: "workspaces", + sortable: true, + }, +}; + +export const ANVIL_CATALOG_STUDIES_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_CATALOG_COLUMN_NAMES.STUDY]: { + name: ANVIL_CATALOG_COLUMN_NAMES.STUDY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.STUDY], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODES], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN]: { + name: ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS]: { + name: ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS, + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB]: { + name: ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB, + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.WORKSPACES]: { + name: ANVIL_CATALOG_COLUMN_NAMES.WORKSPACES, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.WORKSPACES], + sortable: true, + }, +}; + +export const ANVIL_CATALOG_STUDIES_SELECTABLE_COLUMNS_BY_NAME = {}; + +export const ANVIL_CATALOG_WORKSPACES_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_CATALOG_COLUMN_NAMES.TERRA_WORKSPACE]: { + name: ANVIL_CATALOG_COLUMN_NAMES.TERRA_WORKSPACE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.TERRA_WORKSPACE], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.STUDY]: { + name: ANVIL_CATALOG_COLUMN_NAMES.STUDY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.STUDY], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DBGAP_ID], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSORTIUM], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODE]: { + name: ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.CONSENT_CODE], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DISEASE_INDICATION], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE]: { + name: ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.DATA_TYPE], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN]: { + name: ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_CATALOG_COLUMN_NAMES.STUDY_DESIGN], + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS]: { + name: ANVIL_CATALOG_COLUMN_NAMES.PARTICIPANTS, + sortable: true, + }, + [ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB]: { + name: ANVIL_CATALOG_COLUMN_NAMES.SIZE_TB, + sortable: true, + }, +}; + +export const ANVIL_CATALOG_WORKSPACES_SELECTABLE_COLUMNS_BY_NAME = {}; diff --git a/explorer/e2e/anvil/anvil-backpages.spec.ts b/explorer/e2e/anvil/anvil-backpages.spec.ts index 593f958fc..958a6a269 100644 --- a/explorer/e2e/anvil/anvil-backpages.spec.ts +++ b/explorer/e2e/anvil/anvil-backpages.spec.ts @@ -4,26 +4,29 @@ import { testBackpageDetails, testExportBackpage, } from "../testFunctions"; -import { anvilTabs } from "./anvil-tabs"; +import { ANVIL_TABS } 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); + await testExportBackpage(context, page, ANVIL_TABS.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); + await testBackpageAccess(page, ANVIL_TABS.DATASETS); }); test("Check that information on the backpages matches information in the data tables", async ({ page, }) => { test.setTimeout(120000); - await testBackpageDetails(page, anvilTabs.datasets); + const testResult = await testBackpageDetails(page, ANVIL_TABS.DATASETS); + if (!testResult) { + test.fail(); + } }); diff --git a/explorer/e2e/anvil/anvil-filters.spec.ts b/explorer/e2e/anvil/anvil-filters.spec.ts index 12f8d5b6f..fcae7f30b 100644 --- a/explorer/e2e/anvil/anvil-filters.spec.ts +++ b/explorer/e2e/anvil/anvil-filters.spec.ts @@ -9,9 +9,9 @@ import { testFilterTags, } from "../testFunctions"; import { - anvilFilterNames, - anvilTabs, - anvilTabTestOrder, + ANVIL_FILTER_NAMES, + ANVIL_TABS, + ANVIL_TAB_TEST_ORDER, BIOSAMPLE_TYPE_INDEX, CONSENT_GROUP_INDEX, DATASET_INDEX, @@ -38,31 +38,31 @@ const FILTER_INDEX_LIST_SHORT = [ test("Check that all filters exist on the Datasets tab and are clickable", async ({ page, }) => { - await testFilterPresence(page, anvilTabs.datasets, anvilFilterNames); + await testFilterPresence(page, ANVIL_TABS.DATASETS, ANVIL_FILTER_NAMES); }); test("Check that all filters exist on the Donors tab and are clickable", async ({ page, }) => { - await testFilterPresence(page, anvilTabs.donors, anvilFilterNames); + await testFilterPresence(page, ANVIL_TABS.DONORS, ANVIL_FILTER_NAMES); }); test("Check that all filters exist on the BioSamples tab and are clickable", async ({ page, }) => { - await testFilterPresence(page, anvilTabs.biosamples, anvilFilterNames); + await testFilterPresence(page, ANVIL_TABS.BIOSAMPLES, ANVIL_FILTER_NAMES); }); test("Check that all filters exist on the Activities tab and are clickable", async ({ page, }) => { - await testFilterPresence(page, anvilTabs.activities, anvilFilterNames); + await testFilterPresence(page, ANVIL_TABS.ACTIVITIES, ANVIL_FILTER_NAMES); }); test("Check that all filters exist on the Files tab and are clickable", async ({ page, }) => { - await testFilterPresence(page, anvilTabs.files, anvilFilterNames); + await testFilterPresence(page, ANVIL_TABS.FILES, ANVIL_FILTER_NAMES); }); test("Check that the first filter on the Datasets tab creates at least one checkbox, and that checking up to the first five does not cause an error and does not cause there to be no entries in the table", async ({ @@ -70,9 +70,9 @@ test("Check that the first filter on the Datasets tab creates at least one check }) => { test.setTimeout(120000); // Goto the datasets tab - await page.goto(anvilTabs.datasets.url); + await page.goto(ANVIL_TABS.DATASETS.url); await expect( - page.getByRole("tab").getByText(anvilTabs.datasets.tabName) + page.getByRole("tab").getByText(ANVIL_TABS.DATASETS.tabName) ).toBeVisible(); // Select a filter @@ -80,7 +80,9 @@ test("Check that the first filter on the Datasets tab creates at least one check .getByRole("button") .getByText( filterRegex( - anvilFilterNames[Math.floor(Math.random() * anvilFilterNames.length)] + ANVIL_FILTER_NAMES[ + Math.floor(Math.random() * ANVIL_FILTER_NAMES.length) + ] ) ) .click(); @@ -104,8 +106,8 @@ test("Check that filter checkboxes are persistent across pages on an arbitrary f test.setTimeout(120000); const result = await testFilterPersistence( page, - anvilFilterNames[FILE_FORMAT_INDEX], - anvilTabTestOrder.map((x) => anvilTabs[x]) + ANVIL_FILTER_NAMES[FILE_FORMAT_INDEX], + ANVIL_TAB_TEST_ORDER.map((x) => ANVIL_TABS[x]) ); if (!result) { test.fail(); @@ -118,9 +120,9 @@ test("Check that filter menu counts match actual counts on the Datasets tab", as test.setTimeout(120000); const result = await testFilterCounts( page, - anvilTabs.datasets, - FILTER_INDEX_LIST.map((x) => anvilFilterNames[x]), - anvilTabs.datasets.maxPages ?? 0 + ANVIL_TABS.DATASETS, + FILTER_INDEX_LIST.map((x) => ANVIL_FILTER_NAMES[x]), + ANVIL_TABS.DATASETS.maxPages ?? 0 ); if (!result) { test.fail(); @@ -133,9 +135,9 @@ test("Check that filter menu counts match actual counts on the Activities tab", test.setTimeout(120000); await testFilterCounts( page, - anvilTabs.activities, - FILTER_INDEX_LIST.map((x) => anvilFilterNames[x]), - anvilTabs.activities.maxPages ?? 0 + ANVIL_TABS.ACTIVITIES, + FILTER_INDEX_LIST.map((x) => ANVIL_FILTER_NAMES[x]), + ANVIL_TABS.ACTIVITIES.maxPages ?? 0 ); }); @@ -145,8 +147,8 @@ test("Check that the filter tags match the selected filter for an arbitrary filt test.setTimeout(120000); await testFilterTags( page, - anvilTabs.files, - FILTER_INDEX_LIST_SHORT.map((x) => anvilFilterNames[x]) + ANVIL_TABS.FILES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) ); }); @@ -156,8 +158,8 @@ test("Check that the filter tags match the selected filter for an arbitrary filt test.setTimeout(120000); await testFilterTags( page, - anvilTabs.biosamples, - FILTER_INDEX_LIST_SHORT.map((x) => anvilFilterNames[x]) + ANVIL_TABS.BIOSAMPLES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) ); }); @@ -167,7 +169,7 @@ test("Check that the clear all button functions on the files tab", async ({ test.setTimeout(120000); await testClearAll( page, - anvilTabs.files, - FILTER_INDEX_LIST_SHORT.map((x) => anvilFilterNames[x]) + ANVIL_TABS.FILES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) ); }); diff --git a/explorer/e2e/anvil/anvil-pagination-content.spec.ts b/explorer/e2e/anvil/anvil-pagination-content.spec.ts deleted file mode 100644 index 2c4b90445..000000000 --- a/explorer/e2e/anvil/anvil-pagination-content.spec.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { getFirstRowNthColumnCellLocator } from "../testFunctions"; -import { anvilTabs } from "./anvil-tabs"; - -const pageCountRegex = /Page [0-9]+ of [0-9]+/; -const BackButtonTestID = "WestRoundedIcon"; -const ForwardButtonTestID = "EastRoundedIcon"; - -test.setTimeout(90000); -test("Check forward and backwards pagination causes the page content to change on the Biosamples page", async ({ - page, -}) => { - const tab = anvilTabs.biosamples; - // Navigate to the BioSamples page - await page.goto(tab.url); - await expect( - page.getByRole("tab").getByText(tab.tabName, { exact: true }) - ).toHaveAttribute("aria-selected", "true", { timeout: 25000 }); - - const firstElementTextLocator = getFirstRowNthColumnCellLocator(page, 0); - - // Should start on first page - await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( - /Page 1 of [0-9]+/ - ); - const max_pages = 5; - const FirstTableEntries = []; - - // Paginate forwards - for (let i = 2; i < max_pages + 1; i++) { - await expect(firstElementTextLocator).not.toHaveText(""); - const OriginalFirstTableEntry = await firstElementTextLocator.innerText(); - // Click the next button - await page - .getByRole("button") - .filter({ has: page.getByTestId(ForwardButtonTestID) }) - .click(); - // Expect the page count to have incremented - await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( - RegExp(`Page ${i} of [0-9]+`) - ); - // Expect the back button to be enabled - await expect( - page - .getByRole("button") - .filter({ has: page.getByTestId(BackButtonTestID) }) - ).toBeEnabled(); - // Expect the forwards button to be enabled - if (i != max_pages) { - await expect( - page - .getByRole("button") - .filter({ has: page.getByTestId(ForwardButtonTestID) }) - ).toBeEnabled(); - } - // Expect the first entry to have changed on the new page - await expect(firstElementTextLocator).not.toHaveText( - OriginalFirstTableEntry - ); - // Remember the first entry - FirstTableEntries.push(OriginalFirstTableEntry); - } - - // Paginate backwards - for (let i = 0; i < max_pages - 1; i++) { - const OldFirstTableEntry = FirstTableEntries[max_pages - i - 2]; - await page - .getByRole("button") - .filter({ has: page.getByTestId(BackButtonTestID) }) - .click(); - // Expect page number to be correct - await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( - RegExp(`Page ${max_pages - i - 1} of [0-9]+`) - ); - // Expect page entry to be consistent with forward pagination - await expect(firstElementTextLocator).toHaveText(OldFirstTableEntry); - } -}); diff --git a/explorer/e2e/anvil/anvil-pagination.spec.ts b/explorer/e2e/anvil/anvil-pagination.spec.ts index 15e6a7c4b..de3881d5f 100644 --- a/explorer/e2e/anvil/anvil-pagination.spec.ts +++ b/explorer/e2e/anvil/anvil-pagination.spec.ts @@ -1,79 +1,38 @@ -import { expect, test } from "@playwright/test"; -import { anvilTabs } from "./anvil-tabs"; - -const pageCountRegex = /Page [0-9]+ of [0-9]+/; -const BackButtonTestID = "WestRoundedIcon"; -const ForwardButtonTestID = "EastRoundedIcon"; - -test.beforeEach(async ({ page }) => { - // Navigate to the Donors page - await page.goto(anvilTabs.donors.url); - await expect( - page.getByRole("tab").getByText(anvilTabs.donors.tabName) - ).toBeVisible(); -}); +import { test } from "@playwright/test"; +import { + filterAndTestLastPagePagination, + testFirstPagePagination, + testPaginationContent, +} from "../testFunctions"; +import { + ANVIL_FILTER_NAMES, + ANVIL_TABS, + FILE_FORMAT_INDEX, +} from "./anvil-tabs"; test("Check first page has disabled back and enabled forward pagination buttons on Donors Page", async ({ page, }) => { - // Should start on first page - await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( - /Page 1 of [0-9]+/ - ); - // Forward button should start enabled - await expect( - page - .getByRole("button") - .filter({ has: page.getByTestId(ForwardButtonTestID) }) - ).toBeEnabled(); - // Back Button should start disabled - await expect( - page.getByRole("button").filter({ has: page.getByTestId(BackButtonTestID) }) - ).toBeDisabled(); + await testFirstPagePagination(page, ANVIL_TABS.DONORS); }); -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 ({ +test("Paginate through the entire Files tab to confirm that the page number stays consistent and that paginating forwards is disabled on the last page. Uses filters to reduce the amount of calls necessary", 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]+/ - ); - await expect(page.getByText(pageCountRegex, { exact: true })).not.toHaveText( - "Page 1 of 1" - ); - - // Detect number of pages - const SplitStartingPageText = ( - await page.getByText(pageCountRegex, { exact: true }).innerText() - ).split(" "); - const max_pages = parseInt( - SplitStartingPageText[SplitStartingPageText.length - 1] + const result = await filterAndTestLastPagePagination( + page, + ANVIL_TABS.FILES, + ANVIL_FILTER_NAMES[FILE_FORMAT_INDEX] ); - // Paginate forwards - for (let i = 2; i < max_pages + 1; i++) { - await page - .getByRole("button") - .filter({ has: page.getByTestId(ForwardButtonTestID) }) - .click(); - // Expect the page count to have incremented - await expect(page.getByText(pageCountRegex, { exact: true })).toHaveText( - `Page ${i} of ${max_pages}` - ); + if (!result) { + test.fail(); } - // Expect to be on the last page - await expect(page.getByText(pageCountRegex, { exact: true })).toContainText( - `Page ${max_pages} of ${max_pages}` - ); - // Expect the back button to be enabled on the last page - await expect( - page.getByRole("button").filter({ has: page.getByTestId(BackButtonTestID) }) - ).toBeEnabled(); - // Expect the forward button to be disabled - await expect( - page - .getByRole("button") - .filter({ has: page.getByTestId(ForwardButtonTestID) }) - ).toBeDisabled(); +}); + +test("Check forward and backwards pagination causes the page content to change on the Biosamples page", async ({ + page, +}) => { + test.setTimeout(90000); + await testPaginationContent(page, ANVIL_TABS.BIOSAMPLES); }); diff --git a/explorer/e2e/anvil/anvil-sort.spec.ts b/explorer/e2e/anvil/anvil-sort.spec.ts index 4f1b1111b..463530a3a 100644 --- a/explorer/e2e/anvil/anvil-sort.spec.ts +++ b/explorer/e2e/anvil/anvil-sort.spec.ts @@ -1,40 +1,53 @@ import { test } from "@playwright/test"; import { testSortAzul } from "../testFunctions"; -import { anvilTabs } from "./anvil-tabs"; - -test.describe.configure({ mode: "parallel" }); +import { ANVIL_TABS } from "./anvil-tabs"; 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); + const testResult = await testSortAzul(page, ANVIL_TABS.DATASETS); + if (!testResult) { + test.fail(); + } }); 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); + const testResult = await testSortAzul(page, ANVIL_TABS.DONORS); + if (!testResult) { + test.fail(); + } }); 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); + const testResult = await testSortAzul(page, ANVIL_TABS.BIOSAMPLES); + if (!testResult) { + test.fail(); + } }); 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); + const testResult = await testSortAzul(page, ANVIL_TABS.ACTIVITIES); + if (!testResult) { + test.fail(); + } }); 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); + const testResult = await testSortAzul(page, ANVIL_TABS.FILES); + if (!testResult) { + test.fail(); + } }); diff --git a/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts b/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts index a85c96f6d..fdc4c466c 100644 --- a/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts +++ b/explorer/e2e/anvil/anvil-tabs-buttons.spec.ts @@ -1,38 +1,33 @@ import { test } from "@playwright/test"; import { testTab } from "../testFunctions"; -import { anvilTabs } from "./anvil-tabs"; +import { ANVIL_TABS } from "./anvil-tabs"; -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, }) => { - await page.goto(anvilTabs.activities.url); - await testTab(page, anvilTabs.datasets); + await testTab(page, ANVIL_TABS.DATASETS, ANVIL_TABS.ACTIVITIES); }); -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, }) => { - await page.goto(anvilTabs.datasets.url); - await testTab(page, anvilTabs.activities); + await testTab(page, ANVIL_TABS.ACTIVITIES, ANVIL_TABS.DATASETS); }); test("Expect clicking the files tab to go to the correct url and to show all of the relevant columns when selected", async ({ page, }) => { - await page.goto(anvilTabs.datasets.url); - await testTab(page, anvilTabs.files); + await testTab(page, ANVIL_TABS.DATASETS, ANVIL_TABS.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, }) => { - await page.goto(anvilTabs.datasets.url); - await testTab(page, anvilTabs.donors); + await testTab(page, ANVIL_TABS.DATASETS, ANVIL_TABS.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, }) => { - await page.goto(anvilTabs.datasets.url); - await testTab(page, anvilTabs.biosamples); + await testTab(page, ANVIL_TABS.DATASETS, ANVIL_TABS.BIOSAMPLES); }); diff --git a/explorer/e2e/anvil/anvil-tabs.ts b/explorer/e2e/anvil/anvil-tabs.ts index b7601f41b..58616689e 100644 --- a/explorer/e2e/anvil/anvil-tabs.ts +++ b/explorer/e2e/anvil/anvil-tabs.ts @@ -5,12 +5,20 @@ import { TabDescription, } from "../testInterfaces"; import { + ANVIL_ACTIVITIES_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_ACTIVITIES_SELECTABLE_COLUMNS_BY_NAME, + ANVIL_BIOSAMPLES_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_BIOSAMPLES_SELECTABLE_COLUMNS_BY_NAME, ANVIL_COLUMN_NAMES, ANVIL_DATASETS_BACKPAGE_HEADER_NAMES, - ANVIL_PLURALIZED_METADATA_LABELS, + ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_DATASETS_SELECTABLE_COLUMNS_BY_NAME, + ANVIL_DONORS_PRESELECTED_COLUMNS_BY_NAME, + ANVIL_DONORS_SELECTABLE_COLUMNS_BY_NAME, + ANVIL_FILES_PRESELECTED_COLUMNS_BY_NAME, } from "./constants"; -export const anvilFilterNames: string[] = [ +export const ANVIL_FILTER_NAMES: string[] = [ "Anatomical Site", "BioSample Type", "Consent Group", @@ -35,75 +43,24 @@ 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: { +export const ANVIL_TABS: AnvilCMGTabCollection = { + ACTIVITIES: { emptyFirstColumn: false, maxPages: 25, - preselectedColumns: [ - { 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: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, sortable: true }, - { name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, sortable: true }, - { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, - ], + preselectedColumns: ANVIL_ACTIVITIES_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_ACTIVITIES_SELECTABLE_COLUMNS_BY_NAME, tabName: "Activities", url: "/activities", }, - biosamples: { + BIOSAMPLES: { emptyFirstColumn: false, maxPages: 25, - preselectedColumns: [ - { 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: 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, - }, - ], + preselectedColumns: ANVIL_BIOSAMPLES_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_BIOSAMPLES_SELECTABLE_COLUMNS_BY_NAME, tabName: "BioSamples", url: "/biosamples", }, - datasets: { + DATASETS: { backpageAccessTags: { deniedLongName: "Access Required", deniedShortName: "Required", @@ -128,88 +85,87 @@ export const anvilTabs: AnvilCMGTabCollection = { name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DATASET_ID, }, { - correspondingColumn: anvilDatasetsPreselectedColumns[3], + correspondingColumn: + ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.DATASET + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.CONSENT_GROUP, }, { - correspondingColumn: anvilDatasetsPreselectedColumns[4], + correspondingColumn: + ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.ORGANISM_TYPE + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.ORGANISM_TYPE, }, { - correspondingColumn: anvilDatasetsPreselectedColumns[5], + correspondingColumn: + ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.DIAGNOSIS + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DIAGNOSIS, }, { - correspondingColumn: anvilDatasetsPreselectedColumns[6], + correspondingColumn: + ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.DATA_MODALITY + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.DATA_MODALITY, }, { - correspondingColumn: anvilDatasetsSelectableColumns[0], + correspondingColumn: + ANVIL_DATASETS_SELECTABLE_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.PHENOTYPIC_SEX, }, { - correspondingColumn: anvilDatasetsSelectableColumns[1], + correspondingColumn: + ANVIL_DATASETS_SELECTABLE_COLUMNS_BY_NAME[ + ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY + ], name: ANVIL_DATASETS_BACKPAGE_HEADER_NAMES.REPORTED_ETHNICITY, }, ], emptyFirstColumn: false, maxPages: 25, - preselectedColumns: anvilDatasetsPreselectedColumns, - selectableColumns: anvilDatasetsSelectableColumns, + preselectedColumns: ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_DATASETS_SELECTABLE_COLUMNS_BY_NAME, tabName: "Datasets", url: "/datasets", }, - donors: { + DONORS: { emptyFirstColumn: false, maxPages: 25, - preselectedColumns: [ - { 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: [], + preselectedColumns: ANVIL_DONORS_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_DONORS_SELECTABLE_COLUMNS_BY_NAME, tabName: "Donors", url: "/donors", }, - files: { + FILES: { emptyFirstColumn: true, maxPages: 25, - preselectedColumns: [ - { 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: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, sortable: true }, - { name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, sortable: true }, - { name: ANVIL_COLUMN_NAMES.DIAGNOSIS, sortable: true }, - ], + preselectedColumns: ANVIL_FILES_PRESELECTED_COLUMNS_BY_NAME, + selectableColumns: ANVIL_DONORS_SELECTABLE_COLUMNS_BY_NAME, tabName: "Files", url: "/files", }, }; -export const anvilTabList: TabDescription[] = [ - anvilTabs.activities, - anvilTabs.datasets, - anvilTabs.biosamples, - anvilTabs.donors, - anvilTabs.files, +export const ANVIL_TAB_LIST: TabDescription[] = [ + ANVIL_TABS.ACTIVITIES, + ANVIL_TABS.DATASETS, + ANVIL_TABS.BIOSAMPLES, + ANVIL_TABS.DONORS, + ANVIL_TABS.FILES, ]; -export const anvilTabTestOrder: TabCollectionKeys[] = [ - "files", - "datasets", - "activities", - "donors", - "biosamples", +export const ANVIL_TAB_TEST_ORDER: TabCollectionKeys[] = [ + "FILES", + "DATASETS", + "ACTIVITIES", + "DONORS", + "BIOSAMPLES", ]; /* eslint-enable sonarjs/no-duplicate-string -- Checking duplicate strings again*/ diff --git a/explorer/e2e/anvil/anvil-urls.spec.ts b/explorer/e2e/anvil/anvil-urls.spec.ts index 8ce841e68..b37fa0de4 100644 --- a/explorer/e2e/anvil/anvil-urls.spec.ts +++ b/explorer/e2e/anvil/anvil-urls.spec.ts @@ -1,38 +1,38 @@ import { test } from "@playwright/test"; import { testUrl } from "../testFunctions"; -import { anvilTabList, anvilTabs } from "./anvil-tabs"; +import { ANVIL_TABS, ANVIL_TAB_LIST } from "./anvil-tabs"; test("Expect the activities tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilTabs.activities; - await testUrl(page, tab, anvilTabList); + const tab = ANVIL_TABS.ACTIVITIES; + await testUrl(page, tab, ANVIL_TAB_LIST); }); test("Expect the datasets tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilTabs.datasets; - await testUrl(page, tab, anvilTabList); + const tab = ANVIL_TABS.DATASETS; + await testUrl(page, tab, ANVIL_TAB_LIST); }); test("Expect the files tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilTabs.files; - await testUrl(page, tab, anvilTabList); + const tab = ANVIL_TABS.FILES; + await testUrl(page, tab, ANVIL_TAB_LIST); }); test("Expect the donors tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilTabs.donors; - await testUrl(page, tab, anvilTabList); + const tab = ANVIL_TABS.DONORS; + await testUrl(page, tab, ANVIL_TAB_LIST); }); test("Expect the biosamples tab to appear as selected when the corresponding url is accessed", async ({ page, }) => { - const tab = anvilTabs.biosamples; - await testUrl(page, tab, anvilTabList); + const tab = ANVIL_TABS.BIOSAMPLES; + await testUrl(page, tab, ANVIL_TAB_LIST); }); diff --git a/explorer/e2e/anvil/constants.ts b/explorer/e2e/anvil/constants.ts index bd38caa72..950f99c9a 100644 --- a/explorer/e2e/anvil/constants.ts +++ b/explorer/e2e/anvil/constants.ts @@ -1,3 +1,5 @@ +import { ColumnDescription } from "e2e/testInterfaces"; + export const ANVIL_DATASETS_BACKPAGE_HEADER_NAMES = { CONSENT_GROUP: "Consent group", DATASET_ID: "Dataset Id", @@ -30,9 +32,272 @@ export const ANVIL_COLUMN_NAMES = { SIZE: "Size", }; -export const ANVIL_PLURALIZED_METADATA_LABELS = { - DATA_MODALITY: "data modalities", - DIAGNOSIS: "diagnoses", - PHENOTYPIC_SEX: "phenotypic sexes", - REPORTED_ETHNICITIES: "reported ethnicities", +export const PLURALIZED_METADATA_LABEL = { + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: "diagnoses", + [ANVIL_COLUMN_NAMES.DATA_MODALITY]: "data modalities", + [ANVIL_COLUMN_NAMES.DATASET]: "dataset names", + [ANVIL_COLUMN_NAMES.CONSENT_GROUP]: "consent codes", + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: "organism types", + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: "phenotypic sexes", + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: "reported ethnicities", + [ANVIL_COLUMN_NAMES.ACTIVITY_TYPE]: "activity types", + [ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE]: "biosample types", + [ANVIL_COLUMN_NAMES.ANATOMICAL_SITE]: "anatomical entities", + [ANVIL_COLUMN_NAMES.FILE_FORMAT]: "file formats", +}; + +export const ANVIL_DATASETS_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: { + name: ANVIL_COLUMN_NAMES.DIAGNOSIS, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATA_MODALITY]: { + name: ANVIL_COLUMN_NAMES.DATA_MODALITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATA_MODALITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ACCESS]: { + name: ANVIL_COLUMN_NAMES.ACCESS, + sortable: false, + }, + [ANVIL_COLUMN_NAMES.IDENTIFIER]: { + name: ANVIL_COLUMN_NAMES.IDENTIFIER, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.CONSENT_GROUP]: { + name: ANVIL_COLUMN_NAMES.CONSENT_GROUP, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.CONSENT_GROUP], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, +}; + +export const ANVIL_DATASETS_SELECTABLE_COLUMNS_BY_NAME: { + [k: string]: ColumnDescription; +} = { + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, +}; + +export const ANVIL_ACTIVITIES_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.DOCUMENT_ID]: { + name: ANVIL_COLUMN_NAMES.DOCUMENT_ID, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ACTIVITY_TYPE]: { + name: ANVIL_COLUMN_NAMES.ACTIVITY_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ACTIVITY_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATA_MODALITY]: { + name: ANVIL_COLUMN_NAMES.DATA_MODALITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATA_MODALITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE]: { + name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, +}; + +export const ANVIL_ACTIVITIES_SELECTABLE_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: { + name: ANVIL_COLUMN_NAMES.DIAGNOSIS, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], + sortable: true, + }, +}; + +export const ANVIL_BIOSAMPLES_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.BIOSAMPLE_ID]: { + name: ANVIL_COLUMN_NAMES.BIOSAMPLE_ID, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ANATOMICAL_SITE]: { + name: ANVIL_COLUMN_NAMES.ANATOMICAL_SITE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ANATOMICAL_SITE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE]: { + name: ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.BIOSAMPLE_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: { + name: ANVIL_COLUMN_NAMES.DIAGNOSIS, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, +}; + +export const ANVIL_BIOSAMPLES_SELECTABLE_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, +}; + +export const ANVIL_DONORS_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.DONOR_ID]: { + name: ANVIL_COLUMN_NAMES.DONOR_ID, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: { + name: ANVIL_COLUMN_NAMES.DIAGNOSIS, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, +}; + +export const ANVIL_DONORS_SELECTABLE_COLUMNS_BY_NAME = {}; + +export const ANVIL_FILES_PRESELECTED_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.NAME]: { + name: ANVIL_COLUMN_NAMES.NAME, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.FILE_FORMAT]: { + name: ANVIL_COLUMN_NAMES.FILE_FORMAT, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.FILE_FORMAT], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.SIZE]: { + name: ANVIL_COLUMN_NAMES.SIZE, + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DRS_URI]: { + name: ANVIL_COLUMN_NAMES.DRS_URI, + sortable: false, + }, + [ANVIL_COLUMN_NAMES.DATA_MODALITY]: { + name: ANVIL_COLUMN_NAMES.DATA_MODALITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATA_MODALITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.ORGANISM_TYPE]: { + name: ANVIL_COLUMN_NAMES.ORGANISM_TYPE, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.ORGANISM_TYPE], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DATASET]: { + name: ANVIL_COLUMN_NAMES.DATASET, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DATASET], + sortable: true, + }, +}; + +export const ANVIL_FILES_SELECTABLE_COLUMNS_BY_NAME = { + [ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX]: { + name: ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.PHENOTYPIC_SEX], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY]: { + name: ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY, + pluralizedLabel: + PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.REPORTED_ETHNICITY], + sortable: true, + }, + [ANVIL_COLUMN_NAMES.DIAGNOSIS]: { + name: ANVIL_COLUMN_NAMES.DIAGNOSIS, + pluralizedLabel: PLURALIZED_METADATA_LABEL[ANVIL_COLUMN_NAMES.DIAGNOSIS], + sortable: true, + }, }; diff --git a/explorer/e2e/testFunctions.ts b/explorer/e2e/testFunctions.ts index 272f4b36f..71b89fac2 100644 --- a/explorer/e2e/testFunctions.ts +++ b/explorer/e2e/testFunctions.ts @@ -7,6 +7,17 @@ import { /* eslint-disable sonarjs/no-duplicate-string -- ignoring duplicate strings here */ +/** + * Get an array of all visible column header names + * @param page - a Playwright page object + * @returns an array of the text of all visible column headers + */ +const getAllVisibleColumnNames = async (page: Page): Promise => { + return (await page.getByRole("columnheader").allInnerTexts()).map((entry) => + entry.trim() + ); +}; + /** * Get a locator to the cell in the mth row's nth column * @param page - a Playwright page object @@ -41,6 +52,25 @@ export const getFirstRowNthColumnCellLocator = ( return getMthRowNthColumnCellLocator(page, 0, 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 getLastRowNthColumnTextLocator = ( + page: Page, + columnIndex: number +): Locator => { + return page + .getByRole("rowgroup") + .nth(1) + .getByRole("row") + .last() + .getByRole("cell") + .nth(columnIndex); +}; + /** * Tests that the tab url goes to a valid page and that the correct tab (and only * the correct tab) appears selected @@ -53,7 +83,9 @@ export async function testUrl( tab: TabDescription, otherTabs: TabDescription[] ): Promise { + // Go to the selected tab await page.goto(tab.url); + // Check that the selected tab appears selected and the other tabs appear deselected await expect( page.getByRole("tab").getByText(tab.tabName, { exact: true }) ).toHaveAttribute("aria-selected", "true", { timeout: 25000 }); @@ -66,35 +98,42 @@ export async function testUrl( } } -// Run the "Expect each tab to become selected, to go to the correct url, and to show all of its columns when selected" test /** * Checks that all preselected columns listed in the tab object are visible in the correct order * @param page - a Playwright page object - * @param tab - the tab object to test + * @param startTab - the tab object to start testing on + * @param endTab - the tab to select during the test */ -export async function testTab(page: Page, tab: TabDescription): Promise { - await expect( - page - .getByRole("rowgroup") - .nth(1) - .getByRole("row") - .nth(1) - .getByRole("cell") - .nth(1) - ).toBeVisible(); - await page.getByRole("tab").getByText(tab.tabName, { exact: true }).click(); - await expect(page).toHaveURL(tab.url, { timeout: 25000 }); // Long timeout because some tabs take a long time to load - await expect(page.getByRole("tab").getByText(tab.tabName)).toHaveAttribute( +export async function testTab( + page: Page, + startTab: TabDescription, + endTab: TabDescription +): Promise { + // Run the "Expect each tab to become selected, to go to the correct url, and to show all of its columns when selected" test + await page.goto(startTab.url); + await expect(getFirstRowNthColumnCellLocator(page, 1)).toBeVisible(); + await page + .getByRole("tab") + .getByText(endTab.tabName, { exact: true }) + .click(); + await expect(page).toHaveURL(endTab.url, { timeout: 25000 }); // Long timeout because some tabs take a long time to load + await expect(page.getByRole("tab").getByText(endTab.tabName)).toHaveAttribute( "aria-selected", "true" ); - if (tab.emptyFirstColumn) { - await expect(page.getByRole("columnheader")).toHaveText( - [" "].concat(tab.preselectedColumns.map((x) => x.name)) + const columnArray = Array.from(Object.values(endTab.preselectedColumns)); + for (const column of columnArray) { + await expect( + page.getByRole("columnheader").getByText(column.name, { exact: true }) + ).toBeVisible(); + } + if (endTab.emptyFirstColumn) { + await expect(page.getByRole("columnheader")).toHaveCount( + columnArray.length + 1 ); } else { - await expect(page.getByRole("columnheader")).toHaveText( - tab.preselectedColumns.map((x) => x.name) + await expect(page.getByRole("columnheader")).toHaveCount( + columnArray.length ); } } @@ -106,21 +145,35 @@ export async function testTab(page: Page, tab: TabDescription): Promise { * a catalog, so that the last element is visible without excessive scrolling. * @param page - a Playwright page object * @param tab - the tab to check + * @returns - true if the test passes and false if the test fails */ export async function testSortAzul( page: Page, tab: TabDescription -): Promise { +): Promise { // Get the current tab, and go to it's URL await page.goto(tab.url); // For each column + const columnNameArray = (await getAllVisibleColumnNames(page)).slice( + tab.emptyFirstColumn ? 1 : 0 + ); + const columnObjectArray = Array.from(Object.values(tab.preselectedColumns)); for ( let columnPosition = 0; - columnPosition < tab.preselectedColumns.length; + columnPosition < columnNameArray.length; columnPosition++ ) { // Get the column position, taking into account that some tabs start with a non-text first column - if (tab.preselectedColumns[columnPosition].sortable) { + const columnObject = columnObjectArray.find( + (x) => x.name === columnNameArray[columnPosition] + ); + if (columnObject === undefined) { + console.log( + `SORT AZUL: Preselected column object ${columnNameArray[columnPosition]} not found in tab configuration` + ); + return false; + } + if (columnObject?.sortable) { const columnIndex: number = tab.emptyFirstColumn ? columnPosition + 1 : columnPosition; @@ -129,21 +182,17 @@ export async function testSortAzul( page, columnIndex ); - const lastElementTextLocator = page - .getByRole("rowgroup") - .nth(1) - .getByRole("row") - .last() - .getByRole("cell") - .nth(columnIndex); - // Locator for the sort button for the tab + const lastElementTextLocator = getLastRowNthColumnTextLocator( + page, + columnIndex + ); + // Locator for the sort button const columnSortLocator = page .getByRole("columnheader", { exact: true, - name: tab.preselectedColumns[columnPosition].name, + name: columnNameArray[columnPosition], }) .getByRole("button"); - // Expect the first and last cells to be visible and have text await expect(firstElementTextLocator).toBeVisible(); await expect(lastElementTextLocator).toBeVisible(); @@ -161,6 +210,7 @@ export async function testSortAzul( await expect(lastElementTextLocator).not.toHaveText(""); } } + return true; } /** @@ -170,34 +220,45 @@ export async function testSortAzul( * so it only checks the first element of the table. * @param page - a Playwright page object * @param tab - the tab to check + * @returns - true if the test passes, false if the test fails */ export async function testSortCatalog( page: Page, tab: TabDescription -): Promise { +): Promise { // Get the current tab, and go to it's URL await page.goto(tab.url); - // For each column + await expect(getFirstRowNthColumnCellLocator(page, 0)).toBeVisible(); + const columnNameArray = ( + await page.getByRole("columnheader").allInnerTexts() + ).map((entry) => entry.trim()); + console.log(columnNameArray); + const columnObjectArray = Array.from(Object.values(tab.preselectedColumns)); for ( let columnPosition = 0; - columnPosition < tab.preselectedColumns.length; + columnPosition < columnNameArray.length; columnPosition++ ) { + const columnName = columnNameArray[columnPosition]; // Get the column position, taking into account that some tabs start with a non-text first column - if (tab.preselectedColumns[columnPosition].sortable) { - const columnIndex: number = tab.emptyFirstColumn - ? columnPosition + 1 - : columnPosition; - // Locators for the first and last cells in a particular column position on the page + const columnObject = columnObjectArray.find((x) => x.name === columnName); + if (columnObject === undefined) { + console.log( + `SORT CATALOG: Preselected column object ${columnName} not found in tab configuration` + ); + return false; + } + if (columnObject.sortable) { + // Locators for the first cell in a particular column position on the page const firstElementTextLocator = getFirstRowNthColumnCellLocator( page, - columnIndex + columnPosition ); // Locator for the sort button const columnSortLocator = page .getByRole("columnheader", { exact: true, - name: tab.preselectedColumns[columnPosition].name, + name: columnName, }) .getByRole("button"); await expect(firstElementTextLocator).toBeVisible(); @@ -205,13 +266,20 @@ export async function testSortCatalog( await columnSortLocator.click(); // Expect the first cell to still be visible await expect(firstElementTextLocator).toBeVisible(); + const firstElementText = await hoverAndGetText( + page, + columnObject, + 0, + columnPosition + ); // Click again await columnSortLocator.click(); // Expect the first cell to have changed after clicking sort await expect(firstElementTextLocator).toBeVisible(); - await expect(firstElementTextLocator).not.toHaveText(""); + await expect(firstElementTextLocator).not.toHaveText(firstElementText); } } + return true; } /** @@ -225,10 +293,15 @@ export async function testSelectableColumns( page: Page, tab: TabDescription ): Promise { + // Navigate to the tab await page.goto(tab.url); + // Select the "Edit Columns" menu await page.getByRole("button").getByText("Edit Columns").click(); await expect(page.getByRole("menu")).toBeVisible(); - for (const column of tab.selectableColumns) { + // Enable each selectable tab + const tabObjectArray = Array.from(Object.values(tab.selectableColumns)); + for (const column of tabObjectArray) { + // Locate the checkbox for each column const checkboxLocator = page .getByRole("menu") .locator("*") @@ -238,15 +311,18 @@ export async function testSelectableColumns( .filter({ has: page.getByText(column.name, { exact: true }) }), }) .getByRole("checkbox"); + // Expect each column to be enabled and unchecked for selectable tabs await expect(checkboxLocator).toBeEnabled(); await expect(checkboxLocator).not.toBeChecked(); + // Expect clicking the checkbox to function await checkboxLocator.click(); await expect(checkboxLocator).toBeChecked(); } await page.getByRole("document").click(); await expect(page.getByRole("menu")).not.toBeVisible(); + // Expect all selectable tabs to be enabled await expect(page.getByRole("columnheader")).toContainText( - tab.selectableColumns.map((x) => x.name) + tabObjectArray.map((x) => x.name) ); } @@ -263,7 +339,7 @@ export async function testPreSelectedColumns( await page.goto(tab.url); await page.getByRole("button").getByText("Edit Columns").click(); await expect(page.getByRole("menu")).toBeVisible(); - for (const column of tab.preselectedColumns) { + for (const column of Object.values(tab.preselectedColumns)) { const checkboxLocator = page .getByRole("menu") .locator("*") @@ -278,6 +354,15 @@ export async function testPreSelectedColumns( } } +/** + * Returns a string with special characters escaped + * @param string - the string to escape + * @returns - a string with special characters escaped + */ +export function escapeRegExp(string: string): string { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + /** * Returns a regex that matches the sidebar filter buttons * This is useful for selecting a filter from the sidebar @@ -285,7 +370,7 @@ export async function testPreSelectedColumns( * @returns a regular expression matching "[filterName] ([n])" */ export const filterRegex = (filterName: string): RegExp => - new RegExp(filterName + "\\s+\\([0-9]+\\)\\s*"); + new RegExp(escapeRegExp(filterName) + "\\s+\\([0-9]+\\)\\s*"); /** * Checks that each filter specified in filterNames is visible and can be @@ -366,7 +451,9 @@ export async function testFilterPersistence( 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"); + console.log( + "FILTER PERSISTENCE: Filter name is blank, so the test cannot continue" + ); return false; } const filterName = (filterNameMatch ?? [""])[0]; @@ -428,10 +515,14 @@ export async function testFilterCounts( const filterButton = getFirstFilterButtonLocator(page); const filterNumbers = (await filterButton.innerText()).split("\n"); const filterNumber = - filterNumbers.map((x) => Number(x)).find((x) => !isNaN(x) && x !== 0) ?? - -1; + filterNumbers + .reverse() + .map((x) => Number(x)) + .find((x) => !isNaN(x) && x !== 0) ?? -1; if (filterNumber < 0) { - console.log("ERROR: The number associated with the filter is negative"); + console.log( + "FILTER COUNTS: The number associated with the filter is negative" + ); return false; } // Check the filter @@ -440,6 +531,8 @@ export async function testFilterCounts( // Exit the filter menu await page.locator("body").click(); await expect(page.getByRole("checkbox")).toHaveCount(0); + // Wait for the table to load + await expect(getFirstRowNthColumnCellLocator(page, 0)).toBeVisible(); // Expect the displayed count of elements to be 0 const firstNumber = filterNumber <= elementsPerPage ? filterNumber : elementsPerPage; @@ -462,7 +555,7 @@ export async function testFilterTags( tab: TabDescription, filterNames: string[] ): Promise { - page.goto(tab.url); + await page.goto(tab.url); for (const filterName of filterNames) { // Select a filter await page.getByText(filterRegex(filterName)).dispatchEvent("click"); @@ -510,8 +603,8 @@ export async function testClearAll( ): Promise { await page.goto(tab.url); const selectedFilterNamesList = []; + // Select each filter and get the names of the actual filter text for (const filterName of filterNames) { - // Select the passed filter names await page.getByText(filterRegex(filterName)).dispatchEvent("click"); await getFirstFilterButtonLocator(page).getByRole("checkbox").click(); await expect( @@ -531,6 +624,7 @@ export async function testClearAll( page.locator("#sidebar-positioner").getByText(filterName) ).toHaveCount(0); } + // Ensure that the filters still show as unchecked for (let i = 0; i < filterNames.length; i++) { await page.getByText(filterRegex(filterNames[i])).dispatchEvent("click"); await expect( @@ -546,7 +640,7 @@ export async function testClearAll( * 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 + * @returns a Playwright locator object to the first link to a backpage with the specified access */ const getBackpageLinkLocatorByAccess = (page: Page, access: string): Locator => page @@ -661,7 +755,7 @@ export async function testExportBackpage( * 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 + * @param tab - the tab object to test on */ export async function testBackpageAccess( page: Page, @@ -773,24 +867,35 @@ const hoverAndGetText = async ( * 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 { +): 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; + 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 combinedColumns = tab.preselectedColumns.concat(tab.selectableColumns); + 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 @@ -817,8 +922,10 @@ export async function testBackpageDetails( )?.name; if (correspondingHeaderName === undefined) { // Fail the test, because this means there is an incorrect configuration in the tab definition - await expect(false); - return; + console.log( + "BACKPAGE DETAILS error: backpageHeaders is configured incorrectly, so test cannot continue" + ); + return false; } headers.push({ header: correspondingHeaderName, value: tableEntryText }); } @@ -838,6 +945,214 @@ export async function testBackpageDetails( .first() ).toBeVisible(); } + return true; +} + +const PAGE_COUNT_REGEX = /Page [0-9]+ of [0-9]+/; +const BACK_BUTTON_TEST_ID = "WestRoundedIcon"; +const FORWARD_BUTTON_TEST_ID = "EastRoundedIcon"; +const ERROR = "ERROR"; +const MAX_PAGINATIONS = 200; + +/** + * Test that the forward pagination button is enabled and the back button is disabled on the first page of the selected tab + * @param page - a Playwright page object + * @param tab - the tab object to test on + */ +export async function testFirstPagePagination( + page: Page, + tab: TabDescription +): Promise { + await page.goto(tab.url); + await expect(getFirstRowNthColumnCellLocator(page, 0)).toBeVisible(); + // Should start on first page + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + /Page 1 of [0-9]+/ + ); + // Forward button should start enabled + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(FORWARD_BUTTON_TEST_ID) }) + ).toBeEnabled(); + // Back Button should start disabled + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(BACK_BUTTON_TEST_ID) }) + ).toBeDisabled(); +} + +/** + * Filter the current tab to reduce the number of pages to select, then go to the last page and test that the forward button is disabled and the back button is enabled + * @param page - a Playwright page object + * @param tab - the tab object to test on + * @param filterName - the name of the filter top to use to reduce the number of pages + * @returns - true if the test passes, false if it fails due to configuration issues + */ +export async function filterAndTestLastPagePagination( + page: Page, + tab: TabDescription, + filterName: string +): Promise { + // Filter to reduce the number of pages that must be selected + await page.goto(tab.url); + await page.getByText(filterRegex(filterName)).click(); + await expect(page.getByRole("checkbox").first()).toBeVisible(); + const filterTexts = await page + .getByRole("button") + .filter({ hasText: /([0-9]+)[\n\s]*$/ }) + .allInnerTexts(); + // Get the filter with the lowest associated count + const filterCounts = filterTexts + .map((filterText) => + ( + (filterText.match(/[^a-zA-Z0-9]+([0-9]+)[\n\s]*$/) ?? [ + undefined, + "", + ])[1] ?? ERROR + ).trim() + ) + .map((numberText) => parseInt(numberText)) + .filter( + (n) => + !isNaN(n) && + n > (tab.maxPages ?? 0) * 3 && + n < (tab.maxPages ?? 0) * MAX_PAGINATIONS + ); + if (filterCounts.length == 0) { + console.log( + "PAGINATION LAST PAGE: Test would involve too many paginations, so halting" + ); + return false; + } + const minFilterValue = Math.min(...filterCounts); + await page + .getByRole("button") + .filter({ hasText: RegExp(`${minFilterValue}[\\n\\s]*$`) }) + .click(); + await page.locator("body").click(); + await expect(getFirstRowNthColumnCellLocator(page, 0)).toBeVisible(); + + // Should start on first page, and there should be multiple pages available + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + /Page 1 of [0-9]+/ + ); + await expect( + page.getByText(PAGE_COUNT_REGEX, { exact: true }) + ).not.toHaveText("Page 1 of 1"); + + // Detect number of pages + const splitStartingPageText = ( + await page.getByText(PAGE_COUNT_REGEX, { exact: true }).innerText() + ).split(" "); + const maxPages = parseInt( + splitStartingPageText[splitStartingPageText.length - 1] + ); + // Paginate forwards + for (let i = 2; i < maxPages + 1; i++) { + await page + .getByRole("button") + .filter({ has: page.getByTestId(FORWARD_BUTTON_TEST_ID) }) + .dispatchEvent("click"); + await expect(getFirstRowNthColumnCellLocator(page, 0)).toBeVisible(); + // Expect the page count to have incremented + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + `Page ${i} of ${maxPages}` + ); + } + // Expect to be on the last page + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toContainText( + `Page ${maxPages} of ${maxPages}` + ); + // Expect the back button to be enabled on the last page + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(BACK_BUTTON_TEST_ID) }) + ).toBeEnabled(); + // Expect the forward button to be disabled + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(FORWARD_BUTTON_TEST_ID) }) + ).toBeDisabled(); + return true; +} + +/** + * Test that paginating changes the content on the page + * @param page - a Playwright page object + * @param tab - the tab to test on + */ +export async function testPaginationContent( + page: Page, + tab: TabDescription +): Promise { + // Navigate to the correct tab + await page.goto(tab.url); + await expect( + page.getByRole("tab").getByText(tab.tabName, { exact: true }) + ).toHaveAttribute("aria-selected", "true", { timeout: 25000 }); + + const firstElementTextLocator = getFirstRowNthColumnCellLocator(page, 0); + + // Should start on first page + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + /Page 1 of [0-9]+/ + ); + const maxPages = 5; + const FirstTableEntries = []; + + // Paginate forwards + for (let i = 2; i < maxPages + 1; i++) { + await expect(firstElementTextLocator).not.toHaveText(""); + const OriginalFirstTableEntry = await firstElementTextLocator.innerText(); + // Click the next button + await page + .getByRole("button") + .filter({ has: page.getByTestId(FORWARD_BUTTON_TEST_ID) }) + .click(); + // Expect the page count to have incremented + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + RegExp(`Page ${i} of [0-9]+`) + ); + // Expect the back button to be enabled + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(BACK_BUTTON_TEST_ID) }) + ).toBeEnabled(); + // Expect the forwards button to be enabled + if (i != maxPages) { + await expect( + page + .getByRole("button") + .filter({ has: page.getByTestId(FORWARD_BUTTON_TEST_ID) }) + ).toBeEnabled(); + } + // Expect the first entry to have changed on the new page + await expect(firstElementTextLocator).not.toHaveText( + OriginalFirstTableEntry + ); + // Remember the first entry + FirstTableEntries.push(OriginalFirstTableEntry); + } + + // Paginate backwards + for (let i = 0; i < maxPages - 1; i++) { + const OldFirstTableEntry = FirstTableEntries[maxPages - i - 2]; + await page + .getByRole("button") + .filter({ has: page.getByTestId(BACK_BUTTON_TEST_ID) }) + .click(); + // Expect page number to be correct + await expect(page.getByText(PAGE_COUNT_REGEX, { exact: true })).toHaveText( + RegExp(`Page ${maxPages - i - 1} of [0-9]+`) + ); + // Expect page entry to be consistent with forward pagination + await expect(firstElementTextLocator).toHaveText(OldFirstTableEntry); + } } /* eslint-enable sonarjs/no-duplicate-string -- Checking duplicate strings again*/ diff --git a/explorer/e2e/testInterfaces.ts b/explorer/e2e/testInterfaces.ts index e404c1b10..574fabb06 100644 --- a/explorer/e2e/testInterfaces.ts +++ b/explorer/e2e/testInterfaces.ts @@ -1,27 +1,31 @@ +export type StringToColumnDescription = { + [k: string]: ColumnDescription; +}; + export interface TabDescription { backpageAccessTags?: BackpageAccessTags; backpageExportButtons?: BackpageExportButtons; backpageHeaders?: BackpageHeader[]; emptyFirstColumn: boolean; maxPages?: number; - preselectedColumns: ColumnDescription[]; - selectableColumns: ColumnDescription[]; + preselectedColumns: StringToColumnDescription; + selectableColumns: StringToColumnDescription; tabName: string; url: string; } export interface AnvilCMGTabCollection { - activities: TabDescription; - biosamples: TabDescription; - datasets: TabDescription; - donors: TabDescription; - files: TabDescription; + ACTIVITIES: TabDescription; + BIOSAMPLES: TabDescription; + DATASETS: TabDescription; + DONORS: TabDescription; + FILES: TabDescription; } export interface AnvilCatalogTabCollection { - consortia: TabDescription; - studies: TabDescription; - workspaces: TabDescription; + CONSORTIA: TabDescription; + STUDIES: TabDescription; + WORKSPACES: TabDescription; } export type TabCollectionKeys = keyof AnvilCMGTabCollection; diff --git a/explorer/e2e/testReadme.md b/explorer/e2e/testReadme.md index 54a3510ab..aaf7cfea5 100644 --- a/explorer/e2e/testReadme.md +++ b/explorer/e2e/testReadme.md @@ -39,7 +39,7 @@ through the actions taken as part of the test and view the impact on the web pag - Filters (`anvil-filters.spec.ts`) - Check that all filters specified in `e2e/anvil/anvil-tabs.ts` are present, and that clicking them opens a menu with checkboxes - - This filter runs on all tabs in `anviltabs.ts` + - This filter runs on all tabs in `anvil-tabs.ts` - Check that checking up to the first five entries in the first filter on the datasets tab works and that it does not remove all elements from the list of tabs - Check that selecting a filter causes the selected checkbox entries to remain selected across all tabs - Currently uses the fourth filter and starts on the "Files" tab @@ -49,12 +49,12 @@ through the actions taken as part of the test and view the impact on the web pag - Checks an arbitrary list of three filters on the "Files" and "BioSamples" tabs - Check that the clear all button deselects all filters, after an arbitrary list is selected - Uses an arbitrary list of three filters and runs on the "Files" tab -- Pagination (`anvil-pagination.spec.ts` and `anvil-pagination-content.spec.ts`) +- Pagination (`anvil-pagination.spec.ts`) - Check that, on the first page, the back button is disabled and the forward button is enabled - Uses the "Donors" tab only - - Check that paginating forward on the donors tab keeps the currently displayed page number correct, and that on the last page the back button is enabled and the front page is enabled + - Check that paginating forward on the donors tab keeps the currently displayed page number correct, and that on the last page the back button is enabled and the forward button is enabled + - Uses a filter to reduce the number of paginations necessary - Uses the "Donors" tab only - - NOTE: this test may be problematic because it assumes there is relatively short number of pages on the "Donors" tab. This could potentially be resolved by adding filters or by limiting the number of tests run - Check that paginating forwards by up to five pages changes the content on the first row of the table, and that paginating backwards causes that text to remain the same - Uses the "BioSamples" tab only - Sort (`anvil-sort.spec.ts`) @@ -97,7 +97,7 @@ through the actions taken as part of the test and view the impact on the web pag - Edit Columns Button (`anvilcatalog-select-tabs.spec.ts`) - Check that the checkboxes in the "Edit Columns" button are activated/deselected and deactivated/selected where proper - Runs on all tabs - - Check that selecting all checkboxes in the Edit Columns menu adds the correct headets to the table + - Check that selecting all checkboxes in the Edit Columns menu adds the correct headers to the table - Only runs on the "Consortia" tab (other tabs do not have editable columns) - All tests rely on correct lists of tabs, columns, and filters in `anvilcatalog-tabs.ts` diff --git a/explorer/playwright_anvil-catalog.config.ts b/explorer/playwright_anvil-catalog.config.ts index 19bf82642..96f67c9da 100644 --- a/explorer/playwright_anvil-catalog.config.ts +++ b/explorer/playwright_anvil-catalog.config.ts @@ -22,6 +22,7 @@ const config: PlaywrightTestConfig = { ], testDir: "e2e", testMatch: /.*\/(anvil-catalog)\/.*\.spec\.ts/, + timeout: 60 * 1000, use: { baseURL: "http://localhost:3000/", screenshot: "only-on-failure",