Skip to content

Commit 2d67f41

Browse files
fix(e2e): add test isolation for note-modal Slate editor tests (#132)
* fix(e2e): add resetProjectInfoNotes for test isolation in note-modal The note-modal tests were flaky on CI because the "save and persist" test writes content to the DB, and subsequent tests (slash command, heading autoformat) inherit that state. Ctrl+A+Backspace to clear Slate editors is inherently unreliable on slow CI runners. Fix: add resetProjectInfoNotes() helper that resets note fields to seed values before each test, ensuring a known starting state. * fix(e2e): check Slate placeholder visibility for empty editor assertion When the Plate/Slate editor is empty, it renders a placeholder element with data-slate-placeholder="true". textContent() captures this placeholder text, causing the emptiness check to fail. Use placeholder element visibility instead — a visible placeholder means the editor is truly empty.
1 parent 44aa00c commit 2d67f41

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

e2e/helpers/db-query.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,46 @@ export async function resetProjectInfoComments(): Promise<void> {
300300
}
301301
}
302302

303+
/**
304+
* Reset projectinfo notes to seed.sql initial values.
305+
* Call this in beforeEach for tests that interact with the Plate editor
306+
* to ensure a known starting state (avoids flaky Ctrl+A clear on CI).
307+
*
308+
* @example
309+
* test.beforeEach(async () => {
310+
* await resetProjectInfoNotes()
311+
* })
312+
*/
313+
export async function resetProjectInfoNotes(): Promise<void> {
314+
const supabase = createLocalSupabaseClient()
315+
316+
const seedNotes = [
317+
{ id: PROJECT_INFO_IDS.projinfo1, note: 'Important project notes here' },
318+
{ id: PROJECT_INFO_IDS.projinfo2, note: 'Another repo notes' },
319+
{ id: PROJECT_INFO_IDS.projinfo3, note: '' },
320+
{ id: PROJECT_INFO_IDS.projinfo4, note: 'CLI tool documentation' },
321+
]
322+
323+
for (const item of seedNotes) {
324+
const { data, error } = await supabase
325+
.from('projectinfo')
326+
.update({ note: item.note })
327+
.eq('id', item.id)
328+
.select('id, note')
329+
if (error) {
330+
throw new Error(
331+
`resetProjectInfoNotes: failed for id=${item.id}: ${error.message}`,
332+
)
333+
}
334+
if (!data || data.length === 0) {
335+
throw new Error(
336+
`resetProjectInfoNotes: UPDATE matched 0 rows for id=${item.id}. ` +
337+
`URL=${process.env.NEXT_PUBLIC_SUPABASE_URL || 'http://127.0.0.1:54321'}`,
338+
)
339+
}
340+
}
341+
}
342+
303343
/**
304344
* Reset all repo card positions to seed.sql initial values.
305345
* Call this in beforeEach for DnD card tests to ensure clean state.

e2e/logged-in/note-modal.spec.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
PROJECT_INFO_IDS,
1919
BOARD_IDS,
2020
CARD_IDS,
21+
resetProjectInfoNotes,
2122
} from '../helpers/db-query'
2223

2324
/** Platform-aware modifier key: Cmd on macOS, Ctrl on Linux/Windows (CI) */
@@ -29,6 +30,9 @@ test.describe('NoteModal (Authenticated)', () => {
2930
const BOARD_URL = `/board/${BOARD_IDS.testBoard}`
3031

3132
test.beforeEach(async ({ page }) => {
33+
// Reset note content to seed values so each test starts with known state.
34+
// This avoids flaky Ctrl+A+Backspace clears on slow CI runners.
35+
await resetProjectInfoNotes()
3236
await page.goto(BOARD_URL)
3337
// Use networkidle to wait for all data fetches to complete
3438
// This prevents flakiness caused by cards not being rendered yet
@@ -237,22 +241,16 @@ test.describe('NoteModal (Authenticated)', () => {
237241
const editorContent = dialog.locator('[data-slate-editor="true"]')
238242
await editorContent.click()
239243

240-
// Clear existing content first
241-
await page.keyboard.press(`${MOD}+a`)
242-
await page.keyboard.press('Backspace')
243-
244-
// Ensure editor is ready and empty before typing slash command
245-
// Type a character, wait for it to render, then clear and wait for editor to settle
246-
await page.keyboard.type('x')
247-
await expect(editorContent).toContainText('x')
248-
await page.keyboard.press(`${MOD}+a`)
249-
await page.keyboard.press('Backspace')
250-
// Poll until Slate editor finishes processing the clear operation
244+
// Clear existing content — retry inside polling loop for CI resilience
251245
await expect(async () => {
252-
const text = await editorContent.textContent()
253-
// Editor should be empty or contain only placeholder text
254-
expect(text?.replace(/\s/g, '')).toBe('')
255-
}).toPass({ timeout: 5000 })
246+
await page.keyboard.press(`${MOD}+a`)
247+
await page.keyboard.press('Backspace')
248+
// When editor is empty, Plate shows a placeholder with data-slate-placeholder
249+
const placeholder = editorContent.locator(
250+
'[data-slate-placeholder="true"]',
251+
)
252+
await expect(placeholder).toBeVisible()
253+
}).toPass({ timeout: 10000 })
256254

257255
// Generous delay to let Slate editor fully stabilize after clear
258256
// CI runners (GitHub Actions Linux) need more time for contenteditable
@@ -379,6 +377,7 @@ test.describe('NoteModal Editor Height & Scroll (Authenticated)', () => {
379377
const BOARD_URL = `/board/${BOARD_IDS.testBoard}`
380378

381379
test.beforeEach(async ({ page }) => {
380+
await resetProjectInfoNotes()
382381
await page.goto(BOARD_URL)
383382
// Use networkidle to wait for all data fetches to complete
384383
// This prevents flakiness caused by cards not being rendered yet
@@ -514,6 +513,10 @@ test.describe('NoteModal Formatting (Authenticated)', () => {
514513

515514
const BOARD_URL = `/board/${BOARD_IDS.testBoard}`
516515

516+
test.beforeEach(async () => {
517+
await resetProjectInfoNotes()
518+
})
519+
517520
test('should support markdown autoformat for heading', async ({ page }) => {
518521
// Slate autoformat triggers are timing-sensitive on slow CI runners
519522
test.slow()
@@ -539,8 +542,12 @@ test.describe('NoteModal Formatting (Authenticated)', () => {
539542
await expect(async () => {
540543
await page.keyboard.press(`${MOD}+a`)
541544
await page.keyboard.press('Backspace')
542-
const text = await editorContent.textContent()
543-
expect(text?.replace(/\s/g, '')).toBe('')
545+
// When editor is empty, Plate shows a placeholder (e.g. "Type / for commands...")
546+
// which textContent() picks up. Check for placeholder element instead.
547+
const placeholder = editorContent.locator(
548+
'[data-slate-placeholder="true"]',
549+
)
550+
await expect(placeholder).toBeVisible()
544551
}).toPass({ timeout: 10000 })
545552

546553
// Type markdown heading - the space after # triggers autoformat

0 commit comments

Comments
 (0)