Add comprehensive E2E tests for all core flows (M5 Task 5)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
233
e2e/core-flows.spec.ts
Normal file
233
e2e/core-flows.spec.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await page.waitForTimeout(500) // Wait for mock data
|
||||
})
|
||||
|
||||
// --- Flow 1: Open app, click note, verify editor shows content ---
|
||||
|
||||
test('clicking a note opens it in the editor with content', async ({ page }) => {
|
||||
// Click "Build Laputa App" in the note list
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Tab should appear and be active
|
||||
await expect(page.locator('.editor__tab--active')).toHaveText(/Build Laputa App/)
|
||||
|
||||
// Editor should show note content (heading in live preview)
|
||||
await expect(page.locator('.cm-editor')).toBeVisible()
|
||||
|
||||
// Inspector should show properties
|
||||
await expect(page.locator('.inspector__prop-value', { hasText: 'Project' })).toBeVisible()
|
||||
|
||||
await page.screenshot({ path: 'test-results/core-open-note.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('editor shows markdown content with live preview', async ({ page }) => {
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// CodeMirror should render the content
|
||||
const editor = page.locator('.cm-editor')
|
||||
await expect(editor).toBeVisible()
|
||||
|
||||
// Should contain the heading text (rendered by live preview)
|
||||
await expect(page.locator('.cm-content')).toContainText('Build Laputa App')
|
||||
})
|
||||
|
||||
// --- Flow 2: Sidebar filter changes note list ---
|
||||
|
||||
test('sidebar filter: People shows only Person entries', async ({ page }) => {
|
||||
await page.locator('.sidebar__filter-item', { hasText: 'People' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await expect(page.locator('.note-list__count')).toHaveText('1')
|
||||
await expect(page.locator('.note-list__title', { hasText: 'Matteo Cellini' })).toBeVisible()
|
||||
await expect(page.locator('.note-list__title', { hasText: 'Build Laputa App' })).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('sidebar filter: Events shows only Event entries', async ({ page }) => {
|
||||
await page.locator('.sidebar__filter-item', { hasText: 'Events' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await expect(page.locator('.note-list__count')).toHaveText('1')
|
||||
await expect(page.locator('.note-list__title', { hasText: 'Laputa App Design Session' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('sidebar filter: clicking back to All Notes restores full list', async ({ page }) => {
|
||||
await page.locator('.sidebar__filter-item', { hasText: 'People' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.note-list__count')).toHaveText('1')
|
||||
|
||||
await page.locator('.sidebar__filter-item', { hasText: 'All Notes' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.note-list__count')).toHaveText('12')
|
||||
})
|
||||
|
||||
// --- Flow 3: Search for a note ---
|
||||
|
||||
test('search filters notes by title', async ({ page }) => {
|
||||
await page.fill('.note-list__search-input', 'stock')
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await expect(page.locator('.note-list__count')).toHaveText('1')
|
||||
await expect(page.locator('.note-list__title', { hasText: 'Stock Screener' })).toBeVisible()
|
||||
await expect(page.locator('.note-list__title', { hasText: 'Build Laputa App' })).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('clearing search restores all results', async ({ page }) => {
|
||||
await page.fill('.note-list__search-input', 'stock')
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.note-list__count')).toHaveText('1')
|
||||
|
||||
await page.fill('.note-list__search-input', '')
|
||||
await page.waitForTimeout(200)
|
||||
await expect(page.locator('.note-list__count')).toHaveText('12')
|
||||
})
|
||||
|
||||
// --- Flow 4: Open multiple tabs and switch between them ---
|
||||
|
||||
test('opening multiple notes creates multiple tabs', async ({ page }) => {
|
||||
// Open first note
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Open second note
|
||||
await page.locator('.note-list__item', { hasText: 'Grow Newsletter' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Both tabs should exist
|
||||
const tabs = page.locator('.editor__tab')
|
||||
await expect(tabs).toHaveCount(2)
|
||||
|
||||
// Second tab should be active
|
||||
await expect(page.locator('.editor__tab--active')).toHaveText(/Grow Newsletter/)
|
||||
|
||||
await page.screenshot({ path: 'test-results/core-multi-tabs.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('clicking a tab switches to it', async ({ page }) => {
|
||||
// Open two notes
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.locator('.note-list__item', { hasText: 'Grow Newsletter' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Switch back to first tab
|
||||
await page.locator('.editor__tab', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// First tab should now be active
|
||||
await expect(page.locator('.editor__tab--active')).toHaveText(/Build Laputa App/)
|
||||
|
||||
// Editor should show first note's content
|
||||
await expect(page.locator('.cm-content')).toContainText('Build Laputa App')
|
||||
})
|
||||
|
||||
test('closing a tab removes it and switches to adjacent', async ({ page }) => {
|
||||
// Open two notes
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.locator('.note-list__item', { hasText: 'Grow Newsletter' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Close active tab (Grow Newsletter)
|
||||
await page.locator('.editor__tab--active .editor__tab-close').click()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// Should have 1 tab left
|
||||
await expect(page.locator('.editor__tab')).toHaveCount(1)
|
||||
await expect(page.locator('.editor__tab--active')).toHaveText(/Build Laputa App/)
|
||||
})
|
||||
|
||||
// --- Flow 5: Inspector shows correct properties ---
|
||||
|
||||
test('inspector shows properties for selected note', async ({ page }) => {
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Type
|
||||
await expect(page.locator('.inspector__prop-value', { hasText: 'Project' })).toBeVisible()
|
||||
// Status
|
||||
await expect(page.locator('.inspector__status-pill', { hasText: 'Active' })).toBeVisible()
|
||||
// Owner
|
||||
await expect(page.locator('.inspector__prop-value', { hasText: 'Luca Rossi' })).toBeVisible()
|
||||
|
||||
await page.screenshot({ path: 'test-results/core-inspector.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('inspector shows relationships', async ({ page }) => {
|
||||
// Open a note with relationships
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Should show "Related to" relationships
|
||||
await expect(page.locator('.inspector__section', { hasText: 'Relationships' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('inspector shows backlinks', async ({ page }) => {
|
||||
// Open a note that has backlinks (Build Laputa App is referenced by Facebook Ads and Budget Allocation)
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Backlinks section should show
|
||||
const backlinksSection = page.locator('.inspector__section', { hasText: 'Backlinks' })
|
||||
await expect(backlinksSection).toBeVisible()
|
||||
})
|
||||
|
||||
test('inspector shows git history', async ({ page }) => {
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Should show commits
|
||||
await expect(page.locator('.inspector__commit-hash').first()).toBeVisible()
|
||||
await expect(page.locator('.inspector__commit-msg').first()).toContainText('26q1-laputa-app')
|
||||
})
|
||||
|
||||
test('inspector updates when switching tabs', async ({ page }) => {
|
||||
// Open first note
|
||||
await page.locator('.note-list__item', { hasText: 'Build Laputa App' }).click()
|
||||
await page.waitForTimeout(300)
|
||||
await expect(page.locator('.inspector__prop-value', { hasText: 'Project' })).toBeVisible()
|
||||
|
||||
// Open second note with different type — use title locator for precision
|
||||
await page.locator('.note-list__item').filter({ has: page.locator('.note-list__title', { hasText: 'Matteo Cellini' }) }).click()
|
||||
await page.waitForTimeout(300)
|
||||
await expect(page.locator('.inspector__prop-value', { hasText: 'Person' })).toBeVisible()
|
||||
})
|
||||
|
||||
// --- Flow 6: Note list preview snippets ---
|
||||
|
||||
test('note list items show preview snippets', async ({ page }) => {
|
||||
// Check that snippets are visible
|
||||
const snippet = page.locator('.note-list__snippet').first()
|
||||
await expect(snippet).toBeVisible()
|
||||
// Snippet should have some text content
|
||||
const text = await snippet.textContent()
|
||||
expect(text!.length).toBeGreaterThan(10)
|
||||
})
|
||||
|
||||
// --- Flow 7: Create note and verify it appears ---
|
||||
|
||||
test('full create note flow', async ({ page }) => {
|
||||
// Count before
|
||||
const countBefore = await page.locator('.note-list__count').textContent()
|
||||
|
||||
// Create new note
|
||||
await page.click('.note-list__add-btn')
|
||||
await page.waitForTimeout(200)
|
||||
await page.fill('.create-dialog__input', 'E2E Test Note')
|
||||
await page.click('.create-dialog__type-btn:text("Experiment")')
|
||||
await page.click('.create-dialog__btn--create')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Count should increase
|
||||
const countAfter = await page.locator('.note-list__count').textContent()
|
||||
expect(parseInt(countAfter!)).toBe(parseInt(countBefore!) + 1)
|
||||
|
||||
// Note should be opened in editor
|
||||
await expect(page.locator('.editor__tab--active')).toHaveText(/E2E Test Note/)
|
||||
|
||||
await page.screenshot({ path: 'test-results/core-create-note.png', fullPage: true })
|
||||
})
|
||||
Reference in New Issue
Block a user