Skip to content

Commit d576c90

Browse files
test: add E2E tests for image upload in editor (#223) (#230)
Co-authored-by: Ona <no-reply@ona.com>
1 parent 3efeff1 commit d576c90

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

e2e/editor-image-upload.spec.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import path from "node:path";
2+
import { test, expect } from "./fixtures/auth";
3+
import { navigateToEditorPage } from "./fixtures/editor-helpers";
4+
5+
const TEST_IMAGE_PATH = path.join(__dirname, "fixtures", "test-image.png");
6+
7+
/**
8+
* Wait for a successful Supabase PATCH to /rest/v1/pages (content auto-save).
9+
*/
10+
function waitForContentSave(page: import("@playwright/test").Page) {
11+
return page.waitForResponse(
12+
(resp) =>
13+
resp.url().includes("/rest/v1/pages") &&
14+
resp.request().method() === "PATCH" &&
15+
resp.status() >= 200 &&
16+
resp.status() < 300,
17+
{ timeout: 15_000 }
18+
);
19+
}
20+
21+
test.describe("Editor image upload", () => {
22+
test.beforeEach(async ({ authenticatedPage: page }) => {
23+
await navigateToEditorPage(page);
24+
});
25+
26+
test("insert an image via /image slash command and verify it renders", async ({
27+
authenticatedPage: page,
28+
}) => {
29+
const editor = page.locator('[contenteditable="true"]');
30+
await expect(editor).toBeVisible({ timeout: 10_000 });
31+
32+
// Open slash menu and filter to Image
33+
await editor.click();
34+
await page.keyboard.press("End");
35+
await page.keyboard.press("Enter");
36+
await page.keyboard.type("/image");
37+
38+
const imageOption = page
39+
.locator('[role="option"]')
40+
.filter({ hasText: "Image" });
41+
await expect(imageOption).toBeVisible({ timeout: 3_000 });
42+
43+
// Listen for the file chooser BEFORE clicking the option, since
44+
// openImagePicker creates a hidden <input type="file"> and clicks it.
45+
const fileChooserPromise = page.waitForEvent("filechooser");
46+
await imageOption.click();
47+
48+
const fileChooser = await fileChooserPromise;
49+
await fileChooser.setFiles(TEST_IMAGE_PATH);
50+
51+
// The image node renders an <img> inside the editor
52+
const uploadedImage = editor.locator("img");
53+
await expect(uploadedImage).toBeVisible({ timeout: 15_000 });
54+
55+
// Verify the src points to Supabase storage (page-images bucket)
56+
const src = await uploadedImage.getAttribute("src");
57+
expect(src).toBeTruthy();
58+
expect(src).toContain("page-images");
59+
});
60+
61+
test("uploaded image persists after page reload", async ({
62+
authenticatedPage: page,
63+
}) => {
64+
const editor = page.locator('[contenteditable="true"]');
65+
await expect(editor).toBeVisible({ timeout: 10_000 });
66+
67+
// Insert image via slash command
68+
await editor.click();
69+
await page.keyboard.press("End");
70+
await page.keyboard.press("Enter");
71+
await page.keyboard.type("/image");
72+
73+
const imageOption = page
74+
.locator('[role="option"]')
75+
.filter({ hasText: "Image" });
76+
await expect(imageOption).toBeVisible({ timeout: 3_000 });
77+
78+
const fileChooserPromise = page.waitForEvent("filechooser");
79+
await imageOption.click();
80+
81+
const fileChooser = await fileChooserPromise;
82+
83+
// Register the save listener BEFORE providing the file. The upload →
84+
// INSERT_IMAGE_COMMAND → onChange → debounced PATCH chain starts once
85+
// the file is set, and the PATCH may complete before we'd register
86+
// the listener otherwise.
87+
const saveResponse = waitForContentSave(page);
88+
89+
await fileChooser.setFiles(TEST_IMAGE_PATH);
90+
91+
// Wait for the image to render
92+
const uploadedImage = editor.locator("img");
93+
await expect(uploadedImage).toBeVisible({ timeout: 15_000 });
94+
95+
// Capture the image src to verify after reload
96+
const imageSrc = await uploadedImage.getAttribute("src");
97+
expect(imageSrc).toBeTruthy();
98+
99+
// Wait for auto-save to persist the editor state (Lexical JSON with image node)
100+
await saveResponse;
101+
102+
// Reload the page
103+
await page.reload({ waitUntil: "domcontentloaded" });
104+
105+
// Wait for the editor to re-render with persisted content
106+
const reloadedEditor = page.locator('[contenteditable="true"]');
107+
await expect(reloadedEditor).toBeVisible({ timeout: 10_000 });
108+
109+
// The image should still be visible after reload
110+
const persistedImage = reloadedEditor.locator("img");
111+
await expect(persistedImage).toBeVisible({ timeout: 10_000 });
112+
113+
// Verify it's the same image URL
114+
const persistedSrc = await persistedImage.getAttribute("src");
115+
expect(persistedSrc).toBe(imageSrc);
116+
});
117+
});

e2e/fixtures/test-image.png

69 Bytes
Loading

0 commit comments

Comments
 (0)