feat(history): establish Telegram SaaS platform foundation

This commit is contained in:
wmq
2026-07-27 23:03:00 +08:00
parent cf0eed46c3
commit cd06d049fe
92 changed files with 5608 additions and 3749 deletions
+61
View File
@@ -0,0 +1,61 @@
import { expect, test } from '@playwright/test'
const account = process.env.E2E_ACCOUNT || 'e2e-1785008967@local.test'
const password = process.env.E2E_PASSWORD || 'Lingxi-E2E-2026!'
const conversationId = process.env.LIVE_CONVERSATION_ID
test.describe('真实收件箱 AI 交互', () => {
test.skip(!conversationId, '需要 LIVE_CONVERSATION_ID')
test('AI 回复可以关闭、重新打开并采用到输入框', async ({ page }) => {
await page.goto('/login')
await page.locator('input[autocomplete="username"]').fill(account)
await page.locator('input[autocomplete="current-password"]').fill(password)
await page.locator('button[type="submit"]').click()
await expect(page).toHaveURL(/\/dashboard$/)
await page.goto(`/inbox?c=${encodeURIComponent(conversationId!)}`)
await expect(page.getByText('Eternity', { exact: true }).first()).toBeVisible()
await expect(page.getByText('@jsjzlkj', { exact: true }).first()).toBeVisible()
const eternityAvatar = page.locator('img[alt="Eternity"]').first()
await expect(eternityAvatar).toBeVisible()
await expect.poll(() => eternityAvatar.evaluate((image: HTMLImageElement) => image.naturalWidth)).toBeGreaterThan(0)
const insightBar = page.getByTestId('conversation-insight-bar')
await expect(insightBar).toBeVisible()
await expect(insightBar).toContainText('意向')
await expect(insightBar).toContainText(/积极|偏积极|中性|消极|愤怒|不满/)
const messageList = page.getByTestId('message-list')
await expect(messageList).not.toContainText('2026-07-27T')
await expect.poll(() => messageList.evaluate((element) =>
Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop),
)).toBeLessThanOrEqual(4)
const customerBubble = page.locator('[data-testid="message-bubble"][data-sender="customer"]')
await expect(customerBubble.first()).toHaveClass(/animate-message-in-left/)
const agentBubble = page.locator('[data-testid="message-bubble"][data-sender="agent"]')
await expect(agentBubble.first()).toHaveClass(/animate-message-in-right/)
await page.getByTitle('客户洞察').click()
await expect(page.getByText('成交概率', { exact: true })).toBeVisible()
await expect(page.getByText('分析可信度', { exact: true })).toBeVisible()
await expect(page.getByText('判断依据', { exact: true })).toBeVisible()
if ((page.viewportSize()?.width || 1280) < 768) {
await page.getByRole('button', { name: '返回会话' }).click()
}
await expect(page.getByText('AI 智能回复', { exact: true })).toBeVisible()
await page.getByTitle('关闭 AI 智能回复').click()
await expect(page.getByRole('button', { name: 'AI 智能回复', exact: true })).toBeVisible()
await expect(page.getByTitle('关闭 AI 智能回复')).toHaveCount(0)
await page.getByRole('button', { name: 'AI 智能回复', exact: true }).click()
await page.getByRole('button', { name: '生成回复建议', exact: true }).click()
const adoptButtons = page.getByRole('button', { name: '采用', exact: true })
await expect(adoptButtons).toHaveCount(3, { timeout: 30_000 })
await adoptButtons.first().click()
const composer = page.getByPlaceholder('输入回复内容,Enter 发送,Shift+Enter 换行')
await expect(composer).not.toHaveValue('')
const adoptedReply = await composer.inputValue()
expect(adoptedReply).toMatch(/[A-Za-z]/)
expect(adoptedReply).not.toMatch(/[\u3400-\u9fff]/u)
await expect(page.getByRole('button', { name: 'AI 智能回复', exact: true })).toBeVisible()
})
})
+68
View File
@@ -0,0 +1,68 @@
import { expect, test, type Page } from '@playwright/test'
const account = process.env.E2E_ACCOUNT || 'e2e-1785008967@local.test'
const password = process.env.E2E_PASSWORD || 'Lingxi-E2E-2026!'
async function login(page: Page, loginAccount = account, loginPassword = password) {
await page.goto('/login')
await page.locator('input[autocomplete="username"]').fill(loginAccount)
await page.locator('input[autocomplete="current-password"]').fill(loginPassword)
await page.locator('button[type="submit"]').click()
await expect(page).toHaveURL(/\/dashboard$/)
}
test('Telegram 手机号验证码和二步密码登录向导', async ({ page, request }, testInfo) => {
const suffix = `${Date.now()}-${testInfo.project.name}`
const loginAccount = `channel-e2e-${suffix}@local.test`
const loginPassword = 'Lingxi-Channel-2026!'
const response = await request.post('/api/auth/register', {
data: { name: 'Channel E2E', account: loginAccount, password: loginPassword, company: 'Channel E2E', industry: '跨境电商', teamSize: '1-10人' },
})
expect(response.ok()).toBeTruthy()
await login(page, loginAccount, loginPassword)
await page.goto('/channels')
await page.getByRole('button', { name: '连接', exact: true }).click()
await expect(page.getByPlaceholder('+8613800138000')).toBeVisible()
await expect(page.getByText('Telegram 验证码')).toHaveCount(0)
await page.screenshot({ path: testInfo.outputPath('telegram-login-wizard.png'), fullPage: true })
})
test('登录与核心工作台视觉链路', async ({ page }, testInfo) => {
await login(page)
await expect(page).toHaveURL(/\/dashboard$/)
await page.screenshot({ path: testInfo.outputPath('dashboard.png'), fullPage: true })
await page.goto('/channels')
await expect(page.getByText('Telegram', { exact: true }).first()).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('channels.png'), fullPage: true })
await page.goto('/inbox')
await page.getByTestId('conversation-item').first().click()
await expect(page.locator('input[type="file"]')).toHaveCount(4)
await page.screenshot({ path: testInfo.outputPath('inbox.png'), fullPage: true })
})
test('知识文件真实上传解析', async ({ page }, testInfo) => {
await login(page)
await page.goto('/knowledge')
await page.getByRole('button', { name: '上传文件' }).click()
await page.locator('input[type="file"]').setInputFiles({
name: `visual-e2e-${testInfo.project.name}.txt`,
mimeType: 'text/plain',
buffer: Buffer.from('灵犀跨境视觉 E2E 知识解析测试。'),
})
await page.getByRole('button', { name: '上传并解析' }).click()
await expect(page.getByText(/知识文件解析完成/)).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('knowledge-uploaded.png'), fullPage: true })
})
test('团队邀请和支付页不显示虚假成功', async ({ page }, testInfo) => {
await login(page)
await page.goto('/team')
await expect(page.getByRole('button', { name: '邀请成员' })).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('team.png'), fullPage: true })
await page.goto('/billing')
await expect(page.getByText('Stripe Checkout', { exact: false })).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('billing.png'), fullPage: true })
})