diff --git a/test/bulk-edit/e2e.spec.ts b/test/bulk-edit/e2e.spec.ts index e1018387f..49a84a88c 100644 --- a/test/bulk-edit/e2e.spec.ts +++ b/test/bulk-edit/e2e.spec.ts @@ -2,6 +2,7 @@ import type { BrowserContext, Locator, Page } from '@playwright/test' import type { PayloadTestSDK } from 'helpers/sdk/index.js' import { expect, test } from '@playwright/test' +import { addArrayRow } from 'helpers/e2e/fields/array/index.js' import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js' import * as path from 'path' import { wait } from 'payload/shared' @@ -498,7 +499,7 @@ test.describe('Bulk Edit', () => { await wait(500) - await field.locator('button.array-field__add-row').click() + await addArrayRow(page, { fieldName: 'array' }) const row = page.locator(`#array-row-0`) const toggler = row.locator('button.collapsible__toggle') diff --git a/test/field-error-states/e2e.spec.ts b/test/field-error-states/e2e.spec.ts index 810158bf8..b92bb7deb 100644 --- a/test/field-error-states/e2e.spec.ts +++ b/test/field-error-states/e2e.spec.ts @@ -2,6 +2,7 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' import { AdminUrlUtil } from 'helpers/adminUrlUtil.js' +import { addArrayRow, openArrayRowActions, removeArrayRow } from 'helpers/e2e/fields/array/index.js' import path from 'path' import { wait } from 'payload/shared' import { fileURLToPath } from 'url' @@ -45,7 +46,7 @@ describe('Field Error States', () => { await page.goto(`${serverURL}/admin/collections/error-fields/create`) // add parent array - await page.locator('#field-parentArray > .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'parentArray' }) // add first child array await page.locator('#parentArray-row-0 .collapsible__content .array-field__add-row').click() @@ -60,12 +61,10 @@ describe('Field Error States', () => { // add third child array await page.locator('#parentArray-row-0 .collapsible__content .array-field__add-row').click() - // remove the row - await page.locator('#parentArray-0-childArray-row-2 .array-actions__button').click() - - await page - .locator('#parentArray-0-childArray-row-2 .array-actions__action.array-actions__remove') - .click() + await removeArrayRow(page, { + fieldName: 'parentArray__0__childArray', + rowIndex: 2, + }) await page.locator('#action-save').click() diff --git a/test/field-error-states/payload-types.ts b/test/field-error-states/payload-types.ts index abd2f7b97..7295c7cac 100644 --- a/test/field-error-states/payload-types.ts +++ b/test/field-error-states/payload-types.ts @@ -330,6 +330,13 @@ export interface User { hash?: string | null; loginAttempts?: number | null; lockUntil?: string | null; + sessions?: + | { + id: string; + createdAt?: string | null; + expiresAt: string; + }[] + | null; password?: string | null; } /** @@ -707,6 +714,13 @@ export interface UsersSelect { hash?: T; loginAttempts?: T; lockUntil?: T; + sessions?: + | T + | { + id?: T; + createdAt?: T; + expiresAt?: T; + }; } /** * This interface was referenced by `Config`'s JSON-Schema diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index 8b12f8429..0791e8cdf 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -4,9 +4,10 @@ import type { CollectionSlug } from 'payload' import { expect, test } from '@playwright/test' import { assertToastErrors } from 'helpers/assertToastErrors.js' import { addListFilter } from 'helpers/e2e/addListFilter.js' +import { openCreateDocDrawer } from 'helpers/e2e/fields/relationship/openCreateDocDrawer.js' import { goToNextPage } from 'helpers/e2e/goToNextPage.js' import { openDocControls } from 'helpers/e2e/openDocControls.js' -import { openCreateDocDrawer, openDocDrawer } from 'helpers/e2e/toggleDocDrawer.js' +import { openDocDrawer } from 'helpers/e2e/toggleDocDrawer.js' import path from 'path' import { wait } from 'payload/shared' import { fileURLToPath } from 'url' diff --git a/test/fields/collections/Array/e2e.spec.ts b/test/fields/collections/Array/e2e.spec.ts index c5f1c27b1..31433db67 100644 --- a/test/fields/collections/Array/e2e.spec.ts +++ b/test/fields/collections/Array/e2e.spec.ts @@ -4,6 +4,7 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' import { assertToastErrors } from 'helpers/assertToastErrors.js' import { copyPasteField } from 'helpers/e2e/copyPasteField.js' +import { addArrayRow, duplicateArrayRow, removeArrayRow } from 'helpers/e2e/fields/array/index.js' import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js' import path from 'path' import { wait } from 'payload/shared' @@ -89,7 +90,7 @@ describe('Array', () => { test('should render RowLabel using a component', async () => { const label = 'custom row label as component' await loadCreatePage() - await page.locator('#field-rowLabelAsComponent >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'rowLabelAsComponent' }) await expect(page.locator('#field-rowLabelAsComponent__0__title')).toBeVisible() await expect(page.locator('.shimmer-effect')).toHaveCount(0) @@ -119,7 +120,7 @@ describe('Array', () => { const label = 'test custom row label' const updatedLabel = 'updated custom row label' await loadCreatePage() - await page.locator('#field-rowLabelAsComponent >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'rowLabelAsComponent' }) await page.locator('#field-rowLabelAsComponent__0__title').fill(label) @@ -130,14 +131,7 @@ describe('Array', () => { await expect(customRowLabel).toBeVisible() await expect(customRowLabel).toHaveCSS('text-transform', 'uppercase') - const rowActionsButton = page.locator('#rowLabelAsComponent-row-0 .array-actions__button') - await rowActionsButton.click() - - const duplicateButton = page.locator( - '#rowLabelAsComponent-row-0 .popup__scroll-container .array-actions__duplicate', - ) - await expect(duplicateButton).toBeVisible() - await duplicateButton.click() + await duplicateArrayRow(page, { fieldName: 'rowLabelAsComponent' }) await expect(page.locator('#rowLabelAsComponent-row-1')).toBeVisible() await expect( @@ -157,8 +151,7 @@ describe('Array', () => { test('should render default array field within custom component', async () => { await loadCreatePage() - - await page.locator('#field-customArrayField >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'customArrayField' }) await expect(page.locator('#field-customArrayField__0__text')).toBeVisible() }) @@ -169,7 +162,7 @@ describe('Array', () => { test('should fail min rows validation when rows are present', async () => { await loadCreatePage() - await page.locator('#field-arrayWithMinRows >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'arrayWithMinRows' }) // Ensure new array row is visible and fields are rendered await expect(page.locator('#arrayWithMinRows-row-0')).toBeVisible() @@ -210,36 +203,29 @@ describe('Array', () => { await wait(300) // Add 3 rows - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) - await page.locator('#field-potentiallyEmptyArray > .array-field__add-row').click() - await wait(300) + await addArrayRow(page, { fieldName: 'potentiallyEmptyArray' }) + await addArrayRow(page, { fieldName: 'potentiallyEmptyArray' }) + await addArrayRow(page, { fieldName: 'potentiallyEmptyArray' }) // Fill out row 1 await page.locator('#field-potentiallyEmptyArray__0__text').fill(assertText0) + await page .locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') .fill(assertGroupText0) + // Fill out row 2 await page.locator('#field-potentiallyEmptyArray__1__text').fill(assertText1) + // Fill out row 3 await page.locator('#field-potentiallyEmptyArray__2__text').fill(assertText3) + await page .locator('#field-potentiallyEmptyArray__2__groupInRow__textInGroupInRow') .fill(assertGroupText3) - // Remove row 1 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() - // Remove row 2 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() + await removeArrayRow(page, { fieldName: 'potentiallyEmptyArray', rowIndex: 1 }) + await removeArrayRow(page, { fieldName: 'potentiallyEmptyArray', rowIndex: 0 }) // Save document await saveDocAndAssert(page) @@ -251,11 +237,7 @@ describe('Array', () => { const input = page.locator('#field-potentiallyEmptyArray__0__groupInRow__textInGroupInRow') await expect(input).toHaveValue(assertGroupText3) - // Duplicate row - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__duplicate') - .click() + await duplicateArrayRow(page, { fieldName: 'potentiallyEmptyArray' }) // Update duplicated row group field text await page @@ -270,11 +252,7 @@ describe('Array', () => { page.locator('#field-potentiallyEmptyArray__1__groupInRow__textInGroupInRow'), ).toHaveValue(`${assertGroupText3} duplicate`) - // Remove row 1 - await page.locator('#potentiallyEmptyArray-row-0 .array-actions__button').click() - await page - .locator('#potentiallyEmptyArray-row-0 .popup__scroll-container .array-actions__remove') - .click() + await removeArrayRow(page, { fieldName: 'potentiallyEmptyArray', rowIndex: 0 }) // Save document await saveDocAndAssert(page) @@ -361,11 +339,8 @@ describe('Array', () => { await arrayOption.click() await wait(200) - const addRowButton = page.locator('#field-items > .array-field__add-row') + await addArrayRow(page, { fieldName: 'items' }) - await expect(addRowButton).toBeVisible() - - await addRowButton.click() await wait(200) const targetInput = page.locator('#field-items__0__text') @@ -389,7 +364,7 @@ describe('Array', () => { test('should initialize array rows with collapsed state', async () => { await page.goto(url.create) - await page.locator('#field-collapsedArray >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'collapsedArray' }) const row = page.locator(`#collapsedArray-row-0`) const toggler = row.locator('button.collapsible__toggle') @@ -401,7 +376,7 @@ describe('Array', () => { test('should not collapse array rows on input change', async () => { await page.goto(url.create) - await page.locator('#field-collapsedArray >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'collapsedArray' }) const row = page.locator(`#collapsedArray-row-0`) const toggler = row.locator('button.collapsible__toggle') @@ -599,10 +574,8 @@ describe('Array', () => { await page.goto(url.create) const field = page.locator('#field-items') - const addSubArrayBtn = field.locator( - '#field-items__0__subArray > button.array-field__add-row', - ) - await addSubArrayBtn.click() + + await addArrayRow(page, { fieldName: 'items__0__subArray' }) const textInputRowOne = field.locator('#field-items__0__subArray__0__text') await expect(textInputRowOne).toBeVisible() @@ -633,18 +606,10 @@ describe('Array', () => { const field = page.locator('#field-items') - const addSubArrayBtn = field.locator( - '#field-items__0__subArray > button.array-field__add-row', - ) - await expect(addSubArrayBtn).toBeVisible() - await addSubArrayBtn.click() - await addSubArrayBtn.click() + await addArrayRow(page, { fieldName: 'items__0__subArray' }) + await addArrayRow(page, { fieldName: 'items__0__subArray' }) - const addSubArrayBtn2 = field.locator( - '#field-items__1__subArray > button.array-field__add-row', - ) - await expect(addSubArrayBtn2).toBeVisible() - await addSubArrayBtn2.click() + await addArrayRow(page, { fieldName: 'items__1__subArray' }) const subArrayContainer = field.locator( '#field-items__0__subArray > div.array-field__draggable-rows > div', diff --git a/test/fields/collections/Blocks/e2e.spec.ts b/test/fields/collections/Blocks/e2e.spec.ts index 16fc3e14f..55f2d1262 100644 --- a/test/fields/collections/Blocks/e2e.spec.ts +++ b/test/fields/collections/Blocks/e2e.spec.ts @@ -1,10 +1,9 @@ import type { BrowserContext, Page } from '@playwright/test' import { expect, test } from '@playwright/test' -import { addBlock } from 'helpers/e2e/addBlock.js' import { copyPasteField } from 'helpers/e2e/copyPasteField.js' -import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js' -import { reorderBlocks } from 'helpers/e2e/reorderBlocks.js' +import { addArrayRowBelow, duplicateArrayRow } from 'helpers/e2e/fields/array/index.js' +import { addBlock, openBlocksDrawer, reorderBlocks } from 'helpers/e2e/fields/blocks/index.js' import { scrollEntirePage } from 'helpers/e2e/scrollEntirePage.js' import { toggleBlockOrArrayRow } from 'helpers/e2e/toggleCollapsible.js' import path from 'path' @@ -127,14 +126,8 @@ describe('Block fields', () => { test('should open blocks drawer from block row and add below', async () => { await page.goto(url.create) - const firstRow = page.locator('#field-blocks #blocks-row-0') - const rowActions = firstRow.locator('.collapsible__actions') - await expect(rowActions).toBeVisible() - await rowActions.locator('.array-actions__button').click() - const addBelowButton = rowActions.locator('.array-actions__action.array-actions__add') - await expect(addBelowButton).toBeVisible() - await addBelowButton.click() + await addArrayRowBelow(page, { fieldName: 'blocks' }) const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]') await expect(blocksDrawer).toBeVisible() @@ -157,14 +150,8 @@ describe('Block fields', () => { test('should duplicate block', async () => { await page.goto(url.create) - const firstRow = page.locator('#field-blocks #blocks-row-0') - const rowActions = firstRow.locator('.collapsible__actions') - await expect(rowActions).toBeVisible() - await rowActions.locator('.array-actions__button').click() - const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate') - await expect(duplicateButton).toBeVisible() - await duplicateButton.click() + await duplicateArrayRow(page, { fieldName: 'blocks' }) const blocks = page.locator('#field-blocks > .blocks-field__rows > div') expect(await blocks.count()).toEqual(5) @@ -172,14 +159,8 @@ describe('Block fields', () => { test('should save when duplicating subblocks', async () => { await page.goto(url.create) - const subblocksRow = page.locator('#field-blocks #blocks-row-2') - const rowActions = subblocksRow.locator('.collapsible__actions').first() - await expect(rowActions).toBeVisible() - await rowActions.locator('.array-actions__button').click() - const duplicateButton = rowActions.locator('.array-actions__action.array-actions__duplicate') - await expect(duplicateButton).toBeVisible() - await duplicateButton.click() + await duplicateArrayRow(page, { fieldName: 'blocks', rowIndex: 2 }) const blocks = page.locator('#field-blocks > .blocks-field__rows > div') expect(await blocks.count()).toEqual(5) diff --git a/test/fields/collections/Collapsible/e2e.spec.ts b/test/fields/collections/Collapsible/e2e.spec.ts index 23c9d7d15..a548b92fd 100644 --- a/test/fields/collections/Collapsible/e2e.spec.ts +++ b/test/fields/collections/Collapsible/e2e.spec.ts @@ -1,6 +1,7 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' +import { addArrayRow } from 'helpers/e2e/fields/array/index.js' import path from 'path' import { wait } from 'payload/shared' import { fileURLToPath } from 'url' @@ -87,7 +88,7 @@ describe('Collapsibles', () => { const arrayWithCollapsibles = page.locator('#field-arrayWithCollapsibles') await expect(arrayWithCollapsibles).toBeVisible() - await page.locator('#field-arrayWithCollapsibles >> .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'arrayWithCollapsibles' }) await page .locator( diff --git a/test/fields/collections/ConditionalLogic/e2e.spec.ts b/test/fields/collections/ConditionalLogic/e2e.spec.ts index a5dc5ed9d..b2d94a969 100644 --- a/test/fields/collections/ConditionalLogic/e2e.spec.ts +++ b/test/fields/collections/ConditionalLogic/e2e.spec.ts @@ -1,6 +1,7 @@ import type { BrowserContext, Page } from '@playwright/test' import { expect, test } from '@playwright/test' +import { addArrayRow } from 'helpers/e2e/fields/array/index.js' import path from 'path' import { fileURLToPath } from 'url' @@ -175,7 +176,7 @@ describe('Conditional Logic', () => { test('should not render fields when adding array or blocks rows until form state returns', async () => { await page.goto(url.create) - await page.locator('#field-arrayWithConditionalField .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'arrayWithConditionalField' }) const shimmer = '#field-arrayWithConditionalField .collapsible__content > .shimmer-effect' await expect(page.locator(shimmer)).toBeVisible() @@ -204,14 +205,11 @@ describe('Conditional Logic', () => { test('should render field based on path argument', async () => { await page.goto(url.create) - const arrayOneButton = page.locator('#field-arrayOne .array-field__add-row') - await arrayOneButton.click() + await addArrayRow(page, { fieldName: 'arrayOne' }) - const arrayTwoButton = page.locator('#arrayOne-row-0 .array-field__add-row') - await arrayTwoButton.click() + await addArrayRow(page, { fieldName: 'arrayOne__0__arrayTwo' }) - const arrayThreeButton = page.locator('#arrayOne-0-arrayTwo-row-0 .array-field__add-row') - await arrayThreeButton.click() + await addArrayRow(page, { fieldName: 'arrayOne__0__arrayTwo__0__arrayThree' }) const numberField = page.locator('#field-arrayOne__0__arrayTwo__0__arrayThree__0__numberField') diff --git a/test/fields/collections/Relationship/e2e.spec.ts b/test/fields/collections/Relationship/e2e.spec.ts index 0f2010305..3bf2232ff 100644 --- a/test/fields/collections/Relationship/e2e.spec.ts +++ b/test/fields/collections/Relationship/e2e.spec.ts @@ -2,9 +2,10 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' import { addListFilter } from 'helpers/e2e/addListFilter.js' +import { openCreateDocDrawer } from 'helpers/e2e/fields/relationship/openCreateDocDrawer.js' import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js' import { openDocControls } from 'helpers/e2e/openDocControls.js' -import { openCreateDocDrawer, openDocDrawer } from 'helpers/e2e/toggleDocDrawer.js' +import { openDocDrawer } from 'helpers/e2e/toggleDocDrawer.js' import path from 'path' import { wait } from 'payload/shared' import { fileURLToPath } from 'url' diff --git a/test/fields/collections/Tabs2/e2e.spec.ts b/test/fields/collections/Tabs2/e2e.spec.ts index be4567599..6939279eb 100644 --- a/test/fields/collections/Tabs2/e2e.spec.ts +++ b/test/fields/collections/Tabs2/e2e.spec.ts @@ -1,6 +1,7 @@ import type { Page } from '@playwright/test' import { expect, test } from '@playwright/test' +import { addArrayRow } from 'helpers/e2e/fields/array/index.js' import path from 'path' import { fileURLToPath } from 'url' @@ -67,7 +68,7 @@ describe('Tabs', () => { test('should correctly save nested unnamed and named tabs', async () => { await page.goto(url.create) - await page.locator('#field-tabsInArray .array-field__add-row').click() + await addArrayRow(page, { fieldName: 'tabsInArray' }) await page.locator('#field-tabsInArray__0__text').fill('tab 1 text') await page.locator('.tabs-field__tabs button:nth-child(2)').click() await page.locator('#field-tabsInArray__0__tab2__text2').fill('tab 2 text') diff --git a/test/form-state/e2e.spec.ts b/test/form-state/e2e.spec.ts index f141d96d0..989e83cb3 100644 --- a/test/form-state/e2e.spec.ts +++ b/test/form-state/e2e.spec.ts @@ -3,10 +3,11 @@ import type { PayloadTestSDK } from 'helpers/sdk/index.js' import type { FormState } from 'payload' import { expect, test } from '@playwright/test' -import { addBlock } from 'helpers/e2e/addBlock.js' import { assertElementStaysVisible } from 'helpers/e2e/assertElementStaysVisible.js' import { assertNetworkRequests } from 'helpers/e2e/assertNetworkRequests.js' import { assertRequestBody } from 'helpers/e2e/assertRequestBody.js' +import { addArrayRowAsync, removeArrayRow } from 'helpers/e2e/fields/array/index.js' +import { addBlock } from 'helpers/e2e/fields/blocks/index.js' import { waitForAutoSaveToRunAndComplete } from 'helpers/e2e/waitForAutoSaveToRunAndComplete.js' import * as path from 'path' import { fileURLToPath } from 'url' @@ -162,7 +163,7 @@ test.describe('Form State', () => { // The `array` itself SHOULD have a `lastRenderedPath` because it was rendered on initial load await assertRequestBody<{ args: { formState: FormState } }[]>(page, { - action: async () => await page.locator('#field-array .array-field__add-row').click(), + action: async () => await addArrayRowAsync(page, 'array'), url: postsUrl.create, expect: (body) => Boolean( @@ -181,7 +182,7 @@ test.describe('Form State', () => { // The `array` itself SHOULD still have a `lastRenderedPath` // The custom text field in the first row SHOULD ALSO have a `lastRenderedPath` bc it was rendered in the first request await assertRequestBody<{ args: { formState: FormState } }[]>(page, { - action: async () => await page.locator('#field-array .array-field__add-row').click(), + action: async () => await addArrayRowAsync(page, 'array'), url: postsUrl.create, expect: (body) => Boolean( @@ -203,7 +204,7 @@ test.describe('Form State', () => { // The custom text field in the first row SHOULD ALSO have a `lastRenderedPath` bc it was rendered in the first request // The custom text field in the second row SHOULD ALSO have a `lastRenderedPath` bc it was rendered in the second request await assertRequestBody<{ args: { formState: FormState } }[]>(page, { - action: async () => await page.locator('#field-array .array-field__add-row').click(), + action: async () => await addArrayRowAsync(page, 'array'), url: postsUrl.create, expect: (body) => Boolean( @@ -220,10 +221,10 @@ test.describe('Form State', () => { test('should not render stale values for server components while form state is in flight', async () => { await page.goto(postsUrl.create) - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') await page.locator('#field-array #array-row-0 #field-array__0__customTextField').fill('1') - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') await page.locator('#field-array #array-row-1 #field-array__1__customTextField').fill('2') // block the next form state request from firing to ensure the field remains in stale state @@ -235,12 +236,7 @@ test.describe('Form State', () => { await route.continue() }) - // remove the first row - await page.locator('#field-array #array-row-0 .array-actions__button').click() - - await page - .locator('#field-array #array-row-0 .array-actions__action.array-actions__remove') - .click() + await removeArrayRow(page, { fieldName: 'array' }) await expect( page.locator('#field-array #array-row-0 #field-array__0__customTextField'), @@ -390,7 +386,7 @@ test.describe('Form State', () => { }) // Add the first row and expect an optimistic loading state - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') await expect(page.locator('#field-array #array-row-0')).toBeVisible() // use waitForSelector because the shimmer effect is not always visible @@ -401,7 +397,7 @@ test.describe('Form State', () => { await page.waitForRequest((request) => request.url() === postsUrl.create) // Before the first request comes back, add the second row and expect an optimistic loading state - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') await expect(page.locator('#field-array #array-row-1')).toBeVisible() // use waitForSelector because the shimmer effect is not always visible @@ -456,7 +452,7 @@ test.describe('Form State', () => { page, postsUrl.create, async () => { - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') await page.locator('#field-title').fill('Test 2') // use `waitForSelector` to ensure the element doesn't appear and then disappear @@ -485,8 +481,8 @@ test.describe('Form State', () => { page, postsUrl.create, async () => { - await page.locator('#field-array .array-field__add-row').click() - await page.locator('#field-array .array-field__add-row').click() + await addArrayRowAsync(page, 'array') + await addArrayRowAsync(page, 'array') // use `waitForSelector` to ensure the element doesn't appear and then disappear // eslint-disable-next-line playwright/no-wait-for-selector diff --git a/test/helpers/e2e/fields/array/addArrayRow.ts b/test/helpers/e2e/fields/array/addArrayRow.ts new file mode 100644 index 000000000..e47e51367 --- /dev/null +++ b/test/helpers/e2e/fields/array/addArrayRow.ts @@ -0,0 +1,47 @@ +import type { Locator, Page } from 'playwright' + +import { wait } from 'payload/shared' + +import { openArrayRowActions } from './openArrayRowActions.js' + +/** + * Does not wait after adding the row for the row to appear and fully load in. Simply clicks the primary "Add Row" button and moves on. + */ +export const addArrayRowAsync = async (page: Page, fieldName: string) => { + await page.locator(`#field-${fieldName} > .array-field__add-row`).first().click() +} + +/** + * Adds an array row to the end of the array using the primary "Add Row" button. + */ +export const addArrayRow = async ( + page: Page, + { fieldName }: Omit[1], 'rowIndex'>, +) => { + await addArrayRowAsync(page, fieldName) + + // TODO: test the array row has appeared + await wait(300) +} + +/** + * Like `addArrayRow`, but inserts the row at the specified index using the row actions menu. + */ +export const addArrayRowBelow = async ( + page: Page, + { fieldName, rowIndex = 0 }: Parameters[1], +): Promise<{ popupContentLocator: Locator; rowActionsButtonLocator: Locator }> => { + const { popupContentLocator, rowActionsButtonLocator } = await openArrayRowActions(page, { + fieldName, + rowIndex, + }) + + const addBelowButton = popupContentLocator.locator('.array-actions__action.array-actions__add') + + await addBelowButton.click() + + // TODO: test the array row has appeared + await wait(300) + + return { popupContentLocator, rowActionsButtonLocator } +} diff --git a/test/helpers/e2e/fields/array/duplicateArrayRow.ts b/test/helpers/e2e/fields/array/duplicateArrayRow.ts new file mode 100644 index 000000000..4019a13a3 --- /dev/null +++ b/test/helpers/e2e/fields/array/duplicateArrayRow.ts @@ -0,0 +1,25 @@ +import type { Locator, Page } from 'playwright' + +import { openArrayRowActions } from './openArrayRowActions.js' + +/** + * Duplicates the array row at the specified index. + */ +export const duplicateArrayRow = async ( + page: Page, + { fieldName, rowIndex = 0 }: Parameters[1], +): Promise<{ + popupContentLocator: Locator + rowActionsButtonLocator: Locator +}> => { + const { popupContentLocator, rowActionsButtonLocator } = await openArrayRowActions(page, { + fieldName, + rowIndex, + }) + + await popupContentLocator.locator('.array-actions__action.array-actions__duplicate').click() + + // TODO: test the array row has been duplicated + + return { popupContentLocator, rowActionsButtonLocator } +} diff --git a/test/helpers/e2e/fields/array/index.ts b/test/helpers/e2e/fields/array/index.ts new file mode 100644 index 000000000..4961db9cf --- /dev/null +++ b/test/helpers/e2e/fields/array/index.ts @@ -0,0 +1,4 @@ +export { addArrayRow, addArrayRowAsync, addArrayRowBelow } from './addArrayRow.js' +export { duplicateArrayRow } from './duplicateArrayRow.js' +export { openArrayRowActions } from './openArrayRowActions.js' +export { removeArrayRow } from './removeArrayRow.js' diff --git a/test/helpers/e2e/fields/array/openArrayRowActions.ts b/test/helpers/e2e/fields/array/openArrayRowActions.ts new file mode 100644 index 000000000..9e252738a --- /dev/null +++ b/test/helpers/e2e/fields/array/openArrayRowActions.ts @@ -0,0 +1,45 @@ +import type { Locator, Page } from 'playwright' + +import { expect } from 'playwright/test' + +/** + * Opens the row actions menu for the specified array row. + * If already open, does nothing. + */ +export const openArrayRowActions = async ( + page: Page, + { + fieldName, + rowIndex = 0, + }: { + fieldName: string + rowIndex?: number + }, +): Promise<{ + popupContentLocator: Locator + rowActionsButtonLocator: Locator +}> => { + // replace double underscores with single hyphens for the row ID + const formattedRowID = fieldName.toString().replace(/__/g, '-') + + const rowActions = page + .locator(`#field-${fieldName} #${formattedRowID}-row-${rowIndex} .array-actions`) + .first() + + const popupContentLocator = rowActions.locator('.popup__content') + + if (await popupContentLocator.isVisible()) { + throw new Error(`Row actions for field "${fieldName}" at index ${rowIndex} are already open.`) + } + + const rowActionsButtonLocator = rowActions.locator(`.array-actions__button`) + + await rowActionsButtonLocator.click() + + await expect(popupContentLocator).toBeVisible() + + return { + rowActionsButtonLocator, + popupContentLocator, + } +} diff --git a/test/helpers/e2e/fields/array/removeArrayRow.ts b/test/helpers/e2e/fields/array/removeArrayRow.ts new file mode 100644 index 000000000..0d69b4cb8 --- /dev/null +++ b/test/helpers/e2e/fields/array/removeArrayRow.ts @@ -0,0 +1,26 @@ +import type { Locator, Page } from 'playwright' + +import { openArrayRowActions } from './openArrayRowActions.js' + +/** + * Removes an array row at the specified index. + */ +export const removeArrayRow = async ( + page: Page, + { fieldName, rowIndex = 0 }: Parameters[1], +): Promise<{ + popupContentLocator: Locator + rowActionsButtonLocator: Locator +}> => { + const { popupContentLocator, rowActionsButtonLocator } = await openArrayRowActions(page, { + fieldName, + rowIndex, + }) + + await popupContentLocator.locator('.array-actions__action.array-actions__remove').click() + + // TODO: test the array row has been removed + // another row may have been moved into its place, though + + return { popupContentLocator, rowActionsButtonLocator } +} diff --git a/test/helpers/e2e/addBlock.ts b/test/helpers/e2e/fields/blocks/addBlock.ts similarity index 100% rename from test/helpers/e2e/addBlock.ts rename to test/helpers/e2e/fields/blocks/addBlock.ts diff --git a/test/helpers/e2e/fields/blocks/index.ts b/test/helpers/e2e/fields/blocks/index.ts new file mode 100644 index 000000000..5b59954d0 --- /dev/null +++ b/test/helpers/e2e/fields/blocks/index.ts @@ -0,0 +1,4 @@ +export { addBlock } from './addBlock.js' +export { openBlocksDrawer } from './openBlocksDrawer.js' +export { removeAllBlocks } from './removeAllBlocks.js' +export { reorderBlocks } from './reorderBlocks.js' diff --git a/test/helpers/e2e/openBlocksDrawer.ts b/test/helpers/e2e/fields/blocks/openBlocksDrawer.ts similarity index 100% rename from test/helpers/e2e/openBlocksDrawer.ts rename to test/helpers/e2e/fields/blocks/openBlocksDrawer.ts diff --git a/test/helpers/e2e/removeAllBlocks.ts b/test/helpers/e2e/fields/blocks/removeAllBlocks.ts similarity index 100% rename from test/helpers/e2e/removeAllBlocks.ts rename to test/helpers/e2e/fields/blocks/removeAllBlocks.ts diff --git a/test/helpers/e2e/reorderBlocks.ts b/test/helpers/e2e/fields/blocks/reorderBlocks.ts similarity index 100% rename from test/helpers/e2e/reorderBlocks.ts rename to test/helpers/e2e/fields/blocks/reorderBlocks.ts diff --git a/test/helpers/e2e/fields/relationship/openCreateDocDrawer.ts b/test/helpers/e2e/fields/relationship/openCreateDocDrawer.ts new file mode 100644 index 000000000..1e86f44e6 --- /dev/null +++ b/test/helpers/e2e/fields/relationship/openCreateDocDrawer.ts @@ -0,0 +1,20 @@ +import type { Page } from 'playwright' + +import { wait } from 'payload/shared' +import { expect } from 'playwright/test' + +export async function openCreateDocDrawer({ + page, + fieldSelector, +}: { + fieldSelector: string + page: Page +}): Promise { + await wait(500) // wait for parent form state to initialize + const relationshipField = page.locator(fieldSelector) + await expect(relationshipField.locator('input')).toBeEnabled() + const addNewButton = relationshipField.locator('.relationship-add-new__add-button') + await expect(addNewButton).toBeVisible() + await addNewButton.click() + await wait(500) // wait for drawer form state to initialize +} diff --git a/test/helpers/e2e/toggleDocDrawer.ts b/test/helpers/e2e/toggleDocDrawer.ts index 1e05afa65..4fc5a8d63 100644 --- a/test/helpers/e2e/toggleDocDrawer.ts +++ b/test/helpers/e2e/toggleDocDrawer.ts @@ -1,6 +1,5 @@ import type { Page } from '@playwright/test' -import { expect } from '@playwright/test' import { wait } from 'payload/shared' export async function openDocDrawer({ @@ -20,19 +19,3 @@ export async function openDocDrawer({ await page.locator(selector).click(clickProperties) await wait(500) // wait for drawer form state to initialize } - -export async function openCreateDocDrawer({ - page, - fieldSelector, -}: { - fieldSelector: string - page: Page -}): Promise { - await wait(500) // wait for parent form state to initialize - const relationshipField = page.locator(fieldSelector) - await expect(relationshipField.locator('input')).toBeEnabled() - const addNewButton = relationshipField.locator('.relationship-add-new__add-button') - await expect(addNewButton).toBeVisible() - await addNewButton.click() - await wait(500) // wait for drawer form state to initialize -} diff --git a/test/localization/e2e.spec.ts b/test/localization/e2e.spec.ts index ed743488c..f5d4c1724 100644 --- a/test/localization/e2e.spec.ts +++ b/test/localization/e2e.spec.ts @@ -2,6 +2,7 @@ import type { BrowserContext, Page } from '@playwright/test' import type { GeneratedTypes } from 'helpers/sdk/types.js' import { expect, test } from '@playwright/test' +import { addArrayRow } from 'helpers/e2e/fields/array/index.js' import { navigateToDoc } from 'helpers/e2e/navigateToDoc.js' import { openDocControls } from 'helpers/e2e/openDocControls.js' import { upsertPreferences } from 'helpers/e2e/preferences.js' @@ -420,8 +421,7 @@ describe('Localization', () => { const nestedArrayURL = new AdminUrlUtil(serverURL, nestedToArrayAndBlockCollectionSlug) await page.goto(nestedArrayURL.create) await changeLocale(page, 'ar') - const addArrayRow = page.locator('#field-topLevelArray .array-field__add-row') - await addArrayRow.click() + await addArrayRow(page, { fieldName: 'topLevelArray' }) const arrayField = page.locator('#field-topLevelArray__0__localizedText') await expect(arrayField).toBeVisible() @@ -676,8 +676,7 @@ describe('Localization', () => { async function createLocalizedArrayItem(page: Page, url: AdminUrlUtil) { await changeLocale(page, defaultLocale) await page.goto(url.create) - const addArrayRow = page.locator('#field-items .array-field__add-row') - await addArrayRow.click() + await addArrayRow(page, { fieldName: 'items' }) const textField = page.locator('#field-items__0__text') await textField.fill('test') await saveDocAndAssert(page)