mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-10 03:27:54 +00:00
fix(e2e): address review feedback on admin-users-userid-column tests
- Fix race condition: use Promise.all for goto + waitForResponse - Remove all waitForTimeout calls, use explicit assertions/waitForResponse - Assert clipboard content equals the actual userId (not just non-empty) - Fix unused variable (userIdText) that would fail lint --max-warnings 0 - Trigger real search via button click instead of just filling input - Add status filter test to cover the filter path - Add comment explaining mock-profile approach for admin session
This commit is contained in:
parent
a2d08b76c1
commit
d98fe8d802
1 changed files with 70 additions and 72 deletions
|
|
@ -1,135 +1,133 @@
|
|||
import { expect, test } from '@playwright/test'
|
||||
import { setEnglishLocale } from './helpers/auth-fixtures'
|
||||
|
||||
// This spec verifies the admin UI surfaces userId next to username and supports one-click copy.
|
||||
// We rely on the `X-Mock-User-Id: local-admin` mock profile (the same approach used by other
|
||||
// admin specs such as `my-namespaces-data.spec.ts`). The mock profile activates a deterministic
|
||||
// admin session without exercising the full SSO path; treat results here as evidence of the
|
||||
// admin UI behavior, not of the real-service auth path.
|
||||
test.describe('Admin Users - UserId Column', () => {
|
||||
test.beforeEach(async ({ page, context }) => {
|
||||
await setEnglishLocale(page)
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
|
||||
|
||||
// Use mock admin user
|
||||
await page.context().setExtraHTTPHeaders({
|
||||
'X-Mock-User-Id': 'local-admin'
|
||||
'X-Mock-User-Id': 'local-admin',
|
||||
})
|
||||
|
||||
await page.goto('/admin/users')
|
||||
await page.waitForResponse(resp => resp.url().includes('/api/v1/admin/users') && resp.status() === 200)
|
||||
await Promise.all([
|
||||
page.waitForResponse(
|
||||
(resp) => resp.url().includes('/api/v1/admin/users') && resp.status() === 200,
|
||||
),
|
||||
page.goto('/admin/users'),
|
||||
])
|
||||
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('userId column appears after username column', async ({ page }) => {
|
||||
// Wait for table to load
|
||||
await expect(page.getByRole('columnheader', { name: 'Username' })).toBeVisible()
|
||||
|
||||
// Check userId column exists
|
||||
const userIdHeader = page.getByRole('columnheader', { name: 'User ID' })
|
||||
await expect(userIdHeader).toBeVisible()
|
||||
|
||||
// Verify column order: Username should come before User ID
|
||||
const headers = page.getByRole('columnheader')
|
||||
const headerTexts = await headers.allTextContents()
|
||||
|
||||
const usernameIndex = headerTexts.findIndex(text => text.includes('Username'))
|
||||
const userIdIndex = headerTexts.findIndex(text => text.includes('User ID'))
|
||||
const usernameIndex = headerTexts.findIndex((text) => text.includes('Username'))
|
||||
const userIdIndex = headerTexts.findIndex((text) => text.includes('User ID'))
|
||||
|
||||
expect(usernameIndex).toBeGreaterThanOrEqual(0)
|
||||
expect(userIdIndex).toBeGreaterThan(usernameIndex)
|
||||
expect(userIdIndex).toBe(usernameIndex + 1)
|
||||
})
|
||||
|
||||
test('userId values are displayed in table rows', async ({ page }) => {
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
|
||||
// Wait for at least one row to load
|
||||
const firstRow = page.getByRole('row').nth(1)
|
||||
await expect(firstRow).toBeVisible()
|
||||
|
||||
// Check that userId cells contain non-empty values
|
||||
const userIdCells = page.getByRole('cell').filter({ hasText: /^[a-zA-Z0-9-]+$/ })
|
||||
const count = await userIdCells.count()
|
||||
expect(count).toBeGreaterThan(0)
|
||||
// The userId cell is the 2nd column (index 1). It should contain a non-empty identifier.
|
||||
const userIdCell = firstRow.getByRole('cell').nth(1)
|
||||
const userIdText = (await userIdCell.textContent())?.trim() ?? ''
|
||||
expect(userIdText.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('copy button exists for each userId', async ({ page }) => {
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
test('copy button exists for each userId row', async ({ page }) => {
|
||||
const rowCount = await page.getByRole('row').count()
|
||||
test.skip(rowCount <= 1, 'No data rows available to assert copy buttons')
|
||||
|
||||
// Wait for rows to load
|
||||
const rows = page.getByRole('row')
|
||||
const rowCount = await rows.count()
|
||||
|
||||
if (rowCount <= 1) {
|
||||
test.skip()
|
||||
}
|
||||
|
||||
// Check for copy buttons (they should have aria-label or be buttons)
|
||||
const copyButtons = page.getByRole('button').filter({ hasText: /copy/i })
|
||||
const copyButtons = page.getByRole('button', { name: /copy/i })
|
||||
const buttonCount = await copyButtons.count()
|
||||
|
||||
expect(buttonCount).toBeGreaterThan(0)
|
||||
// At least one copy button per row body should be present.
|
||||
expect(buttonCount).toBeGreaterThanOrEqual(rowCount - 1)
|
||||
})
|
||||
|
||||
test('clicking copy button copies userId to clipboard', async ({ page }) => {
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
|
||||
// Find first copy button in the table
|
||||
test('clicking copy button copies the row userId to clipboard', async ({ page }) => {
|
||||
const firstRow = page.getByRole('row').nth(1)
|
||||
await expect(firstRow).toBeVisible()
|
||||
|
||||
// Get the userId text before clicking
|
||||
const userIdCell = firstRow.getByRole('cell').nth(1) // Assuming userId is 2nd column
|
||||
const userIdText = await userIdCell.textContent()
|
||||
// Extract the displayed userId from the monospace span inside the 2nd column cell.
|
||||
const userIdSpan = firstRow.getByRole('cell').nth(1).locator('span.font-mono')
|
||||
const expectedUserId = (await userIdSpan.textContent())?.trim() ?? ''
|
||||
expect(expectedUserId.length).toBeGreaterThan(0)
|
||||
|
||||
// Click the copy button
|
||||
const copyButton = firstRow.getByRole('button', { name: /copy/i }).first()
|
||||
await copyButton.click()
|
||||
|
||||
// Wait for clipboard to update
|
||||
await page.waitForTimeout(100)
|
||||
// CopyButton flips its label to "Copied" once the clipboard write resolves; wait for that.
|
||||
await expect(firstRow.getByRole('button', { name: /copied/i })).toBeVisible()
|
||||
|
||||
// Verify clipboard content
|
||||
const clipboardText = await page.evaluate(() => navigator.clipboard.readText())
|
||||
expect(clipboardText).toBeTruthy()
|
||||
expect(clipboardText.length).toBeGreaterThan(0)
|
||||
expect(clipboardText).toBe(expectedUserId)
|
||||
})
|
||||
|
||||
test('copy button shows feedback after clicking', async ({ page }) => {
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
|
||||
const firstRow = page.getByRole('row').nth(1)
|
||||
await expect(firstRow).toBeVisible()
|
||||
|
||||
const copyButton = firstRow.getByRole('button', { name: /copy/i }).first()
|
||||
await copyButton.click()
|
||||
|
||||
// Check for "Copied" feedback
|
||||
await expect(page.getByText(/copied/i)).toBeVisible({ timeout: 2000 })
|
||||
await expect(firstRow.getByRole('button', { name: /copied/i })).toBeVisible()
|
||||
})
|
||||
|
||||
test('userId column persists after search/filter', async ({ page }) => {
|
||||
test('userId column persists after triggering a search', async ({ page }) => {
|
||||
const searchInput = page.getByLabel('Search users')
|
||||
await expect(searchInput).toBeVisible()
|
||||
|
||||
// Trigger the actual search by clicking the Search button and wait for the API response,
|
||||
// rather than relying on debounced input.
|
||||
await searchInput.fill('admin')
|
||||
await Promise.all([
|
||||
page.waitForResponse(
|
||||
(resp) => resp.url().includes('/api/v1/admin/users') && resp.status() === 200,
|
||||
),
|
||||
page.getByRole('button', { name: 'Search', exact: true }).click(),
|
||||
])
|
||||
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
})
|
||||
|
||||
// Perform a search if search input exists
|
||||
const searchInput = page.getByPlaceholder(/search/i).first()
|
||||
if (await searchInput.isVisible()) {
|
||||
await searchInput.fill('test')
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
test('userId column persists after applying a status filter', async ({ page }) => {
|
||||
// Open the status filter Select and pick "Active". This exercises the filter path that the
|
||||
// previous test version skipped entirely.
|
||||
await page.getByLabel('Status filter').click()
|
||||
await Promise.all([
|
||||
page.waitForResponse(
|
||||
(resp) => resp.url().includes('/api/v1/admin/users') && resp.status() === 200,
|
||||
),
|
||||
page.getByRole('option', { name: 'Active' }).click(),
|
||||
])
|
||||
|
||||
// Verify userId column still exists
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
})
|
||||
|
||||
test('userId column persists across pagination', async ({ page }) => {
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
|
||||
// Check if pagination exists
|
||||
const nextButton = page.getByRole('button', { name: /next/i })
|
||||
const isReachable = (await nextButton.isVisible()) && (await nextButton.isEnabled())
|
||||
test.skip(!isReachable, 'Pagination not reachable with current data set')
|
||||
|
||||
if (await nextButton.isVisible() && await nextButton.isEnabled()) {
|
||||
await nextButton.click()
|
||||
await page.waitForTimeout(500)
|
||||
await Promise.all([
|
||||
page.waitForResponse(
|
||||
(resp) => resp.url().includes('/api/v1/admin/users') && resp.status() === 200,
|
||||
),
|
||||
nextButton.click(),
|
||||
])
|
||||
|
||||
// Verify userId column still exists on next page
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
} else {
|
||||
test.skip()
|
||||
}
|
||||
await expect(page.getByRole('columnheader', { name: 'User ID' })).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue