mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-02 12:22:22 +00:00
* 🧪 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>
97 lines
3.8 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|