From 1d7daeb1507670c3005c1d73d21ba71b7941398f Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:00:00 +0800 Subject: [PATCH] test(web): align namespace flows with unified space navigation Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- web/e2e/batch-import-members.spec.ts | 4 +- web/e2e/dashboard-routes.spec.ts | 4 +- web/e2e/my-namespaces-data.spec.ts | 56 ++++++++++++------- web/e2e/namespace-members-data.spec.ts | 7 ++- .../namespace-review-detail-access.spec.ts | 12 +++- web/e2e/namespace-reviews-data.spec.ts | 8 ++- web/e2e/namespace-skill-pagination.spec.ts | 40 ++++++++++--- 7 files changed, 95 insertions(+), 36 deletions(-) diff --git a/web/e2e/batch-import-members.spec.ts b/web/e2e/batch-import-members.spec.ts index 72cb7d4b..add14671 100644 --- a/web/e2e/batch-import-members.spec.ts +++ b/web/e2e/batch-import-members.spec.ts @@ -50,7 +50,9 @@ test.describe('Batch Import Members (Real API)', () => { } await page.goto(`/dashboard/namespaces/${namespace.slug}/members`) - await expect(page.getByRole('heading', { name: 'Member Management' })).toBeVisible() + await expect(page.getByRole('heading', { name: namespace.displayName, exact: true })).toBeVisible() + await expect(page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Members', exact: true })).toHaveAttribute('aria-current', 'page') const batchButton = page.getByRole('button', { name: 'Batch Import' }) await expect(batchButton).toBeVisible() diff --git a/web/e2e/dashboard-routes.spec.ts b/web/e2e/dashboard-routes.spec.ts index 57604d96..24caf056 100644 --- a/web/e2e/dashboard-routes.spec.ts +++ b/web/e2e/dashboard-routes.spec.ts @@ -43,6 +43,8 @@ test.describe('Dashboard Routes (Real API)', () => { await expect(page.getByRole('heading', { name: 'Namespace not found' })).toBeVisible() await page.goto('/dashboard/namespaces/e2e-missing-namespace/reviews') - await expect(page.getByRole('heading', { name: 'Namespace Reviews' })).toBeVisible() + const reviewSection = page.getByRole('region', { name: 'Namespace Reviews' }) + await expect(reviewSection).toBeVisible() + await expect(reviewSection.getByText('No review records', { exact: true })).toBeVisible() }) }) diff --git a/web/e2e/my-namespaces-data.spec.ts b/web/e2e/my-namespaces-data.spec.ts index 28284603..24d5798e 100644 --- a/web/e2e/my-namespaces-data.spec.ts +++ b/web/e2e/my-namespaces-data.spec.ts @@ -1,13 +1,31 @@ -import { expect, test } from '@playwright/test' +import { expect, test, type Page } from '@playwright/test' import { setEnglishLocale } from './helpers/auth-fixtures' import { E2eTestDataBuilder } from './helpers/test-data-builder' +import { loginWithCredentials } from './helpers/session' + +async function findNamespaceCard(page: Page, slug: string) { + await page.goto('/dashboard/namespaces') + await expect(page.getByRole('heading', { name: 'My Namespaces' })).toBeVisible() + const card = page.getByTestId(`namespace-card-${slug}`) + const next = page.getByRole('button', { name: 'Next', exact: true }) + const firstCard = page.locator('[data-testid^="namespace-card-"]').first() + await expect(firstCard).toBeVisible() + while (!(await card.isVisible()) && await next.count() > 0 && await next.isEnabled()) { + const previousId = await firstCard.getAttribute('data-testid') + await next.click() + await expect(firstCard).not.toHaveAttribute('data-testid', previousId!) + } + await expect(card.getByText(`@${slug}`, { exact: true })).toBeVisible() + return card +} test.describe('My Namespaces Data (Real API)', () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page }, testInfo) => { await setEnglishLocale(page) - await page.context().setExtraHTTPHeaders({ - 'X-Mock-User-Id': 'local-admin', - }) + await loginWithCredentials(page, { + username: process.env.E2E_ADMIN_USERNAME ?? process.env.BOOTSTRAP_ADMIN_USERNAME ?? 'admin', + password: process.env.E2E_ADMIN_PASSWORD ?? process.env.BOOTSTRAP_ADMIN_PASSWORD ?? 'ChangeMe!2026', + }, testInfo) }) test('shows namespace created by request helper', async ({ page }, testInfo) => { @@ -15,11 +33,8 @@ test.describe('My Namespaces Data (Real API)', () => { await builder.init() try { - const namespace = await builder.ensureWritableNamespace() - await page.goto('/dashboard/namespaces') - - await expect(page.getByRole('heading', { name: 'My Namespaces' })).toBeVisible() - await expect(page.getByText(`@${namespace.slug}`)).toBeVisible() + const namespace = await builder.createNamespace('e2e-list') + await findNamespaceCard(page, namespace.slug) } finally { await builder.cleanup() } @@ -32,11 +47,12 @@ test.describe('My Namespaces Data (Real API)', () => { try { const namespace = await builder.createNamespace('e2e-delete') - await page.goto('/dashboard/namespaces') - - const namespaceCard = page.getByTestId(`namespace-card-${namespace.slug}`) - await expect(namespaceCard.getByText(`@${namespace.slug}`)).toBeVisible() + const namespaceCard = await findNamespaceCard(page, namespace.slug) + await namespaceCard.getByRole('button', { name: 'Enter space', exact: true }).click() + await page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Settings', exact: true }).click() + await expect(page.getByRole('heading', { name: 'Space lifecycle', exact: true })).toBeVisible() await page.getByTestId(`delete-namespace-${namespace.slug}`).click() await expect(page.getByTestId('namespace-action-dialog-delete')).toBeVisible() @@ -48,7 +64,8 @@ test.describe('My Namespaces Data (Real API)', () => { const deleteResponse = await deleteResponsePromise expect(deleteResponse.ok()).toBeTruthy() - await expect(page.getByText(`@${namespace.slug}`)).toHaveCount(0) + await expect(page).toHaveURL(/\/dashboard\/namespaces$/) + await expect(page.getByTestId(`namespace-card-${namespace.slug}`)).toHaveCount(0) } finally { await builder.cleanup() } @@ -66,10 +83,11 @@ test.describe('My Namespaces Data (Real API)', () => { description: 'Prevents namespace deletion during Playwright validation', }) - await page.goto('/dashboard/namespaces') - - const namespaceCard = page.getByTestId(`namespace-card-${namespace.slug}`) - await expect(namespaceCard.getByText(`@${namespace.slug}`)).toBeVisible() + const namespaceCard = await findNamespaceCard(page, namespace.slug) + await namespaceCard.getByRole('button', { name: 'Enter space', exact: true }).click() + await page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Settings', exact: true }).click() + await expect(page.getByRole('heading', { name: 'Space lifecycle', exact: true })).toBeVisible() await expect(page.getByTestId(`delete-namespace-${namespace.slug}`)).toHaveCount(0) } finally { await builder.cleanup() diff --git a/web/e2e/namespace-members-data.spec.ts b/web/e2e/namespace-members-data.spec.ts index c3ac91c7..5f2ea9ff 100644 --- a/web/e2e/namespace-members-data.spec.ts +++ b/web/e2e/namespace-members-data.spec.ts @@ -18,8 +18,11 @@ test.describe('Namespace Members Data (Real API)', () => { await page.goto(`/dashboard/namespaces/${namespace.slug}/members`) - await expect(page.getByRole('heading', { name: 'Member Management' })).toBeVisible() - await expect(page.getByText(`@${namespace.slug}`).first()).toBeVisible() + await expect(page.getByRole('heading', { name: namespace.displayName, exact: true })).toBeVisible() + await expect(page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Members', exact: true })).toHaveAttribute('aria-current', 'page') + await expect(page.getByText(`@${namespace.slug}`, { exact: true })).toBeVisible() + await expect(page.getByRole('table')).toBeVisible() } finally { await builder.cleanup() } diff --git a/web/e2e/namespace-review-detail-access.spec.ts b/web/e2e/namespace-review-detail-access.spec.ts index 1daac06d..98c6279b 100644 --- a/web/e2e/namespace-review-detail-access.spec.ts +++ b/web/e2e/namespace-review-detail-access.spec.ts @@ -16,7 +16,10 @@ test.describe('Namespace Review Detail Access (Real API)', () => { await page.goto(`/dashboard/namespaces/${seeded.namespace.slug}/reviews`) - await expect(page.getByRole('heading', { name: 'Namespace Reviews' })).toBeVisible() + await expect(page.getByRole('heading', { name: seeded.namespace.displayName, exact: true })).toBeVisible() + await expect(page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Reviews', exact: true })).toHaveAttribute('aria-current', 'page') + await expect(page.getByRole('region', { name: 'Namespace Reviews' })).toBeVisible() await expect(page.getByText(`${seeded.namespace.slug}/${seeded.skill.slug}`)).toBeVisible() await page.getByRole('link', { name: 'Open review' }).first().click() @@ -37,7 +40,12 @@ test.describe('Namespace Review Detail Access (Real API)', () => { await page.goto('/dashboard/reviews') await expect(page).toHaveURL(/\/dashboard\/namespaces\/.+\/reviews$/) - await expect(page.getByRole('heading', { name: 'Namespace Reviews' })).toBeVisible() + const selectedSlug = decodeURIComponent(new URL(page.url()).pathname.split('/')[3]) + await expect(page.getByRole('heading', { level: 1 })).toBeVisible() + await expect(page.getByText(`@${selectedSlug}`, { exact: true })).toBeVisible() + await expect(page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Reviews', exact: true })).toHaveAttribute('aria-current', 'page') + await expect(page.getByRole('region', { name: 'Namespace Reviews' })).toBeVisible() } finally { await seeded?.cleanup() } diff --git a/web/e2e/namespace-reviews-data.spec.ts b/web/e2e/namespace-reviews-data.spec.ts index b2cad5c6..9e929a2c 100644 --- a/web/e2e/namespace-reviews-data.spec.ts +++ b/web/e2e/namespace-reviews-data.spec.ts @@ -15,8 +15,12 @@ test.describe('Namespace Reviews Data (Real API)', () => { seeded = await createNamespaceReviewData(browser, page, testInfo) await page.goto(`/dashboard/namespaces/${seeded.namespace.slug}/reviews`) - await expect(page.getByRole('heading', { name: 'Namespace Reviews' })).toBeVisible() - await expect(page.getByText(`Review tasks for ${seeded.namespace.displayName}`)).toBeVisible() + await expect(page.getByRole('heading', { name: seeded.namespace.displayName, exact: true })).toBeVisible() + await expect(page.getByRole('navigation', { name: 'Space management' }) + .getByRole('link', { name: 'Reviews', exact: true })).toHaveAttribute('aria-current', 'page') + await expect(page.getByRole('region', { name: 'Namespace Reviews' })).toBeVisible() + await expect(page.getByText(`${seeded.namespace.slug}/${seeded.skill.slug}`, { exact: true })).toBeVisible() + await expect(page.getByRole('combobox', { name: 'Review status' })).toHaveText('Pending') } finally { await seeded?.cleanup() } diff --git a/web/e2e/namespace-skill-pagination.spec.ts b/web/e2e/namespace-skill-pagination.spec.ts index 64e61e09..52c56838 100644 --- a/web/e2e/namespace-skill-pagination.spec.ts +++ b/web/e2e/namespace-skill-pagination.spec.ts @@ -1,12 +1,15 @@ import { expect, test } from '@playwright/test' import { setEnglishLocale } from './helpers/auth-fixtures' -import { registerSession } from './helpers/session' +import { loginWithCredentials } from './helpers/session' import { E2eTestDataBuilder } from './helpers/test-data-builder' test.describe('Namespace Skill List Pagination (Real API)', () => { test.beforeEach(async ({ page }, testInfo) => { await setEnglishLocale(page) - await registerSession(page, testInfo) + await loginWithCredentials(page, { + username: process.env.E2E_ADMIN_USERNAME ?? process.env.BOOTSTRAP_ADMIN_USERNAME ?? 'admin', + password: process.env.E2E_ADMIN_PASSWORD ?? process.env.BOOTSTRAP_ADMIN_PASSWORD ?? 'ChangeMe!2026', + }, testInfo) }) test('shows namespace page with skills and no pagination when under 20 skills', async ({ page }, testInfo) => { @@ -14,17 +17,28 @@ test.describe('Namespace Skill List Pagination (Real API)', () => { await builder.init() try { - const namespace = await builder.ensureWritableNamespace() + const namespace = await builder.createNamespace('e2e-pagination-small') await builder.publishSkill(namespace.slug) + const searchResponsePromise = page.waitForResponse((response) => { + const url = new URL(response.url()) + return url.pathname === '/api/web/skills' + && url.searchParams.get('namespace') === namespace.slug + }) await page.goto(`/space/${namespace.slug}`) + const searchResponse = await searchResponsePromise + expect(searchResponse.ok()).toBeTruthy() + const search: { data: { total: number } } = await searchResponse.json() + expect(search.data.total).toBeLessThanOrEqual(20) await expect(page.getByText(`@${namespace.slug}`).first()).toBeVisible() await expect(page.getByRole('heading', { name: 'Skills', exact: true })).toBeVisible() - // Verify pagination controls do not appear when there are fewer than 20 skills - await expect(page.getByRole('button', { name: 'Previous' })).toHaveCount(0) - await expect(page.getByRole('button', { name: 'Next' })).toHaveCount(0) + // Fresh namespace keeps this fixture independent of previous tests. + const skillList = page.getByRole('heading', { name: 'Skills', exact: true }).locator('..') + await expect(skillList.getByRole('navigation', { name: 'Pagination', exact: true })).toHaveCount(0) + await expect(skillList.getByRole('button', { name: 'Previous', exact: true })).toHaveCount(0) + await expect(skillList.getByRole('button', { name: 'Next', exact: true })).toHaveCount(0) } finally { await builder.cleanup() } @@ -35,7 +49,7 @@ test.describe('Namespace Skill List Pagination (Real API)', () => { await builder.init() try { - const namespace = await builder.ensureWritableNamespace() + const namespace = await builder.createNamespace('e2e-pagination-many') // Build a fake skill list to simulate >20 skills without hitting rate limits. const fakeSkill = (i: number) => ({ @@ -77,8 +91,10 @@ test.describe('Namespace Skill List Pagination (Real API)', () => { await expect(page.getByRole('heading', { name: 'Skills', exact: true })).toBeVisible() // Verify pagination controls appear - const previousButton = page.getByRole('button', { name: 'Previous' }).first() - const nextButton = page.getByRole('button', { name: 'Next' }).first() + const skillList = page.getByRole('heading', { name: 'Skills', exact: true }).locator('..') + const pagination = skillList.getByRole('navigation', { name: 'Pagination', exact: true }) + const previousButton = pagination.getByRole('button', { name: 'Previous', exact: true }) + const nextButton = pagination.getByRole('button', { name: 'Next', exact: true }) await expect(previousButton).toBeVisible() await expect(nextButton).toBeVisible() @@ -87,16 +103,22 @@ test.describe('Namespace Skill List Pagination (Real API)', () => { await expect(previousButton).toBeDisabled() await expect(nextButton).toBeEnabled() + await expect(skillList.getByText('Fake Skill 0', { exact: true })).toBeVisible() // Navigate to second page await nextButton.click() // Second page: Previous should be enabled await expect(previousButton).toBeEnabled() + await expect(nextButton).toBeDisabled() + await expect(skillList.getByText('Fake Skill 20', { exact: true })).toBeVisible() + await expect(skillList.getByText('Fake Skill 0', { exact: true })).toHaveCount(0) + await expect(pagination.getByRole('button', { name: 'Go to page 2', exact: true })).toHaveAttribute('aria-current', 'page') // Navigate back to first page await previousButton.click() await expect(previousButton).toBeDisabled() await expect(nextButton).toBeEnabled() + await expect(skillList.getByText('Fake Skill 0', { exact: true })).toBeVisible() } finally { await builder.cleanup() }