Skip to content

Commit 8228a7d

Browse files
authored
test: bulk upload button clicks timeout in CI (#15703)
### What Fixed two flaky bulk upload e2e tests that were timing out in CI when clicking the "Bulk Upload" button on list views. <img width="1328" height="573" alt="Screenshot 2026-02-20 at 11 33 57 AM" src="https://github.com/user-attachments/assets/2c657555-ceb6-43fe-aac8-e7036ff12a0a" /> ### Why On list pages, the bulk upload button appears enabled but React hasn't attached event handlers yet during hydration. In slow CI environments, clicks fail silently and the drawer never opens, causing timeouts. ### How Added retry logic using `expect().toPass()` that clicks the button repeatedly until the dropzone appears
1 parent 78e8e14 commit 8228a7d

1 file changed

Lines changed: 45 additions & 10 deletions

File tree

test/uploads/e2e.spec.ts

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { fileURLToPath } from 'url'
99
import type { PayloadTestSDK } from '../__helpers/shared/sdk/index.js'
1010
import type { Config } from './payload-types.js'
1111

12+
import { openListColumns, toggleColumn } from '../__helpers/e2e/columns/index.js'
13+
import { openListFilters } from '../__helpers/e2e/filters/index.js'
1214
import {
1315
closeAllToasts,
1416
ensureCompilationIsDone,
@@ -17,13 +19,11 @@ import {
1719
saveDocAndAssert,
1820
waitForFormReady,
1921
} from '../__helpers/e2e/helpers.js'
22+
import { openDocDrawer } from '../__helpers/e2e/toggleDocDrawer.js'
2023
import { AdminUrlUtil } from '../__helpers/shared/adminUrlUtil.js'
2124
import { assertToastErrors } from '../__helpers/shared/assertToastErrors.js'
22-
import { openListColumns, toggleColumn } from '../__helpers/e2e/columns/index.js'
23-
import { openListFilters } from '../__helpers/e2e/filters/index.js'
24-
import { openDocDrawer } from '../__helpers/e2e/toggleDocDrawer.js'
25-
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
2625
import { reInitializeDB } from '../__helpers/shared/clearAndSeed/reInitializeDB.js'
26+
import { initPayloadE2ENoConfig } from '../__helpers/shared/initPayloadE2ENoConfig.js'
2727
import { RESTClient } from '../__helpers/shared/rest.js'
2828
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
2929
import {
@@ -638,7 +638,7 @@ describe('Uploads', () => {
638638
await wait(1000)
639639

640640
await page.locator('#action-save').click()
641-
await expect(page.locator('.payload-toast-container')).toContainText
641+
await expect(page.locator('.payload-toast-container')).toContainText('successfully')
642642
await closeAllToasts(page)
643643

644644
await wait(1000)
@@ -1277,16 +1277,36 @@ describe('Uploads', () => {
12771277

12781278
test('should preserve state when adding additional files to an existing bulk upload', async () => {
12791279
await page.goto(uploadsTwo.list)
1280-
await page.locator('.list-header__title-actions button', { hasText: 'Bulk Upload' }).click()
1280+
1281+
// Wait for page header to be visible (indicates page is loaded and hydrated)
1282+
await expect(page.locator('.list-header__title')).toBeVisible()
1283+
1284+
const bulkUploadButton = page.locator('.list-header__title-actions button', {
1285+
hasText: 'Bulk Upload',
1286+
})
1287+
await expect(bulkUploadButton).toBeEnabled()
1288+
1289+
// Click and retry until dropzone appears (handles hydration timing issues)
1290+
const dropzoneInput = page.locator('.dropzone input[type="file"]')
1291+
await expect(async () => {
1292+
await bulkUploadButton.click()
1293+
await expect(dropzoneInput).toBeAttached({ timeout: 1500 })
1294+
}).toPass({ timeout: 5000, intervals: [500] })
12811295

12821296
await page.setInputFiles('.dropzone input[type="file"]', path.resolve(dirname, './image.png'))
12831297

12841298
await page.locator('#field-prefix').fill('should-preserve')
12851299

12861300
// add another file
1287-
await page
1288-
.locator('.file-selections__header__actions button', { hasText: 'Add File' })
1289-
.click()
1301+
const addFileButton = page.locator('.file-selections__header__actions button', {
1302+
hasText: 'Add File',
1303+
})
1304+
await expect(addFileButton).toBeEnabled()
1305+
await addFileButton.click()
1306+
1307+
// Wait for new dropzone to be ready
1308+
await expect(dropzoneInput).toBeAttached()
1309+
12901310
await page.setInputFiles('.dropzone input[type="file"]', path.resolve(dirname, './small.png'))
12911311

12921312
const originalFileRow = page
@@ -1305,7 +1325,22 @@ describe('Uploads', () => {
13051325

13061326
test('should not redirect to created relationship document inside the bulk upload drawer', async () => {
13071327
await page.goto(bulkUploadsURL.list)
1308-
await page.locator('.list-header__title-actions button', { hasText: 'Bulk Upload' }).click()
1328+
1329+
// Wait for page header to be visible (indicates page is loaded and hydrated)
1330+
await expect(page.locator('.list-header__title')).toBeVisible()
1331+
1332+
const bulkUploadButton = page.locator('.list-header__title-actions button', {
1333+
hasText: 'Bulk Upload',
1334+
})
1335+
await expect(bulkUploadButton).toBeEnabled()
1336+
1337+
// Click and retry until dropzone appears (handles hydration timing issues)
1338+
const dropzoneInput = page.locator('.dropzone input[type="file"]')
1339+
await expect(async () => {
1340+
await bulkUploadButton.click()
1341+
await expect(dropzoneInput).toBeAttached({ timeout: 1500 })
1342+
}).toPass({ timeout: 5000, intervals: [500] })
1343+
13091344
await page.setInputFiles('.dropzone input[type="file"]', path.resolve(dirname, './image.png'))
13101345

13111346
await page.locator('#field-title').fill('Upload title 1')

0 commit comments

Comments
 (0)