import { test, expect } from '@playwright/test'; import { createServer, type ViteDevServer } from 'vite'; import { resolve } from 'node:path'; let server: ViteDevServer; let url: string; test.use({ channel: process.env.MAKELORE_LAYOUT_BROWSER_CHANNEL, video: 'off' }); test.beforeAll(async () => { server = await createServer({ configFile: false, cacheDir: 'node_modules/.vite-teacher-discussion', resolve: { alias: { '@': resolve('src') } }, esbuild: { jsx: 'automatic' }, server: { host: '127.0.0.1', port: 0 }, plugins: [{ name: 'isolated-discussion-test', enforce: 'pre', load(id) { if (id.endsWith('/src/lib/coding-teacher.ts')) return "export { teacherApi, teacherTopicsPath, legacyTopicBase } from '/tests/e2e/fixtures/teacher-discussion-api.ts';"; if (id.endsWith('/src/stores/auth.ts')) return 'export const useAuthStore = selector => selector({user:{userId:"fixture"}});'; }, configureServer(vite) { vite.middlewares.use((req, res, next) => { if (!req.url?.startsWith('/?') && req.url !== '/') return next(); res.setHeader('Content-Type', 'text/html'); res.end('
'); }); }, }] }); await server.listen(); url = server.resolvedUrls!.local[0]; }); test.afterAll(async () => { await server?.close(); }); for (const width of [319, 508]) { test(`rich replies keep code, tables and images inside the panel at ${width}px`, async ({ page }) => { await page.setViewportSize({ width: 1180, height: 800 }); const errors: string[] = []; page.on('pageerror', error => errors.push(error.message)); await page.route('https://example.com/teacher-preview.png', route => route.fulfill({ contentType: 'image/svg+xml', body: 'Flybot project preview', })); await page.goto(`${url}?kind=rich-text&width=${width}`); const body = page.getByTestId('teacher-reply'); await expect(body.getByRole('heading', { name: '项目建议' })).toHaveCount(1); await expect(body.getByRole('table')).toHaveCount(1); await expect(body.locator('.katex')).toHaveCount(1); await expect(body.getByRole('img', { name: '项目截图' })).toBeVisible(); await expect.poll(() => body.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true); await expect.poll(() => page.locator('.consultation-messages').evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true); const raw = page.getByText('查看收到的原始内容'); await expect(raw.locator('..')).not.toHaveAttribute('open'); await raw.click(); await expect(raw.locator('..').locator('pre')).toContainText('"tool"'); const input = page.getByRole('textbox', { name: '向智能体提问' }); await expect(input).toBeVisible(); await page.locator('.consultation-messages').evaluate(el => { el.scrollTop = 0; }); await page.screenshot({ path: test.info().outputPath(`rich-reply-${width}.png`) }); expect(errors).toEqual([]); }); } for (const width of [319, 508]) for (const kind of ['ordinary', 'ideas', 'structure', 'flow', 'comparison']) { test(`${kind} pins the current tool and composer at ${width}px`, async ({ page }) => { await page.setViewportSize({ width: 1180, height: 800 }); const errors: string[] = []; page.on('pageerror', error => errors.push(error.message)); await page.addInitScript(() => localStorage.setItem( 'makelore-consultation-draft:fixture:fixture:friend', JSON.stringify({ text: '', references: [] }), )); await page.goto(`${url}?kind=${kind}&width=${width}`); const input = page.getByRole('textbox', { name: '向智能体提问' }); await expect(input).toBeEnabled(); await expect(page.locator('form')).not.toHaveText(/^0/); await expect(page.getByText('查看旧版朋友草稿')).toHaveCount(0); const tool = page.getByTestId('teacher-discussion'); if (kind === 'ordinary') { await expect(tool).toHaveCount(0); await input.fill('保留我的草稿'); await page.getByRole('button', { name: '有什么好玩的想法?' }).click(); await expect(page.getByText('好,已经把你的补充放在上面了。我们可以接着想。')).toBeVisible(); await expect(input).toHaveValue('保留我的草稿'); } else { await expect(tool).toHaveAttribute('data-kind', kind); const before = await tool.boundingBox(); const composer = await input.boundingBox(); await page.locator('.consultation-messages').hover(); await page.mouse.wheel(0, -1500); await expect.poll(async () => (await tool.boundingBox())?.y).toBe(before!.y); expect((await input.boundingBox())?.y).toBe(composer!.y); expect(await tool.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true); expect(composer!.y + composer!.height).toBeLessThan(800); await input.fill('它还喜欢和我踢球'); await page.getByRole('button', { name: '提问', exact: true }).click(); await expect(page.getByRole('status')).toContainText('正在梳理'); await expect(page.getByText('好,已经把你的补充放在上面了。我们可以接着想。')).toBeVisible(); await expect(tool).toHaveCount(1); await expect(tool).toHaveAttribute('data-kind', kind); if (kind === 'ideas') { await page.getByRole('button', { name: '把想法理一理' }).click(); await expect(tool).toHaveAttribute('data-kind', 'structure'); await page.getByRole('button', { name: '回去补充想法' }).click(); await expect(tool).toHaveAttribute('data-kind', 'ideas'); await expect(tool.getByText('它还喜欢和我踢球', { exact: true })).toHaveCount(1); } await input.fill('先留下下一句'); await page.getByRole('button', { name: '先这些', exact: true }).click(); await expect(tool).toHaveAttribute('data-collapsed', 'true'); await expect(input).toHaveValue('先留下下一句'); await page.getByRole('button', { name: '接着改', exact: true }).click(); await expect(tool).toHaveAttribute('data-collapsed', 'false'); } await page.screenshot({ path: test.info().outputPath(`${kind}-${width}.png`) }); expect(errors).toEqual([]); }); }