From 2bebc6a309b6bdd7b5acaf7dba9b20de78a29acf Mon Sep 17 00:00:00 2001 From: Oliver Stenbom Date: Tue, 27 Oct 2026 13:45:11 -0700 Subject: [PATCH] test(e2e): fix tests that fail when the whole suite runs on one server CI splits the Node suite over 8 shards, each with its own server, so these only fail in a full run against a single server: - The sandbox hook-event test listed the test plugin's events with limit 100; a full run records more. The plugin's events-list route now accepts `ids` for a direct lookup. - The sandbox user-list check required exactly one user, but the passkey setup test adds a second. - In a crowded root folder the dragged media row sat at the viewport's bottom edge, where dnd-kit auto-scrolls and the drop target moves. pointerDrag now scrolls the source to the centre and re-measures the target during the drag. Co-Authored-By: Claude Opus 5.5 --- e2e/tests/media-library.spec.ts | 31 ++++++++++++------- .../sandbox-plugin-admin-surfaces.spec.ts | 8 ++--- e2e/tests/sandbox-plugin-boundaries.spec.ts | 4 ++- .../plugins/marketplace-test/src/plugin.ts | 20 +++++++++--- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/e2e/tests/media-library.spec.ts b/e2e/tests/media-library.spec.ts index efac36b7ef..5512d85b42 100644 --- a/e2e/tests/media-library.spec.ts +++ b/e2e/tests/media-library.spec.ts @@ -146,24 +146,33 @@ async function createFolder(page: Page, name: string) { await expect(dialog).not.toBeVisible(); } +async function boxCenter(locator: Locator) { + const box = await locator.boundingBox(); + if (!box) throw new Error("Drag source or target is not visible"); + return { x: box.x + box.width / 2, y: box.y + box.height / 2 }; +} + async function pointerDrag(page: Page, source: Locator, target: Locator) { - const sourceBox = await source.boundingBox(); - const targetBox = await target.boundingBox(); - if (!sourceBox || !targetBox) throw new Error("Drag source or target is not visible"); - const sourcePoint = { - x: sourceBox.x + sourceBox.width / 2, - y: sourceBox.y + sourceBox.height / 2, - }; - const targetPoint = { - x: targetBox.x + targetBox.width / 2, - y: targetBox.y + targetBox.height / 2, - }; + // Starting a drag near the viewport edge triggers the drag library's + // auto-scroll, which moves the target away from coordinates measured earlier. + await source.evaluate((element) => element.scrollIntoView({ block: "center" })); + const sourcePoint = await boxCenter(source); + let targetPoint = await boxCenter(target); await page.mouse.move(sourcePoint.x, sourcePoint.y); await page.mouse.down(); await page.mouse.move(sourcePoint.x + 12, sourcePoint.y, { steps: 2 }); await page.mouse.move(targetPoint.x, targetPoint.y, { steps: 8 }); const overlayCard = page.locator("[data-media-drag-overlay] > *"); await overlayCard.waitFor(); + await expect + .poll(async () => { + const current = await boxCenter(target); + const moved = Math.hypot(current.x - targetPoint.x, current.y - targetPoint.y) > 1; + targetPoint = current; + if (moved) await page.mouse.move(current.x, current.y, { steps: 2 }); + return moved; + }) + .toBe(false); const overlayBox = await overlayCard.boundingBox(); if (!overlayBox) throw new Error("Drag overlay is not visible"); expect(Math.abs(overlayBox.x + overlayBox.width / 2 - targetPoint.x)).toBeLessThanOrEqual(4); diff --git a/e2e/tests/sandbox-plugin-admin-surfaces.spec.ts b/e2e/tests/sandbox-plugin-admin-surfaces.spec.ts index 2030cbcba6..b1bca86a89 100644 --- a/e2e/tests/sandbox-plugin-admin-surfaces.spec.ts +++ b/e2e/tests/sandbox-plugin-admin-surfaces.spec.ts @@ -106,18 +106,18 @@ test.describe("Sandboxed plugin admin surfaces", () => { const response = await page.request.post("/_emdash/api/plugins/marketplace-test/events-list", { headers: { "X-EmDash-Request": "1" }, - data: {}, + data: { ids: [contentId, `action:publish:${contentId}`] }, }); const body = (await response.json()) as { data: { events: { items: Array<{ id: string; data: Record }> } }; }; expect(body.data.events.items).toEqual( expect.arrayContaining([ - expect.objectContaining({ id: contentId, data: { type: "saved", collection: "posts" } }), - expect.objectContaining({ + { id: contentId, data: { type: "saved", collection: "posts" } }, + { id: `action:publish:${contentId}`, data: { type: "content-action", action: "publish", contentId }, - }), + }, ]), ); }); diff --git a/e2e/tests/sandbox-plugin-boundaries.spec.ts b/e2e/tests/sandbox-plugin-boundaries.spec.ts index 5291b2c095..d289d1ffa4 100644 --- a/e2e/tests/sandbox-plugin-boundaries.spec.ts +++ b/e2e/tests/sandbox-plugin-boundaries.spec.ts @@ -59,7 +59,9 @@ test.describe("Sandboxed plugin production boundaries", () => { posts: { slug: "posts" }, }); await expect(invoke("users-exercise")).resolves.toMatchObject({ - page: { items: [expect.objectContaining({ email: "dev@emdash.local" })] }, + page: { + items: expect.arrayContaining([expect.objectContaining({ email: "dev@emdash.local" })]), + }, }); await expect(invoke("cron-exercise")).resolves.toMatchObject({ scheduled: [expect.objectContaining({ name: "diagnostic" })], diff --git a/packages/plugins/marketplace-test/src/plugin.ts b/packages/plugins/marketplace-test/src/plugin.ts index d5d60cca74..9023cb6b05 100644 --- a/packages/plugins/marketplace-test/src/plugin.ts +++ b/packages/plugins/marketplace-test/src/plugin.ts @@ -1207,10 +1207,22 @@ const plugin: SandboxedPlugin = { }, }, "events-list": { - handler: async (_route, ctx) => ({ - events: await ctx.storage.events.query({ limit: 100 }), - lifecycle: await ctx.storage.lifecycle.query({ limit: 100 }), - }), + handler: async (route, ctx) => { + const ids = + typeof route.input === "object" && route.input !== null && "ids" in route.input + ? route.input.ids + : undefined; + if (Array.isArray(ids) && ids.every((id) => typeof id === "string")) { + const found = await ctx.storage.events.getMany(ids); + return { + events: { items: Array.from(found, ([id, data]) => ({ id, data })) }, + }; + } + return { + events: await ctx.storage.events.query({ limit: 100 }), + lifecycle: await ctx.storage.lifecycle.query({ limit: 100 }), + }; + }, }, "private-user": { permission: "content:edit_any",