Created detailed markdown plans for all items in todo.md: 1. 01-playwright-scaffolding.md - Base Playwright infrastructure 2. 02-magnitude-tests-comprehensive.md - Complete test coverage 3. 03-stream-ai-to-deepgram-tts.md - TTS latency optimization 4. 04-fix-galaxy-node-clicking.md - Galaxy navigation bugs 5. 05-dark-light-mode-theme.md - Dark/light mode with dynamic favicons 6. 06-fix-double-border-desktop.md - UI polish 7. 07-delete-backup-files.md - Code cleanup 8. 08-ai-transition-to-edit.md - Intelligent node creation flow 9. 09-umap-minimum-nodes-analysis.md - Technical analysis Each plan includes: - Detailed problem analysis - Proposed solutions with code examples - Manual Playwright MCP testing strategy - Magnitude test specifications - Implementation steps - Success criteria Ready to implement in sequence. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
10 KiB
Plan: Add Playwright Scaffolding Files
Priority: CRITICAL - Must be done first Dependencies: None Affects: All future testing work
Overview
Create base Playwright scaffolding files to improve efficiency of both manual Playwright MCP testing and Magnitude test automation. This infrastructure will make it easier to write, run, and maintain browser-based tests.
Current State
- No Playwright configuration file exists
- No test helpers or utilities for common operations
- No fixtures for authenticated states
- Manual testing with Playwright MCP is inefficient
- Magnitude tests will lack reusable components
Proposed Solution
Create a complete Playwright testing infrastructure with:
1. Core Configuration Files
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/playwright',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
// Setup authentication
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
// Desktop browsers
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'tests/playwright/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
storageState: 'tests/playwright/.auth/user.json',
},
dependencies: ['setup'],
},
// Mobile browsers
{
name: 'mobile-chrome',
use: {
...devices['Pixel 5'],
storageState: 'tests/playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
.gitignore updates
# Playwright
tests/playwright/.auth/
test-results/
playwright-report/
playwright/.cache/
2. Authentication Setup
tests/playwright/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const authFile = 'tests/playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
// Check if auth already exists and is valid
const authCookie = process.env.PLAYWRIGHT_AUTH_COOKIE;
if (authCookie) {
// Use existing auth from environment
await page.context().addCookies([{
name: 'ponderants-auth',
value: authCookie,
domain: 'localhost',
path: '/',
}]);
} else {
// Perform OAuth login
await page.goto('/');
await page.click('text=Log in with Bluesky');
// Wait for OAuth redirect
await page.waitForURL(/bsky\.social/);
// Fill in credentials
await page.fill('input[name="identifier"]', process.env.TEST_USER_HANDLE!);
await page.fill('input[name="password"]', process.env.TEST_USER_PASSWORD!);
await page.click('button[type="submit"]');
// Wait for redirect back to app
await page.waitForURL(/localhost:3000/);
// Verify we're authenticated
await expect(page.getByRole('button', { name: /profile/i })).toBeVisible();
}
// Save signed-in state
await page.context().storageState({ path: authFile });
});
3. Test Fixtures and Helpers
tests/playwright/fixtures.ts
import { test as base, Page } from '@playwright/test';
type Fixtures = {
authenticatedPage: Page;
chatPage: Page;
galaxyPage: Page;
};
export const test = base.extend<Fixtures>({
authenticatedPage: async ({ page }, use) => {
// Already authenticated via setup
await page.goto('/');
await use(page);
},
chatPage: async ({ page }, use) => {
await page.goto('/chat');
await use(page);
},
galaxyPage: async ({ page }, use) => {
await page.goto('/galaxy');
await use(page);
},
});
export { expect } from '@playwright/test';
tests/playwright/helpers/chat.ts
import { Page, expect } from '@playwright/test';
export class ChatHelper {
constructor(private page: Page) {}
async sendMessage(message: string) {
await this.page.fill('textarea[placeholder*="Type"]', message);
await this.page.press('textarea[placeholder*="Type"]', 'Enter');
}
async waitForAIResponse() {
// Wait for typing indicator to disappear
await this.page.waitForSelector('[data-testid="typing-indicator"]', {
state: 'hidden',
timeout: 30000,
});
}
async createNode() {
await this.page.click('button:has-text("Create Node")');
}
async getLastMessage() {
const messages = this.page.locator('[data-testid="chat-message"]');
const count = await messages.count();
return messages.nth(count - 1);
}
}
tests/playwright/helpers/galaxy.ts
import { Page, expect } from '@playwright/test';
export class GalaxyHelper {
constructor(private page: Page) {}
async waitForGalaxyLoad() {
// Wait for canvas to be visible
await this.page.waitForSelector('canvas', { timeout: 10000 });
// Wait for nodes to be loaded (check for at least one node)
await this.page.waitForFunction(() => {
const canvas = document.querySelector('canvas');
return canvas && canvas.getContext('2d') !== null;
});
}
async clickNode(nodeId: string) {
// Find and click on a specific node
await this.page.evaluate((id) => {
const event = new CustomEvent('node-click', { detail: { nodeId: id } });
window.dispatchEvent(event);
}, nodeId);
}
async getNodeCount() {
return await this.page.evaluate(() => {
// Access R3F scene data
return window.__galaxyNodes?.length || 0;
});
}
}
tests/playwright/helpers/node.ts
import { Page, expect } from '@playwright/test';
export class NodeHelper {
constructor(private page: Page) {}
async createNode(title: string, body: string) {
// Navigate to chat
await this.page.goto('/chat');
// Trigger node creation
await this.page.fill('input[placeholder*="Title"]', title);
await this.page.fill('textarea[placeholder*="Body"]', body);
await this.page.click('button:has-text("Publish")');
// Wait for success
await expect(this.page.getByText(/published/i)).toBeVisible();
}
async waitForUMAPCalculation() {
// Poll /api/galaxy until nodes have coords_3d
await this.page.waitForFunction(async () => {
const response = await fetch('/api/galaxy');
const data = await response.json();
return data.nodes.every((n: any) => n.coords_3d !== null);
}, { timeout: 60000 });
}
}
4. Example Tests
tests/playwright/smoke.spec.ts
import { test, expect } from './fixtures';
test.describe('Smoke Tests', () => {
test('homepage loads', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Ponderants/);
});
test('authenticated user can access chat', async ({ chatPage }) => {
await expect(chatPage.getByRole('textbox')).toBeVisible();
});
test('authenticated user can access galaxy', async ({ galaxyPage }) => {
await expect(galaxyPage.locator('canvas')).toBeVisible();
});
});
5. Environment Variables
.env.test (template)
# Test user credentials
TEST_USER_HANDLE=your-test-handle.bsky.social
TEST_USER_PASSWORD=your-test-password
# Optional: Pre-authenticated cookie for faster tests
PLAYWRIGHT_AUTH_COOKIE=
# Playwright settings
PLAYWRIGHT_BASE_URL=http://localhost:3000
Implementation Steps
-
Create directory structure
mkdir -p tests/playwright/.auth mkdir -p tests/playwright/helpers -
Install dependencies
pnpm add -D @playwright/test -
Create configuration files
- playwright.config.ts
- .gitignore updates
- .env.test template
-
Create authentication setup
- auth.setup.ts
-
Create fixtures and helpers
- fixtures.ts
- helpers/chat.ts
- helpers/galaxy.ts
- helpers/node.ts
-
Create example tests
- smoke.spec.ts
-
Update package.json scripts
{ "scripts": { "test:playwright": "playwright test", "test:playwright:ui": "playwright test --ui", "test:playwright:debug": "playwright test --debug" } }
Testing Plan
Manual Playwright MCP Testing
- Open Playwright MCP browser
- Navigate to http://localhost:3000
- Test authentication flow
- Test chat interaction
- Test node creation
- Test galaxy visualization
- Test node detail modal
Magnitude Test Integration
After manual testing succeeds, create magnitude tests that use the same patterns:
import { test } from 'magnitude-test';
test('User can create and view nodes in galaxy', async (agent) => {
await agent.open('http://localhost:3000');
await agent.act('Log in with test credentials');
await agent.check('User is authenticated');
await agent.act('Navigate to chat');
await agent.act('Create a new node with title "Test Node" and body "Test content"');
await agent.check('Node is published to Bluesky');
await agent.act('Navigate to galaxy');
await agent.check('Can see the new node in 3D visualization');
});
Success Criteria
- ✅ Playwright configuration file exists
- ✅ Authentication setup works (both OAuth and cookie-based)
- ✅ Helper classes for chat, galaxy, and node operations exist
- ✅ Example smoke tests pass
- ✅ Can run
pnpm test:playwrightsuccessfully - ✅ Manual Playwright MCP testing is efficient with helpers
- ✅ Foundation exists for Magnitude test creation
Files to Create
playwright.config.ts- Main configurationtests/playwright/auth.setup.ts- Authentication setuptests/playwright/fixtures.ts- Custom fixturestests/playwright/helpers/chat.ts- Chat helper functionstests/playwright/helpers/galaxy.ts- Galaxy helper functionstests/playwright/helpers/node.ts- Node helper functionstests/playwright/smoke.spec.ts- Example smoke tests.env.test- Environment variables template
Files to Update
.gitignore- Add Playwright artifactspackage.json- Add test scriptsREADME.md- Document testing approach (optional)