skillhub/web/e2e/batch-import-members.spec.ts
dongmucat 068cdaf84c test(namespace): add batch import members E2E tests
Adds Playwright E2E tests for batch member import dialog:
- Opens dialog and verifies upload step UI elements
- Uploads CSV with mixed valid/invalid rows and verifies preview with validation messages

Uses admin context pattern to create namespace and add test user as ADMIN member.
2026-04-22 16:36:18 +08:00

108 lines
4.6 KiB
TypeScript

import { writeFileSync } from 'node:fs'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { expect, test } from '@playwright/test'
import { setEnglishLocale } from './helpers/auth-fixtures'
import { loginWithCredentials, registerSession } from './helpers/session'
import { E2eTestDataBuilder } from './helpers/test-data-builder'
function getAdminCredentials() {
const username = process.env.E2E_ADMIN_USERNAME ?? process.env.BOOTSTRAP_ADMIN_USERNAME ?? 'admin'
const password = process.env.E2E_ADMIN_PASSWORD ?? process.env.BOOTSTRAP_ADMIN_PASSWORD ?? 'ChangeMe!2026'
return { username, password }
}
function createCsvFile(content: string): { filePath: string; cleanup: () => void } {
const tempDir = mkdtempSync(path.join(tmpdir(), 'skillhub-e2e-csv-'))
const filePath = path.join(tempDir, 'members.csv')
writeFileSync(filePath, content, 'utf8')
return {
filePath,
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
}
}
test.describe('Batch Import Members (Real API)', () => {
test.beforeEach(async ({ page }, testInfo) => {
await setEnglishLocale(page)
await registerSession(page, testInfo)
})
test('opens batch import dialog and shows upload step', async ({ page, browser }, testInfo) => {
const credentials = await registerSession(page, testInfo)
const builder = new E2eTestDataBuilder(page, testInfo)
await builder.init()
const adminContext = await browser.newContext()
const adminPage = await adminContext.newPage()
const adminBuilder = new E2eTestDataBuilder(adminPage, testInfo)
try {
await loginWithCredentials(adminPage, getAdminCredentials(), testInfo)
await adminBuilder.init()
const namespace = await adminBuilder.createNamespace('e2e-batch')
const candidates = await adminBuilder.searchNamespaceMemberCandidates(namespace.slug, credentials.username)
const matched = candidates.find((c) => c.userId === credentials.username || c.displayName === credentials.username) ?? candidates[0]
if (matched) {
await adminBuilder.addNamespaceMember(namespace.slug, matched.userId, 'ADMIN')
}
await page.goto(`/dashboard/namespaces/${namespace.slug}/members`)
await expect(page.getByRole('heading', { name: 'Member Management' })).toBeVisible()
const batchButton = page.getByRole('button', { name: 'Batch Import' })
await expect(batchButton).toBeVisible()
await batchButton.click()
await expect(page.getByText('Batch Import Members')).toBeVisible()
await expect(page.getByText('Download CSV template')).toBeVisible()
await expect(page.getByText('Drag a CSV file here')).toBeVisible()
} finally {
await builder.cleanup()
await adminBuilder.cleanup()
await adminContext.close()
}
})
test('uploads CSV and shows preview with validation', async ({ page, browser }, testInfo) => {
const credentials = await registerSession(page, testInfo)
const builder = new E2eTestDataBuilder(page, testInfo)
await builder.init()
const csv = createCsvFile('userId,role\nuser-valid-1,MEMBER\n,ADMIN\nuser-valid-2,BADROLE\n')
const adminContext = await browser.newContext()
const adminPage = await adminContext.newPage()
const adminBuilder = new E2eTestDataBuilder(adminPage, testInfo)
try {
await loginWithCredentials(adminPage, getAdminCredentials(), testInfo)
await adminBuilder.init()
const namespace = await adminBuilder.createNamespace('e2e-batch')
const candidates = await adminBuilder.searchNamespaceMemberCandidates(namespace.slug, credentials.username)
const matched = candidates.find((c) => c.userId === credentials.username || c.displayName === credentials.username) ?? candidates[0]
if (matched) {
await adminBuilder.addNamespaceMember(namespace.slug, matched.userId, 'ADMIN')
}
await page.goto(`/dashboard/namespaces/${namespace.slug}/members`)
await page.getByRole('button', { name: 'Batch Import' }).click()
await expect(page.getByText('Batch Import Members')).toBeVisible()
const fileInput = page.locator('input[type="file"][accept=".csv"]')
await fileInput.setInputFiles(csv.filePath)
await expect(page.getByText('Preview (3 rows)')).toBeVisible()
await expect(page.getByText('1 valid, 2 invalid')).toBeVisible()
await expect(page.getByText('Missing user ID')).toBeVisible()
await expect(page.getByText('Invalid role')).toBeVisible()
} finally {
csv.cleanup()
await builder.cleanup()
await adminBuilder.cleanup()
await adminContext.close()
}
})
})