managed sessions only. allow for rename/delete
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Basic E2E Tests', () => {
|
||||
test('backend health check', async ({ request }) => {
|
||||
const response = await request.get('http://localhost:3000/api/health');
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const body = await response.json();
|
||||
expect(body.status).toBe('ok');
|
||||
expect(body.service).toBe('spiceflow');
|
||||
});
|
||||
|
||||
test('frontend loads', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/Spiceflow/i);
|
||||
});
|
||||
|
||||
test('sessions list loads empty', async ({ request }) => {
|
||||
const response = await request.get('http://localhost:3000/api/sessions');
|
||||
expect(response.ok()).toBeTruthy();
|
||||
const sessions = await response.json();
|
||||
expect(Array.isArray(sessions)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Permissions Workflow', () => {
|
||||
test('permission approval allows file creation and reading', async ({ page }) => {
|
||||
// Increase timeout for this test since it involves real Claude interaction
|
||||
test.setTimeout(180000);
|
||||
|
||||
// Enable console logging for debugging
|
||||
page.on('console', (msg) => {
|
||||
console.log(`[Browser ${msg.type()}]`, msg.text());
|
||||
});
|
||||
|
||||
// Log WebSocket frames for debugging
|
||||
page.on('websocket', (ws) => {
|
||||
console.log(`[WebSocket] Connected to ${ws.url()}`);
|
||||
ws.on('framesent', (frame) => console.log(`[WS Sent]`, frame.payload));
|
||||
ws.on('framereceived', (frame) => console.log(`[WS Received]`, frame.payload));
|
||||
});
|
||||
|
||||
// 1. Navigate to homepage
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/Spiceflow/i);
|
||||
|
||||
// 2. Create a new session
|
||||
const createButton = page.locator('button[title="New Session"]');
|
||||
await expect(createButton).toBeVisible();
|
||||
await createButton.click();
|
||||
|
||||
// 3. Wait for navigation to session page
|
||||
await page.waitForURL(/\/session\/.+/);
|
||||
console.log('[Test] Navigated to session page:', page.url());
|
||||
|
||||
// 4. Wait for the page to load
|
||||
await expect(page.locator('text=Loading')).not.toBeVisible({ timeout: 5000 });
|
||||
await expect(page.locator('text=No messages yet')).toBeVisible();
|
||||
|
||||
// 5. Send message asking Claude to create foo.md with a haiku
|
||||
const textarea = page.locator('textarea');
|
||||
await expect(textarea).toBeVisible();
|
||||
await textarea.fill(
|
||||
'Create a file called foo.md containing a haiku about software testing. Title it "My Haiku" at the top. Just create the file, no other commentary needed.'
|
||||
);
|
||||
|
||||
const sendButton = page.locator('button[type="submit"]');
|
||||
await expect(sendButton).toBeEnabled();
|
||||
await sendButton.click();
|
||||
|
||||
// 6. Verify user message appears
|
||||
await expect(
|
||||
page.locator('text=Create a file called foo.md')
|
||||
).toBeVisible();
|
||||
console.log('[Test] User message displayed');
|
||||
|
||||
// 6b. Verify thinking indicator appears immediately
|
||||
const assistantBubble = page.locator('.rounded-lg.border').filter({
|
||||
has: page.locator('text=Assistant')
|
||||
}).first();
|
||||
await expect(assistantBubble).toBeVisible({ timeout: 2000 });
|
||||
console.log('[Test] Thinking indicator appeared immediately');
|
||||
|
||||
// 7. Wait for permission request UI to appear
|
||||
const permissionUI = page.locator('text=Claude needs permission');
|
||||
await expect(permissionUI).toBeVisible({ timeout: 60000 });
|
||||
console.log('[Test] Permission request UI appeared');
|
||||
|
||||
// 8. Verify the permission shows Write tool for foo.md
|
||||
const permissionDescription = page.locator('li.font-mono').filter({
|
||||
hasText: /Write.*foo\.md|create.*foo\.md/i
|
||||
}).first();
|
||||
await expect(permissionDescription).toBeVisible();
|
||||
console.log('[Test] Permission shows foo.md file creation');
|
||||
|
||||
// 9. Click Accept button
|
||||
const acceptButton = page.locator('button:has-text("Accept")');
|
||||
await expect(acceptButton).toBeVisible();
|
||||
await acceptButton.click();
|
||||
console.log('[Test] Clicked Accept button');
|
||||
|
||||
// 10. Wait for permission UI to disappear
|
||||
await expect(permissionUI).not.toBeVisible({ timeout: 10000 });
|
||||
console.log('[Test] Permission UI disappeared');
|
||||
|
||||
// 11. Wait for streaming to complete after permission granted
|
||||
await page.waitForTimeout(2000);
|
||||
const bouncingDots = page.locator('.animate-bounce');
|
||||
const pulsingCursor = page.locator('.animate-pulse');
|
||||
await expect(bouncingDots).toHaveCount(0, { timeout: 60000 });
|
||||
await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 });
|
||||
console.log('[Test] First streaming complete');
|
||||
|
||||
// Count current assistant messages before sending new request
|
||||
const assistantMessages = page.locator('.rounded-lg.border').filter({
|
||||
has: page.locator('text=Assistant')
|
||||
});
|
||||
const messageCountBefore = await assistantMessages.count();
|
||||
console.log('[Test] Assistant message count before read request:', messageCountBefore);
|
||||
|
||||
// 12. Now ask Claude to read the file back to verify it was created
|
||||
await textarea.fill('Read the contents of foo.md and tell me what it says. Quote the file contents.');
|
||||
await sendButton.click();
|
||||
console.log('[Test] Asked Claude to read the file');
|
||||
|
||||
// 13. Wait for a NEW assistant message to appear
|
||||
await expect(assistantMessages).toHaveCount(messageCountBefore + 1, { timeout: 60000 });
|
||||
console.log('[Test] New assistant message appeared');
|
||||
|
||||
// Wait for streaming to complete on the new message
|
||||
await expect(bouncingDots).toHaveCount(0, { timeout: 60000 });
|
||||
await expect(pulsingCursor).toHaveCount(0, { timeout: 60000 });
|
||||
console.log('[Test] Second streaming complete');
|
||||
|
||||
// 14. Verify the response contains "My Haiku" - confirming file was created and read
|
||||
const lastAssistantMessage = assistantMessages.last();
|
||||
const responseText = await lastAssistantMessage.locator('.font-mono').textContent();
|
||||
console.log('[Test] Claude read back:', responseText);
|
||||
|
||||
// The response should contain "My Haiku" which we asked Claude to title the file
|
||||
expect(responseText).toBeTruthy();
|
||||
expect(responseText).toContain('My Haiku');
|
||||
console.log('[Test] Successfully verified "My Haiku" in Claude response');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Chat Workflow', () => {
|
||||
test('create new chat and send message to Claude', async ({ page }) => {
|
||||
// Enable console logging to debug WebSocket issues
|
||||
page.on('console', (msg) => {
|
||||
console.log(`[Browser ${msg.type()}]`, msg.text());
|
||||
});
|
||||
|
||||
// Log WebSocket frames
|
||||
page.on('websocket', (ws) => {
|
||||
console.log(`[WebSocket] Connected to ${ws.url()}`);
|
||||
ws.on('framesent', (frame) => console.log(`[WS Sent]`, frame.payload));
|
||||
ws.on('framereceived', (frame) => console.log(`[WS Received]`, frame.payload));
|
||||
ws.on('close', () => console.log('[WebSocket] Closed'));
|
||||
});
|
||||
|
||||
// Log network requests to /api
|
||||
page.on('request', (request) => {
|
||||
if (request.url().includes('/api')) {
|
||||
console.log(`[Request] ${request.method()} ${request.url()}`);
|
||||
}
|
||||
});
|
||||
page.on('response', (response) => {
|
||||
if (response.url().includes('/api')) {
|
||||
console.log(`[Response] ${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 1. Navigate to homepage
|
||||
await page.goto('/');
|
||||
await expect(page).toHaveTitle(/Spiceflow/i);
|
||||
|
||||
// 2. Click the + button to create a new session
|
||||
const createButton = page.locator('button[title="New Session"]');
|
||||
await expect(createButton).toBeVisible();
|
||||
await createButton.click();
|
||||
|
||||
// 3. Wait for navigation to session page
|
||||
await page.waitForURL(/\/session\/.+/);
|
||||
console.log('[Test] Navigated to session page:', page.url());
|
||||
|
||||
// 4. Wait for the page to load (no loading spinner)
|
||||
await expect(page.locator('text=Loading')).not.toBeVisible({ timeout: 5000 });
|
||||
|
||||
// 5. Verify we see the empty message state
|
||||
await expect(page.locator('text=No messages yet')).toBeVisible();
|
||||
|
||||
// 6. Type a message in the textarea
|
||||
const textarea = page.locator('textarea');
|
||||
await expect(textarea).toBeVisible();
|
||||
await textarea.fill('say hi. respond in a single concise sentence');
|
||||
|
||||
// 7. Click the send button
|
||||
const sendButton = page.locator('button[type="submit"]');
|
||||
await expect(sendButton).toBeEnabled();
|
||||
await sendButton.click();
|
||||
|
||||
// 8. Verify user message appears immediately (optimistic update)
|
||||
await expect(page.locator('text=say hi. respond in a single concise sentence')).toBeVisible();
|
||||
console.log('[Test] User message displayed');
|
||||
|
||||
// 9. Verify thinking indicator appears immediately after sending
|
||||
// The assistant bubble with bouncing dots should show right away (isThinking state)
|
||||
const assistantMessage = page.locator('.rounded-lg.border').filter({
|
||||
has: page.locator('text=Assistant')
|
||||
}).last();
|
||||
|
||||
// Thinking indicator should appear almost immediately (within 2 seconds)
|
||||
await expect(assistantMessage).toBeVisible({ timeout: 2000 });
|
||||
console.log('[Test] Thinking indicator appeared immediately');
|
||||
|
||||
// Verify bouncing dots are present (thinking state)
|
||||
const bouncingDotsInAssistant = assistantMessage.locator('.animate-bounce');
|
||||
await expect(bouncingDotsInAssistant.first()).toBeVisible({ timeout: 2000 });
|
||||
console.log('[Test] Bouncing dots visible in thinking state');
|
||||
|
||||
// 10. Wait for streaming to complete - progress indicator should disappear
|
||||
// The streaming indicator has animate-bounce dots and animate-pulse cursor
|
||||
// Note: With fast responses, the indicator may appear and disappear quickly,
|
||||
// so we just verify it's gone after the response is visible
|
||||
const bouncingDots = page.locator('.animate-bounce');
|
||||
const pulsingCursor = page.locator('.animate-pulse');
|
||||
|
||||
// Wait for streaming indicators to disappear (they should be gone after message-stop)
|
||||
await expect(bouncingDots).toHaveCount(0, { timeout: 30000 });
|
||||
await expect(pulsingCursor).toHaveCount(0, { timeout: 30000 });
|
||||
console.log('[Test] Streaming complete - progress indicator disappeared');
|
||||
|
||||
// 11. Verify the response contains some text content
|
||||
const responseText = await assistantMessage.locator('.font-mono').textContent();
|
||||
console.log('[Test] Assistant response text:', responseText);
|
||||
expect(responseText).toBeTruthy();
|
||||
expect(responseText!.length).toBeGreaterThan(0);
|
||||
|
||||
// 12. Verify working directory indicator appears
|
||||
// The working directory should be captured from the init event and displayed
|
||||
const workingDirIndicator = page.locator('.font-mono').filter({ hasText: /^\// }).first();
|
||||
await expect(workingDirIndicator).toBeVisible({ timeout: 5000 });
|
||||
const workingDirText = await workingDirIndicator.textContent();
|
||||
console.log('[Test] Working directory displayed:', workingDirText);
|
||||
expect(workingDirText).toMatch(/^\//); // Should start with /
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user