From 545d870650b88ab2d6443ec4aae4544c96a3aa85 Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Wed, 4 Jun 2025 13:34:37 -0700 Subject: [PATCH] chore: fix various e2e test setup issues (#12670) I noticed a few issues when running e2e tests that will be resolved by this PR: - Most important: for some test suites (fields, fields-relationship, versions, queues, lexical), the database was cleared and seeded **twice** in between each test run. This is because the onInit function was running the clear and seed script, when it should only have been running the seed script. Clearing the database / the snapshot workflow is being done by the reInit endpoint, which then calls onInit to seed the actual data. - The slowest part of `clearAndSeedEverything` is recreating indexes on mongodb. This PR slightly improves performance here by: - Skipping this process for the built-in `['payload-migrations', 'payload-preferences', 'payload-locked-documents']` collections - Previously we were calling both `createIndexes` and `ensureIndexes`. This was unnecessary - `ensureIndexes` is a deprecated alias of `createIndexes`. This PR changes it to only call `createIndexes` - Makes the reinit endpoint accept GET requests instead of POST requests - this makes it easier to debug right in the browser - Some typescript fixes - Adds a `dev:memorydb` script to the package.json. For some reason, `dev` is super unreliable on mongodb locally when running e2e tests - it frequently fails during index creation. Using the memorydb fixes this issue, with the bonus of more closely resembling the CI environment - Previously, you were unable to run test suites using turbopack + postgres. This fixes it, by explicitly installing `pg` as devDependency in our monorepo - Fixes jest open handles warning --- package.json | 4 ++- packages/db-mongodb/package.json | 4 +-- packages/db-mongodb/src/connect.ts | 5 ++- packages/db-mongodb/src/destroy.ts | 6 +--- pnpm-lock.yaml | 33 ++++++++++------- templates/plugin/package.json | 2 +- test/admin/seed.ts | 13 +------ test/buildConfigWithDefaults.ts | 2 +- test/fields-relationship/config.ts | 4 +-- test/fields-relationship/seed.ts | 18 ---------- test/fields/baseConfig.ts | 4 +-- test/generateDatabaseAdapter.ts | 2 ++ test/helpers/isErrorWithCode.ts | 9 +++++ test/helpers/{reInit.ts => reInitEndpoint.ts} | 27 ++++++++++---- test/helpers/reInitializeDB.ts | 18 +++++++--- test/helpers/reset.ts | 7 +++- test/helpers/seed.ts | 36 +++++++++++-------- test/helpers/snapshot.ts | 8 ++++- test/helpers/startMemoryDB.ts | 6 ++-- test/helpers/stopMemoryDB.ts | 1 + test/joins/seed.ts | 11 ------ test/lexical/baseConfig.ts | 5 +-- .../collections/Lexical/e2e/main/e2e.spec.ts | 10 ++++++ test/locked-documents/seed.ts | 12 +------ test/package.json | 1 + test/query-presets/seed.ts | 12 +------ test/queues/config.ts | 4 +-- test/versions/e2e.spec.ts | 1 - 28 files changed, 139 insertions(+), 126 deletions(-) create mode 100644 test/helpers/isErrorWithCode.ts rename test/helpers/{reInit.ts => reInitEndpoint.ts} (62%) diff --git a/package.json b/package.json index 12bd213ba..2ebb96df6 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts", "dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts", "dev:generate-types": "pnpm runts ./test/generateTypes.ts", + "dev:memorydb": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --start-memory-db", "dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts", "dev:prod": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod", "dev:prod:memorydb": "cross-env NODE_OPTIONS=--no-deprecation tsx ./test/dev.ts --prod --start-memory-db", @@ -155,10 +156,11 @@ "jest": "29.7.0", "lint-staged": "15.2.7", "minimist": "1.2.8", - "mongodb-memory-server": "^10", + "mongodb-memory-server": "10.1.4", "next": "15.3.2", "open": "^10.1.0", "p-limit": "^5.0.0", + "pg": "8.11.3", "playwright": "1.50.0", "playwright-core": "1.50.0", "prettier": "3.5.3", diff --git a/packages/db-mongodb/package.json b/packages/db-mongodb/package.json index fb485dfcc..ee85c3f37 100644 --- a/packages/db-mongodb/package.json +++ b/packages/db-mongodb/package.json @@ -17,8 +17,8 @@ "url": "https://payloadcms.com" } ], - "type": "module", "sideEffects": false, + "type": "module", "exports": { ".": { "import": "./src/index.ts", @@ -58,7 +58,7 @@ "@types/prompts": "^2.4.5", "@types/uuid": "10.0.0", "mongodb": "6.12.0", - "mongodb-memory-server": "^10", + "mongodb-memory-server": "10.1.4", "payload": "workspace:*" }, "peerDependencies": { diff --git a/packages/db-mongodb/src/connect.ts b/packages/db-mongodb/src/connect.ts index 040c0c1ab..6210bde28 100644 --- a/packages/db-mongodb/src/connect.ts +++ b/packages/db-mongodb/src/connect.ts @@ -40,6 +40,9 @@ export const connect: Connect = async function connect( // If we are running a replica set with MongoDB Memory Server, // wait until the replica set elects a primary before proceeding if (this.mongoMemoryServer) { + this.payload.logger.info( + 'Waiting for MongoDB Memory Server replica set to elect a primary...', + ) await new Promise((resolve) => setTimeout(resolve, 2000)) } @@ -50,7 +53,7 @@ export const connect: Connect = async function connect( this.beginTransaction = defaultBeginTransaction() } - if (!this.mongoMemoryServer && !hotReload) { + if (!hotReload) { if (process.env.PAYLOAD_DROP_DATABASE === 'true') { this.payload.logger.info('---- DROPPING DATABASE ----') await mongoose.connection.dropDatabase() diff --git a/packages/db-mongodb/src/destroy.ts b/packages/db-mongodb/src/destroy.ts index 36aaafc35..f363d4549 100644 --- a/packages/db-mongodb/src/destroy.ts +++ b/packages/db-mongodb/src/destroy.ts @@ -5,11 +5,7 @@ import mongoose from 'mongoose' import type { MongooseAdapter } from './index.js' export const destroy: Destroy = async function destroy(this: MongooseAdapter) { - if (this.mongoMemoryServer) { - await this.mongoMemoryServer.stop() - } else { - await mongoose.disconnect() - } + await mongoose.disconnect() Object.keys(mongoose.models).map((model) => mongoose.deleteModel(model)) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76103c5cc..15103b9d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ overrides: cross-env: 7.0.3 dotenv: 16.4.7 graphql: ^16.8.1 - mongodb-memory-server: ^10 + mongodb-memory-server: 10.1.4 react: 19.1.0 react-dom: 19.1.0 typescript: 5.7.3 @@ -124,8 +124,8 @@ importers: specifier: 1.2.8 version: 1.2.8 mongodb-memory-server: - specifier: ^10 - version: 10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) + specifier: 10.1.4 + version: 10.1.4(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) next: specifier: 15.3.2 version: 15.3.2(@opentelemetry/api@1.9.0)(@playwright/test@1.50.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.77.4) @@ -135,6 +135,9 @@ importers: p-limit: specifier: ^5.0.0 version: 5.0.0 + pg: + specifier: 8.11.3 + version: 8.11.3 playwright: specifier: 1.50.0 version: 1.50.0 @@ -292,8 +295,8 @@ importers: specifier: 6.12.0 version: 6.12.0(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) mongodb-memory-server: - specifier: ^10 - version: 10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) + specifier: 10.1.4 + version: 10.1.4(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) payload: specifier: workspace:* version: link:../payload @@ -1852,6 +1855,9 @@ importers: payload: specifier: workspace:* version: link:../packages/payload + pg: + specifier: 8.11.3 + version: 8.11.3 qs-esm: specifier: 7.0.2 version: 7.0.2 @@ -8503,12 +8509,12 @@ packages: mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} - mongodb-memory-server-core@10.1.3: - resolution: {integrity: sha512-ayBQHeV74wRHhgcAKpxHYI4th9Ufidy/m3XhJnLFRufKsOyDsyHYU3Zxv5Fm4hxsWE6wVd0GAVcQ7t7XNkivOg==} + mongodb-memory-server-core@10.1.4: + resolution: {integrity: sha512-o8fgY7ZalEd8pGps43fFPr/hkQu1L8i6HFEGbsTfA2zDOW0TopgpswaBCqDr0qD7ptibyPfB5DmC+UlIxbThzA==} engines: {node: '>=16.20.1'} - mongodb-memory-server@10.1.3: - resolution: {integrity: sha512-QCUjsIIXSYv/EgkpDAjfhlqRKo6N+qR6DD43q4lyrCVn24xQmvlArdWHW/Um5RS4LkC9YWC3XveSncJqht2Hbg==} + mongodb-memory-server@10.1.4: + resolution: {integrity: sha512-+oKQ/kc3CX+816oPFRtaF0CN4vNcGKNjpOQe4bHo/21A3pMD+lC7Xz1EX5HP7siCX4iCpVchDMmCOFXVQSGkUg==} engines: {node: '>=16.20.1'} mongodb@6.12.0: @@ -8641,6 +8647,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch-native@1.6.4: resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} @@ -18796,7 +18803,7 @@ snapshots: '@types/whatwg-url': 11.0.5 whatwg-url: 13.0.0 - mongodb-memory-server-core@10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3): + mongodb-memory-server-core@10.1.4(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3): dependencies: async-mutex: 0.5.0 camelcase: 6.3.0 @@ -18806,7 +18813,7 @@ snapshots: https-proxy-agent: 7.0.5 mongodb: 6.12.0(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) new-find-package-json: 2.0.0 - semver: 7.6.3 + semver: 7.7.1 tar-stream: 3.1.7 tslib: 2.8.1 yauzl: 3.2.0 @@ -18820,9 +18827,9 @@ snapshots: - socks - supports-color - mongodb-memory-server@10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3): + mongodb-memory-server@10.1.4(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3): dependencies: - mongodb-memory-server-core: 10.1.3(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) + mongodb-memory-server-core: 10.1.4(@aws-sdk/credential-providers@3.687.0(@aws-sdk/client-sso-oidc@3.687.0(@aws-sdk/client-sts@3.687.0)))(socks@2.8.3) tslib: 2.8.1 transitivePeerDependencies: - '@aws-sdk/credential-providers' diff --git a/templates/plugin/package.json b/templates/plugin/package.json index 7598d2d50..79c9a23ac 100644 --- a/templates/plugin/package.json +++ b/templates/plugin/package.json @@ -63,7 +63,7 @@ "eslint": "^9.23.0", "eslint-config-next": "15.3.0", "graphql": "^16.8.1", - "mongodb-memory-server": "^10.1.2", + "mongodb-memory-server": "10.1.4", "next": "15.3.0", "open": "^10.1.0", "payload": "3.37.0", diff --git a/test/admin/seed.ts b/test/admin/seed.ts index 2bbe5f17c..9189315df 100644 --- a/test/admin/seed.ts +++ b/test/admin/seed.ts @@ -2,9 +2,7 @@ import type { Payload } from 'payload' import { devUser } from '../credentials.js' import { executePromises } from '../helpers/executePromises.js' -import { seedDB } from '../helpers/seed.js' import { - collectionSlugs, customViews1CollectionSlug, customViews2CollectionSlug, geoCollectionSlug, @@ -14,7 +12,7 @@ import { with300DocumentsSlug, } from './slugs.js' -export const seed = async (_payload) => { +export const seed = async (_payload: Payload) => { await executePromises( [ () => @@ -139,12 +137,3 @@ export const seed = async (_payload) => { await Promise.all([...manyDocumentsPromises]) } - -export async function clearAndSeedEverything(_payload: Payload) { - return await seedDB({ - _payload, - collectionSlugs, - seedFunction: seed, - snapshotKey: 'adminTests', - }) -} diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index 0de7b5ef9..26e24aa6d 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -32,7 +32,7 @@ import { es } from 'payload/i18n/es' import sharp from 'sharp' import { databaseAdapter } from './databaseAdapter.js' -import { reInitEndpoint } from './helpers/reInit.js' +import { reInitEndpoint } from './helpers/reInitEndpoint.js' import { localAPIEndpoint } from './helpers/sdk/endpoint.js' import { testEmailAdapter } from './testEmailAdapter.js' diff --git a/test/fields-relationship/config.ts b/test/fields-relationship/config.ts index 2ce7562f6..ee9df96e3 100644 --- a/test/fields-relationship/config.ts +++ b/test/fields-relationship/config.ts @@ -18,7 +18,7 @@ import { Restricted } from './collections/Restricted/index.js' import { RelationshipUpdatedExternally } from './collections/UpdatedExternally/index.js' import { Versions } from './collections/Versions/index.js' import { Video } from './collections/Video/index.js' -import { clearAndSeedEverything } from './seed.js' +import { seed } from './seed.js' export default buildConfigWithDefaults({ admin: { @@ -49,7 +49,7 @@ export default buildConfigWithDefaults({ }, onInit: async (payload) => { if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') { - await clearAndSeedEverything(payload) + await seed(payload) } }, typescript: { diff --git a/test/fields-relationship/seed.ts b/test/fields-relationship/seed.ts index 3e5577b5b..10530e953 100644 --- a/test/fields-relationship/seed.ts +++ b/test/fields-relationship/seed.ts @@ -1,14 +1,9 @@ import type { Payload } from 'payload' -import path from 'path' -import { fileURLToPath } from 'url' - import { devUser } from '../credentials.js' -import { seedDB } from '../helpers/seed.js' import { collection1Slug, collection2Slug, - collectionSlugs, podcastCollectionSlug, relationOneSlug, relationRestrictedSlug, @@ -18,9 +13,6 @@ import { videoCollectionSlug, } from './slugs.js' -const filename = fileURLToPath(import.meta.url) -const dirname = path.dirname(filename) - export const seed = async (_payload: Payload) => { await _payload.create({ collection: 'users', @@ -179,13 +171,3 @@ export const seed = async (_payload: Payload) => { }) } } - -export async function clearAndSeedEverything(_payload: Payload) { - return await seedDB({ - _payload, - collectionSlugs, - seedFunction: seed, - snapshotKey: 'fieldsTest', - uploadsDir: path.resolve(dirname, './collections/Upload/uploads'), - }) -} diff --git a/test/fields/baseConfig.ts b/test/fields/baseConfig.ts index 0967d726b..4d57e45e2 100644 --- a/test/fields/baseConfig.ts +++ b/test/fields/baseConfig.ts @@ -38,7 +38,7 @@ import UploadsMultiPoly from './collections/UploadMultiPoly/index.js' import UploadsPoly from './collections/UploadPoly/index.js' import UploadRestricted from './collections/UploadRestricted/index.js' import Uploads3 from './collections/Uploads3/index.js' -import { clearAndSeedEverything } from './seed.js' +import { seed } from './seed.js' export const collectionSlugs: CollectionConfig[] = [ { @@ -157,7 +157,7 @@ export const baseConfig: Partial = { }, onInit: async (payload) => { if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') { - await clearAndSeedEverything(payload) + await seed(payload) } }, typescript: { diff --git a/test/generateDatabaseAdapter.ts b/test/generateDatabaseAdapter.ts index 86dfab58d..a71641bf9 100644 --- a/test/generateDatabaseAdapter.ts +++ b/test/generateDatabaseAdapter.ts @@ -11,6 +11,8 @@ export const allDatabaseAdapters = { export const databaseAdapter = mongooseAdapter({ ensureIndexes: true, + // required for connect to detect that we are using a memory server + mongoMemoryServer: global._mongoMemoryServer, url: process.env.MONGODB_MEMORY_SERVER_URI || process.env.DATABASE_URI || diff --git a/test/helpers/isErrorWithCode.ts b/test/helpers/isErrorWithCode.ts new file mode 100644 index 000000000..24cfc588d --- /dev/null +++ b/test/helpers/isErrorWithCode.ts @@ -0,0 +1,9 @@ +export function isErrorWithCode(err: unknown, code?: string): err is NodeJS.ErrnoException { + return ( + typeof err === 'object' && + err !== null && + 'code' in err && + typeof (err as any).code === 'string' && + (!code || (err as NodeJS.ErrnoException).code === code) + ) +} diff --git a/test/helpers/reInit.ts b/test/helpers/reInitEndpoint.ts similarity index 62% rename from test/helpers/reInit.ts rename to test/helpers/reInitEndpoint.ts index 0363c702b..6cac26caa 100644 --- a/test/helpers/reInit.ts +++ b/test/helpers/reInitEndpoint.ts @@ -1,25 +1,38 @@ import type { Endpoint, PayloadHandler } from 'payload' import { status as httpStatus } from 'http-status' -import { addDataAndFileToRequest } from 'payload' +import * as qs from 'qs-esm' import { path } from './reInitializeDB.js' import { seedDB } from './seed.js' const handler: PayloadHandler = async (req) => { process.env.SEED_IN_CONFIG_ONINIT = 'true' - await addDataAndFileToRequest(req) - const { data, payload } = req + const { payload } = req + + if (!req.url) { + throw new Error('Request URL is required') + } + + const query: { + deleteOnly?: boolean + snapshotKey?: string + uploadsDir?: string | string[] + } = qs.parse(req.url.split('?')[1] ?? '', { + depth: 10, + ignoreQueryPrefix: true, + }) try { + console.log('Calling seedDB') await seedDB({ _payload: payload, collectionSlugs: payload.config.collections.map(({ slug }) => slug), seedFunction: payload.config.onInit, - snapshotKey: String(data.snapshotKey), + snapshotKey: String(query.snapshotKey), // uploadsDir can be string or stringlist - uploadsDir: data.uploadsDir as string | string[], - deleteOnly: data.deleteOnly, + uploadsDir: query.uploadsDir as string | string[], + deleteOnly: query.deleteOnly, }) return Response.json( @@ -40,6 +53,6 @@ const handler: PayloadHandler = async (req) => { export const reInitEndpoint: Endpoint = { path, - method: 'post', + method: 'get', handler, } diff --git a/test/helpers/reInitializeDB.ts b/test/helpers/reInitializeDB.ts index a0bf18169..110b71b80 100644 --- a/test/helpers/reInitializeDB.ts +++ b/test/helpers/reInitializeDB.ts @@ -1,3 +1,5 @@ +import * as qs from 'qs-esm' + export const path = '/re-initialize' export const reInitializeDB = async ({ @@ -19,13 +21,19 @@ export const reInitializeDB = async ({ try { console.log(`Attempting to reinitialize DB (attempt ${attempt}/${maxAttempts})...`) - const response = await fetch(`${serverURL}/api${path}`, { - method: 'post', - body: JSON.stringify({ + const queryParams = qs.stringify( + { snapshotKey, uploadsDir, deleteOnly, - }), + }, + { + addQueryPrefix: true, + }, + ) + + const response = await fetch(`${serverURL}/api${path}${queryParams}`, { + method: 'get', headers: { 'Content-Type': 'application/json', }, @@ -39,7 +47,7 @@ export const reInitializeDB = async ({ console.log(`Successfully reinitialized DB (took ${timeTaken}ms)`) return } catch (error) { - console.error(`Failed to reinitialize DB: ${error.message}`) + console.error(`Failed to reinitialize DB`, error) if (attempt === maxAttempts) { console.error('Max retry attempts reached. Giving up.') diff --git a/test/helpers/reset.ts b/test/helpers/reset.ts index e0a85ff89..ab56173e0 100644 --- a/test/helpers/reset.ts +++ b/test/helpers/reset.ts @@ -5,7 +5,12 @@ import { isMongoose } from './isMongoose.js' export async function resetDB(_payload: Payload, collectionSlugs: string[]) { if (isMongoose(_payload) && 'collections' in _payload.db && collectionSlugs.length > 0) { - await _payload.db.collections[collectionSlugs[0]].db.dropDatabase() + const firstCollectionSlug = collectionSlugs?.[0] + + if (!firstCollectionSlug?.length) { + throw new Error('No collection slugs provided to reset the database.') + } + await _payload.db.collections[firstCollectionSlug]?.db.dropDatabase() } else if ('drizzle' in _payload.db) { const db = _payload.db as unknown as DrizzleAdapter diff --git a/test/helpers/seed.ts b/test/helpers/seed.ts index d01584c63..7e0330699 100644 --- a/test/helpers/seed.ts +++ b/test/helpers/seed.ts @@ -3,6 +3,7 @@ import * as os from 'node:os' import path from 'path' import { type Payload } from 'payload' +import { isErrorWithCode } from './isErrorWithCode.js' import { isMongoose } from './isMongoose.js' import { resetDB } from './reset.js' import { createSnapshot, dbSnapshot, restoreFromSnapshot, uploadsDirCache } from './snapshot.js' @@ -47,15 +48,18 @@ export async function seedDB({ const uploadsDirs = Array.isArray(uploadsDir) ? uploadsDir : [uploadsDir] for (const dir of uploadsDirs) { try { - // Attempt to clear the uploads directory if it exists await fs.promises.access(dir) const files = await fs.promises.readdir(dir) for (const file of files) { - await fs.promises.rm(path.join(dir, file)) + const filePath = path.join(dir, file) + await fs.promises.rm(filePath, { recursive: true, force: true }) } } catch (error) { - if (error.code !== 'ENOENT') { - // If the error is not because the directory doesn't exist + if (isErrorWithCode(error, 'ENOENT')) { + // Directory does not exist - that's okay, skip it + continue + } else { + // Some other error occurred - rethrow it console.error('Error in operation (deleting uploads dir):', dir, error) throw error } @@ -124,16 +128,20 @@ export async function seedDB({ try { if (isMongoose(_payload)) { await Promise.all([ - ...collectionSlugs.map(async (collectionSlug) => { - await _payload.db.collections[collectionSlug].createIndexes() - }), + ...collectionSlugs + .filter( + (collectionSlug) => + ['payload-migrations', 'payload-preferences', 'payload-locked-documents'].indexOf( + collectionSlug, + ) === -1, + ) + .map(async (collectionSlug) => { + await _payload.db.collections[collectionSlug]?.createIndexes({ + // Blocks writes (doesn't matter here) but faster + background: false, + }) + }), ]) - - await Promise.all( - _payload.config.collections.map(async (coll) => { - await _payload.db?.collections[coll.slug]?.ensureIndexes() - }), - ) } } catch (e) { console.error('Error in operation (re-creating indexes):', e) @@ -170,7 +178,7 @@ export async function seedDB({ let newObj: { cacheDir: string originalDir: string - } = null + } | null = null if (!uploadsDirCache[snapshotKey].find((cache) => cache.originalDir === dir)) { // Define new cache folder path to the OS temp directory (well a random folder inside it) newObj = { diff --git a/test/helpers/snapshot.ts b/test/helpers/snapshot.ts index 84273b817..0b77e19f8 100644 --- a/test/helpers/snapshot.ts +++ b/test/helpers/snapshot.ts @@ -116,7 +116,13 @@ export async function createSnapshot( collectionSlugs: string[], ) { if (isMongoose(_payload) && 'collections' in _payload.db) { - const mongooseCollections = _payload.db.collections[collectionSlugs[0]].db.collections + const firstCollectionSlug = collectionSlugs?.[0] + + if (!firstCollectionSlug?.length) { + throw new Error('No collection slugs provided to reset the database.') + } + + const mongooseCollections = _payload.db.collections[firstCollectionSlug]?.db.collections await createMongooseSnapshot(mongooseCollections, snapshotKey) } else { diff --git a/test/helpers/startMemoryDB.ts b/test/helpers/startMemoryDB.ts index 2bc7270f3..cd328a0d6 100644 --- a/test/helpers/startMemoryDB.ts +++ b/test/helpers/startMemoryDB.ts @@ -1,8 +1,7 @@ -import { MongoMemoryReplSet } from 'mongodb-memory-server' import dotenv from 'dotenv' +import { MongoMemoryReplSet } from 'mongodb-memory-server' dotenv.config() - // eslint-disable-next-line no-restricted-exports export default async () => { // @ts-expect-error @@ -23,8 +22,11 @@ export default async () => { }, }) + await db.waitUntilRunning() + global._mongoMemoryServer = db process.env.MONGODB_MEMORY_SERVER_URI = `${global._mongoMemoryServer.getUri()}&retryWrites=true` + console.log('Started memory db') } } diff --git a/test/helpers/stopMemoryDB.ts b/test/helpers/stopMemoryDB.ts index aaaf136f6..ec3f196ad 100644 --- a/test/helpers/stopMemoryDB.ts +++ b/test/helpers/stopMemoryDB.ts @@ -5,4 +5,5 @@ export default async () => { await global._mongoMemoryServer.stop() console.log('Stopped memorydb') } + process.exit(0) } diff --git a/test/joins/seed.ts b/test/joins/seed.ts index d0c617780..5ff1cf6ae 100644 --- a/test/joins/seed.ts +++ b/test/joins/seed.ts @@ -5,12 +5,10 @@ import { getFileByPath } from 'payload' import { fileURLToPath } from 'url' import { devUser } from '../credentials.js' -import { seedDB } from '../helpers/seed.js' import { categoriesJoinRestrictedSlug, categoriesSlug, collectionRestrictedSlug, - collectionSlugs, hiddenPostsSlug, postsSlug, uploadsSlug, @@ -215,12 +213,3 @@ export const seed = async (_payload: Payload) => { data: { title: 'post 5', description: 'This is post 5', folder: sub_folder_2 }, }) } - -export async function clearAndSeedEverything(_payload: Payload) { - return await seedDB({ - _payload, - collectionSlugs, - seedFunction: seed, - snapshotKey: 'joinsTest', - }) -} diff --git a/test/lexical/baseConfig.ts b/test/lexical/baseConfig.ts index 1d86a2b7f..efc7ce833 100644 --- a/test/lexical/baseConfig.ts +++ b/test/lexical/baseConfig.ts @@ -20,7 +20,7 @@ import RichTextFields from './collections/RichText/index.js' import TextFields from './collections/Text/index.js' import Uploads from './collections/Upload/index.js' import TabsWithRichText from './globals/TabsWithRichText.js' -import { clearAndSeedEverything } from './seed.js' +import { seed } from './seed.js' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -60,8 +60,9 @@ export const baseConfig: Partial = { }, }, onInit: async (payload) => { + // IMPORTANT: This should only seed, not clear the database. if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') { - await clearAndSeedEverything(payload) + await seed(payload) } }, localization: { diff --git a/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts b/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts index 910648bd1..3f1bf9385 100644 --- a/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts +++ b/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts @@ -958,6 +958,7 @@ describe('lexicalMain', () => { test('ensure internal links can be created', async () => { await navigateToLexicalFields() + await wait(200) const richTextField = page.locator('.rich-text-lexical').first() await richTextField.scrollIntoViewIfNeeded() await expect(richTextField).toBeVisible() @@ -970,11 +971,15 @@ describe('lexicalMain', () => { const paragraph = richTextField.locator('.LexicalEditorTheme__paragraph').first() await paragraph.scrollIntoViewIfNeeded() await expect(paragraph).toBeVisible() + await wait(200) + /** * Type some text */ await paragraph.click() + await wait(200) await page.keyboard.type('Link') + await wait(200) // Select "Link" by pressing shift + arrow left for (let i = 0; i < 4; i++) { @@ -986,6 +991,7 @@ describe('lexicalMain', () => { const linkButton = inlineToolbar.locator('.toolbar-popup__button-link') await expect(linkButton).toBeVisible() + await wait(200) await linkButton.click() /** @@ -1005,16 +1011,20 @@ describe('lexicalMain', () => { .locator('.radio-input__styled-radio') await radioInternalLink.click() + await wait(200) const internalLinkSelect = linkDrawer .locator('#field-doc .rs__control .value-container') .first() await internalLinkSelect.click() + await wait(200) await expect(linkDrawer.locator('.rs__option').nth(0)).toBeVisible() await expect(linkDrawer.locator('.rs__option').nth(0)).toContainText('Rich Text') // Link to itself - that way we can also test if depth 0 works await linkDrawer.locator('.rs__option').nth(0).click() + await expect(internalLinkSelect).toContainText('Rich Text') + await wait(200) await linkDrawer.locator('button').getByText('Save').first().click() await expect(linkDrawer).toBeHidden() diff --git a/test/locked-documents/seed.ts b/test/locked-documents/seed.ts index 0a7f578f5..a9cc7a41e 100644 --- a/test/locked-documents/seed.ts +++ b/test/locked-documents/seed.ts @@ -2,8 +2,7 @@ import type { Payload } from 'payload' import { devUser, regularUser } from '../credentials.js' import { executePromises } from '../helpers/executePromises.js' -import { seedDB } from '../helpers/seed.js' -import { collectionSlugs, pagesSlug, postsSlug } from './slugs.js' +import { pagesSlug, postsSlug } from './slugs.js' export const seed = async (_payload: Payload) => { await executePromises( @@ -46,12 +45,3 @@ export const seed = async (_payload: Payload) => { false, ) } - -export async function clearAndSeedEverything(_payload: Payload) { - return await seedDB({ - _payload, - collectionSlugs, - seedFunction: seed, - snapshotKey: 'adminTests', - }) -} diff --git a/test/package.json b/test/package.json index 34886b28d..7903e09bd 100644 --- a/test/package.json +++ b/test/package.json @@ -83,6 +83,7 @@ "next": "15.3.2", "nodemailer": "6.9.16", "payload": "workspace:*", + "pg": "8.11.3", "qs-esm": "7.0.2", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/test/query-presets/seed.ts b/test/query-presets/seed.ts index 1027fa22e..e4c3a5826 100644 --- a/test/query-presets/seed.ts +++ b/test/query-presets/seed.ts @@ -2,8 +2,7 @@ import type { Payload, QueryPreset } from 'payload' import { devUser as devCredentials, regularUser as regularCredentials } from '../credentials.js' import { executePromises } from '../helpers/executePromises.js' -import { seedDB } from '../helpers/seed.js' -import { collectionSlugs, pagesSlug, usersSlug } from './slugs.js' +import { pagesSlug, usersSlug } from './slugs.js' type SeededQueryPreset = { relatedCollection: 'pages' @@ -187,12 +186,3 @@ export const seed = async (_payload: Payload) => { false, ) } - -export async function clearAndSeedEverything(_payload: Payload) { - return await seedDB({ - _payload, - collectionSlugs, - seedFunction: seed, - snapshotKey: 'adminTests', - }) -} diff --git a/test/queues/config.ts b/test/queues/config.ts index 28d01a801..2217f29e1 100644 --- a/test/queues/config.ts +++ b/test/queues/config.ts @@ -7,7 +7,7 @@ import path from 'path' import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' import { devUser } from '../credentials.js' import { updatePostStep1, updatePostStep2 } from './runners/updatePost.js' -import { clearAndSeedEverything } from './seed.js' +import { seed } from './seed.js' import { externalWorkflow } from './workflows/externalWorkflow.js' import { inlineTaskTestWorkflow } from './workflows/inlineTaskTest.js' import { inlineTaskTestDelayedWorkflow } from './workflows/inlineTaskTestDelayed.js' @@ -394,7 +394,7 @@ export default buildConfigWithDefaults({ editor: lexicalEditor(), onInit: async (payload) => { if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') { - await clearAndSeedEverything(payload) + await seed(payload) } }, typescript: { diff --git a/test/versions/e2e.spec.ts b/test/versions/e2e.spec.ts index f25f7a68b..6eb151724 100644 --- a/test/versions/e2e.spec.ts +++ b/test/versions/e2e.spec.ts @@ -114,7 +114,6 @@ describe('Versions', () => { }) await ensureCompilationIsDone({ page, serverURL }) - //await clearAndSeedEverything(payload) }) describe('draft collections', () => {