From 05e6f3326b3053d86bea91becb67c26fb6e26a31 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Thu, 6 Feb 2025 16:17:27 -0500 Subject: [PATCH] test: addListFilter helper (#11026) Adds a new `addListFilter` e2e helper. This will help to standardize this common functionality across all tests that require filtering list tables and help reduce the overall lines of code within each test file. --- test/admin/e2e/list-view/e2e.spec.ts | 118 +++++++----------- .../collections/Relationship/index.ts | 1 + test/fields-relationship/e2e.spec.ts | 1 + test/fields/collections/Checkbox/e2e.spec.ts | 25 ++-- test/fields/collections/Email/e2e.spec.ts | 78 ++++-------- test/fields/collections/Number/e2e.spec.ts | 26 ++-- .../collections/Relationship/e2e.spec.ts | 47 ++----- test/fields/payload-types.ts | 4 - test/helpers/e2e/addListFilter.ts | 53 ++++++++ tsconfig.base.json | 2 +- 10 files changed, 143 insertions(+), 212 deletions(-) create mode 100644 test/helpers/e2e/addListFilter.ts diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts index 7a38c63313..2ea8403fb5 100644 --- a/test/admin/e2e/list-view/e2e.spec.ts +++ b/test/admin/e2e/list-view/e2e.spec.ts @@ -43,6 +43,7 @@ import type { PayloadTestSDK } from '../../../helpers/sdk/index.js' import { reorderColumns } from '../../../helpers/e2e/reorderColumns.js' import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) const dirname = path.resolve(currentFolder, '../../') @@ -311,27 +312,12 @@ describe('List View', () => { await expect(page.locator(tableRowLocator)).toHaveCount(2) - await openListFilters(page, {}) - - await page.locator('.where-builder__add-first-filter').click() - - const conditionField = page.locator('.condition__field') - await conditionField.click() - const dropdownFieldOption = conditionField.locator('.rs__option', { - hasText: exactText('ID'), + await addListFilter({ + page, + fieldLabel: 'ID', + operatorLabel: 'equals', + value: id, }) - await dropdownFieldOption.click() - await expect(page.locator('.condition__field')).toContainText('ID') - - const operatorField = page.locator('.condition__operator') - const valueField = page.locator('.condition__value input') - - await operatorField.click() - - const dropdownOptions = operatorField.locator('.rs__option') - await dropdownOptions.locator('text=equals').click() - - await valueField.fill(id) const tableRows = page.locator(tableRowLocator) @@ -347,20 +333,12 @@ describe('List View', () => { test('should reset filter value and operator on field update', async () => { const id = (await page.locator('.cell-id').first().innerText()).replace('ID: ', '') - // open the column controls - await page.locator('.list-controls__toggle-columns').click() - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - - const operatorField = page.locator('.condition__operator') - await operatorField.click() - - const dropdownOperatorOptions = operatorField.locator('.rs__option') - await dropdownOperatorOptions.locator('text=equals').click() - - // execute filter (where ID equals id value) - const valueField = page.locator('.condition__value > input') - await valueField.fill(id) + await addListFilter({ + page, + fieldLabel: 'ID', + operatorLabel: 'equals', + value: id, + }) const filterField = page.locator('.condition__field') await filterField.click() @@ -373,6 +351,7 @@ describe('List View', () => { await expect(filterField).toContainText('Status') // expect operator & value field to reset (be empty) + const operatorField = page.locator('.condition__operator') await expect(operatorField.locator('.rs__placeholder')).toContainText('Select a value') await expect(page.locator('.condition__value input')).toHaveValue('') }) @@ -490,14 +469,14 @@ describe('List View', () => { await expect(page.locator('.collection-list__page-info')).toHaveText('1-5 of 6') await expect(page.locator('.per-page')).toContainText('Per Page: 5') await page.goto(`${postsUrl.list}?limit=5&page=2`) - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - await page.locator('.condition__field .rs__control').click() - const options = page.locator('.rs__option') - await options.locator('text=Tab 1 > Title').click() - await page.locator('.condition__operator .rs__control').click() - await options.locator('text=equals').click() - await page.locator('.condition__value input').fill('test') + + await addListFilter({ + page, + fieldLabel: 'Tab 1 > Title', + operatorLabel: 'equals', + value: 'test', + }) + await page.waitForURL(new RegExp(`${postsUrl.list}\\?limit=5&page=1`)) await expect(page.locator('.collection-list__page-info')).toHaveText('1-3 of 3') }) @@ -505,6 +484,7 @@ describe('List View', () => { test('should reset filter values for every additional filters', async () => { await page.goto(postsUrl.list) await openListFilters(page, {}) + await page.locator('.where-builder__add-first-filter').click() const firstConditionField = page.locator('.condition__field') const firstOperatorField = page.locator('.condition__operator') @@ -536,48 +516,36 @@ describe('List View', () => { test('should not re-render page upon typing in a value in the filter value field', async () => { await page.goto(postsUrl.list) - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - const firstConditionField = page.locator('.condition__field') - const firstOperatorField = page.locator('.condition__operator') - const firstValueField = page.locator('.condition__value >> input') - await firstConditionField.click() - await firstConditionField - .locator('.rs__option', { hasText: exactText('Tab 1 > Title') }) - .click() - await expect(firstConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title') + await addListFilter({ + page, + fieldLabel: 'Tab 1 > Title', + operatorLabel: 'equals', + skipValueInput: true, + }) - await firstOperatorField.click() - await firstOperatorField.locator('.rs__option').locator('text=equals').click() + const valueInput = page.locator('.condition__value >> input') // Type into the input field instead of filling it - await firstValueField.click() - await firstValueField.type('Test', { delay: 100 }) // Add a delay to simulate typing speed + await valueInput.click() + await valueInput.type('Test', { delay: 100 }) // Add a delay to simulate typing speed // Wait for a short period to see if the input loses focus await page.waitForTimeout(500) // Check if the input still has the correct value - await expect(firstValueField).toHaveValue('Test') + await expect(valueInput).toHaveValue('Test') }) test('should still show second filter if two filters exist and first filter is removed', async () => { await page.goto(postsUrl.list) - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - const firstConditionField = page.locator('.condition__field') - const firstOperatorField = page.locator('.condition__operator') - const firstValueField = page.locator('.condition__value >> input') - await firstConditionField.click() - await firstConditionField - .locator('.rs__option', { hasText: exactText('Tab 1 > Title') }) - .click() - await expect(firstConditionField.locator('.rs__single-value')).toContainText('Tab 1 > Title') - await firstOperatorField.click() - await firstOperatorField.locator('.rs__option').locator('text=equals').click() - await firstValueField.fill('Test 1') - await expect(firstValueField).toHaveValue('Test 1') + + await addListFilter({ + page, + fieldLabel: 'Tab 1 > Title', + operatorLabel: 'equals', + value: 'Test 1', + }) await wait(500) @@ -609,6 +577,7 @@ describe('List View', () => { await removeButton.click() const filterListItems = page.locator('.where-builder__and-filters li') await expect(filterListItems).toHaveCount(1) + const firstValueField = page.locator('.condition__value >> input') await expect(firstValueField).toHaveValue('Test 2') }) @@ -647,8 +616,6 @@ describe('List View', () => { )}`, ) - console.log('URL', page.url()) - await openListFilters(page, {}) const condition = page.locator('.condition__field') @@ -1150,23 +1117,22 @@ describe('List View', () => { test('should display translated field titles', async () => { await createPost() - // column controls await page.locator('.list-controls__toggle-columns').click() + await expect( page.locator('.column-selector__column', { hasText: exactText('Title'), }), ).toHaveText('Title') - // filters await openListFilters(page, {}) + await page.locator('.where-builder__add-first-filter').click() await page.locator('.condition__field .rs__control').click() const options = page.locator('.rs__option') await expect(options.locator('text=Tab 1 > Title')).toHaveText('Tab 1 > Title') - // list columns await expect(page.locator('#heading-title .sort-column__label')).toHaveText('Title') await expect(page.locator('.search-filter input')).toHaveAttribute('placeholder', /(Title)/) }) diff --git a/test/fields-relationship/collections/Relationship/index.ts b/test/fields-relationship/collections/Relationship/index.ts index f23f78d632..ddcebbaab8 100644 --- a/test/fields-relationship/collections/Relationship/index.ts +++ b/test/fields-relationship/collections/Relationship/index.ts @@ -57,6 +57,7 @@ export const Relationship: CollectionConfig = { }, { name: 'relationshipFilteredByID', + label: 'Relationship Filtered By ID', filterOptions: (args: FilterOptionsProps) => { return { id: { diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index 7d1f7f75f6..2396d366e6 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -43,6 +43,7 @@ import { slug, versionedRelationshipFieldSlug, } from './slugs.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) diff --git a/test/fields/collections/Checkbox/e2e.spec.ts b/test/fields/collections/Checkbox/e2e.spec.ts index 3552c0efbf..e07df1dabd 100644 --- a/test/fields/collections/Checkbox/e2e.spec.ts +++ b/test/fields/collections/Checkbox/e2e.spec.ts @@ -12,6 +12,8 @@ import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { RESTClient } from '../../../helpers/rest.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { checkboxFieldsSlug } from '../../slugs.js' +import { openListFilters } from 'helpers/e2e/openListFilters.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) @@ -59,23 +61,12 @@ describe('Checkboxes', () => { test('should not crash on filtering where checkbox is first field', async () => { await page.goto(url.list) - const filterButton = page.locator('.list-controls__toggle-where') - await filterButton.click() - - const addButton = page.locator('.where-builder__add-first-filter') - await addButton.click() - - const operator = page.locator('.condition__operator .rs__control') - await operator.click() - - const equals = page.locator('.rs__option:has-text("equals")') - await equals.click() - - const value = page.locator('.condition__value') - await value.click() - - const trueOption = page.locator('.rs__option:has-text("True")') - await trueOption.click() + await addListFilter({ + page, + fieldLabel: 'Checkbox', + operatorLabel: 'equals', + value: 'True', + }) await wait(1000) diff --git a/test/fields/collections/Email/e2e.spec.ts b/test/fields/collections/Email/e2e.spec.ts index 855e3c5f50..4aa480dae7 100644 --- a/test/fields/collections/Email/e2e.spec.ts +++ b/test/fields/collections/Email/e2e.spec.ts @@ -17,6 +17,7 @@ import { RESTClient } from '../../../helpers/rest.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { emailFieldsSlug } from '../../slugs.js' import { emailDoc } from './shared.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) @@ -129,27 +130,12 @@ describe('Email', () => { test('should reset filter conditions when adding additional filters', async () => { await page.goto(url.list) - // open the first filter options - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - - const firstInitialField = page.locator('.condition__field') - const firstOperatorField = page.locator('.condition__operator') - const firstValueField = page.locator('.condition__value >> input') - - await firstInitialField.click() - const firstInitialFieldOptions = firstInitialField.locator('.rs__option') - await firstInitialFieldOptions.locator('text=text').first().click() - await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text') - - await firstOperatorField.click() - await firstOperatorField.locator('.rs__option').locator('text=equals').click() - - await firstValueField.fill('hello') - - await wait(500) - - await expect(firstValueField).toHaveValue('hello') + await addListFilter({ + page, + fieldLabel: 'Text en', + operatorLabel: 'equals', + value: 'hello', + }) // open the second filter options await page.locator('.condition__actions-add').click() @@ -170,23 +156,15 @@ describe('Email', () => { test('should not re-render page upon typing in a value in the filter value field', async () => { await page.goto(url.list) - // open the first filter options - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - - const firstInitialField = page.locator('.condition__field') - const firstOperatorField = page.locator('.condition__operator') - const firstValueField = page.locator('.condition__value >> input') - - await firstInitialField.click() - const firstInitialFieldOptions = firstInitialField.locator('.rs__option') - await firstInitialFieldOptions.locator('text=text').first().click() - await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text') - - await firstOperatorField.click() - await firstOperatorField.locator('.rs__option').locator('text=equals').click() + await addListFilter({ + page, + fieldLabel: 'Text en', + operatorLabel: 'equals', + skipValueInput: true, + }) // Type into the input field instead of filling it + const firstValueField = page.locator('.condition__value >> input') await firstValueField.click() await firstValueField.type('hello', { delay: 100 }) // Add a delay to simulate typing speed @@ -200,27 +178,12 @@ describe('Email', () => { test('should still show second filter if two filters exist and first filter is removed', async () => { await page.goto(url.list) - // open the first filter options - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - - const firstInitialField = page.locator('.condition__field') - const firstOperatorField = page.locator('.condition__operator') - const firstValueField = page.locator('.condition__value >> input') - - await firstInitialField.click() - const firstInitialFieldOptions = firstInitialField.locator('.rs__option') - await firstInitialFieldOptions.locator('text=text').first().click() - await expect(firstInitialField.locator('.rs__single-value')).toContainText('Text') - - await firstOperatorField.click() - await firstOperatorField.locator('.rs__option').locator('text=equals').click() - - await firstValueField.fill('hello') - - await wait(500) - - await expect(firstValueField).toHaveValue('hello') + await addListFilter({ + page, + fieldLabel: 'Text en', + operatorLabel: 'equals', + value: 'hello', + }) // open the second filter options await page.locator('.condition__actions-add').click() @@ -255,6 +218,7 @@ describe('Email', () => { const filterListItems = page.locator('.where-builder__and-filters li') await expect(filterListItems).toHaveCount(1) + const firstValueField = page.locator('.condition__value >> input') await expect(firstValueField).toHaveValue('world') }) }) diff --git a/test/fields/collections/Number/e2e.spec.ts b/test/fields/collections/Number/e2e.spec.ts index c165c09b3d..c3bf556596 100644 --- a/test/fields/collections/Number/e2e.spec.ts +++ b/test/fields/collections/Number/e2e.spec.ts @@ -20,6 +20,7 @@ import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { RESTClient } from '../../../helpers/rest.js' import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { numberDoc } from './shared.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) @@ -74,25 +75,14 @@ describe('Number', () => { test('should filter Number fields in the collection view - greaterThanOrEqual', async () => { await page.goto(url.list) await expect(page.locator('table >> tbody >> tr')).toHaveCount(3) - await openListFilters(page, {}) - await page.locator('.where-builder__add-first-filter').click() - const initialField = page.locator('.condition__field') - const operatorField = page.locator('.condition__operator') - const valueField = page.locator('.condition__value >> input') - await initialField.click() - const initialFieldOptions = initialField.locator('.rs__option') - await initialFieldOptions.locator('text=number').first().click() - await expect(initialField.locator('.rs__single-value')).toContainText('Number') - await operatorField.click() - const operatorOptions = operatorField.locator('.rs__option') - await operatorOptions.last().click() - await expect(operatorField.locator('.rs__single-value')).toContainText( - 'is greater than or equal to', - ) - // enter value of 3 - await valueField.fill('3') - await expect(valueField).toHaveValue('3') + await addListFilter({ + page, + fieldLabel: 'Number', + operatorLabel: 'is greater than or equal to', + value: '3', + }) + await wait(300) await expect(page.locator('table >> tbody >> tr')).toHaveCount(2) }) diff --git a/test/fields/collections/Relationship/e2e.spec.ts b/test/fields/collections/Relationship/e2e.spec.ts index a643d176b9..2953194305 100644 --- a/test/fields/collections/Relationship/e2e.spec.ts +++ b/test/fields/collections/Relationship/e2e.spec.ts @@ -26,6 +26,7 @@ import { reInitializeDB } from '../../../helpers/reInitializeDB.js' import { RESTClient } from '../../../helpers/rest.js' import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js' import { relationshipFieldsSlug, textFieldsSlug } from '../../slugs.js' +import { addListFilter } from 'helpers/e2e/addListFilter.js' const filename = fileURLToPath(import.meta.url) const currentFolder = path.dirname(filename) const dirname = path.resolve(currentFolder, '../../') @@ -572,17 +573,15 @@ describe('relationship', () => { test('should sort relationship options by sortOptions property (ID in ascending order)', async () => { await page.goto(url.create) await page.waitForURL(url.create) - await wait(400) const field = page.locator('#field-relationship') - await wait(400) await field.click() await wait(400) const textDocsGroup = page.locator('.rs__group-heading:has-text("Text Fields")') const firstTextDocOption = textDocsGroup.locator('+div .rs__option').first() const firstOptionLabel = await firstTextDocOption.textContent() - expect(firstOptionLabel.trim()).toBe('Another text document') + expect(firstOptionLabel?.trim()).toBe('Another text document') }) test('should sort relationHasManyPolymorphic options by sortOptions property: text-fields collection (items in descending order)', async () => { @@ -610,43 +609,13 @@ describe('relationship', () => { await page.goto(url.list) await page.waitForURL(new RegExp(url.list)) - await wait(400) - await page.locator('.list-controls__toggle-columns').click() - await wait(400) - - await openListFilters(page, {}) - await wait(400) - - await page.locator('.where-builder__add-first-filter').click() - - await wait(400) - const conditionField = page.locator('.condition__field') - await expect(conditionField.locator('input')).toBeEnabled() - await conditionField.click() - await wait(400) - - const dropdownFieldOptions = conditionField.locator('.rs__option') - await dropdownFieldOptions.locator('text=Relationship').nth(0).click() - await wait(400) - - const operatorField = page.locator('.condition__operator') - await expect(operatorField.locator('input')).toBeEnabled() - await operatorField.click() - await wait(400) - - const dropdownOperatorOptions = operatorField.locator('.rs__option') - await dropdownOperatorOptions.locator('text=equals').click() - await wait(400) - - const valueField = page.locator('.condition__value') - await expect(valueField.locator('input')).toBeEnabled() - await valueField.click() - await wait(400) - - const dropdownValueOptions = valueField.locator('.rs__option') - await dropdownValueOptions.locator('text=some text').click() - await wait(400) + await addListFilter({ + page, + fieldLabel: 'Relationship', + operatorLabel: 'equals', + value: 'some text', + }) await expect(page.locator(tableRowLocator)).toHaveCount(1) }) diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 23bf60aa59..25952d77af 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -1439,8 +1439,6 @@ export interface RelationshipField { | null; relationToRow?: (string | null) | RowField; relationToRowMany?: (string | RowField)[] | null; - disableRelation?: boolean | null; - filteredRelationship?: (string | null) | RelationshipField; updatedAt: string; createdAt: string; } @@ -3030,8 +3028,6 @@ export interface RelationshipFieldsSelect { relationshipWithMinRows?: T; relationToRow?: T; relationToRowMany?: T; - disableRelation?: T; - filteredRelationship?: T; updatedAt?: T; createdAt?: T; } diff --git a/test/helpers/e2e/addListFilter.ts b/test/helpers/e2e/addListFilter.ts new file mode 100644 index 0000000000..f1b7bb47be --- /dev/null +++ b/test/helpers/e2e/addListFilter.ts @@ -0,0 +1,53 @@ +import type { Page } from '@playwright/test' + +import { expect } from '@playwright/test' +import { exactText } from 'helpers.js' +import { wait } from 'payload/shared' + +import { openListFilters } from './openListFilters.js' + +export const addListFilter = async ({ + page, + fieldLabel = 'ID', + operatorLabel = 'equals', + value = '', + skipValueInput, +}: { + fieldLabel: string + operatorLabel: string + page: Page + skipValueInput?: boolean + value?: string +}) => { + await openListFilters(page, {}) + + const whereBuilder = page.locator('.where-builder') + + await whereBuilder.locator('.where-builder__add-first-filter').click() + + const conditionField = whereBuilder.locator('.condition__field') + await conditionField.click() + + const conditionOptions = conditionField.locator('.rs__option', { + hasText: exactText(fieldLabel), + }) + + await conditionOptions.click() + await expect(whereBuilder.locator('.condition__field')).toContainText(fieldLabel) + + const operatorInput = whereBuilder.locator('.condition__operator') + await operatorInput.click() + const operatorOptions = operatorInput.locator('.rs__option') + await operatorOptions.locator(`text=${operatorLabel}`).click() + + if (!skipValueInput) { + const valueInput = whereBuilder.locator('.condition__value >> input') + await valueInput.fill(value) + await wait(100) + await expect(valueInput).toHaveValue(value) + const valueOptions = whereBuilder.locator('.condition__value').locator('.rs__option') + if ((await whereBuilder.locator('.condition__value >> input.rs__input').count()) > 0) { + await valueOptions.locator(`text=${value}`).click() + } + } +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 16ab52137f..fba7b319e9 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,7 +31,7 @@ } ], "paths": { - "@payload-config": ["./test/fields-relationship/config.ts"], + "@payload-config": ["./test/fields/config.ts"], "@payloadcms/live-preview": ["./packages/live-preview/src"], "@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"], "@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],