Files
spiceflow/e2e/tests/last-session.spec.ts
2026-01-21 14:26:30 -05:00

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);
}
});
});