Files
payloadcms/test/sort/e2e.spec.ts
Jarrod Flesch 72349245ca test: fix flaky sorting test (#13303)
Ensures the browser uses fresh data after seeding by refreshing the
route and navigating when done.
2025-07-29 03:25:09 +00:00

161 lines
5.8 KiB
TypeScript

import type { BrowserContext, Page } from '@playwright/test'
import { expect, test } from '@playwright/test'
import { RESTClient } from 'helpers/rest.js'
import path from 'path'
import { fileURLToPath } from 'url'
import type { PayloadTestSDK } from '../helpers/sdk/index.js'
import type { Config } from './payload-types.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { orderableSlug } from './collections/Orderable/index.js'
import { orderableJoinSlug } from './collections/OrderableJoin/index.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const { beforeAll, describe } = test
let page: Page
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let payload: PayloadTestSDK<Config>
let client: RESTClient
let serverURL: string
let context: BrowserContext
describe('Sort functionality', () => {
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({ dirname }))
context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
client = new RESTClient({ defaultSlug: 'users', serverURL })
await client.login()
await ensureCompilationIsDone({ page, serverURL })
})
// NOTES: It works for me in headed browser but not in headless, I don't know why.
// If you are debugging this test, remember to press the seed button before each attempt.
// assertRows contains expect
// eslint-disable-next-line playwright/expect-expect
test('Orderable collection', async () => {
const url = new AdminUrlUtil(serverURL, orderableSlug)
await page.goto(url.list)
const joinFieldResolvePromise = page.waitForResponse(
(response) => response.url().includes('/api/orderable-join') && response.status() === 200,
)
const seedResponsePromise = page.waitForResponse(
(response) => response.url().includes('/api/seed') && response.status() === 200,
)
await page.locator('.collection-list button', { hasText: 'Seed' }).click()
await seedResponsePromise
await joinFieldResolvePromise
await page.goto(`${url.list}?sort=-_order`)
// SORT BY ORDER ASCENDING
await page.locator('.sort-header button').nth(0).click()
await assertRows(0, 'A', 'B', 'C', 'D')
await moveRow(2, 3) // move to middle
await assertRows(0, 'A', 'C', 'B', 'D')
await moveRow(3, 1) // move to top
await assertRows(0, 'B', 'A', 'C', 'D')
await moveRow(1, 4) // move to bottom
await assertRows(0, 'A', 'C', 'D', 'B')
// SORT BY ORDER DESCENDING
await page.locator('.sort-header button').nth(0).click()
await page.waitForURL(/sort=-_order/, { timeout: 2000 })
await assertRows(0, 'B', 'D', 'C', 'A')
await moveRow(1, 3) // move to middle
await assertRows(0, 'D', 'C', 'B', 'A')
await moveRow(3, 1) // move to top
await assertRows(0, 'B', 'D', 'C', 'A')
await moveRow(1, 4) // move to bottom
await assertRows(0, 'D', 'C', 'A', 'B')
// SORT BY TITLE
await page.getByLabel('Sort by Title Ascending').click()
await page.waitForURL(/sort=title/, { timeout: 2000 })
await moveRow(1, 3, 'warning') // warning because not sorted by order first
})
test('Orderable join fields', async () => {
const url = new AdminUrlUtil(serverURL, orderableJoinSlug)
await page.goto(url.list)
await page.getByText('Join A').click()
await expect(page.locator('.sort-header button')).toHaveCount(3)
await assertRows(0, 'A', 'B', 'C', 'D')
await moveRow(2, 3, 'success', 0) // move to middle
await assertRows(0, 'A', 'C', 'B', 'D')
await assertRows(1, 'A', 'B', 'C', 'D')
await moveRow(1, 4, 'success', 1) // move to end
await assertRows(1, 'B', 'C', 'D', 'A')
await page.reload()
await assertRows(0, 'A', 'C', 'B', 'D')
await assertRows(1, 'B', 'C', 'D', 'A')
})
})
async function moveRow(
from: number,
to: number,
expected: 'success' | 'warning' = 'success',
nthTable = 0,
) {
// counting from 1, zero excluded
const table = page.locator(`tbody`).nth(nthTable)
const dragHandle = table.locator(`.sort-row`)
const source = dragHandle.nth(from - 1)
const target = dragHandle.nth(to - 1)
const sourceBox = await source.boundingBox()
const targetBox = await target.boundingBox()
if (!sourceBox || !targetBox) {
throw new Error(
`Could not find elements to DnD. Probably the dndkit animation is not finished. Try increasing the timeout`,
)
}
// steps is important: move slightly to trigger the drag sensor of DnD-kit
await page.mouse.move(sourceBox.x + sourceBox.width / 2, sourceBox.y + sourceBox.height / 2, {
steps: 10,
})
await page.mouse.down()
await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y + targetBox.height / 2, {
steps: 10,
})
await page.mouse.up()
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(400) // dndkit animation
if (expected === 'warning') {
const toast = page.locator('.payload-toast-item.toast-warning')
await expect(toast).toHaveText(
'To reorder the rows you must first sort them by the "Order" column',
)
}
}
async function assertRows(nthTable: number, ...expectedRows: Array<string>) {
const table = page.locator('tbody').nth(nthTable)
const cellTitle = table.locator('.cell-title > :first-child')
const rows = table.locator('.sort-row')
await expect.poll(() => rows.count()).toBe(expectedRows.length)
for (let i = 0; i < expectedRows.length; i++) {
await expect(cellTitle.nth(i)).toHaveText(expectedRows[i]!)
}
}