Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default createE2EConfig([
{ file: 'fields__collections__UploadPoly', shards: 1 },
{ file: 'fields__collections__UploadMultiPoly', shards: 1 },
{ file: 'group-by', shards: 1 },
{ file: 'folders', shards: 1 },
{ file: 'folders', shards: 3 },
{ file: 'hooks', shards: 1 },
// TODO: Enable parallel mode again when ensureCompilationIsDone is extracted into a playwright hook. Otherwise,
// it runs multiple times in parallel, for each single test, which causes the tests to fail occasionally in CI.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
"devDependencies": {
"@axe-core/playwright": "4.11.0",
"@libsql/client": "0.14.0",
"@next/bundle-analyzer": "16.2.0-canary.90",
"@next/bundle-analyzer": "16.2.0",
"@payloadcms/db-postgres": "workspace:*",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/eslint-plugin": "workspace:*",
Expand Down Expand Up @@ -180,7 +180,7 @@
"lint-staged": "15.2.7",
"minimist": "1.2.8",
"mongoose": "8.15.1",
"next": "16.2.0-canary.90",
"next": "16.2.0",
"node-gyp": "12.2.0",
"open": "^10.1.0",
"p-limit": "^5.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
"@babel/preset-env": "7.27.2",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"@next/eslint-plugin-next": "16.2.0-canary.90",
"@next/eslint-plugin-next": "16.2.0",
"@payloadcms/eslint-config": "workspace:*",
"@types/busboy": "1.5.4",
"@types/react": "19.2.9",
Expand Down
11 changes: 8 additions & 3 deletions packages/payload/src/utilities/addDataAndFileToRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ export const addDataAndFileToRequest: AddDataAndFileToRequest = async (req) => {
}

if (!req.file && fields?.file && typeof fields?.file === 'string') {
const { clientUploadContext, collectionSlug, filename, mimeType, size } = JSON.parse(
fields.file,
)
let clientUploadContext, collectionSlug, filename, mimeType, size
try {
;({ clientUploadContext, collectionSlug, filename, mimeType, size } = JSON.parse(
fields.file,
))
} catch {
throw new APIError('A file name is required.', 400)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing bug - fixes cryptic JSON.parse errors thrown in server console when uploading file without file name. Instead, we get a proper 400 error

}
const uploadConfig = req.payload.collections[collectionSlug]!.config.upload

if (!uploadConfig.handlers) {
Expand Down
35 changes: 26 additions & 9 deletions packages/ui/src/elements/BulkUpload/FormsManager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,24 +414,41 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
if (missingFile || exceedsLimit || missingFilename) {
currentForms[i].formState.file.valid = false

// neeed to get the field state to extract count since field errors
// File/Blob objects cannot be serialized via the RSC flight protocol,
// so replace with a plain object before calling the server function.
const originalFileValue = currentForms[i].formState.file?.value
const formStateForServer = { ...currentForms[i].formState }
if (originalFileValue instanceof File) {
formStateForServer.file = {
...formStateForServer.file,
value: { name: originalFileValue.name },
}
}

// Need to get the field state to extract count since field errors
// are not returned when file is missing or exceeds limit
const { state: newState } = await getFormState({
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New bug - Next.js now complains if we call a server action (getFormState) with a File, so we need to strip it. This is new and was not an issue in 16.2.0-canary.90

collectionSlug,
docPermissions,
docPreferences: null,
formState: currentForms[i].formState,
formState: formStateForServer,
operation: 'update',
schemaPath: collectionSlug,
})

currentForms[i] = {
errorCount: Object.values(newState).reduce(
(acc, value) => (value?.valid === false ? acc + 1 : acc),
0,
),
formID: currentForms[i].formID,
formState: newState,
if (newState) {
if (originalFileValue instanceof File && newState.file) {
newState.file = { ...newState.file, value: originalFileValue }
}

currentForms[i] = {
errorCount: Object.values(newState).reduce(
(acc, value) => (value?.valid === false ? acc + 1 : acc),
0,
),
formID: currentForms[i].formID,
formState: newState,
}
}

toast.error(nonFieldErrors[0]?.message)
Expand Down
3,012 changes: 1,504 additions & 1,508 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions test/__helpers/e2e/folders/createFolder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export async function createFolder({
page,
folderType = ['Posts'],
}: Args): Promise<void> {
const drawer = page.locator('dialog .collection-edit--payload-folders')

if (fromDropdown) {
const titleActionsLocator = page.locator('.list-header__title-actions')
const folderDropdown = titleActionsLocator.locator('.create-new-doc-in-folder__action-popup', {
Expand All @@ -23,12 +25,22 @@ export async function createFolder({
const createFolderButton = page.locator('.popup__content .popup-button-list__button', {
hasText: 'Folder',
})
await createFolderButton.click()
await expect(async () => {
if (await drawer.isHidden()) {
await createFolderButton.click()
}
await expect(drawer).toBeVisible()
}).toPass({ timeout: 30000 })
} else {
const createFolderButton = page.locator(
'.list-header__title-and-actions .create-new-doc-in-folder__button:has-text("Create folder")',
)
await createFolderButton.click()
await expect(async () => {
if (await drawer.isHidden()) {
await createFolderButton.click()
}
await expect(drawer).toBeVisible()
}).toPass({ timeout: 30000 })
}

await createFolderDoc({
Expand Down
10 changes: 8 additions & 2 deletions test/__helpers/e2e/folders/createFolderFromDoc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect, type Page } from '@playwright/test'

import { createFolder } from './createFolder.js'
import { createFolderDoc } from './createFolderDoc.js'

type Args = {
Expand All @@ -17,7 +16,14 @@ export async function createFolderFromDoc({
const addFolderButton = page.locator('.create-new-doc-in-folder__button', {
hasText: 'Create folder',
})
await addFolderButton.click()
const drawer = page.locator('dialog .collection-edit--payload-folders')

await expect(async () => {
if (await drawer.isHidden()) {
await addFolderButton.click()
}
await expect(drawer).toBeVisible()
}).toPass({ timeout: 15000 })

await createFolderDoc({
page,
Expand Down
11 changes: 10 additions & 1 deletion test/__helpers/e2e/folders/selectFolderAndConfirmMoveFromList.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Page } from '@playwright/test'

import { expect } from '@playwright/test'

import { clickFolderCard } from './clickFolderCard.js'

type Args = {
Expand All @@ -14,7 +16,14 @@ export async function selectFolderAndConfirmMoveFromList({
}: Args): Promise<void> {
const firstListItem = page.locator(`tbody .row-${rowIndex}`)
const folderPill = firstListItem.locator('.move-doc-to-folder')
await folderPill.click()
const moveDrawer = page.locator('dialog .move-folder-drawer')

await expect(async () => {
if (await moveDrawer.isHidden()) {
await folderPill.click()
}
await expect(moveDrawer).toBeVisible()
}).toPass({ timeout: 30000 })

if (folderName) {
await clickFolderCard({ folderName, doubleClick: true, page })
Expand Down
4 changes: 2 additions & 2 deletions test/admin-root/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import * as path from 'path'
import { adminRoute } from 'shared.js'
import { fileURLToPath } from 'url'

import { login } from '../__helpers/e2e/auth/login.js'
import {
ensureCompilationIsDone,
initPageConsoleErrorCatch,
saveDocAndAssert,
// throttleTest,
} from '../__helpers/e2e/helpers.js'
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
import { login } from '../__helpers/e2e/auth/login.js'
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'

Expand Down Expand Up @@ -131,7 +131,7 @@ test.describe('Admin Panel (Root)', () => {
await expect(favicons.nth(0)).toHaveAttribute('sizes', '32x32')
await expect(favicons.nth(1)).toHaveAttribute('sizes', '32x32')
await expect(favicons.nth(1)).toHaveAttribute('media', '(prefers-color-scheme: dark)')
await expect(favicons.nth(1)).toHaveAttribute('href', /\/payload-favicon-light\.[a-z\d]+\.png/)
await expect(favicons.nth(1)).toHaveAttribute('href', /\/payload-favicon-light\.[a-z\d_]+\.png/)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file name now has an underscore

})

test('config.admin.theme should restrict the theme', async () => {
Expand Down
3 changes: 0 additions & 3 deletions test/admin-root/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ const dirname = path.dirname(__filename)

export default withBundleAnalyzer(
withPayload({
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
Expand Down
4 changes: 2 additions & 2 deletions test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@miniflare/d1": "2.14.4",
"@miniflare/shared": "2.14.4",
"@modelcontextprotocol/sdk": "^1.27.1",
"@next/env": "16.2.0-canary.90",
"@next/env": "16.2.0",
"@opennextjs/cloudflare": "1.16.1",
"@payloadcms/admin-bar": "workspace:*",
"@payloadcms/db-d1-sqlite": "workspace:*",
Expand Down Expand Up @@ -97,7 +97,7 @@
"http-status": "2.1.0",
"jwt-decode": "4.0.0",
"mongoose": "8.15.1",
"next": "16.2.0-canary.90",
"next": "16.2.0",
"nodemailer": "7.0.12",
"object-to-formdata": "4.5.1",
"payload": "workspace:*",
Expand Down
16 changes: 10 additions & 6 deletions test/plugin-form-builder/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
initPageConsoleErrorCatch,
saveDocAndAssert,
} from '../__helpers/e2e/helpers.js'
import { selectInput } from '../__helpers/e2e/selectInput.js'
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
Expand Down Expand Up @@ -136,13 +137,16 @@ test.describe('Form Builder Plugin', () => {

test('can create form submission from the admin panel', async () => {
await page.goto(submissionsUrl.create)
await page.locator('#field-form').click({ delay: 100 })
const options = page.locator('.rs__option')
await options.locator('text=Contact Form').click()

await expect(page.locator('#field-form').locator('.rs__value-container')).toContainText(
'Contact Form',
)
const formSelect = page.locator('#field-form')
await selectInput({
selectLocator: formSelect,
multiSelect: false,
option: 'Contact Form',
selectType: 'relationship',
})

await expect(formSelect.locator('.rs__value-container')).toContainText('Contact Form')

await page.locator('#field-submissionData button.array-field__add-row').click()
await page.locator('#field-submissionData__0__field').fill('name')
Expand Down
36 changes: 13 additions & 23 deletions test/plugin-seo/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { fileURLToPath } from 'url'

import type { Config, Page as PayloadPage } from './payload-types.js'

import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../__helpers/e2e/helpers.js'
import {
ensureCompilationIsDone,
initPageConsoleErrorCatch,
switchTab,
} from '../__helpers/e2e/helpers.js'
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
Expand Down Expand Up @@ -80,12 +84,10 @@ describe('SEO Plugin', () => {

test('Should auto-generate meta title when button is clicked in tabs', async () => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'
const metaTitleClass = '#field-meta__title'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, '.tabs-field__tab-button:has-text("SEO")')

const metaTitle = page.locator(metaTitleClass)

Expand All @@ -109,11 +111,9 @@ describe('SEO Plugin', () => {

test('Indicator should be orangered and characters counted', async () => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, '.tabs-field__tab-button:has-text("SEO")')

const autoGenButton = page.locator(autoGenerateButtonClass).nth(0)
await autoGenButton.click()
Expand All @@ -132,14 +132,11 @@ describe('SEO Plugin', () => {

test('Should generate a search result preview based on content', async () => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'
const metaDescriptionClass = '#field-meta__description'
const previewClass =
'#field-meta > div > div.render-fields.render-fields--margins-small > div:nth-child(5)'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, '.tabs-field__tab-button:has-text("SEO")')

const metaDescription = page.locator(metaDescriptionClass)
await metaDescription.fill('My new amazing SEO description')
Expand All @@ -154,11 +151,10 @@ describe('SEO Plugin', () => {
describe('i18n', () => {
test('support for another language', async () => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'
const autoGenerateButtonClass = '.group-field__wrap .render-fields div:nth-of-type(1) button'
const seoTabSelector = '.tabs-field__tab-button:has-text("SEO")'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, seoTabSelector)

const autoGenButton = page.locator(autoGenerateButtonClass).nth(0)

Expand All @@ -179,10 +175,8 @@ describe('SEO Plugin', () => {

// Navigate back to the page
await page.goto(url.edit(id))
await wait(600)

await secondTab.click()
await wait(600)
await switchTab(page, seoTabSelector)

await expect(autoGenButton).toContainText('Auto-génerar')
})
Expand All @@ -191,10 +185,8 @@ describe('SEO Plugin', () => {
describe('A11y', () => {
test.fixme('SEO fields should have no accessibility violations', async ({}, testInfo) => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, '.tabs-field__tab-button:has-text("SEO")')

const scanResults = await runAxeScan({
exclude: ['.field-description'], // known issue - reported elsewhere @todo: remove this once fixed - see report https://github.com/payloadcms/payload/discussions/14489
Expand All @@ -208,10 +200,8 @@ describe('SEO Plugin', () => {

test.fixme('SEO fields inputs have focus indicators', async ({}, testInfo) => {
await page.goto(url.edit(id))
const contentTabsClass = '.tabs-field__tabs .tabs-field__tab-button'

const secondTab = page.locator(contentTabsClass).nth(1)
await secondTab.click()
await switchTab(page, '.tabs-field__tab-button:has-text("SEO")')

const scanResults = await checkFocusIndicators({
page,
Expand Down
2 changes: 1 addition & 1 deletion test/runE2E.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const {
part,
shard,
workers,
} = minimist(process.argv.slice(2))
} = minimist(process.argv.slice(2), { alias: { g: 'grep' } })
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LLMs sometimes use -g instead of --grep, which did not work previously. Not worth a separate PR

const suiteName = args[0]

// Run all
Expand Down
Loading
Loading