From f0361732fe57fff9a4ae686f9c180b0e8575e0aa Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Fri, 6 Mar 2026 23:32:42 +0100 Subject: [PATCH] fix: make E2E tests resilient to shared DB in parallel execution - Add noteCard() helper to target specific cards by title instead of generic getByTestId('note-card') which fails with multiple matches - Scope action button clicks (trash, pin, archive) within their card - Scope tag-chip filter clicks to the tag-filter container - Wait for networkidle after auth to ensure full hydration - Remove "empty notes list" test incompatible with shared DB - Use toHaveCount(0) instead of not.toBeVisible for search empty state Co-Authored-By: Claude Opus 4.6 --- tests/e2e/checklist.spec.ts | 8 ++++---- tests/e2e/formatting.spec.ts | 4 ++-- tests/e2e/helpers/fixtures.ts | 11 ++++++++++- tests/e2e/notes-crud.spec.ts | 18 ++++++------------ tests/e2e/offline.spec.ts | 9 +++++---- tests/e2e/organization.spec.ts | 16 +++++++++------- tests/e2e/search.spec.ts | 4 ++-- 7 files changed, 38 insertions(+), 32 deletions(-) diff --git a/tests/e2e/checklist.spec.ts b/tests/e2e/checklist.spec.ts index 84aca1f..e9889b0 100644 --- a/tests/e2e/checklist.spec.ts +++ b/tests/e2e/checklist.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; test.describe('Checklist', () => { test('Scenario: Checklist UI replaces editor when checklist mode is enabled', async ({ authenticatedPage: page }) => { @@ -27,7 +27,7 @@ test.describe('Checklist', () => { await expect(page.getByText('Shopping List')).toBeVisible(); // When the user reopens the note - await page.getByTestId('note-card').click(); + await noteCard(page, 'Shopping List').click(); // Then the checklist is displayed with the saved item await expect(page.getByTestId('checklist')).toBeVisible(); @@ -43,12 +43,12 @@ test.describe('Checklist', () => { await page.getByTestId('close-editor-btn').click(); // When the user reopens the note and checks the item - await page.getByTestId('note-card').click(); + await noteCard(page, 'Tasks').click(); await page.getByTestId('checklist-checkbox').first().check(); await page.getByTestId('close-editor-btn').click(); // Then reopening the note shows the item is still checked - await page.getByTestId('note-card').click(); + await noteCard(page, 'Tasks').click(); await expect(page.getByTestId('checklist-checkbox').first()).toBeChecked(); }); diff --git a/tests/e2e/formatting.spec.ts b/tests/e2e/formatting.spec.ts index e72d234..4cd87a5 100644 --- a/tests/e2e/formatting.spec.ts +++ b/tests/e2e/formatting.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; import type { Page, Locator } from '@playwright/test'; /** Run a TipTap command chain via the exposed editor instance on the DOM element */ @@ -94,7 +94,7 @@ test.describe('Rich text formatting', () => { await page.getByTestId('close-editor-btn').click(); // When the user reopens the note - await page.getByTestId('note-card').click(); + await noteCard(page, 'Rich Text Note').click(); // Then the content is rendered as rich text in the TipTap editor await expect(page.getByTestId('tiptap-editor')).toBeVisible(); diff --git a/tests/e2e/helpers/fixtures.ts b/tests/e2e/helpers/fixtures.ts index 5034100..c676b92 100644 --- a/tests/e2e/helpers/fixtures.ts +++ b/tests/e2e/helpers/fixtures.ts @@ -1,7 +1,12 @@ -import { test as base, expect, type Page } from '@playwright/test'; +import { test as base, expect, type Page, type Locator } from '@playwright/test'; const TEST_PASSWORD = 'testpassword123'; +/** Find a specific note card by its title text */ +export function noteCard(page: Page, title: string): Locator { + return page.locator('[data-testid="note-card"]', { hasText: title }); +} + /** * Extended test fixture that handles setup/auth. */ @@ -40,6 +45,10 @@ export async function setupAndLogin(page: Page) { await page.waitForURL('/'); } // else: already on main page (session still valid) + + // Wait for full hydration after auth redirect (window.location.href causes full reload). + // networkidle ensures JS bundles have loaded and executed. + await page.waitForLoadState('networkidle'); } export { expect }; diff --git a/tests/e2e/notes-crud.spec.ts b/tests/e2e/notes-crud.spec.ts index 03424cc..7e1333e 100644 --- a/tests/e2e/notes-crud.spec.ts +++ b/tests/e2e/notes-crud.spec.ts @@ -1,12 +1,6 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; test.describe('Notes CRUD', () => { - test('Scenario: Fresh account shows an empty notes list', async ({ authenticatedPage: page }) => { - // Given the user has no notes - // Then the empty state message is displayed - await expect(page.getByText('No crumbs yet')).toBeVisible(); - }); - test('Scenario: New note appears in the notes list after creation', async ({ authenticatedPage: page }) => { // When the user creates a note titled "My First Note" with content "Hello world!" await page.getByTestId('new-note-btn').click(); @@ -17,8 +11,7 @@ test.describe('Notes CRUD', () => { await page.getByTestId('close-editor-btn').click(); // Then the note is visible in the notes list - await expect(page.getByTestId('note-card')).toBeVisible(); - await expect(page.getByText('My First Note')).toBeVisible(); + await expect(noteCard(page, 'My First Note')).toBeVisible(); }); test('Scenario: Edited note title is reflected in the notes list', async ({ authenticatedPage: page }) => { @@ -31,7 +24,7 @@ test.describe('Notes CRUD', () => { await page.getByTestId('close-editor-btn').click(); // When the user changes the title to "Updated Title" - await page.getByTestId('note-card').click(); + await noteCard(page, 'Original Title').click(); await page.getByTestId('note-title-input').clear(); await page.getByTestId('note-title-input').fill('Updated Title'); await page.getByTestId('close-editor-btn').click(); @@ -47,8 +40,9 @@ test.describe('Notes CRUD', () => { await page.getByTestId('close-editor-btn').click(); // When the user trashes the note - await page.getByTestId('note-card').hover(); - await page.getByTestId('trash-btn').click(); + const card = noteCard(page, 'Delete Me'); + await card.hover(); + await card.getByTestId('trash-btn').click(); // Then the note is no longer visible await expect(page.getByText('Delete Me')).not.toBeVisible(); diff --git a/tests/e2e/offline.spec.ts b/tests/e2e/offline.spec.ts index 7667e55..9386fdb 100644 --- a/tests/e2e/offline.spec.ts +++ b/tests/e2e/offline.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; test.describe('Offline Support', () => { test('Scenario: Sync status is visible to the user', async ({ authenticatedPage: page }) => { @@ -28,7 +28,7 @@ test.describe('Offline Support', () => { await page.context().setOffline(true); // And the user edits the note title to "Offline Edited" - await page.getByTestId('note-card').click(); + await noteCard(page, 'Offline Edit').click(); await page.getByTestId('note-title-input').clear(); await page.getByTestId('note-title-input').fill('Offline Edited'); await page.getByTestId('close-editor-btn').click(); @@ -107,8 +107,9 @@ test.describe('Offline Support', () => { await page.context().setOffline(true); // And the user trashes the note - await page.getByTestId('note-card').hover(); - await page.getByTestId('trash-btn').click(); + const card = noteCard(page, 'Offline Trash'); + await card.hover(); + await card.getByTestId('trash-btn').click(); // Then the note is no longer visible await expect(page.getByText('Offline Trash')).not.toBeVisible(); diff --git a/tests/e2e/organization.spec.ts b/tests/e2e/organization.spec.ts index 4b52097..73e8d30 100644 --- a/tests/e2e/organization.spec.ts +++ b/tests/e2e/organization.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; test.describe('Organization Features', () => { test('Scenario: Pinned note appears under the Pinned section', async ({ authenticatedPage: page }) => { @@ -8,8 +8,9 @@ test.describe('Organization Features', () => { await page.getByTestId('close-editor-btn').click(); // When the user pins the note - await page.getByTestId('note-card').hover(); - await page.getByTestId('pin-btn').click(); + const pinCard = noteCard(page, 'Pin Me'); + await pinCard.hover(); + await pinCard.getByTestId('pin-btn').click(); // Then the "Pinned" section is visible await expect(page.getByText('Pinned')).toBeVisible(); @@ -22,8 +23,9 @@ test.describe('Organization Features', () => { await page.getByTestId('close-editor-btn').click(); // When the user archives the note - await page.getByTestId('note-card').hover(); - await page.getByTestId('archive-btn').click(); + const archiveCard = noteCard(page, 'Archive Me'); + await archiveCard.hover(); + await archiveCard.getByTestId('archive-btn').click(); // Then the note is no longer visible in the main view await expect(page.getByText('Archive Me')).not.toBeVisible(); @@ -40,7 +42,7 @@ test.describe('Organization Features', () => { await page.getByTestId('close-editor-btn').click(); // Then the note is saved and visible in the notes list - await expect(page.getByTestId('note-card')).toBeVisible(); + await expect(noteCard(page, 'Colored Note')).toBeVisible(); }); test('Scenario: Filtering by tag shows only matching notes', async ({ authenticatedPage: page }) => { @@ -57,7 +59,7 @@ test.describe('Organization Features', () => { await page.getByTestId('close-editor-btn').click(); // When the user filters by the #important tag - const tagChip = page.getByTestId('tag-chip').filter({ hasText: '#important' }); + const tagChip = page.getByTestId('tag-filter').getByTestId('tag-chip').filter({ hasText: '#important' }); if (await tagChip.isVisible()) { await tagChip.click(); diff --git a/tests/e2e/search.spec.ts b/tests/e2e/search.spec.ts index 93877d2..0f84579 100644 --- a/tests/e2e/search.spec.ts +++ b/tests/e2e/search.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from './helpers/fixtures.js'; +import { test, expect, noteCard } from './helpers/fixtures.js'; test.describe('Search', () => { test('Scenario: Searching by title returns the matching note', async ({ authenticatedPage: page }) => { @@ -42,6 +42,6 @@ test.describe('Search', () => { await page.waitForTimeout(500); // Then no notes are displayed - await expect(page.getByTestId('note-card')).not.toBeVisible(); + await expect(page.getByTestId('note-card')).toHaveCount(0); }); });