From dddee6090708f09871ea8bf24d6f6ca1253e61b5 Mon Sep 17 00:00:00 2001 From: jpaten Date: Tue, 17 Sep 2024 15:38:57 -0700 Subject: [PATCH 1/5] test: added select filter tests to anvil-cmg (#4124) --- explorer/e2e/anvil/anvil-filters.spec.ts | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/explorer/e2e/anvil/anvil-filters.spec.ts b/explorer/e2e/anvil/anvil-filters.spec.ts index fcae7f30b..90a61a3ce 100644 --- a/explorer/e2e/anvil/anvil-filters.spec.ts +++ b/explorer/e2e/anvil/anvil-filters.spec.ts @@ -3,10 +3,12 @@ import { filterRegex, getFirstRowNthColumnCellLocator, testClearAll, + testDeselectFiltersThroughSearchBar, testFilterCounts, testFilterPersistence, testFilterPresence, testFilterTags, + testSelectFiltersThroughSearchBar, } from "../testFunctions"; import { ANVIL_FILTER_NAMES, @@ -173,3 +175,113 @@ test("Check that the clear all button functions on the files tab", async ({ FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) ); }); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.DATASETS, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.DATASETS, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Donors tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.DONORS, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the BioSamples tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.BIOSAMPLES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Activities tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.ACTIVITIES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Files tab', async ({ + page, +}) => { + await testSelectFiltersThroughSearchBar( + page, + ANVIL_TABS.FILES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ + page, +}) => { + await testDeselectFiltersThroughSearchBar( + page, + ANVIL_TABS.DATASETS, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Donors tab', async ({ + page, +}) => { + await testDeselectFiltersThroughSearchBar( + page, + ANVIL_TABS.DONORS, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the BioSamples tab', async ({ + page, +}) => { + await testDeselectFiltersThroughSearchBar( + page, + ANVIL_TABS.BIOSAMPLES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Activities tab', async ({ + page, +}) => { + await testDeselectFiltersThroughSearchBar( + page, + ANVIL_TABS.ACTIVITIES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); + +test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Files tab', async ({ + page, +}) => { + await testDeselectFiltersThroughSearchBar( + page, + ANVIL_TABS.FILES, + FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) + ); +}); From 6633bedd27e1c2986dbbd21f4191c3cface4641d Mon Sep 17 00:00:00 2001 From: jpaten Date: Tue, 17 Sep 2024 16:05:11 -0700 Subject: [PATCH 2/5] test: removed duplicate test, added missing tsdocs (#4124) --- explorer/e2e/anvil/anvil-filters.spec.ts | 10 ---------- explorer/e2e/testFunctions.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/explorer/e2e/anvil/anvil-filters.spec.ts b/explorer/e2e/anvil/anvil-filters.spec.ts index 90a61a3ce..601432cb3 100644 --- a/explorer/e2e/anvil/anvil-filters.spec.ts +++ b/explorer/e2e/anvil/anvil-filters.spec.ts @@ -186,16 +186,6 @@ test('Check that selecting filters through the "Search all Filters" textbox work ); }); -test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ - page, -}) => { - await testSelectFiltersThroughSearchBar( - page, - ANVIL_TABS.DATASETS, - FILTER_INDEX_LIST_SHORT.map((x) => ANVIL_FILTER_NAMES[x]) - ); -}); - test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Donors tab', async ({ page, }) => { diff --git a/explorer/e2e/testFunctions.ts b/explorer/e2e/testFunctions.ts index 977029c00..c214fceae 100644 --- a/explorer/e2e/testFunctions.ts +++ b/explorer/e2e/testFunctions.ts @@ -232,7 +232,6 @@ export async function testSortCatalog( 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; @@ -420,10 +419,14 @@ export const getFirstFilterOptionLocator = (page: Page): Locator => { .first(); }; +/** + * Gets the name of the filter option associated with a locator + * @param firstFilterOptionLocator - a Playwright locator to the filter option + * @returns the name of the filter option as a promise + */ export const getFilterOptionName = async ( firstFilterOptionLocator: Locator ): Promise => { - console.log(await firstFilterOptionLocator.innerText()); // Filter options display as "[text]\n[number]" , sometimes with extra whitespace, so we split on newlines and take the first non-empty string return ( (await firstFilterOptionLocator.innerText()) @@ -434,7 +437,7 @@ export const getFilterOptionName = async ( }; /** - * Cheks that selecting a specified filter is persistent across the tabs in tabOrder + * Checks that selecting a specified filter is persistent across the tabs in tabOrder * @param page - a Playwright page object * @param testFilterName - the name of the filter to check * @param tabOrder - the tabs to check, in order. The filter will be selected on the first tab. From ad049964196af069becfe57dd7bb0418c78c11fc Mon Sep 17 00:00:00 2001 From: jpaten Date: Tue, 17 Sep 2024 17:38:50 -0700 Subject: [PATCH 3/5] test: fixed edge case with empty filter option names (#4124) --- explorer/e2e/anvil/anvil-filters.spec.ts | 10 +++ explorer/e2e/testFunctions.ts | 87 ++++++++++++++++-------- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/explorer/e2e/anvil/anvil-filters.spec.ts b/explorer/e2e/anvil/anvil-filters.spec.ts index 601432cb3..cd24b1200 100644 --- a/explorer/e2e/anvil/anvil-filters.spec.ts +++ b/explorer/e2e/anvil/anvil-filters.spec.ts @@ -179,6 +179,7 @@ test("Check that the clear all button functions on the files tab", async ({ test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ page, }) => { + test.setTimeout(120000); await testSelectFiltersThroughSearchBar( page, ANVIL_TABS.DATASETS, @@ -189,6 +190,7 @@ test('Check that selecting filters through the "Search all Filters" textbox work test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Donors tab', async ({ page, }) => { + test.setTimeout(120000); await testSelectFiltersThroughSearchBar( page, ANVIL_TABS.DONORS, @@ -199,6 +201,7 @@ test('Check that selecting filters through the "Search all Filters" textbox work test('Check that selecting filters through the "Search all Filters" textbox works correctly on the BioSamples tab', async ({ page, }) => { + test.setTimeout(120000); await testSelectFiltersThroughSearchBar( page, ANVIL_TABS.BIOSAMPLES, @@ -209,6 +212,7 @@ test('Check that selecting filters through the "Search all Filters" textbox work test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Activities tab', async ({ page, }) => { + test.setTimeout(120000); await testSelectFiltersThroughSearchBar( page, ANVIL_TABS.ACTIVITIES, @@ -219,6 +223,7 @@ test('Check that selecting filters through the "Search all Filters" textbox work test('Check that selecting filters through the "Search all Filters" textbox works correctly on the Files tab', async ({ page, }) => { + test.setTimeout(120000); await testSelectFiltersThroughSearchBar( page, ANVIL_TABS.FILES, @@ -229,6 +234,7 @@ test('Check that selecting filters through the "Search all Filters" textbox work test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Datasets tab', async ({ page, }) => { + test.setTimeout(120000); await testDeselectFiltersThroughSearchBar( page, ANVIL_TABS.DATASETS, @@ -239,6 +245,7 @@ test('Check that deselecting filters through the "Search all Filters" textbox wo test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Donors tab', async ({ page, }) => { + test.setTimeout(120000); await testDeselectFiltersThroughSearchBar( page, ANVIL_TABS.DONORS, @@ -249,6 +256,7 @@ test('Check that deselecting filters through the "Search all Filters" textbox wo test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the BioSamples tab', async ({ page, }) => { + test.setTimeout(120000); await testDeselectFiltersThroughSearchBar( page, ANVIL_TABS.BIOSAMPLES, @@ -259,6 +267,7 @@ test('Check that deselecting filters through the "Search all Filters" textbox wo test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Activities tab', async ({ page, }) => { + test.setTimeout(120000); await testDeselectFiltersThroughSearchBar( page, ANVIL_TABS.ACTIVITIES, @@ -269,6 +278,7 @@ test('Check that deselecting filters through the "Search all Filters" textbox wo test('Check that deselecting filters through the "Search all Filters" textbox works correctly on the Files tab', async ({ page, }) => { + test.setTimeout(120000); await testDeselectFiltersThroughSearchBar( page, ANVIL_TABS.FILES, diff --git a/explorer/e2e/testFunctions.ts b/explorer/e2e/testFunctions.ts index c214fceae..2e1a9835d 100644 --- a/explorer/e2e/testFunctions.ts +++ b/explorer/e2e/testFunctions.ts @@ -408,32 +408,63 @@ export const getNamedFilterOptionLocator = ( }; /** - * Get a locator for the first filter option. Requires a filter menu to be open + * Get a locator for the nth filter option on the page. * @param page - a Playwright page object - * @returns a Playwright locator to the filter button + * @param n - the index of the filter option to get + * @returns - a Playwright locator object for the first filter option on the page */ -export const getFirstFilterOptionLocator = (page: Page): Locator => { +const getNthFilterOptionLocator = (page: Page, n: number): Locator => { return page .getByRole("button") .filter({ has: page.getByRole("checkbox") }) - .first(); + .nth(n); }; +/** + * Get a locator for the first filter option on the page. + * @param page - a Playwright page object + * @returns - a Playwright locator object for the first filter option on the page + */ +export const getFirstFilterOptionLocator = (page: Page): Locator => { + return getNthFilterOptionLocator(page, 0); +}; + +interface FilterOptionNameAndLocator { + locator: Locator; + name: string; +} + +const MAX_FILTER_OPTIONS_TO_CHECK = 10; + /** * Gets the name of the filter option associated with a locator - * @param firstFilterOptionLocator - a Playwright locator to the filter option - * @returns the name of the filter option as a promise + * @param page - a Playwright Page object, on which a filter must be currently selected + * @returns the innerText of the first nonempty filter option as a promise */ -export const getFilterOptionName = async ( - firstFilterOptionLocator: Locator -): Promise => { - // Filter options display as "[text]\n[number]" , sometimes with extra whitespace, so we split on newlines and take the first non-empty string - return ( - (await firstFilterOptionLocator.innerText()) - .split("\n") - .map((x) => x.trim()) - .find((x) => x.length > 0) ?? "" - ); +const getFirstNonEmptyFilterOptionNameAndLocator = async ( + page: Page +): Promise => { + let filterNameToSelect = ""; + let filterOptionLocator = undefined; + let i = 0; + while (filterNameToSelect === "" && i < MAX_FILTER_OPTIONS_TO_CHECK) { + // Filter options display as "[text]\n[number]" , sometimes with extra whitespace, so we split on newlines and take the first non-empty string + const regex = /^(.*)\n+([0-9]+)\s*$/; + filterOptionLocator = getNthFilterOptionLocator(page, i); + console.log(await filterOptionLocator.innerText()); + filterNameToSelect = ((await filterOptionLocator.innerText()).trim().match(regex) ?? [ + "", + "", + ])[1]; + i += 1; + } + + if (filterOptionLocator === undefined) { + throw new Error( + "No locator found within the maximum number of filter options" + ); + } + return { locator: filterOptionLocator, name: filterNameToSelect }; }; /** @@ -625,16 +656,16 @@ export async function testClearAll( ): Promise { await page.goto(tab.url); const selectedFilterNamesList = []; - // Select each filter and get the names of the actual filter text + // Select each filter and get the names of the filter option text for (const filterName of filterNames) { await page.getByText(filterRegex(filterName)).dispatchEvent("click"); - await getFirstFilterOptionLocator(page).getByRole("checkbox").click(); + const filterOptionNameAndLocator = + await getFirstNonEmptyFilterOptionNameAndLocator(page); + await filterOptionNameAndLocator.locator.click(); await expect( - getFirstFilterOptionLocator(page).getByRole("checkbox") + filterOptionNameAndLocator.locator.getByRole("checkbox") ).toBeChecked(); - selectedFilterNamesList.push( - await getFilterOptionName(getFirstFilterOptionLocator(page)) - ); + selectedFilterNamesList.push(filterOptionNameAndLocator.name); await page.locator("body").click(); } // Click the "Clear All" button @@ -682,9 +713,9 @@ export async function testSelectFiltersThroughSearchBar( await expect(page.getByText(filterRegex(filterName))).toBeVisible(); await page.getByText(filterRegex(filterName)).dispatchEvent("click"); const firstFilterOptionLocator = getFirstFilterOptionLocator(page); - const filterOptionName = await getFilterOptionName( - firstFilterOptionLocator - ); + const filterOptionName = ( + await getFirstNonEmptyFilterOptionNameAndLocator(page) + ).name; await page.locator("body").click(); // Search for the filter option const searchFiltersInputLocator = page.getByPlaceholder( @@ -720,10 +751,10 @@ export async function testDeselectFiltersThroughSearchBar( // Select each filter option await expect(page.getByText(filterRegex(filterName))).toBeVisible(); await page.getByText(filterRegex(filterName)).dispatchEvent("click"); + const firstFilterOptionNameAndLocator = + await getFirstNonEmptyFilterOptionNameAndLocator(page); const firstFilterOptionLocator = getFirstFilterOptionLocator(page); - const filterOptionName = await getFilterOptionName( - firstFilterOptionLocator - ); + const filterOptionName = firstFilterOptionNameAndLocator.name; await firstFilterOptionLocator.click(); await page.locator("body").click(); // Search for and check the selected filter From 186b370f1a965d974ae7445088fb34b9f9b20450 Mon Sep 17 00:00:00 2001 From: jpaten Date: Tue, 17 Sep 2024 22:19:31 -0700 Subject: [PATCH 4/5] test: linting (#4124) --- explorer/e2e/testFunctions.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/explorer/e2e/testFunctions.ts b/explorer/e2e/testFunctions.ts index 2e1a9835d..96c43a323 100644 --- a/explorer/e2e/testFunctions.ts +++ b/explorer/e2e/testFunctions.ts @@ -448,18 +448,17 @@ const getFirstNonEmptyFilterOptionNameAndLocator = async ( let filterOptionLocator = undefined; let i = 0; while (filterNameToSelect === "" && i < MAX_FILTER_OPTIONS_TO_CHECK) { - // Filter options display as "[text]\n[number]" , sometimes with extra whitespace, so we split on newlines and take the first non-empty string - const regex = /^(.*)\n+([0-9]+)\s*$/; + // Filter options display as "[text]\n[number]" , sometimes with extra whitespace, so we want the string before the newline + const filterOptionRegex = /^(.*)\n+([0-9]+)\s*$/; filterOptionLocator = getNthFilterOptionLocator(page, i); console.log(await filterOptionLocator.innerText()); - filterNameToSelect = ((await filterOptionLocator.innerText()).trim().match(regex) ?? [ - "", - "", - ])[1]; + filterNameToSelect = ((await filterOptionLocator.innerText()) + .trim() + .match(filterOptionRegex) ?? ["", ""])[1]; i += 1; } - - if (filterOptionLocator === undefined) { + + if (filterOptionLocator === undefined || filterNameToSelect === "") { throw new Error( "No locator found within the maximum number of filter options" ); @@ -712,7 +711,6 @@ export async function testSelectFiltersThroughSearchBar( // Get the first filter option await expect(page.getByText(filterRegex(filterName))).toBeVisible(); await page.getByText(filterRegex(filterName)).dispatchEvent("click"); - const firstFilterOptionLocator = getFirstFilterOptionLocator(page); const filterOptionName = ( await getFirstNonEmptyFilterOptionNameAndLocator(page) ).name; @@ -755,7 +753,7 @@ export async function testDeselectFiltersThroughSearchBar( await getFirstNonEmptyFilterOptionNameAndLocator(page); const firstFilterOptionLocator = getFirstFilterOptionLocator(page); const filterOptionName = firstFilterOptionNameAndLocator.name; - await firstFilterOptionLocator.click(); + await firstFilterOptionLocator.getByRole("checkbox").click(); await page.locator("body").click(); // Search for and check the selected filter const searchFiltersInputLocator = page.getByPlaceholder( From 5a40e4c3ec730214474166b68bc1e04f777ce528 Mon Sep 17 00:00:00 2001 From: jpaten Date: Tue, 17 Sep 2024 22:52:17 -0700 Subject: [PATCH 5/5] test: updated readme (#4124) --- explorer/e2e/testReadme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/explorer/e2e/testReadme.md b/explorer/e2e/testReadme.md index baf30e7eb..1bfbc720d 100644 --- a/explorer/e2e/testReadme.md +++ b/explorer/e2e/testReadme.md @@ -49,6 +49,10 @@ 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 + - Search filters bar + - Check that filters can be selected through the search bar + - Check that filters can be deselected through the search bar + - Both tests run on all tabs - 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