EternalAI/e2e/navigation.spec.js

216 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { test, expect } = require('@playwright/test');
const bcrypt = require('bcryptjs');
const { cleanDatabase, disconnect, prisma } = require('./fixtures/database');
test.describe('导航与可访问性', () => {
test.beforeAll(async () => {
await cleanDatabase();
});
test.afterAll(async () => {
await cleanDatabase();
await disconnect();
});
test('首页显示两张卡片', async ({ page }) => {
await page.goto('/');
await expect(page.locator('#landing')).toBeVisible();
// 两张卡片open-characters 和 open-distill
const cards = page.locator('[data-action="open-characters"], [data-action="open-distill"]');
await expect(cards).toHaveCount(2, { timeout: 5000 });
});
test('底部 tabBar 三个 tab 可见', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.tab-bar')).toBeVisible();
await expect(page.locator('[data-tab-action="tab-home"]')).toBeVisible();
await expect(page.locator('[data-tab-action="tab-distill"]')).toBeVisible();
await expect(page.locator('[data-tab-action="tab-mine"]')).toBeVisible();
});
test('tabBar 切换视图', async ({ page }) => {
await page.goto('/');
// 点击蒸馏前任 tab
await page.locator('[data-tab-action="tab-distill"]').click();
await expect(page.locator('#distill')).toBeVisible();
// 点击首页 tab
await page.locator('[data-tab-action="tab-home"]').click();
await expect(page.locator('#landing')).toBeVisible();
// 点击我的 tab未登录应跳转到 auth
await page.locator('[data-tab-action="tab-mine"]').click();
await expect(page.locator('#auth')).toBeVisible();
});
test('首页卡片点击进入对应视图', async ({ page }) => {
await page.goto('/');
// 点击蒸馏前任卡片
await page.locator('[data-action="open-distill"]').first().click();
await expect(page.locator('#distill')).toBeVisible();
// 返回首页
await page.locator('[data-tab-action="tab-home"]').click();
await expect(page.locator('#landing')).toBeVisible();
// 点击「我的 XXX」卡片未登录进入 auth
await page.locator('[data-action="open-characters"]').first().click();
await expect(page.locator('#auth')).toBeVisible();
});
test('关于页面可访问', async ({ page }) => {
await page.goto('/');
await page.locator('[data-action="open-about"]').first().click();
await expect(page.locator('#about')).toBeVisible();
});
test('创作者入驻页面可访问', async ({ page }) => {
await page.goto('/');
await page.locator('[data-action="open-onboarding"]').first().click();
await expect(page.locator('#onboarding')).toBeVisible();
});
test('FAQ 折叠展开', async ({ page }) => {
await page.goto('/');
await page.locator('[data-action="open-about"]').first().click();
await expect(page.locator('#about')).toBeVisible();
// 点击第一个 FAQ 按钮(.faq-q非 .faq-item__question
const faqButton = page.locator('.faq-q').first();
await faqButton.click();
// 验证 aria-expanded 变为 true
await expect(faqButton).toHaveAttribute('aria-expanded', 'true');
// 再次点击折叠
await faqButton.click();
await expect(faqButton).toHaveAttribute('aria-expanded', 'false');
});
test('返回按钮基于历史记录导航', async ({ page }) => {
await page.goto('/');
// 导航路径: landing → about → 返回
await page.locator('[data-action="open-about"]').first().click();
await expect(page.locator('#about')).toBeVisible();
// about 视图使用 data-action="back"
await page.locator('#about [data-action="back"]').click();
await expect(page.locator('#landing')).toBeVisible();
});
test('跳过链接a11y存在且可用', async ({ page }) => {
await page.goto('/');
const skipLink = page.locator('.skip-link');
// 跳过链接应存在
await expect(skipLink).toHaveCount(1);
});
test('tabBar 有正确的 ARIA 角色', async ({ page }) => {
await page.goto('/');
const tabbar = page.locator('.tab-bar');
await expect(tabbar).toHaveAttribute('role', 'tablist');
// tab-bar 内的按钮应有 role="tab"
const tabs = page.locator('.tab-bar [data-tab-action]');
const count = await tabs.count();
expect(count).toBe(3);
for (let i = 0; i < count; i++) {
await expect(tabs.nth(i)).toHaveAttribute('role', 'tab');
}
});
test('视图切换时 aria-live 播报', async ({ page }) => {
await page.goto('/');
// #sr-announce 是 aria-live 区域role="status" aria-live="polite"
const liveRegion = page.locator('#sr-announce');
await expect(liveRegion).toHaveAttribute('aria-live', 'polite');
await expect(liveRegion).toHaveAttribute('role', 'status');
// 切换视图后 live region 应有内容
await page.locator('[data-action="open-about"]').first().click();
await expect(liveRegion).not.toHaveText('');
const liveText = await liveRegion.textContent();
expect(liveText.length).toBeGreaterThan(0);
});
test('角色卡片支持键盘操作', async ({ page }) => {
// 创建非创作者用户 + 角色
const bcrypt = require('bcryptjs');
const user = await prisma.user.create({
data: {
account: 'e2e_keyboard_user',
password: bcrypt.hashSync('Test123456', 10),
isCreator: false,
libraryName: '键盘测试库',
},
});
await prisma.role.create({
data: {
creatorId: user.id,
displayName: '键盘测试角色',
gender: 'female',
personality: '测试',
background: '测试',
speechStyle: '测试',
greeting: '测试',
desc: '键盘测试',
price: 9.9,
status: 'running',
reviewStatus: 'synced',
},
});
// 登录非创作者(登录后自动进入角色库)
await page.goto('/');
await page.locator('[data-action="open-characters"]').first().click();
await expect(page.locator('#auth')).toBeVisible();
await page.fill('[data-form="login"] [name="account"]', 'e2e_keyboard_user');
await page.fill('[data-form="login"] [name="password"]', 'Test123456');
await page.locator('[data-form="login"] button[type="submit"]').click();
await expect(page.locator('#role-library')).toBeVisible({ timeout: 5000 });
// 聚焦角色卡片role-card 有 tabindex="0"
const roleCard = page.locator('#role-list .role-card').first();
await roleCard.focus();
// 按 Enter 应进入详情
await page.keyboard.press('Enter');
await expect(page.locator('#role-detail')).toBeVisible({ timeout: 5000 });
// 清理
await prisma.role.deleteMany();
await prisma.user.deleteMany();
});
test('表单 label 与 input 关联', async ({ page }) => {
await page.goto('/');
await page.locator('[data-action="open-characters"]').first().click();
await expect(page.locator('#auth')).toBeVisible();
// 检查登录表单的 input 存在
const accountInput = page.locator('[data-form="login"] [name="account"]');
await expect(accountInput).toBeVisible();
// 检查是否有对应的 label包裹式 label 包含账号相关文案)
const labelText = await page.locator('label').filter({ hasText: /账号|用户名|Account/i }).count();
expect(labelText).toBeGreaterThan(0);
});
test('focus-visible 样式存在', async ({ page }) => {
await page.goto('/');
// Tab 到第一个可聚焦元素
await page.keyboard.press('Tab');
// 聚焦元素应有可见的焦点样式(:focus-visible 使用 outline
const focused = await page.evaluate(() => {
const el = document.activeElement;
if (!el) return false;
const styles = window.getComputedStyle(el);
return styles.outlineStyle !== 'none' || styles.boxShadow !== 'none';
});
expect(focused).toBe(true);
});
});