LibreChat/e2e/specs/mock/isolation.spec.ts
Teresa Blanco b45e4aeae5
🎭 feat: Add Credential-Free Playwright Smoke Suite with a Local Mock LLM (#13472)
* 🧪 feat: add e2e playwright tests

* 🧪 feat: Add Playwright Recording Harness

* test: fix mock playwright config

* test: harden mock e2e environment

* test: preserve mock dotenv secrets

* test: harden mock isolation setup

* ci: cache mock e2e builds

* test: harden e2e cache and recorder checks

* test: preserve data-provider exports in oauth route test

* test: isolate mock auth logout state

* test: allow isolated logout smoke setup

* test: prepare logout smoke auth via api

* test: isolate oauth route module mock

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2026-06-02 16:36:39 -04:00

97 lines
3.8 KiB
TypeScript

import { expect, test } from '@playwright/test';
import type { Browser, Page } from '@playwright/test';
import type { User } from '../../types';
import { MOCK_ENDPOINTS, NEW_CHAT_PATH, selectMockEndpoint, sendMessage } from './helpers';
import { getSecondaryE2EUser } from '../../setup/users.mock';
import cleanupUser from '../../setup/cleanupUser';
const A_PRIVATE_MARKER = 'A-private-conversation-marker';
async function register(page: Page, user: User) {
await page.getByRole('link', { name: 'Sign up' }).click();
await page.getByLabel('Full name').fill(user.name);
await page.getByLabel('Email').fill(user.email);
await page.getByTestId('password').fill(user.password);
await page.getByTestId('confirm_password').fill(user.password);
await page.getByLabel('Submit registration').click();
}
async function registrationErrorIsVisible(page: Page) {
return page
.getByTestId('registration-error')
.isVisible({ timeout: 500 })
.catch(() => false);
}
async function registerSecondaryUser(page: Page, user: User) {
await page.goto('/', { timeout: 10000 });
await page.waitForURL(/\/login/, { timeout: 10000 });
await register(page, user);
try {
await page.waitForURL(/\/c\/new/, { timeout: 10000 });
} catch (error) {
if (!(await registrationErrorIsVisible(page))) {
throw error;
}
await cleanupUser(user);
await page.goto('/', { timeout: 10000 });
await page.waitForURL(/\/login/, { timeout: 10000 });
await register(page, user);
await page.waitForURL(/\/c\/new/, { timeout: 10000 });
}
}
/** Register the secondary user in a throwaway context, then log in within `page`. */
async function ensureSecondaryUser(browser: Browser, page: Page, user: User, baseURL: string) {
const setupContext = await browser.newContext({ storageState: undefined, baseURL });
const setupPage = await setupContext.newPage();
try {
await registerSecondaryUser(setupPage, user);
} finally {
await setupContext.close();
}
await page.goto('/login', { timeout: 10000 });
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByTestId('login-button').click();
await page.waitForURL(/\/c\/new/, { timeout: 10000 });
}
test.describe('user isolation', () => {
test('user B cannot see user A conversations', async ({ page, browser, baseURL }) => {
test.setTimeout(90000);
if (typeof baseURL !== 'string') {
throw new Error('baseURL must be configured for mock isolation tests');
}
// User A (authenticated via storageState) creates a private conversation.
await page.goto(NEW_CHAT_PATH, { timeout: 10000 });
await selectMockEndpoint(page, MOCK_ENDPOINTS[0]);
await sendMessage(page, A_PRIVATE_MARKER);
await expect(page.getByText(A_PRIVATE_MARKER)).toBeVisible();
await expect(page).toHaveURL(/\/c\/[0-9a-fA-F-]{36}$/);
const conversationAUrl = page.url();
// User B in a fresh, unauthenticated context.
const contextB = await browser.newContext({ storageState: undefined, baseURL });
const pageB = await contextB.newPage();
try {
await ensureSecondaryUser(browser, pageB, getSecondaryE2EUser(), baseURL);
// (a) Sidebar list does not expose A's conversation.
await pageB.goto(NEW_CHAT_PATH, { timeout: 10000 });
await expect(pageB.getByRole('textbox', { name: 'Message input' })).toBeVisible();
await expect(pageB.getByText(A_PRIVATE_MARKER)).toHaveCount(0);
// (b) Direct navigation to A's conversation does not reveal its content.
await pageB.goto(conversationAUrl, { timeout: 10000 });
await expect(pageB.getByRole('textbox', { name: 'Message input' })).toBeVisible();
await expect(pageB.getByText(A_PRIVATE_MARKER)).toHaveCount(0);
} finally {
await contextB.close();
}
});
});