mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-10 22:41:02 +00:00
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:
parent
50886a01cb
commit
3f78115277
14 changed files with 75 additions and 81 deletions
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue