test: blocks field helpers (#11259)

Similar to the goals of #11026. Adds helper utilities to make
interacting with the blocks field easier within e2e tests. This will
also standardize common functionality across tests and reduce the
overall lines of code for each, making them easier to navigate and
digest.

The following helpers are now available:

- `openBlocksDrawer`: self-explanatory
- `addBlock`: opens the blocks drawer and selects the given block
- `reorderBlocks`: similar to `reorderColumn`, moves blocks using the
drag handle
- `removeAllBlocks`: iterates all rows of a given blocks field and
removes them
This commit is contained in:
Jacob Fletcher
2025-02-18 15:48:57 -05:00
committed by GitHub
parent 6d36a28cdc
commit 8166784ba2
6 changed files with 163 additions and 66 deletions

View File

@@ -1,6 +1,8 @@
import type { Page } from '@playwright/test'
import type { BrowserContext, Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { addBlock } from 'helpers/e2e/addBlock.js'
import { openBlocksDrawer } from 'helpers/e2e/openBlocksDrawer.js'
import path from 'path'
import { fileURLToPath } from 'url'
@@ -24,6 +26,8 @@ const { beforeAll, beforeEach, describe } = test
let client: RESTClient
let page: Page
let serverURL: string
let context: BrowserContext
// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
describe('Block fields', () => {
@@ -35,12 +39,13 @@ describe('Block fields', () => {
dirname,
}))
const context = await browser.newContext()
context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
await ensureCompilationIsDone({ page, serverURL })
})
beforeEach(async () => {
await reInitializeDB({
serverURL,
@@ -58,25 +63,19 @@ describe('Block fields', () => {
})
let url: AdminUrlUtil
beforeAll(() => {
url = new AdminUrlUtil(serverURL, 'block-fields')
})
test('should open blocks drawer and select first block', async () => {
await page.goto(url.create)
const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler')
await expect(addButton).toContainText('Add Block')
await addButton.click()
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
await expect(blocksDrawer).toBeVisible()
// select the first block in the drawer
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await expect(firstBlockSelector).toContainText('Content')
await firstBlockSelector.click()
await addBlock({
page,
fieldName: 'blocks',
blockLabel: 'Content',
})
// ensure the block was appended to the rows
const addedRow = page.locator('#field-blocks .blocks-field__row').last()
@@ -88,17 +87,17 @@ describe('Block fields', () => {
test('should reset search state in blocks drawer on re-open', async () => {
await page.goto(url.create)
const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler')
await expect(addButton).toContainText('Add Block')
await addButton.click()
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
await expect(blocksDrawer).toBeVisible()
const blocksDrawer = await openBlocksDrawer({
page,
fieldName: 'blocks',
})
const searchInput = page.locator('.block-search__input')
await searchInput.fill('Number')
// select the first block in the drawer
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
@@ -106,6 +105,7 @@ describe('Block fields', () => {
await expect(firstBlockSelector).toContainText('Number')
await page.locator('.drawer__header__close').click()
const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler')
await addButton.click()
await expect(blocksDrawer).toBeVisible()
@@ -131,6 +131,7 @@ describe('Block fields', () => {
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await expect(firstBlockSelector).toContainText('Content')
await firstBlockSelector.click()
@@ -179,19 +180,11 @@ describe('Block fields', () => {
await page.goto(url.create)
await expect(page.locator('#field-i18nBlocks .blocks-field__header')).toContainText('Block en')
const addButton = page.locator('#field-i18nBlocks > .blocks-field__drawer-toggler')
await expect(addButton).toContainText('Add Block en')
await addButton.click()
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
await expect(blocksDrawer).toBeVisible()
// select the first block in the drawer
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await expect(firstBlockSelector).toContainText('Text en')
await firstBlockSelector.click()
await addBlock({
page,
fieldName: 'i18nBlocks',
blockLabel: 'Text en',
})
// ensure the block was appended to the rows
const firstRow = page.locator('#field-i18nBlocks .blocks-field__row').first()
@@ -203,15 +196,12 @@ describe('Block fields', () => {
test('should render custom block row label', async () => {
await page.goto(url.create)
const addButton = page.locator('#field-blocks > .blocks-field__drawer-toggler')
await addButton.click()
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
await blocksDrawer
.locator('.blocks-drawer__block .thumbnail-card__label', {
hasText: 'Content',
})
.click()
await addBlock({
page,
fieldName: 'blocks',
blockLabel: 'Content',
})
await expect(
page.locator('#field-blocks .blocks-field__row .blocks-field__block-header', {
@@ -223,20 +213,17 @@ describe('Block fields', () => {
test('should add different blocks with similar field configs', async () => {
await page.goto(url.create)
async function addBlock(name: 'Block A' | 'Block B') {
await page
.locator('#field-blocksWithSimilarConfigs')
.getByRole('button', { name: 'Add Blocks With Similar Config' })
.click()
await page.getByRole('button', { name }).click()
}
await addBlock('Block A')
await addBlock({
page,
fieldName: 'blocksWithSimilarConfigs',
blockLabel: 'Block A',
})
await page
.locator('#blocksWithSimilarConfigs-row-0')
.getByRole('button', { name: 'Add Item' })
.click()
await page
.locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]')
.fill('items>0>title')
@@ -245,12 +232,17 @@ describe('Block fields', () => {
page.locator('input[name="blocksWithSimilarConfigs.0.items.0.title"]'),
).toHaveValue('items>0>title')
await addBlock('Block B')
await addBlock({
page,
fieldName: 'blocksWithSimilarConfigs',
blockLabel: 'Block B',
})
await page
.locator('#blocksWithSimilarConfigs-row-1')
.getByRole('button', { name: 'Add Item' })
.click()
await page
.locator('input[name="blocksWithSimilarConfigs.1.items.0.title2"]')
.fill('items>1>title')
@@ -269,19 +261,11 @@ describe('Block fields', () => {
test('should fail min rows validation when rows are present', async () => {
await page.goto(url.create)
await page
.locator('#field-blocksWithMinRows')
.getByRole('button', { name: 'Add Blocks With Min Row' })
.click()
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
await expect(blocksDrawer).toBeVisible()
const firstBlockSelector = blocksDrawer
.locator('.blocks-drawer__blocks .blocks-drawer__block')
.first()
await firstBlockSelector.click()
await addBlock({
page,
fieldName: 'blocksWithMinRows',
blockLabel: 'Block With Min Row',
})
const firstRow = page.locator('input[name="blocksWithMinRows.0.blockTitle"]')
await expect(firstRow).toBeVisible()
@@ -328,6 +312,7 @@ describe('Block fields', () => {
.locator('.custom-blocks-field-management')
.getByRole('button', { name: 'Add Block 2' })
.click()
await expect(
page.locator('#field-customBlocks input[name="customBlocks.1.block2Title"]'),
).toHaveValue('Block 2: Prefilled Title')
@@ -336,6 +321,7 @@ describe('Block fields', () => {
.locator('.custom-blocks-field-management')
.getByRole('button', { name: 'Replace Block 2' })
.click()
await expect(
page.locator('#field-customBlocks input[name="customBlocks.1.block1Title"]'),
).toHaveValue('REPLACED BLOCK')
@@ -344,13 +330,13 @@ describe('Block fields', () => {
})
describe('sortable blocks', () => {
test('should have disabled admin sorting', async () => {
test('should not render sort controls when sorting is disabled', async () => {
await page.goto(url.create)
const field = page.locator('#field-disableSort > div > div > .array-actions__action-chevron')
expect(await field.count()).toEqual(0)
})
test('the drag handle should be hidden', async () => {
test('should not render drag handle when sorting is disabled', async () => {
await page.goto(url.create)
const field = page.locator(
'#field-disableSort > .blocks-field__rows > div > div > .collapsible__drag',

View File

@@ -0,0 +1,28 @@
import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { exactText } from 'helpers.js'
import { openBlocksDrawer } from './openBlocksDrawer.js'
export const addBlock = async ({
page,
fieldName = 'blocks',
blockLabel = 'Block',
}: {
blockLabel: string
fieldName: string
page: Page
}) => {
const blocksDrawer = await openBlocksDrawer({ page, fieldName })
const blockCard = blocksDrawer.locator('.blocks-drawer__block .thumbnail-card__label', {
hasText: blockLabel,
})
await expect(blockCard).toBeVisible()
await blocksDrawer.getByRole('button', { name: exactText(blockLabel) }).click()
// expect to see the block on the page
}

View File

@@ -0,0 +1,22 @@
import type { Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
export const openBlocksDrawer = async ({
page,
fieldName = 'blocks',
}: {
fieldName: string
page: Page
}): Promise<Locator> => {
const blocksDrawer = page.locator('[id^=drawer_1_blocks-drawer-]')
if (!(await blocksDrawer.isVisible())) {
const addButton = page.locator(`#field-${fieldName} > .blocks-field__drawer-toggler`)
await addButton.click()
}
await expect(blocksDrawer).toBeVisible()
return blocksDrawer
}

View File

@@ -0,0 +1,25 @@
import type { Page } from '@playwright/test'
import { expect } from '@playwright/test'
export const removeAllBlocks = async ({
page,
fieldName = 'blocks',
}: {
fieldName: string
page: Page
}) => {
const blocksField = page.locator(`#field-${fieldName}`)
const blocks = blocksField.locator(`[id^="${fieldName}-row-"]`)
const count = await blocks.count()
expect(count).toBeGreaterThan(0)
for (let i = 0; i < count; i++) {
// delete in reverse order to avoid index issues
const block = blocksField.locator(`[id^="${fieldName}-row-${count - i - 1}"]`)
await block.locator('.array-actions__button').first().click()
await block.locator('.array-actions__action.array-actions__remove').first().click()
}
}

View File

@@ -0,0 +1,36 @@
import type { Page } from '@playwright/test'
import { wait } from 'payload/shared'
export const reorderBlocks = async ({
page,
fromBlockIndex = 1,
toBlockIndex = 2,
fieldName = 'blocks',
}: {
fieldName?: string
fromBlockIndex: number
page: Page
toBlockIndex: number
}) => {
const blocksField = page.locator(`#field-${fieldName}`).first()
const fromField = blocksField.locator(`[id^="${fieldName}-row-${fromBlockIndex}"]`)
const fromBoundingBox = await fromField.locator(`.collapsible__drag`).boundingBox()
const toField = blocksField.locator(`[id^="${fieldName}-row-${toBlockIndex}"]`)
const toBoundingBox = await toField.locator(`.collapsible__drag`).boundingBox()
if (!fromBoundingBox || !toBoundingBox) {
return
}
// drag the "from" column to the left of the "to" column
await page.mouse.move(fromBoundingBox.x + 2, fromBoundingBox.y + 2, { steps: 10 })
await page.mouse.down()
await wait(300)
await page.mouse.move(toBoundingBox.x - 2, toBoundingBox.y - 2, { steps: 10 })
await page.mouse.up()
}

View File

@@ -31,7 +31,7 @@
}
],
"paths": {
"@payload-config": ["./test/_community/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"],