test(web): align e2e with redesigned experience

Update real-service E2E assertions for the current landing and dashboard flows, and make settings card headings distinct from their page headings.

Made-with: Proma

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-09-07 17:09:43 +08:00
parent 50886a01cb
commit 3f78115277
14 changed files with 75 additions and 81 deletions

View file

@ -8,12 +8,13 @@ test.describe('Dashboard Shell (Real API)', () => {
await registerSession(page, testInfo)
})
test('renders account summary and quick links', async ({ page }) => {
test('renders account navigation and overview links', async ({ page }) => {
await page.goto('/dashboard')
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
await expect(page.getByText('Account Information')).toBeVisible()
await expect(page.getByRole('link', { name: 'View API Tokens' })).toBeVisible()
await expect(page.getByRole('link', { name: 'View My Skills' }).first()).toBeVisible()
const sidebar = page.getByRole('complementary')
await expect(sidebar.getByRole('link', { name: 'Profile', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'My Skills', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'API Tokens', exact: true })).toBeVisible()
await expect(page.getByText('View and manage all your published skills')).toBeVisible()
})
})

View file

@ -9,9 +9,9 @@ test.describe('Landing Navigation (Real API)', () => {
test('submits the hero search to the search page', async ({ page }) => {
await page.goto('/')
await expect(page.getByRole('heading', { name: 'Discover & Share AI Skills' })).toBeVisible()
await expect(page.getByRole('heading', { name: '把团队的专业能力,沉淀成 Agent 可用的技能' })).toBeVisible()
const searchInput = page.getByPlaceholder('Search skills...')
const searchInput = page.getByPlaceholder('搜索技能...')
await searchInput.fill('agent ops')
await searchInput.press('Enter')

View file

@ -1,80 +1,69 @@
import { expect, test } from '@playwright/test'
import { setEnglishLocale } from './helpers/auth-fixtures'
test.describe('Landing Quick Start CLI Tab (Real API)', () => {
test.describe('Landing access methods (Real API)', () => {
test.beforeEach(async ({ page }) => {
await setEnglishLocale(page)
})
test('renders three peer tabs and exposes the CLI install command', async ({ page }) => {
test('renders three access methods and exposes current CLI commands', async ({ page }) => {
await page.goto('/')
const agentTab = page.getByRole('button', { name: 'I am Agent', exact: true })
const humanTab = page.getByRole('button', { name: 'I am Human', exact: true })
const cliTab = page.getByRole('button', { name: 'CLI', exact: true })
const agentMode = page.getByRole('button', { name: /Agent 自动接入/ })
const cliMode = page.getByRole('button', { name: /CLI 命令行/ })
const webMode = page.getByRole('button', { name: /Web 界面/ })
await expect(agentTab).toBeVisible()
await expect(humanTab).toBeVisible()
await expect(cliTab).toBeVisible()
await expect(agentMode).toBeVisible()
await expect(cliMode).toBeVisible()
await expect(webMode).toBeVisible()
await expect(agentMode).toHaveAttribute('aria-pressed', 'true')
await expect(agentTab).toHaveAttribute('aria-pressed', 'true')
await cliMode.click()
await expect(cliMode).toHaveAttribute('aria-pressed', 'true')
await expect(agentMode).toHaveAttribute('aria-pressed', 'false')
await expect(webMode).toHaveAttribute('aria-pressed', 'false')
await cliTab.click()
await expect(cliTab).toHaveAttribute('aria-pressed', 'true')
await expect(agentTab).toHaveAttribute('aria-pressed', 'false')
await expect(humanTab).toHaveAttribute('aria-pressed', 'false')
await expect(
page.getByText('Install the SkillHub CLI locally to run skillhub install for skills.'),
).toBeVisible()
await expect(page.getByText('npm i -g @astron-team/skillhub', { exact: true })).toBeVisible()
await expect(page.getByText('npx -y @astron-team/skillhub@0.1.11 --version', { exact: true })).toBeVisible()
await expect(page.getByText(/npx -y @astron-team\/skillhub@0\.1\.11 search weather/)).toBeVisible()
await expect(page.getByText(/npx -y @astron-team\/skillhub@0\.1\.11 install @global\/weather/)).toBeVisible()
await expect(page.getByRole('link', { name: 'CLI 文档' })).toHaveAttribute(
'href',
'https://github.com/iflytek/skillhub/tree/main/cli',
)
})
test('agent and human tabs expose the current SkillHub guidance', async ({ page }) => {
test('agent views expose Registry configuration and implicit discovery', async ({ page }) => {
await page.goto('/')
const agentTab = page.getByRole('button', { name: 'I am Agent', exact: true })
const humanTab = page.getByRole('button', { name: 'I am Human', exact: true })
const registryTab = page.getByRole('tab', { name: 'Registry 配置' })
const discoveryTab = page.getByRole('tab', { name: '隐式发现' })
await expect(registryTab).toHaveAttribute('aria-selected', 'true')
await expect(page.getByText(/registry\/skill\.md/).first()).toBeVisible()
await discoveryTab.click()
await expect(discoveryTab).toHaveAttribute('aria-selected', 'true')
await expect(page.getByText('检索 SkillHub Registry')).toBeVisible()
await expect(page.getByText('匹配 @global/weather · v1.3.0')).toBeVisible()
await expect(
page.getByText(
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
{ exact: true },
),
).toBeVisible()
const guideResponse = await page.request.get('/install/skillhub.md')
expect(guideResponse.status()).toBe(200)
const guide = await guideResponse.text()
expect(guide).toContain('http://127.0.0.1:3000')
expect(guideResponse.headers()['cache-control']).toContain('no-cache')
const legacyGuideResponse = await page.request.get('/registry/skill.md')
expect(legacyGuideResponse.status()).toBe(200)
expect(await legacyGuideResponse.text()).toBe(guide)
const hostileHostResponse = await page.request.get('/install/skillhub.md', {
headers: { Host: 'attacker.example' },
})
expect(hostileHostResponse.status()).toBe(403)
const extensionHostResponse = await page.request.get('/install/skillhub.md', {
headers: { Host: 'chrome-extension:evil;echo_injected' },
})
expect(extensionHostResponse.status()).toBe(400)
await humanTab.click()
await expect(humanTab).toHaveAttribute('aria-pressed', 'true')
await expect(
page.getByText(
'npx @astron-team/skillhub@latest search <keyword> --registry http://127.0.0.1:3000',
{ exact: true },
),
).toBeVisible()
await agentTab.click()
await expect(agentTab).toHaveAttribute('aria-pressed', 'true')
await expect(
page.getByText(
'Connect SkillHub using http://127.0.0.1:3000/install/skillhub.md',
{ exact: true },
),
).toBeVisible()
})
})

View file

@ -20,6 +20,7 @@ test.describe('Route Guards (Real API)', () => {
await page.goto('/dashboard')
await expect(page).toHaveURL('/dashboard')
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
await expect(page.getByRole('link', { name: 'My Skills', exact: true })).toBeVisible()
await expect(page.getByRole('link', { name: 'Profile', exact: true })).toBeVisible()
})
})

View file

@ -15,11 +15,11 @@ test.describe('Settings Pages (Real API)', () => {
await expect(page.getByRole('heading', { name: 'Profile Settings' })).toBeVisible()
})
test('navigates to reset-password page from profile settings', async ({ page }) => {
test('navigates to security settings from profile settings', async ({ page }) => {
await page.goto('/settings/profile')
await page.getByRole('button', { name: 'Reset Password' }).click()
await expect(page).toHaveURL('/reset-password')
await expect(page.getByRole('heading', { name: 'Reset Password' })).toBeVisible()
await expect(page).toHaveURL('/settings/security')
await expect(page.getByRole('heading', { name: 'Security Settings' })).toBeVisible()
})
test('shows validation when current password is missing', async ({ page }) => {

View file

@ -168,7 +168,7 @@ test.describe('Light and dark theme', () => {
await page.reload()
await expect(page.locator('html')).toHaveClass(/dark/)
await expect(page.getByRole('switch', { name: 'Dark theme' })).toHaveAttribute('aria-checked', 'true')
await expect(page.getByRole('heading', { name: 'SkillHub', exact: true })).toBeVisible()
await expect(page.getByRole('heading', { name: '把团队的专业能力,沉淀成 Agent 可用的技能' })).toBeVisible()
await expect.poll(() => page.evaluate(() => (
window as Window & { __themeAtFirstReactContent?: boolean }
).__themeAtFirstReactContent)).toBe(true)

View file

@ -8,18 +8,12 @@ test.describe('User ID Display', () => {
await registerSession(page, testInfo)
})
test('shows user ID in dashboard account card', async ({ page }) => {
test('shows the signed-in account identity in the dashboard sidebar', async ({ page }) => {
await page.goto('/dashboard')
await expect(page.getByText('Account Information')).toBeVisible()
const userIdText = page.getByText('User ID', { exact: false })
await expect(userIdText).toBeVisible()
// The dashboard renders "User ID: <value>" in a single element.
// Verify the value is not empty by checking the text content is longer than just the label.
const content = await userIdText.textContent()
const valueAfterLabel = content?.replace(/^.*User ID[:\s]*/i, '').trim() ?? ''
expect(valueAfterLabel.length).toBeGreaterThan(0)
const accountSummary = page.locator('aside').locator('.mb-4').first()
await expect(accountSummary).toBeVisible()
await expect(accountSummary).not.toHaveText(/^\s*$/)
})
test('shows user ID on profile settings page', async ({ page }) => {

View file

@ -1,7 +1,7 @@
{
"name": "skillhub-web",
"private": true,
"version": "0.1.14",
"version": "0.1.15",
"type": "module",
"packageManager": "pnpm@10.33.0",
"pnpm": {

View file

@ -50,7 +50,7 @@ export function NotificationPreferenceForm() {
return (
<Card className="glass-strong">
<CardHeader>
<CardTitle>{t('notification.preferences.title')}</CardTitle>
<CardTitle>{t('notification.preferences.channelsTitle')}</CardTitle>
<CardDescription>{t('notification.preferences.description')}</CardDescription>
</CardHeader>
<CardContent>

View file

@ -820,7 +820,8 @@
"partiallyAppliedTitle": "Partially Applied",
"partiallyAppliedDescription": "Some changes were applied immediately. Others are pending admin review.",
"noChanges": "No changes to save.",
"userId": "User ID"
"userId": "User ID",
"detailsTitle": "Profile Details"
},
"security": {
"title": "Security Settings",
@ -837,7 +838,8 @@
"unavailableTitle": "Password changes are unavailable for this account.",
"unavailableDescription": "This account signs in through an external identity provider or has no local password credential.",
"submitting": "Submitting...",
"submit": "Update Password"
"submit": "Update Password",
"passwordTitle": "Password"
},
"accounts": {
"initiateTitle": "Initiate Account Merge",
@ -1702,7 +1704,8 @@
"promotion": "Promotion Notifications",
"promotionDesc": "Notify on promotion request, approval, or rejection",
"report": "Report Notifications",
"reportDesc": "Notify on report submission or resolution"
"reportDesc": "Notify on report submission or resolution",
"channelsTitle": "Notification Channels"
}
},
"adminNamespaces": {

View file

@ -885,7 +885,8 @@
"partiallyAppliedTitle": "Применено частично",
"partiallyAppliedDescription": "Часть изменений применена сразу. Остальные ожидают проверки администратором.",
"noChanges": "Нет изменений для сохранения.",
"userId": "ID пользователя"
"userId": "ID пользователя",
"detailsTitle": "Данные профиля"
},
"security": {
"title": "Настройки безопасности",
@ -902,7 +903,8 @@
"unavailableTitle": "Смена пароля недоступна для этой учётной записи.",
"unavailableDescription": "Эта учётная запись входит через внешнего поставщика идентификации или не имеет локальных учётных данных пароля.",
"submitting": "Отправка...",
"submit": "Обновить пароль"
"submit": "Обновить пароль",
"passwordTitle": "Пароль"
},
"accounts": {
"initiateTitle": "Начать объединение учётных записей",
@ -1477,7 +1479,8 @@
"promotion": "Уведомления о продвижении",
"promotionDesc": "Уведомлять о заявке, одобрении или отклонении продвижения",
"report": "Уведомления о жалобах",
"reportDesc": "Уведомлять о подаче или разрешении жалобы"
"reportDesc": "Уведомлять о подаче или разрешении жалобы",
"channelsTitle": "Каналы уведомлений"
}
},
"nsReviews": {

View file

@ -336,7 +336,7 @@
"viewSubscriptions": "查看我的订阅",
"mySkillsTitle": "我的技能",
"openMySkills": "查看我的技能",
"mySkillsPreviewDescription": "展示最近的 5 个技能,可进入详情或前往\u201C我的技能\u201D查看全部。",
"mySkillsPreviewDescription": "展示最近的 5 个技能,可进入详情或前往“我的技能”查看全部。",
"mySkillsPreviewEmpty": "你还没有发布任何技能",
"credentials": "访问凭证",
"openTokens": "查看 API Tokens",
@ -820,7 +820,8 @@
"partiallyAppliedTitle": "部分已生效",
"partiallyAppliedDescription": "部分修改已立即生效,其余修改正在等待管理员审核。",
"noChanges": "没有需要保存的修改。",
"userId": "用户 ID"
"userId": "用户 ID",
"detailsTitle": "个人资料"
},
"security": {
"title": "安全设置",
@ -837,7 +838,8 @@
"unavailableTitle": "此账号暂不可修改密码。",
"unavailableDescription": "此账号通过外部身份提供方登录,或尚未配置本地密码凭据。",
"submitting": "提交中...",
"submit": "更新密码"
"submit": "更新密码",
"passwordTitle": "密码"
},
"accounts": {
"initiateTitle": "发起账号合并",
@ -1701,7 +1703,8 @@
"promotion": "提升通知",
"promotionDesc": "提升申请提交、通过或拒绝时通知",
"report": "举报通知",
"reportDesc": "举报提交或处理完成时通知"
"reportDesc": "举报提交或处理完成时通知",
"channelsTitle": "通知类型"
}
},
"adminNamespaces": {

View file

@ -181,7 +181,7 @@ export function ProfileSettingsPage() {
<Card className="glass-strong">
<CardHeader className="flex flex-row items-center justify-between">
<div>
<CardTitle>{t('profile.title')}</CardTitle>
<CardTitle>{t('profile.detailsTitle')}</CardTitle>
<CardDescription>{t('profile.subtitle')}</CardDescription>
</div>
{!isEditing ? (

View file

@ -92,7 +92,7 @@ export function SecuritySettingsPage() {
<DashboardPageHeader title={t('security.title')} subtitle={t('security.subtitle')} />
<Card className="glass-strong">
<CardHeader>
<CardTitle>{t('security.title')}</CardTitle>
<CardTitle>{t('security.passwordTitle')}</CardTitle>
<CardDescription>{t('security.subtitle')}</CardDescription>
</CardHeader>
<CardContent>