247 lines
8.9 KiB
TypeScript
247 lines
8.9 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { E2E_BACKEND_URL } from '../playwright.config';
|
|
|
|
// Helper to create a tmux session via API
|
|
async function createTmuxSession(request: import('@playwright/test').APIRequestContext) {
|
|
const res = await request.post(`${E2E_BACKEND_URL}/api/sessions`, {
|
|
data: { provider: 'tmux' }
|
|
});
|
|
expect(res.ok()).toBeTruthy();
|
|
return await res.json();
|
|
}
|
|
|
|
// Helper to delete a session
|
|
async function deleteSession(request: import('@playwright/test').APIRequestContext, sessionId: string) {
|
|
try {
|
|
await request.delete(`${E2E_BACKEND_URL}/api/sessions/${encodeURIComponent(sessionId)}`);
|
|
} catch (e) {
|
|
console.log(`[Cleanup] Failed to delete session ${sessionId}:`, e);
|
|
}
|
|
}
|
|
|
|
// Helper to wait for tmux session to load
|
|
async function waitForTmuxSession(page: import('@playwright/test').Page) {
|
|
// Wait for terminal badge to appear
|
|
await expect(page.locator('text=TERMINAL')).toBeVisible({ timeout: 10000 });
|
|
}
|
|
|
|
// Helper to click a session card by ID (triggers client-side navigation)
|
|
async function clickSessionById(page: import('@playwright/test').Page, sessionId: string) {
|
|
const card = page.locator(`a[href="/session/${encodeURIComponent(sessionId)}"]`);
|
|
await expect(card).toBeVisible({ timeout: 10000 });
|
|
await card.click();
|
|
}
|
|
|
|
// Helper to go back to home via the back button (client-side navigation)
|
|
async function goBackToHome(page: import('@playwright/test').Page) {
|
|
const backBtn = page.locator('button[aria-label="Go back"]');
|
|
await expect(backBtn).toBeVisible();
|
|
await backBtn.click();
|
|
await expect(page).toHaveTitle(/Spiceflow/i);
|
|
}
|
|
|
|
test.describe('Last Session Navigation', () => {
|
|
test('navigating between sessions shows last session button', async ({ page, request }) => {
|
|
// Create two sessions
|
|
const sessionA = await createTmuxSession(request);
|
|
const sessionB = await createTmuxSession(request);
|
|
|
|
try {
|
|
// Start at home page and wait for sessions to load
|
|
await page.goto('/');
|
|
await expect(page).toHaveTitle(/Spiceflow/i);
|
|
await expect(page.locator('a[href^="/session/"]').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click session A (client-side navigation)
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button should NOT be visible (no previous session)
|
|
await expect(page.locator('button[title="Go to last session"]')).not.toBeVisible();
|
|
|
|
// Go back to home
|
|
await goBackToHome(page);
|
|
|
|
// Click session B (client-side navigation, sets lastSession = A)
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button SHOULD be visible now (previous was A)
|
|
const lastSessionBtn = page.locator('button[title="Go to last session"]');
|
|
await expect(lastSessionBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
// Click it to go back to A
|
|
await lastSessionBtn.click();
|
|
await waitForTmuxSession(page);
|
|
|
|
// Verify we're on session A
|
|
await expect(page).toHaveURL(new RegExp(`/session/${encodeURIComponent(sessionA.id)}`));
|
|
|
|
// Now last session should still be visible (pointing to B)
|
|
await expect(lastSessionBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
} finally {
|
|
await deleteSession(request, sessionA.id);
|
|
await deleteSession(request, sessionB.id);
|
|
}
|
|
});
|
|
|
|
test('going home and re-entering same session preserves last session', async ({ page, request }) => {
|
|
// Create two sessions
|
|
const sessionA = await createTmuxSession(request);
|
|
const sessionB = await createTmuxSession(request);
|
|
|
|
try {
|
|
// Start at home page
|
|
await page.goto('/');
|
|
await expect(page.locator('a[href^="/session/"]').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click session A, then back to home, then click session B
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
await goBackToHome(page);
|
|
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Verify last session button is visible (points to A)
|
|
const lastSessionBtn = page.locator('button[title="Go to last session"]');
|
|
await expect(lastSessionBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
// Go back to home
|
|
await goBackToHome(page);
|
|
|
|
// Re-enter session B (same session)
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button should STILL be visible and point to A
|
|
// (re-entering same session should NOT override the last session)
|
|
await expect(lastSessionBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
// Click it to verify it goes to A
|
|
await lastSessionBtn.click();
|
|
await waitForTmuxSession(page);
|
|
await expect(page).toHaveURL(new RegExp(`/session/${encodeURIComponent(sessionA.id)}`));
|
|
|
|
} finally {
|
|
await deleteSession(request, sessionA.id);
|
|
await deleteSession(request, sessionB.id);
|
|
}
|
|
});
|
|
|
|
test('last session button not shown when it equals current session', async ({ page, request }) => {
|
|
// Create two sessions
|
|
const sessionA = await createTmuxSession(request);
|
|
const sessionB = await createTmuxSession(request);
|
|
|
|
try {
|
|
// Start at home
|
|
await page.goto('/');
|
|
await expect(page.locator('a[href^="/session/"]').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Go A -> home -> B (sets localStorage to A)
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
await goBackToHome(page);
|
|
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Go back to home
|
|
await goBackToHome(page);
|
|
|
|
// Now go to A (localStorage still has A, which equals current)
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button should NOT be visible (lastSession = current session = A)
|
|
await expect(page.locator('button[title="Go to last session"]')).not.toBeVisible();
|
|
|
|
} finally {
|
|
await deleteSession(request, sessionA.id);
|
|
await deleteSession(request, sessionB.id);
|
|
}
|
|
});
|
|
|
|
test('last session via settings menu works', async ({ page, request }) => {
|
|
// Create two sessions
|
|
const sessionA = await createTmuxSession(request);
|
|
const sessionB = await createTmuxSession(request);
|
|
|
|
try {
|
|
// Start at home
|
|
await page.goto('/');
|
|
await expect(page.locator('a[href^="/session/"]').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Navigate A -> home -> B
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
await goBackToHome(page);
|
|
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Open settings menu
|
|
const settingsBtn = page.locator('button[aria-label="Session settings"]');
|
|
await expect(settingsBtn).toBeVisible();
|
|
await settingsBtn.click();
|
|
|
|
// Click "Last session" in the menu
|
|
const lastSessionMenuItem = page.locator('button:has-text("Last session")');
|
|
await expect(lastSessionMenuItem).toBeVisible({ timeout: 5000 });
|
|
await lastSessionMenuItem.click();
|
|
|
|
// Should navigate to A
|
|
await waitForTmuxSession(page);
|
|
await expect(page).toHaveURL(new RegExp(`/session/${encodeURIComponent(sessionA.id)}`));
|
|
|
|
} finally {
|
|
await deleteSession(request, sessionA.id);
|
|
await deleteSession(request, sessionB.id);
|
|
}
|
|
});
|
|
|
|
// Skip: tmux session deletion/validation has timing issues in tests
|
|
test.skip('deleting last session clears the reference', async ({ page, request }) => {
|
|
// Create two sessions
|
|
const sessionA = await createTmuxSession(request);
|
|
const sessionB = await createTmuxSession(request);
|
|
|
|
try {
|
|
// Start at home
|
|
await page.goto('/');
|
|
await expect(page.locator('a[href^="/session/"]').first()).toBeVisible({ timeout: 10000 });
|
|
|
|
// Navigate A -> home -> B
|
|
await clickSessionById(page, sessionA.id);
|
|
await waitForTmuxSession(page);
|
|
await goBackToHome(page);
|
|
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button should be visible
|
|
const lastSessionBtn = page.locator('button[title="Go to last session"]');
|
|
await expect(lastSessionBtn).toBeVisible({ timeout: 5000 });
|
|
|
|
// Delete session A via API
|
|
await deleteSession(request, sessionA.id);
|
|
|
|
// Go home and wait for session A to disappear from the list
|
|
await goBackToHome(page);
|
|
await expect(page.locator(`a[href="/session/${encodeURIComponent(sessionA.id)}"]`)).not.toBeVisible({ timeout: 10000 });
|
|
|
|
// Go back to B
|
|
await clickSessionById(page, sessionB.id);
|
|
await waitForTmuxSession(page);
|
|
|
|
// Last session button should NOT be visible (A was deleted)
|
|
await expect(lastSessionBtn).not.toBeVisible({ timeout: 5000 });
|
|
|
|
} finally {
|
|
await deleteSession(request, sessionB.id);
|
|
}
|
|
});
|
|
});
|