feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径

This commit is contained in:
yuzhiran-dev
2026-05-18 09:48:51 +08:00
commit 11bb86854c
277 changed files with 37755 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import { test, expect } from '@playwright/test';
test.describe('后台管理页面', () => {
const adminPages = [
'/admin',
'/admin/login',
'/admin/users',
'/admin/courses',
'/admin/prompts',
'/admin/contents',
'/admin/tools',
'/admin/orders',
'/admin/comments',
'/admin/enterprise',
];
for (const path of adminPages) {
test.describe(`${path}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(path);
});
test('页面能正常加载', async ({ page }) => {
await expect(page.locator('body')).toBeVisible();
await expect(page.locator('body')).not.toHaveText(/500/);
await expect(page.locator('body')).not.toHaveText(/Application Error/);
await expect(page.locator('body')).not.toHaveText(/Cannot read properties/);
});
test('页面有内容展示', async ({ page }) => {
const anyHeading = page.locator('h1, h2, h3, h4, .text-2xl, .text-3xl');
const headingCount = await anyHeading.count();
const bodyText = await page.locator('body').textContent() || '';
expect(headingCount > 0 || bodyText.length > 50).toBeTruthy();
});
});
}
test.describe('管理后台编辑页面', () => {
const editPages = [
'/admin/courses/new',
'/admin/prompts/new',
'/admin/contents/new',
'/admin/tools/new',
];
for (const path of editPages) {
test(`${path} 渲染正常`, async ({ page }) => {
await page.goto(path);
await expect(page.locator('body')).toBeVisible();
await expect(page.locator('body')).not.toHaveText(/500/);
});
}
});
});
+20
View File
@@ -0,0 +1,20 @@
import { test, expect } from '@playwright/test';
test.describe('暗黑模式切换', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('主题切换按钮存在', async ({ page }) => {
const toggleBtn = page.locator('button').filter({ has: page.locator('svg') }).first();
await expect(toggleBtn).toBeVisible();
});
test('点击主题切换按钮无错误', async ({ page }) => {
const toggleBtn = page.locator('button').filter({ has: page.locator('svg') }).first();
await expect(toggleBtn).toBeEnabled();
await toggleBtn.click();
await page.waitForTimeout(500);
await expect(page.locator('html')).toBeAttached();
});
});
+119
View File
@@ -0,0 +1,119 @@
import { test, expect } from '@playwright/test';
test.describe('功能页面', () => {
test.describe('沙盒 (/sandbox)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/sandbox');
});
test('页面渲染正常', async ({ page }) => {
await expect(page.locator('h1')).toBeVisible();
});
test('模型选择器存在', async ({ page }) => {
const select = page.locator('select');
const count = await select.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test('消息输入框存在', async ({ page }) => {
await expect(page.locator('textarea, input[type="text"]').first()).toBeVisible();
});
test('发送按钮存在', async ({ page }) => {
await expect(page.getByText('发送').first()).toBeVisible();
});
test('初始欢迎消息展示', async ({ page }) => {
await expect(page.getByText(/你好!我是宇之然 AI 助手/)).toBeVisible();
});
test('对比实验室链接存在', async ({ page }) => {
const compareLink = page.getByText('对比实验室').or(page.getByText('模型对比'));
const count = await page.getByText('对比实验室').count();
if (count > 0) {
const link = page.getByText('对比实验室').first();
await expect(link).toBeVisible();
const href = await link.getAttribute('href');
if (href) {
await expect(link).toHaveAttribute('href', /\/sandbox\/compare|\/compare/);
}
}
});
});
test.describe('对比实验室 (/sandbox/compare)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/sandbox/compare');
});
test('页面渲染正常', async ({ page }) => {
await expect(page.locator('h1')).toBeVisible();
});
});
test.describe('提示词工坊 (/prompts/workshop)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/prompts/workshop');
});
test('页面渲染正常', async ({ page }) => {
await expect(page.locator('h1')).toBeVisible();
});
});
test.describe('搜索 (/search)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/search');
});
test('搜索输入框存在', async ({ page }) => {
await expect(page.locator('input[type="text"], input[name="q"]').first()).toBeVisible();
});
test('搜索按钮存在', async ({ page }) => {
await expect(page.getByText('搜索').first()).toBeVisible();
});
});
test.describe('发现页及子页', () => {
test('/discover 渲染', async ({ page }) => {
await page.goto('/discover');
await expect(page.locator('h1')).toBeVisible();
const dailyLink = page.getByText('每日精选').first();
if (await dailyLink.isVisible()) {
await expect(dailyLink).toHaveAttribute('href', '/discover/daily');
}
});
test('/discover/daily 渲染', async ({ page }) => {
await page.goto('/discover/daily');
await expect(page.locator('h1')).toBeVisible();
});
test('/discover/hot 渲染', async ({ page }) => {
await page.goto('/discover/hot');
await expect(page.locator('h1')).toBeVisible();
});
test('/discover/tools 渲染', async ({ page }) => {
await page.goto('/discover/tools');
await expect(page.locator('h1')).toBeVisible();
});
});
test.describe('认证页 (/auth)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/auth');
});
test('登录表单可见', async ({ page }) => {
await expect(page.getByText('登录').first()).toBeVisible();
});
test('注册表单可切换', async ({ page }) => {
await page.goto('/auth?tab=register');
await expect(page.getByText('注册').first()).toBeVisible();
});
});
});
+67
View File
@@ -0,0 +1,67 @@
import { test, expect } from '@playwright/test';
test.describe('首页 (/)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('页面标题正确', async ({ page }) => {
await expect(page).toHaveTitle(/宇之然 AI/);
});
test('Hero 区域渲染', async ({ page }) => {
await expect(page.getByText('免费 AI 知识社区')).toBeVisible();
await expect(page.getByText('让每个人').first()).toBeVisible();
await expect(page.getByText('都能用好 AI').first()).toBeVisible();
await expect(page.getByText('宇之然 AI 是面向大众的 AI 工具与知识社区')).toBeVisible();
});
test('Hero 按钮存在并指向正确链接', async ({ page }) => {
const startBtn = page.getByText('开始探索').first();
await expect(startBtn).toBeVisible();
await expect(startBtn).toHaveAttribute('href', '/courses');
const registerBtn = page.getByText('免费注册').first();
await expect(registerBtn).toBeVisible();
await expect(registerBtn).toHaveAttribute('href', '/auth?tab=register');
});
test('统计数据区域', async ({ page }) => {
await expect(page.getByText('50+').first()).toBeVisible();
await expect(page.getByText('200+').first()).toBeVisible();
await expect(page.getByText('30+').first()).toBeVisible();
await expect(page.getByText('10,000+').first()).toBeVisible();
});
test('特性区域', async ({ page }) => {
await expect(page.getByText('为什么选择宇之然?')).toBeVisible();
await expect(page.getByText('分领域指南').first()).toBeVisible();
await expect(page.getByText('AI 沙盒实战').first()).toBeVisible();
await expect(page.getByText('提示词库').first()).toBeVisible();
await expect(page.getByText('持续更新').first()).toBeVisible();
});
test('热门专题区域', async ({ page }) => {
await expect(page.getByText('热门专题')).toBeVisible();
await expect(page.getByText('AI 通识:零基础入门')).toBeVisible();
await expect(page.getByText('提示词工程从入门到精通')).toBeVisible();
await expect(page.getByText('用 AI 提升 10 倍办公效率')).toBeVisible();
});
test('AI 沙盒预览区域', async ({ page }) => {
await expect(page.getByText('AI 沙盒').first()).toBeVisible();
await expect(page.getByText('在线体验 AI 对话,边学边练')).toBeVisible();
const sandboxLink = page.getByText('打开沙盒').first();
await expect(sandboxLink).toHaveAttribute('href', '/sandbox');
});
test('CTA 区域', async ({ page }) => {
await expect(page.getByText('准备好开启 AI 之旅了吗?')).toBeVisible();
await expect(page.getByText('立即注册,免费探索所有内容')).toBeVisible();
});
test('点击 Hero 开始探索进入课程页', async ({ page }) => {
await page.getByText('开始探索').first().click();
await expect(page).toHaveURL(/\/courses/);
});
});
+42
View File
@@ -0,0 +1,42 @@
import { test, expect } from '@playwright/test';
test.describe('列表页面', () => {
const listingPages = [
{ path: '/courses', title: '专题', skeleton: true },
{ path: '/prompts', title: '提示词', skeleton: true },
{ path: '/community', title: '社区', skeleton: true },
{ path: '/tools', title: 'AI 工具', skeleton: true },
{ path: '/contents', title: '文章', skeleton: true },
{ path: '/models', title: '模型', skeleton: false },
{ path: '/circles', title: '圈子', skeleton: false },
{ path: '/discover', title: '发现', skeleton: false },
{ path: '/dashboard', title: '控制台', skeleton: true },
];
for (const { path, title, skeleton } of listingPages) {
test.describe(`${path}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(path);
});
test('页面标题元素存在', async ({ page }) => {
const anyHeading = page.locator('h1, h2, h3, h4, [class*="text-2xl"], [class*="text-3xl"]');
const count = await anyHeading.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test('页面能正常加载', async ({ page }) => {
await expect(page.locator('body')).not.toHaveText(/500/);
await expect(page.locator('body')).not.toHaveText(/Application Error/);
});
if (skeleton) {
test('加载骨架屏显示', async ({ page }) => {
const skeletons = page.locator('[class*="animate-pulse"], [class*="skeleton"]');
const count = await skeletons.count();
expect(count).toBeGreaterThanOrEqual(1);
});
}
});
}
});
+112
View File
@@ -0,0 +1,112 @@
import { test, expect } from '@playwright/test';
test.describe('导航与页脚', () => {
test.describe('Header 导航', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('品牌名称可见', async ({ page }) => {
await expect(page.getByText('宇之然').first()).toBeVisible();
});
test('主导航链接都可见', async ({ page }) => {
const navLinks = ['首页', '专题', '沙盒', '模型', '提示词', '文章', 'AI 工具', '社区'];
for (const link of navLinks) {
const el = page.getByRole('link', { name: link, exact: true });
const count = await el.count();
if (count > 1) {
await expect(el.first()).toBeVisible();
} else if (count === 1) {
await expect(el).toBeVisible();
}
}
});
test('搜索框可见', async ({ page }) => {
const searchInput = page.locator('input[placeholder="搜索..."]');
await expect(searchInput).toBeVisible();
});
test('主题切换按钮可见', async ({ page }) => {
const themeToggle = page.locator('button').filter({ has: page.locator('svg') }).first();
await expect(themeToggle).toBeVisible();
});
test('登录/注册按钮可见', async ({ page }) => {
await expect(page.getByText('登录').first()).toBeVisible();
await expect(page.getByText('注册').first()).toBeVisible();
});
test('点击导航链接可跳转', async ({ page }) => {
const navMap = [
{ label: '专题', url: '/courses' },
{ label: '沙盒', url: '/sandbox' },
{ label: '模型', url: '/models' },
{ label: '提示词', url: '/prompts' },
{ label: 'AI 工具', url: '/tools' },
{ label: '社区', url: '/community' },
{ label: '文章', url: '/contents' },
];
for (const { label, url } of navMap) {
await page.getByRole('link', { name: label }).first().click();
await expect(page).toHaveURL(new RegExp(url));
await page.goBack();
}
});
});
test.describe('Footer 页脚', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('品牌信息展示', async ({ page }) => {
await expect(page.getByText('让每个人都能用好 AI').first()).toBeVisible();
const footerHeading = page.locator('footer h3');
await expect(footerHeading).toContainText('宇之然 AI');
});
test('导航链接', async ({ page }) => {
await expect(page.getByText('专题').last()).toBeVisible();
await expect(page.getByText('提示词库').last()).toBeVisible();
await expect(page.getByText('AI 工具').last()).toBeVisible();
});
test('关于链接', async ({ page }) => {
await expect(page.getByText('关于我们')).toBeVisible();
await expect(page.getByText('隐私政策')).toBeVisible();
await expect(page.getByText('服务协议').first()).toBeVisible();
await expect(page.getByText('AI 服务协议')).toBeVisible();
});
test('联系方式', async ({ page }) => {
await expect(page.getByText('contact@yuzhiran.com').first()).toBeVisible();
const footerInfo = page.locator('footer li, footer p').filter({ hasText: '北京宇之然科技中心' });
await expect(footerInfo.first()).toBeVisible();
});
test('ICP 备案号链接', async ({ page }) => {
const icpEl = page.locator('footer a').filter({ hasText: /ICP备案|ICP 备案/ });
await expect(icpEl).toBeVisible();
await expect(icpEl).toHaveAttribute('href', 'https://beian.miit.gov.cn/');
});
test('Footer 链接点击跳转正确', async ({ page }) => {
const footerLinks: [string, RegExp][] = [
['专题', /\/courses/],
['关于我们', /\/about/],
['隐私政策', /\/privacy/],
['服务协议', /\/terms/],
['AI 服务协议', /\/ai-agreement/],
];
for (const [text, urlPattern] of footerLinks) {
const link = page.locator('footer a').filter({ hasText: text });
await expect(link.first()).toBeVisible();
await link.first().click();
await expect(page).toHaveURL(urlPattern);
await page.goBack();
}
});
});
});
+16
View File
@@ -0,0 +1,16 @@
import { test, expect } from '@playwright/test';
test.describe('404 页面', () => {
test('访问不存在页面显示 404', async ({ page }) => {
await page.goto('/this-page-does-not-exist-12345');
await expect(page.locator('body')).toBeVisible();
const has404 = page.getByText('404').first();
const hasNotFound = page.getByText(/找不到|不存在|页面/i).first();
const is404 = await has404.isVisible().catch(() => false);
const isNotFound = await hasNotFound.isVisible().catch(() => false);
expect(is404 || isNotFound).toBe(true);
});
});
+41
View File
@@ -0,0 +1,41 @@
import { test, expect } from '@playwright/test';
test.describe('静态页面', () => {
const pages = [
{ path: '/about', title: '关于宇之然', checks: ['我们的使命', '北京宇之然科技中心'] },
{ path: '/privacy', title: '隐私政策', checks: ['隐私政策'] },
{ path: '/terms', title: '服务协议', checks: ['服务协议'] },
{ path: '/ai-agreement', title: 'AI 服务协议', checks: ['AI 服务协议'] },
];
for (const { path, title, checks } of pages) {
test.describe(`${path}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(path);
});
test('页面标题正确', async ({ page }) => {
await expect(page).toHaveTitle(new RegExp(title));
});
test('页面标题元素存在', async ({ page }) => {
const h1 = page.locator('h1');
await expect(h1).toContainText(title);
});
for (const check of checks) {
test(`包含 "${check}" 内容`, async ({ page }) => {
const el = page.getByText(check, { exact: true });
const count = await el.count();
if (count > 1) {
await expect(el.first()).toBeVisible();
} else if (count === 0) {
await expect(page.locator('main, article, section').filter({ hasText: check })).toBeVisible();
} else {
await expect(el).toBeVisible();
}
});
}
});
}
});
+32
View File
@@ -0,0 +1,32 @@
import { test, expect } from '@playwright/test';
test.describe('用户相关页面', () => {
const userPages = [
{ path: '/my', title: '我的' },
{ path: '/my/learning', title: '学习进度' },
{ path: '/my/favorites', title: '收藏夹' },
{ path: '/my/member', title: '会员中心' },
{ path: '/my/settings', title: '设置' },
{ path: '/my/dashboard', title: '数据看板' },
{ path: '/notifications', title: '通知' },
];
for (const { path, title } of userPages) {
test.describe(`${path}`, () => {
test.beforeEach(async ({ page }) => {
await page.goto(path);
});
test('页面标题元素存在', async ({ page }) => {
const anyHeading = page.locator('h1, h2, h3, h4, [class*="text-2xl"], [class*="text-3xl"]');
const count = await anyHeading.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test('无错误展示', async ({ page }) => {
await expect(page.locator('body')).not.toHaveText(/500/);
await expect(page.locator('body')).not.toHaveText(/Application Error/);
});
});
}
});